Skip to content

UVM RAL · Chapter 4 · Accessing Registers

Backdoor Access & HDL Paths

Backdoor access is only as reliable as the HDL path that locates a register's flip-flops in the RTL hierarchy, and on a real design that path is more than one string. It composes from parts: a block-level root at the top of the hierarchy, sub-block paths that stack down the tree the same way address offsets do, and the register's own path. A common surprise is that a register is often not a single RTL signal at all. Its fields may live in separate flops, so you map each bit range to its own signal with a slice. This lesson walks the HDL path structure end to end, and shows what breaks when a split register is given one path and backdoor access reaches only part of it.

Foundation12 min readUVM RALBackdoorhdl_pathSlicesHierarchyHDL

Chapter 4 · Section 4.7 · Accessing Registers

1. Why Should I Learn This?

The backdoor's reliability is entirely a function of its HDL path, and on a real design that path is a composed, multi-part thing that is easy to get partly right and silently wrong. A path that resolves for a top-level register may be wrong for a sub-block register whose relative path was never set; a path that works for a register in one RTL signal reaches only part of a register whose fields are separate flops. These are not exotic cases — split registers and deep hierarchies are the norm — so understanding the path structure is what makes backdoor access work on anything beyond a toy.

Learning how HDL paths are rooted, composed down the hierarchy, and sliced across RTL signals lets you configure backdoor access correctly for real registers, reason about why a peek or poke reaches the wrong (or partial) storage, and use the built-in path-check sequence to catch mistakes. It is the structural depth behind the backdoor intuition of 4.3.

2. Industry Story — the register that lived in three flip-flops

A block has a CTRL register whose fields the RTL implements as separate flops — enable_q (1 bit), mode_q (2 bits), prio_q (4 bits) — a completely ordinary coding style. The verification engineer configures the register's HDL path with a single add_hdl_path("ctrl_q"), assuming, as with most registers, that the whole register lives in one signal named ctrl_q. There is no ctrl_q; the register is spread across the three field flops.

Backdoor access to CTRL now misbehaves in a way that is hard to read: depending on how the tool resolves the missing (or partially-matching) signal, a peek returns X or a stale partial value, and a poke writes to nothing or to only part of the register, so a scenario that pokes CTRL to a specific value leaves mode and prio untouched while maybe setting enable. The corner case the poke was meant to set up never fully materializes, and a peek used to confirm it reads a value that is right in one field and wrong in the others. The engineer spends a day assuming the register model or the RTL is wrong before discovering the register is split across three signals and the single HDL path could only ever reach one of them. The fix is to describe the register the way the RTL actually implements it — a slice per field flop. The lesson: a register is not always one RTL signal; when its fields are separate flops, a single HDL path reaches only part of it, and you must add a slice mapping each bit range to the signal that holds it.

3. Concept — the parts of an HDL path

An HDL path locates a register's storage in the RTL, and it is built from composing parts:

  • Block-level root. set_hdl_path_root("tb.dut") on the top block sets the base of the RTL hierarchy — where the design sits in the testbench. Everything below is relative to it.
  • Sub-block relative paths. When a sub-block is configured, it is given a path relative to its parent (e.g., the timer sub-block at u_timer). These compose down the hierarchy, exactly like address offsets in a map (3.2): a register in the timer sub-block of the soc block resolves to tb.dut + u_timer + the register's path.
  • Register path. add_hdl_path("ctrl_q") gives the register its signal(s) relative to its block. Simple registers that live in one RTL signal need only this.
  • Slices. add_hdl_path_slice("enable_q", 0, 1) maps a bit range of the register (lsb, width) to a specific RTL signal. A register whose fields are separate flops uses one slice per field, together covering all its bits. A slice says 'bits [lsb +: width] of this register live in this RTL signal.'
  • Named / abstract paths. A register can carry multiple named HDL paths (e.g., "RTL" and "GATE") for different views of the design; backdoor access selects one.

The composition mirrors the address hierarchy: the full path to a register is root + sub-block paths down the tree + the register's own path (or slices). Here is a split register mapped to its RTL signals by slices:

CTRL register fields mapped to separate RTL signals via HDL path slicesCTRL — a split register, one slice per field flopCTRL — a split register, one slice per field flopHIGHHIGHLOWLOW[31:8] unusedtied 0 — no flopbit 31[7:4] prio -> prio_qadd_hdl_path_slice('prio_q', 4, 4)bit 7[3:2] mode -> mode_qadd_hdl_path_slice('mode_q', 2, 2)bit 3[0] enable -> enable_qadd_hdl_path_slice('enable_q', 0, 1)bit 0
Figure 1 — a CTRL register whose fields are implemented as SEPARATE RTL flip-flops, and the slices that map each bit range to its signal. There is no single 'ctrl_q' holding all 32 bits; enable lives in enable_q, mode in mode_q, prio in prio_q. add_hdl_path_slice maps [0 +: 1] to enable_q, [2 +: 2] to mode_q, [4 +: 4] to prio_q. A single add_hdl_path could reach at most one of these signals; the backdoor needs a slice per field flop to read or write the whole register.

4. Mental Model — the HDL path is GPS coordinates, and a split register has several

5. Working Example — root, composed paths, and slices for a split register

The block sets its root, sub-blocks carry relative paths, and a split register is described by slices:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Top block: set the RTL root once. Sub-blocks carry relative paths that compose.
soc.set_hdl_path_root("tb.dut");                 // base of the RTL hierarchy
// (a sub-block is configured with its relative path, e.g. timer at "u_timer",
//  so timer registers resolve to tb.dut.u_timer.<register path> automatically.)
 
// A simple register that lives in ONE RTL signal:
status.add_hdl_path_slice("status_q", 0, 32);    // whole register in status_q
 
// A SPLIT register whose fields are separate flops — one slice per flop:
ctrl.add_hdl_path_slice("enable_q", 0, 1);       // bits [0]   live in enable_q
ctrl.add_hdl_path_slice("mode_q",   2, 2);       // bits [3:2] live in mode_q
ctrl.add_hdl_path_slice("prio_q",   4, 4);       // bits [7:4] live in prio_q

With the slices in place, a single backdoor access reaches the whole register — RAL reads or writes each slice's signal and assembles the register value:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
uvm_status_e s;  logic [31:0] got;
 
ctrl.poke(s, (5<<4) | (2<<2) | (1<<0));   // backdoor: writes prio_q=5, mode_q=2, enable_q=1
if (s != UVM_IS_OK) `uvm_error("BKD", "poke failed — check hdl_path slices")
 
ctrl.peek(s, got);                        // backdoor: reads all three flops, assembles the value
// 'got' now holds the full register (prio=5, mode=2, enable=1), gathered from three signals.

RAL splits the backdoor access across the slices exactly as the RTL splits the register, so the whole register round-trips. The next section shows what happens when the register is described as a single signal it does not have.

6. Debugging Session — the split register with one HDL path

1

A register split across separate RTL flops given a single HDL path makes backdoor access reach only part of it, reading and writing garbage for the rest

SPLIT REGISTER, ONE PATH
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// CTRL's fields are separate flops (enable_q, mode_q, prio_q), but it is given ONE path:
ctrl.add_hdl_path("ctrl_q");     // BUG: there is no 'ctrl_q'; the register is split across 3 flops
// Backdoor access can reach at most one signal, so it misses most of the register:
ctrl.poke(s, (5<<4)|(2<<2)|(1<<0));   // pokes nothing / only part — mode_q and prio_q untouched
ctrl.peek(s, got);                    // reads X or a partial value; fields other than one are wrong
Symptom

Backdoor setup of CTRL does not fully take: after a poke, some fields hold the intended value and others are still at reset, so a scenario that needed the whole register set up never fully materializes. A confirming peek returns a value that is correct in one field and wrong (or X) in the others — a partial, inconsistent read that looks like intermittent corruption. Frontdoor access works fine (the bus does not care how the register is implemented in RTL), so the problem appears only through the backdoor, which makes it look like a backdoor-mechanism or tool issue rather than a path-description one.

Root Cause

The register is split across multiple RTL signals — its fields are separate flip-flops — but its HDL path describes it as a single signal (ctrl_q) that either does not exist or holds only some of the bits. Backdoor access follows the HDL path to reach the register's storage, and a single path can reach only one signal, so it touches at most one field's flop and misses the others entirely: a poke writes only the reachable bits, a peek reads only them (and X/garbage for the unresolved rest). The register model, the frontdoor, and the RTL are all correct; the HDL path simply does not describe how the register is physically implemented. Because the frontdoor is unaffected (it addresses the register on the bus, not by RTL signal), the fault shows up only through the backdoor.

Fix

Describe the register the way the RTL implements it — one slice per field flop:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ctrl.add_hdl_path_slice("enable_q", 0, 1);
ctrl.add_hdl_path_slice("mode_q",   2, 2);
ctrl.add_hdl_path_slice("prio_q",   4, 4);

Now a single peek/poke reaches all three signals and the whole register round-trips. Verify the paths rather than trusting them: run uvm_reg_mem_hdl_paths_seq, which resolves every register's HDL path (all its slices) and round-trips backdoor against frontdoor, catching split registers with incomplete paths across the whole model. The rule the bug teaches: a register is not always one RTL signal; when its fields are separate flops, a single HDL path reaches only one of them, so map each bit range to its signal with add_hdl_path_slice — and if backdoor access is partial while the frontdoor is fine, suspect an incompletely-described split register.

7. Common Mistakes

  • A single HDL path for a split register. Backdoor reaches one field flop and misses the rest — partial reads and writes.
  • Not setting a sub-block's relative path. Backdoor to sub-block registers cannot compose the full path and fails.
  • Wrong slice bit ranges. A slice with the wrong lsb/width maps the register's bits to the wrong RTL bits — quiet corruption.
  • Forgetting the root. Without set_hdl_path_root, paths have no base and do not resolve.
  • Trusting paths instead of checking them. Run uvm_reg_mem_hdl_paths_seq to resolve and round-trip every path (and slice).

8. Industry Best Practices

  • Describe registers as the RTL implements them. One add_hdl_path for a single-signal register; one add_hdl_path_slice per field flop for a split one.
  • Compose paths down the hierarchy. Set the root once and each sub-block's relative path once, so a sub-block is relocatable in the RTL as in the map.
  • Verify every HDL path with the built-in sequence. uvm_reg_mem_hdl_paths_seq resolves and round-trips paths and slices across the model.
  • Keep abstract/named paths for multi-view designs. RTL vs gate-level views via named paths where you need them.
  • Suspect the path when backdoor differs from frontdoor. Frontdoor fine, backdoor partial or wrong is almost always an HDL-path/slice description problem.

9. Interview / Review Questions

10. Key Takeaways

  • The backdoor locates a register by its HDL path, built from composing parts: a block-level root (set_hdl_path_root), sub-block relative paths that compose down the hierarchy (like addresses in 3.2), and the register path (add_hdl_path).
  • A register is not always one RTL signal — when its fields are separate flops, map each bit range to its signal with add_hdl_path_slice(signal, lsb, width), one slice per field flop.
  • Paths compose down the block hierarchy exactly as addresses do, so a sub-block is relocatable in the RTL as in the map.
  • The signature bug is a split register given a single HDL path: backdoor access reaches one field flop and misses the rest — partial, inconsistent reads and writes — while the frontdoor is unaffected.
  • Verify HDL paths with uvm_reg_mem_hdl_paths_seq (it resolves and round-trips every slice); backdoor-wrong-but-frontdoor-fine is almost always an HDL-path/slice description problem.