Analysis Ports & Scoreboards
Connect components with TLM analysis ports and build scoreboards that verify DUT correctness.
Analysis Ports & Scoreboards
Analysis ports are the publish-subscribe communication mechanism in UVM. Scoreboards use them to receive transactions and verify DUT behavior.
How Analysis Ports Work
An analysis port broadcasts transactions to all connected subscribers. It's a one-to-many, non-blocking channel.
text┌──▶ Scoreboard Monitor ──▶ ap ──────┼──▶ Coverage Collector └──▶ Logger
Port Types
| Type | Direction | Purpose |
|---|---|---|
uvm_analysis_port #(T) | Producer → | Broadcasts transactions |
uvm_analysis_imp #(T, C) | → Consumer | Receives transactions (calls write()) |
uvm_analysis_export #(T) | Pass-through | Routes between hierarchy levels |
Building a Scoreboard
A typical scoreboard receives from two sources: expected (reference model) and actual (DUT output).
systemverilog// Step 1: Declare the two analysis implementations `uvm_analysis_imp_decl(_expected) `uvm_analysis_imp_decl(_actual) class my_scoreboard extends uvm_scoreboard; `uvm_component_utils(my_scoreboard) // Two input ports uvm_analysis_imp_expected #(my_txn, my_scoreboard) expected_imp; uvm_analysis_imp_actual #(my_txn, my_scoreboard) actual_imp; // Internal state my_txn expected_q[$]; // Queue of expected transactions int match_count = 0; int mismatch_count = 0; function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); expected_imp = new("expected_imp", this); actual_imp = new("actual_imp", this); endfunction // Called when an expected transaction arrives function void write_expected(my_txn txn); expected_q.push_back(txn); endfunction // Called when an actual transaction arrives function void write_actual(my_txn txn); my_txn exp; if (expected_q.size() == 0) begin `uvm_error("SB", "Received actual but no expected!") mismatch_count++; return; end exp = expected_q.pop_front(); if (txn.data === exp.data && txn.addr === exp.addr) begin match_count++; `uvm_info("SB", $sformatf( "MATCH: addr=0x%h data=0x%h", txn.addr, txn.data ), UVM_MEDIUM) end else begin mismatch_count++; `uvm_error("SB", $sformatf( "MISMATCH: addr=0x%h exp=0x%h act=0x%h", txn.addr, exp.data, txn.data )) end endfunction // Print summary at end of simulation function void report_phase(uvm_phase phase); `uvm_info("SB", $sformatf( "Results: %0d matches, %0d mismatches", match_count, mismatch_count ), UVM_NONE) if (mismatch_count > 0) `uvm_error("SB", "TEST FAILED") else `uvm_info("SB", "TEST PASSED", UVM_NONE) endfunction endclass
Wiring It All Together
systemverilog// In the environment's connect_phase: function void connect_phase(uvm_phase phase); // Monitor output → Scoreboard actual input agent.monitor.ap.connect(scoreboard.actual_imp); // Reference model output → Scoreboard expected input ref_model.ap.connect(scoreboard.expected_imp); endfunction
Out-of-Order Scoreboard
For protocols where responses arrive out of order, use an associative array:
systemverilogmy_txn expected_map[bit [31:0]]; // Keyed by transaction ID function void write_expected(my_txn txn); expected_map[txn.id] = txn; endfunction function void write_actual(my_txn txn); if (!expected_map.exists(txn.id)) begin `uvm_error("SB", $sformatf("Unexpected txn id=%0d", txn.id)) return; end if (txn.data !== expected_map[txn.id].data) `uvm_error("SB", "Data mismatch!") expected_map.delete(txn.id); // Remove matched entry endfunction
Checking for Leftovers
In check_phase, verify no expected transactions remain unmatched:
systemverilogfunction void check_phase(uvm_phase phase); if (expected_q.size() > 0) `uvm_error("SB", $sformatf( "%0d expected transactions were never matched!", expected_q.size() )) endfunction
Best Practices
- Always report results in
report_phase - Use
\uvm_errorfor mismatches — not\uvm_fatal(let the test finish) - Check for leftover transactions in
check_phase - Keep scoreboard logic simple — complex checking belongs in a reference model