Two flags, aw_seen and w_seen, latch the write halves independently. Each rises on its own channel handshake and both clear when the pair commits and the B response is raised.
Committing needs the word index, addr[AW-1:2]: word 0 is reg0 at byte address 0x00 and word 1 is reg1 at 0x04. Any other word is unmapped and answers SLVERR without touching a register.
Read traffic is simpler: one address handshake loads registered read data and response, and s_arready = !s_rvalid limits the slave to a single outstanding read.
This register-bank template supports only DW=32 and AW>=3: its two 32-bit words occupy byte addresses 0x00 and 0x04. Elaboration guards reject other widths instead of silently changing the data width while retaining a four-byte stride. A 64-bit bank needs an eight-byte stride and matching address/lane semantics; it is a separate implementation.
The full listing is below. The testbench under Testbench checks directed cases.
Same-edge read/write policy: if AR is accepted for the register being written on that edge, R captures the old value. Nonblocking assignments evaluate both right-hand sides before the write updates the register. A later read sees the new value; this is this bank's policy, not a universal AXI requirement.
module axilite_regs #(
parameter int AW = 8,
parameter int DW = 32
) (
input logic clk,
input logic rst_n,
input logic s_awvalid,
output logic s_awready,
input logic [AW-1:0] s_awaddr,
input logic s_wvalid,
output logic s_wready,
input logic [DW-1:0] s_wdata,
input logic [DW/8-1:0] s_wstrb,
output logic s_bvalid,
input logic s_bready,
output logic [1:0] s_bresp,
input logic s_arvalid,
output logic s_arready,
input logic [AW-1:0] s_araddr,
output logic s_rvalid,
input logic s_rready,
output logic [DW-1:0] s_rdata,
output logic [1:0] s_rresp
);
localparam [1:0] OKAY = 2'b00;
localparam [1:0] SLVERR = 2'b10;
if (DW != 32 || AW < 3) begin : g_bad_contract
$error("axilite_regs requires DW=32 and AW>=3 (four-byte register stride)");
end
logic [DW-1:0] reg0, reg1;
logic aw_seen, w_seen;
logic [AW-1:0] awaddr_q;
logic [DW-1:0] wdata_q;
logic [DW/8-1:0] wstrb_q;
logic [1:0] bresp_q, rresp_q;
assign s_awready = !aw_seen && !s_bvalid;
assign s_wready = !w_seen && !s_bvalid;
assign s_arready = !s_rvalid;
assign s_bresp = bresp_q;
assign s_rresp = rresp_q;
function automatic logic [DW-1:0] apply_wstrb(
input logic [DW-1:0] old_v,
input logic [DW-1:0] new_v,
input logic [DW/8-1:0] strb
);
logic [DW-1:0] tmp;
int i;
begin
tmp = old_v;
for (i = 0; i < DW/8; i++) begin
if (strb[i])
tmp[i*8 +: 8] = new_v[i*8 +: 8];
end
apply_wstrb = tmp;
end
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
aw_seen <= 1'b0;
w_seen <= 1'b0;
awaddr_q <= '0;
wdata_q <= '0;
wstrb_q <= '0;
s_bvalid <= 1'b0;
bresp_q <= OKAY;
s_rvalid <= 1'b0;
rresp_q <= OKAY;
s_rdata <= '0;
reg0 <= '0;
reg1 <= '0;
end else begin
if (s_awvalid && s_awready) begin
aw_seen <= 1'b1;
awaddr_q <= s_awaddr;
end
if (s_wvalid && s_wready) begin
w_seen <= 1'b1;
wdata_q <= s_wdata;
wstrb_q <= s_wstrb;
end
if (aw_seen && w_seen && !s_bvalid) begin
case (awaddr_q[AW-1:2])
0: begin
reg0 <= apply_wstrb(reg0, wdata_q, wstrb_q);
bresp_q <= OKAY;
end
1: begin
reg1 <= apply_wstrb(reg1, wdata_q, wstrb_q);
bresp_q <= OKAY;
end
default: begin
bresp_q <= SLVERR;
end
endcase
s_bvalid <= 1'b1;
aw_seen <= 1'b0;
w_seen <= 1'b0;
end
if (s_bvalid && s_bready)
s_bvalid <= 1'b0;
if (s_arvalid && s_arready) begin
case (s_araddr[AW-1:2])
0: begin
s_rdata <= reg0;
rresp_q <= OKAY;
end
1: begin
s_rdata <= reg1;
rresp_q <= OKAY;
end
default: begin
s_rdata <= '0;
rresp_q <= SLVERR;
end
endcase
s_rvalid <= 1'b1;
end
if (s_rvalid && s_rready)
s_rvalid <= 1'b0;
end
end
endmoduleDetail | Choice in this slave |
|---|---|
Write order | AW and W pair in either order; commit happens once, after both are held |
Partial writes |
|
Unmapped access | Write and read both answer SLVERR ( |
Read depth | One outstanding read: |
Case style | Plain |
The write waveform shows a W-before-AW pair with a stalled B channel. Each slot is one clock cycle, numbered from slot 0. The rule for reading it: a channel transfers at the rising edge that ends a slot in which its VALID and READY are both high.
Slot 0: WVALID and WREADY are both high, so the W beat transfers at the end of this slot.
Slot 1: WVALID is low again (the beat is held inside the slave, so WREADY is low too). AWVALID and AWREADY are both high, so the AW beat transfers at the end of this slot.
Slot 2: both halves are held and both readies are low. The pair commits at the end of this slot.
Slot 3: BVALID is high and BREADY is low, so the response stalls and holds stable.
Slot 4: still stalled.
Slot 5: BVALID and BREADY are both high, so the B handshake completes at the end of this slot.
Slot 6: idle: BVALID is low and both readies are high again.
The read waveform shows one accepted read, a second request waiting for ARREADY, and a stalled R channel. The same rule applies: a transfer happens at the end of a slot where VALID and READY are both high.
Slot 0: ARVALID and ARREADY are both high, so the address transfers at the end of this slot.
Slot 1: RVALID is high with the read data, but RREADY is low, so R stalls. ARREADY is low while RVALID is high.
Slot 2: a second ARVALID is high but ARREADY is low, so the request waits: the master must keep ARVALID and ARADDR stable until ARREADY rises. Deasserting ARVALID before the address is accepted is a protocol violation.
Slot 3: the stall continues on both channels.
Slot 4: RVALID and RREADY are both high, so the first R beat transfers at the end of this slot. ARVALID is still high and unchanged.
Slot 5: RVALID is low, so ARREADY is high. With ARVALID still high, the held address transfers at the end of this slot.
Slot 6: RVALID is high again with the second read's data.
BVALID rise, and when can B first transfer?AW capture updates aw_seen after edge 4 by a nonblocking assignment. The commit condition first sees both flags on edge 5, so BVALID rises after edge 5. With BREADY high, the earliest B handshake is edge 6.
WSTRB all ones. Which bytes change, and what does B carry?None: the decode misses, so no register updates and B carries SLVERR.
The slave plus finite directed checks for AW-before-W, W-before-AW, three strobe patterns, zero-strobe address errors, same-edge old-data read/write collision, and three-cycle stalls on B and R. Drive on falling edges, sample handshakes before rising-edge updates, and inspect registered results after them.
module axilite_regs #(
parameter int AW = 8,
parameter int DW = 32
) (
input logic clk,
input logic rst_n,
input logic s_awvalid,
output logic s_awready,
input logic [AW-1:0] s_awaddr,
input logic s_wvalid,
output logic s_wready,
input logic [DW-1:0] s_wdata,
input logic [DW/8-1:0] s_wstrb,
output logic s_bvalid,
input logic s_bready,
output logic [1:0] s_bresp,
input logic s_arvalid,
output logic s_arready,
input logic [AW-1:0] s_araddr,
output logic s_rvalid,
input logic s_rready,
output logic [DW-1:0] s_rdata,
output logic [1:0] s_rresp
);
localparam [1:0] OKAY = 2'b00;
localparam [1:0] SLVERR = 2'b10;
if (DW != 32 || AW < 3) begin : g_bad_contract
$error("axilite_regs requires DW=32 and AW>=3 (four-byte register stride)");
end
logic [DW-1:0] reg0, reg1;
logic aw_seen, w_seen;
logic [AW-1:0] awaddr_q;
logic [DW-1:0] wdata_q;
logic [DW/8-1:0] wstrb_q;
logic [1:0] bresp_q, rresp_q;
assign s_awready = !aw_seen && !s_bvalid;
assign s_wready = !w_seen && !s_bvalid;
assign s_arready = !s_rvalid;
assign s_bresp = bresp_q;
assign s_rresp = rresp_q;
function automatic logic [DW-1:0] apply_wstrb(
input logic [DW-1:0] old_v,
input logic [DW-1:0] new_v,
input logic [DW/8-1:0] strb
);
logic [DW-1:0] tmp;
int i;
begin
tmp = old_v;
for (i = 0; i < DW/8; i++) begin
if (strb[i])
tmp[i*8 +: 8] = new_v[i*8 +: 8];
end
apply_wstrb = tmp;
end
endfunction
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
aw_seen <= 1'b0;
w_seen <= 1'b0;
awaddr_q <= '0;
wdata_q <= '0;
wstrb_q <= '0;
s_bvalid <= 1'b0;
bresp_q <= OKAY;
s_rvalid <= 1'b0;
rresp_q <= OKAY;
s_rdata <= '0;
reg0 <= '0;
reg1 <= '0;
end else begin
if (s_awvalid && s_awready) begin
aw_seen <= 1'b1;
awaddr_q <= s_awaddr;
end
if (s_wvalid && s_wready) begin
w_seen <= 1'b1;
wdata_q <= s_wdata;
wstrb_q <= s_wstrb;
end
if (aw_seen && w_seen && !s_bvalid) begin
case (awaddr_q[AW-1:2])
0: begin
reg0 <= apply_wstrb(reg0, wdata_q, wstrb_q);
bresp_q <= OKAY;
end
1: begin
reg1 <= apply_wstrb(reg1, wdata_q, wstrb_q);
bresp_q <= OKAY;
end
default: begin
bresp_q <= SLVERR;
end
endcase
s_bvalid <= 1'b1;
aw_seen <= 1'b0;
w_seen <= 1'b0;
end
if (s_bvalid && s_bready)
s_bvalid <= 1'b0;
if (s_arvalid && s_arready) begin
case (s_araddr[AW-1:2])
0: begin
s_rdata <= reg0;
rresp_q <= OKAY;
end
1: begin
s_rdata <= reg1;
rresp_q <= OKAY;
end
default: begin
s_rdata <= '0;
rresp_q <= SLVERR;
end
endcase
s_rvalid <= 1'b1;
end
if (s_rvalid && s_rready)
s_rvalid <= 1'b0;
end
end
endmodule
Source checker, run with Icarus Verilog 13.0 (2026-10-16): prints PASS. Finite directed checks: AW before W, W before AW, three strobe patterns, zero-strobe address errors, a same-edge old-data read and write collision, and three-cycle stalls on B and R. It is not a proof for every traffic pattern.