Read first: Handshake & Backpressure: accepting edges and retained offers
Assume one AXI4 interface, one rising-edge clock and signals outside reset. AW, W, B, AR and R each have their own VALID/READY acceptance event. AWVALID && AWREADY accepts one write-address item; WVALID && WREADY accepts one write-data beat. There is no extra event called an 'AXI write handshake' that requires AW and W to be high together.
Channel independence permits overlap and buffering, but it does not promise progress by itself. A target may need accepted address context, data capacity or downstream service before it can advance another channel. State those resource dependencies separately from the protocol's VALID/READY rules.
Five channels, five acceptance events
AXI4 interface outside reset; field lists highlight the lesson's correlation and payload signals, not every signal in the specification.
Channel | Source presents | Destination accepts when |
|---|---|---|
AW | Address/control and AWID | AWVALID && AWREADY |
W | Data/strobes and WLAST | WVALID && WREADY |
B | Response and BID | BVALID && BREADY |
AR | Address/control and ARID | ARVALID && ARREADY |
R | Data/status, RID and RLAST | RVALID && RREADY |
What to notice
One channel's acceptance does not imply another channel accepted on that edge.
Reconstruct complete transactions from the required channel items, identities and ordering rules.
W. The accepted address is retained context; write data must still be accepted, including the required final beat, before the response prerequisites are met.
The traces below show single-beat writes with items A0 (address) and D0 (data). Each row is sampled at the named rising edge. The source retains a stalled offer; the target records whichever channel accepts first. These are arrival patterns, not three different write semantics.
An implementation must have a documented admission policy for early data. It may accept and buffer W before AW, or hold WREADY low until it has capacity/context. It must not require the source to wait for AWREADY before asserting WVALID, and it must not create B before both required acceptances occur.
Case 1: AW before W
Single-beat A0/D0 write, outside reset. V/R means sampled VALID/READY; D0 carries WLAST=1.
Edge | AW V/R | W V/R | Accepted | Stored after edge |
|---|---|---|---|---|
E1 | 1/1 | 0/0 | A0 | Address A0 |
E2 | 0/1 | 1/1 | D0 final | A0 + final data |
Case 2: W before AW
Edge | AW V/R | W V/R | Accepted | Stored after edge |
|---|---|---|---|---|
E1 | 0/0 | 1/1 | D0 final | Early data D0 |
E2 | 1/1 | 0/0 | A0 | A0 + final data |
Case 3: same accepting edge
Edge | AW V/R | W V/R | Accepted | Stored after edge |
|---|---|---|---|---|
E1 | 1/1 | 1/1 | A0 and D0 final | A0 + final data |
What to notice: Locate WVALID && WREADY and AWVALID && AWREADY independently. Early accepted W is retained work. BVALID appears later and premature_b remains clear.
No. The channels are independent. The response must wait for accepted AW and the accepted final W beat; the shown B satisfies that dependency.
What to notice
AW and W acceptance are counted independently even in Case 3.
All three cases satisfy the same prerequisites for B: address and final data accepted.
No. AXI4 permits W before AW. The target must retain accepted early W data, or backpressure W until it has capacity to honour the transfer.
A channel source retains VALID and its payload until that channel's accepting edge. In this trace, AW stalls for one edge while W is accepted. The target can remember D0, but it cannot assert BVALID merely because WLAST was accepted: A0 has not yet been accepted.
After E2 the target has accepted A0 and the final data beat. It may perform additional implementation work before presenting B; AXI defines the ordering dependency, not a universal response latency. Once BVALID is presented, BRESP and BID remain stable until BVALID && BREADY.
Early W, stalled AW, delayed B
Single-beat A0/D0 write with WLAST=1 on D0; no prior accepted address or data. V/R means VALID/READY sampled at the edge.
Edge | AW V/R | W V/R | B V/R | Accepted | Target after edge |
|---|---|---|---|---|---|
E1 | 1/0 | 1/1 | 0/0 | D0 final | Data stored; address missing |
E2 | 1/1 | 0/1 | 0/0 | A0 | Address and final data stored |
E3 | 0/1 | 0/1 | 1/0 | None | Retain B0 |
E4 | 0/1 | 0/1 | 1/1 | B0 | Response accepted |
What to notice: AWVALID/AWREADY never establish an address acceptance in this isolated window. A W handshake is insufficient. The registered premature_b flag appears after the faulty response observation.
The write address was never accepted. Accepted final data cannot substitute for accepted AW.
What to notice: BVALID remains asserted across BREADY-low samples. The offer is pending, not repeatedly completed. This capture focuses on acceptance timing; the listing checks the wider response obligations.
Only an edge with BVALID && BREADY. Earlier BVALID-only samples are a retained offer.
What to notice: The observer is reset for this scenario. Follow the absent W handshake/WLAST prerequisite, then BVALID and the later error flag. BREADY being low does not make an invalid response offer legal.
No. The response offer itself depends on the required accepted request parts. Backpressure does not waive that prerequisite.
What to notice
A0 is unchanged from E1 through its accepting edge E2.
No B offer appears before both A0 and final D0 were accepted.
B0 remains offered through the E3 stall and completes only at E4.
After E2: that edge completes the missing AW acceptance. Before then, having the final W beat alone does not satisfy the AXI4 response dependency.
AXI4 AW and W offers and handshakes are independent in time: W may precede AW. Associate W bursts with AW request order; AXI4 has no WID and does not allow write-data interleaving between bursts. WLAST qualifies the final beat, counted only on WVALID && WREADY. BVALID follows acceptance of both AW and the final W beat, with BID matching AWID. Read beats within a burst are ordered; same-ID read bursts return in AR order, while different IDs may reorder or interleave.
AxSIZE describes a transfer-size window and AxLEN encodes beats minus one. A transfer can be narrower than the bus. WSTRB selects which permitted byte lanes actually write; count useful bytes at accepted W edges. The following examples use the course's 64-bit AXI4 bus and ordinary INCR transactions, outside reset. Arm IHI 0022H A3.4 defines the address and byte-lane rules; FIXED and WRAP need their own address calculations.
Two-beat write: count accepted edges
Before E1, address A0 has already been accepted with AWLEN=1 (two beats). V/R means WVALID/WREADY.
Edge | W V/R | WDATA | WLAST | Accepted | Count after edge |
|---|---|---|---|---|---|
E1 | 1/1 | D0 | 0 | D0 | 1 of 2 |
E2 | 1/0 | D1 | 1 | None | 1 of 2 |
E3 | 1/0 | D1 | 1 | None | 1 of 2 |
E4 | 1/1 | D1 | 1 | D1 final | 2 of 2 |
What to notice
D1/WLAST is retained unchanged at E2, E3 and E4.
The burst has two accepted beats across four visible cycles.
No. WREADY is 0 at E2. The final W beat is accepted at E4; only after that acceptance are both required prerequisites satisfied.
Early W data needs buffering or backpressure until associated AW context exists. W bursts follow AW order across IDs and cannot interleave; gaps/stalls between beats are legal. Never require AWREADY before presenting WVALID.
WSTRB indicates which byte lanes are valid — don't ignore on sub-word writes.
AxCACHE and AxPROT affect system behaviour; document their preservation or permitted transformation across each bridge.
The 64-bit bus has eight byte lanes. AWSIZE=1 describes a two-byte transfer window; it does not require every write to occupy eight bytes. For one beat at AWADDR=0x2000_0002 with AWLEN=0, the permitted lanes are 2 and 3. WSTRB bit n qualifies WDATA bits 8n+7 through 8n. AWSIZE=3 fits the eight-byte bus; AWSIZE=4 requests sixteen bytes and cannot be used on this interface.
For these isolated SRAM0 examples, bytes 0x2000_0000 through 0x2000_0007 start at zero and there are no other writers. Offer WDATA=0x0000_0000_5AA5_0000: lane 2 carries A5 and lane 3 carries 5A. A fully enabled halfword uses WSTRB=0x0C. A sparse version can enable only lane 3 with 0x08. Each table row is a separate replay from zero, after one accepted W beat and the corresponding successful target update.
One two-byte window on an eight-byte bus
WSTRB | Enabled lanes | Byte at ...0002 | Byte at ...0003 | Bytes written |
|---|---|---|---|---|
0x0C | 2 and 3 | A5 | 5A | 2 |
0x08 | 3 only | 00 unchanged | 5A | 1 |
0x00 | None | 00 unchanged | 00 unchanged | 0 |
What to notice
Address and size constrain the permitted lanes; strobes can select a subset of that window.
A zero-strobe accepted beat still counts toward AWLEN+1 and does not permit early termination of a burst.
No. The permitted window is lanes 2 and 3. Enabling lanes 0 or 1 lies outside that window, even though all four lanes physically exist on the bus.
Use a fresh zeroed SRAM0 byte range for an INCR request at 0x2000_0003 with AWSIZE=2 and AWLEN=1. The nominal window is four bytes per beat. Its aligned base is 0x2000_0000. The first beat can cover address ...0003 only; the next beat starts at aligned_base+4, address ...0004, and covers ...0004–...0007. Do not compute the second address as the unaligned start plus four.
This exercise assumes ordinary memory and an endpoint/path that implements the shown unaligned access. Use WDATA=0x0000_0000_AB00_0000 on the first beat and 0x8877_6655_0000_0000 on the second. With all permitted lanes enabled, successful updates leave bytes at addresses ...0003–...0007 equal to AB, 55, 66, 77 and 88; earlier bytes remain zero.
Two accepted beats, five enabled bytes
Accepted beat | Beat address | Enabled lanes | WSTRB | WLAST |
|---|---|---|---|---|
First | 0x2000_0003 | 3 | 0x08 | 0 |
Second | 0x2000_0004 | 4–7 | 0xF0 | 1 |
What to notice
The first unaligned window is shortened; subsequent INCR addresses use the transfer-aligned base.
Useful enabled bytes are five here, although two four-byte windows define the transaction container.
No. The second beat starts at ...0004, so its permitted lanes are 4–7 and the full mask is 0xF0. Lane 3 belongs to the first beat's window.
Even when both addresses decode to SRAM0, one AXI4 burst cannot cross a 4 KiB address boundary. For aligned INCR transfers, last_byte=start+(AWLEN+1)*2^AWSIZE-1. Compare start and last_byte after shifting right by 12; also check for address-width overflow. This shortcut is scoped to aligned INCR bursts; unaligned first beats need the aligned-base/container calculation.
At 0x2000_0FF8, AWSIZE=3 and AWLEN=1 describe two eight-byte beats: the first ends at ...0FFF and the second at ...1007. Plan two legal single-beat requests instead, before issuing addresses. This creates two write transactions, each requiring its own final W beat and B response. It is not permission for an intermediate component to rewrite any accepted transaction regardless of its attributes or ordering contract.
Sixteen bytes expressed as two legal requests
Request | AWADDR | AWSIZE / AWLEN | Byte range | WLAST |
|---|---|---|---|---|
Original: illegal | 0x2000_0FF8 | 3 / 1 | ...0FF8–...1007 | Would span two regions |
A: legal | 0x2000_0FF8 | 3 / 0 | ...0FF8–...0FFF | 1 on its only beat |
B: legal | 0x2000_1000 | 3 / 0 | ...1000–...1007 | 1 on its only beat |
What to notice
A common target decode does not waive AXI's burst-boundary restriction.
Each new request has its own address/data/response bookkeeping; retain the required ID ordering.
No. The address/size/length describe an illegal crossing burst. Zero strobes suppress writes on a beat; they do not repair its address structure or remove the beat from the burst.
This arithmetic exercise sweeps sizes, offsets and strobe masks on the 64-bit interface, checks the shown byte updates, and rejects out-of-window strobes, crossing bursts and address overflow. It models ordinary INCR transfer shape only; there are no channel handshakes or timing waveforms in this listing.
// Ordinary AXI4 INCR arithmetic on a 64-bit data / 32-bit address interface.
// This is a transfer-shape exercise, not a channel-level AXI implementation.
module tb;
reg [7:0] memory [0:15];
integer checks = 0;
integer expected_count, first_lane;
reg [7:0] reference_mask;
reg reference_subset;
function automatic bit legal_incr(input [31:0] address, input integer size, input integer beats);
reg [63:0] aligned, last_byte;
begin
if (size < 0 || size > 3 || beats < 1 || beats > 256) legal_incr = 0;
else begin
aligned = ({32'b0, address} >> size) << size;
last_byte = aligned + (64'd1 << size) * beats - 1;
legal_incr = last_byte <= 64'hffff_ffff &&
({32'b0, address} >> 12) == (last_byte >> 12);
end
end
endfunction
function automatic [7:0] lane_mask(input [31:0] address, input integer size, input integer beat);
reg [63:0] aligned, current_addr, bus_base;
integer lower, upper;
begin
lane_mask = 0;
if (size >= 0 && size <= 3 && beat >= 0) begin
aligned = ({32'b0, address} >> size) << size;
current_addr = beat == 0 ? {32'b0, address} : aligned + (64'd1 << size) * beat;
bus_base = (current_addr >> 3) << 3;
lower = current_addr - bus_base;
upper = beat == 0 ? aligned + (64'd1 << size) - 1 - bus_base : lower + (1 << size) - 1;
for (integer lane = 0; lane < 8; lane = lane + 1)
if (lane >= lower && lane <= upper) lane_mask[lane] = 1;
end
end
endfunction
function automatic integer bytes_enabled(input [7:0] strobe);
begin
bytes_enabled = 0;
for (integer lane = 0; lane < 8; lane = lane + 1)
bytes_enabled = bytes_enabled + strobe[lane];
end
endfunction
task require(input bit condition, input string message);
begin
checks = checks + 1;
if (!condition) begin $display("FAIL: %s", message); $finish; end
end
endtask
task apply_beat(input [31:0] address, input integer size, input integer beat,
input [63:0] data, input [7:0] strobe);
reg [63:0] aligned, current_addr, bus_base;
integer index;
begin
require((strobe & ~lane_mask(address, size, beat)) == 0, "strobe outside transfer window");
aligned = ({32'b0, address} >> size) << size;
current_addr = beat == 0 ? {32'b0, address} : aligned + (64'd1 << size) * beat;
bus_base = (current_addr >> 3) << 3;
for (integer lane = 0; lane < 8; lane = lane + 1) begin
if (strobe[lane]) begin
index = bus_base - 64'h2000_0000 + lane;
require(index >= 0 && index < 16, "exercise memory bounds");
memory[index] = data[8*lane +: 8];
end
end
end
endtask
initial begin
// Sweep widths, unaligned starts and beats, rather than asserting only one constant mask.
for (integer size = 0; size <= 3; size = size + 1)
for (integer offset = 0; offset < 16; offset = offset + 1)
for (integer beat = 0; beat < 4; beat = beat + 1) begin
expected_count = beat == 0 ? (1 << size) - offset % (1 << size) : (1 << size);
first_lane = (beat == 0 ? offset : (offset / (1 << size)) * (1 << size) + beat * (1 << size)) % 8;
reference_mask = ((1 << expected_count) - 1) << first_lane;
require(legal_incr(32'h2000_0000 + offset, size, 4), "local four-beat INCR must fit");
require(bytes_enabled(lane_mask(32'h2000_0000 + offset, size, beat)) ==
expected_count,
"lane count must match shortened first beat or full subsequent window");
require(lane_mask(32'h2000_0000 + offset, size, beat) == reference_mask,
"lane positions must match independent packed-window construction");
for (integer mask = 0; mask < 256; mask = mask + 1) begin
reference_subset = 1;
for (integer lane = 0; lane < 8; lane = lane + 1)
if ((mask & (1 << lane)) != 0 && !reference_mask[lane]) reference_subset = 0;
require(((mask & ~lane_mask(32'h2000_0000 + offset, size, beat)) == 0) ==
reference_subset, "strobe subset rule against per-lane reference");
end
end
for (integer size = 0; size <= 3; size = size + 1)
for (integer offset = 4080; offset < 4096; offset = offset + 1)
for (integer beats = 1; beats <= 4; beats = beats + 1)
require(legal_incr(32'h2000_0000 + offset, size, beats) ==
(beats <= (4096 - (offset / (1 << size)) * (1 << size)) / (1 << size)),
"burst count must fit remaining transfer windows before 4 KiB");
require(lane_mask(32'h2000_0002, 1, 0) == 8'h0c, "halfword uses lanes 2 and 3");
require((8'h0f & ~lane_mask(32'h2000_0002, 1, 0)) != 0, "reject enabled lanes outside halfword");
require(lane_mask(32'h2000_0003, 2, 0) == 8'h08 &&
lane_mask(32'h2000_0003, 2, 1) == 8'hf0, "unaligned first beat then aligned second beat");
require(!legal_incr(32'h2000_0ff8, 3, 2), "reject crossing even if strobes are zero");
require(legal_incr(32'h2000_0ff8, 3, 1) && legal_incr(32'h2000_1000, 3, 1), "both split requests fit");
require(!legal_incr(32'h2000_0000, 4, 1), "reject size wider than bus");
require(!legal_incr(32'h2000_0000, 3, 0) && !legal_incr(32'h2000_0000, 3, 257), "reject invalid AXI4 INCR lengths");
require(!legal_incr(32'hffff_fff8, 3, 2), "detect address-width overflow");
for (integer i = 0; i < 16; i = i + 1) memory[i] = 0;
apply_beat(32'h2000_0002, 1, 0, 64'h0000_0000_5aa5_0000, 8'h08);
require(memory[2] == 0 && memory[3] == 8'h5a, "sparse write preserves disabled byte");
for (integer i = 0; i < 16; i = i + 1) memory[i] = 0;
apply_beat(32'h2000_0003, 2, 0, 64'h0000_0000_ab00_0000, 8'h08);
apply_beat(32'h2000_0003, 2, 1, 64'h8877_6655_0000_0000, 8'hf0);
require(memory[0] == 0 && memory[1] == 0 && memory[2] == 0 &&
memory[3] == 8'hab && memory[4] == 8'h55 && memory[5] == 8'h66 &&
memory[6] == 8'h77 && memory[7] == 8'h88, "unaligned replay byte contents");
require(checks > 256, "non-zero varied-input work");
$display("PASS: transfer windows, sparse bytes, unaligned INCR and boundary counterexamples checked");
$finish;
end
endmoduleRead first: Ordering & Outstanding Rules: the ordering stream behind an ID
AXI4 permits multiple outstanding read requests with the same ARID. Their responses must preserve request order for that ID, even though transactions with different IDs can complete in another order. A checker keyed only by RID is therefore insufficient: RID selects a per-ID queue, and the queue head identifies which request the next completed response belongs to.
For the bounded pilot exercise, tags A0 and B1 are teaching-only correlation labels, not AXI signals. The executable observer records two accepted AR items for ID 3 and compares each accepted final R beat with the queue head. It also checks the separate write rule that B is not presented before an AW and final W acceptance. This is a focused executable model, not an AXI compliance monitor.
When a response completes on the same edge as a new request is accepted, compare the response with the pre-edge queue head, remove that head and append the new request. Occupancy stays unchanged. The observer reports a third queued request separately as its own capacity limit; AXI itself does not impose this exercise's two-entry limit.
The bench exercises all three write-arrival patterns, retained context while B stalls, same-edge read queue replacement, a response without a request and the observer's reset policy. Reset clears this exercise's tracked state; a real system must separately specify how managers, targets and outstanding transactions recover together.
Repeated ID: faulty completion order
Single-beat reads, queue initially empty. A0 and B1 are observer correlation labels, not additional AXI wires. The observer retains its queue on mismatch.
Edge | Accepted event | ID 3 queue after edge | Verdict |
|---|---|---|---|
E1 | AR: ID 3, A0 | [A0] | Legal |
E2 | AR: ID 3, B1 | [A0, B1] | Legal |
E3 | R: ID 3, B1, RLAST | [A0, B1] | Mismatch: head is A0 |
Legal replay after the same E1 and E2
Edge | Accepted response | ID 3 queue after edge |
|---|---|---|
E3 | R: ID 3, A0, RLAST | [B1] |
E4 | R: ID 3, B1, RLAST | [] |
What to notice: ar_tag and r_tag are teaching identities in the observer, not extra AXI signals. Follow read_q_count from zero to two and back to zero. Matching an ID is not sufficient when two requests use it.
Responses match the queued teaching tags in order and read_q_count drains to zero; wrong_same_id stays clear.
What to notice: Inspect the overlap of AR and R acceptance. An unchanged queue count can hide two useful events. The old head must be checked before the replacement becomes the new head.
No. One enqueue and one dequeue can cancel in the occupancy count. Check both handshakes and their identities.
What to notice: The teaching observer has two entries. Admission at full occupancy is supported here when a matching response retires on the same edge; it must preserve the old second entry and append the new one. Capacity and order flags both stay clear.
A valid simultaneous retirement releases a slot. This observer explicitly supports that exchange; occupancy alone before the edge is insufficient.
What to notice: There is no compensating response retirement. model_capacity_error rises while wrong_same_id remains clear. This distinguishes insufficient checker storage from a demonstrated protocol ordering violation.
No. Two is this teaching observer's storage bound. The flag says it cannot track this observation, not that the protocol imposes that limit.
What to notice: Follow the two accepted ar_tag values and compare the first r_tag with the queue head. The ID can match while the transaction is wrong. The error flag is registered after the sampled response.
It loses the order and identities of multiple requests with ID 3. A per-ID queue is needed to compare the response against the oldest unfinished request.
What to notice
RID=3 alone cannot distinguish A0 from B1.
The first divergence is the E3 comparison with the queue head.
No. Accepting B1 must not overwrite A0. Use an ordered queue for the read stream at this interface and ID; compare with its head before removing a completed request.
This SystemVerilog observer keeps one write context and a two-entry read queue for ID 3. Its bench includes legal arrivals, a stalled B response, simultaneous queue replacement, unexpected responses and deliberate ordering faults. A model-capacity flag is separate from protocol-ordering flags. Read and write state are independent, and reset clears the observer; a complete system needs its own outstanding-work recovery policy.
// Bounded teaching checker for two AXI4 pilot claims; this is not a complete AXI monitor.
// It observes one write at a time and two single-beat reads for one tracked ID.
module axi_pilot_checker #(
parameter [3:0] TRACK_ID = 4'h3
) (
input wire clk,
input wire rst,
input wire aw_valid,
input wire aw_ready,
input wire w_valid,
input wire w_ready,
input wire w_last,
input wire b_valid,
input wire b_ready,
input wire ar_valid,
input wire ar_ready,
input wire [3:0] ar_id,
input wire [7:0] ar_tag,
input wire r_valid,
input wire r_ready,
input wire r_last,
input wire [3:0] r_id,
input wire [7:0] r_tag,
output reg premature_b,
output reg wrong_same_id,
output reg model_capacity_error,
output reg [1:0] read_q_count
);
reg aw_seen, final_w_seen;
reg [7:0] read_q0, read_q1;
wire aw_accept = aw_valid && aw_ready;
wire w_accept = w_valid && w_ready;
wire ar_accept = ar_valid && ar_ready;
wire r_accept = r_valid && r_ready;
wire push_read = ar_accept && ar_id == TRACK_ID;
wire read_complete = r_accept && r_last && r_id == TRACK_ID;
wire pop_read = read_complete && read_q_count != 0 && r_tag == read_q0;
always @(posedge clk) begin
if (rst) begin
aw_seen <= 1'b0;
final_w_seen <= 1'b0;
premature_b <= 1'b0;
wrong_same_id <= 1'b0;
model_capacity_error <= 1'b0;
read_q_count <= 2'd0;
read_q0 <= 8'h00;
read_q1 <= 8'h00;
end else begin
if (aw_accept) aw_seen <= 1'b1;
if (w_accept && w_last) final_w_seen <= 1'b1;
// BVALID is an offer. Its prerequisites must have completed on earlier edges.
if (b_valid && !(aw_seen && final_w_seen)) premature_b <= 1'b1;
if (b_valid && b_ready) begin
aw_seen <= 1'b0;
final_w_seen <= 1'b0;
end
// The response matches the pre-edge head. A new AR on this edge cannot
// justify a response when the queue was empty before the edge.
if (read_complete && !pop_read) wrong_same_id <= 1'b1;
case ({push_read, pop_read})
2'b10: begin
if (read_q_count == 0) begin
read_q0 <= ar_tag;
read_q_count <= 1;
end else if (read_q_count == 1) begin
read_q1 <= ar_tag;
read_q_count <= 2;
end else begin
// Exceeding this observer's capacity is not an AXI violation.
model_capacity_error <= 1'b1;
end
end
2'b01: begin
read_q0 <= read_q1;
read_q_count <= read_q_count - 1'b1;
end
2'b11: begin
if (read_q_count == 1) read_q0 <= ar_tag;
else begin
read_q0 <= read_q1;
read_q1 <= ar_tag;
end
// One request enters and one completes: occupancy is unchanged.
end
default: begin end
endcase
end
end
endmodule
module tb;
reg clk = 1'b0;
always #5 clk = ~clk;
reg rst;
reg aw_valid, aw_ready, w_valid, w_ready, w_last, b_valid, b_ready;
reg ar_valid, ar_ready, r_valid, r_ready, r_last;
reg [3:0] ar_id, r_id;
reg [7:0] ar_tag, r_tag;
wire premature_b, wrong_same_id, model_capacity_error;
wire [1:0] read_q_count;
reg saw_premature = 1'b0;
reg saw_wrong_order = 1'b0;
axi_pilot_checker dut (.*);
task idle_inputs;
begin
aw_valid = 0; aw_ready = 0; w_valid = 0; w_ready = 0; w_last = 0;
b_valid = 0; b_ready = 0; ar_valid = 0; ar_ready = 0;
r_valid = 0; r_ready = 0; r_last = 0;
ar_id = 0; r_id = 0; ar_tag = 0; r_tag = 0;
end
endtask
task reset_checker;
begin
@(negedge clk); idle_inputs(); rst = 1;
@(negedge clk); rst = 0;
end
endtask
task accept_aw;
begin
@(negedge clk); aw_valid = 1; aw_ready = 1;
@(negedge clk); aw_valid = 0; aw_ready = 0;
end
endtask
task accept_final_w;
begin
@(negedge clk); w_valid = 1; w_ready = 1; w_last = 1;
@(negedge clk); w_valid = 0; w_ready = 0; w_last = 0;
end
endtask
task offer_b;
begin
@(negedge clk); b_valid = 1; b_ready = 1;
@(negedge clk); b_valid = 0; b_ready = 0;
end
endtask
task accept_aw_and_final_w;
begin
@(negedge clk);
aw_valid = 1; aw_ready = 1; w_valid = 1; w_ready = 1; w_last = 1;
@(negedge clk);
aw_valid = 0; aw_ready = 0; w_valid = 0; w_ready = 0; w_last = 0;
end
endtask
task accept_ar(input [7:0] tag);
begin
@(negedge clk); ar_valid = 1; ar_ready = 1; ar_id = 4'h3; ar_tag = tag;
@(negedge clk); ar_valid = 0; ar_ready = 0;
end
endtask
task accept_r(input [7:0] tag);
begin
@(negedge clk); r_valid = 1; r_ready = 1; r_last = 1; r_id = 4'h3; r_tag = tag;
@(negedge clk); r_valid = 0; r_ready = 0; r_last = 0;
end
endtask
task exchange_read(input [7:0] completed_tag, input [7:0] new_tag);
begin
@(negedge clk);
ar_valid = 1; ar_ready = 1; ar_id = 4'h3; ar_tag = new_tag;
r_valid = 1; r_ready = 1; r_last = 1; r_id = 4'h3; r_tag = completed_tag;
@(negedge clk);
ar_valid = 0; ar_ready = 0; r_valid = 0; r_ready = 0; r_last = 0;
end
endtask
initial begin
$dumpfile("wave.vcd");
$dumpvars(0, tb);
rst = 1;
idle_inputs();
// Positive write: early final W, then AW, then B.
reset_checker();
accept_final_w();
accept_aw();
offer_b();
if (premature_b) begin
$display("FAIL: legal W-before-AW write was rejected");
$finish;
end
// Negative write: B is presented after final W but before AW acceptance.
reset_checker();
accept_final_w();
offer_b();
saw_premature = premature_b;
if (!saw_premature) begin
$display("FAIL: premature B was not detected");
$finish;
end
// Positive reads: two requests with ID 3 complete A0 then B1.
reset_checker();
accept_ar(8'hA0);
accept_ar(8'hB1);
accept_r(8'hA0);
accept_r(8'hB1);
if (wrong_same_id || model_capacity_error || read_q_count != 0) begin
$display("FAIL: legal same-ID completion order was rejected");
$finish;
end
// Positive reads: replace a completed head on the same edge as a new AR.
reset_checker();
accept_ar(8'hA0);
exchange_read(8'hA0, 8'hB1);
if (wrong_same_id || model_capacity_error || read_q_count != 1) begin
$display("FAIL: single-entry simultaneous enqueue/dequeue lost occupancy");
$finish;
end
accept_r(8'hB1);
if (wrong_same_id || read_q_count != 0) begin
$display("FAIL: replacement request was lost");
$finish;
end
// Positive reads: a full queue can pop and push without exceeding capacity.
reset_checker();
accept_ar(8'hA0);
accept_ar(8'hB1);
exchange_read(8'hA0, 8'hC2);
if (wrong_same_id || model_capacity_error || read_q_count != 2) begin
$display("FAIL: full-queue exchange lost occupancy or reported overflow");
$finish;
end
accept_r(8'hB1);
accept_r(8'hC2);
if (wrong_same_id || read_q_count != 0) begin
$display("FAIL: full-queue exchange changed response order");
$finish;
end
// Observer limit: a third queued request is reported separately from ordering.
reset_checker();
accept_ar(8'hA0);
accept_ar(8'hB1);
accept_ar(8'hC2);
if (!model_capacity_error || wrong_same_id || read_q_count != 2) begin
$display("FAIL: observer capacity limit was confused with AXI ordering");
$finish;
end
// Negative reads: the same requests complete B1 before A0.
reset_checker();
accept_ar(8'hA0);
accept_ar(8'hB1);
accept_r(8'hB1);
saw_wrong_order = wrong_same_id;
if (!saw_wrong_order) begin
$display("FAIL: wrong same-ID completion order was not detected");
$finish;
end
// Positive write: AW before final W satisfies the same prerequisites.
reset_checker();
accept_aw();
accept_final_w();
offer_b();
if (premature_b) begin
$display("FAIL: legal AW-before-W write was rejected");
$finish;
end
// Positive write: AW and final W can accept at the same edge.
reset_checker();
accept_aw_and_final_w();
offer_b();
if (premature_b) begin
$display("FAIL: same-edge AW/final-W acceptance was rejected");
$finish;
end
// Positive write: a legal B offer can remain stalled before completion.
reset_checker();
accept_aw_and_final_w();
@(negedge clk); b_valid = 1; b_ready = 0;
repeat (2) @(negedge clk);
if (premature_b || !dut.aw_seen || !dut.final_w_seen) begin
$display("FAIL: B stall lost accepted write context");
$finish;
end
b_ready = 1;
@(negedge clk); b_valid = 0; b_ready = 0;
if (premature_b || dut.aw_seen || dut.final_w_seen) begin
$display("FAIL: accepted B did not retire write context");
$finish;
end
// Negative write: an illegal B offer is wrong even when BREADY is low.
reset_checker();
accept_aw();
@(negedge clk); b_valid = 1; b_ready = 0;
@(negedge clk);
if (!premature_b) begin
$display("FAIL: B offer without final W was hidden by BREADY=0");
$finish;
end
b_valid = 0;
// Negative read: an R completion needs an earlier request.
reset_checker();
accept_r(8'hA0);
if (!wrong_same_id || read_q_count != 0) begin
$display("FAIL: unexpected R response did not report an empty queue");
$finish;
end
// Observer reset policy: abandon tracked work and clear previous flags.
reset_checker();
accept_ar(8'hA0);
accept_aw_and_final_w();
reset_checker();
if (read_q_count != 0 || dut.aw_seen || dut.final_w_seen ||
premature_b || wrong_same_id || model_capacity_error) begin
$display("FAIL: reset retained observer work or stale failure flags");
$finish;
end
$display("PASS: legal arrivals, stalls and reset passed; response/order faults and observer capacity were detected");
$finish;
end
endmoduleAW and W accept independently in either arrival order; a B offer requires accepted AW and final W.
Count burst beats on accepting edges and retain every stalled channel's payload and control fields.
Keep accepted requests in separate read/write queues per interface and ID; repeated IDs append rather than overwrite.