A decoder without a default slave is a promise the hardware cannot keep: some address in 32 bits will miss every select, the master will wait for a response that never comes, and the CPU will hang on what should have been a trappable error. The default slave exists so that every address completes. On NanoSoC three responders share that duty: the interconnect default answering DECERR for holes only (first beat one clock after the address handshake, then one beat per clock while the master is ready), the secure-fault responder answering SLVERR with no data for a mapped-but-forbidden access, and the APB default answering PSLVERR with the same timing as a normal peripheral access so the bridge never has to special-case it. The first two differ only in the code they return, so one design serves both behind a mode input. One simplification to note: the sketch omits the ID ports, and a production responder must echo each transaction's ID on its read and write responses; the beat accounting below is unaffected.
Both AXI responders accept the address at once, then return one error beat per requested beat: a burst of length N, encoded as ARLEN N minus 1, gets exactly N read beats carrying the responder's code on every beat, DECERR for the default slave and SLVERR for the fault responder, with RLAST on the last beat only. Writes get every write beat accepted and dropped, then a single write response carrying the same code. The beat count is held in a small down-counter preloaded from ARLEN, which is why the proof below checks every length from 1 to 16 in both modes. The APB default slave is simpler: it answers any selected access with pready and pslverr together after one wait state, three clocks per access, so unmapped slots and offsets cost software exactly what a slow peripheral costs and hang nothing.
// NanoSoC teaching values: error responders. Every rejected access completes; none hangs.
// fault_mode = 0: default slave, holes only, DECERR. fault_mode = 1: secure-fault
// responder, mapped-but-forbidden, SLVERR with no data. Both return one error beat
// per requested beat with RLAST on the last; writes are sunk, one B response.
module nano_axi_default_slave (
input logic aclk,
input logic aresetn,
input logic fault_mode,
input logic arvalid,
output logic arready,
input logic [7:0] arlen,
output logic rvalid,
input logic rready,
output logic [1:0] rresp,
output logic rlast,
input logic awvalid,
output logic awready,
input logic wvalid,
output logic wready,
input logic wlast,
output logic bvalid,
input logic bready,
output logic [1:0] bresp
);
logic [7:0] beats_left;
logic active;
logic w_active, w_last_seen;
always_ff @(posedge aclk or negedge aresetn) begin
if (!aresetn) begin
active <= 1'b0;
beats_left <= 8'h0;
end else if (arvalid && arready) begin
active <= 1'b1;
beats_left <= arlen;
end else if (rvalid && rready) begin
if (beats_left == 8'h0) active <= 1'b0;
else beats_left <= beats_left - 8'h1;
end
end
assign arready = ~active;
assign rvalid = active;
assign rresp = fault_mode ? 2'b10 : 2'b11; // SLVERR for fault, DECERR for holes
assign rlast = (beats_left == 8'h0);
always_ff @(posedge aclk or negedge aresetn) begin
if (!aresetn) begin
w_active <= 1'b0;
w_last_seen <= 1'b0;
end else if (awvalid && awready) begin
w_active <= 1'b1;
w_last_seen <= 1'b0;
end else if (wvalid && wready && wlast) begin
w_last_seen <= 1'b1;
end else if (bvalid && bready) begin
w_active <= 1'b0;
w_last_seen <= 1'b0;
end
end
assign awready = ~w_active;
assign wready = w_active & ~w_last_seen; // stop accepting once WLAST seen
assign bvalid = w_active & w_last_seen;
assign bresp = fault_mode ? 2'b10 : 2'b11; // same code as the read side
endmodule
module nano_apb_default_slave (
input logic pclk,
input logic presetn,
input logic psel,
input logic penable,
output logic pready,
output logic pslverr
);
// One wait state like the DMA: setup, access, access-with-ready.
logic wait1, done2;
always_ff @(posedge pclk or negedge presetn) begin
if (!presetn) begin
wait1 <= 1'b0;
done2 <= 1'b0;
end else begin
wait1 <= psel & ~penable;
done2 <= wait1 & psel & penable;
end
end
assign pready = done2;
assign pslverr = done2;
endmodule
Two checks back the responder. The reference model below is an independent statement of the required behaviour, not a copy of the RTL: for every legal burst length it requires exactly that many response beats carrying the mode's code on every beat with RLAST only on the last, even when the master stalls ready, and exactly one write response per burst. It checks all sixteen lengths in both modes (DECERR for holes, SLVERR for faults), with and without stalls. Because the model is written from the requirement, it can fail: change the required RLAST position or a code and it does. The RTL itself is exercised by the self-checking testbench at the end of the listing, which drives all sixteen lengths in both modes with a stalled RREADY and write beats with gaps, holds the write response until it is accepted, and offers the first beat of the next burst while the previous response is still pending. Re-add the earlier bug, wready = w_active with no WLAST term, and the testbench fails: that early beat would be swallowed and the next burst would hang.
DECERR, SLVERR = 0b11, 0b10
def run_reads(arlen, fault, stall_every=0):
# Mirror of the RTL down-counter; resp code follows fault_mode.
resp = SLVERR if fault else DECERR
beats_left = arlen
codes, last_at = [], None
cycle = 0
while True:
rready = True if stall_every == 0 else (cycle % stall_every != 1)
if rready:
codes.append(resp)
if beats_left == 0:
last_at = len(codes)
break
beats_left -= 1
cycle += 1
assert cycle < 1000
return codes, last_at
def run_writes(awlen, fault):
# Every beat accepted and dropped, then exactly one write response.
sunk = awlen + 1
return sunk, [SLVERR if fault else DECERR]
for fault in (False, True):
want = SLVERR if fault else DECERR
for arlen in range(16):
for stall in (0, 3):
codes, last_at = run_reads(arlen, fault, stall)
assert len(codes) == arlen + 1 and last_at == arlen + 1
assert all(c == want for c in codes)
sunk, bresps = run_writes(arlen, fault)
assert sunk == arlen + 1 and bresps == [want]
codes, last_at = run_reads(3, False)
try:
assert last_at == 3 # negative control: RLAST one beat early must be rejected
raise SystemExit("reference model cannot fail")
except AssertionError:
pass
print("PASS: both modes, lengths 1..16, exact beats, code per beat, RLAST last, one B response")Every decoder output fans into either a real slave or a default slave; the connectivity check proves no select dangles.
The reference model above, or its equivalent, runs in the build for all sixteen burst lengths in both modes.
Write bursts to holes are sunk and answered, not just reads: the write path has its own counter and its own test.
Send single and sixteen-beat reads and writes to one address in each hole and count the beats: H1 and H4 must each return exactly the requested beats with DECERR and a single RLAST or write response, H3 must return one SLVERR per access with no hang, and a non-secure burst at SRAM1 must return SLVERR on every beat from the fault responder, never DECERR from the default slave. If a burst returns short, the down-counter was preloaded wrong; if a faulted burst returns DECERR, the fault select is wired into the wrong responder. Both are caught by the model before they reach simulation.
Four beats, DECERR on all four, RLAST low on the first three and high on the fourth. The count comes from ARLEN plus one; the code is 2'b11 on every beat.