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

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
    • Testbench Anatomy & Layering
    • Stimulus: Generators, Transactions & Drivers
    • Monitors, Scoreboards & Reference Models
      • Monitor Design
      • Scoreboard Architecture
      • Compare and End-of-Test
      • Reference Models and Coverage
    • Self-Checking Tests & Regression
    • From Hand-Built TB to UVM
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Monitors, Scoreboards & Reference Models
  1. Topics
  2. SystemVerilog
  3. Testbench Architecture in SystemVerilog
  4. Monitors, Scoreboards & Reference Models
  5. Scoreboard Architecture

Part 6 · Testbench Architecture · Intermediate

Scoreboard Architecture

Compare expected against observed with a FIFO for in-order DUTs or by-id lookup for reorder.

On this page

    Two streams, one verdict

    Every scoreboard has the same shape: an expected stream of what the DUT should produce, an actual stream of what the output monitor observed, and a compare engine between them. FIFOs and lookup tables are bookkeeping around that core.

    Expected flows from the input side through the model; actual flows from the output monitor.
    Expected flows from the input side through the model; actual flows from the output monitor.

    Expected comes from

    Fits

    Rule

    Reference model fed by the input monitor

    Any DUT with defined behavior

    Predict from what the DUT received.

    Input monitor directly

    FIFOs, bridges, pass-through paths

    The input transaction is the expected output.

    Never the generator or driver

    Nothing

    The DUT may legally drop or reorder stimulus.

    In-order compare

    A DUT that preserves order needs only a queue. Push expected at the back; each actual transaction must match the front.

    The next two listings are classes with no top module, so this course's checks do not run them. They share one transaction type with an id field and a plain compare wrapper.

    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
    // not-run: classes only, no top module
    class bus_txn;
      int unsigned   id;
      logic [31:0]   addr;
      logic [31:0]   data;
      logic [1:0]    resp;
      int unsigned   len;
      time           observed_t;   // metadata, never compared
      int unsigned   dbg_id;       // bookkeeping, never compared
    
      typedef enum int unsigned {
        F_ADDR = 'h1, F_DATA = 'h2, F_RESP = 'h4, F_LEN = 'h8
      } field_e;
      localparam int unsigned F_ALL = 'hF;
    
      function bit do_compare(bus_txn rhs, int unsigned mask,
                              output string mismatch_desc);
        bit ok = 1;
        mismatch_desc = "";
        if ((mask & F_ADDR) && addr !== rhs.addr) begin
          ok = 0;
          mismatch_desc = {mismatch_desc,
            $sformatf(" addr(exp=0x%08h act=0x%08h)", addr, rhs.addr)};
        end
        if ((mask & F_DATA) && data !== rhs.data) begin
          ok = 0;
          mismatch_desc = {mismatch_desc,
            $sformatf(" data(exp=0x%08h act=0x%08h)", data, rhs.data)};
        end
        if ((mask & F_RESP) && resp !== rhs.resp) begin
          ok = 0;
          mismatch_desc = {mismatch_desc,
            $sformatf(" resp(exp=%0d act=%0d)", resp, rhs.resp)};
        end
        if ((mask & F_LEN) && len != rhs.len) begin
          ok = 0;
          mismatch_desc = {mismatch_desc,
            $sformatf(" len(exp=%0d act=%0d)", len, rhs.len)};
        end
        return ok;
      endfunction
    
      // Plain full compare used by the scoreboards below
      function bit compare(bus_txn rhs);
        string desc;
        return do_compare(rhs, F_ALL, desc);
      endfunction
    
      function string convert2string();
        return $sformatf("id=%0d addr=0x%08h data=0x%08h resp=%0d len=%0d",
                         id, addr, data, resp, len);
      endfunction
    endclass
    
    class fifo_scoreboard;
      mailbox #(bus_txn) mbx_exp;   // from model or input monitor
      mailbox #(bus_txn) mbx_act;   // from output monitor
      bus_txn expected_q[$];
      int match_count, mismatch_count;
      bit stop;                     // set by env at drain
    
      function new(mailbox #(bus_txn) mbx_exp, mailbox #(bus_txn) mbx_act);
        this.mbx_exp = mbx_exp;
        this.mbx_act = mbx_act;
      endfunction
    
      task run();
        fork
          while (!stop) begin               // expected side: enqueue
            bus_txn t;
            mbx_exp.get(t);
            expected_q.push_back(t);
          end
          while (!stop) begin               // actual side: match the front
            bus_txn act, exp;
            mbx_act.get(act);
            if (expected_q.size() == 0) begin
              mismatch_count++;
              $error("[SCB] actual with empty expected queue: %s",
                     act.convert2string());
            end else begin
              exp = expected_q.pop_front();
              if (exp.compare(act)) match_count++;
              else begin
                mismatch_count++;
                $error("[SCB] DIFFERENCE\n  exp: %s\n  act: %s",
                       exp.convert2string(), act.convert2string());
              end
            end
          end
        join_none
      endtask
    endclass

    Order violations surface for free: a reordered DUT fails position-by-position compares. The !== operator keeps X propagation failing instead of slipping through.

    Out-of-order compare

    A DUT that completes out of order breaks the queue model. Key expected transactions by id in an associative array and look each actual transaction up.

    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
    // not-run: fragment: uses bus_txn from the listing above, no top module
    class ooo_scoreboard;
      mailbox #(bus_txn) mbx_exp, mbx_act;
      bus_txn expected[int];   // keyed by transaction id
      int match_count, mismatch_count, orphan_count;
      bit stop;
    
      function new(mailbox #(bus_txn) mbx_exp, mailbox #(bus_txn) mbx_act);
        this.mbx_exp = mbx_exp;
        this.mbx_act = mbx_act;
      endfunction
    
      task run();
        fork
          while (!stop) begin
            bus_txn t;
            mbx_exp.get(t);
            if (expected.exists(t.id))
              $error("[SCB] duplicate expected id=%0d", t.id);
            expected[t.id] = t;
          end
          while (!stop) begin
            bus_txn act;
            mbx_act.get(act);
            if (!expected.exists(act.id)) begin
              orphan_count++;
              $error("[SCB] actual id=%0d has no expected entry", act.id);
            end else begin
              if (expected[act.id].compare(act)) match_count++;
              else begin
                mismatch_count++;
                $error("[SCB] DIFFERENCE id=%0d\n  exp: %s\n  act: %s",
                       act.id, expected[act.id].convert2string(),
                       act.convert2string());
              end
              expected.delete(act.id);   // consumed; rest checked at end of test
            end
          end
        join_none
      endtask
    endclass

    DUT ordering

    Structure

    Note

    In order

    Queue, compare against the front

    A reorder bug fails compares by itself.

    Out of order

    Associative array keyed by id

    Delete on consume; leftovers fail at end of test.

    In order within each id

    Array of queues, one queue per id

    Hybrid for per-channel ordering rules.

    Common mistakes

    Common pitfall

    Feeding expected from the generator turns legal DUT drops into false alarms. Running a FIFO compare on an out-of-order DUT passes or fails with completion timing instead of data.

    Interview

    Likely task: “Design a scoreboard for responses that return out of order.” Walk through the by-id array, the duplicate-id guard, deleting consumed entries, and the empty-at-end check.

    Check yourself The FIFO scoreboard flags every transaction after the first real bug. Is each flag a new bug?

    Almost certainly not. One alignment slip puts the queue out of step, so every later compare fails. Fix the first one and re-run.

    Check yourself An actual transaction arrives with an id that has no expected entry. What does that mean?

    The DUT produced something unpredicted: a duplicated, invented, or mis-tagged transaction. Count it as an orphan and report it like any other difference.

    Testbench

    FIFO versus by-id compare on one reordered arrival sequence.

    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
    module tb_scoreboard_order;
      timeunit 1ns; timeprecision 1ps;
      reg [7:0] exp_data [0:3];
      reg [7:0] act_data [0:3];
      reg [1:0] act_id [0:3];
      int errors = 0;
      int fifo_diff = 0;
      int byid_match = 0;
    
      initial begin
        for (int i = 0; i < 4; i++)
          exp_data[i] = 8'h10 * (i + 1);          // 10, 20, 30, 40
        act_id[0] = 2; act_data[0] = 8'h30;        // arrival order 2,0,3,1
        act_id[1] = 0; act_data[1] = 8'h10;
        act_id[2] = 3; act_data[2] = 8'h40;
        act_id[3] = 1; act_data[3] = 8'h20;
        #1;
        for (int i = 0; i < 4; i++)                // positional compare
          if (act_data[i] !== exp_data[i]) fifo_diff++;
        for (int i = 0; i < 4; i++)                // lookup by id
          if (act_data[i] === exp_data[act_id[i]]) byid_match++;
        if (fifo_diff !== 4) errors++;
        if (byid_match !== 4) errors++;
        if (errors == 0) $display("PASS: fifo flags 4 positional differences on reorder, by-id matches all 4");
        else $fatal(1, "FAIL: fifo_diff=%0d byid_match=%0d", fifo_diff, byid_match);
        $finish;
      end
    endmodule
    Previous

    176 / 259

    Next: Compare and End-of-Test

    Up next

    Up nextCompare and End-of-Test

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Reference Models and Coverage