Skip to content

AMBA APB · Module 11

Read-Data Mux

The slave's read path — the mux that selects the addressed register onto PRDATA, the defined default for an unmapped offset (never X), the combinational-versus-registered PRDATA trade, and the read-side-effect hazard where a clear-on-read must fire exactly once at completion, not on the combinational select.

The register bank holds the values; the read path is what presents the right one on PRDATA. That sounds like a one-line case statement — and the happy path is — but the read mux is where three subtle, interview-grade failures live: an unmapped offset returning X instead of a defined value, a comb-versus-registered timing choice that mirrors the PREADY trade, and the nastiest of all, a read side effect — a clear-on-read or FIFO pop — firing on the combinational select instead of the real completion, silently destroying data. The single idea to carry: the read mux is a pure selector, but the design lives in its three margins — the default value, the timing of when PRDATA settles, and the discipline that any read side effect fires exactly once, at completion, never on the live mux select.

1. Problem statement

The problem is driving the addressed register's value onto PRDATA so it is valid at the moment the master samples it — and only then — while every unmapped offset returns a defined value and every read side effect fires exactly once.

A read is, structurally, a selection: the decoder produces a read-select from PADDR, and the slave must route exactly one register's output onto the shared PRDATA bus. But "route the right register" hides three decisions that are the whole of this chapter:

  • What the bus reads when nothing is mapped. The case is over a finite set of offsets; the address space is larger. Every gap needs a defined default — conventionally 0 — so an out-of-range or reserved read returns a known value and never propagates an X into the master.
  • When PRDATA is actually required to be valid. APB does not demand PRDATA be meaningful for the whole transfer — only in the access phase at completion (when PSEL & PENABLE & !PWRITE & PREADY). Outside that, PRDATA is don't-care. That single fact decides whether you may drive 0 when unselected, whether you may register the output, and how a shared read bus is built.
  • When a read changes state. Some reads have side effects: a clear-on-read status, a FIFO pop, an interrupt-acknowledge. These must fire on the real, completed read — exactly once — not on the combinational mux select, which is live for the entire access including any wait state. Get this wrong and a read destroys data without the master ever seeing it.

So the job is not "mux the registers onto PRDATA" — it is to build a selector with a defined default, settled at the right time, whose side effects fire exactly once at completion.

2. Why previous knowledge is insufficient

You have just built the two things the read mux consumes, and you have already learned the timing discipline it mirrors — but none of those chapters built the read path itself.

  • Chapter 11.1 gave you the register outputs, not the path to the bus. The register-bank design produced ctrl_q, status_q, iflag_q — the typed storage outputs. It deliberately stopped at "the read mux selects among these." It never said what happens at an unmapped offset, how the selection is timed, or what a clear-on-read does. That is this chapter.
  • Chapter 11.2 gave you the read-select, not what to do with it. The address decoder turns PADDR into a one-hot or case selector that says which register is addressed. But a select line is combinational and live for the whole access; the decoder does not know about completion, defaults, or side effects. The mux must add all three on top of the raw select.
  • Module 8 gave you the timing discipline, not the read mux that obeys it. PREADY timing taught the combinational-versus-registered trade and that the master samples in the access phase at completion. The read path mirrors that exact trade — a combinational mux gives the value in the access cycle, a registered PRDATA adds a cycle and must pair with a registered PREADY. You know the principle; you have not yet applied it to PRDATA.

So the model to add is the read path as its own module: a selector that consumes the bank outputs (11.1) and the read-select (11.2), obeys the access-phase sampling rule (Module 8), and adds the three things none of them covered — the default, the output timing, and side-effect gating. The write logic (11.4) and PREADY generation (11.5) are the symmetric pieces you build next.

3. Mental model

The model: the read mux is a switchboard operator. The decoder hands the operator a slip of paper — the read-select — naming one line. The operator's only job is to patch that one line's current value through to a single outgoing wire, PRDATA. Three rules make a real operator out of a naive one. First, if the slip names a line that doesn't exist, the operator does not leave the wire floating and does not patch in noise — they patch in a standard silence, a defined 0. Second, the value on the outgoing wire only matters at one instant — the moment the caller picks up at the far end (completion); before that, whatever is on the wire is irrelevant. Third, and most important: patching a line through is not the same as acting on it. Some lines have a meaning to "answering" them — picking up drains a message queue. The operator must only drain the queue when the call truly completes, not every time the slip happens to name that line while the caller is still dialing.

Three refinements make it precise:

  • The mux is combinational selection; the default is a real input. The selector is a case (or one-hot AND-OR) over the read-select, choosing among the register outputs. The default arm is not an afterthought — it is a wired input, 32'h0, that handles every unmapped offset deterministically. An incomplete case with no default is how an X (or an inferred latch) gets onto PRDATA.
  • Validity is a phase property, not an always property. PRDATA is only required valid in the access phase at completion — the cycle where PSEL & PENABLE & !PWRITE & PREADY. Outside that window it is don't-care, which gives you freedom: you may drive 0 when your slave is unselected (easing a shared/ORed read bus), or you may register the output (settling it a cycle later, paired with a registered PREADY).
  • A side effect is gated on completion, not on select. The combinational mux select is asserted for the entire access, including wait states. A clear-on-read or FIFO pop must fire on the qualified completion pulsePSEL & PENABLE & !PWRITE & PREADY — so it happens exactly once, on the cycle the master actually captures the data. Tying it to the bare select pops during the wait, before the master reads, and loses the entry.
A block diagram of an APB slave read path: register-bank outputs ctrl_q, status_q, iflag_q and a constant 32'h0 default feed a combinational multiplexer whose select is the address decoder's read-select; the mux output drives PRDATA, with an optional registered-PRDATA flop variant shown, and a read-completion qualifier PSEL and PENABLE and not-PWRITE and PREADY marked as the only pulse that samples PRDATA and may fire a read side effect.
Figure 1 — the slave's read-data path. On the left, the register bank (chapter 11.1) exposes its read outputs ctrl_q, status_q, and iflag_q, plus a wired constant 32'h0 as the default for any unmapped offset. These feed the data inputs of a combinational multiplexer in the centre, whose select line is the read-select produced by the address decoder (chapter 11.2). The mux output drives PRDATA, which is required valid only in the access phase at completion. An optional registered-PRDATA variant (amber) inserts a flop on the output: +1 cycle, cleaner timing, paired with a registered PREADY. At the bottom, the read-completion qualifier — PSEL & PENABLE & !PWRITE & PREADY — is the only pulse that samples PRDATA and the only condition under which a read side effect (clear-on-read, FIFO pop) may fire. The figure shows the read mux as pure selection, the default value wired in deliberately, and side effects gated on completion rather than on the live mux select.

4. Real SoC implementation

In RTL, the read path is a combinational mux with an explicit default, feeding PRDATA, plus a separate completion-qualified pulse for any read side effect. Keep the selection and the side effect in different always-blocks: the mux is pure combinational logic; the side effect is a clocked event gated on completion. Here is the idiomatic structure.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Read path of an APB slave: a combinational mux onto PRDATA with a defined
// default, plus a completion-gated read side effect (clear-on-read FIFO pop).
// Inputs: the bank outputs (chapter 11.1) and the read-select (chapter 11.2).
module apb_read_path (
  input              pclk, presetn,
  // --- APB phase signals (for the completion qualifier) ---
  input              psel, penable, pwrite, pready,
  input  [11:0]      paddr,
  // --- register-bank read outputs (chapter 11.1) ---
  input  [31:0]      ctrl_q, status_q, iflag_q,
  input  [31:0]      fifo_head,          // RO data, head of a read FIFO
  // --- side-effect handshake to the FIFO ---
  output             fifo_pop,           // 1-cycle pop pulse on real read
  // --- read data to the bus ---
  output reg [31:0]  prdata
);
  // Offset map (word-aligned). The decoder (11.2) could hand a one-hot select
  // instead; here we case on paddr for clarity.
  localparam CTRL   = 12'h000,
             STATUS = 12'h004,
             IFLAG  = 12'h008,
             FIFO   = 12'h00C;
 
  // 1) The read MUX — pure combinational selection with a DEFINED DEFAULT.
  //    No latch, no X: every unmapped offset returns 0. Note this is the
  //    COMBINATIONAL variant: PRDATA is valid in the access cycle itself.
  always_comb begin
    case (paddr)
      CTRL:    prdata = ctrl_q;
      STATUS:  prdata = status_q;
      IFLAG:   prdata = iflag_q;
      FIFO:    prdata = fifo_head;
      default: prdata = 32'h0000_0000;   // unmapped offset → defined 0, never X
    endcase
  end
  // (Optionally drive 0 when unselected to ease a shared/ORed read bus:
  //   if (!psel) prdata = 32'h0;  — legal because PRDATA is don't-care then.)
 
  // 2) The READ COMPLETION qualifier — the ONLY pulse that "really" reads.
  //    True for exactly one cycle: access phase, a read, and PREADY high.
  wire read_done = psel & penable & ~pwrite & pready;
 
  // 3) The read SIDE EFFECT — gated on completion, NOT on the mux select.
  //    The pop fires once, on the cycle the master captures fifo_head.
  //    Tying this to (paddr==FIFO) alone would pop during a wait state.
  assign fifo_pop = read_done & (paddr == FIFO);
endmodule

Three facts make this the right structure. First, the mux has an explicit default so PRDATA is defined for every possible PADDR — there is no case arm that leaves it un-assigned, so no latch and no X can reach the bus. Second, PRDATA is the combinational selection itself — valid in the access cycle, the read-path analogue of a combinational PREADY; a registered variant simply flops the mux output (always_ff @(posedge pclk) prdata <= mux_out;), adding one cycle of latency that you must absorb by also registering PREADY (Module 8), or the master samples stale data. Third — the critical line — the side effect is read_done & (paddr==FIFO), not just (paddr==FIFO): the pop fires on the single completion pulse, so the FIFO advances exactly when the master captures fifo_head, never during the combinational select that is live for the whole access. The byte lanes follow the same rule as the mux: APB returns a full 32-bit word and the master extracts the bytes it wants, so the slave drives all lanes of the selected register and lets the master's PSTRB-less read take the word whole.

5. Engineering tradeoffs

The read path's shape is one main trade — when PRDATA settles — plus the unselected-drive policy that a shared bus forces.

PropertyCombinational PRDATARegistered PRDATADrive 0 when unselected
Read latencyValue ready in the access cycle (zero extra)One extra cycle; must pair with registered PREADYIndependent of this trade — applies to either
Timing closureLong path: decode → mux → PRDATA in one cycleShort path: flop output isolates the mux from the busNo effect on the data path itself
PREADY couplingWorks with combinational or registered PREADYMust pair with registered PREADY (Module 8) or master samples staleOrthogonal
Shared / ORed read busNeeds each slave to drive 0 (or tristate) when unselectedSame requirement, on the registered outputThis is the enabler — ORing read buses need 0-when-idle
Read-side-effect safetySide effect must be gated on completion regardlessSame gating; the flop does not change the ruleUnaffected — the pulse is still completion-qualified
When to chooseDefault for small/fast register slavesLarge fan-in mux, deep banks, or tight PCLKWhenever multiple slaves share an ORed PRDATA

The throughline: the combinational mux is the default and the simplest — PRDATA is valid in the access cycle, like a zero-wait PREADY. You register the output only when the mux's fan-in or the clock period makes the decode-to-PRDATA path the critical path — and the moment you do, you owe a registered PREADY to keep the master sampling the right cycle. The "drive 0 when unselected" choice is orthogonal to the timing trade: it is what an ORed shared read bus needs, and it is legal precisely because PRDATA is don't-care outside your access phase. None of these touch the side-effect rule — that pulse is completion-qualified in every variant.

6. Common RTL mistakes

7. Debugging scenario

The signature read-path bug is a clear-on-read FIFO that silently loses an entry, caused by gating the read side effect on the combinational mux select instead of the real completion — and it only manifests when the read takes a wait state.

  • Observed symptom: a status FIFO (or a read-to-clear interrupt log) occasionally skips an entry. Software reads the FIFO head, gets entry N, but the next read returns entry N+2 — entry N+1 vanished. It is intermittent and worsens under bus contention, when reads to that slave are more likely to incur a wait state. Back-to-back zero-wait reads look fine; the bug only appears when PREADY is low for a cycle.
  • Waveform clue: zoom in on a read that waits. PSEL and PENABLE are high, PADDR selects the FIFO, but PREADY is low for one cycle (the access-phase wait). The fifo_pop strobe fires during that wait cycle — while PREADY is still low — and then PRDATA at the real completion cycle shows the next entry, not the one that was at the head when the access began. One entry was popped without ever being captured.
  • Root cause: the side effect was gated on the combinational decode/select — assign fifo_pop = (paddr == FIFO_OFFSET) & psel; — which is asserted for the whole access, including the wait. So the pop fires on the access cycle regardless of PREADY, advancing the FIFO before the master samples. The mux dutifully presents the new head at completion, the master captures that, and the entry that was popped during the wait is gone. It is a completion-qualification bug in the side effect, not in the mux or the FIFO.
  • Correct RTL: qualify the side effect with the full completion condition so it fires exactly once, on the cycle the master actually reads — assign fifo_pop = psel & penable & ~pwrite & pready & (paddr == FIFO_OFFSET);. Now the pop and the master's capture happen on the same cycle: the master gets entry N, the FIFO advances to N+1, and the next read gets N+1. No wait state can pop early, because the pulse is gated on pready.
  • Verification assertion: assert the pop only ever fires on a completed read, and exactly once per read — assert property (@(posedge pclk) disable iff(!presetn) fifo_pop |-> (psel & penable & ~pwrite & pready & (paddr == FIFO_OFFSET))); (every pop coincides with a real FIFO read completion) — plus a no-X check on the data bus at completion: assert property (@(posedge pclk) disable iff(!presetn) (psel & penable & ~pwrite & pready) |-> !$isunknown(prdata)); (PRDATA is never X when the master samples).
  • Debug habit: when a read-to-clear or pop register loses data, do not start in the FIFO or the software driver — go to the gating condition of the side effect and check it against the completion qualifier, not the select. The litmus test: does the strobe survive a wait state without firing? If the side effect can fire while PREADY is low, it is gated on the wrong signal. Any read with a side effect must be conditioned on PSEL & PENABLE & !PWRITE & PREADY, full stop — the combinational select is a trap because it is live for the entire access.
Two stacked APB timing diagrams of a clear-on-read FIFO across a read with one wait state: in the top buggy case fifo_pop fires during the wait cycle on the combinational mux select, popping an entry before the master captures it so an entry is lost; in the bottom correct case fifo_pop fires as a single pulse only on the completion cycle when PSEL, PENABLE, not-PWRITE and PREADY are all true, so the master captures the entry exactly as the FIFO advances and no data is lost.
Figure 2 — a clear-on-read FIFO pop gated on the wrong signal, shown across a read with one wait state. In both cases PSEL asserts, PENABLE asserts, and PREADY stays low for one wait cycle (T2) before completing (T3). Top (bug, red): fifo_pop fires on the combinational mux select during the wait cycle T2 — before the access completes — so the FIFO advances early; at the real completion the master captures the next entry and entry N is silently lost. Bottom (correct, green): fifo_pop is gated on the completion qualifier PSEL & PENABLE & !PWRITE & PREADY, so it fires as a single pulse only on the completion cycle T3 — the master captures entry N exactly as the pop advances the FIFO, and no data is lost. The figure shows the failure is in the side effect's gating condition, not in the mux or the FIFO, and that the fix is to qualify on real completion rather than the live select.

8. Verification perspective

The read path is verified against three properties — every register reads back, no offset ever returns X, and every read side effect fires exactly once at completion — and the comb-versus-registered timing must be covered explicitly.

  • Read back every register, against its kind. For each mapped offset, drive a read and check PRDATA equals the register's expected value: an RW reads what was written, an RO reflects hardware, a W1C shows the current flags. A register-model-driven check (UVM RAL or equivalent) automates the mirror, but only if the model knows the read value of each kind — a WO/strobe must read its defined value (e.g. 0), not whatever the storage holds.
  • Hit the unmapped space — assert a defined value, never X. Read a sweep of reserved and out-of-range offsets and assert PRDATA is the defined default (0) and is never unknown: !$isunknown(prdata) at completion for all addresses, mapped and unmapped. This is the single highest-value read-path check because an X on PRDATA corrupts the master and is exactly what a missing default produces. Cover that the unmapped range was actually exercised.
  • Side effect fires exactly once, at completion. For every read with a side effect (clear-on-read, FIFO pop, interrupt-ack), assert the strobe coincides only with PSEL & PENABLE & !PWRITE & PREADY and never during a wait state — and count it: exactly one pop per read. Drive reads with inserted wait states deliberately; a side effect bug is invisible to zero-wait reads. Cover both the wait-state and zero-wait read of every side-effecting register.
  • Cover the timing variant. If the slave can be built combinational or registered (or has both modes), functional coverage must record reads in each configuration, and a scoreboard must check that registered PRDATA pairs with registered PREADY so the master samples the correct cycle. A test passing in combinational mode says nothing about the registered path.

The point: the read path's correctness is defined-value-everywhere plus side-effect-once-at-completion. Read back every register, sweep the unmapped space for a defined non-X value, force wait states to expose early-firing side effects, and cover both timing variants — not just that each mapped address returned the right value on a zero-wait read.

9. Interview discussion

"Walk me through an APB slave's read path" separates candidates fast: a weak answer is "a mux on PADDR onto PRDATA"; a strong one names the three margins that actually break.

Lead with the structure: the read mux is a combinational selector — a case (or one-hot) over the decoder's read-select — routing one register's output onto PRDATA, with an explicit default of 0. Then deliver the depth. First, the default is mandatory: every offset, including reserved and out-of-range, must return a defined value, because an incomplete case infers a latch or drives X that propagates into the master. Second, PRDATA is only required valid in the access phase at completion — outside that it is don't-care, which is why you may drive 0 when unselected to feed an ORed shared read bus, and why you may register the output. Third — and this is the line that signals you have debugged real silicon — a read side effect (clear-on-read, FIFO pop) must be gated on the completion qualifier PSEL & PENABLE & !PWRITE & PREADY, not on the combinational select, because the select is live for the whole access including wait states; gating on the select pops during the wait and silently loses data. Close on timing: the comb-versus-registered choice mirrors the PREADY trade — a combinational mux gives the value in the access cycle; a registered PRDATA adds a cycle and must move together with a registered PREADY (Module 8). Mentioning that the lost-FIFO-entry bug lives in the side effect's gating, not the FIFO, shows you have actually fixed one.

10. Practice

  1. Write the mux. From memory, write the always_comb read mux for a slave with CTRL, STATUS, IFLAG, and a FIFO data register, with a defined default. State why the default arm is not optional.
  2. Place the default. Given a 4 KB address space and four mapped 32-bit registers, describe what PRDATA returns for every unmapped offset and the one line of RTL that guarantees it is never X.
  3. Gate the side effect. Write the assign for a clear-on-read FIFO pop and explain, cycle by cycle, why it must be psel & penable & ~pwrite & pready & (paddr==FIFO) and not just (paddr==FIFO).
  4. Convert to registered. Take the combinational mux and turn PRDATA into a registered output; state exactly what else must change in the slave so the master still samples the right cycle.
  5. Find the lost entry. Given a FIFO that returns entry N then N+2, with fifo_pop firing while PREADY is low on a waited read, locate the bug and give the one-line fix.

11. Q&A

12. Key takeaways

  • The read mux is a pure combinational selector — a case or one-hot over the decoder's read-select (11.2), routing one register-bank output (11.1) onto PRDATA. The design lives in its three margins: the default, the output timing, and side-effect gating.
  • Every offset must return a defined value — conventionally 0 — never X. The default arm of the case is mandatory; omitting it infers a latch or drives X that propagates into the master and corrupts reads.
  • PRDATA is required valid only in the access phase at completion (PSEL & PENABLE & !PWRITE & PREADY). Outside that it is don't-care — so you may drive 0 when unselected to feed an ORed shared read bus, or register the output.
  • A read side effect (clear-on-read, FIFO pop) fires on the completion qualifier, not the mux select. The combinational select is live for the whole access, including wait states; gating a side effect on it pops/clears during the wait and silently loses data.
  • Combinational versus registered PRDATA mirrors the PREADY trade. A combinational mux gives the value in the access cycle; a registered PRDATA adds a cycle and must move together with a registered PREADY (Module 8), or the master samples stale data.
  • Verify defined-value-everywhere and side-effect-once-at-completion. Read back every register, sweep the unmapped space for a non-X default, force wait states to expose early-firing side effects, and cover both timing variants — not just zero-wait reads of mapped addresses.