The integrated UVM FIFO capstone

Assemble a complete UVM bench for a four-entry FIFO, follow one value from the test to the scoreboard, and end the run with a ledger, coverage and a verdict that a broken design cannot pass.

Lesson
U16.3
Time
35 min
Runs on
Verilator with UVM
Check
5 questions

Every piece of a FIFO bench passed its own small test: the driver, the monitor, the scoreboard and a reset sequence. Put together, the first test with a reset in the middle fails with a data error. The driver had reset the design, so the FIFO was empty again, but nobody told the scoreboard, which still expected the old values.

StepDesign holdsScoreboard expectsResult
Write 10, write 2010, 2010, 20-
Resetnothing10, 20 (not told)-
Write 303010, 20, 30-
Read-10 expectedUVM_ERROR [DATA] got 30, expected 10

Each part was right on its own. The bug lived between them: the reset reached the design but not the model.

The bench, piece by piece

The capstone bench uses every idea from this course. Read the table as a map of fifo_bench.svh, the file the example includes.

PieceClassJobCourse
Itemfifo_itemOne bus cycle: reset, write, read, dataU2, U4
Sequencesrandom_seq, directed_seqDecide which items to sendU4, U10
Driverfifo_driverTurns an item into pins for one clock; idles when no item waitsU4
Monitorfifo_monitorSamples the pins at each edge and publishes a fifo_sampleU7
Agentfifo_agentHolds sequencer, driver and monitor; passive builds the monitor onlyU11
Scoreboardfifo_scoreboardQueue model: checks every accepted read and both flagsU7, U10
Coveragefifo_coverageCounts the nine bins from U16.2U16.2
Configurationfifo_cfgInterface handle, active or passive, coverage on or offU2, U11
Testcapstone_testSets the configuration, a factory override and the planU1, U2

The parts of the capstone bench and where each was introduced.

The test never touches the environment's insides at build time. It fills a fifo_cfg object and puts it in the configuration database for env, which passes it to the agent. Everything below the test is reusable: another test changes only configure() and run_phase.

One value, end to end

Follow the write of one value, say 8'h3c, through the bench. Every hand-off is one you have seen in an earlier lesson.

  1. Sequence

    A sequence creates a fifo_item through the factory. The override returns a write_heavy_item. It calls start_item, randomizes it to a write of 3c, and calls finish_item.

  2. Driver

    At the falling edge the driver takes the item with try_next_item and sets wr and wdata. It waits for the rising edge, then calls item_done, which releases the sequence.

  3. Design

    At that rising edge the FIFO is not full, so it stores 3c and its count goes up by one.

  4. Monitor

    At the same edge the monitor reads the pins before the design updates them. It sees a write while not full, so it marks the sample as an accepted write.

  5. Scoreboard and coverage

    The analysis port delivers the sample to both subscribers. The scoreboard appends 3c to its queue, and coverage adds a hit to the new fill level.

  6. Later read

    When a read is accepted, the scoreboard pops the front of its queue and compares it with rdata. A difference is a DATA error.

The monitor decides what the design accepted from the pins alone, never from the driver's intent. That is why a reset counts in the scoreboard: the monitor sees rst high at an edge and publishes a reset sample. The scoreboard then moves every stored value into its canceled count, which fixes the hook's bug.

The test plan as one run

The capstone test runs five stages, each aimed at something the earlier stages cannot show.

The five stages of the capstone run
01234stage1 random2 reset3 empty read4 random5 drainitems241245aimfill, fullreset with dataread when emptyafter resetend emptymid-test reset

Random stages explore; directed stages hit what the random ones cannot, then leave the FIFO empty.

Stage 1 is random traffic. A factory override replaces every fifo_item with write_heavy_item, which never sends a read on its own, so the FIFO fills and writes get refused. Stage 2 resets the design while values are stored, and stage 3 reads the empty FIFO. Stage 4 checks that the bench still works after the reset. Stage 5 sends five reads, enough to empty a four-entry FIFO whatever it holds.

The override is one line in configure(). No sequence or component changed. That is the reason the bench creates every item through the factory.

A type override reaches every fifo_item the bench creates, the directed ones included. A first version of directed_seq randomized its reads with rd == 1 and wr == 0. Under the override every such call failed: six RAND errors, a drain that read nothing, and three values left stored. So directed_seq now assigns its fields, and no constraint applies to them.

The plan also fixes its own length. Stages 2, 3 and 5 send 7 items, and the two random stages send 24 each, so the driver must report 55 items. The test counts what it planned and compares that with the driver's count at the end.

A ledger that must balance

At the end, every value the design accepted must be accounted for exactly once. It was read and checked, canceled by a reset, or is still stored. The ledger is that sum: writes equal reads plus canceled plus stored.

The verdict in report_phase requires six things together. The driver drove every planned item, and at least one read was checked. Nothing is still stored, and the ledger balances. Coverage is 9 of 9, and there are no errors or fatals. Each condition catches a different kind of broken bench. A monitor that sees nothing fails the read count. A driver that hangs before item_done is different: finish_item never returns, the test keeps its objection and report_phase never runs. Only a timeout catches that, such as uvm_root::get().set_timeout(...) or a watchdog in tb.

A wrong flag would slip past a scoreboard that only compares data, because the monitor decides acceptance from the design's own full and empty. A FIFO that claimed to be full at three entries would refuse the fourth write, and the model would agree. So the scoreboard also checks both flags against its queue size at every edge.

Challenge the bench with a broken design

A bench is only trusted once you have seen it fail. Break the design on purpose, one fault at a time, and run the same test. Store bus.wdata + 1 and the first read must give a DATA error. Remove the reset of count, or narrow count to two bits, and the flag check must fire.

A broken design that still passes points to a gap: a missing check, a missing bin or a stage that never reaches the fault. Fix the bench, then run the fault again.

Keep each broken design as a small, named experiment that anyone can rerun. When the bench changes later, run the experiments again. A check that once caught a fault can stop catching it after a refactor, and only a rerun shows that.

Know what this bench does not show. It drives one clock domain and resets on a clock edge, so it says nothing about clock-domain crossings or asynchronous reset. Its queue model is written in SystemVerilog. A C model can replace it through DPI, as module 8 shows, if it is fed the same accepted samples and told about the same resets.

Try it

The run prints three lines of accounting before its verdict. Check the ledger by hand: writes must equal reads plus canceled plus stored, and stored must be 0 after the drain.

Now break the design in fifo_bench.svh. Change memory[wp] <= bus.wdata; to memory[wp] <= bus.wdata + 1; and look for DATA errors and a failed verdict. Restore it, then delete count <= 0; from the reset branch and see which check fires first.

RUNVerilator + UVM 2020.3.1 ยท simulator service
Edit the code, then run it
Expected among the UVM messages: items: 55 planned, 55 driven ledger: 24 writes = 21 reads + 3 canceled + 0 stored checks: 0 mismatches; coverage 9 of 9, holes: none TEST PASSED UVM_ERROR : 0
Read included file: fifo_bench.svh
// The four-entry FIFO and its UVM bench.
interface fifo_if (input bit clk);
  bit       rst = 1, wr = 0, rd = 0;
  bit [7:0] wdata = 0;
  wire [7:0] rdata;
  wire       full, empty;
endinterface

module fifo_dut (fifo_if bus);
  bit [7:0] memory[4];
  bit [1:0] wp = 0, rp = 0;
  bit [2:0] count = 0;
  wire push = bus.wr && !bus.full;     // a write is refused when full
  wire pop  = bus.rd && !bus.empty;    // a read is refused when empty
  assign bus.full  = (count == 4);
  assign bus.empty = (count == 0);
  assign bus.rdata = memory[rp];
  always @(posedge bus.clk)
    if (bus.rst) begin wp <= 0; rp <= 0; count <= 0; end
    else begin
      if (push) begin memory[wp] <= bus.wdata; wp <= wp + 1'b1; end
      if (pop) rp <= rp + 1'b1;
      count <= count + push - pop;
    end
endmodule

// One bus cycle the sequence asks for.
class fifo_item extends uvm_sequence_item;
  `uvm_object_utils(fifo_item)
  rand bit       reset, wr, rd;
  rand bit [7:0] data;
  constraint c_no_reset { reset == 0; }
  function new(string name = "fifo_item"); super.new(name); endfunction
endclass

// What the monitor saw at one clock edge.
class fifo_sample extends uvm_sequence_item;
  `uvm_object_utils(fifo_sample)
  bit       reset, push, pop, write_full, read_empty, full, empty;
  bit [7:0] wdata, rdata;
  function new(string name = "fifo_sample"); super.new(name); endfunction
endclass

class fifo_driver extends uvm_driver #(fifo_item);
  `uvm_component_utils(fifo_driver)
  virtual fifo_if vif;
  int items;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task run_phase(uvm_phase phase);
    forever begin
      @(negedge vif.clk);
      seq_item_port.try_next_item(req);
      if (req == null) begin                  // no item: an idle cycle
        vif.rst <= 0; vif.wr <= 0; vif.rd <= 0;
      end else begin
        vif.rst <= req.reset; vif.wr <= req.wr; vif.rd <= req.rd; vif.wdata <= req.data;
        @(posedge vif.clk);
        items++;
        seq_item_port.item_done();
      end
    end
  endtask
endclass

class fifo_monitor extends uvm_monitor;
  `uvm_component_utils(fifo_monitor)
  virtual fifo_if vif;
  uvm_analysis_port #(fifo_sample) ap;
  function new(string name, uvm_component parent); super.new(name, parent); ap = new("ap", this); endfunction
  task run_phase(uvm_phase phase);
    forever begin
      fifo_sample s;
      @(posedge vif.clk);                     // values just before the design updates
      s = fifo_sample::type_id::create("s");
      s.reset      = vif.rst;
      s.push       = !vif.rst && vif.wr && !vif.full;
      s.pop        = !vif.rst && vif.rd && !vif.empty;
      s.write_full = !vif.rst && vif.wr && vif.full;
      s.read_empty = !vif.rst && vif.rd && vif.empty;
      s.full  = vif.full;  s.empty = vif.empty;
      s.wdata = vif.wdata; s.rdata = vif.rdata;
      ap.write(s);
    end
  endtask
endclass

// Checks every accepted read against a queue model.
class fifo_scoreboard extends uvm_subscriber #(fifo_sample);
  `uvm_component_utils(fifo_scoreboard)
  bit [7:0] expected[$];
  int writes, reads, canceled, mismatches;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void write(fifo_sample t);
    if (t.reset) begin canceled += expected.size(); expected.delete(); return; end
    if (t.full != (expected.size() == 4) || t.empty != (expected.size() == 0)) begin
      mismatches++;
      `uvm_error("FLAGS", $sformatf("full=%0d empty=%0d with %0d values stored", t.full, t.empty, expected.size()))
    end
    if (t.pop) begin
      bit [7:0] want = expected.pop_front();
      reads++;
      if (t.rdata != want) begin
        mismatches++;
        `uvm_error("DATA", $sformatf("read %0d: got %02h, expected %02h", reads, t.rdata, want))
      end
    end
    if (t.push) begin expected.push_back(t.wdata); writes++; end
  endfunction
  function void check_phase(uvm_phase phase);
    if (expected.size() != 0) `uvm_error("PENDING", $sformatf("%0d values never read", expected.size()))
  endfunction
endclass

// Counts which situations the stimulus reached. Each name is one bin.
class fifo_coverage extends uvm_subscriber #(fifo_sample);
  `uvm_component_utils(fifo_coverage)
  string names[9] = '{"level 0", "level 1", "level 2", "level 3", "level 4",
                      "write when full", "read when empty", "write and read", "reset with data"};
  int hits[9];
  int level;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void write(fifo_sample t);
    if (t.reset) begin
      if (level > 0) hits[8]++;
      level = 0;
      return;
    end
    if (t.write_full) hits[5]++;
    if (t.read_empty) hits[6]++;
    if (t.push && t.pop) hits[7]++;
    level += t.push - t.pop;
    hits[level]++;
  endfunction
  function int covered();
    foreach (hits[i]) if (hits[i] > 0) covered++;
  endfunction
  function string holes();
    foreach (hits[i]) if (hits[i] == 0) holes = {holes, holes == "" ? "" : ", ", names[i]};
    if (holes == "") holes = "none";
  endfunction
endclass

// Settings the test chooses for the environment.
class fifo_cfg extends uvm_object;
  `uvm_object_utils(fifo_cfg)
  virtual fifo_if         vif;
  uvm_active_passive_enum is_active    = UVM_ACTIVE;
  bit                     has_coverage = 1;
  function new(string name = "fifo_cfg"); super.new(name); endfunction
endclass

class fifo_agent extends uvm_agent;
  `uvm_component_utils(fifo_agent)
  fifo_cfg                   cfg;
  uvm_sequencer #(fifo_item) sqr;
  fifo_driver                drv;
  fifo_monitor               mon;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    if (!uvm_config_db #(fifo_cfg)::get(this, "", "cfg", cfg))
      `uvm_fatal("NOCFG", "no fifo_cfg for the agent")
    mon = fifo_monitor::type_id::create("mon", this);
    mon.vif = cfg.vif;
    if (cfg.is_active == UVM_ACTIVE) begin
      sqr = uvm_sequencer #(fifo_item)::type_id::create("sqr", this);
      drv = fifo_driver::type_id::create("drv", this);
      drv.vif = cfg.vif;
    end
  endfunction
  function void connect_phase(uvm_phase phase);
    if (cfg.is_active == UVM_ACTIVE) drv.seq_item_port.connect(sqr.seq_item_export);
  endfunction
endclass

class fifo_env extends uvm_env;
  `uvm_component_utils(fifo_env)
  fifo_cfg        cfg;
  fifo_agent      agent;
  fifo_scoreboard sb;
  fifo_coverage   cov;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    if (!uvm_config_db #(fifo_cfg)::get(this, "", "cfg", cfg))
      `uvm_fatal("NOCFG", "no fifo_cfg for the environment")
    uvm_config_db #(fifo_cfg)::set(this, "agent", "cfg", cfg);
    agent = fifo_agent::type_id::create("agent", this);
    sb    = fifo_scoreboard::type_id::create("sb", this);
    if (cfg.has_coverage) cov = fifo_coverage::type_id::create("cov", this);
  endfunction
  function void connect_phase(uvm_phase phase);
    agent.mon.ap.connect(sb.analysis_export);
    if (cfg.has_coverage) agent.mon.ap.connect(cov.analysis_export);
  endfunction
endclass

// Builds the configuration and the environment. Tests extend this class.
class fifo_base_test extends uvm_test;
  `uvm_component_utils(fifo_base_test)
  fifo_cfg cfg;
  fifo_env env;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    cfg = fifo_cfg::type_id::create("cfg");
    if (!uvm_config_db #(virtual fifo_if)::get(this, "", "vif", cfg.vif))
      `uvm_fatal("NOVIF", "no virtual interface")
    configure(cfg);
    uvm_config_db #(fifo_cfg)::set(this, "env", "cfg", cfg);
    env = fifo_env::type_id::create("env", this);
  endfunction
  virtual function void configure(fifo_cfg cfg); endfunction   // tests change settings here
endclass

// Random traffic: n items, each a write, a read, both or neither.
class random_seq extends uvm_sequence #(fifo_item);
  `uvm_object_utils(random_seq)
  int n = 16;
  function new(string name = "random_seq"); super.new(name); endfunction
  task body();
    repeat (n) begin
      fifo_item it = fifo_item::type_id::create("it");
      start_item(it);
      if (!it.randomize()) `uvm_error("RAND", "randomize failed")
      finish_item(it);
    end
  endtask
endclass

Check yourself

Predict the outputThe script below runs on the capstone bench. The FIFO holds four values, refuses a write when full and a read when empty. What line does the $display print? Count a refused write or read under "refused".
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;
// The four-entry FIFO and its UVM bench.
interface fifo_if (input bit clk);
  bit       rst = 1, wr = 0, rd = 0;
  bit [7:0] wdata = 0;
  wire [7:0] rdata;
  wire       full, empty;
endinterface

module fifo_dut (fifo_if bus);
  bit [7:0] memory[4];
  bit [1:0] wp = 0, rp = 0;
  bit [2:0] count = 0;
  wire push = bus.wr && !bus.full;     // a write is refused when full
  wire pop  = bus.rd && !bus.empty;    // a read is refused when empty
  assign bus.full  = (count == 4);
  assign bus.empty = (count == 0);
  assign bus.rdata = memory[rp];
  always @(posedge bus.clk)
    if (bus.rst) begin wp <= 0; rp <= 0; count <= 0; end
    else begin
      if (push) begin memory[wp] <= bus.wdata; wp <= wp + 1'b1; end
      if (pop) rp <= rp + 1'b1;
      count <= count + push - pop;
    end
endmodule

// One bus cycle the sequence asks for.
class fifo_item extends uvm_sequence_item;
  `uvm_object_utils(fifo_item)
  rand bit       reset, wr, rd;
  rand bit [7:0] data;
  constraint c_no_reset { reset == 0; }
  function new(string name = "fifo_item"); super.new(name); endfunction
endclass

// What the monitor saw at one clock edge.
class fifo_sample extends uvm_sequence_item;
  `uvm_object_utils(fifo_sample)
  bit       reset, push, pop, write_full, read_empty, full, empty;
  bit [7:0] wdata, rdata;
  function new(string name = "fifo_sample"); super.new(name); endfunction
endclass

class fifo_driver extends uvm_driver #(fifo_item);
  `uvm_component_utils(fifo_driver)
  virtual fifo_if vif;
  int items;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task run_phase(uvm_phase phase);
    forever begin
      @(negedge vif.clk);
      seq_item_port.try_next_item(req);
      if (req == null) begin                  // no item: an idle cycle
        vif.rst <= 0; vif.wr <= 0; vif.rd <= 0;
      end else begin
        vif.rst <= req.reset; vif.wr <= req.wr; vif.rd <= req.rd; vif.wdata <= req.data;
        @(posedge vif.clk);
        items++;
        seq_item_port.item_done();
      end
    end
  endtask
endclass

class fifo_monitor extends uvm_monitor;
  `uvm_component_utils(fifo_monitor)
  virtual fifo_if vif;
  uvm_analysis_port #(fifo_sample) ap;
  function new(string name, uvm_component parent); super.new(name, parent); ap = new("ap", this); endfunction
  task run_phase(uvm_phase phase);
    forever begin
      fifo_sample s;
      @(posedge vif.clk);                     // values just before the design updates
      s = fifo_sample::type_id::create("s");
      s.reset      = vif.rst;
      s.push       = !vif.rst && vif.wr && !vif.full;
      s.pop        = !vif.rst && vif.rd && !vif.empty;
      s.write_full = !vif.rst && vif.wr && vif.full;
      s.read_empty = !vif.rst && vif.rd && vif.empty;
      s.full  = vif.full;  s.empty = vif.empty;
      s.wdata = vif.wdata; s.rdata = vif.rdata;
      ap.write(s);
    end
  endtask
endclass

// Checks every accepted read against a queue model.
class fifo_scoreboard extends uvm_subscriber #(fifo_sample);
  `uvm_component_utils(fifo_scoreboard)
  bit [7:0] expected[$];
  int writes, reads, canceled, mismatches;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void write(fifo_sample t);
    if (t.reset) begin canceled += expected.size(); expected.delete(); return; end
    if (t.full != (expected.size() == 4) || t.empty != (expected.size() == 0)) begin
      mismatches++;
      `uvm_error("FLAGS", $sformatf("full=%0d empty=%0d with %0d values stored", t.full, t.empty, expected.size()))
    end
    if (t.pop) begin
      bit [7:0] want = expected.pop_front();
      reads++;
      if (t.rdata != want) begin
        mismatches++;
        `uvm_error("DATA", $sformatf("read %0d: got %02h, expected %02h", reads, t.rdata, want))
      end
    end
    if (t.push) begin expected.push_back(t.wdata); writes++; end
  endfunction
  function void check_phase(uvm_phase phase);
    if (expected.size() != 0) `uvm_error("PENDING", $sformatf("%0d values never read", expected.size()))
  endfunction
endclass

// Counts which situations the stimulus reached. Each name is one bin.
class fifo_coverage extends uvm_subscriber #(fifo_sample);
  `uvm_component_utils(fifo_coverage)
  string names[9] = '{"level 0", "level 1", "level 2", "level 3", "level 4",
                      "write when full", "read when empty", "write and read", "reset with data"};
  int hits[9];
  int level;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void write(fifo_sample t);
    if (t.reset) begin
      if (level > 0) hits[8]++;
      level = 0;
      return;
    end
    if (t.write_full) hits[5]++;
    if (t.read_empty) hits[6]++;
    if (t.push && t.pop) hits[7]++;
    level += t.push - t.pop;
    hits[level]++;
  endfunction
  function int covered();
    foreach (hits[i]) if (hits[i] > 0) covered++;
  endfunction
  function string holes();
    foreach (hits[i]) if (hits[i] == 0) holes = {holes, holes == "" ? "" : ", ", names[i]};
    if (holes == "") holes = "none";
  endfunction
endclass

// Settings the test chooses for the environment.
class fifo_cfg extends uvm_object;
  `uvm_object_utils(fifo_cfg)
  virtual fifo_if         vif;
  uvm_active_passive_enum is_active    = UVM_ACTIVE;
  bit                     has_coverage = 1;
  function new(string name = "fifo_cfg"); super.new(name); endfunction
endclass

class fifo_agent extends uvm_agent;
  `uvm_component_utils(fifo_agent)
  fifo_cfg                   cfg;
  uvm_sequencer #(fifo_item) sqr;
  fifo_driver                drv;
  fifo_monitor               mon;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    if (!uvm_config_db #(fifo_cfg)::get(this, "", "cfg", cfg))
      `uvm_fatal("NOCFG", "no fifo_cfg for the agent")
    mon = fifo_monitor::type_id::create("mon", this);
    mon.vif = cfg.vif;
    if (cfg.is_active == UVM_ACTIVE) begin
      sqr = uvm_sequencer #(fifo_item)::type_id::create("sqr", this);
      drv = fifo_driver::type_id::create("drv", this);
      drv.vif = cfg.vif;
    end
  endfunction
  function void connect_phase(uvm_phase phase);
    if (cfg.is_active == UVM_ACTIVE) drv.seq_item_port.connect(sqr.seq_item_export);
  endfunction
endclass

class fifo_env extends uvm_env;
  `uvm_component_utils(fifo_env)
  fifo_cfg        cfg;
  fifo_agent      agent;
  fifo_scoreboard sb;
  fifo_coverage   cov;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    if (!uvm_config_db #(fifo_cfg)::get(this, "", "cfg", cfg))
      `uvm_fatal("NOCFG", "no fifo_cfg for the environment")
    uvm_config_db #(fifo_cfg)::set(this, "agent", "cfg", cfg);
    agent = fifo_agent::type_id::create("agent", this);
    sb    = fifo_scoreboard::type_id::create("sb", this);
    if (cfg.has_coverage) cov = fifo_coverage::type_id::create("cov", this);
  endfunction
  function void connect_phase(uvm_phase phase);
    agent.mon.ap.connect(sb.analysis_export);
    if (cfg.has_coverage) agent.mon.ap.connect(cov.analysis_export);
  endfunction
endclass

// Builds the configuration and the environment. Tests extend this class.
class fifo_base_test extends uvm_test;
  `uvm_component_utils(fifo_base_test)
  fifo_cfg cfg;
  fifo_env env;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    cfg = fifo_cfg::type_id::create("cfg");
    if (!uvm_config_db #(virtual fifo_if)::get(this, "", "vif", cfg.vif))
      `uvm_fatal("NOVIF", "no virtual interface")
    configure(cfg);
    uvm_config_db #(fifo_cfg)::set(this, "env", "cfg", cfg);
    env = fifo_env::type_id::create("env", this);
  endfunction
  virtual function void configure(fifo_cfg cfg); endfunction   // tests change settings here
endclass

// Random traffic: n items, each a write, a read, both or neither.
class random_seq extends uvm_sequence #(fifo_item);
  `uvm_object_utils(random_seq)
  int n = 16;
  function new(string name = "random_seq"); super.new(name); endfunction
  task body();
    repeat (n) begin
      fifo_item it = fifo_item::type_id::create("it");
      start_item(it);
      if (!it.randomize()) `uvm_error("RAND", "randomize failed")
      finish_item(it);
    end
  endtask
endclass

class script_seq extends uvm_sequence #(fifo_item);
  `uvm_object_utils(script_seq)
  function new(string name = "script_seq"); super.new(name); endfunction
  task send(bit reset, bit wr, bit rd, bit [7:0] data = 0);
    fifo_item it = fifo_item::type_id::create("it");
    start_item(it);
    it.reset = reset; it.wr = wr; it.rd = rd; it.data = data;
    finish_item(it);
  endtask
  task body();
    send(0, 1, 0, 8'h10); send(0, 1, 0, 8'h20);
    send(0, 1, 0, 8'h30); send(0, 1, 0, 8'h40);
    send(0, 1, 0, 8'hff);              // write while full
    send(0, 0, 1);                     // read
    send(0, 1, 1, 8'h50);              // write and read together
    send(0, 0, 1);                     // read
    send(1, 0, 0);                     // reset
    send(0, 0, 1);                     // read while empty
    send(0, 1, 0, 8'h60); send(0, 0, 1);
  endtask
endclass
class script_test extends fifo_base_test;
  `uvm_component_utils(script_test)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task run_phase(uvm_phase phase);
    script_seq seq = script_seq::type_id::create("seq");
    phase.raise_objection(this);
    seq.start(env.agent.sqr);
    #1;
    phase.drop_objection(this);
  endtask
  function void report_phase(uvm_phase phase);
    $display("writes %0d, reads %0d, canceled %0d, refused %0d",
             env.sb.writes, env.sb.reads, env.sb.canceled, env.cov.hits[5] + env.cov.hits[6]);
  endfunction
endclass
module tb;
  bit clk;
  always #5 clk = ~clk;
  fifo_if  bus (clk);
  fifo_dut dut (bus);
  initial begin
    uvm_config_db #(virtual fifo_if)::set(null, "uvm_test_top", "vif", bus);
    run_test("script_test");
  end
endmodule
Spot the bugEach option is the body of fifo_dut. The capstone script from the predict item runs on each one. Which design fails the bench?
Read the waveformThe scoreboard's queue and the design's pins at five edges. Assume full and empty are correct at every edge. At which edge does the scoreboard first report an error?
Scoreboard queue against the design pins, one column per rising edge
01234edge12345rstwr, rdwr-wrrdwdata / rdata1020-30queue before edgeempty1010 20empty30reset

The scoreboard empties its queue when it sees a reset sample.

ConceptA design fault is injected and the capstone test still prints TEST PASSED. What does that tell you?
ConceptWhy does the capstone scoreboard also compare full and empty with the size of its own queue?

Remember

  • The test fills a configuration object and sets factory overrides; the environment below it stays unchanged between tests.
  • The monitor decides what the design accepted from the pins, including resets, so the scoreboard can cancel values on a reset.
  • Close a run with a ledger: writes equal reads plus canceled plus stored, and stored is zero after the drain.
  • A verdict combines item counts, the ledger, coverage and the error count; each catches a different broken bench.
  • Trust the bench only after it has failed on designs broken on purpose.