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
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
    • Senior Debug Playbook
    • Reviewing Verification Code
    • Verification Strategy: Spec to Sign-Off
    • SystemVerilog Interview Q&A Bank
    • Capstone: Verify a Parameterized FIFO
      • Spec Analysis and Verification Plan
      • Harness and Stimulus
      • Monitor, Scoreboard and Model
      • Assertions, Coverage and Closure
  • Principal-Level Verification28
Loading lesson

‹Capstone: Verify a Parameterized FIFO
  1. Topics
  2. SystemVerilog
  3. Senior & Interview Preparation
  4. Capstone: Verify a Parameterized FIFO
  5. Monitor, Scoreboard and Model

Part 8 · Senior & Interview Prep · Intermediate

Monitor, Scoreboard and Model

Passive monitors, a queue reference model and a scoreboard with drain checks.

On this page

    What the checkers watch

    Two passive monitors watch the pins and publish observed events into mailboxes. The scoreboard owns a queue-based reference model: a push observed on the input side enqueues the expected word, and a pop observed on the output side dequeues and compares.

    The stack never looks at generator intent. It checks what the DUT actually did, which lets it ride along passively at subsystem level later.

    Diagram
    pins ──► in_mon  ──(WIDTH-bit word)──► mb_in  ──┐
                         (in_valid && in_ready)                 │  scoreboard
                                                                │  exp_q[$]  ◄─ model
       pins ──► out_mon ──(WIDTH-bit word)──► mb_out ──┘  pop → compare pop_front()

    Monitors

    The monitors below are class declarations that need fifo_if, with no top module, so this course's checks do not run them.

    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
    // not-run: fragment: needs fifo_if from the harness lesson and has no top module
    class fifo_in_mon #(parameter int WIDTH = 8);
      virtual fifo_if #(WIDTH).MON vif;            // input-only view
      mailbox #(logic [WIDTH-1:0]) mb_in;          // 4-state: keeps X
    
      function new(virtual fifo_if #(WIDTH).MON v,
                   mailbox #(logic [WIDTH-1:0]) mb);
        vif = v;
        mb_in = mb;
      endfunction
    
      task run();
        forever begin
          @(vif.mon_cb);
          if (!vif.rst_n) continue;                       // ignore reset cycles
          if (vif.mon_cb.in_valid && vif.mon_cb.in_ready) // a push happened
            mb_in.put(vif.mon_cb.in_data);
        end
      endtask
    endclass
    
    class fifo_out_mon #(parameter int WIDTH = 8);
      virtual fifo_if #(WIDTH).MON vif;            // input-only view
      mailbox #(logic [WIDTH-1:0]) mb_out;         // 4-state: keeps X
    
      function new(virtual fifo_if #(WIDTH).MON v,
                   mailbox #(logic [WIDTH-1:0]) mb);
        vif = v;
        mb_out = mb;
      endfunction
    
      task run();
        forever begin
          @(vif.mon_cb);
          if (!vif.rst_n) continue;
          if (vif.mon_cb.out_valid && vif.mon_cb.out_ready)  // a pop happened
            mb_out.put(vif.mon_cb.out_data);
        end
      endtask
    endclass
    • A transaction exists only when valid and ready are both high. Sampling on valid alone counts stalled beats repeatedly.

    • Both monitors hold the MON modport view, which exposes only the input-only clocking block and rst_n, so they cannot drive by construction.

    • Reset cycles are skipped. In-flight data is discarded per spec rule 8, so it must not enter the model.

    • Flags stay out of the monitors. Assertions own the flags, keeping each checker single-purpose.

    Scoreboard and drain check

    The scoreboard below is a class declaration with no top module, so this course's checks do not run it.

    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
    // not-run: fragment: class declaration only, no top module
    class fifo_sb #(parameter int WIDTH = 8);
      mailbox #(logic [WIDTH-1:0]) mb_in, mb_out;
      logic [WIDTH-1:0] exp_q[$];      // the reference model: a 4-state queue
      int n_checked, n_errors;
      int unsigned n_push, n_pop;       // observed handshake totals
    
      function new(mailbox #(logic [WIDTH-1:0]) mi,
                   mailbox #(logic [WIDTH-1:0]) mo);
        mb_in = mi;
        mb_out = mo;
      endfunction
    
      task run();
        fork
          model_thread();
          check_thread();
        join_none
      endtask
    
      task model_thread();
        logic [WIDTH-1:0] d;
        forever begin
          mb_in.get(d);
          exp_q.push_back(d);          // model: push enqueues expectation
          n_push++;
        end
      endtask
    
      task check_thread();
        logic [WIDTH-1:0] got, exp;
        forever begin
          mb_out.get(got);
          n_pop++;
          if (exp_q.size() == 0) begin
            n_errors++;
            $error("SB: pop with empty model — spurious out_valid? got=0x%0h", got);
            continue;
          end
          exp = exp_q.pop_front();     // model: pop dequeues, in order
          n_checked++;
          if (got !== exp) begin
            n_errors++;
            $error("SB: data word %0d differs: exp=0x%0h got=0x%0h", n_checked, exp, got);
          end
        end
      endtask
    
      // called on reset: spec rule 8 — in-flight data discarded
      function void flush();
        exp_q.delete();
      endfunction
    
      // end-of-test drain check: leftovers mean lost data or a dead pop path.
      // dut_count_now comes from a hierarchical probe (tb_top.dut.count) or the
      // L4 coverage mirror: the pins carry no count, so the test supplies it.
      function void report(int dut_count_now);
        if (exp_q.size() != dut_count_now)
          $error("SB: drain check failed — model holds %0d, DUT count %0d",
                 exp_q.size(), dut_count_now);
        $display("SB: %0d pushed, %0d popped, %0d checked, %0d still in FIFO",
                 n_push, n_pop, n_checked, exp_q.size());
      endfunction
    endclass
    • The model is the queue. Push-back on observed push and pop-front on observed pop give in-order compare for free (F-01).

    • The data path stays 4-state from monitor to compare: logic mailboxes, queue and locals. Case inequality then makes an X in out_data fail loudly instead of matching by luck. A bit anywhere on the path would turn the X into 0 first.

    • Pop-with-empty-model is its own error: a spurious out_valid, not a data difference.

    • The drain check compares leftover model entries against DUT occupancy, catching pushes the DUT swallowed without storing.

    Common pitfall

    Three classic misses: sampling on valid alone, which double-counts every stalled beat; forgetting flush on mid-test reset, which misfires every later compare; and skipping the drain check, which lets swallowed pushes pass silently.

    Interview

    Likely question: “Why not check against what the generator sent?”

    • Independence. Generator intent says what should happen. Pins say what did.

    • Reuse. A pin-watching scoreboard rides along at subsystem level unchanged.

    • Diagnosis. Spurious-pop, data-difference and drain-leftover point at three different bugs.

    Check yourself The monitor samples a beat with in_valid high and in_ready low. Does it publish a word, and why?

    No. A transfer needs both high, so this beat never moved. Publishing it would double-count the model when ready arrives.

    Check yourself Reset asserts mid-test and the test forgets flush. What breaks?

    Every later compare. The model still holds discarded words while the DUT restarted empty, so matching data reports differences.

    Related topics

    • Assertions, Coverage and Closure

    Previous

    230 / 259

    Next: Assertions, Coverage and Closure

    Up next

    Up nextAssertions, Coverage and Closure

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Spec Analysis and Verification Plan