Advanced UVM

Chapter 6 of 6

Capstone: Assembling the Advanced Environment

Organize five chapters' worth of classes into one package, add a second scenario that never clears the interrupt -- proving COUNT keeps incrementing past the threshold while irq stays level and the monitor never double-reports -- and switch between it and chapter 5's scenario from the command line, the same factory-override trick uvm ch10 used, without touching run_phase.

Every piece of regfile_env already exists — axi_agent and irq_agent (chapters 2-3), the virtual sequencer coordinating them (chapter 4), the register model (chapter 5). Like uvm ch10, this chapter is closer to pure assembly than to new concepts: organize everything into one package, add a second scenario that genuinely exercises the environment differently, and switch between the two from the command line without rewriting anything that already works.

Organizing everything into a package

Same principle SV ch16 established and uvm ch10 reused: one class per file, `included into a single package, imported once by the top module.

`include "uvm_macros.svh"
 
package regfile_uvm_pkg;
  import uvm_pkg::*;
 
  `include "axil_regfile.sv"
  `include "axi4lite_if.sv"
  `include "irq_if.sv"
 
  `include "axi_txn.sv"
  `include "axi_driver.sv"
  `include "axi_monitor.sv"
  `include "axi_sequencer.sv"
  `include "axi_agent.sv"
  `include "axi_basic_seq.sv"
 
  `include "irq_txn.sv"
  `include "irq_monitor.sv"
  `include "irq_agent.sv"
  `include "irq_watcher.sv"
 
  `include "ctrl_reg.sv"
  `include "status_reg.sv"
  `include "data_reg.sv"
  `include "count_reg.sv"
  `include "regfile_reg_block.sv"
  `include "axi_reg_adapter.sv"
 
  `include "regfile_vsqr.sv"
  `include "regfile_env.sv"
  `include "regfile_virtual_seq.sv"
  `include "regfile_no_clear_seq.sv"
 
  `include "axi_smoke_test.sv"
  `include "regfile_no_clear_test.sv"
endpackage

Twenty-two files, five chapters — and every single one of them is either unchanged from when it was first introduced, or has exactly the one addition each later chapter called out explicitly (irq_monitor gained one line in chapter 4, regfile_vsqr/regfile_env each grew by one field per chapter). Nothing here required going back and rewriting an earlier chapter's design.

A second scenario: never clearing the interrupt

Chapter 5's scenario always clears the interrupt right after confirming it asserts. This chapter's new sequence does the opposite on purpose — write past the threshold repeatedly, never clear, and check that both the register model and the interrupt behave exactly as they should under sustained assertion:

class regfile_no_clear_seq extends regfile_virtual_seq;
  `uvm_object_utils(regfile_no_clear_seq)
 
  function new(string name = "regfile_no_clear_seq");
    super.new(name);
  endfunction
 
  task body();
    uvm_event          irq_event = uvm_event_pool::get_global("irq_event");
    irq_txn             ev_txn;
    uvm_status_e        status;
    uvm_reg_data_t       rdata;
    regfile_reg_block   regmodel = p_sequencer.regmodel;
 
    write(8'h00, 32'h1);   // CTRL: ENABLE=1
    write(8'h08, 32'hAA);  // #1
    write(8'h08, 32'hBB);  // #2
    write(8'h08, 32'hCC);  // #3
 
    fork
      write(8'h08, 32'hDD);   // #4 -> crosses IRQ_THRESHOLD
      irq_event.wait_trigger();
    join
 
    if ($cast(ev_txn, irq_event.get_trigger_data()) && ev_txn.level == 1'b1)
      `uvm_info("VSEQ", "confirmed: irq asserted at write #4", UVM_LOW)
    else
      `uvm_error("VSEQ", "expected irq to assert at write #4")
 
    // Two more writes past the threshold, no IRQ_CLR anywhere -- irq
    // should stay asserted the whole time, and since irq_monitor only
    // reports on a *change* (chapter 3), neither write should produce a
    // second ASSERTED event.
    write(8'h08, 32'hEE);  // #5
    write(8'h08, 32'hFF);  // #6
 
    regmodel.COUNT.read(status, rdata, UVM_FRONTDOOR, regmodel.default_map, this);
    if (rdata == 32'd6)
      `uvm_info("VSEQ", $sformatf("confirmed: COUNT kept incrementing past the threshold to %0d, while irq stayed asserted (and silent) the whole time", rdata), UVM_LOW)
    else
      `uvm_error("VSEQ", $sformatf("expected COUNT=6 after 6 enabled DATA writes with no clear, got %0d", rdata))
  endtask
endclass

extends regfile_virtual_seq — not uvm_sequence directly — the same reason chapter 10 of uvm had my_sequence_exhaustive extends my_sequence: the inherited write() helper task and `uvm_declare_p_sequencer are reused as-is, and only body() changes. Running this scenario against the actual DUT confirms both halves precisely: COUNT really does reach 6, and exactly one irq event fires for the entire sequence — writes #5 and #6 happen while irq is already asserted, so irq_monitor's vif.cb.irq !== last check (chapter 3) never sees a change to report.

A second test, without rewriting run_phase

axi_smoke_test (chapters 2-5) is completely unchanged. The new test swaps which sequence class actually runs, using exactly the mechanism uvm ch6/ch10 already taught:

class regfile_no_clear_test extends axi_smoke_test;
  `uvm_component_utils(regfile_no_clear_test)
 
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
 
  function void build_phase(uvm_phase phase);
    regfile_virtual_seq::type_id::set_type_override(regfile_no_clear_seq::get_type());
    super.build_phase(phase);
  endfunction
endclass

axi_smoke_test's run_phase creates a regfile_virtual_seq via type_id::create(...) and runs it — unchanged, inherited byte-for-byte. The factory override set in build_phase, before that create() call ever executes, is what makes it actually construct a regfile_no_clear_seq instead — the identical trick uvm ch10 used for exhaustive_test, just one level removed (overriding a virtual sequence's type instead of a plain sequence's).

Running both scenarios

module tb_top;
  // ... same DUT, axi4lite_if, irq_if, config_db handoffs as chapters 1-5 ...
 
  initial begin
    uvm_config_db#(virtual axi4lite_if.tb_mp)::set(null, "*", "vif", axi_if);
    uvm_config_db#(virtual irq_if.mon_mp)::set(null, "*", "vif", irq_intf);
    run_test();
  end
endmodule

run_test() with no argument (uvm ch10) reads which test to build from +UVM_TESTNAME:

+UVM_TESTNAME=axi_smoke_test          // chapter 5's scenario: assert, then clear
+UVM_TESTNAME=regfile_no_clear_test   // this chapter's scenario: assert, stay asserted, keep counting

Same compiled environment, same DUT, same package — which scenario actually runs is a command-line flag, not a source edit.

What this track didn't cover

This environment coordinates two agents, drives a real register map through RAL, and synchronizes across a passive observer — genuinely more than a single-agent environment could. It still only checks the two scenarios this track wrote by hand. Nothing here measures whether these two scenarios are enough — whether every reachable value of COUNT, every response code, every ordering of writes relative to reads has actually been exercised — and nothing here has a systematic way to find a bug neither scenario happens to trigger. That's exactly dv-methodology's stated scope: coverage-driven judgment (measuring and directing thoroughness, not just checking two hand-picked scenarios) and debug intuition (a systematic way to chase down a failure once one of these checks actually fires). Same handoff shape as every capstone before this one: everything here gets reused, nothing gets thrown away, and the next track standardizes what's still being decided by hand.

Summary

  • Twenty-two files, one `include-based package — the same organizing principle from SV ch16 and uvm ch10, and every file is either unchanged since its introducing chapter or grew by exactly the one addition that chapter called out.
  • regfile_no_clear_seq extends regfile_virtual_seq reuses the inherited write() helper and p_sequencer declaration, changing only body() — the same shape as uvm ch10's my_sequence_exhaustive extends my_sequence.
  • Verified against the real DUT: COUNT reaches 6 after six enabled DATA writes with no clear, and exactly one irq event fires for the whole sequence — irq_monitor's change-only reporting (chapter 3) means writes past the threshold produce no duplicate events.
  • regfile_no_clear_test extends axi_smoke_test, overriding only build_phase to install a factory override before the inherited run_phase's create() call ever runs — run_phase itself is never touched, the identical trick uvm ch6/ch10 already taught.
  • run_test() with +UVM_TESTNAME switches between chapter 5's scenario and this chapter's from the command line, same compiled package.
  • This track checked two hand-picked scenarios; it never measured whether that's enough, and has no systematic way to chase a failure neither one happens to trigger. That's dv-methodology's job.

Why does regfile_no_clear_seq extend regfile_virtual_seq instead of uvm_sequence directly?

Running regfile_no_clear_seq against the real DUT, how many irq events does irq_monitor report in total, and why?

What system task/function, called with no argument, reads which test to construct from the +UVM_TESTNAME command-line plusarg?