Case study · Design verification
How a strict scoreboard caught 3 real RTL bugs
Verification of a burst-capable AXI4 slave using SystemVerilog and UVM. The testbench predicts every beat exactly, so when the RTL got something wrong, it had nowhere to hide.
- 3RTL bugs found
- 14SystemVerilog assertions
- 100%functional coverage
- 8tests on Cadence Xcelium
The design under test
An AXI4 slave in front of a 128-byte, byte-addressable memory. It accepts FIXED, INCR and WRAP bursts of 1–16 beats, with 1-, 2- or 4-byte beats and per-byte write strobes, handles one outstanding transaction per direction, and checks AXI3-style WID against AWID. Anything illegal (an out-of-range address, an illegal size, a reserved burst type) must come back as DECERR.
The testbench
| Component | Role |
|---|---|
| Driver | Master BFM: clocking-block handshakes, random BREADY/RREADY backpressure, reset abort |
| Monitor | Passively rebuilds every burst from bus handshakes, so checking never trusts the driver |
| Reference model + scoreboard | Golden byte-accurate memory; checks RDATA, RRESP, RID, RLAST, BRESP and BID on every beat |
| Coverage | Burst type × size × length cross, alignment, strobe shape, responses |
| Interface assertions | 14 SVA properties: handshake rules, payload stability, reset behavior, no X |
Strategy: predict every beat, exactly
- No “either answer is fine” checks. The scoreboard's golden memory predicts the exact response and data for every beat. Loose checking is precisely what had masked the first bug.
- Random for the legal space, directed for the corners. Constrained-random bursts cover every type, size and length. Directed sequences inject what the constraints deliberately exclude: out-of-range addresses, illegal sizes, reserved burst types, bursts that run off the end of memory, a mismatched
WIDand an earlyWLAST. - Assertions for what transactions can't show. The 14 interface assertions cover handshake rules, payload stability while stalled, VALID retraction, reset behavior and X-propagation.
- A silent pass is not a pass. A test that observes zero transactions fails by design.
Three bugs, found and reproduced
The RTL is deliberately left unfixed in the repo, so each bug can be reproduced live with make bugs. The log lines below are real output from Cadence Xcelium.
Bug #1
The error response arrives one beat late
- Symptom
- A read burst that runs past the end of memory hits address
0x80on beat 2 but returnsOKAY;DECERRonly shows up a beat later. A write burst whose final beat is the first illegal one gets anOKAYwrite response. - Root cause
- The error flag is set with a non-blocking assignment and read back in the same
always_ffevaluation, so the response logic sees the previous value, not the one just computed. - How it was caught
- Tightening the scoreboard to expect the exact response on every beat, plus directed bursts built to turn illegal mid-burst. Bursts that are illegal from beat 0 return
DECERRcorrectly, so only this case exposes it. - Fix direction
- Compute the error for the current beat combinationally, or register the response one cycle later. Never read an NBA-updated flag in the block that wrote it.
UVM_ERROR axi_scoreboard.sv(119) [AXI_SCB] RRESP mismatch beat2 exp=DECERR got=0 (addr=0x80 len=3 size=2 burst=AXI_BURST_INCR)
UVM_ERROR axi_scoreboard.sv(77) [AXI_SCB] BRESP mismatch exp=DECERR got=0 (addr=0x74 len=3 size=2 burst=AXI_BURST_INCR wid_mm=0 wlast_mm=0)
Bug #2
The write FSM deadlocks on an early WLAST
- Symptom
- When a master ends a write burst one beat early, the slave never returns a write response. No
BVALID, no recovery: the write channel is locked. - Root cause
- An early
WLASTonly sets the error flag. The write FSM still waits for the beat counter to reachAWLEN+1, so it never moves on to the response state. - How it was caught
- A directed early-
WLASTtest with a 20 µs watchdog, backed by the scoreboard's zero-transaction guard, which refuses to call a burst that never completed a pass. - Fix direction
- Treat
WLASTas the burst terminator: on an earlyWLAST, go straight to the response state with an error response.
UVM_ERROR axi_tests.sv(208) [BUG2_HANG] DUT hung after early WLAST: write FSM ignored WLAST and never returned BRESP (axi_slave.sv WR_DATA state)
UVM_ERROR axi_scoreboard.sv(151) [AXI_SCB] No transactions observed - the test drove nothing; a silent pass is not a pass
Bug #3
A rejected burst still writes to memory
- Symptom
- A burst whose
WIDdoesn't matchAWIDcorrectly getsDECERR, but its data is written to memory anyway. - Root cause
- The memory write is gated only by the per-beat size, burst and address checks, never by the
WIDmismatch detected on the same beat. - How it was caught
- The reference model predicted the whole burst would be rejected and kept the memory's reset value; the readback returned the “rejected” data instead. Constrained-random
WRAPbursts with an unaligned start address independently exposed the same commit-on-error behavior. - Fix direction
- Qualify the memory write with the accumulated per-beat error, or buffer the burst and commit it only once it's confirmed clean.
UVM_ERROR axi_scoreboard.sv(126) [AXI_SCB] RDATA mismatch beat0 addr=0x40 exp=0x0c0c0c0c got=0xc1d00000
UVM_ERROR axi_scoreboard.sv(126) [AXI_SCB] RDATA mismatch beat1 addr=0x44 exp=0x0c0c0c0c got=0xc1d00001
...
The tests
| Test | What it verifies | Result |
|---|---|---|
axi_smoke_test | Directed 4-beat INCR write and readback | Pass |
axi_random_test | 400 constrained-random bursts across every burst type, size and length, with random strobes and backpressure | Pass |
axi_backpressure_test | Bursty multi-cycle BREADY/RREADY stalls that stress the stability assertions | Pass |
axi_b2b_test | Zero-idle back-to-back write/read pairs | Pass |
axi_reset_test | Reset in the middle of a write burst, then clean traffic after recovery | Pass |
axi_error_test | Out-of-range addresses, illegal sizes, reserved bursts, bursts crossing the end of memory | Finds Bug #1 |
axi_wid_test | A WID ≠ AWID burst, then readback | Finds Bug #3 |
axi_early_wlast_test | The master ends a write burst one beat early | Finds Bug #2 |
Results
- The five regression tests end with zero UVM errors.
- The three bug-hunting tests fail on purpose, each with its documented signature, so every bug stays reproducible.
- 100% functional coverage via constrained-random and directed error-injection stimulus, with 14 interface assertions watching every handshake.