Altifigence Academy

58 / 63 · Concept

FIFO and memory verification plans with an independent scoreboard

Build a queue scoreboard independent of implementation pointers, and connect boundary, reset, valid, and uninitialized-read cases to a verification plan.

Make the reference model different from the DUT

A checker compares contract-based expected results with the DUT. Copying the FIFO'sFIFO A queue that removes data in the order in which it was inserted. Rules for full, empty and simultaneous reads and writes are part of its behavioral contract. Learn more internal rptr and wptr to calculate expected values can reproduce the same pointer bug. An independent scoreboardScoreboard Reference state in a verification environment that compares expected data with actual outputs in order. Compute it independently rather than copying the implementation's internal state. stores only logical order in a SystemVerilog queue. An accepted push appends at the back, and an accepted pop removes from the front.

List cross-combinations of state and requests in the verification plan. For empty, partially filled, and full states, check no request, push only, pop only, and both requests. Add repeated wrap-around, consecutive pops, and reset while data remains. Record whether each case was observed, and retain the random seed and failing inputs for reproduction.

Expected acceptance comes from the queue's previous size NN, not the DUT's full or occupancy:

q=pop∧(N>0)p=push∧((N<4)∨q)N+=N+p−q\begin{aligned} q&=\mathrm{pop}\land(N>0)\\ p&=\mathrm{push}\land((N<4)\lor q)\\ N^{+}&=N+p-q \end{aligned}

Remove the old head before appending new data, preserving the old head for simultaneous requests at full. Compare dout only when expected q=1q=1.

Complete testbench

This complete testbench compiles with fifo4 from the preceding lesson. It is verification code using delays and a queue. Inputs change at the falling edge, and DUT results are compared 1 ps after the rising edge, following its nonblockingNonblocking assignment An assignment written as <= in sequential RTL. It evaluates the right-hand side and schedules the update, allowing registers at the same edge to compute from the previous state. Learn more updates.

SystemVerilog
module tb_fifo4;
  timeunit 1ns;
  timeprecision 1ps;
  typedef logic [7:0] word_t;
  logic clk=0, rst=1, push=0, pop=0;
  word_t din=0, dout;
  logic dout_valid, full, empty;
  logic [2:0] occupancy;
  word_t model[$], expected;
  bit expect_pop, expect_push;
  fifo4 dut (.*);
  always #5ns clk = ~clk;

  always @(posedge clk) begin
    if (rst) begin
      model.delete();
      expect_pop = 0;
    end else begin
      expect_pop = pop && (model.size() != 0);
      expect_push = push && ((model.size() < 4) || expect_pop);
      if (expect_pop) expected = model.pop_front();
      if (expect_push) model.push_back(din);
    end
    #1ps;
    assert (dout_valid === expect_pop)
      else $fatal(1, "valid mismatch");
    if (expect_pop)
      assert (dout === expected)
        else $fatal(1, "data mismatch");
    assert (int'(occupancy) === model.size())
      else $fatal(1, "count mismatch");
    assert (empty === (model.size() == 0))
      else $fatal(1, "empty mismatch");
    assert (full === (model.size() == 4))
      else $fatal(1, "full mismatch");
  end

  task automatic step(input bit r, p, o, input word_t d);
    @(negedge clk);
    rst=r; push=p; pop=o; din=d;
  endtask

  initial begin
    step(0,0,1,0);
    step(0,1,1,8'hA0);
    for (int i=1; i<4; i++)
      step(0,1,0,word_t'(160+i));
    step(0,1,0,8'hEE);
    step(0,1,1,8'hB0);
    repeat (5) step(0,0,1,0);
    repeat (3) begin
      for (int i=0; i<4; i++) step(0,1,0,word_t'(i));
      repeat (4) step(0,0,1,0);
    end
    step(0,1,0,8'h5A);
    step(1,1,1,8'hFF);
    step(0,0,1,0);
    repeat (100)
      step(0,bit'($urandom_range(1,0)),bit'($urandom_range(1,0)),
           word_t'($urandom));
    repeat (4) step(0,0,1,0);
    @(negedge clk);
    $display("PASS");
    $finish;
  end
endmodule

The first rising edge has rst=1, clearing both DUT and model. === compares strictly, including X. When valid is 0, the uninitialized dout is not compared, but an X on valid itself fails. The final four pops check all remaining data. PASS is the message produced when this test actually runs and passes every assertion; presenting the code here does not itself mean it has been executed.

Before inserting write data B0, separate the old head A0 as the expected output. This compares logical data order rather than pointers or physical slots.

Scoreboard order for simultaneous requests at full
  1. Previous queue: [A0, A1, A2, A3]
  2. Expected pop value: Save the old head A0
  3. Apply push: [A1, A2, A3, B0]
  4. Compare response: dout=A0 when valid=1

Check the scoreboard with boundary examples

With queue [A0,A1,A2,A3], a push-only request discards EE because the size is 4. The next simultaneous request first saves expected=A0, then appends B0 to produce [A1,A2,A3,B0]. If the DUT immediately outputs the new B0, the data assertion fails. Conversely, simultaneous requests at empty give expect_pop=0 and expect_push=1, storing one item without an output comparison. During reset, both requests are ignored, the queue is emptied, and valid=0 is checked.

Use the same principle for memory verification. Maintain a reference array and an initialized table per address. For read-first, capture the expected read value before applying the reference write update. For registerRegister A circuit that stores multiple bits of state. The synchronous registers in this course store their specified inputs at a clock edge. Learn more file bypass, select wr_data on a collision before updating the reference array. State whether uninitialized reads are forbidden or undefined responses are allowed as a test assumption; do not invent an expected value of zero. Addressed memories require their own checker.

Inject faults individually, such as returning the new value on a simultaneous pop when full, incrementing the count on simultaneous acceptance, or asserting valid for a pop when empty. Each corresponding check must fail. Such reviews and boundary coverage reveal omissions in the test itself. Verify RAM resource mapping and timing separately through synthesis and timing analysis.

Try it yourself

A faulty scoreboard first inserts a push request into its queue, then uses the updated size to decide whether pop is allowed. What goes wrong when push and pop arrive together at empty? When should expected data be saved for simultaneous requests at full? Also evaluate a rule that always checks dout=0 after reset.

Read the explanation

It incorrectly allows the newly inserted item to be popped immediately at empty, producing expect_pop=1. The contract determines acceptance from the size before the edge, so only push is accepted and output valid must be 0. At full, first save and remove the old head as expected, then append the new data. Reset does not set dout to 0, so check only valid=0 and an empty queue/occupancy. Compare data when expect_pop=1, and separately compare DUT valid; otherwise, a bug that always outputs valid=0 could escape detection.

Your choice applies to this browser. Change it any time using the footer.