Skip to content
VLSI Mentor

Wishbone · Module 13

Partial Writes

A masked write has two terms and one is invisible when missing. Measured: 0xA1B2C3D4 becoming 0x00000044 through a design whose mask is entirely correct.

Chapter 13.2 built byte enables two ways and both worked. Both were written by someone who already knew the answer.

What does a partial write have to preserve, and what does a design look like that preserves nothing?

1. Preservation, Not Restoration

The unselected lanes are never disturbed. That sounds like a detail of wording and it is the difference between two architectures.

A design that preserves never writes the unselected lanes at all. There is no window in which they hold something else.

A design that restores would read the old word, combine, and write back — which is a read-modify-write, a genuine bus operation with its own cycle type, its own atomicity question, and its own chapter. Module 15 builds it.

They are not the same thing and they do not cost the same. A partial write is one transfer; the preservation happens inside the target, in the same clock, because the target already holds the old value. A read-modify-write is two transfers with the old value travelling across the bus and back.

The confusion matters because it changes what you look for when debugging. If a partial write is losing data, the fault is inside one target. If a read-modify-write is losing data, the fault may be another master writing in the window between the two halves — a completely different investigation, and one that does not apply here at all.

2. RTL — The Term Removed

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── THE DEFECT — mask the incoming data and forget the old word ─────────
// wb_broken_mask_reg. NOT A REFERENCE DESIGN.
//
//     q <= dat_i & mask;
//
// The reasoning that produces it is half-right, which is why it survives a
// reading: "only the selected lanes are valid, so mask the data." Masking
// the data IS correct — it is the (dat & mask) term. What is missing is the
// other term, and its absence does not look like an absence.
//
// The result is that every unselected lane is written with ZERO. The
// transfer does not merely fail to update them; it destroys them. Chapter
// 13.3 measures exactly what is lost.
module wb_broken_mask_reg #(
  parameter int unsigned DW    = 32,
  parameter int unsigned GRAN  = 8,
  parameter logic [31:0] RESET = 32'hA1B2_C3D4,
  localparam int unsigned SELW = DW / GRAN
) (
  input  logic            clk_i,
  input  logic            rst_i,
  input  logic            we_i,
  input  logic [SELW-1:0] sel_i,
  input  logic [DW-1:0]   dat_i,
  output logic [DW-1:0]   q_o
);
  logic [DW-1:0] q, mask;
  wb_sel_to_mask #(.DW(DW), .GRAN(GRAN)) u_mask (.sel_i(sel_i), .mask_o(mask));

  always_ff @(posedge clk_i) begin
    if (rst_i)      q <= DW'(RESET);
    // ── THE DEFECT: no (q & ~mask) term. ──
    else if (we_i)  q <= dat_i & mask;
  end
  assign q_o = q;
endmodule

Reading it

It differs from Chapter 13.2's style B by one term. Same mask expander, same commit condition, same reset value. q <= dat_i & mask instead of q <= (q & ~mask) | (dat_i & mask).

Its mask is entirely correct. It instantiates the same wb_sel_to_mask, which Chapter 13.1 audited exhaustively. Every diagnostic aimed at the select path exonerates this design, which is what makes it durable.

And the reasoning that produces it is half true. "Only the selected lanes are valid, so mask the data" is correct — that is the (dat & mask) term. What is missing is a statement about the lanes the sentence never mentioned.

3. Simulation — SIM C: With and Without the Preserve Term

Three registers, identical stimulus. Per-lane enables, the masked merge, and the merge with (old & ~mask) deleted. Each resets to 0xA1B2C3D4 before every row.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM C - the preserve term, present and absent ===
    identical stimulus to three registers: per-lane enables,
    expanded-mask merge, and the merge missing (old & ~mask).

    SEL    lane-enable  mask-merge   dat & mask   lost
    0001   0xa1b2c344   0xa1b2c344   0x00000044   0xa1b2c300
    0011   0xa1b23344   0xa1b23344   0x00003344   0xa1b20000
    0101   0xa122c344   0xa122c344   0x00220044   0xa100c300
    1000   0x11b2c3d4   0x11b2c3d4   0x11000000   0x00b2c3d4
    1111   0x11223344   0x11223344   0x11223344   0x00000000

    The two correct columns are identical on every row.
    The third zeroes every lane it was not given, and the
    'lost' column is exactly the state it destroyed.
    On SEL=1111 all three agree - a full-word write has no
    unselected lanes, so the missing term has nothing to do.

Reading it

Row 1 is the headline. A one-byte write to a register holding 0xA1B2C3D4:

result
per-lane enables0xa1b2c344
masked merge0xa1b2c344
dat & mask0x00000044

Three bytes destroyed by a write that was only ever about one. The lost column, 0xa1b2c300, is exactly the state that disappeared.

The two correct columns are identical on every row, which is the equivalence Chapter 13.2 claimed, measured rather than asserted.

Row 3, SEL = 0101, is where the damage is least intuitive. Correct gives 0xa122c344; the broken design gives 0x00220044. The surviving bytes are not a prefix or a suffix — they are the selected lanes, scattered, with holes punched between them. A design that assumes a contiguous transfer would not even produce this shape.

4. Simulation — SIM D: All Sixteen Patterns, Lane by Lane

The five rows above are examples. This is the property. Every select pattern, against old = 0xA1B2C3D4 and dat = 0x11223344 — words chosen so that every lane differs between them, so a preserved lane and a replaced lane can never be confused.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM D - every select pattern, checked lane by lane ===
    all 16 patterns against old=0xa1b2c3d4 dat=0x11223344

    lane checks performed              64
    selected lane not updated          0
    unselected lane not preserved      0
    the two styles disagreed           0

    Non-contiguous patterns are in this sweep - 0101, 1010,
    1001 - and behave like every other pattern. A bitmap has
    no notion of contiguity to violate.

Reading it

Sixty-four lane checks — sixteen patterns times four lanes — and three counters at zero.

"selected lane not updated" and "unselected lane not preserved" are separate counters on purpose. A design could satisfy one and fail the other, and they point at different code: the first at the data path, the second at the missing preserve term. Collapsing them into one pass/fail throws away the diagnosis.

"the two styles disagreed" runs the equivalence check on every pattern. Per-lane enables and the masked merge produce bit-identical results sixteen times out of sixteen.

And the sweep includes the patterns nothing else would have chosen. 0101, 1010, 1001, 0110non-contiguous lane sets, which behave exactly like the contiguous ones because a bitmap has no notion of contiguity to violate.

One write, two registers, three bytes

8 cycles
Eight clock cycles of a single one-byte write with select equal to binary 0001. The correct register's three upper byte lanes hold the values a1, b2 and c3 unchanged across the whole figure, while its lowest lane changes from d4 to 44 after the acknowledged edge. The register missing its preserve term has all three upper lanes drop to zero at the same edge, and its lowest lane also becomes 44. The two designs received identical stimulus.SEL=0001: lane 0 onlySEL=0001: lane 0 onlyboth take 0x44 in lane 0both take 0x44 in lane 0bad: three bytes gone, no errorbad: three bytes gone, noerrorCLK_ISEL_O01111111CYC_O/STB_OACK_Iok: 31:8a1b2c3a1b2c3a1b2c3a1b2c3a1b2c3a1b2c3a1b2c3a1b2c3ok: 7:0d4d4d4d444444444bad: 31:8a1b2c3a1b2c3a1b2c3a1b2c3000000000000000000000000bad: 7:0d4d4d4d444444444t0t1t2t3t4t5t6t7

The two lowest rows are identical. Both designs write the selected lane correctly, and a read of that byte returns the right value from either. Nothing about the selected lane distinguishes them.

The two upper rows are the entire difference, and it happens in one clock with no termination, no error and no indication of any kind. The transfer was acknowledged normally.

5. A Bitmap Is Not a Size

This is the misconception the sweep exists to destroy, and it is worth naming precisely.

SEL is described as a bitmap. The signal description: "Each individual select signal correlates to one of eight active bytes on the 64-bit data port." One bit, one lane. Four bits give sixteen patterns.

A size encoding would be a different abstraction. Two bits encoding "byte / halfword / word" gives four values and needs a separate offset to say where. It cannot express 0101 at all, and it cannot express 0000.

select bitmapsize encoding
values for a 4-lane port163 or 4
expresses 0101yesno
expresses "no lanes"yesno
needs a separate offsetnoyes

On legality, the honest statement is narrow. The specification does not forbid non-contiguous patterns — no rule enumerates permitted ones. That is permission by absence, not explicit blessing, and it is worth saying as the weaker claim.

What is a real question is whether a given target supports them. The specification's operand nomenclature covers BYTE, WORD, DWORD and QWORD; a scattered lane set is not one of those operands. Whether a core handles one is a datasheet matter under RULE 2.15's maximum-operand-size requirement — and a core built as a per-lane bitmap, like every register in this module, handles them without ever being asked to.

The design consequence is what makes this practical. A target implemented per-lane gets all sixteen patterns for free. A target implemented as a size decoder has a hole in its behaviour that no contiguous test will find.

6. Bus Lanes and Register Meaning Are Different Layers

SEL decides which lanes participate. It does not decide what participating means.

For an ordinary read/write register they are nearly the same thing — a participating lane takes the data — which is why this chapter can treat them as one until now.

They separate as soon as the register has its own rule. A write-1-clear status register, a command port, a FIFO write port: for each, "this lane took part" is the input to a rule, not the whole of it.

register kinda participating lane means
ordinary RWtake the written value
write-1-clearclear the bits that were written as 1
commandfire the side effect
FIFO portenqueue the delivered bytes

The order is always the same: mask first, register rule second. A lane that did not participate was not written at all, so the register's rule has nothing to apply to it.

Chapter 13.5 measures all three, including a command register that fires from a lane it was never given. This chapter stops here — the distinction is the point, and the register bank that depends on it is a chapter away. Designing register semantics themselves belongs to Module 24.

7. Failure Modes and Discriminating Evidence

Symptom: a byte write clears the rest of the register.

Candidate causes. The merge missing its preserve term — dat & mask with no old & ~mask.

Discriminating evidence. Whether the unselected lanes became zero or took DAT's values. Zero means the preserve term is missing. DAT's values means SEL is ignored entirely. The two look similar in a bug report and have different fixes, and one byte-write from a non-zero register separates them.

Likely RTL location: the register's update expression, not the mask.

Symptom: whole-word writes work and byte writes corrupt data.

Candidate causes. Any preservation defect at all.

Discriminating evidence. That the symptom is conditional on SEL != all-ones is itself the diagnosis. SIM C's last row shows why: with every lane selected there is nothing to preserve, so the defect is dormant. Look at the update expression, not at the transfers that work.

Symptom: a scattered select pattern behaves differently from a contiguous one.

Candidate causes. A target that decodes SEL as a size and an offset rather than as a bitmap.

Discriminating evidence. Compare 0011 against 0101. Both select two lanes. A bitmap treats them alike; a size decoder handles the first and mishandles the second. SIM D's sweep is this test generalised.

Symptom: data loss appears in a variable nothing wrote to.

Candidate causes. A neighbouring byte in the same word destroyed by someone else's partial write.

Discriminating evidence. Whether the lost bytes share a word with a byte that was legitimately written. If so, the corruption is a partial-write defect in whatever owns that word. The routine that appears to be at fault is the victim, and the one at fault reported success.

8. Verification

The properties in Chapter 13.1 Section 10 cover this chapter without change. P2 says a selected lane takes the data; P3 says an unselected lane is unchanged, and P3 is precisely what wb_broken_mask_reg violates on every write where SEL is not all-ones.

P3 is the reason the property is written per lane. A word-wide statement — "the result equals the merge" — would also catch this defect, but it would not say which lanes were wrong, and the per-lane form fails once for every destroyed byte.

SIM D is those two properties in executable form. Icarus cannot run the assertions; it can run sixty-four lane comparisons, and the counters are separated along the same lines the properties are.

9. Common Mistakes

"Partial write means zero the other bytes."

Wrong mental model: the transfer defines the whole word.

What is true: the transfer defines the lanes it selected and says nothing about the others. SIM C measures 0xA1B2C3D4 becoming 0x00000044 in a design that believes otherwise.

"DAT & mask implements a masked write."

Wrong mental model: masking the data is the operation.

What is true: it is one of two terms. Masking the data is right; the missing half is old & ~mask. The expression looks complete because nothing in it is wrong — what is wrong is what is absent.

"Only contiguous select patterns matter."

Wrong mental model: real software only does byte, halfword and word accesses.

What is true: a bitmap has sixteen patterns and a per-lane design handles all of them identically. A target that special-cases contiguity has untested behaviour; SIM D covers the whole space for the cost of a loop.

"A partial write is a read-modify-write."

Wrong mental model: preservation requires reading.

What is true: the target already holds the old value — no read crosses the bus. A partial write is one transfer. Module 15's read-modify-write is a different operation with different atomicity concerns, and conflating them sends a debugging session to the wrong place entirely.

"If writes read back correctly, the register is fine."

Wrong mental model: read-back is a complete test.

What is true: a broken register reads back perfectly for whole-word writes. SIM C's last row is the proof. The test that finds it needs a partial write from a non-zero state, which is two conditions that a naive test satisfies neither of.

10. Interview Reasoning

Start from where each bit's new value comes from.

Every bit of the register is in exactly one of two sets after the write. If its lane was selected, the new value comes from the write data. If not, it comes from the register itself. Those sets are complementary, which is what makes a mask the right tool.

So with MASK = expand(SEL):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  NEW = (old & ~MASK) | (dat & MASK)

The first term keeps the bits the transfer did not claim; the second supplies the ones it did.

Say why the | is safe. MASK and ~MASK partition the word, so the two terms are disjoint — no bit gets a contribution from both. This is a concatenation, not a combination, which is exactly what made Chapter 12.4's ORed response mux unsafe by contrast: there the terms overlapped.

And name the failure. Dropping the first term gives dat & MASK, which zeroes every unselected lane. Measured: 0xA1B2C3D4 becoming 0x00000044 on a one-byte write.

11. Understanding Check

Because SEL = 1111 selects every lane, so there is nothing to preserve.

The broken design's defect is that it zeroes unselected lanes. With all four selected there are no unselected lanes — the missing (old & ~mask) term would contribute nothing even if it were there, because ~mask is all zeros.

So the three results are identical, and the register looks correct.

This is why the row is published rather than trimmed. It shows the exact conditions under which the defect hides, and those conditions are the common case during bring-up. A test suite made of whole-word writes gives a clean pass on a register that destroys data.

12. What's Next

The equation is derived, the missing term is measured, and the property holds across the entire select space.

Every select pattern so far arrived ready-made. Software does not have select patterns — it has pointers.

Where does a select pattern come from, and what happens when the bytes software wants do not fit in one word?

Chapter 13.4 — Alignment translates byte addresses into ADR plus SEL, measures what changes when the endianness does, and classifies every offset and size the teaching configuration allows. The full path is on the Wishbone curriculum index.

Continue learning

Standards & specifications

Governing standard
Wishbone SoC Interconnection Architecture (OpenCores)(opens OpenCores in a new tab)

Defines the Wishbone signal set, the bus cycles built from it and the interface rules a portable IP core must follow. It deliberately leaves interconnect topology, address map and arbitration policy to the integrator, so those are system decisions rather than requirements of the specification.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the Wishbone curriculum.