Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here5
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive39
  • SystemVerilog Assertions — Interview-Critical Deep Dive41
  • Functional Coverage in SystemVerilog25
  • Testbench Architecture in SystemVerilog24
  • Advanced Language & Integration26
  • Senior & Interview Preparation23
  • Principal-Level Verification34
    • Reusable Testbench Architecture
    • Checking Out-of-Order Traffic
    • Assertion Depth
    • Coverage Modelling for Real Features
    • Capstone: Out-of-Order Memory Controller
      • Spec and Plan
      • Executable Spec: One Bench per Rule
      • Environment
      • Environment Self-Test
      • Checking
      • Testing the Checkers
      • Coverage and Closure
      • Merging Runs and Reading Holes
      • Bug Hunt: Six Bugs and the Full Bench
      • Bug Hunt Results and Sign-Off
    • Principal Judgment
Loading lesson

←Capstone: Out-of-Order Memory Controller
  1. Topics
  2. SystemVerilog
  3. Principal-Level Verification
  4. Capstone: Out-of-Order Memory Controller
  5. Testing the Checkers
On this page

    Part 9 of 9, lesson 25 of 34Part 9 of 9/Principal-Level Verification/Lesson 25 of 34Advanced

    Testing the Checkers

    A designless bench that scripts each failure mode the spec forbids, and the one legal case that must stay silent, and requires the right checker to fire.

    Why checkers need a test

    The previous lesson assigned every rule an owner. An owner that has never been seen to fail is a belief, not evidence: a scoreboard with a wrong key, or a reset flush that was never wired, stays quiet on a broken design and looks identical to a healthy one. The cheapest proof is a test of the checkers themselves, with no design in the loop.

    Runnable example: testing the checkers

    Checkers are code and need tests. This bench has no design. It drives the pins the capstone monitor watches from a script, one case per failure mode, and requires each case to be flagged by exactly the checkers expected, and the legal case by none.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    // ===================== Checkers (from Checking Out-of-Order Traffic) =====================
    class id_scoreboard #(type T = logic [31:0]);
      string name;
      protected T exp_q[int][$];
      int n_added, n_matched, n_errors;
      function new(string name); this.name = name; endfunction
      virtual function void add_expected(int id, T item);
        exp_q[id].push_back(item);
        n_added++;
      endfunction
      virtual function void check_actual(int id, T item);
        T exp;
        if (!exp_q.exists(id) || exp_q[id].size() == 0) begin
          n_errors++;
          $display("%s: UNEXPECTED response for ID %0d with nothing outstanding (got %h)", name, id, item);
          return;
        end
        exp = exp_q[id].pop_front();
        if (exp_q[id].size() == 0) exp_q.delete(id);
        if (item !== exp) begin
          n_errors++;
          $display("%s: MISMATCH ID %0d expected %h got %h", name, id, exp, item);
        end else n_matched++;
      endfunction
      function int outstanding();
        int n = 0;
        foreach (exp_q[id]) n += exp_q[id].size();
        return n;
      endfunction
      function int outstanding_on(int id);
        return exp_q.exists(id) ? exp_q[id].size() : 0;
      endfunction
      virtual function void flush();               // reset: outstanding work is dropped by spec
        exp_q.delete();
      endfunction
    endclass
    
    class drain_scoreboard #(type T = logic [31:0]) extends id_scoreboard #(T);
      protected longint issued_at[int][$];
      longint last_progress;
      int     max_lat, late;
      int     leaked[$], stuck[$];
      function new(string name, int max_lat);
        super.new(name);
        this.max_lat = max_lat;
      endfunction
      function void add_at(longint now, int id, T item);
        add_expected(id, item);
        issued_at[id].push_back(now);
      endfunction
      function void check_at(longint now, int id, T item);
        check_actual(id, item);
        if (issued_at.exists(id)) begin
          void'(issued_at[id].pop_front());
          if (issued_at[id].size() == 0) issued_at.delete(id);
        end
        last_progress = now;
      endfunction
      // Returns how many items passed max_lat on this cycle (each counted once).
      function int watchdog(longint now);
        int n = 0;
        foreach (issued_at[id])
          if (now - issued_at[id][0] == max_lat) begin
            n++;
            $display("%s: ID %0d waited %0d cycles (issued at %0d)", name, id, max_lat, issued_at[id][0]);
          end
        return n;
      endfunction
      virtual function void flush();
        super.flush();
        issued_at.delete();
      endfunction
      function void classify(longint now);
        foreach (issued_at[id])
          foreach (issued_at[id][k])
            if (last_progress - issued_at[id][k] > max_lat) leaked.push_back(id);
            else stuck.push_back(id);
        foreach (leaked[i]) $display("%s: LEAK ID %0d never answered", name, leaked[i]);
        if (stuck.size() != 0) $display("%s: STUCK IDs %p, no response for %0d cycles", name, stuck, now - last_progress);
      endfunction
    endclass
    
    class mem_model;
      protected bit [7:0] bytes[bit [31:0]];
      function void write(bit [31:0] addr, bit [31:0] data, bit [3:0] strb);
        for (int b = 0; b < 4; b++) if (strb[b]) bytes[addr + b] = data[8*b +: 8];
      endfunction
      function bit [31:0] read(bit [31:0] addr);
        bit [31:0] w;
        for (int b = 0; b < 4; b++) w[8*b +: 8] = bytes.exists(addr + b) ? bytes[addr + b] : 8'h00;
        return w;
      endfunction
    endclass
    
    

    The bench passes. The log shows each case and what fired:

    Case

    Scoreboard

    a_no_orphan

    Leak

    Legal cross-ID reorder

    -

    -

    -

    Within-ID swap (bug 6)

    MISMATCH x2

    -

    -

    Duplicate response (bug 5)

    UNEXPECTED

    fires

    -

    ID swap (bug 1)

    UNEXPECTED

    fires

    -

    Strobe lane dropped (bug 2)

    MISMATCH 88bb8888 vs 88888888

    -

    -

    Response after reset

    UNEXPECTED

    fires

    -

    Lost response (bug 3)

    -

    -

    LEAK ID 0

    Mutant run. Removing sb.flush() from the reset branch fails the bench on the reset case: the stale response matches the dropped read, the scoreboard stays quiet, and only a_no_orphan fires. That is exactly the escape a missing flush causes in a real regression, where a design that wrongly answers a dropped read would pass.

    Likely question: “How do you know your checker works?” Answer: give it a unit test. Script the failure modes the spec allows and forbids, and assert which checker must fire for each, including the legal case that must stay silent. Then mutate the checker (remove the reset flush, search anywhere instead of the queue head) and confirm the test fails.

    Check yourself A response arrives during reset. Which checker should report it?

    None in this bench: during reset nothing is sampled, and the controller holds rsp.valid low in reset. If the spec allowed responses during reset, the monitor would need a rule for them; R5 says outstanding reads are dropped, so a response for one after reset is UNEXPECTED.

    Check yourself The legal cross-ID reorder case must fire no checker. Why include a case that is supposed to pass?

    A checker that fires on everything also passes every failing case. The legal case proves the checkers are not simply noisy, and it catches a scoreboard that searches or compares across IDs.

    Related topics

    • Previous: Checking

    • Next: Coverage and Closure

    • ID-Tagged Scoreboard

    • Capstone: Out-of-Order Memory Controller (topic)

    Previous

    266 / 275

    Next: Coverage and Closure

    Up next

    Up nextCoverage and Closure

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Spec and Plan
    • Executable Spec: One Bench per Rule
    • Environment