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 twelve 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.
----------------------------------------------------------------------
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
`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; | |
// virtual interface | |
my_vif vif; | |
// The length of time, in ps, that reset will stay active | |
int unsigned reset_time_ps = 10; | |
// Minimum length of payload data | |
int unsigned min_payload_length = 5; | |
// Maximum length of payload data | |
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; | |
// Random varialbes | |
rand logic [`ADDR_WIDTH_IN_BITS - 1 : 0] start_addr; | |
rand logic [`DATA_WIDTH_IN_BITS - 1 : 0] data[]; | |
rand int unsigned payload_length; | |
// Non random variables | |
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 |
----------------------------------------------------------------------
----------------------------------------------------------------------
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//----------------------------------------------------------------------------- | |
// 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 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.
----------------------------------------------------------------------
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//----------------------------------------------------------------------------- | |
// 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 at that 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 at that time run_count is 1, so
at that time it will not do phase jumping.
Note: It is not good to use a phase jumping feature in case any of the
components of testbench don’t use the sub-phases of UVM.
Reference:
but some modification may be require in this code like we can use virtual my_interface instead of my_vif in some files
ReplyDeleteEx top file.
why we need agent.config.vif, we can instead use virtual interface in driver and drive on that?
ReplyDelete