AXI has five independent channels, bursts of three types, narrow and unaligned transfers, outstanding transactions and per-beat responses. A slave can be wrong in any of them, and a hand-written test that checks a few words will not find most bugs. AXI verification combines three things: random traffic that covers the burst shapes, a byte-level model that predicts every read, and protocol checks that watch all five channels on every clock edge. This article builds all three in plain Verilog and uses them to find a bug in the memory slave.
9 min read | Intermediate
What You'll Learn
- Generate random AXI bursts that stay legal
- Predict read data with a byte-level reference model
- Check the AXI handshake and burst rules on every edge
- Know what verification IP adds on top
The Testbench
The master is the one used throughout the AXI chapter: a command list per channel, so that address, data and responses move independently. The DUT is the memory slave from Designing an AXI4 Memory Slave. Around them sit the stimulus, the model and scoreboard, and the protocol checker. Each check counts errors, and the test ends with a single PASS or FAIL line.
Random but Legal Traffic
The stimulus writes 24 bursts and then reads every one of them back. Each burst has a random type, beat size and length, and its own 64-byte region, so that the data of different bursts never overlaps:
for (k = 0; k < count; k = k + 1) begin
t = {$random(seed)} % 3;
burst = (t == 0) ? FIXED : (t == 1) ? INCR : WRAP;
size = {$random(seed)} % 3; // 1, 2 or 4 bytes per beat
bytes = 12'd1 << size;
if (burst == WRAP)
len = (8'd2 << ({$random(seed)} % 4)) - 8'd1; // 2, 4, 8 or 16 beats
else
len = {$random(seed)} % 8; // 1 to 8 beats
// every burst gets a 64-byte region of its own; the start is aligned to the beat size
a = 12'h040 * k + (({$random(seed)} % 12'h40) & ~(bytes - 12'd1));
if (burst != WRAP && ((a & 12'h03F) + bytes * (len + 12'd1)) > 12'h040)
a = 12'h040 * k; // keep INCR and FIXED inside the region
write(k % 16, a, len, size, burst, $random(seed), 0, 0, 0);
end
The constraints follow the protocol: WRAP bursts get only legal lengths and an aligned start, and no burst leaves its region, so none can cross a 4 KB boundary. Random BREADY and RREADY patterns add backpressure. The seed makes every run repeatable.
A Byte-Level Model
With narrow and unaligned bursts, the model must work in bytes. It follows the W channel: because AXI4 never interleaves write data, the W beats belong to the write transactions in the order of their addresses, so the model can compute the address of every beat with the same next-address function the slave uses, and store the bytes whose strobes are set:
if (WVALID && WREADY) begin
if (wb == 0)
wa = aw_addr[wt];
for (b = 0; b < 4; b = b + 1)
if (WSTRB[b])
model[(wa & ~12'd3) + b] = WDATA[8*b +: 8];
if (WLAST) begin
wb = 0;
wt = wt + 1;
end
else begin
wb = wb + 1;
wa = next_addr(wa, aw_size[wt], aw_burst[wt], aw_len[wt]);
end
end
The read side does the same for R beats and compares the active byte lanes of each beat with the model. Reads are only issued after every write response has arrived, because AXI does not order reads against writes.
Quick Check
Q: The model uses the testbench's own copy of next_addr. Doesn't that hide a bug if the slave's copy is wrong in the same way?
Show Answer
It can, if the testbench copy is taken from the RTL. The model's address calculation must be written from the specification's burst address equations and checked against them on its own. Here both copies follow those equations, and the seeded bug below lives only in the slave's copy, so the two disagree.
Protocol Checks
The checker samples all five channels on every edge and compares with the previous edge:
// Once VALID is high it stays high, with stable information, until READY
if (p_awv && !p_awr && (!AWVALID || AWADDR != p_awaddr))
fail("AWVALID dropped or AWADDR changed before AWREADY");
if (p_rv && !p_rr && (!RVALID || RDATA != p_rdata))
fail("RVALID dropped or RDATA changed before RREADY");
// ... the same for W, AR and B ...
// WRAP bursts have 2, 4, 8 or 16 beats
if (AWVALID && AWBURST == WRAP && AWLEN != 1 && AWLEN != 3 && AWLEN != 7 && AWLEN != 15)
fail("AW WRAP burst length is not 2, 4, 8 or 16");
// WLAST exactly on beat AWLEN + 1, RLAST exactly on beat ARLEN + 1, RID = ARID
if (RVALID && RREADY) begin
if (RLAST != (r_beats == ar_len[r_tr]))
fail("RLAST on the wrong beat");
if (RID != ar_id[r_tr])
fail("RID does not match ARID");
// ... count the beats ...
end
// A write response only after the last data beat of its transaction
if (BVALID && !p_bv && bn >= w_tr)
fail("BVALID before the last write data beat");
Some checks watch the master (address stability, WRAP lengths, WLAST), others the slave (RVALID stability, RLAST, RID, the write response dependency). This checker is simplified for an in-order, single-slave setup; a full one tracks outstanding transactions per ID.
Finding the Bug
With the correct slave:
PASS: 24 bursts written and read back, 252 bytes checked, no errors
The example's slave has a one-line bug. The same test fails:
1945 MISMATCH read id=0 beat 0 at 0x030 byte 0: got 0x9c, expected 0x96 1955 MISMATCH read id=0 beat 1 at 0x032 byte 2: got 0x9d, expected 0x97 1965 MISMATCH read id=0 beat 2 at 0x034 byte 0: got 0x9c, expected 0x98 1975 MISMATCH read id=0 beat 3 at 0x036 byte 2: got 0x9d, expected 0x99 FAIL: 53 errors, 252 bytes checked
The protocol checker is silent, so the traffic is legal and the problem is in the data. The failing beats walk through consecutive halfword addresses, and the expected values count up beat by beat while the received ones repeat: the slave wrote the beats of a wrapping burst to the wrong addresses. The cause is in the slave's wrap calculation, which uses len instead of len + 1 beats for the size of the wrap block:
wrap_bytes = bytes * len; // should be bytes * (len + 1)
INCR and FIXED bursts are unaffected, which is why a test that uses only INCR bursts would pass.
Verification IP
Commercial and open AXI verification IP packages these pieces: a master and slave agent that can generate any legal traffic, a monitor, a protocol checker with hundreds of rules (often as SystemVerilog assertions), coverage of burst types, sizes, lengths, IDs and response types, and a scoreboard framework. In UVM, the structure is the same as here: the command lists become sequences and a driver, the checker sits in the monitor or the interface, and the byte model becomes a scoreboard component. The UVM agents in UVM AHB-Lite Master Agent show the pattern for AHB.
Quick Check
Q: Which coverage points would show that the random test really exercised the WRAP bug?
Show Answer
Burst type crossed with length and size: at least one WRAP burst of each legal length (2, 4, 8, 16) and each size, ideally with a start address that is not the start of the wrap block. Without that coverage, a passing test might simply never have issued a WRAP burst.
Running It in ChipVerify Lab
The example contains the buggy slave and this testbench. Read the mismatches and find the bug before you look at the wrap calculation.
Common Mistakes
Mistake #1: Reading back before the write response
AXI does not order a read after a write, even with the same ID. A scoreboard that checks reads issued before the write response arrived reports false mismatches, or hides real ones.
Mistake #2: A model at word level
Narrow and unaligned writes change only some bytes. A word-level model cannot predict them; model memory in bytes and compare only the active lanes.
Mistake #3: Random traffic without coverage
A random test that passes says nothing about the cases it never generated. Measure which burst types, lengths, sizes and responses actually occurred.
Practice Exercise
Challenge: An Early RLAST
Fix the wrap bug, then plant a different one: make the slave raise RLAST one beat early for 4-beat bursts. Which check fires first?
Hint
Change the line that sets RLAST in the read engine.
Solution
RLAST <= (r_left == 8'd0) || (r_len == 8'd3 && r_left == 8'd1); // RLAST one beat early for 4-beat bursts
2455 PROTOCOL: RLAST on the wrong beat 2465 MISMATCH read id=9 beat 0 at 0x2a8 byte 0: got 0xac, expected 0x9c 2465 MISMATCH read id=9 beat 0 at 0x2a8 byte 1: got 0x25, expected 0x86 2465 MISMATCH read id=9 beat 0 at 0x2a8 byte 2: got 0x9b, expected 0xa2 FAIL: 236 errors, 270 bytes checked
The protocol checker fires on the first 4-beat read. After that, the master and the scoreboard think the burst has ended and pair the remaining beat with the next transaction, so mismatches cascade. The first protocol error points at the real cause; the hundreds of mismatches after it are only its effect.
Open the ready-made example in the lab and run the simulation.
Summary
- AXI verification needs random, constrained traffic covering every burst type, size and length.
- A byte-level model follows the W beats in order and checks the active lanes of every R beat.
- Protocol checks watch VALID stability, LAST signals, IDs and channel dependencies on all five channels.
- Read back only after the write response: AXI does not order reads against writes.
- Verification IP adds complete rule sets, coverage and reusable agents to the same structure.