Skip to content

AMBA AHB · Module 16

Bridge FSM (RTL)

The capstone slave control-FSM pattern in RTL — a bridge-style slave where one upstream AHB transfer expands into a multi-cycle downstream transaction, so it needs a real control state machine (IDLE to SETUP to ACCESS to COMPLETE) rather than the collapsed combinational logic of a simple slave. The FSM holds the upstream master with wait states (HREADYOUT low) while it runs the downstream sequence, then completes only on genuine downstream completion. It reuses every earlier building block: address/control capture, HREADYOUT generation, write/read commit gated to completion, and HRESP mapping.

Every slave so far has been single-resource: a simple slave (16.1), a register bank (16.2), a memory (16.3). Their control collapsed to combinational logic plus a capture — no real state machine. This chapter builds the capstone: a slave whose control cannot collapse, because one upstream transfer expands into a multi-cycle downstream transaction — the bridge-style slave, which needs a real control FSM. The canonical example is an AHB-to-APB bridge (an AHB slave on its upstream side that drives an APB peripheral bus downstream), and we'll use it as the vehicle — but the focus here is the RTL control-FSM pattern, not the protocol details of APB. (The full AHB-to-APB bridge — APB's two-phase SETUP/ACCESS protocol, PSEL/PENABLE/PREADY, multi-peripheral fan-out — is a separate subject; this chapter is the reusable control-FSM skeleton.) The pattern, generically: IDLE → on a captured real transfer, SETUP (drive the downstream command — address/control/write-data) → ACCESS (the downstream transaction executes, one or more cycles) → COMPLETE (capture the downstream result, drive HREADYOUT high for one cycle). Throughout SETUP/ACCESS, the FSM holds the upstream master (HREADYOUT low — wait states), because the upstream transfer can't finish until the downstream one does. The key insight: this FSM reuses every block from 16.4–16.9 (capture, HREADYOUT, write/read commit, HRESP), sequenced into a multi-cycle controller. This chapter builds that capstone control FSM.

1. What Is It?

The bridge FSM is a slave control state machine that maps one upstream transfer onto a multi-cycle downstream transaction, holding the upstream side until it completes. Its parts:

  • Why a real FSM — a simple slave collapses to combinational logic; a bridge can't, because one upstream transfer becomes a multi-cycle downstream sequence needing sequencing.
  • IDLE → SETUP → ACCESS → COMPLETE — capture a real transfer, drive the downstream command, run the downstream transaction (multi-cycle), then complete the upstream transfer.
  • Hold the upstream side — throughout SETUP/ACCESS, drive HREADYOUT low (wait states), since the upstream transfer can't finish until downstream does.
  • Reuses every block — capture (16.7), HREADYOUT generation (16.4), write/read commit gated to completion (16.8/16.9), HRESP mapping (16.6).
A bridge control FSM: IDLE → SETUP → ACCESS (multi-cycle) → COMPLETE, holding the upstream side with wait states throughout.
Figure 1 — a bridge-style control FSM. From IDLE (HREADYOUT=1), a captured real transfer moves to SETUP (drive the downstream address/control/write-data). Then ACCESS, where the downstream transaction executes — possibly over several cycles (the self-loop while the downstream side is busy). Throughout SETUP and ACCESS, the bridge holds the upstream side with HREADYOUT low (wait states), because the upstream transfer can't complete until downstream finishes. When the downstream transaction is done, COMPLETE captures the read data/response and drives HREADYOUT high for one cycle, completing the upstream transfer, then returns to IDLE. The same capture → command → wait → complete skeleton fronts any slow/multi-cycle downstream resource.

So the bridge FSM is the rate-matching controller between a fast upstream bus and a slow/multi-cycle downstream resource. The simple slaves (16.1–16.3) responded in one cycle (or a few waits) because their resource (registers, SRAM) was directly accessible — their "FSM" collapsed to a capture plus combinational logic. A bridge is different: its resource is another bus transaction (the downstream APB access), which itself takes multiple cycles (a SETUP phase, an ACCESS phase, a wait for the peripheral). So the bridge cannot respond in one cycle — it must run the downstream transaction to completion, holding the upstream master (wait states) the whole time, then complete. That multi-cycle sequencing is what requires a real FSM — IDLE → SETUP → ACCESS → COMPLETE. And crucially, that FSM is built from the blocks you already have: it captures the upstream transfer (16.7), generates HREADYOUT to hold/release (16.4), commits writes / produces reads on completion (16.8/16.9), and maps the downstream response to HRESP (16.6). The bridge FSM is therefore the assembly of the slave building blocks into a multi-cycle controller.

2. Why Does It Exist?

The bridge FSM exists because some slaves front a multi-cycle downstream resource (another bus, a slow peripheral) — so a single upstream transfer expands into a multi-cycle sequence that must be sequenced by a real state machine, holding the upstream master throughout.

The multi-cycle downstream resource is the root: a bridge doesn't hold the data itself (like a register or SRAM) — it forwards the access to a downstream resource (an APB peripheral bus, a slow external interface). That downstream access is itself a multi-cycle transaction — e.g. APB's two-phase SETUP/ACCESS, plus wait cycles for the peripheral. So a single upstream AHB transfer triggers a multi-cycle downstream sequence. So the bridge cannot respond in one cycle: it must drive and track the downstream transaction across cycles, which is what requires states.

The the upstream must be held drives the wait states: since the upstream transfer can't complete until the downstream one does, the bridge must hold the upstream master — insert wait states (HREADYOUT low) — for the entire duration of the downstream sequence. So the FSM's states (SETUP, ACCESS) each drive HREADYOUT low (holding), and only COMPLETE drives it high (releasing). The FSM's job is therefore to manage the holding — coordinating the upstream HREADYOUT with downstream progress.

The assembly of the building blocks is the deeper why: the bridge is where the individual slave outputs (16.4–16.9) must work together across multiple cycles — capture the transfer (16.7), hold/release via HREADYOUT (16.4), commit the write / produce the read on completion (16.8/16.9), map the response (16.6) — all sequenced. A simple slave does these in one cycle (collapsed); a bridge does them across the multi-cycle sequence (an FSM). So the bridge FSM is the integration point that assembles the blocks into a coherent multi-cycle controller. In summary, it exists because: some slaves front a multi-cycle downstream resource (a single upstream transfer expands into a multi-cycle sequence — the why); the upstream master must be held throughout (wait states across SETUP/ACCESS — the rate-matching); and the building blocks must be sequenced together across cycles (the assembly — capture, HREADYOUT, commit, HRESP). The bridge FSM is the multi-cycle slave controller that maps a fast upstream transfer onto a slow downstream transaction, integrating every slave block into a real state machine.

3. Mental Model

Model the bridge FSM as a hotel concierge taking your one request, then carrying out a multi-step errand on your behalf while you wait at the desk. You ask for theater tickets (one upstream transfer). The concierge can't hand them over instantly — they have to call the box office (SETUP), wait while the box office checks availability and processes it (ACCESS, several minutes), and only then hand you the tickets (COMPLETE). Throughout, they keep you politely waiting at the desk ("one moment, please" — HREADYOUT low), and only say "here you are" once the whole errand is done.

A hotel concierge desk (the bridge) between you, the guest (the upstream master) and the outside world (the downstream resource). You make one request — "theater tickets, please" (a single upstream transfer). A simple clerk handling something on-hand (like a room key — a register/SRAM) would hand it over instantly. But the concierge's request isn't on-hand — it requires a multi-step errand with an outside party (the box office — a downstream bus transaction). So the concierge can't respond in one beat. Instead: first they pick up the phone and place the order"box office, I need two tickets for tonight" (SETUP — driving the downstream command: which show, how many). Then they wait on the line while the box office checks availability and processes the payment — which takes several minutes (ACCESS — the downstream transaction executing, multi-cycle; sometimes the box office says "hold please" — downstream wait states, the self-loop). Throughout all of this, the concierge keeps you politely waiting at the desk"one moment, please, I'm on it"not dismissing you, not saying "done" yet (holding the upstream master with HREADYOUT low — wait states). Only when the box office confirms and the tickets are secured does the concierge turn to you and say "here you are" — handing over the tickets (COMPLETE — capturing the downstream result, driving HREADYOUT high for one beat to release you). And if the box office says "sold out" (a downstream error), the concierge relays "I'm sorry, that's not available" (mapping the downstream response to HRESP = ERROR). So the concierge takes one request, runs a multi-step errand with the outside party, holds you waiting the whole time, and completes only when the errand is donerelaying the outcome.

This captures the bridge FSM: your single request = one upstream AHB transfer; the concierge = the bridge control FSM; the on-hand clerk = a simple slave (collapsed, instant); the multi-step errand with the box office = the multi-cycle downstream transaction; placing the order on the phone = SETUP (driving the downstream command); waiting on the line while it processes = ACCESS (the downstream transaction executing, multi-cycle); "hold please" from the box office = downstream wait states (the ACCESS self-loop); keeping you waiting at the desk = holding the upstream master (HREADYOUT low) throughout; "here you are" once done = COMPLETE (HREADYOUT high for one cycle, releasing); relaying "sold out" = mapping the downstream response to HRESP. Take one request, run the multi-step errand, hold the guest waiting, complete and relay only when done.

Watch one upstream transfer span the multi-cycle downstream sequence, holding the master:

One upstream transfer spans the downstream sequence

4 cycles
Cycle 0 IDLE captures the upstream transfer. Cycle 1 SETUP drives the downstream command, HREADYOUT low. Cycle 2 ACCESS runs the downstream transaction, HREADYOUT low. Cycle 3 COMPLETE finishes the downstream side and drives HREADYOUT high for one cycle, completing the upstream transfer. HREADYOUT is low through SETUP and ACCESS, high only on COMPLETE.IDLE: capture the upstream transferIDLE: capture the upstreamtransferSETUP/ACCESS: run downstream, hold master (HREADYOUT=0)SETUP/ACCESS: rundownstream, hold master(HREADYOUT=0)COMPLETE: downstream done → HREADYOUT=1, upstream completesCOMPLETE: downstream done →HREADYOUT=1, upstreamcompletesHCLKstateIDLESETUPACCESSDONEdownstream cmd0driveexecresultHREADYOUTupstream xferstartheldhelddonet0t1t2t3
Figure 2 — one upstream transfer spans the multi-cycle downstream sequence; the bridge holds the master throughout. Cycle 0: IDLE → the bridge captures a real upstream transfer (addr, write/read). Cycle 1: SETUP — drive the downstream command (downstream address/control); HREADYOUT=0 (hold the upstream master). Cycle 2: ACCESS — the downstream transaction executes (the peripheral responds); still HREADYOUT=0 (the downstream side isn't done). Cycle 3: COMPLETE — the downstream transaction finishes; the bridge captures the result and drives HREADYOUT=1 for one cycle, completing the upstream transfer. The 'state' row walks IDLE→SETUP→ACCESS→COMPLETE; HREADYOUT is low through SETUP/ACCESS and high only on COMPLETE. The single upstream transfer is stretched across the whole downstream sequence by the wait states.

The model's lesson: take one request, run the multi-step errand, hold the guest waiting, and complete only when done. In the waveform, the single upstream transfer is stretched across the downstream sequence — HREADYOUT low through SETUP/ACCESS (holding), high only on COMPLETE (releasing).

4. Real Hardware Perspective

In hardware, the bridge is a state register plus next-state/output logic; SETUP/ACCESS drive the downstream interface and hold HREADYOUT low; COMPLETE captures the downstream result and pulses HREADYOUT high; and the whole thing reuses the capture/commit/HRESP logic across the states.

The state register and transitions: the FSM is a state register (state_q ∈ {IDLE, SETUP, ACCESS, COMPLETE}) with next-state logic: IDLE → SETUP on a captured real transfer; SETUP → ACCESS after driving the command; ACCESS → ACCESS while the downstream side is busy (e.g. downstream PREADY low), ACCESS → COMPLETE when the downstream transaction finishes; COMPLETE → IDLE (or → SETUP for a pipelined next transfer). In hardware this is an ordinary Moore/Mealy FSM.

One upstream transfer mapping to a multi-cycle downstream SETUP/ACCESS/COMPLETE sequence, with the upstream held by wait states throughout.
Figure 3 — one upstream transfer expands into a multi-cycle downstream transaction. The upstream (fast AHB) side issues one transfer, stretched by wait states. The bridge FSM maps it to a downstream sequence: SETUP (drive address/control), ACCESS (the peripheral responds, possibly multi-cycle), COMPLETE (read data/response back). Through SETUP and ACCESS, the upstream is held — HREADYOUT=0 (wait states) — because the single upstream transfer can't complete until the downstream transaction finishes; on COMPLETE, HREADYOUT=1. This rate-matching across a fast/slow boundary is why a bridge needs a real FSM, not collapsed combinational logic.

The HREADYOUT held across the states: the HREADYOUT output is a function of the statelow in SETUP and ACCESS (holding the upstream master — the downstream isn't done), high in COMPLETE (releasing — the transfer finishes), high in IDLE (ready for the next). This is exactly the HREADYOUT generation of 16.4 — driven by the FSM state rather than a simple counter. HREADYOUT is therefore the FSM's hold/release output, and the pace is FSM-driven rather than counter-driven.

The reused blocks sequenced across states: the other slave blocks appear within the states. The capture (16.7) happens at IDLE → SETUP (capturing the upstream addr_q/write_q to drive downstream). The write commit (16.8) — here, driving the downstream write data — happens in SETUP/ACCESS (the downstream write executes); the upstream "commit" is the downstream completing. The read production (16.9) — capturing the downstream read data and presenting it on the upstream HRDATA — happens at COMPLETE. The HRESP mapping (16.6) — translating the downstream response (e.g. APB PSLVERR) to the upstream HRESP (OKAY/ERROR, two-cycle) — happens at COMPLETE. So in hardware, the bridge is the slave blocks (16.4–16.9) instantiated and sequenced by the FSM across SETUP/ACCESS/COMPLETE. The FSM is the conductor; the blocks are the players. A bridge is therefore a state machine driving a downstream interface, reusing the capture, HREADYOUT, commit and HRESP logic across its states.

5. System Architecture Perspective

At the system level, the bridge is the rate-matching gateway between a fast high-performance bus and a slow peripheral domain — and its FSM is where the fast/slow boundary is managed, making it both a necessary SoC component and the clearest demonstration that the slave building blocks compose.

The fast/slow gateway: SoCs have a fast bus (AHB — for the CPU, memory, DMA) and a slow peripheral bus (APB — for UARTs, timers, GPIO). The bridge is the gateway between them — it lets the fast bus reach the slow peripherals without slowing the fast bus for everyone (only the bridged accesses pay the slow-peripheral cost). The bridge is therefore the fast-to-slow gateway — a standard SoC component that every microcontroller has.

The boundary management: the bridge's FSM is where the rate mismatch is absorbed. The fast bus expects its protocol (AHB pipelining); the slow bus has its protocol (APB phases); the bridge FSM translates — accepting the fast transfer, holding it (wait states) while running the slow transaction, then completing. So the boundary between the fast and slow domains is managed by the bridge FSM — it's the translation/rate-matching point. The boundary between the fast and slow domains therefore lives in the bridge FSM.

The composition demonstration: the bridge is the proof that the slave building blocks (16.4–16.9) compose into arbitrary slave complexity. A simple slave uses them collapsed (one cycle); a bridge uses them sequenced (multi-cycle FSM); a more complex slave (a cache controller, a DMA-fronting slave) uses them in a richer FSM. The bridge therefore demonstrates the generality of the building-block approach: the same blocks, assembled by an FSM of any complexity. At the system level, the bridge is the rate-matching gateway between fast and slow bus domains (a standard SoC component — the peripheral gateway), its FSM is the domain-boundary manager (absorbing the fast/slow rate mismatch — translating and holding), and it's the clearest demonstration that the slave building blocks compose (the same blocks, sequenced by an FSM, scale to arbitrary slave complexity). The bridge is where the slave RTL culminates: a real multi-cycle controller built entirely from the foundational blocks, managing the most important boundary in the SoC's bus hierarchy. (The detailed bridge architecture — APB protocol, peripheral fan-out, setup/access timing — is a separate subject; here it's the RTL control-FSM capstone.)

6. Engineering Tradeoffs

The bridge FSM embodies the multi-cycle, hold-the-upstream, compose-the-blocks design.

  • Real FSM vs collapsed logic. A bridge needs a real FSM (multi-cycle downstream sequencing); a simple slave collapses to combinational logic. Use an FSM only when the slave is genuinely multi-cycle (a bridge, a cache, a complex peripheral).
  • Hold with wait states vs buffer/post. Holding the upstream master (wait states) through the downstream access is simplest and correct for a basic bridge; a posted-write buffer (accept the upstream write, complete it immediately, drain to downstream later) improves upstream throughput at the cost of buffering/ordering complexity. Start with holding; add posting if throughput demands.
  • Reuse blocks vs bespoke logic. Reusing the capture/HREADYOUT/commit/HRESP blocks (16.4–16.9) sequenced by the FSM is clean and verified; bespoke per-bridge logic risks re-introducing the subtle bugs. Compose from the verified blocks.
  • Minimal states vs explicit per-phase states. A minimal FSM (few states) is compact; explicit per-phase states (one per downstream phase) are clearer and easier to verify. Favor clarity for a capstone block.

The throughline: a bridge FSM is the capstone slave controller — a real state machine (IDLE → SETUP → ACCESS → COMPLETE) that maps one upstream transfer onto a multi-cycle downstream transaction, because the bridge fronts a slow/multi-cycle resource (another bus) rather than holding data directly. Throughout SETUP/ACCESS, it holds the upstream master with wait states (HREADYOUT low — chapter 6.2), since the upstream transfer can't finish until the downstream one does; COMPLETE releases it (HREADYOUT high, one cycle) and maps the downstream result to the upstream HRDATA/HRESP. It reuses every block — capture (16.7), HREADYOUT (16.4), write/read commit gated to completion (16.8/16.9), HRESP (16.6) — sequenced by the FSM. It's the fast/slow gateway of the SoC and the proof that the slave blocks compose into arbitrary complexity. (The full bridge protocol is a separate subject.)

7. Industry Example

Trace a CPU write and read through a bridge to a slow peripheral — the rate-matching in action.

A CPU on AHB accesses a UART on a downstream (APB-style) peripheral bus through a bridge.

  • The write (CPU → UART config register). The CPU issues one AHB write to the UART's config address. The bridge: IDLE → SETUP captures the upstream transfer and drives the downstream command (the peripheral address, control, and the write data); HREADYOUT low (hold the CPU). SETUP → ACCESS: the downstream write executes — the peripheral bus asserts its enable and the UART accepts the write, possibly over several cycles (downstream wait states); HREADYOUT stays low. ACCESS → COMPLETE: the downstream write is accepted (downstream PREADY); the bridge drives HREADYOUT high for one cycle — the CPU's write completes. The CPU was held for the whole downstream sequence.
  • The read (CPU ← UART status register). The CPU issues one AHB read. IDLE → SETUP: capture, drive the downstream read command; HREADYOUT low. SETUP → ACCESS: the downstream read executes — the UART produces its status; HREADYOUT low until the data is ready. ACCESS → COMPLETE: the downstream read data is valid (downstream PREADY); the bridge captures it and presents it on the upstream HRDATA, driving HREADYOUT high — the CPU samples the status. Critically, the bridge drives HREADYOUT high only when the downstream read data is genuinely ready — not before (which would return garbage).
  • A downstream error. If the UART signals a downstream error (e.g. APB PSLVERR), the bridge maps it to the upstream HRESP = ERROR (the two-cycle ERROR — 16.6) at COMPLETE. The CPU takes a bus fault.
  • The hold. Throughout both transfers, the CPU is held (HREADYOUT low) for the entire downstream sequence — the bridge rate-matches the fast CPU to the slow UART. Only the bridged accesses pay the cost; the rest of the AHB runs full-speed.
  • The reused blocks. The bridge captured (16.7), generated HREADYOUT (16.4) from its state, committed the (downstream) write / presented the (downstream) read on completion (16.8/16.9), and mapped HRESP (16.6) — all sequenced by the FSM.

The example shows the bridge FSM rate-matching a fast CPU to a slow peripheral: one upstream transfer stretched across the multi-cycle downstream sequence, the CPU held throughout, completion only on genuine downstream completion (no early release), and the reused slave blocks sequenced by the FSM. (The APB-side protocol detail is developed in the RTL below.)

7b. The Bridge, Written Out

The description above is complete enough to implement directly. What it leaves implicit is the one interaction that makes a bridge harder than it looks: the AHB ERROR response takes two cycles, so "COMPLETE drives HREADYOUT high for one cycle" is true for OKAY and wrong for ERROR. The FSM below separates them.

ahb_apb_bridge.sv — AHB-Lite subordinate front, APB manager back
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module ahb_apb_bridge #(
  parameter int AW = 32,
  parameter int DW = 32
) (
  input  logic            HCLK,
  input  logic            HRESETn,
 
  // ---------------- AHB-Lite subordinate (upstream) ----------------
  input  logic            HSEL,
  input  logic [1:0]      HTRANS,
  input  logic [AW-1:0]   HADDR,
  input  logic            HWRITE,
  input  logic [DW-1:0]   HWDATA,
  input  logic            HREADY,       // GLOBAL ready from the interconnect
  output logic            HREADYOUT,
  output logic [DW-1:0]   HRDATA,
  output logic [1:0]      HRESP,
 
  // ---------------- APB manager (downstream) -----------------------
  output logic            PSEL,
  output logic            PENABLE,
  output logic            PWRITE,
  output logic [AW-1:0]   PADDR,
  output logic [DW-1:0]   PWDATA,
  input  logic [DW-1:0]   PRDATA,
  input  logic            PREADY,
  input  logic            PSLVERR
);
  localparam logic [1:0] HTRANS_IDLE = 2'b00;
  localparam logic [1:0] RSP_OKAY = 2'b00, RSP_ERROR = 2'b01;
 
  // ── Address-phase capture, gated on the GLOBAL HREADY.
  //    HWDATA is NOT captured here: in AHB it arrives one cycle later, during
  //    the data phase, so it is sampled separately below.
  logic          sel_q, write_q;
  logic [AW-1:0] addr_q;
  wire           active = HSEL && HTRANS[1];   // NONSEQ or SEQ
 
  always_ff @(posedge HCLK or negedge HRESETn) begin
    if (!HRESETn) begin
      sel_q <= 1'b0; write_q <= 1'b0; addr_q <= '0;
    end else if (HREADY && HREADYOUT) begin
      // Only accept a new address phase when THIS slave is also releasing.
      // While the bridge is stretching, the master holds its address phase,
      // so re-capturing would be harmless but re-arming the FSM would not.
      sel_q <= active; write_q <= HWRITE; addr_q <= HADDR;
    end
  end
 
  // ── The FSM. B_ERR1/B_ERR2 exist solely because AHB's ERROR response is a
  //    two-cycle sequence; the OKAY path completes straight out of B_ACCESS.
  typedef enum logic [2:0] {
    B_IDLE, B_SETUP, B_ACCESS, B_ERR1, B_ERR2
  } state_e;
  state_e state, next;
 
  // APB write data is the AHB write data of the cycle after the address phase.
  logic [DW-1:0] wdata_q, prdata_q;
  always_ff @(posedge HCLK) begin
    if (state == B_IDLE && sel_q && write_q) wdata_q  <= HWDATA;
    if (state == B_ACCESS && PREADY)         prdata_q <= PRDATA;
  end
 
  always_comb begin
    next = state;
    unique case (state)
      // A captured transfer starts the downstream access.
      B_IDLE  : if (sel_q) next = B_SETUP;
      // APB SETUP is exactly one cycle: PSEL high, PENABLE low.
      B_SETUP : next = B_ACCESS;
      // APB ACCESS holds PSEL and PENABLE until PREADY. The transfer occurs
      // on the cycle where PSEL && PENABLE && PREADY are all true, and
      // PSLVERR is only meaningful in that same cycle.
      B_ACCESS: if (PREADY) next = PSLVERR ? B_ERR1
                                           : (sel_q ? B_SETUP : B_IDLE);
      B_ERR1  : next = B_ERR2;
      B_ERR2  : next = sel_q ? B_SETUP : B_IDLE;
    endcase
  end
 
  always_ff @(posedge HCLK or negedge HRESETn)
    if (!HRESETn) state <= B_IDLE; else state <= next;
 
  // ── APB outputs.
  //    PENABLE must be low for the whole SETUP cycle and high through ACCESS.
  //    Holding it high into a back-to-back transfer is an APB violation, which
  //    is why B_ACCESS returns through B_SETUP rather than looping directly.
  assign PSEL    = (state == B_SETUP) || (state == B_ACCESS);
  assign PENABLE = (state == B_ACCESS);
  assign PWRITE  = write_q;
  assign PADDR   = addr_q;
  assign PWDATA  = wdata_q;
 
  // ── AHB outputs.
  //    HREADYOUT is high in IDLE (nothing in flight) and on the completing
  //    cycle of an OKAY transfer. It is LOW during the first ERROR cycle and
  //    high on the second - that is the two-cycle ERROR sequence.
  assign HREADYOUT = (state == B_IDLE)
                  || (state == B_ACCESS && PREADY && !PSLVERR)
                  || (state == B_ERR2);
 
  assign HRESP  = (state == B_ERR1 || state == B_ERR2) ? RSP_ERROR : RSP_OKAY;
 
  // Read data is presented on the completing cycle, taken combinationally from
  // PRDATA (which APB guarantees valid when PREADY is high). prdata_q exists
  // for designs that prefer to register it and add a completion cycle.
  assign HRDATA = PRDATA;
endmodule

Three points in that file are the ones a prose description leaves out.

HWDATA is not captured with the address. AHB's write data arrives one cycle after its address phase, so a bridge that latches HWDATA alongside HADDR captures the previous transfer's data. The capture block takes the address phase; a separate register takes the data phase.

PENABLE must drop between transfers. APB defines the access phase as one PENABLE-high region terminated by PREADY, so back-to-back transfers go ACCESS → SETUP → ACCESS, not ACCESS → ACCESS. The FSM routes through B_SETUP for exactly this reason; looping in B_ACCESS would save a cycle and violate the protocol.

ERROR costs a state pair. On PSLVERR the bridge cannot simply raise HREADYOUT with HRESP = ERROR. AHB requires ERROR to span two cycles — HREADYOUT low with HRESP asserted, then HREADYOUT high with HRESP still asserted — so the master gets a cycle in which to cancel the address phase it has already issued. B_ERR1 and B_ERR2 are that sequence.

Checking it

ahb_apb_bridge_sva.sv — the protocol rules on both sides
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module ahb_apb_bridge_sva (
  input logic HCLK, HRESETn,
  input logic HREADYOUT, input logic [1:0] HRESP,
  input logic PSEL, PENABLE, PREADY, PSLVERR, PWRITE,
  input logic [31:0] PADDR, PWDATA
);
  localparam logic [1:0] RSP_ERROR = 2'b01;
 
  // --- APB side ---
  // SETUP lasts exactly one cycle: PSEL without PENABLE is always followed by
  // PSEL with PENABLE.
  a_setup_one_cycle: assert property (@(posedge HCLK) disable iff (!HRESETn)
    (PSEL && !PENABLE) |=> (PSEL && PENABLE));
 
  // Once in ACCESS, PSEL/PENABLE hold until PREADY. No aborting.
  a_access_holds: assert property (@(posedge HCLK) disable iff (!HRESETn)
    (PSEL && PENABLE && !PREADY) |=> (PSEL && PENABLE));
 
  // Address and write data are stable for the whole transfer.
  a_apb_stable: assert property (@(posedge HCLK) disable iff (!HRESETn)
    (PSEL && !(PENABLE && PREADY)) |=>
      $stable(PADDR) && $stable(PWRITE) && $stable(PWDATA));
 
  // PENABLE must drop after a completed access - no ACCESS-to-ACCESS.
  a_penable_drops: assert property (@(posedge HCLK) disable iff (!HRESETn)
    (PSEL && PENABLE && PREADY) |=> !PENABLE);
 
  // --- AHB side ---
  // The two-cycle ERROR: the first ERROR cycle has HREADYOUT low, and the
  // next cycle must still assert ERROR, now with HREADYOUT high.
  a_error_two_cycle: assert property (@(posedge HCLK) disable iff (!HRESETn)
    (HRESP == RSP_ERROR && !HREADYOUT) |=> (HRESP == RSP_ERROR && HREADYOUT));
 
  // An ERROR never appears as a single completing cycle out of nowhere.
  a_error_not_one_cycle: assert property (@(posedge HCLK) disable iff (!HRESETn)
    (HRESP == RSP_ERROR && HREADYOUT) |->
      $past(HRESP == RSP_ERROR && !HREADYOUT));
 
  // Coverage: without these, a green run cannot distinguish "error path
  // correct" from "error path never taken".
  c_error_seen: cover property (@(posedge HCLK) disable iff (!HRESETn)
    PSEL && PENABLE && PREADY && PSLVERR);
  c_wait_seen:  cover property (@(posedge HCLK) disable iff (!HRESETn)
    PSEL && PENABLE && !PREADY);
endmodule
1

A bridge reported peripheral errors that the CPU never took a fault on

ONE-CYCLE-ERROR
Symptom

Accesses to an unmapped peripheral register behaved inconsistently. The APB peripheral correctly asserted PSLVERR, the bridge correctly drove HRESP = ERROR, and the CPU sometimes took a bus fault and sometimes did not — with the outcome depending on what instruction followed the faulting access. A faulting load followed by an unrelated nop faulted; the same load followed by another load to a different peripheral often did not, and instead corrupted the second load's result.

Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The error path completed in a single cycle, like the OKAY path.
assign HREADYOUT = (state == B_IDLE)
                || (state == B_ACCESS && PREADY);          // <-- error included
 
assign HRESP = (state == B_ACCESS && PREADY && PSLVERR) ? RSP_ERROR : RSP_OKAY;
Diagnostic Evidence

A waveform of a faulting access showed HRESP asserted for exactly one cycle, in the same cycle HREADYOUT went high. The AHB master's own trace showed it had already driven the next transfer's address phase during that cycle — which is normal, because AHB is pipelined and the address phase of transfer N+1 overlaps the data phase of transfer N.

That overlap is the entire mechanism. The master issues the next address phase speculatively; the ERROR response is its signal to cancel it. Delivering the ERROR and the completion in the same cycle gives the master no cycle in which to act, so whether it cancels depends on internal implementation details — which is exactly the instruction-dependent behaviour that was observed.

Root Cause

AHB defines the ERROR response as a two-cycle sequence: HRESP = ERROR with HREADYOUT low, then HRESP = ERROR with HREADYOUT high. The first cycle is not a formality — it exists so the master can see the error while its next address phase is still uncommitted and drive HTRANS to IDLE to cancel it. The bridge collapsed the two cycles into one, so the error and the completion arrived together and the master's speculatively-issued next transfer proceeded anyway.

The reason this survived block-level testing is the usual one for pipeline bugs: a testbench that issues one transfer at a time, waiting for each to complete before starting the next, never has a speculative address phase in flight. There is nothing to cancel, so a one-cycle ERROR and a two-cycle ERROR are indistinguishable. Only back-to-back traffic separates them, and only when the first of the pair errors.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Split the error completion into the two cycles the protocol requires.
B_ACCESS: if (PREADY) next = PSLVERR ? B_ERR1 : (sel_q ? B_SETUP : B_IDLE);
B_ERR1  : next = B_ERR2;
B_ERR2  : next = sel_q ? B_SETUP : B_IDLE;
 
assign HREADYOUT = (state == B_IDLE)
                || (state == B_ACCESS && PREADY && !PSLVERR)  // OKAY: 1 cycle
                || (state == B_ERR2);                          // ERROR: 2nd cycle
 
assign HRESP = (state == B_ERR1 || state == B_ERR2) ? RSP_ERROR : RSP_OKAY;

The test that separates the two is a back-to-back pair in which the first access errors: issue a faulting transfer immediately followed by a second transfer to a different address, and check that the second one does not complete. Against the old RTL the second transfer proceeds and returns data; against the new RTL the master sees the error with a cycle to spare and cancels it.

The assertion is the durable form, because it holds regardless of what the stimulus happens to do:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a_error_two_cycle: assert property (@(posedge HCLK) disable iff (!HRESETn)
  (HRESP == RSP_ERROR && !HREADYOUT) |=> (HRESP == RSP_ERROR && HREADYOUT));
a_error_not_one_cycle: assert property (@(posedge HCLK) disable iff (!HRESETn)
  (HRESP == RSP_ERROR && HREADYOUT) |-> $past(HRESP == RSP_ERROR && !HREADYOUT));

Pair them with c_error_seen from the checker above. A two-cycle-ERROR assertion in a regression that never generates an error is vacuously true, and a bridge whose error path has never been exercised is a bridge whose error path is unverified — see error response and the two-cycle ERROR response for the response rules in full.

8. Common Mistakes

9. Interview Insight

The bridge FSM is a senior RTL interview topic — the multi-cycle-needs-an-FSM insight, the hold-the-upstream discipline, and the reuses-every-block framing are the signals.

A summary card on the bridge FSM: multi-cycle expansion, the IDLE/SETUP/ACCESS/COMPLETE skeleton, holding the upstream, and reusing the blocks.
Figure 4 — a strong answer in one card: one upstream transfer expands into a multi-cycle downstream transaction, so a bridge needs a real FSM (not collapsed logic); the skeleton is IDLE → SETUP (drive downstream addr/ctrl/wdata) → ACCESS (run it) → COMPLETE; hold the upstream master (HREADYOUT=0) through SETUP/ACCESS, HREADYOUT=1 one cycle to complete; it reuses every block — capture, HREADYOUT generation, write/read commit gated to completion, HRESP mapping. The senior point: a bridge is a rate-matching control FSM — hold upstream with waits, run the downstream transaction, then complete.

The answer that lands gives the multi-cycle insight and the hold discipline: "A bridge is the most complex slave because, unlike a simple slave or a memory that holds the data directly, a bridge fronts a downstream resource — another bus, like APB — and one upstream transfer expands into a multi-cycle downstream transaction. So it can't collapse to combinational logic; it needs a real control FSM. The skeleton is: from idle, capture a real upstream transfer and go to a setup state that drives the downstream command — the address, control, and for a write the write data. Then an access state where the downstream transaction actually executes, which takes one or more cycles. Then a complete state that captures the downstream read data or response and finishes the upstream transfer. The critical discipline is that throughout setup and access, you hold the upstream master by driving HREADYOUT low — inserting wait states — because the single upstream transfer can't complete until the downstream transaction finishes. You only drive HREADYOUT high, completing the upstream transfer, when the downstream side genuinely completes. Releasing early is the classic bug: for a read, you'd return garbage because the downstream data isn't back yet; for a write, you'd break ordering by telling the master it's done before downstream accepted it. And the elegant part is that the bridge reuses every building block — the address capture, the HREADYOUT generation, the write and read commit gated to completion, the HRESP mapping from the downstream response — all sequenced by the FSM. The FSM is the conductor; the blocks are the players. So a bridge is really a rate-matching control FSM between a fast and a slow bus." The multi-cycle-needs-an-FSM insight, the hold-the-upstream discipline (and the early-release bug), and the reuses-every-block framing are the senior signals.

10. Practice Challenge

Build and reason from the bridge FSM.

  1. Why an FSM. Explain why a bridge needs a real control FSM while a simple slave/memory doesn't (the multi-cycle downstream expansion).
  2. The states. Describe the IDLE → SETUP → ACCESS → COMPLETE skeleton and what happens in each state.
  3. Read the waveform. From Figure 2, explain how one upstream transfer spans the downstream sequence, and how HREADYOUT holds then releases.
  4. Hold discipline. Explain why the bridge holds the upstream master for the entire downstream sequence, and the early-release bug (reads and writes).
  5. Reuse. Explain how the bridge reuses the capture, HREADYOUT, commit, and HRESP blocks, sequenced by the FSM.

10b. Where This Is Specified

  • Arm AMBA 5 AHB Protocol Specification (ARM IHI 0033). The address-phase/data-phase pipeline and the requirement that write data appears one cycle after its address phase; the HREADY / HREADYOUT distinction; and the two-cycle ERROR responseHRESP asserted with HREADYOUT low, then asserted again with HREADYOUT high — that exists so a manager can cancel an address phase it has already issued.
  • Arm AMBA APB Protocol Specification (ARM IHI 0024). The IDLE → SETUP → ACCESS state model, PSEL asserted in SETUP with PENABLE low for exactly one cycle, PENABLE asserted through ACCESS, the transfer completing only when PSEL && PENABLE && PREADY, and PSLVERR being meaningful only in that completing cycle.
  • IEEE 1800-2023 §16 — Assertions. The concurrent properties and cover property constructs used by the protocol checker on both interfaces.

11. Key Takeaways

  • A bridge needs a real control FSM — one upstream transfer expands into a multi-cycle downstream transaction, which can't collapse to combinational logic (unlike 16.1–16.3).
  • The skeleton is IDLE → SETUP → ACCESS → COMPLETE — capture the upstream transfer, drive the downstream command, run the downstream transaction (multi-cycle), then complete.
  • Hold the upstream master throughout SETUP/ACCESS (HREADYOUT low — wait states), since the upstream transfer can't finish until downstream does; release (HREADYOUT high, one cycle) only on COMPLETE.
  • Complete only on genuine downstream completion — releasing early returns garbage reads (data not back) and breaks ordering/error-reporting on writes. The multi-cycle generalization of the HREADYOUT/commit discipline (16.3/16.4/16.8).
  • It reuses every slave block — capture (16.7), HREADYOUT (16.4), write/read commit gated to completion (16.8/16.9), HRESP mapping (16.6) — sequenced by the FSM (the conductor; the blocks are the players).
  • It's the fast/slow gateway of the SoC and the proof the slave blocks compose into arbitrary complexity. (The full AHB-to-APB bridge protocol is a separate subject.)

12. What Comes Next

You now can build the capstone multi-cycle slave controller. The final chapter of this module packages the building blocks for reuse:

  • Reusable AHB RTL Templates (next) — drop-in, parameterised AHB RTL building blocks (capture, slave, memory, bridge skeletons) you can instantiate and configure.

To revisit the blocks the bridge sequences, see Address / Control Capture, HREADYOUT Generation, Write FSM, and HRESP Generation; for crossing clock domains in a bridge, see CDC Bridges.