Read first: AXI Channels & Transfers: independent channel acceptance
AXI4-Lite removes burst and ID signals: each request describes one data transfer, and responses are returned in order. Implementations can still support more than one outstanding transfer within the protocol's ordering and buffering rules. The reduced signal/ordering contract can simplify endpoints, but throughput and complexity depend on the complete path.
Register and configuration blocks are common AXI4-Lite users. A low-rate data mover could also use it if its single-transfer throughput, latency and outstanding behaviour meet the product requirement; high-bandwidth DMA commonly benefits from full AXI bursts. Choose by measured traffic and implementation cost rather than a blanket block-type ban.
Use a local 32-bit Lite register-interface comparison, outside reset on a 100 MHz clock. This is a variant of the LEN_BYTES exercise, not a new connection in the base AXI-to-APB system. The register starts at zero. A single address at 0x4000_0008 pairs with WDATA=0x0000_0010 and WSTRB=1111. There is no WLAST port on Lite. The exercise target admits one write at a time but retains AW and W independently, so either can arrive first.
Early W retained until AW arrives
AW V/R, W V/R and B V/R are per-channel sampled VALID/READY. The chosen target commits once both items are present, then blocks new write admission until B completes. Its successful response is BRESP=OKAY.
Edge | AW V/R | W V/R | B V/R | State after edge |
|---|---|---|---|---|
E1 | 0/1 | 1/1 | 0/0 | Retain WDATA/strobes; register still 0 |
E2 | 1/1 | 0/0 | 0/0 | AW joins stored W; commit 0x10 once |
E3 | 0/0 | 0/0 | 1/0 | Retain B response; register stays 0x10 |
E4 | 0/0 | 0/0 | 1/1 | Response accepted; admission can reopen |
What to notice
Independent AW/W storage is required by this target's admission policy; waiting for both to be valid in the same cycle would lose legal cases.
B backpressure must not repeat the register write or discard its pending response.
No. AW has not been accepted, so there is no paired write address. Lite retains the AXI4 requirement to accept both the address and data before offering the write response.
AWPROT and ARPROT carry protection attributes. AXI4-Lite has no AWCACHE or ARCACHE signals; its accesses have fixed Non-modifiable, Non-bufferable attributes, equivalent to AxCACHE=0b0000 on full AXI.
Write response (B) is required even for single-beat writes.
A memory-like target can honour WSTRB per byte. Register targets must document supported strobe handling, including their treatment of unsupported patterns.
AXI4-Lite B1.1 in Arm IHI 0022H defines a fixed 32- or 64-bit data width and single-transfer requests. AWPROT/ARPROT remain; AWCACHE/ARCACHE do not appear on the Lite interface. Its fixed attributes are Non-modifiable and Non-bufferable, equivalent to AxCACHE=0000 when discussing full AXI transactions. Do not invent a Lite AxSIZE signal: strobes select written bytes within the fixed data width.
For this register exercise the target honours all four strobes. A sparse write changes enabled bytes only, and an all-zero-strobe write still receives a B response without changing LEN_BYTES. Read and write response streams are independent; sharing a register address does not itself create a universal cross-channel ordering promise. A software sequence must follow its platform's ordering and completion rules.
Independent snapshots of a 32-bit register initially 0x11223344
WDATA=0xAABBCCDD in each replay; lane 0 is bits 7:0. These byte-update results describe this target's chosen strobe contract.
WSTRB | New register value | Required write response |
|---|---|---|
1111 | 0xAABBCCDD | One B |
0011 | 0x1122CCDD | One B |
0000 | 0x11223344 unchanged | One B |
What to notice: value changes to hexadecimal 10 and commits reaches one only after the request parts pair. BREADY stays low throughout this short window, so it does not show response retirement. dropped_value belongs to the deliberately faulty data-retention target.
The register write occurred once, as value and commits show. B was not accepted because BREADY remains low; the response is still pending.
No. The accepted transaction still needs completion. In this exercise it updates no bytes and returns one OKAY response after the address/data prerequisites are satisfied.
This write-only local target uses one AW slot, one W slot and one retained B. Its bench covers W-first, AW-first, simultaneous arrival, sparse/zero strobes and B stalls. A second target deliberately loses early data. A separate reusable response observer receives independently scripted legal AW-first, W-first and simultaneous traces, then three illegal B offers with AW, W or both missing; the generator does not read checker state. The test requires clear results for every legal trace, detection for every faulty trace and a reset/clean replay afterwards. The good target separately commits exactly four paired writes and returns four responses. The model omits AR/R and does not claim complete Lite compliance.
// Local 32-bit AXI4-Lite write-only exercise; one pending write and one register.
// DROP_EARLY is a deliberate storage fault. AR/R, reset-in-traffic and full compliance are outside scope.
`timescale 1ns/1ps
module lite_write #(parameter DROP_EARLY = 0) (
input wire clk, rst,
input wire awvalid,
output wire awready,
input wire [31:0] awaddr,
input wire wvalid,
output wire wready,
input wire [31:0] wdata,
input wire [3:0] wstrb,
output reg bvalid,
input wire bready,
output reg [1:0] bresp,
output reg [31:0] value,
output integer commits
);
reg aw_full, w_full;
reg [31:0] saved_addr, saved_data;
reg [3:0] saved_strb;
wire aw_accept = awvalid && awready;
wire w_accept = wvalid && wready;
wire [31:0] address_now = aw_full ? saved_addr : awaddr;
wire [31:0] data_now = w_full ? saved_data : wdata;
wire [3:0] strb_now = w_full ? saved_strb : wstrb;
assign awready = !aw_full && !bvalid;
assign wready = !w_full && !bvalid;
always @(posedge clk) begin
if (rst) begin
aw_full <= 0; w_full <= 0; bvalid <= 0; bresp <= 0; value <= 0; commits <= 0;
end else begin
if (aw_accept) begin aw_full <= 1; saved_addr <= awaddr; end
if (w_accept) begin
w_full <= 1; saved_strb <= wstrb;
saved_data <= DROP_EARLY && !aw_full && !aw_accept ? 32'h0 : wdata;
end
if (!bvalid && (aw_full || aw_accept) && (w_full || w_accept)) begin
bvalid <= 1;
bresp <= address_now == 32'h4000_0008 ? 2'b00 : 2'b10;
if (address_now == 32'h4000_0008) begin
for (integer lane = 0; lane < 4; lane = lane + 1)
if (strb_now[lane]) value[8*lane +: 8] <= data_now[8*lane +: 8];
commits <= commits + 1;
end
aw_full <= 0; w_full <= 0;
end
if (bvalid && bready) bvalid <= 0;
end
end
endmodule
// Single-outstanding response prerequisite observer. Inputs are sampled offers
// and acceptance events; fault stimulus never reads this observer's state.
module lite_response_guard (
input wire clk, rst, aw_fire, w_fire, bvalid, bready,
output reg violation
);
reg have_aw, have_w;
always @(posedge clk) begin
if (rst) begin have_aw <= 0; have_w <= 0; violation <= 0; end
else begin
if (aw_fire) have_aw <= 1;
if (w_fire) have_w <= 1;
if (bvalid && !(have_aw && have_w)) violation <= 1;
if (bvalid && bready) begin have_aw <= 0; have_w <= 0; end
end
end
endmodule
module tb;
reg clk = 0;
always #5 clk = ~clk;
reg rst = 1, awvalid = 0, wvalid = 0, bready = 0;
reg [31:0] awaddr = 32'h4000_0008, wdata = 0;
reg [3:0] wstrb = 0;
wire awready, wready, bvalid, drop_awready, drop_wready, drop_bvalid;
wire [1:0] bresp, drop_bresp;
wire [31:0] value, dropped_value;
integer commits, drop_commits;
integer aw_count = 0, w_count = 0, responses = 0;
reg aw_seen = 0, w_seen = 0, lost_data_seen = 0;
wire good_response_error, premature_seen;
reg probe_rst = 1, probe_aw = 0, probe_w = 0, probe_b = 0, probe_ready = 0;
lite_response_guard good_guard (
.clk(clk), .rst(rst), .aw_fire(awvalid && awready), .w_fire(wvalid && wready),
.bvalid(bvalid), .bready(bready), .violation(good_response_error)
);
lite_response_guard probe_guard (
.clk(clk), .rst(probe_rst), .aw_fire(probe_aw), .w_fire(probe_w),
.bvalid(probe_b), .bready(probe_ready), .violation(premature_seen)
);
lite_write good (.*);
lite_write #(.DROP_EARLY(1)) dropped (
.clk(clk), .rst(rst), .awvalid(awvalid), .awready(drop_awready), .awaddr(awaddr),
.wvalid(wvalid), .wready(drop_wready), .wdata(wdata), .wstrb(wstrb),
.bvalid(drop_bvalid), .bready(bready), .bresp(drop_bresp),
.value(dropped_value), .commits(drop_commits)
);
always @(posedge clk) begin
if (rst) begin
aw_seen <= 0; w_seen <= 0; aw_count <= 0; w_count <= 0; responses <= 0;
end else begin
if (awvalid && awready) begin aw_seen <= 1; aw_count <= aw_count + 1; end
if (wvalid && wready) begin w_seen <= 1; w_count <= w_count + 1; end
if (bvalid && !(aw_seen && w_seen)) begin $display("FAIL: good target offered B before AW/W"); $finish; end
if (good_response_error) begin $display("FAIL: good response guard flagged legal traffic"); $finish; end
if (bvalid && bready) begin aw_seen <= 0; w_seen <= 0; responses <= responses + 1; end
if (awready !== drop_awready || wready !== drop_wready || bvalid !== drop_bvalid || bresp !== drop_bresp) begin
$display("FAIL: data-loss variant unexpectedly changed admission/response timing"); $finish;
end
end
end
task send_aw;
begin
@(negedge clk); awvalid = 1;
@(posedge clk); if (!awready) begin $display("FAIL: test AW not accepted"); $finish; end
@(negedge clk); awvalid = 0;
end
endtask
task send_w(input [31:0] data, input [3:0] strb);
begin
@(negedge clk); wvalid = 1; wdata = data; wstrb = strb;
@(posedge clk); if (!wready) begin $display("FAIL: test W not accepted"); $finish; end
@(negedge clk); wvalid = 0; wdata = 0; wstrb = 0;
end
endtask
task finish_b(input integer stalls, input [31:0] expected_value, input integer expected_commits);
begin
@(negedge clk);
if (!bvalid || bresp !== 2'b00 || value !== expected_value || commits != expected_commits) begin
$display("FAIL: bounded target did not create the expected OKAY write result"); $finish;
end
repeat (stalls) begin
@(negedge clk);
if (!bvalid || bresp != 0 || commits != expected_commits || value !== expected_value) begin
$display("FAIL: stalled B changed response or repeated/lost write"); $finish;
end
end
bready = 1;
@(negedge clk); bready = 0;
if (bvalid || value !== expected_value || commits != expected_commits) begin
$display("FAIL: write value/count or B retirement"); $finish;
end
end
endtask
task probe_step(input a, input w, input b, input ready);
begin
@(negedge clk); probe_aw = a; probe_w = w; probe_b = b; probe_ready = ready;
@(posedge clk); #1;
end
endtask
task check_response_trace(input integer scenario, input expected_error);
begin
@(negedge clk); probe_rst = 1; probe_aw = 0; probe_w = 0; probe_b = 0; probe_ready = 0;
@(posedge clk); #1;
@(negedge clk); probe_rst = 0;
// Explicit independent traffic: no signal here depends on guard state.
case (scenario)
0: begin probe_step(1,0,0,0); probe_step(0,1,0,0); end // AW first
1: begin probe_step(0,1,0,0); probe_step(1,0,0,0); end // W first
2: probe_step(1,1,0,0); // same-edge acceptance
3: probe_step(0,1,0,0); // fault: missing AW
4: probe_step(1,0,0,0); // fault: missing W
5: probe_step(0,0,0,0); // fault: neither prerequisite
default: begin $display("FAIL: unknown guard scenario"); $finish; end
endcase
probe_step(0,0,1,0); // offered response, even though receiver is stalled
if (premature_seen !== expected_error) begin
$display("FAIL: guard scenario=%0d expected=%0d actual=%0d", scenario, expected_error, premature_seen); $finish;
end
probe_step(0,0,1,1);
probe_step(0,0,0,0);
if (premature_seen !== expected_error) begin $display("FAIL: guard lost sticky result"); $finish; end
$display("guard scenario=%0d error=%0d", scenario, premature_seen);
end
endtask
initial begin
$dumpfile("wave.vcd"); $dumpvars(0, tb);
repeat (2) @(posedge clk);
@(negedge clk); rst = 0;
send_w(32'h10, 4'hf); send_aw(); finish_b(2, 32'h10, 1);
lost_data_seen = dropped_value === 32'h0;
send_aw(); send_w(32'h1122_3344, 4'hf); finish_b(0, 32'h1122_3344, 2);
@(negedge clk); awvalid = 1; wvalid = 1; wdata = 32'haabb_ccdd; wstrb = 4'h3;
@(posedge clk);
if (!awready || !wready) begin $display("FAIL: simultaneous AW/W were not accepted"); $finish; end
@(negedge clk); awvalid = 0; wvalid = 0; wdata = 0;
finish_b(0, 32'h1122_ccdd, 3);
send_w(32'hffff_ffff, 0); send_aw(); finish_b(0, 32'h1122_ccdd, 4);
if (!lost_data_seen || good_response_error || aw_count != 4 || w_count != 4 || responses != 4) begin
$display("FAIL: negative cases or exact-once traffic counts"); $finish;
end
check_response_trace(0, 0);
check_response_trace(1, 0);
check_response_trace(2, 0);
check_response_trace(3, 1);
check_response_trace(4, 1);
check_response_trace(5, 1);
check_response_trace(0, 0); // reset and clean replay after all faults
$display("PASS: Lite target and independent response guard checked; three legal orders and three missing-prerequisite faults");
$finish;
end
endmoduleAXI4-Stream moves data without per-transfer addresses. TVALID and TREADY define beat acceptance. When implemented, TLAST can mark a packet boundary, TKEEP qualifies byte lanes, and TID/TDEST/TUSER carry stream, routing or application-defined context. The source and destination must agree which optional signals and semantics form their interface contract.
This local example uses the AXI4-Stream subset in Arm IHI 0051B: one stream, 32-bit input, 16-bit output, TKEEP and TLAST present, no position bytes or packet interleaving. TSTRB, TID, TDEST and TUSER are absent under the agreed interface contract. Bytes are packed from lane 0 and the last input beat may be partial. These are exercise restrictions, not requirements on every AXI-Stream interface.
A packet carries bytes 11,22,33,44,55 in that order. Its input transfers are TDATA=0x44332211/TKEEP=1111/TLAST=0 and TDATA=0x00000055/TKEEP=0001/TLAST=1. Count bytes only when TVALID && TREADY; retain data, keep mask and last flag together during a stall. TLAST high without acceptance does not end the received packet.
Final input beat stalls before packet completion
Input-side rising-edge observations outside reset; initially no bytes accepted. V/R means TVALID/TREADY.
Edge | V/R | TDATA / TKEEP / TLAST | Bytes accepted | Packet ended? |
|---|---|---|---|---|
E1 | 1/1 | 44332211 / 1111 / 0 | 11,22,33,44 | No |
E2 | 1/0 | 00000055 / 0001 / 1 | None | No |
E3 | 1/0 | 00000055 / 0001 / 1 | None | No |
E4 | 1/1 | 00000055 / 0001 / 1 | 55 | Yes |
What to notice
Three visible cycles of the last offer contribute one byte, accepted once at E4.
Retain the final flag with its data and keep mask through a stall.
No. TREADY is low at E2/E3. The final byte and TLAST are accepted together at E4.
The 32-to-16 converter emits the same five bytes as three narrower transfers. Its output handshake is separate from its input handshake. The exercise buffers one input word and admits no new input while that word drains. It can omit an unused upper half whose lanes are null, while TLAST remains on the final emitted transfer for the packet.
Compare the accepted byte sequence and accepted packet-end marker at both boundaries, not the raw beat counts. General converters also need contracts for position bytes, TID/TDEST, TUSER and zero-byte TLAST transfers; this packed-data exercise does not implement those cases.
Expected accepted output sequence
Transfer | TDATA | TKEEP | TLAST | Bytes |
|---|---|---|---|---|
First | 0x2211 | 11 | 0 | 11,22 |
Second | 0x4433 | 11 | 0 | 33,44 |
Third | 0x0055 | 01 | 1 | 55 |
What to notice: Use output handshakes to advance output_bytes through 2, 4 and 5. The retained output does not count again during a stall. TLAST identifies the final accepted output beat, not the first presentation of input TLAST.
Its keep mask qualifies only the remaining byte. The accepted byte total reaches five and the packet count reaches one.
No. That input word was not the packet's last word. Byte 55 still follows, so TLAST belongs on the third output transfer.
This bounded converter handles the shown packed-data subset. Its bench checks accepted output bytes against 11,22,33,44,55, verifies the partial final keep mask through byte counts, holds an output through two stalled edges and requires one packet end. Two matched-timing variants corrupt a byte or remove TLAST; each fault must be detected. It does not model arbitrary holes, position bytes, multiple streams or zero-byte packet ends.
// Packed-data exercise only: 32-to-16, one stream, no position bytes or zero-byte packet ends.
// BUG_BYTE corrupts one output byte; BUG_LAST removes the packet-end marker.
`timescale 1ns/1ps
module stream_downsize #(parameter BUG_BYTE = 0, parameter BUG_LAST = 0) (
input wire clk, rst,
input wire in_valid,
output wire in_ready,
input wire [31:0] in_data,
input wire [3:0] in_keep,
input wire in_last,
output wire out_valid,
input wire out_ready,
output wire [15:0] out_data,
output wire [1:0] out_keep,
output wire out_last
);
reg busy, upper;
reg [31:0] saved_data;
reg [3:0] saved_keep;
reg saved_last;
assign in_ready = !busy;
assign out_valid = busy;
assign out_data = (upper ? saved_data[31:16] : saved_data[15:0]) ^
(BUG_BYTE && upper ? 16'h1 : 16'h0);
assign out_keep = upper ? saved_keep[3:2] : saved_keep[1:0];
assign out_last = BUG_LAST ? 1'b0 : saved_last && (upper || saved_keep[3:2] == 0);
always @(posedge clk) begin
if (rst) begin busy <= 0; upper <= 0; saved_data <= 0; saved_keep <= 0; saved_last <= 0; end
else begin
if (in_valid && in_ready) begin
saved_data <= in_data; saved_keep <= in_keep; saved_last <= in_last;
busy <= 1; upper <= 0;
end
if (out_valid && out_ready) begin
if (!upper && saved_keep[3:2] != 0) upper <= 1;
else begin busy <= 0; upper <= 0; end
end
end
end
endmodule
module tb;
reg clk = 0;
always #5 clk = ~clk;
reg rst = 1, in_valid = 0, in_last = 0, out_ready = 0;
reg [31:0] in_data = 0;
reg [3:0] in_keep = 0;
wire in_ready, out_valid, out_last;
wire [15:0] out_data;
wire [1:0] out_keep;
wire byte_ready, byte_valid, byte_last, last_ready, last_valid, last_last;
wire [15:0] byte_data, last_data;
wire [1:0] byte_keep, last_keep;
integer accepted_inputs = 0, output_beats = 0, output_bytes = 0, packets = 0;
integer next_index;
reg [7:0] expected [0:4];
reg byte_fault_seen = 0, last_fault_seen = 0, was_stalled = 0;
reg [18:0] held_output;
stream_downsize good (.*);
stream_downsize #(.BUG_BYTE(1)) bad_byte (
.clk(clk), .rst(rst), .in_valid(in_valid), .in_ready(byte_ready), .in_data(in_data),
.in_keep(in_keep), .in_last(in_last), .out_valid(byte_valid), .out_ready(out_ready),
.out_data(byte_data), .out_keep(byte_keep), .out_last(byte_last)
);
stream_downsize #(.BUG_LAST(1)) bad_last (
.clk(clk), .rst(rst), .in_valid(in_valid), .in_ready(last_ready), .in_data(in_data),
.in_keep(in_keep), .in_last(in_last), .out_valid(last_valid), .out_ready(out_ready),
.out_data(last_data), .out_keep(last_keep), .out_last(last_last)
);
always @(posedge clk) begin
if (rst) begin
accepted_inputs <= 0; output_beats <= 0; output_bytes <= 0; packets <= 0;
byte_fault_seen <= 0; last_fault_seen <= 0; was_stalled <= 0;
end else begin
if (in_ready !== byte_ready || in_ready !== last_ready ||
out_valid !== byte_valid || out_valid !== last_valid ||
out_keep !== byte_keep || out_keep !== last_keep) begin
$display("FAIL: fault variants changed the intended handshake/keep schedule"); $finish;
end
if (was_stalled && (!out_valid || {out_data, out_keep, out_last} !== held_output)) begin
$display("FAIL: good converter changed its stalled output"); $finish;
end
was_stalled <= out_valid && !out_ready;
held_output <= {out_data, out_keep, out_last};
if (in_valid && in_ready) begin
if (!(in_keep == 4'hf || in_keep == 4'h1)) begin
$display("FAIL: stimulus outside this exercise's declared keep subset"); $finish;
end
accepted_inputs <= accepted_inputs + 1;
end
if (out_valid && out_ready) begin
next_index = output_bytes;
for (integer lane = 0; lane < 2; lane = lane + 1) begin
if (out_keep[lane]) begin
if (next_index >= 5) begin $display("FAIL: unexpected extra output byte"); $finish; end
if (out_data[8*lane +: 8] !== expected[next_index]) begin
$display("FAIL: good converter changed accepted byte order/value"); $finish;
end
if (byte_data[8*lane +: 8] !== expected[next_index]) byte_fault_seen <= 1;
next_index = next_index + 1;
end
end
if (out_last !== (next_index == 5)) begin $display("FAIL: good packet-end position"); $finish; end
if (last_last !== (next_index == 5)) last_fault_seen <= 1;
output_bytes <= next_index;
output_beats <= output_beats + 1;
if (out_last) packets <= packets + 1;
end
end
end
task send_word(input [31:0] data, input [3:0] keep, input last);
begin
@(negedge clk); in_valid = 1; in_data = data; in_keep = keep; in_last = last;
begin : wait_accept
for (integer cycle = 0; cycle < 8; cycle = cycle + 1) begin
@(posedge clk);
if (in_ready) begin @(negedge clk); in_valid = 0; disable wait_accept; end
end
$display("FAIL: bounded test source exceeded its acceptance budget"); $finish;
end
end
endtask
initial begin
expected[0] = 8'h11; expected[1] = 8'h22; expected[2] = 8'h33;
expected[3] = 8'h44; expected[4] = 8'h55;
$dumpfile("wave.vcd"); $dumpvars(0, tb);
repeat (2) @(posedge clk);
@(negedge clk); rst = 0;
send_word(32'h4433_2211, 4'hf, 0);
repeat (2) @(negedge clk);
out_ready = 1;
send_word(32'h0000_0055, 4'h1, 1);
repeat (3) @(negedge clk);
if (accepted_inputs != 2 || output_beats != 3 || output_bytes != 5 || packets != 1 ||
!byte_fault_seen || !last_fault_seen || out_valid) begin
$display("FAIL: input/output counts, drain or intended negative detections"); $finish;
end
$display("PASS: stalled conversion preserved five bytes and one packet; byte/TLAST faults detected");
$finish;
end
endmoduleLite AW and W can arrive separately; retain both and provide one response for each paired write.
A stalled Stream offer retains data, keep mask and packet-end flag until acceptance.
Width conversion preserves the agreed byte sequence and packet boundaries, not the original number of beats.