Reset Testing using Phase Jump in UVM
Reset testing is a crucial element of functional sign-off for any chip. The architectural components of the entire verification environment need to be correctly synchronized to be made aware of the reset condition. Scoreboards, drivers and monitors need to be tidied up, and the complex stimulus generation needs to be killed gracefully.
As we know, in UVM, there are sub phases parallel to run_phase:
- pre_reset_phase(), reset_phase(), post_reset_phase(): Phases involved in reset activity.
- pre_configure_phase(), configure_phase(), post_configure_phase(): Phases involved in configuring DUT.
- pre_main_phase(), main_phase(), post_main_phase(): Phases involved in driving main stimulus to the DUT.
- pre_shutdown_phase(), shutdown_phase and post_shutdown_phase(): Phases involved in settling down the DUT after driving main stimulus.
Using these phases instead of using only run_phase, we can achieve synchronization between all components of verification environment also easily test reset functionality.
In reset testing, user drives random sequence to the DUT and in between data transmission, reset is applied followed by driving restart sequence. We will see how the reset functionality could be easily tested using phases parallel to run_phase and phase jump feature of UVM.
Let’s go through complete example to understand how it is achieved using UVM phases and Phase jump feature.
`timescale 1ns/1ps
`include "uvm_macros.svh"
import uvm_pkg :: *;
`define ADDR_WIDTH_IN_BITS 8
`define DATA_WIDTH_IN_BITS 8
//-----------------------------------------------------------------------------
// Interface
//-----------------------------------------------------------------------------
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;
//-----------------------------------------------------------------------------
// Agent Configuration Class
//-----------------------------------------------------------------------------
class my_agent_cfg extends uvm_object;
my_vif vif;
// The length of time, in ps, that reset will stay active
int unsigned reset_time_ps = 10;
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
function void is_valid();
if (max_payload_length < min_payload_length) begin
`uvm_error(get_name(),
$sformatf("Value of max_payload_length is shall be greater or equal to value of min_payload_length, configured values of max_payload_length:%0d, min_payload_length:%0d",
max_payload_length, min_payload_length))
end
if (reset_time_ps <= 0) begin
`uvm_error(get_name(), $sformatf("reset_time_ps shall be greater than 0"))
end
endfunction : is_valid
endclass : my_agent_cfg
//-----------------------------------------------------------------------------
// Sequence Item Class
//-----------------------------------------------------------------------------
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();
convert2string = $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_item
//-----------------------------------------------------------------------------
// Sequencer Class
//-----------------------------------------------------------------------------
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) begin
`uvm_fatal(get_name(),
$sformatf("NULL handle is provided in parent argument of constructor"))
end
else begin
if (!$cast(this.parent, parent)) begin
`uvm_fatal(get_name(),
$sformatf("Casting Failed, provide parent of valid type"))
end
end
endfunction : new
endclass : my_sequencer
//-----------------------------------------------------------------------------
// Driver Class
//-----------------------------------------------------------------------------
class my_driver extends uvm_driver#(my_seq_item);
my_agent parent;
event reset_driver;
`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) begin
`uvm_fatal(get_name(),
$sformatf("NULL handle is provided in parent argument of constructor"))
end
else begin
if (!$cast(this.parent, parent)) begin
`uvm_fatal(get_name(),
$sformatf("Casting Failed, provide parent of valid type"))
end
end
endfunction : new
function void build_phase (uvm_phase phase);
super.build_phase(phase);
endfunction : build_phase
task pre_reset_phase(uvm_phase phase);
// De-assert valid signal after enterring into pre_reset_phase
parent.cfg.vif.valid <= 1'b0;
endtask : pre_reset_phase
// Wait for Reset to assert and
// Do something you want to do after reset is asserted
task reset_phase(uvm_phase phase);
phase.raise_objection(this);
wait (parent.cfg.vif.rstn == 1'b0);
`uvm_info(get_name(),
$sformatf("after waiting for rstn to be asserted"), UVM_LOW)
phase.drop_objection(this);
endtask : reset_phase
// Wait for Reset to de-assert
task post_reset_phase(uvm_phase phase);
phase.raise_objection(this);
wait (parent.cfg.vif.rstn == 1'b1);
`uvm_info(get_name(),
$sformatf("after waiting for rstn to be deasserted"), UVM_LOW)
phase.drop_objection(this);
endtask : post_reset_phase
// Drive stimulus on interface and in parallel
// wait for reset to be asserted
task main_phase(uvm_phase phase);
`uvm_info(get_name(), $sformatf("enter in main_phase"), UVM_LOW)
forever begin
fork
begin
drive();
end
begin
wait (reset_driver.triggered);
`uvm_info(get_name(),
$sformatf("after wait for reset_driver event to be triggered"), UVM_LOW)
end
join_any
disable fork;
end
endtask : main_phase
task drive();
my_seq_item tr;
`uvm_info(get_name(), $sformatf("Before get_next_item"), UVM_LOW)
seq_item_port.get_next_item(tr);
`uvm_info(get_name(),
$sformatf("After get_next_item tr:\n%s", tr.convert2string()), UVM_LOW)
@(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) begin
parent.cfg.vif.valid <= 1'b0;
end
end
seq_item_port.item_done(tr);
`uvm_info(get_name(), $sformatf("item_done is called"), UVM_LOW)
endtask : drive
endclass : my_driver
//-----------------------------------------------------------------------------
// Monitor Class
//-----------------------------------------------------------------------------
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) begin
`uvm_fatal(get_name(), $sformatf("NULL handle is provided in parent argument of constructor"))
end
else begin
if (!$cast(this.parent, parent)) begin
`uvm_fatal(get_name(), $sformatf("Casting Failed, provide parent of valid type"))
end
end
endfunction : new
function void build_phase (uvm_phase phase);
super.build_phase(phase);
endfunction : build_phase
task pre_reset_phase(uvm_phase phase);
endtask : pre_reset_phase
// Wait for reset to de-assert
task reset_phase(uvm_phase phase);
@(posedge parent.cfg.vif.rstn);
`uvm_info(get_name(), $sformatf("rstn deassertion detected"), UVM_LOW)
endtask : reset_phase
// Sample interface signals and form packet and
// in parallel wait for reset to be asserted
task main_phase(uvm_phase phase);
my_seq_item tr;
forever begin
fork
begin
receive(tr);
end
begin
@(negedge parent.cfg.vif.rstn);
`uvm_info(get_name(), $sformatf("rstn is asserted during reception of data"), UVM_LOW)
`uvm_info(get_name(), $sformatf(" tr:\n%s", tr.convert2string()), UVM_LOW)
end
join_any
disable fork;
// put tr into analysis port
tr = null;
end
endtask : main_phase
task receive(ref my_seq_item tr);
@(negedge parent.cfg.vif.clk);
if (parent.cfg.vif.valid == 1'b1) begin
`uvm_info(get_name(), $sformatf("valid is detected"), UVM_LOW)
tr = new("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) begin
@(negedge parent.cfg.vif.clk);
end
end
`uvm_info(get_name(), $sformatf("Complete tr:\n%s", tr.convert2string()), UVM_LOW)
end
endtask : receive
endclass : my_monitor
//-----------------------------------------------------------------------------
// Agent Class
//-----------------------------------------------------------------------------
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", this.cfg) &&
this.cfg != null) begin
`uvm_fatal(get_name(), $sformatf("configuration object of type:%s is not set for this instance", this.cfg.get_type()))
end
if (!uvm_config_db#(my_vif) :: get(this, "", "vif", this.cfg.vif)) begin
`uvm_fatal(get_name(), $sformatf("Interface is not passed to agent"))
end
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) begin
drv.seq_item_port.connect(sqr.seq_item_export);
end
endfunction : connect_phase
task pre_reset_phase(uvm_phase phase);
if(cfg.is_active == UVM_ACTIVE) begin
// Tells the sequencer to kill all sequences and child sequences currently
// operating on the sequencer, and remove all requests, locks and responses
// that are currently queued.
// This essentially resets the sequencer to an idle state.
sqr.stop_sequences();
// Indicates Driver that reset is asserted
->drv.reset_driver;
`uvm_info(get_name(), $sformatf("reset_driver event is triggered"), UVM_LOW)
end
endtask : pre_reset_phase
endclass : my_agent
As shown in the above code,
Driver is waiting for Reset to be asserted (in reset_phase) by raising objection and then perform action which user want on assertion of Reset signal and at last drop the objection and move to post_reset_phase. In post_reset_phase driver is waiting for Reset to de-assert and then move to main_phase. In main_phase driver drives stimulus on interface and in parallel to that wait for indication from agent about assertion of reset.
Components such as monitors that attach to signaling interfaces should be designed to be phase independent because they are intended to mimic other real devices in the system. These components should watch the reset signal associated with their interface and reset themselves accordingly.
You may find that the driver, the sequencer, and their currently running sequences will squawk with errors if they are not synchronized properly. UVM requires that the sequencer first stop its sequences and then the driver must be certain to not call item_done on any outstanding sequences. However, the order that a simulator executes threads in the various components is indeterminate. To synchronize these operations, the containing agent has a pre_reset_phase such as the above.
//-----------------------------------------------------------------------------
// Sequence Class
//-----------------------------------------------------------------------------
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;
my_seq_item rsp;
`uvm_create(req)
req.cfg = p_sequencer.parent.cfg;
if (!req.randomize() with {payload_length == local::payload_length;}) begin
`uvm_fatal(get_name(), $sformatf("Randomization failed"))
end
else begin
`uvm_info(get_name(),
$sformatf("After randomization tr in seq:\n%s", req.convert2string()), UVM_LOW)
end
`uvm_send(req)
get_response(rsp);
`uvm_info(get_name(),
$sformatf("Got response from driver"), UVM_LOW)
endtask : body
endclass : my_sequence
//-----------------------------------------------------------------------------
// Test Class
//-----------------------------------------------------------------------------
class my_test extends uvm_test;
my_vif act_vif;
my_vif psv_vif;
my_agent act_agent;
my_agent psv_agent;
my_agent_cfg act_agent_cfg;
my_agent_cfg psv_agent_cfg;
// The number of times the test has run so far
int unsigned run_count;
`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, "", $sformatf("act_vif"), act_vif)) begin
`uvm_fatal(get_name(), $sformatf("act_vif is not set for this class from top module"))
end
if (!uvm_config_db#(my_vif) :: get(this, "", $sformatf("psv_vif"), psv_vif)) begin
`uvm_fatal(get_name(), $sformatf("psv_vif is not set for this class from top module"))
end
act_agent = my_agent :: type_id :: create ($sformatf("act_agent"), this);
psv_agent = my_agent :: type_id :: create ($sformatf("psv_agent"), this);
act_agent_cfg = my_agent_cfg :: type_id :: create ($sformatf("act_agent_cfg"), this);
act_agent_cfg.is_active = UVM_ACTIVE;
psv_agent_cfg = my_agent_cfg :: type_id :: create ($sformatf("psv_agent_cfg"), this);
psv_agent_cfg.is_active = UVM_PASSIVE;
uvm_config_db#(my_vif) :: set(this, $sformatf("act_agent"), "vif", act_vif);
uvm_config_db#(my_vif) :: set(this, $sformatf("psv_agent"), "vif", psv_vif);
uvm_config_db#(my_agent_cfg) :: set(this, $sformatf("act_agent"), "cfg", act_agent_cfg);
uvm_config_db#(my_agent_cfg) :: set(this, $sformatf("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
// Run sequence
my_sequence seq;
seq = my_sequence :: type_id :: create ("seq");
seq.payload_length = 50;
seq.start(act_agent.sqr);
`uvm_info(get_name(), $sformatf("finished first seq"), UVM_LOW)
#1000;
`uvm_info(get_name(),
$sformatf("finished thread parallel to waiting for rstn"), UVM_LOW)
end
begin
@(negedge act_vif.rstn);
`uvm_info(get_name(), $sformatf("rstn assertion detected"), UVM_LOW)
end
join_any
disable fork;
end
else if (run_count == 1) begin
// Run sequence
my_sequence seq;
seq = my_sequence :: type_id :: create ("seq");
seq.payload_length = 40;
seq.start(act_agent.sqr);
#100;
end
if (run_count == 0) begin
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_test
//-----------------------------------------------------------------------------
// TB TOP module
//-----------------------------------------------------------------------------
module top();
bit clk;
logic rstn;
my_interface act_if (clk, rstn);
my_interface psv_if (clk, rstn);
initial begin
run_test("my_test");
end
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", $sformatf("act_vif"), act_if);
uvm_config_db#(my_vif) :: set(uvm_root::get(), "uvm_test_top", $sformatf("psv_vif"), psv_if);
end
initial begin
forever begin
#5 clk = !clk;
end
end
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 : top
When test enters main_phase initially first time run_count is 0, so on assertion of Reset test will do phase.jump method and move to pre_reset_phase from main_phase.
When test enters main_phase second time run_count is 1, so at that time it will not do phase jumping.
Note: It is not recommended to use a phase jumping feature in case any of the components of testbench don’t use the sub-phases of UVM.