AMBA APB · Module 7
Write Completion Contract
The write completion contract — the register commits on exactly the single edge PSEL and PENABLE and PWRITE and PREADY all high, exactly once, never on a wait cycle, and the silent corruption a misgated write-enable causes.
A write is a one-way push, and the whole push hangs on one instant: the cycle the value actually lands in the register. This chapter pins that instant down with a contract. The single idea to carry: an APB write commits on exactly the edge where PSEL, PENABLE, PWRITE, and PREADY are all high — before that edge nothing is stored, on that edge the register updates, and it updates exactly once, never on a wait cycle and never twice. That is the write completion contract. Get it exact and every write-timing subtlety — wait states, back-to-back transfers, stale data — reduces to "did the commit land on the right edge, exactly once?"
1. Problem statement
The problem is defining the one cycle on which a write's value is committed into the subordinate's register — unambiguously, and exactly once — so that the manager and the subordinate agree on when the state change happened and the register never updates early, late, or twice.
A write spans several cycles. The address and data are presented in setup, the enable rises in access, and the subordinate may stretch the access with wait states. Across all of that, PWDATA is sitting on the bus being offered. But "offered" is not "stored." There has to be one cycle — and only one — on which the offered value is captured. The contract names it:
- Selected (
PSELhigh): a subordinate is targeted; this write is aimed somewhere real. - In access (
PENABLEhigh): the bus is performing the access, not just presenting the address. - A write (
PWRITEhigh): the direction is write, so a capture is the right action. - Ready (
PREADYhigh): the subordinate declares it accepts the value now; the access finishes this cycle.
All four high on the same rising edge is the commit. Not three of them, not "the access started," not "the enable rose" — the full conjunction, on one edge. That edge is where the register's write-enable pulses, and the contract guarantees it pulses there once and nowhere else.
2. Why previous knowledge is insufficient
You already have the general completion rule. Module 4's transfer-completion-rules taught the unifying contract — a transfer completes on PSEL & PENABLE & PREADY, and a write commit, read validity, and PSLVERR sampling all fire there. That chapter was deliberately direction-agnostic: it gave you the completion edge and listed the write commit as one of four things that happen on it. Correct, but a write engineer needs the commit drilled all the way down, because the general rule hides three write-specific guarantees:
- The commit is a state change, and it must happen exactly once. Read-data validity is a sampling event — sample twice, you read the same value, no harm. A write commit is destructive: it overwrites the register. "Fires on the right edge" is not enough; it must fire on the right edge and not also on a wait cycle. Exactly-once is a write-only requirement, and it is the part of the contract a wait state stresses hardest.
PWRITEis part of the commit term, not a side note. The general completion edge isPSEL & PENABLE & PREADY. The write commit addsPWRITE—psel & penable & pwrite & pready— so a read completing on the same bus never pulses a register write-enable. DropPWRITEand a read at the same address can corrupt the register.- The value committed depends on stability until the commit. Because you cannot predict which access cycle completes (a wait state moves the commit edge later), the captured value is only correct if
PWDATAis held all the way to the commit. The commit edge and thePWDATAhold rule are two halves of one guarantee: the right edge capturing the right value.
So what this chapter adds is not a new edge — it is the exactness of the write commit: the full four-term contract, committed once, on the PREADY-high access edge, capturing a value held until then.
3. Mental model
The model: the commit is a turnstile that admits exactly one person and only when the light is green. The value queues up at the turnstile from setup onward, but it does not pass through until the green light (PREADY high in the access phase) — and the turnstile clicks exactly once, admitting one value, never letting two through and never admitting anyone while the light is red (a wait).
The four conditions are the turnstile's interlock: someone is at this turnstile (PSEL), the gate is live (PENABLE), they are going in not out (PWRITE), and the light is green (PREADY). Only with all four does the arm release. Before that, the person stands and waits — PWDATA is offered, nothing stored. On the green, one click, one entry: the register captures the value. Red again afterward: the arm locks, the bus moves on.
Three refinements make the contract precise:
- One click per write. The write-enable pulses on exactly one cycle — the first (and only) access cycle where
PREADYis high. A wait state does not add a second click; it just delays the one click. Exactly-once is the heart of the contract. - The light, not the gate, releases the arm.
PENABLEhigh (the gate live) is not the trigger. The access phase includes every wait cycle. The commit waits forPREADY— the green light — inside that phase. Triggering on the gate alone admits someone while the light is still red. - The value is whatever is held at the click. The register captures
PWDATAas it stands on the commit edge. If the manager let go ofPWDATAearly, the turnstile admits whatever is there now — garbage. The commit edge is exact; the value is only correct if it was held to that edge.
4. Real SoC implementation
In silicon the write completion contract is one gating wire and one register write path. The subordinate forms write_commit from the four conditions and lets only that wire enable the register flops. Because there is no second path that writes those flops, the contract — commit on the one edge, exactly once — is structural, not a runtime promise.
// The write completion contract in RTL: the register commits on EXACTLY one edge.
// write_commit is high only on the access cycle where the subordinate is ready
// AND it is a write — PSEL, PENABLE, PWRITE, PREADY all high. PADDR/PWDATA are
// held stable through access, so the decode is settled and the value is correct.
wire write_commit = psel & penable & pwrite & pready; // the one commit edge
always_ff @(posedge pclk or negedge presetn) begin
if (!presetn) begin
ctrl_q <= '0;
baud_q <= '0;
end else if (write_commit) begin // the ONLY path that writes these flops
case (paddr)
ADDR_CTRL: ctrl_q <= pwdata; // commit here, and only here — never on a wait
ADDR_BAUD: baud_q <= pwdata;
default: /* unmapped write: no side effect */ ;
endcase
end
// Because write_commit is the sole enable, the register cannot change on the
// PENABLE rise, on a wait cycle, or twice. This single gated path IS what makes
// the commit exact: once, only here, never on a wait.
endTwo facts make the contract hold under stress. First, write_commit is low on every setup cycle (PENABLE low) and every wait cycle (PREADY low), so the enable pulses exactly once — on the completion edge — no matter how many wait states the subordinate inserts. Exactly-once is handled for free by the gating term. Second, PWRITE is part of the term, so a read completing at the same address never enables these flops — the read and write paths are disjoint by construction. (The other halves of the write timing are the rest of this module: the PWDATA hold rule that guarantees the captured value, and the overall write flow this commit sits inside.)
5. Engineering tradeoffs
Pinning the write commit to one four-term edge is a deliberate, minimal choice. Each property trades a capability APB does not need for the exactness it does.
| Commit-contract property | What it gives up | What it buys | Why it is correct for APB |
|---|---|---|---|
Commit on PSEL & PENABLE & PWRITE & PREADY | A simpler "one signal = commit" | An unambiguous single state-change edge | Manager and subordinate agree on when the write landed |
PWRITE in the commit term | A shared read/write commit path | Reads can never enable a register write | Disjoint paths — a read at the same address is harmless |
| Exactly-once across waits | A simpler always-on capture | No double-commit, no early commit | A destructive update must fire once, deterministically |
Commit gated on PREADY, not PENABLE | Earliest-possible capture | Capture only when the subordinate accepts | The access can take any number of cycles |
Value = PWDATA held to the edge | Just-in-time data delivery | The captured value is the intended one | One capture rule works at any subordinate speed |
The throughline: the contract spends a four-term AND and gains a write that commits the right value, on one agreed edge, exactly once — independent of how long the subordinate stalls. The commit edge is the single state-change landmark, which is why an APB write is small to build and trivial to verify: you assert one thing fires once.
6. Common RTL mistakes
7. Debugging scenario
8. Verification perspective
A write commit is a destructive, invisible state change, so verification must check two distinct properties that the bus alone cannot show: the register changes only on a commit, and it commits exactly once per transfer. The standard structure is a reference model plus a scoreboard plus two commit assertions.
- Assert commit-only. The register must never change except on a commit:
property p_commit_only; @(posedge pclk) disable iff (!presetn) $changed(ctrl_q) |-> (write_commit && paddr == ADDR_CTRL); endproperty. This fails the instant a stray write path or a misgated enable touches the register off the contract — catching the wrong-edge bug above directly. - Assert exactly-once. Across a single selected access, the commit term must be true on at most one cycle. A practical form: when a transfer is in access but not yet ready (
psel && penable && !pready), the commit must not be high —assert property (@(posedge pclk) (psel && penable && !pready) |-> !write_commit);— which forbids the early/double pulse on a wait cycle. Pair it with a check that a completed write produces exactly onewrite_commitpulse perPSELaccess. - Model and scoreboard the commit. On each
write_committhe monitor sees, the reference model doesmodel_reg[paddr] = pwdata; on each read completion the scoreboard comparesPRDATAtomodel_reg[paddr]. A wrong-edge or double commit shows up as a read mismatch, and the assertions localise it to the exact cycle. - Cover the corners that hide commit bugs. Functional coverage must hit: a write with wait states (the case that exposes wrong-edge gating), a write whose
PWDATAchanges between the address phase and the commit edge (to catch capture-of-stale-data), back-to-back writes to the same register, and a read immediately following a write to the same address (to confirm thePWRITEterm keeps the paths disjoint). Coverwrite_commit && paddrper register so "did we ever commit this one" is provable.
The point: at zero wait the commit edge coincides with the PENABLE rise, so a wrong-edge bug is unobservable. Only a wait-state stimulus plus the commit-only and exactly-once assertions prove the contract holds — the scoreboard alone, run at zero wait, would pass a broken design.
9. Interview discussion
"When exactly does an APB write commit?" sounds like a one-line answer and is a depth probe. A weak answer says "when PENABLE is high" or "at the end of the access." A strong answer states the full contract and its two write-specific guarantees.
Lead with the contract: a write commits on the single edge where PSEL, PENABLE, PWRITE, and PREADY are all high — not when PENABLE rises, not on a wait cycle. Then give the two guarantees that separate levels. First, exactly-once: a destructive register update must fire on one cycle and not repeat across wait states, which is why you gate on PREADY, not PENABLE. Second, disjointness: PWRITE is in the term so a read completing at the same address never enables a register write. Close with the failure mode that proves you have debugged real silicon: gating on PENABLE alone works at zero wait and breaks under a wait state, committing early and double — and you catch it with a commit-only assertion plus an exactly-once assertion, not with the scoreboard alone. Volunteering "the commit edge and the PWDATA hold rule are two halves of one guarantee — right edge, right value" signals you see the contract as a system, not a signal.
10. Practice
- Write the contract. From memory, write
write_commitas a four-term expression and thealways_ffthat commitsPWDATAinto one register, and explain in one sentence why this single gated path makes the commit exact. - Pin the edge under a wait. On a write with two wait states, mark the one cycle the register changes, and state what the register holds on every earlier access cycle and why.
- Find the wrong-edge bug. A subordinate gates its commit on
PENABLEalone. On a one-wait write, show which cycle it wrongly writes, what value it captures, and why the same RTL passes at zero wait. - Defend
PWRITE. Explain what goes wrong if the commit term isPSEL & PENABLE & PREADY(noPWRITE) when a read completes at the same address as a recently written register. - Assert exactly-once. Write, in words or SVA, the property that fails if the register changes off a commit, and the property that fails if the commit pulses during a wait cycle.
11. Q&A
12. Key takeaways
- A write commits on exactly one edge:
PSEL,PENABLE,PWRITE, andPREADYall high on the samePCLKrising edge. That four-term conjunction is the whole write completion contract. - The commit fires exactly once per write. A destructive register update must not repeat; a wait state delays the single commit, it does not add a second one. Gate on
PREADY, never onPENABLE. PWRITEkeeps the paths disjoint. IncludingPWRITEin the term ensures a read completing at the same address can never enable a register write and corrupt the value.- Before the commit, nothing is stored; after it, the bus moves on. During setup and every wait cycle,
PWDATAis only offered. The register holds its old value until the one commit edge, then advances. - The captured value is only correct if held to the edge. Because a wait state moves the commit later, the manager must hold
PWDATAto the commit; the commit edge and thePWDATAhold rule are two halves of one guarantee — right edge, right value. - Gating on a subset is the silent silicon bug.
PENABLE-alone gating passes at zero wait and commits early and double under a wait state; catch it with commit-only and no-commit-on-wait assertions plus wait-state coverage, not the scoreboard alone.