UVM HDL access routines let a test read, deposit, force and release signals inside the design by their hierarchical path name, given as a string. They are built on the simulator's DPI/VPI interface, so the path can be chosen at run time, for example from a configuration file or a command line argument.
The same routines are used internally by the register model for backdoor accesses, as described in UVM Register Backdoor Access. They can also be called directly from any test, sequence or component.
7 min read | Intermediate Level
What You'll Learn
- How to check that an HDL path exists with
uvm_hdl_check_path() - How to read and deposit values with
uvm_hdl_read()anduvm_hdl_deposit() - How to force and release signals with
uvm_hdl_force(),uvm_hdl_force_time(),uvm_hdl_release()anduvm_hdl_release_and_read() - The difference between a deposit and a force
Example Design
Consider the small design hierarchy below. The testbench module tb instantiates a of module A, which instantiates b of module B, which contains the register cfg. The testbench also starts the test.
module B;
reg [3:0] cfg;
endmodule
module A;
B b();
endmodule
// Testbench module
module tb;
A a();
initial
run_test("base_test");
endmodule
The path to cfg is made of instance names, not module names, joined by dots: "tb.a.b.cfg". All routines take this path string as their first argument.
Values are passed with the type uvm_hdl_data_t, a 4-state vector of `UVM_HDL_MAX_WIDTH bits (1024 by default). Most routines return 1 on success and 0 on failure, for example when the path does not exist.
| Routine | What it does | Returns |
|---|---|---|
uvm_hdl_check_path() | Checks that the path exists | 1 or 0 |
uvm_hdl_read() | Reads the current value | 1 or 0 |
uvm_hdl_deposit() | Writes a value once, like a normal assignment | 1 or 0 |
uvm_hdl_force() | Forces a value until it is released | 1 or 0 |
uvm_hdl_force_time() | Forces a value for a given time, then releases it | Nothing (task) |
uvm_hdl_release() | Releases a forced value | 1 or 0 |
uvm_hdl_release_and_read() | Releases a forced value and reads the value after release | 1 or 0 |
uvm_hdl_check_path
import "DPI-C" context function int uvm_hdl_check_path (string path);
This function returns 1 if the given HDL path exists, else it returns 0. It is useful in build_phase() to catch wrong paths before the simulation starts.
class base_test extends uvm_test;
...
virtual function void build_phase (uvm_phase phase);
if (uvm_hdl_check_path ("tb.a.b.cfg"))
`uvm_info ("TEST", "Path tb.a.b.cfg exists", UVM_MEDIUM)
if (!uvm_hdl_check_path ("tb.a.def"))
`uvm_info ("TEST", "Path tb.a.def does not exist", UVM_MEDIUM)
endfunction
endclass
uvm_hdl_deposit
import "DPI-C" context function int uvm_hdl_deposit (string path, uvm_hdl_data_t value);
This function deposits the given value onto the HDL path and returns 1 if successful, else 0. A deposit works like a normal assignment. The value stays until the design changes it: a reg keeps it until its next procedural assignment, and a wire with a driver takes the driver's value again when the driver updates.
class base_test extends uvm_test;
...
virtual task run_phase (uvm_phase phase);
if (! uvm_hdl_deposit("tb.a.b.cfg", 4'h9))
`uvm_error ("TEST", "Deposit on tb.a.b.cfg failed")
endtask
endclass
uvm_hdl_force
import "DPI-C" context function int uvm_hdl_force (string path, uvm_hdl_data_t value);
This function forces the value on the given path and returns 1 if successful, else 0. Unlike a deposit, a force holds the value even when the design tries to change it. The signal stays forced until it is released with uvm_hdl_release() or uvm_hdl_release_and_read().
class base_test extends uvm_test;
...
virtual task run_phase (uvm_phase phase);
if (! uvm_hdl_force("tb.a.b.cfg", 4'h9))
`uvm_error ("TEST", "Force on tb.a.b.cfg failed")
endtask
endclass
uvm_hdl_force_time
task uvm_hdl_force_time (string path, uvm_hdl_data_t value, time force_time = 0);
This task forces the given value onto the HDL path for the time given by force_time, and then releases it automatically. If force_time is 0, it calls uvm_hdl_deposit() instead. Because it is a task, it consumes simulation time and has no return value, so success cannot be checked with an if statement.
class base_test extends uvm_test;
...
virtual task run_phase (uvm_phase phase);
uvm_hdl_force_time("tb.a.b.cfg", 4'h9, 30);
endtask
endclass
uvm_hdl_release_and_read
import "DPI-C" context function int uvm_hdl_release_and_read (string path, inout uvm_hdl_data_t value);
This function releases a value previously forced with uvm_hdl_force(), and returns 1 if successful, else 0. After the release, value holds the value of the HDL signal:
- For a
regor other variable, this is still the forced value, until the design assigns a new one. - For a wire, this is the value of its drivers, which takes effect immediately.
class base_test extends uvm_test;
...
virtual task run_phase (uvm_phase phase);
uvm_hdl_data_t rdata;
if (! uvm_hdl_release_and_read("tb.a.b.cfg", rdata))
`uvm_error ("TEST", "Release on tb.a.b.cfg failed")
endtask
endclass
uvm_hdl_release
import "DPI-C" context function int uvm_hdl_release (string path);
This function releases the value previously forced on the given HDL path with uvm_hdl_force(), and returns 1 if successful, else 0. The value after release follows the same rules as for uvm_hdl_release_and_read().
class base_test extends uvm_test;
...
virtual task run_phase (uvm_phase phase);
if (! uvm_hdl_release("tb.a.b.cfg"))
`uvm_error ("TEST", "Release on tb.a.b.cfg failed")
endtask
endclass
uvm_hdl_read
import "DPI-C" context function int uvm_hdl_read (string path, output uvm_hdl_data_t value);
This function reads the current value on the given HDL path into value, and returns 1 if successful, else 0.
class base_test extends uvm_test;
...
virtual task run_phase (uvm_phase phase);
uvm_hdl_data_t rdata;
if (! uvm_hdl_read("tb.a.b.cfg", rdata))
`uvm_error ("TEST", "Read from tb.a.b.cfg failed")
endtask
endclass
Quick Check
Q: A test calls uvm_hdl_force_time("tb.a.b.cfg", 4'h9, 30). Nothing else in the design assigns cfg. What does uvm_hdl_read() return for cfg at time 50?
Show Answer
4'h9. The force is released at time 30, but cfg is a reg. A released variable keeps the forced value until it is assigned again, and nothing assigns cfg.
Where is This Used?
Error injection tests often force a status or error signal deep inside a block for a few cycles, to check that the design and the testbench react correctly. Tests also use uvm_hdl_read() to sample internal state, such as an FSM state, for checks or coverage that cannot be seen at the ports.
Common Mistakes
Mistake #1: Compiling without signal access
The routines use the simulator's VPI interface, which many simulators restrict by default for speed. Enable read and write access to the signals with the simulator's access or debug options, or the routines fail even with correct paths. They also need the UVM DPI library, so they do not work if UVM is compiled with UVM_NO_DPI.
Mistake #2: Using module names in the path
Wrong: "tb.A.B.cfg"
Right: "tb.a.b.cfg"
Why? The path is a chain of instance names. Module names such as A and B are not part of it. Check every path once with uvm_hdl_check_path().
Mistake #3: Forgetting to release a force
A signal forced with uvm_hdl_force() stays forced for the rest of the simulation, unless it is released. Later parts of the test then see the design behave strangely. Use uvm_hdl_force_time() when the force should only last for a fixed time.
Practice Exercise
Challenge: Save and Restore a Value
Write a few lines for a run_phase() that read the current value of cfg, force it to 4'hF for 100 time units, release it, and finally deposit the saved value back so that cfg ends with its original value.
Hint
Because cfg is a reg, releasing the force does not restore the old value. A deposit after the release does.
Solution
uvm_hdl_data_t saved;
if (!uvm_hdl_read ("tb.a.b.cfg", saved))
`uvm_error ("TEST", "Read from tb.a.b.cfg failed")
if (!uvm_hdl_force ("tb.a.b.cfg", 4'hF))
`uvm_error ("TEST", "Force on tb.a.b.cfg failed")
#100;
if (!uvm_hdl_release ("tb.a.b.cfg"))
`uvm_error ("TEST", "Release on tb.a.b.cfg failed")
if (!uvm_hdl_deposit ("tb.a.b.cfg", saved))
`uvm_error ("TEST", "Deposit on tb.a.b.cfg failed")
Without the final deposit, cfg would still read 4'hF after the release.
Summary
In this tutorial, you learned:
- UVM HDL access routines read and write design signals by their path string, using DPI/VPI
- The path is made of instance names joined by dots, and can be checked with
uvm_hdl_check_path() - A deposit acts like one assignment, while a force holds the value until it is released
uvm_hdl_force_time()is a task that releases the force by itself after the given time- After a release, a variable keeps the forced value until it is assigned again, while a wire takes its drivers' value
- The simulator must allow signal access, and UVM must be compiled with its DPI library