A timeout detector notices that a transaction has waited too long; a transaction engine decides what happens next. The bus_timeout module in this lesson is only the detector: it raises a level after LIMIT cycles, and every recovery step is the engine's job.
The default slave returns an error response for illegal addresses. AXI names two of them: SLVERR for a slave-detected error and DECERR for a decode error on an unmapped address.
Case | Response | Code |
|---|---|---|
Slave detects a bad access | SLVERR | 2'b10 |
Unmapped address at decode | DECERR | 2'b11 |
Hung transaction past the agreed limit | System engine may synthesize one SLVERR completion; detector alone does not | 2'b10 under that policy |
Normal completion | OKAY | 2'b00 |
Timeout monitors protect hung transactions in bring-up. A counter runs while a transaction is in flight and clears when the slave answers.
timeout is a combinational level qualified by req && !ack: it asserts when the counter reaches LIMIT and drops immediately if ack arrives or req falls. It neither emits SLVERR nor remembers aborted ownership, quarantines late replies or authorises reuse.
LIMIT must be at least 1; the procedural initial check reports an error at simulation time zero, not a portable elaboration guarantee.
One hung transaction, from issue to reuse, shows every obligation the detector leaves to the engine.
Read the axis left to right. Everything after the timeout mark is work for the engine; the detector only supplies the mark.
Timeout for A: latch the decision and keep A's response context; produce at most one upstream error completion.
B arrives: keep admission closed while A's downstream request can still answer.
Late reply for A: consume and discard it under A's retained ownership, never as B's response. The raw ack that clears the detector's level must not clear the fault policy.
Reopen: only after a bounded drain, or a coordinated peer isolation or reset that proves old responses cannot return. Otherwise stay faulted.
Choose the system policy explicitly. The engine above is a sketch of one policy, not part of the listing. Admission is open only in IDLE, so B waits through BUSY and QUARANTINE. See the sequencer's drain/fault/recover contract for a full version. A downstream reset alone cannot retract a response already queued in a bridge; include all owners and queues in the quiescence proof.
No. The agreed latency policy decides when to declare failure. An engine must latch that decision, issue one upstream completion and retain/quarantine downstream ownership until drain or coordinated isolation/reset. The detector cannot safely assign a late old reply to a new request.
DECERR. An unmapped address never reaches a slave, so the decode error is the true cause and the one software should see.
The timeout monitor plus a testbench that proves a normal access never fires, a hung access fires exactly at cycle 8 and holds without re-firing, and dropping the request restarts the count.
// bus_timeout: detects a hung transaction; recovery/response ownership are external.
module bus_timeout #(
parameter int LIMIT = 8 // must be at least 1
)(
input logic clk, rst_n,
input logic req, // transaction in flight
input logic ack, // slave answered
output logic timeout // qualified level, not a sticky fault or response
);
// LIMIT = 0 would give W = $clog2(1) = 0, a zero-width counter and an illegal cast,
// so the width stays elaborable and the check below rejects the configuration.
localparam int W = (LIMIT > 0) ? $clog2(LIMIT + 1) : 1;
initial begin
if (LIMIT < 1) $error("bus_timeout: LIMIT must be at least 1");
end
logic [W-1:0] count;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) count <= '0;
else if (!req || ack) count <= '0;
else if (count < LIMIT[W-1:0]) count <= count + 1'b1;
end
assign timeout = (count == LIMIT[W-1:0]) && req && !ack;
endmodule