Skip to content

AMBA AHB · Module 16

HREADYOUT Generation

Implementing correct per-slave HREADYOUT timing — the slave output saying 'I can complete the current transfer this cycle' (high) or 'insert a wait state' (low). A zero-wait slave ties it high; a wait slave drives it from a counter/FSM modeling the access latency. Crucially, HREADYOUT (the slave's output) is not HREADY (the shared input — the selected slave's HREADYOUT routed back to all); gate the address-phase capture on HREADY, not your own HREADYOUT, and never drive HREADYOUT high before genuinely ready.

The last three chapters built whole slaves; this chapter zooms in on the single most timing-critical output a slave drives: HREADYOUT. Every AHB slave drives a HREADYOUT that answers one question each cycle — "can I complete the current transfer this cycle?" High means yes, finish now; low means no, insert a wait state (hold the master — chapter 6.2). A zero-wait slave (the simple slave, the register bank) ties HREADYOUT high — it's always ready. A wait-inserting slave (the memory slave, a slow peripheral) drives HREADYOUT from a small counter or FSM that models the access latencylow while the access is in progress, high in the cycle it can genuinely finish. The chapter's other half is a crucial distinction most beginners get wrong: HREADYOUT (the slave's output) is not HREADY (the slave's input). HREADY is the shared bus-ready signal — the interconnect selects the addressed slave's HREADYOUT and routes it back to every slave (and the master), so all slaves know when the current bus transfer actually completes (HREADY vs HREADYOUT). This chapter implements correct HREADYOUT generation — the timing contract at the heart of every slave.

1. What Is It?

HREADYOUT generation is the slave logic that drives the HREADYOUT output — the slave's per-cycle statement of whether it can complete the current transfer. Its parts:

  • The meaningHREADYOUT high = "I can complete the current transfer this cycle"; low = "not ready — insert a wait state" (hold the master).
  • Zero-wait — a slave that always responds in one cycle ties HREADYOUT high (no logic needed).
  • Wait-inserting — a slave with latency drives HREADYOUT from a counter/FSM modeling the access: low while not ready, high when ready.
  • HREADYOUT ≠ HREADYHREADYOUT is the slave's output; HREADY is the shared input (the selected slave's HREADYOUT routed back to all slaves and the master).
HREADYOUT generation: captured context → ready logic (tie-high or counter/FSM) → HREADYOUT output, with HREADY as a separate input.
Figure 1 — generating HREADYOUT. The slave decides, each cycle, whether it can complete the current transfer. A zero-wait slave ties HREADYOUT high. A wait-inserting slave uses a counter/FSM (fed by the captured context — sel_q, the memory-ready condition, access-in-progress) to drive HREADYOUT low while not ready and high in the cycle it can finish. The HREADYOUT output goes to the interconnect, which forms the shared HREADY; the slave also receives HREADY as an input. Drive HREADYOUT high only when the slave can genuinely complete, and qualify wait insertion to a real selected transfer.

So HREADYOUT generation is the slave's timing contract: it tells the bus, every cycle, whether the slave is done (high) or needs more time (low). For a zero-wait slave, this is trivial — tie it high (the slave is always ready, so it never stalls the bus). For a wait-inserting slave, it's the core timing logic — a counter or FSM that models how many cycles the access needs and drives HREADYOUT low for the wait cycles, high in the cycle the access completes. The discipline is twofold: (1) drive HREADYOUT high only in the cycle the slave can genuinely finish (too early → the master samples garbage; chapter 16.3), and (2) don't confuse HREADYOUT (output) with HREADY (input). HREADYOUT generation is the timing heart of a slave — the signal that paces every transfer.

2. Why Does It Exist?

HREADYOUT exists because the bus protocol is fixed-rhythm (one transfer advances per cycle when ready), but slaves have varying speeds — so each slave needs a per-cycle signal to say "ready" or "not ready", and the bus needs to combine these into a single shared pace.

The per-slave ready signal is the root: the bus advances a transfer when the addressed slave is ready. But slaves differ — a fast SRAM is ready immediately, a slow flash needs cycles, a busy peripheral may stall. So each slave needs a way to say, each cycle, whether it's ready — and that's HREADYOUT. So HREADYOUT exists as the slave's per-cycle ready statement. So the bus can know when this slave is done. So it's the slave's pace signal.

The shared bus pace drives the HREADY distinction: the bus has one master pacing one transfer at a time, but many slaves. Only the addressed slave's readiness matters for the current transfer — yet every slave (and the master) needs to know when the current transfer completes (to advance their own pipelines correctly). So the interconnect selects the addressed slave's HREADYOUT and broadcasts it as the shared HREADY — the one signal that paces the whole bus. So HREADYOUT (per-slave) and HREADY (shared) exist as two signals: each slave drives its HREADYOUT, the interconnect combines them (by selection) into the shared HREADY, and everyone receives HREADY. So the two-signal structure exists to turn many per-slave readinesses into one shared bus pace. So it's per-slave out, shared in. So the distinction is structural.

The correct timing requirement is why generation is delicate: because HREADYOUT paces the transfer, getting its timing right is critical. Drive it high too early (before the data is ready) and the master samples garbage (corruption). Drive it low forever (stuck) and the bus hangs (chapter 18.1). Drive it low on an idle cycle (unqualified) and you stall the bus needlessly. So HREADYOUT generation must be precisely timed — high exactly when ready, low only for genuine waits on real transfers. So the logic (tie-high for zero-wait, counter/FSM for waits) exists to get this timing right. So HREADYOUT exists because: the bus advances on slave readiness (the per-slave ready signal — the why); the bus needs one shared pace from many slaves (the interconnect selects and broadcasts the addressed slave's HREADYOUT as the shared HREADY — the two-signal structure); and the timing must be precise (high exactly when ready, low only for real waits — driving the careful generation logic). So HREADYOUT is the slave's contribution to the bus's pace — the per-cycle "ready/not-ready" that, selected and broadcast as HREADY, paces every transfer on the bus. So generating it correctly is foundational to a working slave. So this chapter gets the pace signal right.

3. Mental Model

Model HREADYOUT as each chef in a kitchen raising or lowering a "dish ready" paddle — paddle up means "this dish is plated, send it out now", paddle down means "still cooking, the waiter must wait". The head waiter (the interconnect) watches only the chef cooking the current order (the addressed slave) and rings a single shared bell (HREADY) when that chef's paddle is up — and everyone in the kitchen hears the bell, so they all know the current order just went out.

A kitchen of chefs (the slaves), each cooking to order. Each chef has a "dish ready" paddle (HREADYOUT). When a chef's dish is plated and ready, they raise the paddle (HREADYOUT high) — "send it out now". When the dish is still cooking, the paddle stays down (HREADYOUT low) — "the waiter must wait". A fast chef (a zero-wait slave) always has the paddle up — their dishes are instant (the paddle is nailed up). A slow chef (a wait-inserting slave) keeps the paddle down while cooking and raises it the moment the dish is done — and they know how long each dish takes (a counter/FSM modeling the latency). Now, the head waiter (the interconnect) is handling one order at a time, and watches only the chef cooking that order (the addressed slave). When that chef's paddle goes up, the head waiter rings a single shared bell (HREADY) — "the current order is ready, it's going out". Crucially, everyone in the kitchen hears the bellall the chefs, and the expediter (the master). They need to hear it: the bell tells them the current order just completed, so they can advance — start prepping the next order, clear the pass, etc. So the paddle is each chef's own signal (raise it when your dish is ready), but the bell is the shared signal (rung from the current chef's paddle, heard by all). A chef must not raise the paddle before the dish is actually ready — sending out a half-cooked dish (the master sampling garbage). And a chef shouldn't keep the paddle down when there's no order for them (stalling the pass needlessly — an unqualified wait on an idle cycle).

This captures HREADYOUT: each chef's paddle = each slave's HREADYOUT output; paddle up = HREADYOUT high (ready, complete now); paddle down = HREADYOUT low (wait state); the always-up paddle = a zero-wait slave (tie high); the slow chef raising it when done, knowing the cook time = a wait slave's counter/FSM; the head waiter watching only the current chef = the interconnect selecting the addressed slave's HREADYOUT; the single shared bell heard by all = the shared HREADY broadcast to every slave and the master; not raising the paddle on a half-cooked dish = not driving HREADYOUT high before ready (no garbage); not holding the paddle down with no order = qualifying waits to a real selected transfer. Each chef's own paddle, one shared bell rung from the current chef's.

Watch a zero-wait slave and a wait-inserting slave drive HREADYOUT, and the shared HREADY follow the selected one:

Per-slave HREADYOUT and the shared HREADY

5 cycles
Slave A is zero-wait (HREADYOUT_A always high). Slave B inserts one wait (HREADYOUT_B low in cycle 3, high in cycle 4). The shared HREADY follows the selected slave: high while A is addressed, then low in cycle 3 and high in cycle 4 while B is addressed.A addressed, zero-wait — HREADY high (completes)A addressed, zero-wait —HREADY high (completes)B addressed, inserts a wait — HREADY low (selected B's HREADYOUT)B addressed, inserts a wait— HREADY low (selected B'sHREADYOUT)B ready — HREADY high (completes)B ready — HREADY high(completes)HCLKaddressed slaveAABBBHREADYOUT_AHREADYOUT_BHREADY (shared)t0t1t2t3t4
Figure 2 — two slaves' HREADYOUT, and the shared HREADY following the selected one. Cycles 0-1: the master addresses Slave A (a zero-wait slave) — HREADYOUT_A is high throughout, so when A is selected, the shared HREADY is high (A's transfer completes in one cycle). Cycles 2-4: the master addresses Slave B (a wait-inserting slave) — HREADYOUT_B goes low in cycle 3 (B inserts a wait), then high in cycle 4; so the shared HREADY (now following B, the selected slave) is low in cycle 3 and high in cycle 4. HREADY always mirrors the currently-addressed slave's HREADYOUT. (HREADYOUT_A stays high even while B is selected — but it's ignored, because A isn't addressed.)

The model's lesson: each slave drives its own HREADYOUT paddle; the shared HREADY is rung from the currently-addressed slave's paddle and heard by all. In the waveform, the shared HREADY follows whichever slave is addressed — high for zero-wait A, then low-then-high for wait-inserting B.

4. Real Hardware Perspective

In hardware, HREADYOUT is a single output wire driven either by a constant (zero-wait) or by a small counter/FSM (wait-inserting); the interconnect builds the shared HREADY by muxing the addressed slave's HREADYOUT; and the slave consumes HREADY to pace its own pipeline.

The zero-wait tie-high: for a slave that always completes in one cycle (the simple slave 16.1, the register bank 16.2 — combinational read, single-cycle write), HREADYOUT is tied high (assign HREADYOUT = 1'b1;). No logic, no flop — the slave is always ready. A zero-wait slave's HREADYOUT is therefore a constant, and costs nothing.

HREADYOUT per-slave outputs feeding an interconnect mux that forms the shared HREADY broadcast back to all slaves and the master.
Figure 3 — HREADYOUT (output) vs HREADY (input). Each slave drives its own HREADYOUT (Slave A drives HREADYOUT_A, Slave B drives HREADYOUT_B). The interconnect selects the addressed slave's HREADYOUT and routes it back as the shared HREADY, broadcast to all slaves and the master. So HREADYOUT is per-slave (this slave's readiness); HREADY is shared (the selected slave's readiness, seen by everyone). A slave drives HREADYOUT to control its own transfers and consumes HREADY to know when the current bus transfer completes.

The wait-inserting counter/FSM: for a slave with latency (the memory slave 16.3, a slow peripheral), HREADYOUT is driven by a small counter or FSM. When a real access starts (sel_q and a transfer in the data phase), the slave drives HREADYOUT low and counts the access latency; while counting, HREADYOUT stays low (wait states); when the count expires (the access can complete), HREADYOUT goes high for one cycle (completing the transfer), then the slave is ready for the next. The FSM ensures HREADYOUT is low exactly for the wait cycles and high exactly when ready. A wait-inserting slave's HREADYOUT is therefore sequential logic modelling the access latency.

The interconnect mux and the HREADY input: the interconnect forms the shared HREADY by selecting the addressed slave's HREADYOUTHREADY = HREADYOUT[selected_slave] (a mux driven by the address decode — chapter 13.7, HREADY aggregation). This HREADY is broadcast to all slaves and the master. Each slave consumes HREADY as an input — it tells the slave when the current bus transfer completes, which the slave needs to advance its own address-phase capture correctly (recall 16.1: the slave captures the address-phase context gated on HREADY high — because the address phase is only valid to capture when the previous transfer is completing). So in hardware, HREADY is a separate input the slave uses to pace its capture — distinct from the HREADYOUT it drives. So in hardware, HREADYOUT generation is a constant (zero-wait) or a counter/FSM (wait), the interconnect muxes the addressed HREADYOUT into the shared HREADY, and the slave consumes HREADY to pace its capture. The two signals — HREADYOUT out, HREADY in — are physically distinct wires with distinct roles. So in hardware, keep them separate. So the wiring distinguishes them.

5. System Architecture Perspective

At the system level, HREADYOUT/HREADY is the bus's flow-control mechanism — the way the single shared bus paces itself to the speed of whichever slave is currently addressed — and its correctness is foundational: a single mistimed or stuck HREADYOUT can corrupt data or hang the entire bus.

The bus flow control: HREADYOUT/HREADY is the flow-control signal of AHB — it's how the bus adapts its pace to the current slave. When a fast slave is addressed, HREADY is high and transfers fly by (one per cycle); when a slow slave is addressed, HREADY drops (wait states) and the bus slows to match. So at the system level, HREADYOUT/HREADY is the throttle — the mechanism that paces the bus to the addressed slave. So it's how the bus handles heterogeneous slave speeds. So it's the flow-control backbone.

The single-point criticality: because HREADY is shared and paces the whole bus, a single slave's HREADYOUT bug has system-wide consequences. If one slave drives HREADYOUT high too early, the master samples garbage on that slave's transfers (data corruption). If one slave gets stuck with HREADYOUT low (a hung FSM, a missed completion), the shared HREADY (when that slave is addressed) is stuck low — and the entire bus hangs (the master waits forever — chapter 18.1, stuck HREADY). So at the system level, every slave's HREADYOUT must be correct — a single faulty slave can break the whole bus. So HREADYOUT correctness is a system property, not a local one. So it's a single point of failure. So get every slave's HREADYOUT right.

The timeout protection: because a stuck HREADYOUT hangs the bus, systems often add a timeout/watchdog — if HREADY stays low too long (a slave that never completes), a bus watchdog triggers an error/recovery (rather than hanging forever). So at the system level, the risk of a stuck HREADYOUT is mitigated by timeout logic. So robust systems guard against the single-point failure. So at the system level, HREADYOUT/HREADY is the bus's flow-control mechanism (the throttle pacing the bus to the addressed slave — adapting to heterogeneous speeds), its correctness is a system property (a single mistimed HREADYOUT corrupts data; a single stuck one hangs the whole bus — chapter 18.1), and robust systems add timeout/watchdog protection against stuck-ready hangs. So HREADYOUT is where a local slave decision becomes a global bus behavior — making its correct generation one of the most consequential pieces of slave RTL. So pace correctly, never stick, and guard with timeouts.

6. Engineering Tradeoffs

HREADYOUT generation embodies the ready/not-ready, per-slave-vs-shared, precisely-timed design.

  • Tie-high (zero-wait) vs counter/FSM (wait). Tie-high is free and fast (no logic) but requires the slave to truly be single-cycle; a counter/FSM handles latency at the cost of sequential logic. Tie high only if genuinely zero-wait.
  • Combinational vs registered HREADYOUT. A combinational HREADYOUT (derived from current state) can respond fast but may create long paths / timing pressure; a registered HREADYOUT eases timing but must be planned a cycle ahead. Choose by timing closure.
  • Drive high exactly when ready vs conservatively late. Driving HREADYOUT high exactly when ready maximizes performance but demands precise latency modeling; an extra wait (conservative) is safe but slower. Be precise, but never early.
  • Per-slave correctness vs system robustness. Relying on every slave's HREADYOUT being correct is the ideal but fragile (one bug hangs the bus); adding a bus timeout/watchdog costs logic but contains the failure. Add a timeout in robust systems.

The throughline: HREADYOUT is the slave's per-cycle output stating whether it can complete the current transfer — high to finish now, low to insert a wait state (hold the master — chapter 6.2). A zero-wait slave ties it high; a wait-inserting slave drives it from a counter/FSM modeling the access latency (low while not ready, high when ready). It must be driven high only when the slave can genuinely finish (too early → garbage; chapter 16.3) and never stick low (hangs the bus; chapter 18.1). Critically, HREADYOUT (the slave's output) is not HREADY (the shared input — the interconnect selects the addressed slave's HREADYOUT and broadcasts it to all slaves and the master). Qualify wait insertion to a real selected transfer. It's the bus's flow-control signal — local correctness with system-wide consequence.

7. Industry Example

Trace HREADYOUT across a fast SRAM, a slow flash, and the interconnect that combines them.

A system has a zero-wait SRAM (Slave A) and a 4-cycle flash (Slave B) on the bus.

  • SRAM (Slave A) — tie-high. The SRAM is single-cycle, so HREADYOUT_A = 1 always (tied high). When the master addresses A, the interconnect selects HREADYOUT_A as the shared HREADY — which is high — so A's transfers complete one per cycle. No wait logic.
  • Flash (Slave B) — counter FSM. The flash needs 4 cycles. When the master addresses B with a real read (sel_q and a transfer), B's FSM drives HREADYOUT_B low and loads a 3-count; for 3 cycles HREADYOUT_B stays low (3 wait states); on the 4th cycle (data ready), HREADYOUT_B goes high, completing the read. The FSM models the flash's latency exactly.
  • The interconnect — select and broadcast. The interconnect forms the shared HREADY by selecting the addressed slave's HREADYOUT: when A is addressed, HREADY = HREADYOUT_A; when B is addressed, HREADY = HREADYOUT_B. The shared HREADY is broadcast to A, B, and the master. So HREADY is high during A's transfers and low-for-3-then-high during B's.
  • The slaves consume HREADY. Both A and B use the shared HREADY (not their own HREADYOUT) to gate their address-phase capture — they sample the next address phase only when HREADY is high (the current transfer completing). So even Slave A, while B is mid-wait and B is addressed, paces its pipeline by the shared HREADY.
  • A stuck-ready guard. A bus watchdog watches HREADY: if it stays low for, say, 256 cycles (a slave that never completes — a flash fault), the watchdog triggers an error, preventing a permanent hang.

The example shows HREADYOUT across the speed spectrum and the per-slave/shared distinction: the SRAM ties it high (zero-wait), the flash drives it from a counter FSM (4-cycle latency), the interconnect selects and broadcasts the addressed slave's HREADYOUT as the shared HREADY, the slaves consume HREADY (not HREADYOUT) to pace their capture, and a watchdog guards against a stuck-ready hang. This is HREADYOUT generation in a real multi-slave system. This is the bus's flow control.

7b. The Generator, Written Out

The mistakes list below is correct that the address-phase capture is gated on HREADY. What it does not say — and what turns that advice into a bug when applied carelessly — is the converse rule:

HREADYOUT must not depend combinationally on HREADY.

The interconnect builds HREADY from the selected subordinate's HREADYOUT. A subordinate whose HREADYOUT reads HREADY therefore closes a combinational loop through the fabric. HREADY may feed sequential logic freely — the capture register does exactly that — and must never reach HREADYOUT through combinational logic.

ahb_wait_gen.sv — HREADYOUT for a latency-modelled subordinate
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module ahb_wait_gen #(
  parameter int AW    = 32,
  parameter int DW    = 32,
  // Access latency in cycles. 0 = zero-wait (HREADYOUT ties high).
  parameter int WAITS = 3
) (
  input  logic          HCLK,
  input  logic          HRESETn,
  input  logic          HSEL,
  input  logic [1:0]    HTRANS,
  input  logic [AW-1:0] HADDR,
  input  logic          HWRITE,
  input  logic          HREADY,        // GLOBAL ready. Sequential use ONLY.
  input  logic [DW-1:0] HWDATA,
  input  logic [DW-1:0] mem_rdata,     // valid when mem_done is high
  input  logic          mem_done,
  output logic          mem_start,
  output logic          HREADYOUT,
  output logic [DW-1:0] HRDATA,
  output logic [1:0]    HRESP
);
  localparam logic [1:0] RSP_OKAY = 2'b00;
 
  // ── Address-phase capture, gated on the BUS's pace.
  //    HREADY here is a flop enable, not a term in HREADYOUT - that is the
  //    distinction the whole module turns on.
  wire ap_valid = HSEL && HTRANS[1];        // NONSEQ or SEQ
 
  logic            dp_sel, dp_write;
  logic [AW-1:0]   dp_addr;
 
  always_ff @(posedge HCLK or negedge HRESETn) begin
    if (!HRESETn) begin
      dp_sel <= 1'b0; dp_write <= 1'b0; dp_addr <= '0;
    end else if (HREADY) begin
      dp_sel <= ap_valid; dp_write <= ap_valid && HWRITE; dp_addr <= HADDR;
    end
  end
 
  // ── The wait counter. It is driven from the CAPTURED context only.
  //    Nothing here reads HREADY.
  logic [$clog2(WAITS+1)-1:0] cnt;
  wire  counting = dp_sel && (cnt != WAITS[$bits(cnt)-1:0]);
 
  always_ff @(posedge HCLK or negedge HRESETn)
    if (!HRESETn)      cnt <= '0;
    else if (!dp_sel)  cnt <= '0;            // no transfer in the data phase
    else if (counting) cnt <= cnt + 1'b1;
    else               cnt <= '0;            // completing: re-arm for the next
 
  // ── HREADYOUT.
  //    High when idle - a subordinate that is not providing the data phase must
  //    not stall the bus. Low only while a REAL selected transfer is still
  //    waiting. Note the absence of HREADY on the right-hand side.
  generate
    if (WAITS == 0) begin : g_zero_wait
      assign HREADYOUT = 1'b1;               // genuinely single-cycle: tie high
    end else begin : g_waited
      assign HREADYOUT = !counting;
    end
  endgenerate
 
  // The access is launched on the first data-phase cycle and runs while stalled.
  assign mem_start = dp_sel && (cnt == '0);
 
  // ── Outputs valid on the completing cycle.
  //    HRDATA need only be valid when HREADYOUT is high; driving the raw memory
  //    output the rest of the time is legal and keeps the mux small.
  assign HRDATA = mem_rdata;
  assign HRESP  = RSP_OKAY;   // see /protocols/ahb/error-response for faults
endmodule

Three properties of that module are the ones worth carrying to any subordinate.

HREADYOUT is high when idle. A subordinate that drives it low while another subordinate owns the data phase stalls the whole bus for a transfer it is not part of. !counting is high whenever dp_sel is low, which covers idle by construction.

Waits are qualified by dp_sel. Stalling on an unselected or IDLE cycle slows every other master on the bus for no reason.

The counter lives on captured context. dp_addr, dp_write and dp_sel are frozen while the transfer stalls, because their enable is HREADY and HREADY is low throughout. Losing that context mid-stall is the second-most-common subordinate bug, and the assertion below pins it down.

Checking it

ahb_wait_gen_sva.sv — protocol requirements and engineering policy, separated
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module ahb_wait_gen_sva #(
  // NOTE: this is a PROJECT POLICY bound, not an AMBA limit. AMBA places no
  // maximum on the number of wait states a subordinate may insert. The bound
  // exists so that a stuck HREADYOUT fails loudly in simulation instead of
  // hanging the run - see the note under the properties.
  parameter int MAX_WAIT_POLICY = 32
) (
  input logic HCLK, HRESETn,
  input logic HSEL, HREADY, HREADYOUT,
  input logic [1:0] HTRANS, HRESP,
  input logic dp_sel, dp_write,
  input logic [31:0] dp_addr
);
  // ---- AMBA protocol requirements -------------------------------------
  // The captured context must not move while the transfer is stalled. This is
  // the direct expression of "the data phase belongs to one transfer".
  a_context_stable: assert property (@(posedge HCLK) disable iff (!HRESETn)
    (dp_sel && !HREADYOUT) |=> ($stable(dp_addr) && $stable(dp_write) &&
                                $stable(dp_sel)))
    else $error("transfer context changed during a wait state");
 
  // A subordinate that is not providing the data phase must not stall the bus.
  a_idle_is_ready: assert property (@(posedge HCLK) disable iff (!HRESETn)
    !dp_sel |-> HREADYOUT)
    else $error("HREADYOUT low while no transfer is in the data phase");
 
  // ---- ENGINEERING POLICY, not protocol -------------------------------
  // AMBA does not bound wait-state count. A real subordinate does, and a
  // simulation that hangs tells you far less than one that fails on a cycle.
  a_bounded_wait_policy: assert property (@(posedge HCLK) disable iff (!HRESETn)
    (dp_sel && !HREADYOUT) |-> ##[1:MAX_WAIT_POLICY] HREADYOUT)
    else $error("HREADYOUT low for more than %0d cycles (policy bound)",
                MAX_WAIT_POLICY);
 
  // Coverage - an exactly-once check over a run with no waits proves nothing.
  c_waited:   cover property (@(posedge HCLK) dp_sel && !HREADYOUT);
  c_zerowait: cover property (@(posedge HCLK) dp_sel &&  HREADYOUT);
endmodule

The MAX_WAIT_POLICY property deserves its comment. AMBA places no upper bound on wait states, so this assertion encodes a project decision rather than a protocol rule. Writing it unbounded — |-> ##[1:$] HREADYOUT — would express the protocol faithfully and be useless in simulation: an unbounded eventuality cannot fail, so a permanently stuck HREADYOUT produces one incomplete attempt and zero failures while the run hangs. Bound it, label the bound as policy, and set it from the subordinate's own worst case.

1

A subordinate that simulated correctly and would not synthesise

HREADYOUT-COMBINATIONAL-LOOP
Symptom

A wait-inserting subordinate passed its block-level regression completely: every transfer completed, the wait counts were right, and the data was correct. It was integrated into the SoC and synthesis rejected the design with a combinational loop error naming a path that ran out of the subordinate, through the interconnect, and back into the subordinate.

Before synthesis flagged it, the SoC-level simulation had behaved oddly rather than incorrectly — some tests showed HREADY glitching within a cycle, and one simulator reported a NOCONV convergence warning that had been filtered out of the regression log months earlier.

Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The author wanted "stall only when a new transfer is actually being
// accepted", and reached for the natural expression of "being accepted".
wire accepting = HSEL && HTRANS[1] && HREADY;      // <-- HREADY here...
 
assign HREADYOUT = !(accepting && access_busy);    // <-- ...reaches HREADYOUT
Diagnostic Evidence

The synthesis error named the loop explicitly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Error: Combinational loop detected through the following path:
   u_slave/HREADYOUT -> u_interconnect/hready_mux -> u_slave/HREADY
   -> u_slave/accepting -> u_slave/HREADYOUT

The block-level testbench had hidden it for a structural reason. It drove HREADY as an independent input rather than tying it back from HREADYOUT, so the loop was open — the testbench supplied a value the interconnect would have derived. Every block-level test therefore exercised a circuit that does not exist in the SoC.

The NOCONV warning was the one piece of evidence that had been present all along. A simulator reporting a convergence failure on a combinational settle is reporting a loop; it had been filtered because it was noisy in an unrelated block.

Root Cause

The interconnect derives HREADY by selecting the addressed subordinate's HREADYOUT. A subordinate that computes HREADYOUT from HREADY therefore closes a zero-delay feedback path through the fabric.

The reason the code looked right is that the intent was right. "Only stall when a transfer is actually being accepted" is correct reasoning, and HREADY genuinely is the signal that says a transfer is being accepted. The error is in where that signal is allowed to be used: HREADY may feed sequential logic — the address-phase capture register uses it as a flop enable, which is both correct and required — and must never reach HREADYOUT through combinational logic.

The protocol makes the combinational term unnecessary anyway. While a subordinate is stalling, the manager is required to hold its address phase stable, so the unqualified HSEL && HTRANS[1] carries the same information for the whole duration of the stall. The HREADY term was buying nothing.

A block-level testbench that drives HREADY independently will never expose this, which makes it a class of bug that is structurally invisible until integration. Tying HREADY back from HREADYOUT in the block testbench — one line — closes the loop and reproduces the SoC topology.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Drop the HREADY term. The manager holds its address phase while stalled,
// so the unqualified form is stable and safe for the whole stall.
wire accepting = HSEL && HTRANS[1];
 
assign HREADYOUT = !(accepting && access_busy);
 
// HREADY keeps its correct role - a flop enable, never a combinational term:
always_ff @(posedge HCLK or negedge HRESETn)
  if (!HRESETn)      dp_sel <= 1'b0;
  else if (HREADY)   dp_sel <= accepting;

Verifying the fix has two parts, and the second is what keeps it fixed.

The direct check is structural: re-run synthesis or lint with combinational-loop detection enabled and confirm the path is gone. This is one of the few bugs where the tool's own check is the authoritative test.

The durable change is to the block-level testbench: tie HREADY back from HREADYOUT so the block is exercised in the topology it will actually sit in.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Single-subordinate interconnect model - reproduces the SoC feedback path.
wire HREADY = HREADYOUT;

That one line converts a bug that is invisible until SoC integration into one that shows up as a simulator convergence failure on the first block-level run.

Two habits generalise. Never filter convergence warningsNOCONV and its equivalents almost always indicate a real loop, and they are cheap to investigate. And write down which direction each signal is allowed to flow: HREADY into flops, HREADYOUT out of state. Stated that way, the rule is easy to check in review, which "don't create a combinational loop" is not.

For the two signals and their directions see HREADY vs HREADYOUT; for the capture discipline the flop enable belongs to, address / control capture.

8. Common Mistakes

9. Interview Insight

HREADYOUT generation is a sharp RTL interview topic — the high/low meaning, the zero-wait-vs-counter distinction, and especially the HREADYOUT-vs-HREADY separation are the signals.

A summary card on HREADYOUT generation: meaning, zero-wait vs counter/FSM, the HREADY distinction, and the timing rules.
Figure 4 — a strong answer in one card: HREADYOUT is the slave's output (high = can finish this cycle, low = insert a wait); zero-wait ties it high, a wait slave drives it from a counter/FSM modeling latency; don't confuse it with HREADY (the shared input = selected slave's HREADYOUT routed back to all); qualify waits to a real transfer, and never drive high before ready. The senior point: HREADYOUT high means 'I can finish this cycle', low inserts a wait, and HREADY is the shared bus version fed back to everyone.

The answer that lands gives the meaning and the distinction: "HREADYOUT is the slave's output that says, each cycle, whether it can complete the current transfer — high means I can finish this cycle, low means insert a wait state and hold the master. A zero-wait slave, like a simple register slave, just ties HREADYOUT high — it's always ready. A slave with latency, like a memory slave on a slow flash, drives HREADYOUT from a small counter or FSM that models the access latency: low while the access is in progress, high in the cycle it can genuinely complete. Two things are critical. First, you drive HREADYOUT high only when you're truly ready — if you drive it high before the data is ready, the master samples garbage, a data-corruption bug. And you must never get stuck low, because that hangs the whole bus. Second — and this is the one people get wrong — HREADYOUT is not HREADY. HREADYOUT is this slave's output, its own readiness. HREADY is the shared bus-ready input: the interconnect selects the currently-addressed slave's HREADYOUT and routes it back to every slave and the master. A slave drives HREADYOUT to control its own transfers, but consumes HREADY to know when the current bus transfer completes — and it gates its address-phase capture on HREADY, the bus's pace, not on its own HREADYOUT. If you mix them up and gate on HREADYOUT, you mis-time the slave's pipeline — a subtle bug that hides in a single-slave testbench and surfaces in a real multi-slave system. So: output your readiness as HREADYOUT, input the bus pace as HREADY." The high/low meaning, the zero-wait-vs-FSM, and the HREADYOUT/HREADY separation are the senior signals.

10. Practice Challenge

Build and reason from HREADYOUT generation.

  1. The meaning. State what HREADYOUT high and low mean, and how a zero-wait slave vs a wait-inserting slave drives it.
  2. HREADYOUT vs HREADY. Explain the difference (output vs input), how the interconnect forms HREADY, and who receives it.
  3. Read the waveform. From Figure 2, explain how the shared HREADY follows the addressed slave (zero-wait A, then wait-inserting B).
  4. Gate on HREADY. Explain why a slave gates its address-phase capture on HREADY, not its own HREADYOUT, and the bug otherwise.
  5. Timing failures. Explain the consequences of driving HREADYOUT high too early, stuck low, and too late.

11. Key Takeaways

  • HREADYOUT is the slave's per-cycle output stating whether it can complete the current transfer — high = finish now; low = insert a wait state (hold the master — chapter 6.2).
  • Zero-wait slaves tie HREADYOUT high; wait-inserting slaves drive it from a counter/FSM modeling the access latency (low while not ready, high when ready).
  • HREADYOUT (output) ≠ HREADY (input)HREADYOUT is this slave's own readiness; HREADY is the shared signal (the interconnect selects the addressed slave's HREADYOUT and broadcasts it to all slaves and the master).
  • Gate address-phase capture on HREADY, not HREADYOUT — the slave's pipeline must advance with the bus's pace; using HREADYOUT mis-times capture (a subtle multi-slave bug).
  • Drive HREADYOUT high only when genuinely ready (too early → garbage — chapter 16.3) and never stick low (hangs the bus — chapter 18.1); qualify waits to a real selected transfer.
  • It's the bus's flow-control signal — a local slave decision with system-wide consequence; robust systems add a timeout/watchdog against stuck-ready hangs.

11b. Where This Is Specified

  • Arm AMBA 5 AHB Protocol Specification (ARM IHI 0033). The definition of HREADYOUT as a subordinate output and HREADY as the interconnect-combined input broadcast to all subordinates and the manager; the requirement that a manager holds its address phase stable while HREADY is low; and the absence of any maximum wait-state count.
  • Arm AMBA 3 AHB-Lite Protocol Specification (ARM IHI 0033A). Slave-inserted wait states and the requirement that a subordinate not providing the data phase drives HREADYOUT high so it does not stall the bus.
  • IEEE 1800-2023 §16 — Assertions. The concurrent properties and cover property used above, and the ##[m:n] bounded range that makes the policy wait bound falsifiable in simulation.
  • IEEE 1800-2023 §27 — Generate constructs. The generate used to select between the zero-wait tie-high and the counter-driven form.

12. What Comes Next

You now can generate HREADYOUT correctly. The next chapters complete the slave's output paths:

  • HRDATA Muxing (next) — build the read-data return path and the slave read mux.
  • HRESP Generation, Address / Control Capture, and the FSMs — the rest of the slave RTL.

To revisit the wait-state mechanism HREADYOUT implements, see Slave-Inserted Wait States and HREADY vs HREADYOUT; for the slave this paces, see A Simple AHB-Lite Slave.