Skip to content

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 (PSEL high): a subordinate is targeted; this write is aimed somewhere real.
  • In access (PENABLE high): the bus is performing the access, not just presenting the address.
  • A write (PWRITE high): the direction is write, so a capture is the right action.
  • Ready (PREADY high): 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.
  • PWRITE is part of the commit term, not a side note. The general completion edge is PSEL & PENABLE & PREADY. The write commit adds PWRITEpsel & penable & pwrite & pready — so a read completing on the same bus never pulses a register write-enable. Drop PWRITE and 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 PWDATA is held all the way to the commit. The commit edge and the PWDATA hold 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 PREADY is 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. PENABLE high (the gate live) is not the trigger. The access phase includes every wait cycle. The commit waits for PREADY — 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 PWDATA as it stands on the commit edge. If the manager let go of PWDATA early, 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.
A figure showing four condition boxes — PSEL, PENABLE, PWRITE, PREADY — feeding an AND gate whose output is the register write-enable that captures PWDATA, above a three-state timeline: before completion nothing commits and the register holds its old value, at completion the enable pulses once and the register captures the new value, and after completion the bus moves on.
Figure 1 — the write completion contract as a structure. Four conditions — PSEL high (a subordinate is selected), PENABLE high (the access phase is active), PWRITE high (the transfer is a write), and PREADY high (the subordinate completes now) — are AND-ed into a single register write-enable, write_commit equals PSEL and PENABLE and PWRITE and PREADY. That enable is the only path that drives the addressed register's capture of PWDATA, with the PADDR decode already settled because the address is held stable through access. The timeline below shows the three states the contract defines: before completion the enable is low and the register holds its old value (nothing commits); at completion the enable pulses exactly once and the register captures the new value; after completion the enable is low again and the bus advances to the next transfer. The figure stresses that the commit requires all four conditions true together on one edge, happens exactly once, and is gated so that before it nothing is stored and after it the bus moves on.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.
end

Two 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.)

An APB write timing diagram with one wait state and rows PCLK, PSEL, PENABLE, PWRITE, PADDR, PWDATA, PREADY, write-enable, register: PSEL/PENABLE/PWRITE high across access, PADDR and PWDATA held stable, PREADY low on the first access cycle and high on the second, the write-enable pulsing once on that second edge where the register transitions from old value to new value at the dashed commit marker.
Figure 2 — the commit edge pinned by a wait state, walked IDLE to IDLE against PCLK. PSEL, PENABLE, and PWRITE are high across the whole access. PADDR and PWDATA are held stable as one continuous band from setup through completion. On the first access cycle PREADY is low — a wait — so the register write-enable stays low and the register still holds its old value: nothing is stored, the value is only offered. On the second access cycle PREADY rises, so PSEL, PENABLE, PWRITE, and PREADY are all high: the commit edge, marked with a dashed line, where the write-enable pulses exactly once and the register transitions old to new. The figure shows the contract under a wait: the commit is not the PENABLE rise and not the wait cycle, but the single PREADY-high access edge, where the register changes once and only once.

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 propertyWhat it gives upWhat it buysWhy it is correct for APB
Commit on PSEL & PENABLE & PWRITE & PREADYA simpler "one signal = commit"An unambiguous single state-change edgeManager and subordinate agree on when the write landed
PWRITE in the commit termA shared read/write commit pathReads can never enable a register writeDisjoint paths — a read at the same address is harmless
Exactly-once across waitsA simpler always-on captureNo double-commit, no early commitA destructive update must fire once, deterministically
Commit gated on PREADY, not PENABLEEarliest-possible captureCapture only when the subordinate acceptsThe access can take any number of cycles
Value = PWDATA held to the edgeJust-in-time data deliveryThe captured value is the intended oneOne 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

Two stacked APB write waveforms with one wait state and identical PSEL/PENABLE/PWRITE/PREADY: the top correct case pulses the register write-enable once on the PREADY-high completion edge and captures the correct value; the bottom buggy case, in red, pulses the write-enable early on the wait cycle when PENABLE rose and PREADY was low, captures a wrong value, and pulses again at completion as a double-commit.
Figure 3 — the wrong-edge commit bug under one wait state. Both rows are the same write with identical PSEL, PENABLE, PWRITE, and PREADY. Top (correct, green): the write-enable is gated on the full commit, PSEL and PENABLE and PWRITE and PREADY, so it pulses exactly once on the completion edge where PREADY is high, and the register captures the intended new value. Bottom (bug, red): the write-enable is gated on PENABLE alone, so it fires on the first access cycle when PENABLE rises while PREADY is still low — a wait — committing an early, wrong value, and because PENABLE stays high it fires again on the completion cycle, a double commit. The figure shows that with one wait state a PENABLE-gated commit lands on the wrong edge and twice, storing a wrong value, which is why the commit must be gated on the full PREADY-high contract.

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 one write_commit pulse per PSEL access.
  • Model and scoreboard the commit. On each write_commit the monitor sees, the reference model does model_reg[paddr] = pwdata; on each read completion the scoreboard compares PRDATA to model_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 PWDATA changes 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 the PWRITE term keeps the paths disjoint). Cover write_commit && paddr per 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

  1. Write the contract. From memory, write write_commit as a four-term expression and the always_ff that commits PWDATA into one register, and explain in one sentence why this single gated path makes the commit exact.
  2. 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.
  3. Find the wrong-edge bug. A subordinate gates its commit on PENABLE alone. On a one-wait write, show which cycle it wrongly writes, what value it captures, and why the same RTL passes at zero wait.
  4. Defend PWRITE. Explain what goes wrong if the commit term is PSEL & PENABLE & PREADY (no PWRITE) when a read completes at the same address as a recently written register.
  5. 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, and PREADY all high on the same PCLK rising 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 on PENABLE.
  • PWRITE keeps the paths disjoint. Including PWRITE in 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, PWDATA is 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 PWDATA to the commit; the commit edge and the PWDATA hold 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.