Skip to content

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 START or ABORT strobe), writing a 1 to 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 sees 0. 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), or PSLVERR (strict — reading a write-only address is a software error worth flagging). The choice is part of the contract a driver is written against.
An eight-cycle timing diagram: an APB write of 1 to START in cycle T2 produces a one-cycle start_pulse and self-clears the cmd_start bit, the engine runs for several cycles (engine_busy high), and an APB read of CMD in cycle T6 returns PRDATA = 0 rather than the written value 1.
Figure 1 — a write-only command-strobe register over eight PCLK cycles. In T2 software writes 1 to the START bit of CMD (PWDATA = 1). The decode produces start_pulse, an action enable HIGH for exactly one cycle (amber), which kicks off the hardware action — engine_busy goes high (green) and the engine runs. The internal cmd_start flop is set by the write but self-clears the next cycle, so it never holds the value 1. In T6 software reads CMD and PRDATA returns a defined 0 (green), not the 1 that was written — the read is not a mirror of the write. The figure labels the write event, the one-cycle action pulse, the self-clear of the START bit, and the read-returns-zero policy, framing the WO contract: a write triggers an effect, the strobe self-clears, and the read is policy-defined.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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 bitstart_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 patternWrite effectRead policyUse case
Command strobe (value selects action)Write a code/bit triggers a specific action; whole-word value chooses whichDefined 0 (or PSLVERR strict)Multi-command port: START/ABORT/FLUSH encoded in one register
Self-clearing go-bitWrite 1 to one bit fires a one-cycle action pulse; bit auto-clearsDefined 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 payloadDefined 0 (or PSLVERR) — data not mirroredStreaming data into a block where read-back is meaningless
Write-only key / secure portWrite loads a secret (key, password) into hardware; value absorbedDefined 0 (often PSLVERR on read) — secret never exposedSecurity: 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 START once 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 an ABORT bit also re-triggers START). A driver that does read CMD; set a bit; write CMD re-launches a command it never meant to issue.

  • Waveform clue: on the bus the cmd_start bit goes high on the write and stays high — it is held at 1 across 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 is 1; 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 a 1 was written, so the action fires once per write. Keep no readable storage for the strobe (read returns the policy 0); 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 Mentor
    Snippet
    // 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.

Two timing cases: the top buggy case shows a START bit held stuck at 1 so the action fires on every cycle in red; the bottom correct case shows a self-clearing strobe that pulses for one cycle, auto-clears, and fires the action exactly once in green.
Figure 2 — a strobe self-clear bug. Top (bug, red): START is a plain read-write flop, so the write sets cmd_start and the bit stays STUCK at 1; because the action enable is gated on the held level, the action fires every cycle the bit is high — one intended start becomes a continuous re-trigger, and any later write that does not clear START re-runs it. Bottom (correct, green): START is a self-clearing strobe — the write produces a one-cycle action pulse, the bit auto-clears the next cycle (read returns 0), and the action fires exactly once per write. The figure shows the fix is to derive the action enable from a one-cycle write strobe and self-clear the bit, never to gate the action on a held read-write bit level.

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 == 0 for a defined-zero map, or a PSLVERR response 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 return 0/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

  1. Spell the WO contract. For a START command bit, state the write effect, whether the written value matters, the self-clear behaviour, and exactly what a read returns.
  2. Pick the read policy. For (a) a GO strobe and (b) a secure key port, choose defined-0 versus PSLVERR and justify each from the contract's point of view.
  3. Design a multi-command port. Lay out a single WO CMD register that encodes START, ABORT, and FLUSH, and decide whether each is a self-clearing bit or a written code.
  4. Make a strobe self-clear. Given a START flop held at its written level, rewrite the action so it fires from a one-cycle write pulse and the bit reads back 0.
  5. 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 1 to 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) or PSLVERR (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.