AMBA APB · Module 15
APB Scoreboards
The APB scoreboard is the data-integrity checker: it predicts what each transaction should do using a reference model of the slave's registers — a shadow array updated on writes and queried on reads — then compares the DUT's actual PRDATA and PSLVERR against the prediction. It catches functional data bugs that protocol assertions never look at, and only works if the reference model mirrors the slave's register kinds (RW, RO, W1C).
The monitor tells you a transaction happened; the scoreboard tells you it was correct. Assertions and the monitor police protocol — did PENABLE rise on time, was PSEL one-hot, did the handshake complete. None of them know or care whether a read returned the right value. That is a different question — a data question — and the component that answers it is the scoreboard. The single idea to carry: the APB scoreboard predicts what each transaction should do using a reference model of the slave's registers, then compares the DUT's actual data and response against that prediction — catching functional data bugs no protocol check is even looking for. A read that returns stale data, a write that never committed, a PSLVERR that didn't fire when it should — all are protocol-legal and assertion-clean, and all are caught only by a scoreboard that knows what the answer should have been.
1. Problem statement
The problem is deciding whether the DUT's data behaviour is functionally correct — does a read return what was last written, does a write commit, does an error response appear exactly when the access is illegal — given only the stream of transactions the monitor observed on the bus.
This is not a protocol question. A perfectly legal APB transfer can carry completely wrong data: the handshake is textbook, PENABLE timing is flawless, PSLVERR is asserted only at completion — and the read still returns 0xDEAD when it should have returned 0xBEEF. Protocol checks (assertions, Module 15.2) prove the form of the transfer is legal; they are structurally blind to its content. So a second, independent checker is needed, and it must be able to answer three concrete questions for every transaction the monitor hands it:
- Did this read return the correct value? "Correct" is defined relative to history — the last write to that address, the reset value, or a hardware-driven value. Answering it requires remembering what should be there.
- Did this write take effect? A write itself produces no read-back data to check immediately; its correctness is confirmed only by a later read returning the written value. The checker must carry the write's effect forward.
- Did the response match the expected error condition? A read to an unmapped address should set
PSLVERR; a normal access should not. The checker must predict whether each access is legal and confirmPSLVERRmatches.
To answer all three, the checker needs a model of what the slave should be holding at every moment — and that model, plus the compare logic around it, is the scoreboard.
2. Why previous knowledge is insufficient
By this point you can observe and police the bus, but observation and policing are not checking-against-intent:
- The monitor reconstructs transactions; it does not judge their data. In Module 15.3 the monitor samples the bus and rebuilds each transfer into a transaction object, then broadcasts it on its analysis port. That is exactly the input the scoreboard needs — but the monitor stops at "here is a transaction
{addr, data, write, slverr}." It has no memory of prior writes and no notion of what should have come back. The scoreboard is the consumer that adds judgement to the monitor's reporting. - Assertions check protocol, and are blind to data by design. The assertions from Module 15.2 — phase sequencing, signal stability, the ready handshake,
PSLVERRlegality — operate on signal relationships, not on whether a value is the right value. An assertion can prove the transfer was a legal read; it has no way to know the returned data was wrong, because "right data" is not a protocol property. This is the division of labour to internalise: SVA checks protocol legality; the scoreboard checks data correctness. Neither can do the other's job. - Knowing the register map is not the same as having a live model of it. You learned register kinds — read-write, read-only, write-1-clear — in CSR design fundamentals. That is static knowledge of what each register does. The scoreboard needs that knowledge made live: a running, address-indexed model that updates on every write and is queried on every read, mirroring the slave cycle by cycle. Static spec knowledge in your head does not check anything; an executable reference model does.
So the missing piece is the reference model — an executable, address-indexed predictor of the slave's state — wrapped in predict-then-compare logic. Downstream, the same transaction stream the scoreboard consumes also feeds functional coverage (Module 15.5), and the whole scoreboard lives inside the UVM APB agent / environment (Module 15.6) that wires the monitor's analysis port to the scoreboard's import.
3. Mental model
The model: the scoreboard is a shadow accountant running a duplicate ledger. The DUT keeps the real books (its registers); the scoreboard keeps a predicted copy. Every time a write transaction passes, the accountant posts the same entry to the shadow ledger — predicting the new balance. Every time a read transaction passes, the accountant looks up the predicted balance and compares it against the figure the DUT actually reported. Two ledgers that should always agree; the instant they diverge on a read, the accountant has found a data bug — and the divergence is in whichever ledger was kept wrong (usually the DUT, sometimes the model itself).
Three refinements make it precise:
- The reference model is a shadow register array indexed by address. Concretely, a SystemVerilog associative array
bit [31:0] predicted_regs[addr](or a UVM RAL model). It holds, for every address the slave implements, the value a read should return right now. It is the accountant's ledger — the entire memory the scoreboard has. - Writes predict, reads compare — they are not symmetric. A write transaction is an update to the model:
predicted_regs[addr]is set to reflect the write's effect, and nothing is compared (a write returns no data to check). A read transaction is a check: the model is queried for the expected value and that is compared against the actualPRDATA; the model is not updated by a normal read. Treating reads and writes the same way is a category error. - The model must mirror the slave's register kinds, not just store bytes. A read-write register reads back the written value, so the model stores it verbatim. A read-only register ignores writes and reflects a hardware-driven value, so the model must not store the written value. A write-1-clear register clears the bits you write
1to, so the model appliesclear = old & ~write_data. If the model treats every register as plain read-write, it predicts the wrong value for RO/W1C registers and manufactures false mismatches — the model and the slave must agree on semantics, not just addresses.
4. Real SoC implementation
In a UVM environment the scoreboard is a uvm_scoreboard (or plain uvm_component) with a uvm_analysis_imp connected to the monitor's analysis port. Every reconstructed transaction lands in write(), which branches on read vs write: a write predicts into the reference model, a read compares the model's prediction against the DUT's actual data. The reference model is the associative array predicted_regs[addr], and the write() logic must mirror each register's kind — here RW (reads back the written value) and the special kind RO (a hardware-driven read-only register that ignores writes), with a W1C case shown for completeness.
// APB data-integrity scoreboard. Consumes monitor transactions via an
// analysis import, predicts on writes, compares on reads. The reference
// model (predicted_regs) MIRRORS the slave's register kinds — get the kind
// wrong and you manufance false mismatches (see Module 15.4 debug scenario).
typedef enum { REG_RW, REG_RO, REG_W1C } reg_kind_e; // mirror the CSR map
`uvm_analysis_imp_decl(_apb) // names the imp's callback write_apb()
class apb_scoreboard extends uvm_scoreboard;
`uvm_component_utils(apb_scoreboard)
// analysis import — the monitor's analysis port connects here (15.3)
uvm_analysis_imp_apb #(apb_txn, apb_scoreboard) item_imp;
// THE REFERENCE MODEL: shadow register array, indexed by address.
// Holds the value a read SHOULD return right now.
bit [31:0] predicted_regs [bit [31:0]];
// static map of each implemented address to its register KIND + reset value
reg_kind_e reg_kind [bit [31:0]];
bit [31:0] hw_status [bit [31:0]]; // shadow of HW-driven RO values
int unsigned reads = 0, writes = 0, mismatches = 0;
function new(string name, uvm_component parent);
super.new(name, parent);
item_imp = new("item_imp", this);
endfunction
// load the register map so the model mirrors the slave's semantics.
// In a real env this is generated from the IP-XACT / register spec,
// or replaced entirely by a UVM RAL model (uvm_reg) that predicts for us.
function void build_phase(uvm_phase phase);
super.build_phase(phase);
reg_kind[32'h00] = REG_RW; predicted_regs[32'h00] = 32'h0; // CTRL
reg_kind[32'h04] = REG_RO; predicted_regs[32'h04] = 32'h0; // STATUS
reg_kind[32'h08] = REG_W1C; predicted_regs[32'h08] = 32'h0; // IRQ
endfunction
// EVERY monitor transaction arrives here. Predict on write, compare on read.
function void write_apb(apb_txn t);
if (!predicted_regs.exists(t.addr)) begin
// unmapped address: the slave should have flagged an error
check_error_only(t);
return;
end
if (t.write) predict_write(t); // WRITE -> update the model
else compare_read(t); // READ -> check model vs DUT
endfunction
// ---- PREDICT (writes) : update predicted_regs per the register KIND ----
function void predict_write(apb_txn t);
writes++;
case (reg_kind[t.addr])
REG_RW : predicted_regs[t.addr] = t.wdata; // reads back
REG_RO : /* ignore: RO ignores writes, model unchanged */ ;
REG_W1C : predicted_regs[t.addr] &= ~t.wdata; // write-1-clear
endcase
// a normal write to a mapped, writable address must NOT raise PSLVERR
if (t.slverr)
`uvm_error("SB_WR_ERR",
$sformatf("unexpected PSLVERR on write @0x%0h", t.addr))
endfunction
// ---- COMPARE (reads) : actual PRDATA vs predicted, and PSLVERR ----
function void compare_read(apb_txn t);
bit [31:0] exp;
reads++;
// RO registers reflect the hardware value, not anything written
exp = (reg_kind[t.addr] == REG_RO) ? hw_status[t.addr]
: predicted_regs[t.addr];
if (t.slverr)
`uvm_error("SB_RD_ERR",
$sformatf("unexpected PSLVERR on read @0x%0h", t.addr))
else if (t.rdata !== exp) begin
mismatches++;
`uvm_error("SB_DATA_MISMATCH",
$sformatf("READ @0x%0h : actual=0x%0h expected=0x%0h",
t.addr, t.rdata, exp))
end
else
`uvm_info("SB_MATCH",
$sformatf("READ @0x%0h = 0x%0h OK", t.addr, t.rdata), UVM_HIGH)
endfunction
// an access to an UNMAPPED address must set PSLVERR (the predicted error)
function void check_error_only(apb_txn t);
if (!t.slverr)
`uvm_error("SB_NO_ERR",
$sformatf("expected PSLVERR on access to unmapped 0x%0h", t.addr))
endfunction
// end-of-test consistency check (see Verification perspective)
function void check_phase(uvm_phase phase);
if (reads == 0)
`uvm_warning("SB_EMPTY", "scoreboard saw no reads — nothing was checked")
endfunction
endclassTwo facts make this the right shape. First, the write() method is the entire scoreboard, and it is asymmetric on purpose — writes mutate the model, reads interrogate it, and the split is the predict-then-compare principle made executable. Second, the reference model is only correct if it mirrors register kinds — the case (reg_kind[...]) is not decoration, it is the difference between a scoreboard that catches real data bugs and one that drowns the log in false mismatches. In production you often replace the hand-rolled array with a UVM RAL model (uvm_reg), which already encodes every register's kind and does the predict-on-write for you via its predictor — but the concept is identical: a live, kind-aware shadow of the slave's registers. The same transaction stream is also tapped for functional coverage.
5. Engineering tradeoffs
Building the scoreboard is a sequence of judgement calls about the reference model's fidelity, structure, and ordering.
| Decision | Option A | Option B | When to choose which |
|---|---|---|---|
| Reference-model form | Hand-rolled predicted_regs[addr] associative array | UVM RAL (uvm_reg) model + predictor | RAL for any real register map — it encodes kinds and reset values once, spec-generated; hand-roll only for a tiny map or to teach the mechanism |
| Register-kind fidelity | Model every kind exactly (RW/RO/W1C/RC…) | Model all as RW and "tolerate" mismatches on RO/W1C | Model exactly — a "tolerated" mismatch is indistinguishable from a real bug; RW-everything makes the scoreboard worse than useless on status registers |
| Compare timing | Compare in write() as each read arrives | Queue expected/actual and compare at end | In-order APB has one outstanding transfer, so compare-on-arrival is simplest and correct; queueing is only for pipelined/out-of-order fabrics |
| RO / HW-driven values | Model a separate hw_status[] shadow updated by a backdoor/predictor | Mark RO addresses "uncheckable" and skip them | Shadow the HW value when you can predict it (counters, fixed status); skip-checking RO is a real, deliberate coverage hole, not a default |
| Mismatch on first vs every txn | Report every mismatch | Report once then suppress duplicates | Report every one during bring-up (you want the count), suppress duplicates only in mature regressions to keep logs readable |
The throughline: a good scoreboard is kind-accurate, in-order-simple, and spec-derived. The single highest-leverage decision is register-kind fidelity — because a model that doesn't mirror RO/W1C semantics doesn't fail safe, it fails loud and wrong, burying real bugs under false ones. Reaching for a RAL model is usually the right call precisely because it makes kind-accuracy the default instead of a thing you can forget.
6. Common RTL mistakes
7. Debugging scenario
The signature scoreboard failure is a false-mismatch flood: the scoreboard screams data bugs on a register the DUT is driving perfectly, because the reference model — not the DUT — is wrong.
- Observed symptom: a regression that was clean suddenly reports hundreds of
SB_DATA_MISMATCHerrors, and they all hit one register — theSTATUSregister at0x04. Every read ofSTATUS"fails"; reads ofCTRLand every other register pass. The failures are perfectly correlated with one address, and the count scales with how many times the test pollsSTATUS. - Waveform clue: on the bus,
STATUSreads return a hardware-driven value (say0x01, a busy flag the slave's logic sets), and the protocol is flawless —PENABLE,PREADY,PSLVERRall legal, so every assertion is green. But the scoreboard's expected value is whatever the test last wrote to0x04(0xAA). The DUT is returning the correct hardware value; the prediction is the written value, and the two never agree. - Root cause: the reference model treats
STATUSas read-write when it is read-only / hardware-driven. On the write to0x04,predict_write()ran theREG_RWbranch and stored0xAAintopredicted_regs[0x04]; on every subsequent read it predicted0xAAwhile the DUT correctly returned its hardware value. The bug is in the reference model's register-kind modelling, not in the DUT — the slave is behaving exactly to spec, and the scoreboard is the thing that's wrong. - Correct RTL: there is no RTL fix — the DUT is correct. The fix is in the reference model: mark
0x04asREG_ROso writes are ignored, and predict the hardware value instead of the written one:
// reference-model fix: STATUS is RO/HW-driven, not RW
reg_kind[32'h04] = REG_RO; // was wrongly REG_RW
function void predict_write(apb_txn t);
case (reg_kind[t.addr])
REG_RW : predicted_regs[t.addr] = t.wdata; // CTRL: reads back
REG_RO : /* ignore the write — RO does not store it */ ; // STATUS
REG_W1C : predicted_regs[t.addr] &= ~t.wdata;
endcase
endfunction
// on read, RO reflects the hardware shadow, not predicted_regs
exp = (reg_kind[t.addr] == REG_RO) ? hw_status[t.addr]
: predicted_regs[t.addr];- Verification assertion: the deeper guard is traceability between the model and the register spec. Add a build-time check that every address in
reg_kind[]matches the kind in the authoritative register map (IP-XACT / RAL) —assert (reg_kind[addr] == ral_model.get_kind(addr))for every address — so a miscoded kind is caught at elaboration, not by a regression flood. Migrating to a RAL model removes the class of bug entirely, because the kind is generated from the spec, not hand-typed. - Debug habit: when the scoreboard floods mismatches on one register with a consistent pattern while every assertion stays green, suspect the model before the DUT. A protocol-clean bus plus mass data-mismatch on a single address is the fingerprint of a reference-model kind error — RO modelled as RW, or W1C as RW. Confirm by hand: read the spec's kind for that address and check what the model predicts versus what the hardware drives. The fix lives in the model; the DUT is innocent until the model is proven right.
8. Verification perspective
The scoreboard is itself a piece of verification logic, so it has to be verified — a wrong reference model is worse than no scoreboard, because it asserts false confidence.
- Validate that the reference model mirrors the spec / register map. The model's correctness is its fidelity to the register map: every address must carry the right kind (RW/RO/W1C/RC), the right reset value, and the right hardware-driven behaviour. The strongest defence is to generate the model from the authoritative source (IP-XACT, or a register-map / RAL model) rather than hand-code it, and to add a build-time equivalence check that the hand-tuned parts agree with that source. A reference model reviewed against the spec line-by-line is the precondition for trusting any result.
- Prove the scoreboard catches injected data bugs. A scoreboard that never fires might be perfect — or broken. Deliberately inject errors (mutate the DUT's read data, drop a write commit, suppress an expected
PSLVERR) and confirm the scoreboard reports each one. This is the scoreboard's own "does it detect?" test, analogous to fault-injection, and it is the only way to know silence means correct, not blind. - Distinguish scoreboard-model bugs from DUT bugs on every mismatch. A mismatch means model and DUT disagree; the investigation is always "which is right?" The tell-tales: a mismatch isolated to one address with a consistent pattern, or one that appears the moment you added a register, usually indicts the model; a mismatch that tracks a specific traffic pattern, corner case, or back-to-back sequence usually indicts the DUT. Confirm against the spec before filing a DUT bug.
- Run end-of-test empty and consistency checks. In
check_phase, assert the scoreboard actually saw transactions (reads > 0, the import was connected) and that no expected response is left unmatched. An empty scoreboard at end-of-test — zero reads compared — is a silent hole: the run was green only because nothing was ever checked. This check converts "the scoreboard said nothing" from ambiguous into meaningful.
The point: you verify the scoreboard by validating its reference model against the spec, by proving it detects injected bugs, and by end-of-test consistency checks — because a scoreboard you haven't verified is a confidence generator, not a checker.
9. Interview discussion
"What does an APB scoreboard do, and how does it know what's correct?" separates candidates who have built a verification environment from those who have only read about one.
Lead with the division of labour: assertions and the monitor check protocol legality; the scoreboard checks data correctness — they are independent and neither can do the other's job. Then state the mechanism: the scoreboard predicts what each transaction should do using a reference model of the slave's registers — a shadow, address-indexed array updated on every write and queried on every read — then compares the DUT's actual PRDATA and PSLVERR against the prediction. Make the asymmetry explicit, because it is the heart of it: a write predicts (updates the model, compares nothing); a read compares (queries the model, checks actual against expected). The senior depth is register kinds: the reference model must mirror the slave's register semantics — RW reads back, RO ignores writes and follows hardware, W1C clears on write-one — or it manufactures false mismatches, and the canonical bug is modelling a hardware-driven status register as RW so every read falsely fails. Close with the engineering reality: "in production the reference model is usually a UVM RAL model, which encodes every register's kind from the spec and does the predict-on-write for me — and a mismatch is always model versus DUT disagree, so the first question is which one is wrong, not 'file a DUT bug.'" That last line — that a mismatch indicts the model as readily as the DUT — is the mark of someone who has actually debugged a scoreboard.
10. Practice
- Predict vs compare. For a sequence
write 0x00=0x5; read 0x00; write 0x00=0x9; read 0x00, write out what the reference model holds and what the scoreboard compares at each step, for an RW register at0x00. - Model the kinds. Given a
CTRL(RW), aSTATUS(RO, hardware-driven), and anIRQ(W1C) register, write thepredict_writecaseand the read-side expected-value logic for all three. - W1C trace.
IRQstarts at0x0F. Trace the reference-model value and the expected read result for:read IRQ; write IRQ=0x05; read IRQ; write IRQ=0x0A; read IRQ. - Catch the injected bug. Describe how you'd inject a "write that never commits" bug into the DUT and what the scoreboard would report on the following read — and which transaction's prediction proves the write failed.
- Diagnose the flood. Your scoreboard reports mismatches on only the
INT_STATUSregister, all with the same pattern, while every assertion passes. State the most likely root cause, whether it's a DUT or model bug, and the one-line fix.
11. Q&A
12. Key takeaways
- The scoreboard checks DATA correctness; assertions and the monitor check PROTOCOL legality — they are independent, and a legal APB transfer can still carry wrong data that only the scoreboard catches.
- The scoreboard predicts with a reference model — a shadow, address-indexed array of the slave's registers (or a UVM RAL model) — then compares the DUT's actual
PRDATAandPSLVERRagainst the prediction. - Writes predict, reads compare — a write updates the model and compares nothing; a read queries the model and checks actual against expected. Treating them symmetrically corrupts the checking.
- The reference model must mirror the slave's register kinds — RW reads back, RO ignores writes and follows hardware, W1C clears on write-one — or it manufactures false mismatches on exactly the status/interrupt registers that have special semantics.
- A mismatch is a disagreement between model and DUT, not automatically a DUT bug — a flood on one register with a consistent pattern usually indicts the model (a miscoded kind), so validate the model against the spec before filing a DUT bug.
- Verify the scoreboard itself — generate or validate the reference model against the register map, prove it detects injected data bugs, and run end-of-test empty/consistency checks so that silence means correct, not blind.