Reset Testing using Phase Jump in UVM
In short: Reset testing checks that your whole verification environment reacts correctly when reset is applied in the middle of activity. UVM makes this clean with run-time sub-phases (pre_reset, reset, post_reset, configure, main, shutdown) that run in parallel with the run phase, plus a phase jump feature. You drive traffic in the main phase, assert reset partway through, then call phase.jump() back to the pre_reset phase so every component tidies up and restarts in order.
Reset is one of the first things a real chip must get right, and one of the easiest things to verify badly. If you apply reset while your scoreboard, driver, and monitor are mid-transaction and you have not synchronized them, you get a storm of errors that hide the real bug. This post shows how the UVM run-time sub-phases and the phase jump feature let you apply reset during traffic and have every component clean up gracefully. It ends with common mistakes and an FAQ.
Why reset testing needs care
During reset the parts of your environment must agree on what is happening. Scoreboards, drivers, and monitors need to tidy up, and any complex stimulus in flight must be stopped cleanly. If the sequencer keeps handing items to the driver while the driver is trying to stop, or the driver calls item_done on a sequence that was already killed, the simulator reports confusing errors. The order in which a simulator runs those threads is not fixed, so you need an explicit point where everyone stops together. That point is the pre_reset phase.
The UVM run-time sub-phases
Alongside the single run phase, UVM defines a set of run-time sub-phases that run in parallel and in order. They group the job of a test into natural stages:
pre_reset_phase,reset_phase,post_reset_phase: everything to do with reset.pre_configure_phase,configure_phase,post_configure_phase: setting up the design.pre_main_phase,main_phase,post_main_phase: driving the main stimulus.pre_shutdown_phase,shutdown_phase,post_shutdown_phase: settling the design after the main traffic.
Using these instead of one big run phase lets each component know exactly which stage it is in, which is what makes clean reset handling possible.
A simple way to picture it
Think of a school fire drill during a normal lesson. When the alarm rings, nobody keeps writing their test. The teacher stops the class, students put down pens, everyone files out in a set order, and only when the all-clear sounds does the lesson start again from a known point. The alarm is reset, the “stop and line up” step is the pre_reset phase where the sequencer stops its sequences and tells the driver, and restarting the lesson is the phase jump back to the beginning.
Without that drill, if everyone reacted at their own pace, you would get chaos in the hallway. The pre_reset phase is the practiced routine that keeps the restart orderly, which is exactly what your testbench needs when reset lands in the middle of traffic.
How phase jump drives the reset test
The idea is simple. In the main phase you start a sequence and, in parallel, watch for reset to assert. The first time through, when reset asserts you call phase.jump(uvm_pre_reset_phase::get()), which sends every component back to the pre_reset phase. There the sequencer stops its sequences and signals the driver, so the restart is clean. The second time through the main phase you finish normally without jumping.
The complete working example
Below is a full, self-contained testbench that puts this together: the interface, a config object, the sequence item, sequencer, driver, monitor, agent, sequences, the test with the phase jump, and the top module with a reset waveform. Read it top to bottom; the parts after it explain the key pieces.
1. Header, interface, and typedefs
`timescale 1ns/1ps
`include "uvm_macros.svh"
import uvm_pkg::*;
`define ADDR_WIDTH_IN_BITS 8
`define DATA_WIDTH_IN_BITS 8
// The pin-level interface, with an active-low reset rstn.
interface my_interface(input logic clk,
input logic rstn /* active-low reset */);
bit valid;
logic [`ADDR_WIDTH_IN_BITS - 1 : 0] start_addr;
reg [`DATA_WIDTH_IN_BITS - 1 : 0] data_reg;
wire [`DATA_WIDTH_IN_BITS - 1 : 0] data;
int unsigned length_in_bytes;
assign data = data_reg;
endinterface : my_interface
typedef virtual my_interface my_vif;
typedef class my_agent;2. Agent configuration object
// One config object per agent: virtual interface, reset time, and
// the payload length range for randomized items.
class my_agent_cfg extends uvm_object;
my_vif vif;
int unsigned reset_time_ps = 10; // how long reset stays active
int unsigned min_payload_length = 5;
int unsigned max_payload_length = 100;
uvm_active_passive_enum is_active = UVM_ACTIVE;
`uvm_object_utils_begin(my_agent_cfg)
`uvm_field_enum(uvm_active_passive_enum, is_active, UVM_DEFAULT)
`uvm_field_int(reset_time_ps, UVM_DEFAULT | UVM_DEC)
`uvm_field_int(min_payload_length, UVM_DEFAULT | UVM_DEC)
`uvm_field_int(max_payload_length, UVM_DEFAULT | UVM_DEC)
`uvm_object_utils_end
function new(string name = "my_agent_cfg");
super.new(name);
endfunction : new
// Sanity-check the settings before the run starts.
function void is_valid();
if (max_payload_length < min_payload_length)
`uvm_error(get_name(), $sformatf(
"max_payload_length (%0d) must be >= min_payload_length (%0d)",
max_payload_length, min_payload_length))
if (reset_time_ps == 0)
`uvm_error(get_name(), "reset_time_ps shall be greater than 0")
endfunction : is_valid
endclass : my_agent_cfg3. Sequence item
// The transaction: a start address plus a randomized data payload.
class my_seq_item extends uvm_sequence_item;
rand logic [`ADDR_WIDTH_IN_BITS - 1 : 0] start_addr;
rand logic [`DATA_WIDTH_IN_BITS - 1 : 0] data[];
rand int unsigned payload_length;
my_agent_cfg cfg;
`uvm_object_utils_begin(my_seq_item)
`uvm_field_int (start_addr, UVM_DEFAULT | UVM_HEX)
`uvm_field_int (payload_length, UVM_DEFAULT | UVM_DEC)
`uvm_field_array_int(data, UVM_DEFAULT | UVM_HEX)
`uvm_object_utils_end
constraint length_cn {
payload_length inside {[cfg.min_payload_length : cfg.max_payload_length]};
data.size == payload_length;
}
constraint order_cn { solve payload_length before data; }
function new(string name = "my_seq_item");
super.new(name);
endfunction : new
function string convert2string();
return $sformatf("start_addr:%0h, payload_length:%0d, data[0]:%0h, data[%0d]:%0h",
start_addr, payload_length, data[0], payload_length-1, data[payload_length-1]);
endfunction : convert2string
endclass : my_seq_item4. Sequencer
// A typed sequencer that keeps a handle to its parent agent.
class my_sequencer extends uvm_sequencer #(my_seq_item);
my_agent parent;
`uvm_component_utils(my_sequencer)
function new(string name = "my_sequencer", uvm_component parent = null);
super.new(name, parent);
if (parent == null)
`uvm_fatal(get_name(), "NULL parent handle in sequencer constructor")
else if (!$cast(this.parent, parent))
`uvm_fatal(get_name(), "Cast failed: provide a parent of type my_agent")
endfunction : new
endclass : my_sequencer5. Driver: waits for reset, drives in the main phase
The driver uses the sub-phases directly. In the reset phase it raises an objection and waits for reset to assert; in the post_reset phase it waits for reset to release; in the main phase it drives items while, in parallel, watching for a reset event from the agent.
class my_driver extends uvm_driver #(my_seq_item);
my_agent parent;
event reset_driver; // agent triggers this when reset asserts
`uvm_component_utils_begin(my_driver)
`uvm_component_utils_end
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
if (parent == null)
`uvm_fatal(get_name(), "NULL parent handle in driver constructor")
else if (!$cast(this.parent, parent))
`uvm_fatal(get_name(), "Cast failed: provide a parent of type my_agent")
endfunction : new
// On entering pre_reset, de-assert valid.
task pre_reset_phase(uvm_phase phase);
parent.cfg.vif.valid <= 1'b0;
endtask : pre_reset_phase
// Wait for reset to assert.
task reset_phase(uvm_phase phase);
phase.raise_objection(this);
wait (parent.cfg.vif.rstn == 1'b0);
`uvm_info(get_name(), "reset asserted", UVM_LOW)
phase.drop_objection(this);
endtask : reset_phase
// Wait for reset to release.
task post_reset_phase(uvm_phase phase);
phase.raise_objection(this);
wait (parent.cfg.vif.rstn == 1'b1);
`uvm_info(get_name(), "reset released", UVM_LOW)
phase.drop_objection(this);
endtask : post_reset_phase
// Drive stimulus; in parallel, stop if reset is signalled.
task main_phase(uvm_phase phase);
`uvm_info(get_name(), "enter main_phase", UVM_LOW)
forever begin
fork
begin : drive_thread
drive();
end
begin : reset_watch
wait (reset_driver.triggered);
`uvm_info(get_name(), "reset_driver event triggered", UVM_LOW)
end
join_any
disable fork;
end
endtask : main_phase
task drive();
my_seq_item tr;
seq_item_port.get_next_item(tr);
@(posedge parent.cfg.vif.clk);
parent.cfg.vif.valid <= 1'b1;
parent.cfg.vif.length_in_bytes <= tr.payload_length;
parent.cfg.vif.start_addr <= tr.start_addr;
for (int unsigned i = 0; i < tr.payload_length; i++) begin
parent.cfg.vif.data_reg <= tr.data[i];
@(posedge parent.cfg.vif.clk);
if (i == tr.payload_length - 1)
parent.cfg.vif.valid <= 1'b0;
end
seq_item_port.item_done(tr);
endtask : drive
endclass : my_driver6. Monitor: phase independent, watches its own reset
A monitor mimics a real device, so it should be phase independent: it watches the reset on its interface and resets itself, rather than relying on phase objections.
class my_monitor extends uvm_monitor;
my_agent parent;
`uvm_component_utils_begin(my_monitor)
`uvm_component_utils_end
function new(string name = "my_monitor", uvm_component parent = null);
super.new(name, parent);
if (parent == null)
`uvm_fatal(get_name(), "NULL parent handle in monitor constructor")
else if (!$cast(this.parent, parent))
`uvm_fatal(get_name(), "Cast failed: provide a parent of type my_agent")
endfunction : new
task reset_phase(uvm_phase phase);
@(posedge parent.cfg.vif.rstn);
`uvm_info(get_name(), "rstn de-assertion detected", UVM_LOW)
endtask : reset_phase
// Collect a packet; in parallel, drop it if reset asserts.
task main_phase(uvm_phase phase);
my_seq_item tr;
forever begin
fork
begin : collect
receive(tr);
end
begin : reset_watch
@(negedge parent.cfg.vif.rstn);
`uvm_info(get_name(), "reset asserted during reception", UVM_LOW)
end
join_any
disable fork;
tr = null; // drop any partial packet
end
endtask : main_phase
task receive(ref my_seq_item tr);
@(negedge parent.cfg.vif.clk);
if (parent.cfg.vif.valid == 1'b1) begin
tr = my_seq_item::type_id::create("tr");
tr.payload_length = parent.cfg.vif.length_in_bytes;
tr.start_addr = parent.cfg.vif.start_addr;
tr.data = new[tr.payload_length];
for (int unsigned i = 0; i < tr.payload_length; i++) begin
tr.data[i] = parent.cfg.vif.data;
if (i != tr.payload_length - 1)
@(negedge parent.cfg.vif.clk);
end
`uvm_info(get_name(), $sformatf("collected tr:\n%s", tr.convert2string()), UVM_LOW)
end
endtask : receive
endclass : my_monitor7. Agent: the pre_reset synchronization point
This is the key part for clean reset. In the pre_reset phase the agent tells the sequencer to stop all sequences, then triggers the driver’s reset event. Doing both here, in a known phase, avoids the race between the sequencer and the driver.
class my_agent extends uvm_agent;
my_agent_cfg cfg;
my_sequencer sqr;
my_driver drv;
my_monitor mon;
`uvm_component_utils_begin(my_agent)
`uvm_field_object(cfg, UVM_DEFAULT)
`uvm_field_object(sqr, UVM_DEFAULT)
`uvm_field_object(drv, UVM_DEFAULT)
`uvm_field_object(mon, UVM_DEFAULT)
`uvm_component_utils_end
function new(string name = "my_agent", uvm_component parent = null);
super.new(name, parent);
endfunction : new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(my_agent_cfg)::get(this, "", "cfg", cfg))
`uvm_fatal(get_name(), "cfg not set for this agent")
if (!uvm_config_db#(my_vif)::get(this, "", "vif", cfg.vif))
`uvm_fatal(get_name(), "virtual interface not passed to agent")
cfg.is_valid();
if (cfg.is_active == UVM_ACTIVE) begin
sqr = my_sequencer::type_id::create("sqr", this);
drv = my_driver::type_id::create("drv", this);
end
mon = my_monitor::type_id::create("mon", this);
endfunction : build_phase
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
if (cfg.is_active == UVM_ACTIVE)
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction : connect_phase
// The synchronization point: stop sequences, then signal the driver.
task pre_reset_phase(uvm_phase phase);
if (cfg.is_active == UVM_ACTIVE) begin
sqr.stop_sequences(); // reset the sequencer to idle
-> drv.reset_driver; // tell the driver reset happened
`uvm_info(get_name(), "reset_driver event triggered", UVM_LOW)
end
endtask : pre_reset_phase
endclass : my_agentIn plain words: the driver waits for reset in the reset phase, does what it needs on assertion, then in the post_reset phase waits for reset to release before moving to the main phase. Monitors, which mimic real devices, stay phase independent and watch their own reset. Because a simulator runs component threads in an unpredictable order, UVM asks that the sequencer stop its sequences first and the driver avoid calling item_done on a killed sequence. The agent’s pre_reset phase gives one clear place for that handshake.
8. Sequences
class my_base_sequence extends uvm_sequence #(my_seq_item);
int unsigned payload_length;
`uvm_object_utils(my_base_sequence)
`uvm_declare_p_sequencer(my_sequencer)
function new(string name = "my_base_sequence");
super.new(name);
endfunction : new
task body();
endtask : body
endclass : my_base_sequence
class my_sequence extends my_base_sequence;
`uvm_object_utils(my_sequence)
function new(string name = "my_sequence");
super.new(name);
endfunction : new
task body();
my_seq_item req, rsp;
`uvm_create(req)
req.cfg = p_sequencer.parent.cfg;
if (!req.randomize() with { payload_length == local::payload_length; })
`uvm_fatal(get_name(), "randomization failed")
`uvm_send(req)
get_response(rsp);
`uvm_info(get_name(), "got response from driver", UVM_LOW)
endtask : body
endclass : my_sequence9. Test: the phase jump
The test drives a sequence in the main phase and, in parallel, waits for reset. On the first pass (run_count == 0) it calls phase.jump back to pre_reset so the whole environment restarts cleanly. On the second pass it finishes normally.
class my_test extends uvm_test;
my_vif act_vif, psv_vif;
my_agent act_agent, psv_agent;
my_agent_cfg act_agent_cfg, psv_agent_cfg;
int unsigned run_count; // how many times main_phase has run
`uvm_component_utils_begin(my_test)
`uvm_field_int (run_count, UVM_DEFAULT | UVM_DEC)
`uvm_field_object(act_agent, UVM_DEFAULT)
`uvm_field_object(act_agent_cfg, UVM_DEFAULT)
`uvm_field_object(psv_agent, UVM_DEFAULT)
`uvm_field_object(psv_agent_cfg, UVM_DEFAULT)
`uvm_component_utils_end
function new(string name = "my_test", uvm_component parent = null);
super.new(name, parent);
endfunction : new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(my_vif)::get(this, "", "act_vif", act_vif))
`uvm_fatal(get_name(), "act_vif not set from top module")
if (!uvm_config_db#(my_vif)::get(this, "", "psv_vif", psv_vif))
`uvm_fatal(get_name(), "psv_vif not set from top module")
act_agent = my_agent::type_id::create("act_agent", this);
psv_agent = my_agent::type_id::create("psv_agent", this);
act_agent_cfg = my_agent_cfg::type_id::create("act_agent_cfg", this);
act_agent_cfg.is_active = UVM_ACTIVE;
psv_agent_cfg = my_agent_cfg::type_id::create("psv_agent_cfg", this);
psv_agent_cfg.is_active = UVM_PASSIVE;
uvm_config_db#(my_vif)::set(this, "act_agent", "vif", act_vif);
uvm_config_db#(my_vif)::set(this, "psv_agent", "vif", psv_vif);
uvm_config_db#(my_agent_cfg)::set(this, "act_agent", "cfg", act_agent_cfg);
uvm_config_db#(my_agent_cfg)::set(this, "psv_agent", "cfg", psv_agent_cfg);
endfunction : build_phase
task main_phase(uvm_phase phase);
phase.raise_objection(this);
if (run_count == 0) begin
fork
begin
my_sequence seq = my_sequence::type_id::create("seq");
seq.payload_length = 50;
seq.start(act_agent.sqr);
#1000;
end
begin
@(negedge act_vif.rstn);
`uvm_info(get_name(), "reset assertion detected", UVM_LOW)
end
join_any
disable fork;
end
else if (run_count == 1) begin
my_sequence seq = my_sequence::type_id::create("seq");
seq.payload_length = 40;
seq.start(act_agent.sqr);
#100;
end
if (run_count == 0) begin
// Jump back to pre_reset so every component restarts in order.
phase.get_objection().set_report_severity_id_override(
UVM_WARNING, "OBJTN_CLEAR", UVM_INFO);
phase.jump(uvm_pre_reset_phase::get());
end
else begin
phase.drop_objection(this);
end
run_count++;
endtask : main_phase
endclass : my_test10. Top module and reset waveform
module top();
bit clk;
logic rstn;
my_interface act_if (clk, rstn);
my_interface psv_if (clk, rstn);
initial run_test("my_test");
// The passive interface mirrors the active one.
assign psv_if.length_in_bytes = act_if.length_in_bytes;
assign psv_if.start_addr = act_if.start_addr;
assign psv_if.data_reg = act_if.data_reg;
assign psv_if.valid = act_if.valid;
initial begin
uvm_config_db#(virtual my_interface)::set(uvm_root::get(),
"uvm_test_top", "act_vif", act_if);
uvm_config_db#(my_vif)::set(uvm_root::get(),
"uvm_test_top", "psv_vif", psv_if);
end
initial forever #5 clk = !clk; // 100 MHz clock
// Reset waveform: assert, release, assert again, release again.
initial begin
#7 rstn = 1'b1;
#30 rstn = 1'b0;
#100 rstn = 1'b1;
#270 rstn = 1'b0;
#70 rstn = 1'b1;
#1000 $finish;
end
endmodule : topThe first time the test reaches the main phase, run_count is 0, so when reset asserts the test performs the phase jump back to pre_reset. The second time, run_count is 1, so it does not jump and the test finishes normally.
Common mistakes
- Doing reset handling only in the run phase, so components never agree on when to stop. Use the reset sub-phases instead.
- Forgetting the pre_reset handshake, so the sequencer keeps feeding the driver during reset and the log fills with errors.
- Calling
item_doneon a sequence that was already killed bystop_sequences(). Stop the sequencer first, then quiet the driver. - Making the monitor depend on phase objections. A monitor mimics a real device, so keep it phase independent and let it watch its own reset.
- Using phase jump when some components ignore the sub-phases. If any component only uses the run phase, jumping can leave it out of step.
- Leaving objections raised across a jump, which can stall the test. Handle the objection before you jump.
Key takeaways
- UVM run-time sub-phases (pre_reset through post_shutdown) let each component know which stage it is in.
- The agent’s pre_reset phase is the one clear place to stop sequences and signal the driver, which removes the race.
phase.jump(uvm_pre_reset_phase::get())restarts the environment in order so you can test reset mid-traffic.- Keep monitors phase independent; they should watch their own reset signal.
- Only use phase jump when every component honours the sub-phases.
Honesty note: this example follows a plain reading of the UVM LRM (IEEE 1800.2) and is written to show the flow rather than captured from a specific simulator run. Confirm behaviour on your own tool or on EDA Playground before relying on it.
Frequently asked questions
Related reading
- UVM phasing explained
- How UVM phasing is triggered
- How to terminate a UVM test with objections
- Typical UVM testbench architecture
What is reset testing in UVM?
It checks that the design and the whole verification environment react correctly when reset is applied, including in the middle of traffic. Components must stop cleanly and restart from a known state.
What are the UVM run-time sub-phases?
They are the phases that run in parallel with the run phase, in order: pre_reset, reset, post_reset, pre_configure, configure, post_configure, pre_main, main, post_main, pre_shutdown, shutdown, and post_shutdown.
What does phase.jump do?
It moves every component to the target phase. For reset testing you jump back to uvm_pre_reset_phase, so the environment restarts in order after reset is applied.
Why stop sequences in the pre_reset phase?
Because the simulator runs component threads in an unpredictable order. Stopping the sequencer first, then signalling the driver, avoids the driver calling item_done on a sequence that was already killed.
Should the monitor use phase objections for reset?
No. A monitor mimics a real device, so keep it phase independent. It should watch the reset on its own interface and reset itself.
When should I not use phase jump?
When any component only uses the run phase and ignores the sub-phases. Jumping can then leave that component out of step with the rest.
What is the difference between the run phase and the sub-phases?
The run phase is a single long phase. The sub-phases run in parallel with it and split the work into named stages, which is what makes synchronized reset handling practical.
How do I apply reset more than once in a test?
Use a counter like run_count. Jump back to pre_reset on the passes where you want another reset, and finish normally on the last pass.



