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:

  1. pre_reset_phase(), reset_phase(), post_reset_phase(): Phases involved in reset activity.
  2. pre_configure_phase(), configure_phase(), post_configure_phase(): Phases involved in configuring DUT.
  3. pre_main_phase(), main_phase(), post_main_phase(): Phases involved in driving main stimulus to the DUT.
  4. 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.