AMBA APB · Module 12
Status Registers
Designing status registers so events are neither missed nor un-clearable — level versus sticky bits, the clear mechanisms (W1C, W0C, read-to-clear), and the interrupt raw-status / mask / enable structure where the raw status latches regardless of the mask.
A status register is how hardware tells software what happened — and its design is a contract decision, not an afterthought. A control register carries software's intent into the hardware; a status register carries the hardware's reality out to software. The single idea to carry through this chapter: a status bit must be designed to either reflect a live condition or capture a transient event, and that choice — level versus sticky, plus the clear mechanism (W1C, W0C, read-to-clear) — is what decides whether an event is reliably seen, reliably cleared, or silently lost. Get the behaviour wrong and you get the two worst peripheral bugs in the field: an interrupt that fires with no apparent cause (the event was missed) and an interrupt that will not go away (the bit cannot be cleared). This chapter designs the status-register family and the interrupt raw/mask/enable structure that sits on top of it.
1. Problem statement
The problem is reporting hardware events to software through a register, such that every event of interest is observable and, once observed, can be reliably acknowledged. That is harder than it sounds, because hardware events come in two fundamentally different shapes and software reads the register at an arbitrary later time.
- Some conditions are live — they describe the hardware's current state. "Is the engine busy right now?" "Is the FIFO empty right now?" Software wants the answer as of the read. A bit that simply mirrors the live signal is exactly right: read it, get the present truth. No clearing, no latching — the bit is just a window onto a wire.
- Some events are transient — they happen and are gone. "A transfer completed." "An error occurred." "A byte arrived." These can be a single cycle wide. If the status bit only mirrors the live signal, the event is over long before software reads — the bit reads zero and the event is lost. To report a transient event you must capture it: the bit latches when the event occurs and holds until software acknowledges it.
- A captured event must be acknowledgeable — and the acknowledge must not race the hardware. Once a sticky bit is latched, software has to clear it, or it stays set forever and the interrupt never deasserts. But a new event can occur in the very cycle software clears the old one. The clear mechanism has to be defined so that a coincident hardware-set is never lost to a software-clear.
So the job is to design, per status condition: level or sticky, and if sticky, which clear mechanism, and how the bit participates in the interrupt structure — a precise contract so software never misses an event and never gets stuck unable to clear one.
2. Why previous knowledge is insufficient
Chapter 12.1 established that access type is a software-visible contract, and 12.4 made read-only concrete — a window onto hardware state that ignores writes. A live status bit is an RO bit. But that is only half the status-register family, and the easier half.
- RO covers the live case; it cannot capture a transient event. A read-only bit that mirrors a wire is perfect for "busy right now," but a one-cycle "done" pulse is gone before software reads it. The whole sticky half of status design — latch the event, hold it, clear it — is a different contract that RO does not describe. This chapter adds it.
- Module 11.1 already built W1C as an RTL bank kind — the flop that hardware sets and a write-1 clears, including the hardware-set-beats-software-clear race. That is the implementation. This chapter does not re-teach the W1C RTL or the race resolution; it assumes them. What it adds is the map-level design: when a bit should be sticky W1C versus level versus read-to-clear, and how those bits compose into a raw-status / mask / enable interrupt structure — the contract, not the circuit.
- Status bits rarely live alone — they form an interrupt subsystem. A single sticky bit is only useful once it is wired into raw-status, enable/mask, and an interrupt output. None of the prior chapters designed that structure, and it is where most of the subtle bugs (masked-but-lost events, self-clearing interrupts, stuck interrupts) live.
So the model to add is the behavioural taxonomy of status bits — level vs sticky, and the clear mechanisms — plus the raw/mask/enable interrupt architecture they plug into. The sibling write-only chapter is the action-trigger counterpart; the configuration-registers chapter (12.7) covers the persistent control side.
3. Mental model
The model: a status register is hardware's outbox to software, and each bit is either a window or a mailbox.
- A window bit (live / level, read-only) shows the current state of something. Looking through it always shows the present truth; there is nothing to clear because there is nothing stored — it is a pane of glass onto a wire.
BUSY,FIFO_EMPTY,PLL_LOCKEDare windows. - A mailbox bit (sticky) holds a message that hardware dropped in when an event occurred, and it keeps holding it until software takes the message out (clears the bit). A momentary event is captured because the mailbox does not empty itself.
DONE,OVERFLOW,RX_NOT_EMPTY(as an interrupt source) are mailboxes. The defining property is capture-and-hold: the bit survives the event.
Two refinements make the mailbox precise:
- How software empties the mailbox is the clear mechanism. The dominant convention is W1C — write a 1 to the bit's position to clear it (writing 0 leaves it alone), so software can acknowledge exactly the bits it has handled in one write. Rarer is W0C (write 0 to clear). And RC / read-to-clear makes the act of reading empty the mailbox — convenient but dangerous, because any read clears it, including a debugger peek or a trace probe.
- The hardware-set must out-prioritise the software-clear. If a new event lands in the same cycle software is clearing the old one, the bit must stay set — the fresh message must not be thrown away with the old one. This W1C-ownership rule is implemented in 11.1; at the contract level the promise is simply "a clear never destroys a coincident event."
On top of these bits sits the interrupt structure: the raw status (sticky bits, set by hardware, regardless of any mask), an enable / mask register (one bit per source), and the interrupt output = OR of (raw status AND enable). The mask gates the output line only — the raw status still latches every event whether or not it is enabled.
4. Real SoC implementation
A real peripheral exposes a raw status register (sticky, W1C), an interrupt enable register (the mask), and derives one interrupt output. The pattern below is the idiomatic skeleton — hardware sets the sticky bits with set-priority over the software clear, the enable register gates only the output, and the interrupt is the OR-reduce of the masked bits.
// ---- Interrupt-status block for an APB peripheral ----------------------
// Map contract:
// RAWSTAT offset 0x10 [N-1:0] sticky, W1C (hw sets, write-1 clears)
// INTEN offset 0x14 [N-1:0] RW (the mask / enable)
// irq = |(rawstat & inten)
//
// hw_event[i] is a (possibly 1-cycle) pulse from the datapath for source i.
localparam int N = 3; // DONE, ERR, RXNE
logic [N-1:0] rawstat; // the sticky raw-status bits
logic [N-1:0] inten; // the enable / mask
logic [N-1:0] w1c_clear; // per-bit write-1-to-clear strobe
// W1C clear strobe: an APB write to RAWSTAT, with a 1 in each bit to clear.
assign w1c_clear = (wr_en_rawstat ? wr_data[N-1:0] : '0);
// RAW STATUS: sticky. Hardware-set has PRIORITY over the software clear,
// so an event coincident with the acknowledge is never lost (W1C ownership,
// implemented as a bank kind in 11.1 — reused here, not re-derived).
always_ff @(posedge pclk or negedge presetn) begin
if (!presetn)
rawstat <= '0; // reset: all events cleared
else
for (int i = 0; i < N; i++) begin
if (hw_event[i]) rawstat[i] <= 1'b1; // SET wins...
else if (w1c_clear[i]) rawstat[i] <= 1'b0; // ...over CLEAR
// else: hold — the bit is sticky, it does not fall on its own
end
end
// INTERRUPT ENABLE (mask): plain RW. It gates the OUTPUT only — it does
// NOT gate the latch above, so masking a source never loses its event.
always_ff @(posedge pclk or negedge presetn)
if (!presetn) inten <= '0; // reset: all sources masked
else if (wr_en_inten) inten <= wr_data[N-1:0];
// MASKED status (what the CPU usually reads to decide what to service) and
// the single interrupt line: OR-reduce of raw AND enable.
wire [N-1:0] maskstat = rawstat & inten;
assign irq = |maskstat; // interrupt = |(raw & enable)
// Read mux: RAWSTAT returns the latched events; a separate MASKSTAT view is
// common so software can read "what is both pending AND enabled" directly.
always_comb unique case (paddr[7:0])
8'h10: prdata = {{(32-N){1'b0}}, rawstat}; // raw: every latched event
8'h14: prdata = {{(32-N){1'b0}}, inten}; // the mask
8'h18: prdata = {{(32-N){1'b0}}, maskstat}; // raw & enable (read-only view)
default: prdata = 32'h0;
endcaseThree facts make this status-register design and not just coding. First, the latch is independent of the mask: rawstat is set by hw_event unconditionally, while inten only feeds the output irq — so disabling an interrupt never loses the underlying event, and re-enabling it later still finds the bit set. Second, the interrupt does not clear itself: irq is purely combinational from rawstat & inten, so it stays asserted until software clears the relevant rawstat bit via W1C — clearing the status is the only way to deassert the interrupt. Third, hardware-set beats software-clear in the rawstat flop, so an event that lands in the same cycle as the acknowledge is captured, not dropped — the contract promise that no coincident event is lost.
5. Engineering tradeoffs
Each status condition is a contract choice along two axes — level vs sticky and, if sticky, which clear mechanism. The table is the design menu.
| Behaviour | Set by | Cleared by | Miss risk | When to use |
|---|---|---|---|---|
| Live / level (RO) | Reflects the live hardware signal continuously | Nothing — falls when the condition ends | High for transient events (event gone before software reads); none for steady state | Report a current condition: BUSY, FIFO_EMPTY, PLL_LOCKED. Never for one-shot events. |
| Sticky W1C | Hardware latches on the event (set-priority) | Software writes a 1 to the bit (writing 0 leaves it) | Low — event held until acknowledged; coincident set wins | The default for transient events / interrupt sources: DONE, OVERFLOW, ERR. Selective ack of exactly the handled bits. |
| Sticky W0C | Hardware latches on the event | Software writes a 0 to the bit | Low (same capture); but error-prone semantics | Rare / legacy. Awkward because a benign full-word write of all-1s leaves bits set while all-0s clears them — opposite of intuition. Avoid in new designs. |
| Read-to-clear (RC) | Hardware latches on the event | The act of reading the register clears it | Low if read exactly once; high if read again (debugger/trace peek, double-read) clears it out from under the ISR | When a single trusted reader (the ISR) consumes the event and re-reading must not re-fire — but fragile under shared reads; W1C is usually safer. |
The throughline: default to sticky W1C for any transient event, and reserve level RO for genuinely live conditions. W1C wins because the acknowledge is explicit and selective — software clears exactly the bits it handled, in one write, with no read-modify-write and no surprise side effects. W0C and RC both capture events correctly but carry semantic traps (W0C's inverted intuition, RC's clear-on-any-read), so they are special-purpose, not defaults. And the mask is always separate from the latch, so disabling an interrupt is a policy decision that never costs you the event.
6. Common RTL mistakes
7. Debugging scenario
The signature status-register bug is a transient event missed because the bit was level, not sticky — software gets an interrupt (or a downstream symptom) with no status to explain it, because the condition was gone by the time the ISR read the register.
- Observed symptom: the ISR fires, reads the status register, finds nothing set, and returns having done nothing — "an interrupt with no cause." Or, more insidiously, a completion is occasionally lost: the engine finished, but the driver's wait loop polling the
DONEstatus never sees it set and the transaction hangs or times out. The miss is intermittent and correlates with system load (longer interrupt latency makes it more likely). - Waveform clue: the underlying event signal is a single-cycle pulse. The status bit on the bus read tracks that pulse exactly — it was high only while the event line was high — and it has already fallen to zero several cycles later when the APB read of the status register occurs. There is no latch: the bit and the event rise and fall together, and the read samples after the fall.
- Root cause: the status bit was specified (and implemented) as a level / read-only window onto a live signal, but the condition it reports is a transient event. A level bit reports current state; it cannot capture a momentary pulse. By the time software reads, the event is over, so the bit reads zero and the event is permanently lost. The defect is the behavioural choice in the map — level where it needed sticky — not a wiring error.
- Correct RTL: make the bit sticky W1C: latch it set when the event pulse occurs, hold it (do not let it fall with the event), and clear it only when software writes a 1. Hardware-set keeps priority over the software clear so a coincident event is not lost.
Azvya Education Pvt. Ltd.VLSI MentorSnippet
always_ff @(posedge pclk or negedge presetn) if (!presetn) done <= 1'b0; else if (done_pulse) done <= 1'b1; // latch the transient event else if (w1c_done) done <= 1'b0; // hold until software W1C - Verification assertion: prove the sticky bit holds the event until a clear — that once a hardware event has set it, the bit stays set on every cycle that has no W1C clear (and that masking does not change this latching):
Azvya Education Pvt. Ltd.VLSI MentorSnippet
// A latched event stays set until (and only until) software clears it. assert property (@(posedge pclk) disable iff (!presetn) (done_pulse |=> (done throughout (!w1c_done [*0:$])))); // Masking the interrupt does not prevent the raw bit from latching the event. assert property (@(posedge pclk) disable iff (!presetn) (hw_event[i] |=> rawstat[i])); // holds regardless of inten[i] - Debug habit: when an ISR sees "an interrupt with no cause," do not start hunting glue logic — first ask "is this status bit level or sticky, and is the event it reports transient?" A transient event behind a level bit is a missed-event waiting to happen. The reciprocal habit: when a status bit is stuck set, check that the ISR actually issues the W1C (and writes only the handled bits); when it clears unexpectedly, suspect a read-to-clear bit being read by a debugger or a stray second read.
8. Verification perspective
Status registers are verified against their behavioural contract — capture, hold, clear, and the raw/masked/interrupt relationship — not just "the address reads back."
- Event-always-latched coverage. For every sticky source, prove (assertion + coverage) that a hardware event always leaves the bit set, including a one-cycle pulse, and that the bit holds across arbitrary delay until a clear. Cover the worst case explicitly: a single-cycle event observed N cycles later. This is the check that catches level-where-sticky-needed.
- Clear-mechanism correctness. For W1C, prove that writing a 1 clears exactly that bit and writing a 0 leaves it unchanged (no accidental clear of un-acknowledged bits); for W0C, the inverse; for RC, that a read clears and — critically — that exactly one clear happens per read (the read-clear-side-effect-once property below). A directed test should attempt to clear with a 0 and confirm the bit survives.
- Raw-vs-masked interrupt. Assert that the raw status latches an event regardless of the mask (
hw_event |=> rawstat), and that the interrupt output equalsOR(raw & enable)exactly — masked sources do not drive the line, enabled-and-pending sources do, and re-enabling a masked-but-pending source re-asserts the interrupt. Cover: event while masked, then unmask → interrupt fires. - Coincident set/clear. Drive a hardware event in the same cycle as a software W1C of that bit and assert the bit ends up set (hardware-set priority) — the no-coincident-event-lost contract. This is a high-value, easily-missed corner.
- Read-clear side-effect-once. For any RC bit, prove that a read clears it exactly once and that a second read returns zero — and add a deliberate scenario where a stray extra read (modelling a debugger/trace access) clears a pending event, to document the fragility. If the verification environment ever back-door or RAL-mirror-reads an RC register, it can mask a real bug, so RC registers need front-door-only read policies in the testbench.
The point: verify the status register as behaviour over time — latched, held, cleared, masked — with the coincident set/clear and the read-clear-once corners called out explicitly, because those are exactly the cases a naive read/write-back test passes while the contract is broken.
9. Interview discussion
"How do you design a status register?" separates engineers who list bit types from engineers who reason about missed and stuck events.
Lead with the dichotomy: a status bit is either live/level (a read-only window onto a current condition, no clear) or sticky (it captures a transient event and holds it until software acknowledges). State the decision rule crisply: if the condition can be momentary, the bit must be sticky — a level bit cannot capture a transient event. Then cover the clear mechanisms: W1C is the default (write a 1 to acknowledge exactly the bits you handled — explicit, selective, side-effect-free); W0C exists but has inverted intuition; read-to-clear is convenient but dangerous because any read clears it, including a debugger peek, so the real ISR can lose the event. Now deliver the depth that signals real silicon experience: the interrupt structure — raw status (sticky, latches every event regardless of mask), an enable/mask register (one bit per source), and interrupt = OR(raw & enable). Make the three contract points explicit: the mask gates only the output, never the latch, so masking never loses an event; the interrupt never clears itself — software clears the status, and clearing the status is what deasserts the interrupt (forget it and you get a stuck interrupt); and on a coincident hardware-set and software-clear, hardware-set must win or a fresh event is lost (the W1C-ownership rule from the bank RTL). Closing with the two field bugs — "interrupt with no cause" (level where it should be sticky) and "interrupt that won't clear" (missing W1C in the ISR, or mask mistaken for acknowledge) — shows you can debug, not just define.
10. Practice
- Classify the bits. For a DMA engine with
BUSY,CHANNEL_DONE,BUS_ERROR,FIFO_HALF_FULL, andABORT_ACK, label each live/level (RO) or sticky, and justify each from whether the condition is steady or transient. - Choose the clear. For each sticky bit above, pick W1C / W0C / RC and explain the consequence of the choice for a driver that polls versus one that takes an interrupt.
- Build the interrupt structure. Given three sticky sources, write the raw-status, the enable/mask, and the
irq = OR(raw & enable)expression, and show that masking a source still latches its event. - Trace a stuck interrupt. Given an ISR that reads the status, services the work, but never writes back, explain why the interrupt re-fires forever and the one-line fix.
- Trace a missed event. Given a one-cycle event behind a level RO bit, draw the waveform and show why the ISR reads zero, then convert the bit to sticky W1C and re-draw.
11. Q&A
12. Key takeaways
- A status register is hardware's outbox to software, and every bit is either a window (live/level, RO) or a mailbox (sticky). The choice is dictated by whether the condition is a steady state or a transient event.
- Transient events must be sticky. A level bit only reports current state and cannot capture a momentary pulse — read it too late and the event is lost ("interrupt with no cause").
- W1C is the default clear mechanism — write a 1 to acknowledge exactly the bits you handled, atomically and selectively. W0C (inverted intuition) and read-to-clear (clears on any read, including a debugger peek) are special-purpose, not defaults.
- The interrupt structure is raw status (sticky, latches every event regardless of mask), an enable/mask register, and
interrupt = OR(raw & enable). The mask gates only the output line — masking never loses an event. - The interrupt never clears itself: software clears the status, and clearing the status is what deasserts the interrupt. Forgetting the W1C in the ISR is the classic stuck interrupt; mistaking the mask for an acknowledge re-fires it on unmask.
- Hardware-set beats software-clear on a coincident cycle (the W1C-ownership rule from 11.1), so a fresh event is never lost to an acknowledge — and an ISR must write back exactly the bits it read, never a blanket all-ones.