UVM register model allows access to the DUT registers using the front door as we have seen before in the register environment.

This means that all register read and write operations in the environment are converted into bus transactions that get driven into the bus interface of the design, just like any other hardware component in a typical system would do. Because these accesses are sent as valid bus transactions, it consumes bus cycles. This is the recommended way to verify registers in any design as it closely resembles with what happens in the system.

What are backdoor accesses ?

UVM also allows backdoor accesses which uses a simulator database to directly access the signals within the DUT. Write operations deposit a value onto the signal and read operations sample the current value from the register signal. Since registers are the leaf nodes in a digital system, depositing a new value in the middle of any design transitions should not cause any problem. However, the control logic that becomes active during a typical bus write operation will not be active and hence any logic connected to this may not work as expected.

uvm register backdoor access

A backdoor access takes zero simulation time since the HDL values are directly accessed and do not consume a bus transaction. This is not the recommended way to verify register acesses in any design, but under certain circumstances, backdoor accesses help to enhance verification efforts using frontdoor mechanism.

Define backdoor HDL path

The simulator has to know the HDL path to the signal for which it is trying to do a backdoor operation. So, it is the responsibility of the user to specify the signal path in the register model for every register or for those registers which required backdoor access.

To enhance reuse and portability, the whole path is broken into multiple hierarchies. For example, the path to a given register can be split into the path from testbench top to subsystem top level, and from subsystem top to block level top and block level top to register block top and finally register block top to the given register. If the subsystem is reused in another design, only the section of HDL path from testbench top to the subsystem top level needs to be updated.


Click here to refresh the design example in frontdoor access !

We'll add HDL paths for the traffic controller design example seen in frontdoor access example, and then perform some backdoor access writes and reads. HDL paths are added during register model construction. Basically they are added by certain function calls and hence can be done later after register model construction.

It is not uncommon to see an automated flow developed by verification teams that parse the HDL path list provided by designers and then have the information added to an existent register model. The most logical way to understand how it is arranged is by a top down approach. First look at the testbench level, then at the DUT level, then at any subsystem level if present, then any register block levels and finally the registers.

In our simple traffic controller design, registers are directly placed in the DUT and hence the hierarchy to access these signals are simple.

// Assume that ral_cfg_ctl and other "uvm_reg" classes are already
// defined as in the frontdoor example

class ral_block_traffic_cfg extends uvm_reg_block;
	rand ral_cfg_ctl    ctrl;       // RW
	rand ral_cfg_timer  timer[2];   // RW
       ral_cfg_stat   stat;       // RO


	function new(string name = "traffic_cfg");
		super.new(name, build_coverage(UVM_NO_COVERAGE));

  virtual function void build();
    default_map = create_map("", 0, 4, UVM_LITTLE_ENDIAN, 0);
    ctrl = ral_cfg_ctl::type_id::create("ctrl",,get_full_name());
    ctrl.configure(this, null, "");
    // HDL path to this instance is "tb.DUT.ctl_reg"
    ctrl.add_hdl_path_slice("ctl_reg", 0, ctrl.get_n_bits());
    default_map.add_reg(this.ctrl, `UVM_REG_ADDR_WIDTH'h0, "RW", 0);
    timer[0] = ral_cfg_timer::type_id::create("timer[0]",,get_full_name());
    timer[0].configure(this, null, "");
    // HDL path to this instance is "tb.DUT.timer_0"
    timer[0].add_hdl_path_slice("timer_0", 0, timer[0].get_n_bits());
    default_map.add_reg(this.timer[0], `UVM_REG_ADDR_WIDTH'h4, "RW", 0);

    timer[1] = ral_cfg_timer::type_id::create("timer[1]",,get_full_name());
    timer[1].configure(this, null, "");
    // HDL path to this instance is "tb.DUT.timer_1"
    timer[1].add_hdl_path_slice("timer_1", 0, timer[1].get_n_bits());
    default_map.add_reg(this.timer[1], `UVM_REG_ADDR_WIDTH'h8, "RW", 0);

    stat = ral_cfg_stat::type_id::create("stat",,get_full_name());
    stat.configure(this, null, "");
    // HDL path from DUT to the status register will now be 
    // "tb.DUT.stat_reg" after the previous hierarchies are used 
    // for path concatenation
    stat.add_hdl_path_slice("stat_reg", 0, stat.get_n_bits());
    default_map.add_reg(this.stat, `UVM_REG_ADDR_WIDTH'hc, "RO", 0);

class ral_sys_traffic extends uvm_reg_block;
  rand ral_block_traffic_cfg cfg;

	function new(string name = "traffic");

	function void build();
    default_map = create_map("", 0, 4, UVM_LITTLE_ENDIAN, 0);
    cfg = ral_block_traffic_cfg::type_id::create("cfg",,get_full_name());
    // Since registers exist at the DUT level in our design, configure
    // "cfg" class to have an HDL path called "DUT". So complete path to 
    // DUT is now "tb.DUT"
    cfg.configure(this, "DUT");
    // Path to this top level regblock in our testbench environment is "tb"
    default_map.add_submap(this.cfg.default_map, `UVM_REG_ADDR_WIDTH'h0);


Example of backdoor access in sequence

class reg_backdoor_test extends base_test;
   `uvm_component_utils (reg_backdoor_test)
   function new (string name="reg_backdoor_test", uvm_component parent);
      super.new (name, parent);

   virtual task main_phase(uvm_phase phase);
      ral_sys_traffic   m_ral_model;
      uvm_status_e      status;
      int               rdata;


      m_env.m_reg_env.set_report_verbosity_level (UVM_HIGH);
      uvm_config_db#(ral_sys_traffic)::get(null, "uvm_test_top", "m_ral_model", m_ral_model);

      // Perform a normal frontdoor access -> write some data first and then read it back
      m_ral_model.cfg.timer[1].write(status, 32'h1234_5678);
      m_ral_model.cfg.timer[1].read(status, rdata);
      `uvm_info(get_type_name(), $sformatf("desired=0x%0h mirrored=0x%0h", m_ral_model.cfg.timer[1].get(), m_ral_model.cfg.timer[1].get_mirrored_value()), UVM_MEDIUM)
      // Perform a backdoor access for write and then do a frontdoor read 
      m_ral_model.cfg.timer[1].write(status, 32'ha5a5_a5a5, UVM_BACKDOOR);
      m_ral_model.cfg.timer[1].read(status, rdata);
      `uvm_info(get_type_name(), $sformatf("desired=0x%0h mirrored=0x%0h", m_ral_model.cfg.timer[1].get(), m_ral_model.cfg.timer[1].get_mirrored_value()), UVM_MEDIUM)

      // Perform a frontdoor write and then do a backdoor read
      m_ral_model.cfg.timer[1].write(status, 32'hface_face);
     // Wait for a time unit so that backdoor access reads update value
      m_ral_model.cfg.timer[1].read(status, rdata, UVM_BACKDOOR);
      `uvm_info(get_type_name(), $sformatf("desired=0x%0h mirrored=0x%0h", m_ral_model.cfg.timer[1].get(), m_ral_model.cfg.timer[1].get_mirrored_value()), UVM_MEDIUM)


  virtual task shutdown_phase(uvm_phase phase);

Simulation Log
ncsim> run
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_root.svh(392) @ 0: reporter [UVM/RELNOTES]
UVM_INFO @ 0: reporter [RNTST] Running test reg_backdoor_test...
UVM_WARNING /playground_lib/uvm-1.2/src/base/uvm_resource.svh(1416) @ 0: reporter [UVM/RSRC/NOREGEX] a resource with meta characters in the field name has been created "m_ral_model.cfg"
UVM_INFO env.sv(215) @ 0: [email protected]@m_reset_seq [RESET] Running reset ...
UVM_INFO test.sv(58) @ 110: uvm_test_top [reg_backdoor_test] desired=0x12345678 mirrored=0x12345678
UVM_INFO test.sv(63) @ 150: uvm_test_top [reg_backdoor_test] desired=0xa5a5a5a5 mirrored=0xa5a5a5a5
UVM_INFO test.sv(70) @ 191: uvm_test_top [reg_backdoor_test] desired=0xfaceface mirrored=0xfaceface
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 291: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---