TO Be coming

Oct 10, 2026

UVM : UVM_ScoreBoard

UVM TUTORIAL SERIES

🎯 UVM Scoreboard Explained

Study Notes • Working Code • Animated Demo

🔍 What is a UVM Scoreboard?

The Scoreboard is the self-checking brain of a UVM testbench. It receives transactions from the monitor, compares DUT behavior against a golden reference (predicted) result, and declares PASS or FAIL.

📥
CollectReceives actual results from the Monitor via TLM analysis ports
🧠
PredictContains a reference model that computes expected results
⚖️
CompareChecks actual vs expected and logs MATCH / MISMATCH
🏁
VerdictDeclares overall pass/fail in report_phase
💡 Golden Rule: A scoreboard never drives stimulus — it only observes and checks. It is always a uvm_component, never a sequence item.

📡 Where does the Scoreboard sit?

Two data streams flow into the scoreboard — the expected results (from a predictor/reference model) and the actual results (from the monitor). Watch them converge:

🧠 Reference Modelexpected results
👀 Monitoractual results
🎯
SCOREBOARD
✓ PASS
✗ FAIL

🔌 How transactions reach the Scoreboard (TLM)

TLM ConstructDirectionUsed By
uvm_analysis_portBroadcasts (many subscribers)Monitor
uvm_analysis_impReceives — calls write()Scoreboard
uvm_analysis_exportPass-through for hierarchiesEnv / Subscribers
uvm_subscriberBase class with built-in impScoreboard / Coverage
⚡ Key point: write() in the scoreboard is a function (zero delay), called automatically every time the monitor publishes a transaction.

⚙️ Scoreboard Internals — Live Demo

Expected and actual transactions arrive, get paired up, and the comparator fires its verdict:

EXPECTED QUEUE (from Reference Model)
E1
E2
E3
⚖️ COMPARE
✓ MATCH / ✗ MISMATCH
ACTUAL STREAM (from Monitor)
A1
A2
A3

💻 Example 1: Self-Checking ALU Scoreboard

Step 1 — The Transaction

// alu_txn.sv
class alu_txn extends uvm_sequence_item;
  rand bit [7:0] a, b;
  rand bit [1:0] op;        // 00:ADD 01:SUB 10:AND 11:OR
       bit [7:0] result;    // captured from DUT

  `uvm_object_utils_begin(alu_txn)
    `uvm_field_int(a,      UVM_ALL_ON)
    `uvm_field_int(b,      UVM_ALL_ON)
    `uvm_field_int(op,     UVM_ALL_ON)
    `uvm_field_int(result, UVM_ALL_ON)
  `uvm_object_utils_end

  function new(string name = "alu_txn");
    super.new(name);
  endfunction
endclass

Step 2 — The Scoreboard (predictor + checker inside)

// alu_scoreboard.sv
class alu_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(alu_scoreboard)

  // TLM imp — receives every transaction from the monitor
  uvm_analysis_imp #(alu_txn, alu_scoreboard) mon_imp;

  int match_count    = 0;
  int mismatch_count = 0;

  function new(string name, uvm_component parent);
    super.new(name, parent);
    mon_imp = new("mon_imp", this); // 'this' → imp calls our write()
  endfunction

  // ---------- Reference Model (Predictor) ----------
  function bit [7:0] predict(alu_txn t);
    case (t.op)
      2'b00 : return t.a + t.b;      // ADD
      2'b01 : return t.a - t.b;      // SUB
      2'b10 : return t.a & t.b;      // AND
      2'b11 : return t.a | t.b;      // OR
    endcase
  endfunction

  // ---------- Checker ----------
  // Called AUTOMATICALLY for every monitor transaction
  function void write(alu_txn t);
    bit [7:0] exp = predict(t);

    if (t.result === exp) begin
      match_count++;
      `uvm_info("SCOREBOARD",
        $sformatf("MATCH   : a=%0d b=%0d → %0d", t.a, t.b, t.result), UVM_LOW)
    end
    else begin
      mismatch_count++;
      `uvm_error("SCOREBOARD",
        $sformatf("MISMATCH: expected=%0d  got=%0d", exp, t.result))
    end
  endfunction

  // ---------- Final Verdict ----------
  function void report_phase(uvm_phase phase);
    `uvm_info("SCOREBOARD", $sformatf("Matches=%0d | Mismatches=%0d",
              match_count, mismatch_count), UVM_LOW)
    if (mismatch_count == 0)
      `uvm_info("SCOREBOARD", "*** TEST PASSED ***", UVM_LOW)
    else
      `uvm_error("SCOREBOARD", "*** TEST FAILED ***")
  endfunction
endclass

Step 3 — Connect it in the Environment

// alu_env.sv
class alu_env extends uvm_env;
  alu_agent      agent;
  alu_scoreboard sb;
  `uvm_component_utils(alu_env)

  function void build_phase(uvm_phase phase);
    agent = alu_agent::type_id::create("agent", this);
    sb    = alu_scoreboard::type_id::create("sb",    this);
  endfunction

  function void connect_phase(uvm_phase phase);
    agent.monitor.ap.connect(sb.mon_imp);  // ◀ THE magic line!
  endfunction
endclass

💻 Example 2: Queue-Based Scoreboard (2 data streams)

When expected and actual results arrive from different sources, use uvm_analysis_imp_decl to create two imps with separate write() methods, and store expected results in a queue:

// Declare imps with unique write method names
`uvm_analysis_imp_decl(_exp)
`uvm_analysis_imp_decl(_act)

class fifo_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(fifo_scoreboard)

  uvm_analysis_imp_exp #(mem_txn, fifo_scoreboard) expected_imp;
  uvm_analysis_imp_act #(mem_txn, fifo_scoreboard) actual_imp;

  mem_txn exp_q[$];              // golden queue

  function new(string name, uvm_component parent);
    super.new(name, parent);
    expected_imp = new("expected_imp", this);
    actual_imp   = new("actual_imp",   this);
  endfunction

  // From the reference model — store expected
  function void write_exp(mem_txn t);
    exp_q.push_back(t);
  endfunction

  // From the monitor — pop & compare (in-order)
  function void write_act(mem_txn t);
    if (exp_q.size() == 0) begin
      `uvm_error("SB", "Actual arrived with empty expected queue!")
      return;
    end
    mem_txn e = exp_q.pop_front();
    if (e.data === t.data)
      `uvm_info("SB", "MATCH ✓", UVM_HIGH)
    else
      `uvm_error("SB", $sformatf("MISMATCH exp=%0h got=%0h", e.data, t.data))
  endfunction
endclass
🔄 Out-of-order responses? Replace the queue with an associative array keyed by transaction ID, and delete the entry on match instead of popping.

Alternative: uvm_subscriber shortcut

class my_scoreboard extends uvm_subscriber #(alu_txn);
  `uvm_component_utils(my_scoreboard)
  // imp is built-in — just implement write():
  function void write(alu_txn t);
    // checking logic here
  endfunction
endclass

✅ Key Takeaways

✔ Scoreboard = predict + compare + verdict
✔ Uses analysis TLM connections (monitor.ap → sb.imp)
✔ write() is a function — no time-consuming code inside
✔ Use === (4-state compare) to catch X/Z values
✔ Use queues for in-order, associative arrays for out-of-order
✔ Final PASS/FAIL verdict goes in report_phase / check_phase

🎤 Interview Quick-Fire

Q: uvm_analysis_port vs uvm_analysis_imp?
A: Port broadcasts transactions (monitor side); imp receives them and invokes the attached component's write() (scoreboard side).
Q: Why must write() be a function, not a task?
A: Analysis traffic is instantaneous broadcast — it cannot block or consume simulation time.
Q: How do you handle responses that return out of order?
A: Key an associative array with a unique transaction ID; look up and delete on arrival.
Q: What happens if two scoreboards connect to one analysis port?
A: Both receive every transaction — analysis ports support multiple subscribers.
Q: Where should the predictor live?
A: Either inside the scoreboard, or as a separate component feeding an "expected" analysis stream — the second scales better for complex designs.

⭐ If this helped you, share it with your verification team! ⭐

No comments:

Post a Comment

SiteMap