AMBA APB · Module 12
Write-Only Registers
Write-only registers as a write-triggers-effect contract where the read is not a mirror of the write — command/strobe registers with self-clearing go-bits, write-only data and key ports, and the read policy decision of returning a defined zero or an error.
A write-only (WO) register is a contract where software writes to cause an effect, and reading it does not return the written data. That single inversion — the read is not a mirror of the write — is what makes WO its own access type rather than "an RW register you happen not to read." A WO register exists to do something (start an engine, push a key, abort a transfer) and to expose nothing meaningful back: the written value triggered an action or disappeared into secure hardware, so the read is governed by a deliberately chosen policy (a defined 0, or an error) rather than by the last write. This chapter designs WO registers as a map contract — the two canonical patterns, the read policy decision, and the self-clearing strobe semantics — without re-teaching the RTL that 11.4 write-logic already built.
1. Problem statement
The problem is specifying a register whose purpose is to trigger an effect, not to store software-visible state — and deciding, as part of the contract, exactly what a read of it returns when the written value is by design not retained.
Most registers you have met so far are mirrors: an RW control field returns its last write; an RO status field returns hardware state. A write-only register breaks that symmetry on purpose. Software writes it to act — and the act, not a stored value, is the point. Two things follow that the map must decide explicitly:
- The write is an event, not a store. Writing a WO command register kicks off an action — start a DMA, abort a transfer, soft-reset a block. The hardware consumes the write as a trigger; it may not keep the value at all. So "what does this register hold?" is the wrong question — the right one is "what does writing it do?"
- The read has no natural answer, so the map must choose one. There is no last-written value worth returning (it triggered something, or it was a secret pushed into hardware). The map therefore declares the read policy: return a defined
0, or signal an error (PSLVERR). This is a contract decision, not an RTL accident — a driver is written against whatever the map promises a read returns.
So the job is to author a write-triggers-effect contract: name the effect the write causes, decide whether the written value matters, and explicitly specify the read policy — because unlike RW/RO, a WO register has no automatic answer to "what do I read back?"
2. Why previous knowledge is insufficient
Chapter 12.1 — CSR design fundamentals established that access type is a contract declared in the map, and 12.4 — read-only registers designed the RO sibling: a register hardware drives and software only reads. WO is the mirror inversion of RO — software only writes, and the read is the part with no natural meaning. Prior chapters get you to the doorway but not through it:
- 12.1 named WO as one access type among many; this chapter designs it. The fundamentals chapter listed WO ("write triggers, read defined") in the access-type taxonomy. That is the what. This chapter is the how to design one: the command-strobe versus write-only-data patterns, the read-policy choice, and the self-clear semantics that make a strobe behave correctly.
- 12.4 designed RO as "read returns hardware state, write ignored"; WO is the exact opposite axis. RO is all about the read (it is a mirror of hardware); its write is the inert side. WO is all about the write (it is an event); its read is the inert side. Designing WO is designing the write effect and the read policy, which RO never had to.
- 11.4 write-logic built the commit; it did not design the contract. Chapter 11.4 implemented how a write lands in a flop and how a one-cycle write strobe is generated. That is the RTL mechanism. This chapter decides what the map promises: that a write triggers an effect, that the strobe self-clears, and that a read returns a defined value — the contract the RTL must serve. We reference that strobe-generation logic; we do not re-derive it.
The model to add is the WO contract: a write is an effect, the read is policy-defined, and the strobe self-clears — a specification authored before the RTL. The natural next step is the status register (12.6), where the read is the point and the write is inert — the opposite emphasis again.
3. Mental model
The model: a write-only register is a doorbell, not a mailbox. A mailbox (RW) stores what you put in and hands it back when you look. A doorbell does something when you press it — and looking at the button tells you nothing about who pressed it last. Pressing is the entire purpose; the button has no readable memory of the press. A WO register is that button: the write is the event, and the read is, by contract, blank.
Three refinements make it precise:
- The write carries an effect; the value may or may not matter. For a pure trigger (a
STARTorABORTstrobe), writing a1to one bit is the whole message — the rest of the word is don't-care. For a write-only data port (a key, a FIFO push), the value is the payload that flows into hardware. Either way the write is consumed as an action or absorbed as data, not parked as readable state. - A trigger bit self-clears — it is a strobe, not a level. The cleanest command bit is a self-clearing go-bit: software writes
1, the hardware emits a one-cycle action pulse, and the bit auto-clears the next cycle so a subsequent read sees0. This is what makes it a strobe (an event) rather than a level (a held state). A level-held command bit is a bug magnet (beat 7). - The read is policy, not data. Because there is no meaningful value to return, the map chooses the read behaviour: most commonly a defined
0(clean, predictable, lets a driver read-modify-write safely if it must), orPSLVERR(strict — reading a write-only address is a software error worth flagging). The choice is part of the contract a driver is written against.
4. Real SoC implementation
Two patterns dominate. First, a self-clearing command strobe: write 1 to START to trigger an action; the bit auto-clears and the read returns 0. Second, a write-only data/key port: written data is pushed into hardware (a FIFO, a secure key) and cannot be read back. The RTL leans on the one-cycle write strobe from 11.4; the contract is the self-clear and the read policy.
// Write-only command/strobe register + write-only key port.
// Offset Register Field Bits Access Effect
// 0x10 CMD START [0] WO write-1 triggers run; self-clears; read 0
// 0x10 CMD ABORT [1] WO write-1 triggers abort; self-clears; read 0
// 0x14 KEYIN KEY [31:0] WO write pushes a key byte into secure store; read 0
localparam CMD = 8'h10, KEYIN = 8'h14;
// wr_en_<reg> is the one-cycle write strobe from the write-logic stage (Ch 11.4):
// high for exactly the single ACCESS-phase cycle of an APB write to that offset.
// --- Pattern A: self-clearing command strobe -------------------------------
// The action enable is a ONE-CYCLE PULSE derived from the write event AND the
// written bit — NOT a held level. The bit is never stored as readable state.
logic start_pulse, abort_pulse;
always_comb begin
start_pulse = wr_en_cmd & pwdata[0]; // fires for exactly one cycle, only if a 1 was written
abort_pulse = wr_en_cmd & pwdata[1]; // write value of the bit selects WHICH action
end
// Downstream engine consumes the one-cycle pulse. Because it is a pulse, the
// action fires EXACTLY ONCE per write — it cannot re-trigger on later cycles.
always_ff @(posedge pclk or negedge presetn)
if (!presetn) engine_run <= 1'b0;
else if (abort_pulse) engine_run <= 1'b0;
else if (start_pulse) engine_run <= 1'b1;
// There is NO cmd_start flop holding the written value: nothing to read back.
// (If a flop is used, it must self-clear: set on write, clear next cycle.)
// --- Pattern B: write-only data / secure key port --------------------------
// The written word is the payload; it flows into hardware and is never exposed.
logic key_push;
assign key_push = wr_en_keyin; // each write pushes one word into the key store
// secure_key_load(.data(pwdata), .push(key_push)); // value consumed, not stored for read-back
// --- Read policy: WO addresses return a DEFINED 0 (this map's choice) -------
always_comb unique case (paddr[7:0])
CMD: prdata = 32'h0; // WO strobe: read is policy-defined 0, not the last write
KEYIN: prdata = 32'h0; // WO key: secret never read back — defined 0
default: prdata = 32'h0;
endcase
// (Strict alternative: assert pslverr for a read to a WO-only offset instead of returning 0.)Two facts make this design and not just coding. First, the action is gated on a one-cycle pulse, never on a held bit — start_pulse = wr_en_cmd & pwdata[0] is true for exactly the single write cycle, so the action fires once per write and the START bit has no stored value to read. Second, the read policy is an explicit contract choice: this map returns a defined 0 for every WO offset (predictable, RMW-safe); a stricter map would raise PSLVERR on a read of a write-only address. Either is correct — what is not correct is leaving it undefined, because a driver is written against whatever a read promises.
5. Engineering tradeoffs
WO design is a set of contract decisions — which pattern, what the value means, and what a read returns.
| WO pattern | Write effect | Read policy | Use case |
|---|---|---|---|
| Command strobe (value selects action) | Write a code/bit triggers a specific action; whole-word value chooses which | Defined 0 (or PSLVERR strict) | Multi-command port: START/ABORT/FLUSH encoded in one register |
| Self-clearing go-bit | Write 1 to one bit fires a one-cycle action pulse; bit auto-clears | Defined 0 (bit always reads back 0) | Single trigger: GO, SOFT_RESET, kick-off an engine |
| Write-only data port (FIFO push) | Each write pushes one word into hardware (FIFO/stream); value is the payload | Defined 0 (or PSLVERR) — data not mirrored | Streaming data into a block where read-back is meaningless |
| Write-only key / secure port | Write loads a secret (key, password) into hardware; value absorbed | Defined 0 (often PSLVERR on read) — secret never exposed | Security: a key register that must never read back what was written |
The throughline: choose the pattern by what the write is for — a trigger (strobe/go-bit), a payload (data port), or a secret (key) — then choose the read policy deliberately. Defined 0 is the common, ergonomic default (predictable, lets a driver RMW a shared word safely). PSLVERR is the strict choice when reading a write-only address is genuinely a software error worth catching — most justified for secure key ports, where a silent 0 could mask a driver that wrongly tries to read a secret. And whichever you choose, a trigger bit must self-clear so it is a strobe, not a level.
6. Common RTL mistakes
7. Debugging scenario
The signature WO bug is a command strobe that fails to self-clear — a START bit implemented as a normal read-write flop, so the action re-triggers every cycle the bit stays set, or a driver's read-modify-write re-fires a stale command.
-
Observed symptom: writing
STARTonce kicks off the engine, but it never cleanly completes — it restarts continuously, or it re-runs on the next unrelated write to the same register (e.g. setting anABORTbit also re-triggersSTART). A driver that doesread CMD; set a bit; write CMDre-launches a command it never meant to issue. -
Waveform clue: on the bus the
cmd_startbit goes high on the write and stays high — it is held at1across many cycles instead of pulsing for one. The engine's action-enable tracks the level of the bit, so it fires every cycle the bit is1; the action pulse, instead of being a single clean cycle, is a continuous high. A later full-word write that does not clear the bit leaves it set, and the engine re-launches. -
Root cause: the command bit was implemented as a stored, level-held read-write flop, and the action was gated on the bit's level (
if (cmd_start_q) fire;) instead of on a one-cycle write strobe. Nothing self-clears the bit, so it persists; persistence plus level-gating means the action re-triggers. The contract said "self-clearing strobe"; the RTL built a held level. The map's WO/self-clear semantics were not honoured. -
Correct RTL: gate the action on a one-cycle write event, not a held level:
assign start_pulse = wr_en_cmd & pwdata[0];fires for exactly the single ACCESS-phase cycle of the write and only if a1was written, so the action fires once per write. Keep no readable storage for the strobe (read returns the policy0); if a flop is genuinely needed, set it on the write and explicitly clear it the next cycle so it self-clears. A driver must never read-modify-write a WO command register — issue each command with a direct write. -
Verification assertion: prove the strobe is a one-cycle pulse and the action fires once per write:
Azvya Education Pvt. Ltd.VLSI MentorSnippet// The action enable is HIGH for at most one cycle per write (strobe, not level). assert property (@(posedge pclk) disable iff (!presetn) start_pulse |=> !start_pulse); // A WO command read returns the defined policy value (0), never the written data. assert property (@(posedge pclk) disable iff (!presetn) (rd_en_cmd) |-> (prdata == 32'h0)); // Each write of START=1 produces exactly one action fire (no re-trigger). assert property (@(posedge pclk) disable iff (!presetn) (wr_en_cmd && pwdata[0]) |=> $rose(engine_run) || engine_run); -
Debug habit: when "a command runs more than once" or "an unrelated write re-triggers an action," do not chase the engine — check the strobe. Confirm the action is gated on a one-cycle write pulse, not a held bit level, and that the command bit self-clears. A held command bit and level-gating is the canonical WO multiple-trigger bug; the fix is to make the strobe an event, not a state.
8. Verification perspective
A WO register is verified against its write-triggers-effect contract — the strobe is a one-cycle pulse, the read returns the policy value, the action fires once per write, and a write-only data/key port is genuinely not readable.
- Strobe is exactly one cycle. Assert that any internal action-enable derived from a WO command bit is high for at most one cycle per write (
start_pulse |=> !start_pulse). This is the structural check that the bit is a strobe, not a held level — it catches the self-clear bug directly, before it manifests as a re-trigger. - Read returns the defined value. Drive a read of every WO offset and assert the result matches the map's declared policy:
prdata == 0for a defined-zero map, or aPSLVERRresponse for a strict map. A per-register read test that simply "doesn't check the value" will pass while the contract is silently violated — verify the specific policy value. - Action fires once per write — and only on a write. Cover and assert that each write of a command triggers exactly one action and that no other event (a read, an unrelated write to the same word) re-fires it. This is where the multiple-trigger bug surfaces: the action must be one-to-one with the write event, not with the bit's level.
- Write-only data/key is not readable. For a key or data port, prove the written value never appears on
prdata— read back after a write and assert the secret is not exposed (returns the policy value). For a secure key, include negative coverage: a read of the key offset is exercised and confirmed to return0/PSLVERR, never the loaded key.
The point: verify WO as an event contract — one-cycle strobe, policy-defined read, one action per write, no read-back of write-only data — so the "write triggers an effect, the read is not a mirror" promise is provably what the silicon does.
9. Interview discussion
"What is a write-only register, and how would you implement a START command bit?" is a sharp question because a weak answer says "a register you can only write," while a strong one frames WO as a write-triggers-effect contract with a deliberately chosen read policy and a self-clearing strobe.
Lead with the inversion: a write-only register is a doorbell, not a mailbox — software writes it to cause an effect, and the read does not return the written data. Name the two patterns: a command/trigger strobe (writing a value or a 1 to a bit kicks off an action — start, abort, soft-reset — and the value may or may not matter) and a write-only data/key port (a FIFO push or a secure key where the written data flows into hardware and cannot be read back). Then deliver the depth that signals real experience: a command bit must be a self-clearing strobe — gate the action on a one-cycle write pulse, not a held bit level, or the action re-triggers every cycle the bit stays set (the classic multiple-trigger bug); the read policy is an explicit contract choice — a defined 0 (ergonomic, RMW-safe) or PSLVERR (strict, common for secure key ports), never undefined; and WO is not "RW you don't read" — drivers must not read-modify-write a WO command register or they re-fire stale commands. Closing with "the write is an event, the read is policy — and a trigger bit self-clears so it is a strobe, not a state" shows you understand why WO is its own access type.
10. Practice
- Spell the WO contract. For a
STARTcommand bit, state the write effect, whether the written value matters, the self-clear behaviour, and exactly what a read returns. - Pick the read policy. For (a) a
GOstrobe and (b) a secure key port, choose defined-0versusPSLVERRand justify each from the contract's point of view. - Design a multi-command port. Lay out a single WO
CMDregister that encodesSTART,ABORT, andFLUSH, and decide whether each is a self-clearing bit or a written code. - Make a strobe self-clear. Given a
STARTflop held at its written level, rewrite the action so it fires from a one-cycle write pulse and the bit reads back0. - Find the re-trigger bug. Given an engine that re-runs on an unrelated write to the command register, identify the level-gating/self-clear defect and the fix.
11. Q&A
12. Key takeaways
- A write-only register is a write-triggers-effect contract — software writes to cause an effect, and the read does not return the written data. The read is not a mirror of the write; that inversion is what makes WO its own access type.
- Two canonical patterns: a command/trigger strobe (writing a value or a
1to a bit kicks off start/abort/soft-reset; the value may or may not matter) and a write-only data/key port (a FIFO push or a secure key whose written data flows into hardware and cannot be read back). - A trigger bit must self-clear — gate the action on a one-cycle write pulse, never a held bit level. A self-clearing strobe fires once per write and reads back
0; a level-held bit re-triggers and is the classic multiple-trigger bug. - The read policy is an explicit contract choice — a defined
0(ergonomic, RMW-safe, common) orPSLVERR(strict, justified for secure key ports), never undefined. A driver is written against whatever a read promises. - WO is not "RW you don't read" — the read semantics differ deliberately, and drivers must not read-modify-write a WO command register or they re-fire stale commands.
- Verify the event contract — strobe is one cycle, read returns the policy value, the action fires once per write, and a write-only data/key port is provably not readable.