Choosing the checking strategy

Pick how a scoreboard will know the right answer, from read-back self-checking to a full model, and know what each choice can and cannot catch.

Lesson
U9.1
Time
30 min
Runs on
Verilator with UVM
Check
5 questions

A memory controller passes a thousand regression runs with a scoreboard that writes a byte, reads it back and compares. Then a firmware engineer sets the controller's inversion mode, and every read returns the complement of what was written. The scoreboard reports a hundred mismatches against a design that does exactly what the specification says. Nobody had decided what the checker should know, so it never knew the mode existed.

Request takenMemory returnsRead-back checkerModel checker
WRITE bank 0 addr 1 = 11-remembers 11stores 11
READ tag 1 bank 0 addr 111matchmatch
READ tag 2 bank 1 addr 5d5 (power-up)skipped: never writtenskipped: unspecified
CTRL inv = 1-ignoredinv = 1
READ tag 3 bank 0 addr 1eefalse mismatch: expected 11match: 11 inverted
READ tag 4 bank 1 addr 222matchmatch

The same traffic through two checkers. One of them is wrong about a correct design.

The questions a plan has to answer

Before anyone writes a scoreboard, the verification plan answers a short list of questions for each block. How many agents does the block need? Which kind of scoreboard: in-order queue, or out-of-order matching by a key as in U8.2? Does the checker need a reference model, a second implementation of the function that computes the expected output? And if the model works in transactions while the design works in cycles, where do the two meet? That meeting is the sync point. It has two halves: the prediction point, when the model is asked, and the comparison point, when its answer meets the design's result.

The last two questions are the subject of this module. U8 took the prediction for granted: model0[t.addr] was one line inside the scoreboard. That line is a choice. The figure walks it for one output at a time; the table adds what each strategy costs and where it is simply wrong.

Which reference, per outputEach box on the left is a question about one output of the design. Follow the answers to the cheapest checker that can answer for that output.
Which reference, per outputEach box on the left is a question about one output of the design. Follow the answers to the cheapest checker that can answer for that output. question Is the datatransformed? Modes, arithmetic,encodings no model Write, read back,compare Assumes data comesback unchanged question Does historymatter? Modes, state, earlierinputs model Transferfunction One expression in thepredictor question Is timing thequestion? Latency, cycle order model Untimed model SV class (U9.2) or Cover DPI (U9.3) model Timing check Cycle-accurate modelor latency meter(U8.4) noyesnoyesnoyes
  1. Plain data paths

    A FIFO or bridge that must deliver bytes unchanged needs no model; the checker assumes exactly that.

    More context

    One question settles it. The checker's only assumption, what goes in comes out, is the specification itself.

    Follow the calls, values and waits
    1. data FIFO output byte: Is the data transformed? → Write, read back, compare
  2. Stateless transforms

    An incrementer (U7.3) is one expression, x + 1.

    More context

    The data changes, but each result depends only on its own input, so the predictor needs no memory.

    Follow the calls, values and waits
    1. data incrementer y: Is the data transformed? → Does history matter?
    2. data incrementer y: Does history matter? → Transfer function
  3. State and modes

    A model keeps the state. Write it as a class, or reuse a C model if one exists.

    More context

    This lesson's memory: the stored byte and the inv mode set by an earlier control write both decide the answer. The value does not depend on how many cycles the read takes.

    Follow the calls, values and waits
    1. data read data, inv mode: Is the data transformed? → Does history matter?
    2. data read data, inv mode: Does history matter? → Is timing the question?
    3. data read data, inv mode: Is timing the question? → Untimed model
  4. Timing

    When the question is when, not what, the check needs time: a cycle-accurate model or a meter.

    More context

    The same read, asked a different question. Its value still goes to the untimed model; its latency goes to a timing check.

    Follow the calls, values and waits
    1. data when the read returns: Is the data transformed? → Does history matter?
    2. data when the read returns: Does history matter? → Is timing the question?
    3. data when the read returns: Is timing the question? → Timing check
Read the diagram as text
  • Is the data transformed?: Modes, arithmetic, encodings
  • Write, read back, compare: Assumes data comes back unchanged
  • Does history matter?: Modes, state, earlier inputs
  • Transfer function: One expression in the predictor
  • Is timing the question?: Latency, cycle order
  • Untimed model: SV class (U9.2) or C over DPI (U9.3)
  • Timing check: Cycle-accurate model or latency meter (U8.4)
  • Is the data transformed? → Write, read back, compare: no
  • Is the data transformed? → Does history matter?: yes
  • Does history matter? → Transfer function: no
  • Does history matter? → Is timing the question?: yes
  • Is timing the question? → Untimed model: no
  • Is timing the question? → Timing check: yes

Most outputs need two answers: an untimed model for values plus a timing check. For a whole processor subsystem, an ISS or virtual platform (U9.5).

StrategyWhat the checker knowsCostWhere it is wrong
No model: write, read back, compareOnly what the test sentLowestIts assumption, data back unchanged, fails for any transform (modes, encodings); locations never written
Transfer functionOne expression per output, as in U7.3's x + 1LowState: anything where the answer depends on history
Untimed class modelStorage and modes, answered at once (U9.2)MediumTiming: a result that is right but late or early looks fine
Cycle-accurate modelThe design's timing tooHigh, and it changes with every pipeline changeShares the design's assumptions; two copies of the same misreading agree
C or C++ over DPIA model that already exists, or one that must run fast (U9.3)Medium plus a build stepType and width mismatches at the hand-off
Instruction-set simulator or ESL platformThe whole programmer's view of a subsystem (U9.5)A team's work, usually boughtOnly as timed as it was built: an untimed one compares at instruction or transaction level
AssertionsProtocol rules: one response per read, a response only when a read is waitingLow per ruleThese protocol rules check no data values

Seven ways to know the right answer. Most benches combine two or three.

Every strategy encodes an assumption. A read-back check assumes the path returns data unchanged: cheap to state and, as the hook shows, wrong for a design with modes. A model encodes a fuller reading of the specification, so it checks more, and a bug in the model is a bug in the test.

Two strategies on one memory

One stream, two checkersArrows carry the same monitor transactions to both checkers. Each checker queues its own predictions by tag.
One stream, two checkersArrows carry the same monitor transactions to both checkers. Each checker queues its own predictions by tag. monitor mem_monitor Requests taken,responses given no model raw_checker Last byte written per{bank, addr} model model_checker Storage plus the invmode verdict Tag 3: expects11 Memory returned ee:false alarm verdict Tag 3: expects ee 11 inverted: match req, rspreq, rsppredictpredict
  1. Writes15 ns (illustrative)

    Both checkers record 11 at bank 0 address 1.

    More context

    One write on the monitor's request port reaches both subscribers in turn, in the same time step. The bank 1 write of 22 follows at 25 ns.

    Follow the calls, values and waits
    1. data WRITE bank 0 addr 1 = 11: mem_monitor → raw_checker
    2. data WRITE bank 0 addr 1 = 11: mem_monitor → model_checker
  2. The control write55 ns (illustrative)

    Only the model records the control write that sets inv. The raw checker receives it too, and ignores it.

    More context

    Same transaction, two readings of the specification. To the raw checker a CTRL request is not a storage write, so it changes nothing.

    Follow the calls, values and waits
    1. data CTRL inv = 1: mem_monitor → raw_checker
    2. data CTRL inv = 1: mem_monitor → model_checker
  3. The read with inv set65 ns (illustrative)

    Tag 3 reads address 1 while inv is 1. Each checker queues its own prediction for tag 3.

    More context

    The predictions are made when the read is taken, from what each checker knows at that edge. The model applies inv now; a CTRL write after this edge must not change it.

    Follow the calls, values and waits
    1. data READ tag 3, addr 1: mem_monitor → raw_checker
    2. data READ tag 3, addr 1: mem_monitor → model_checker
    3. data raw: expect 11: raw_checker → Tag 3: expects 11
    4. data model: expect ee: model_checker → Tag 3: expects ee
    5. Waiting: Tag 3: expects 11 — waiting for tag 3
    6. Waiting: Tag 3: expects ee — waiting for tag 3
  4. The response85 ns (illustrative)

    The memory returns ee for tag 3. The model matches; the raw checker reports a false alarm against a correct design.

    More context

    Log line 85 ns model tag=3 data=ee matches. The run ends with raw at one mismatch and model at zero: UVM_ERROR : 1.

    Follow the calls, values and waits
    1. data response tag 3 = ee: mem_monitor → raw_checker
    2. data response tag 3 = ee: mem_monitor → model_checker
    3. data mismatch: 11 vs ee: raw_checker → Tag 3: expects 11
    4. data match: ee: model_checker → Tag 3: expects ee
    5. Released: Tag 3: expects 11
    6. Released: Tag 3: expects ee
Read the diagram as text
  • mem_monitor: Requests taken, responses given
  • raw_checker: Last byte written per {bank, addr}
  • model_checker: Storage plus the inv mode
  • Tag 3: expects 11: Memory returned ee: false alarm
  • Tag 3: expects ee: 11 inverted: match
  • mem_monitor → raw_checker: req, rsp
  • mem_monitor → model_checker: req, rsp
  • raw_checker → Tag 3: expects 11: predict
  • model_checker → Tag 3: expects ee: predict

Same pins, two readings of the specification. A disagreement says the readings differ; the specification says which one is right.

The raw_checker remembers only what the test wrote, keyed by {bank, addr}; a location never written gets no prediction, and control writes mean nothing to it. The model_checker also tracks the control write and predicts the stored byte XOR {8{inv}}. Both queue predictions per tag, as in U8.2.

Requests taken and responses returned
01234567891011edge (ns)152535455565758595105115125requestW 11W 22R t1R t2inv=1R t3inv=0R t4-response-11 t1-ee t3d5 t2-22 t4raw says-ok-wrongskip-okmodel says-ok-okskip-okinv was 1 when tag 3 was taken

Observed at the earliest two edges after the read for bank 0 and four for bank 1; bank 0 wins a tie, so tag 2 waits behind tag 3 and is seen at 95 ns. Tag 3 reads addr 1 with inv set, so ee is right.

Response ee at 85 ns is where the two part. The memory inverted the stored 11 because inv was 1 when the read was taken. The model predicted ee and matched. The raw checker still expected 11 and reported a mismatch, a false alarm against a correct design. The run ends with raw at one mismatch and model at zero, on the same pins.

Tag 2 shows the opposite limit. Nobody wrote bank 1 address 5, and the specification leaves the power-up contents open. The model cannot predict what it does not know either, so both checkers skip the response. A model is not magic: it carries exactly what the specification says, and no more.

When may the run stop? The test waits until neither checker has a read waiting, but at most 200 ns; if the bound wins, it reports DRAIN. It then watches 50 ns more, the slowest read-to-response time in this run (tag 2), so an extra response is still reported as having no read waiting. Finally check_phase lists every read still pending. Rerun with tag_mem #(.LOSE_TAG(4)): tag 4 is never answered, and both checkers name it.

When the cheap strategy is the right one

Read-back checking looks like the loser here, and for a design with modes it is. But it has a real place. For a plain storage path, a FIFO, or a bus bridge that must deliver bytes unchanged, "what went in comes out" is the specification. The check costs a few lines, and its one assumption is easy to check against the document.

It also keeps working when the model is missing. Early in a project the model may not exist yet, or it may belong to another team and arrive late. A read-back scoreboard finds dropped and corrupted data from day one. When the model arrives, it joins the bench as a second checker rather than replacing the first.

One checker that knows everything

// model_checker only
mon.req_ap.connect(model.req_imp);
mon.rsp_ap.connect(model.rsp_imp);

Every verdict depends on one reading of the specification. A model bug and a design bug look the same.

Two checkers that know different things

mon.req_ap.connect(raw.req_imp);
mon.rsp_ap.connect(raw.rsp_imp);
mon.req_ap.connect(model.req_imp);
mon.rsp_ap.connect(model.rsp_imp);

A disagreement means their assumptions differ, and the specification decides which is wrong. Agreement can still share a wrong assumption.

The pattern generalises. Assertions guard the protocol: one response per read, no response without a read. A read-back or transfer-function checker guards the data path, and the model guards the modes and the history. Each is small enough to review, and each covers behaviour the others do not.

When the model is the thing that is wrong

A model is written by a person who read the same specification as the designer. If the sentence "inv inverts the data" is ambiguous, both may read it the same wrong way, and the bench passes. Or they read it differently, and the bench fails against a design that is right. The quiz below has a model that applies inv on the write path instead of the read path. It disagrees with the memory as soon as the mode changes between a write and a read.

So a mismatch is a question, never a verdict. The report must carry enough to answer it: the request that was predicted, when it was taken, what the model said and what the pins said. The example's error line names all four. U9.3 returns to this when the model is in C and the two sides disagree on a corner neither author thought about.

Try it

Both checkers see the same requests and responses. They agree on tags 1, 2 and 4. On tag 3 the raw checker reports data=ee, expected 11: the one UVM_ERROR in the summary is a false alarm, because inv was 1 when that read was taken. At each time the model's line comes first: the monitor's port calls its listeners in name order, as U7.2 showed.

Remove the two CTRL lines from the script and rerun: both checkers agree on everything, which is exactly why the bug in the hook survived a thousand runs that never set the mode.

RUNVerilator + UVM 2020.3.1 · simulator service
Edit the code, then run it
Expected among the UVM messages: 55 ns model tag=1 data=11 matches 55 ns raw tag=1 data=11 matches 85 ns model tag=3 data=ee matches 95 ns model tag=2 data=d5 skipped: no expectation 95 ns raw tag=2 data=d5 skipped: no expectation 125 ns model tag=4 data=22 matches 125 ns raw tag=4 data=22 matches raw checked 3, skipped 1, mismatches 1 model checked 3, skipped 1, mismatches 0 UVM_ERROR : 1

Check yourself

Predict the outputThe same two checkers watch this script: write 0f to bank 0 address 3, set inv = 1, read it, write f0 to the same address while inv is still 1, read it again, then read a never-written address. What line does the report_phase print?
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;

interface mem_if (input logic clk);
  logic       req_valid = 0;
  logic [1:0] req_kind  = 0;    // 0 read, 1 write, 2 control write
  logic [3:0] req_tag   = 0;
  logic       req_bank  = 0;
  logic [3:0] req_addr  = 0;
  logic [7:0] req_data  = 0;
  logic       rsp_valid = 0;
  logic [3:0] rsp_tag   = 0;
  logic [7:0] rsp_data  = 0;
endinterface

// The two-bank memory from U8. A bank 0 answer is ready one edge after the read is taken and a
// bank 1 answer three edges after (observed one edge later still). One response per edge; bank 0
// goes first when both are ready, so a bank 1 answer can wait longer. Reads that share a tag
// answer in request order. A control write sets inv; a read taken while inv is 1 returns the
// stored byte inverted. Writes store the byte as sent. The power-up contents are unspecified.
// LOSE_TAG is a fault switch for the bench: reads with that tag are taken and never answered.
module tag_mem #(parameter int LOSE_TAG = 16) (mem_if bus);
  typedef struct { bit [3:0] tag; bit [7:0] data; int due; int seq; } pend_t;
  bit [7:0] mem0 [16], mem1 [16];
  bit       inv = 0;
  pend_t    q0[$], q1[$];
  int       cycle = 0, seqno = 0;
  initial foreach (mem0[i]) begin mem0[i] = 8'hC0 | i[3:0]; mem1[i] = 8'hD0 | i[3:0]; end
  function automatic bit held(pend_t head, pend_t other[$]);
    foreach (other[i]) if (other[i].tag == head.tag && other[i].seq < head.seq) return 1;
    return 0;
  endfunction
  always @(posedge bus.clk) begin
    pend_t p;
    cycle++;
    if (bus.req_valid) case (bus.req_kind)
      0: begin
        p.tag  = bus.req_tag;
        p.data = (bus.req_bank ? mem1[bus.req_addr] : mem0[bus.req_addr]) ^ {8{inv}};
        p.due  = cycle + (bus.req_bank ? 3 : 1);
        p.seq  = seqno++;
        if (bus.req_tag != LOSE_TAG[3:0] || LOSE_TAG > 15) begin
          if (bus.req_bank) q1.push_back(p); else q0.push_back(p);
        end
      end
      1: if (bus.req_bank) mem1[bus.req_addr] = bus.req_data; else mem0[bus.req_addr] = bus.req_data;
      2: inv = bus.req_data[0];
      default: ;
    endcase
    bus.rsp_valid <= 0;
    if (q0.size() != 0 && q0[0].due <= cycle && !held(q0[0], q1)) begin
      p = q0.pop_front();
      bus.rsp_valid <= 1; bus.rsp_tag <= p.tag; bus.rsp_data <= p.data;
    end else if (q1.size() != 0 && q1[0].due <= cycle && !held(q1[0], q0)) begin
      p = q1.pop_front();
      bus.rsp_valid <= 1; bus.rsp_tag <= p.tag; bus.rsp_data <= p.data;
    end
  end
endmodule

class mem_req extends uvm_sequence_item;
  `uvm_object_utils(mem_req)
  typedef enum bit [1:0] { READ, WRITE, CTRL } kind_e;
  kind_e    kind;
  bit [3:0] tag;
  bit       bank;
  bit [3:0] addr;
  bit [7:0] data;
  time      at;
  function new(string name = "mem_req"); super.new(name); endfunction
  function string convert2string();
    if (kind == READ) return $sformatf("READ tag=%0d bank=%0d addr=%0d", tag, bank, addr);
    return $sformatf("%s bank=%0d addr=%0d data=%02h", kind.name(), bank, addr, data);
  endfunction
endclass

class mem_rsp extends uvm_sequence_item;
  `uvm_object_utils(mem_rsp)
  bit [3:0] tag;
  bit [7:0] data;
  bit       care = 1;          // 0: nobody can say what this read should return
  time      at;
  mem_req   req;               // a prediction remembers the read it answers
  function new(string name = "mem_rsp"); super.new(name); endfunction
endclass

// One request per cycle: offered at a falling edge, taken at the next rising edge.
class mem_driver extends uvm_driver #(mem_req);
  `uvm_component_utils(mem_driver)
  virtual mem_if vif;
  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
        vif.req_valid <= 0;
        continue;
      end
      vif.req_valid <= 1;
      vif.req_kind  <= req.kind;
      vif.req_tag   <= req.tag;
      vif.req_bank  <= req.bank;
      vif.req_addr  <= req.addr;
      vif.req_data  <= req.data;
      @(posedge vif.clk);
      seq_item_port.item_done();
    end
  endtask
endclass

// Publishes every request the memory takes and every response it gives.
class mem_monitor extends uvm_monitor;
  `uvm_component_utils(mem_monitor)
  virtual mem_if vif;
  uvm_analysis_port #(mem_req) req_ap;
  uvm_analysis_port #(mem_rsp) rsp_ap;
  function new(string name, uvm_component parent);
    super.new(name, parent);
    req_ap = new("req_ap", this);
    rsp_ap = new("rsp_ap", this);
  endfunction
  task run_phase(uvm_phase phase);
    forever begin
      @(posedge vif.clk);
      if (vif.req_valid) begin
        mem_req t = mem_req::type_id::create("t");
        t.kind = mem_req::kind_e'(vif.req_kind);
        t.tag  = vif.req_tag;
        t.bank = vif.req_bank;
        t.addr = vif.req_addr;
        t.data = vif.req_data;
        t.at   = $time;
        req_ap.write(t);
      end
      if (vif.rsp_valid) begin
        mem_rsp r = mem_rsp::type_id::create("r");
        r.tag  = vif.rsp_tag;
        r.data = vif.rsp_data;
        r.at   = $time;
        rsp_ap.write(r);
      end
    end
  endtask
endclass

`uvm_analysis_imp_decl(_req)
`uvm_analysis_imp_decl(_rsp)

virtual class mem_checker extends uvm_component;
  uvm_analysis_imp_req #(mem_req, mem_checker) req_imp;
  uvm_analysis_imp_rsp #(mem_rsp, mem_checker) rsp_imp;
  mem_rsp pending[bit [3:0]][$];          // predictions waiting for a response, by tag
  int checked, skipped, mismatches;
  function new(string name, uvm_component parent);
    super.new(name, parent);
    req_imp = new("req_imp", this);
    rsp_imp = new("rsp_imp", this);
  endfunction
  pure virtual function void write_req(mem_req t);
  // Queue a prediction for this read, or a "cannot say" marker when care is 0.
  function void predict(mem_req t, bit [7:0] data, bit care = 1);
    mem_rsp e = mem_rsp::type_id::create("e");
    e.tag = t.tag; e.data = data; e.care = care; e.at = t.at; e.req = t;
    pending[t.tag].push_back(e);
  endfunction
  function void write_rsp(mem_rsp r);
    mem_rsp e;
    if (!pending.exists(r.tag) || pending[r.tag].size() == 0) begin
      `uvm_error("SB", $sformatf("%s: response tag=%0d has no read waiting", get_name(), r.tag))
      return;
    end
    e = pending[r.tag].pop_front();
    if (!e.care) begin
      skipped++;
    end else if (r.data == e.data) begin
      checked++;
    end else begin
      checked++; mismatches++;
      `uvm_error("SB", $sformatf("%s: tag=%0d data=%02h, expected %02h for %s taken at %0t",
                 get_name(), r.tag, r.data, e.data, e.req.convert2string(), e.at))
    end
  endfunction
  function int waiting();
    int n = 0;
    foreach (pending[t]) n += pending[t].size();
    return n;
  endfunction
  function string summary();
    return $sformatf("%-6s checked %0d, skipped %0d, mismatches %0d", get_name(), checked, skipped, mismatches);
  endfunction
  // End of test: every read still waiting is a response that never came.
  function void check_phase(uvm_phase phase);
    foreach (pending[t]) foreach (pending[t][i])
      `uvm_error("SB", $sformatf("%s: %s taken at %0t was never answered",
                 get_name(), pending[t][i].req.convert2string(), pending[t][i].at))
  endfunction
endclass

class raw_checker extends mem_checker;
  `uvm_component_utils(raw_checker)
  bit [7:0] written[bit [4:0]];           // last byte written, by {bank, addr}
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void write_req(mem_req t);
    case (t.kind)
      mem_req::WRITE: written[{t.bank, t.addr}] = t.data;
      mem_req::READ:  if (written.exists({t.bank, t.addr})) predict(t, written[{t.bank, t.addr}]);
                      else predict(t, 0, 0);
      default: ;                          // a control write means nothing to this checker
    endcase
  endfunction
endclass

class model_checker extends mem_checker;
  `uvm_component_utils(model_checker)
  bit [7:0] mem0 [16], mem1 [16];         // what each location holds
  bit       valid[bit [4:0]];             // which locations have been written
  bit       inv;                          // the mode the next read will see
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void store(bit bank, bit [3:0] addr, bit [7:0] data);
    if (bank) mem1[addr] = data; else mem0[addr] = data;
    valid[{bank, addr}] = 1;
  endfunction
  function bit [7:0] load(bit bank, bit [3:0] addr); return bank ? mem1[addr] : mem0[addr]; endfunction
  function bit known(bit bank, bit [3:0] addr); return valid.exists({bank, addr}); endfunction
  function void write_req(mem_req t);
    case (t.kind)
      mem_req::WRITE: store(t.bank, t.addr, t.data);
      mem_req::CTRL:  inv = t.data[0];
      mem_req::READ:  if (known(t.bank, t.addr)) predict(t, load(t.bank, t.addr) ^ {8{inv}});
                      else predict(t, 0, 0);
    endcase
  endfunction
endclass

class script_seq extends uvm_sequence #(mem_req);
  `uvm_object_utils(script_seq)
  function new(string name = "script_seq"); super.new(name); endfunction
  task send(mem_req::kind_e kind, bit [3:0] tag, bit bank, bit [3:0] addr, bit [7:0] data = 0);
    mem_req it = mem_req::type_id::create("it");
    start_item(it);
    it.kind = kind; it.tag = tag; it.bank = bank; it.addr = addr; it.data = data;
    finish_item(it);
  endtask
  task body();
    send(mem_req::WRITE, 0, 0, 3, 8'h0F);   // bank 0, addr 3 = 0f
    send(mem_req::CTRL,  0, 0, 0, 8'h01);   // inv = 1
    send(mem_req::READ,  1, 0, 3);
    send(mem_req::WRITE, 0, 0, 3, 8'hF0);   // written while inv is 1
    send(mem_req::READ,  2, 0, 3);
    send(mem_req::READ,  3, 1, 9);          // never written
  endtask
endclass

class predict_test extends uvm_test;
  `uvm_component_utils(predict_test)
  uvm_sequencer #(mem_req) sqr;
  mem_driver  drv;
  mem_monitor mon;
  raw_checker   raw;
  model_checker model;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    virtual mem_if vif;
    sqr = uvm_sequencer #(mem_req)::type_id::create("sqr", this);
    drv = mem_driver::type_id::create("drv", this);
    mon = mem_monitor::type_id::create("mon", this);
    if (!uvm_config_db #(virtual mem_if)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", "no virtual interface")
    drv.vif = vif;
    mon.vif = vif;
    raw   = raw_checker::type_id::create("raw", this);
    model = model_checker::type_id::create("model", this);
  endfunction
  function void connect_phase(uvm_phase phase);
    drv.seq_item_port.connect(sqr.seq_item_export);
    mon.req_ap.connect(raw.req_imp);   mon.rsp_ap.connect(raw.rsp_imp);
    mon.req_ap.connect(model.req_imp); mon.rsp_ap.connect(model.rsp_imp);
  endfunction
  task run_phase(uvm_phase phase);
    script_seq seq = script_seq::type_id::create("seq");
    phase.raise_objection(this);
    seq.start(sqr);
    fork
      while (!(raw.waiting() == 0 && model.waiting() == 0)) #10ns;        // tested every cycle: a wait() on a function call is not re-tested
      begin
        #200ns;
        `uvm_error("DRAIN", "reads still unanswered 200 ns after the last request")
      end
    join_any
    disable fork;
    #50ns;
    phase.drop_objection(this);
  endtask
  function void report_phase(uvm_phase phase);
    $display("raw: checked %0d skipped %0d mismatches %0d; model: checked %0d skipped %0d mismatches %0d",
             raw.checked, raw.skipped, raw.mismatches, model.checked, model.skipped, model.mismatches);
  endfunction
endclass

module tb;
  bit clk;
  always #5 clk = ~clk;
  mem_if bus (clk);
  tag_mem dut (bus);
  initial begin
    uvm_root::get().set_timeout(2000ns, 1);
    uvm_config_db #(virtual mem_if)::set(null, "uvm_test_top", "vif", bus);
    run_test("predict_test");
  end
endmodule
Spot the bugEach option is the case body of the model checker's write_req. The script writes 11, sets inv, reads it back (the memory returns ee), clears inv, writes 22 to bank 1 address 2 and reads both locations. Which model reports a mismatch?
ConceptA bench checks a FIFO that must deliver bytes unchanged. Which checking strategy fits the data path best?
ConceptBoth the designer and the model author read "bit 0 of the control register inverts the data" and both applied it on the write path. The specification meant the read path. What does the bench report?
ConceptWhere does the sync point question come from, and why does it belong in the plan rather than in the scoreboard code?

Remember

  • The plan decides, per output, how the checker knows the right answer: read-back, transfer function, untimed model, cycle-accurate model, C over DPI, a platform, or assertions.
  • Read-back self-checking is cheap and its one assumption, data back unchanged, is easy to review; it is blind to any transform of the data and to locations the test never wrote.
  • A model encodes a reading of the specification: it catches modes and history, and it is wrong exactly where that reading is wrong.
  • Checkers that know different things cover different behaviour; when they disagree, the specification decides which assumption is wrong, and agreement can still share a wrong one.
  • A run may stop only when every read is answered or a reported bound expires, plus a quiet window for late extras; check_phase lists what is still pending.