Wishbone · Module 13
Data Masking
Mask first, register semantics second. Measured: a command firing from a lane the transfer never delivered, and a status register surviving a word of ones.
Chapter 13.4 finished the translation. A byte address becomes ADR plus SEL, and by now SEL reliably means these lanes participate.
Every register in this module has done the same thing with a participating lane: taken the data. Real peripherals do not all agree about that.
What does "this lane participates" mean to a status register, or to a command port?
1. Where the Mask Stops Being Enough
For an ordinary read/write register, SEL and the register's meaning almost coincide — a participating lane takes the data, which is the whole rule. Chapter 13.3 could treat them as one thing.
They separate the moment the register has an opinion of its own.
A write-1-clear status register does not store what you write. It uses the written bits as a request to clear. So "participates" feeds a rule rather than being it — and a bit clears only if its lane took part and the delivered bit was 1.
A command register does not store anything meaningful. Writing it does something. The question becomes whether the side effect happened at all, which is a per-transfer decision rather than a per-bit one.
This is a layering statement, and it holds in both directions:
| layer | decided by | Wishbone's involvement |
|---|---|---|
| which lanes participate | SEL_I | normative — RULE 3.100 binds the lanes |
| what participation means | the register | none — the specification does not reach inside a target |
Designing register semantics is Module 24's subject. This chapter takes three ordinary ones as given and asks only how SEL composes with each.
2. RTL — One Mask, Four Registers
// ─────────────────────────────────────────────────────────────────────────
// wb_masked_register_bank — four registers, four different meanings for
// "this lane participates".
//
// SEL answers ONE question: which lanes of the data word take part. What
// participation MEANS is the register's own business, and the four here
// answer it differently:
//
// 0 CONTROL ordinary RW next = (old & ~mask) | (dat & mask)
// 1 OUTPUT ordinary RW same
// 2 EVENTS write-1-clear a participating 1 clears; a 0 leaves alone
// 3 COMMAND side effect a participating write fires one pulse
//
// THE ORDER OF OPERATIONS IS THE POINT OF THE WHOLE CHAPTER:
//
// mask FIRST, register semantics SECOND.
//
// Not "W1C, then mask" and not "fire the command, then check SEL". The
// lanes that do not participate are not part of the transfer at all, so the
// register's rule is applied only to what is left after masking. Every
// defect in Chapter 13.5 is that order reversed or that step skipped.
//
// COMMIT MODEL, inherited from Module 7 and Module 12: state changes on a
// qualified, acknowledged write and at no other time. `xfer` is
// cyc_i && stb_i; `wr` adds we_i and a mapped offset. Nothing here reacts
// to a presented-but-unterminated transfer.
// ─────────────────────────────────────────────────────────────────────────
module wb_masked_register_bank #(
parameter int unsigned OFF_AW = 4,
parameter int unsigned DW = 32,
parameter int unsigned GRAN = 8,
localparam int unsigned SELW = DW / GRAN
) (
input logic clk_i,
input logic rst_i,
input logic cyc_i,
input logic stb_i,
input logic we_i,
input logic [OFF_AW-1:0] adr_i, // LOCAL word offset
input logic [SELW-1:0] sel_i,
input logic [DW-1:0] dat_i,
// hardware event sources. A status register is set by the hardware it
// reports on and cleared by software; without this input the W1C
// semantics would have nothing to clear.
input logic [DW-1:0] event_set_i,
output logic [DW-1:0] dat_o,
output logic ack_o,
output logic err_o,
// observation only — not part of the Wishbone interface
output logic [DW-1:0] control_o,
output logic [DW-1:0] output_o,
output logic [DW-1:0] events_o,
output int unsigned cmd_count_o,
output logic [DW-1:0] cmd_last_o
);
localparam logic [OFF_AW-1:0] O_CONTROL = OFF_AW'('h0);
localparam logic [OFF_AW-1:0] O_OUTPUT = OFF_AW'('h1);
localparam logic [OFF_AW-1:0] O_EVENTS = OFF_AW'('h2);
localparam logic [OFF_AW-1:0] O_COMMAND = OFF_AW'('h3);
logic xfer, mapped, wr;
assign xfer = cyc_i && stb_i;
assign mapped = (adr_i == O_CONTROL) || (adr_i == O_OUTPUT) ||
(adr_i == O_EVENTS) || (adr_i == O_COMMAND);
assign ack_o = xfer && mapped;
assign err_o = xfer && !mapped;
assign wr = xfer && mapped && we_i;
logic [DW-1:0] mask;
wb_sel_to_mask #(.DW(DW), .GRAN(GRAN)) u_mask (.sel_i(sel_i), .mask_o(mask));
logic [DW-1:0] control_q, output_q, events_q, cmd_last_q;
int unsigned cmd_count_q;
// A set from hardware and a clear from software can land on the same
// clock. SET WINS here, because losing an event that genuinely occurred
// is worse than reporting it twice. That is a LOCAL RTL POLICY — nothing
// in Wishbone reaches inside a register — and it is written as one
// expression so the precedence cannot drift.
always_ff @(posedge clk_i) begin
if (rst_i) begin
control_q <= 32'hA1B2_C3D4;
output_q <= 32'h0000_0000;
events_q <= 32'h0000_0000;
cmd_last_q <= 32'h0000_0000;
cmd_count_q <= 0;
end else if (!wr) begin
events_q <= events_q | event_set_i;
end else begin
case (adr_i)
// ── ordinary read/write: preserve, then replace ──
O_CONTROL: control_q <= (control_q & ~mask) | (dat_i & mask);
O_OUTPUT: output_q <= (output_q & ~mask) | (dat_i & mask);
// ── write-1-clear ──
// A bit clears only if BOTH its lane participates AND the written
// bit is 1. Masking first is what makes the second condition apply
// to the right bits: (dat_i & mask) is the set of ones the transfer
// actually delivered, and nothing outside the selected lanes can
// enter it however dat_i is driven.
O_EVENTS: events_q <= (events_q & ~(dat_i & mask)) | event_set_i;
// ── command with a side effect ──
// The pulse is conditioned on the command lane PARTICIPATING. A
// command value sitting in dat_i on an unselected lane was never
// delivered, so it must not fire anything — which is exactly what
// wb_broken_cmd_bank gets wrong.
O_COMMAND: begin
if (sel_i[0]) begin
cmd_count_q <= cmd_count_q + 1;
cmd_last_q <= dat_i & mask;
end
end
default: ;
endcase
end
end
// Reads return the whole word. RULE 3.65 qualifies DAT_O() with the
// termination, and SEL_I() says where data "should be present" on a read
// — it does not require the slave to blank the other lanes. Returning the
// full word and letting the master take the lanes it asked for is
// IMPLEMENTATION BEHAVIOUR, permitted rather than required, and Chapter
// 13.1 says so rather than presenting it as protocol.
always_comb begin
dat_o = '0;
if (xfer && mapped && !we_i) begin
case (adr_i)
O_CONTROL: dat_o = control_q;
O_OUTPUT: dat_o = output_q;
O_EVENTS: dat_o = events_q;
O_COMMAND: dat_o = cmd_last_q;
default: dat_o = '0;
endcase
end
end
assign control_o = control_q;
assign output_o = output_q;
assign events_o = events_q;
assign cmd_count_o = cmd_count_q;
assign cmd_last_o = cmd_last_q;
endmoduleReading it
One wb_sel_to_mask instance serves all four registers. The lane binding is computed once, audited exhaustively in Chapter 13.1, and consumed three different ways.
wr is the commit condition and SEL is not in it. A write happens or it does not; SEL says what it delivers. A write with SEL = 0000 still acknowledges and still costs a clock.
O_EVENTS reads events_q & ~(dat_i & mask). The mask is inside the parenthesis, against dat_i, before the complement. Moving it outside — (events_q & ~dat_i) | ... — would let a lane that took no part clear bits, which is P5's subject.
O_COMMAND is conditioned on sel_i[0], the lane the command byte lives in, and stores dat_i & mask rather than dat_i. The second half matters as much as the first: a record of what was delivered, not of what happened to be on the bus.
event_set_i is a hardware input, not scaffolding. A status register is set by the hardware it reports on and cleared by software; without it the W1C semantics would have nothing to clear. Set wins over clear in the same cycle — a LOCAL RTL POLICY, written as one expression so the precedence cannot drift, and chosen because losing a real event is worse than reporting it twice.
Reads return the whole word. SEL_I() says where data "should be present" on a read — it does not require blanking the rest. That is implementation behaviour, permitted rather than required, and Chapter 13.1 §4 says where the difference starts to matter.
3. Simulation — SIM G: An Ordinary Register in Sequence
Six writes with no reset between them. Each starts from whatever the last one left, so this tests that a partial write preserves the results of earlier writes — not merely a constant. The reference model walks lanes rather than masking words, so a shared mistake is not shared.
=== SIM G - an ordinary register under partial writes ===
CONTROL resets to 0xA1B2C3D4. Each row writes the word
below through the select pattern shown, and the result is
compared against a lane-walking reference model.
SEL DAT CONTROL reference match
0001 0x11223344 0xa1b2c344 0xa1b2c344 yes
0100 0x55667788 0xa166c344 0xa166c344 yes
1010 0x99aabbcc 0x9966bb44 0x9966bb44 yes
0110 0xdeadbeef 0x99adbe44 0x99adbe44 yes
1000 0x00000000 0x00adbe44 0x00adbe44 yes
1111 0xffffffff 0xffffffff 0xffffffff yes
writes 6 mismatches 0
Row 5 writes 0x00000000 through lane 3 and the register
keeps its other three bytes. Writing zero is a value, not
an absence - the lane participated and received zero.Reading it
Follow one byte down the table. Lane 3 starts as a1, survives rows 1 and 2, becomes 99 in row 3 (SEL = 1010), survives row 4, becomes 00 in row 5, and ff in row 6.
Row 5 is the one worth stopping on. DAT = 0x00000000 through SEL = 1000 gives 0x00adbe44 — the high byte became zero and the other three kept their contents.
Writing zero is a value, not an absence. The lane participated and received 0x00. A design that treated "no bits set" as "nothing to do" would leave a1 in place and be wrong — and a design that treated the write as empty because DAT was zero would be making participation depend on data, which SEL alone decides.
Row 3, SEL = 1010, updates two non-adjacent lanes and leaves the two between them alone. Chapter 13.3 established that a bitmap has no contiguity constraint; here it happens mid-sequence, against a register already three writes deep.
Six writes, zero mismatches, and the reference model was never reset either. It accumulates alongside the design, so the comparison is between two independent running states rather than between a design and a constant.
4. Simulation — SIM H: Write-1-Clear Under a Mask
EVENTS is seeded from hardware to all-ones before each row, so every bit is available to be cleared and nothing can hide behind a bit that was already zero.
=== SIM H - write-1-clear under a select mask ===
EVENTS is seeded from hardware to 0xFFFFFFFF before each
row, so every bit is available to be cleared.
SEL DAT EVENTS reference match
0001 0xffffffff 0xffffff00 0xffffff00 yes
1000 0xffffffff 0x00ffffff 0x00ffffff yes
0101 0xffffffff 0xff00ff00 0xff00ff00 yes
1111 0x0f0f0f0f 0xf0f0f0f0 0xf0f0f0f0 yes
1111 0x00000000 0xffffffff 0xffffffff yes
writes 5 mismatches 0
Rows 1-3 write all-ones and clear ONLY the selected lanes.
An unselected lane survives a word of ones, which is the
whole point: the mask is applied BEFORE the W1C rule, so
a bit outside the selected lanes was never written at all.
Row 4 shows the other condition - selected lanes, but only
the bits set in DAT clear. Row 5 writes zeros and clears
nothing despite selecting every lane.Reading it
Rows 1 to 3 write 0xFFFFFFFF — every bit a clear request — and clear only the selected lanes.
SEL = 0001 leaves 0xffffff00. SEL = 1000 leaves 0x00ffffff. SEL = 0101 leaves 0xff00ff00 — two scattered lanes cleared, two untouched.
A word of all ones did not clear three quarters of the register. The mask is applied to the data before the W1C rule sees it, so the bits outside the selected lanes were never delivered — there was nothing for the rule to act on.
This is the failure mode the ordering prevents. A design computing events & ~dat would clear everything on every one of those three rows, and it would look correct on a full-word clear, which is how driver code usually acknowledges interrupts.
Rows 4 and 5 isolate the other condition. Row 4 selects every lane and writes 0x0F0F0F0F, clearing exactly the low nibbles: 0xf0f0f0f0. Selected, but only where the data bit is 1. Row 5 selects every lane and writes zeros, and nothing clears at all.
Two conditions, tested separately. A bit clears if and only if its lane participated and the delivered bit was 1. Rows 1–3 vary the first with the second held true; rows 4–5 vary the second with the first held true. P5 and P6 in Section 7 are those two halves.
5. Simulation — SIM I: A Command Byte That Was Not Selected
Two designs, identical stimulus. COMMAND lives in lane 0. The first write carries a valid command value in DAT with lane 0 not selected — which is what a routine updating a neighbouring byte of the same word produces.
The second design differs in the O_COMMAND case and nowhere else:
// ── THE DEFECT — a command decoder that never looks at SEL ──────────────
// wb_broken_cmd_bank. NOT A REFERENCE DESIGN.
//
// Identical to wb_masked_register_bank except for the COMMAND case, where
// the `sel_i[0]` condition is gone and the stored value is dat_i rather
// than dat_i & mask.
//
// The reasoning behind it is ordinary and almost right: "a write to the
// COMMAND register is a command." It is true that the write happened. What
// it misses is that a write can happen while the command lane takes no part
// in it — a software routine updating a neighbouring byte of the same word
// delivers nothing to lane 0, and this design fires anyway on whatever
// dat_i happens to carry there.
module wb_broken_cmd_bank #(
parameter int unsigned OFF_AW = 4,
parameter int unsigned DW = 32,
parameter int unsigned GRAN = 8,
localparam int unsigned SELW = DW / GRAN
) (
input logic clk_i,
input logic rst_i,
input logic cyc_i,
input logic stb_i,
input logic we_i,
input logic [OFF_AW-1:0] adr_i,
input logic [SELW-1:0] sel_i,
input logic [DW-1:0] dat_i,
input logic [DW-1:0] event_set_i,
output logic [DW-1:0] dat_o,
output logic ack_o,
output logic err_o,
output logic [DW-1:0] control_o,
output logic [DW-1:0] output_o,
output logic [DW-1:0] events_o,
output int unsigned cmd_count_o,
output logic [DW-1:0] cmd_last_o
);
localparam logic [OFF_AW-1:0] O_CONTROL = OFF_AW'('h0);
localparam logic [OFF_AW-1:0] O_OUTPUT = OFF_AW'('h1);
localparam logic [OFF_AW-1:0] O_EVENTS = OFF_AW'('h2);
localparam logic [OFF_AW-1:0] O_COMMAND = OFF_AW'('h3);
logic xfer, mapped, wr;
assign xfer = cyc_i && stb_i;
assign mapped = (adr_i == O_CONTROL) || (adr_i == O_OUTPUT) ||
(adr_i == O_EVENTS) || (adr_i == O_COMMAND);
assign ack_o = xfer && mapped;
assign err_o = xfer && !mapped;
assign wr = xfer && mapped && we_i;
logic [DW-1:0] mask;
wb_sel_to_mask #(.DW(DW), .GRAN(GRAN)) u_mask (.sel_i(sel_i), .mask_o(mask));
logic [DW-1:0] control_q, output_q, events_q, cmd_last_q;
int unsigned cmd_count_q;
always_ff @(posedge clk_i) begin
if (rst_i) begin
control_q <= 32'hA1B2_C3D4;
output_q <= 32'h0000_0000;
events_q <= 32'h0000_0000;
cmd_last_q <= 32'h0000_0000;
cmd_count_q <= 0;
end else if (!wr) begin
events_q <= events_q | event_set_i;
end else begin
case (adr_i)
O_CONTROL: control_q <= (control_q & ~mask) | (dat_i & mask);
O_OUTPUT: output_q <= (output_q & ~mask) | (dat_i & mask);
O_EVENTS: events_q <= (events_q & ~(dat_i & mask)) | event_set_i;
// ── THE DEFECT: no sel_i[0] term, and dat_i is taken unmasked. ──
O_COMMAND: begin
cmd_count_q <= cmd_count_q + 1;
cmd_last_q <= dat_i;
end
default: ;
endcase
end
end
always_comb begin
dat_o = '0;
if (xfer && mapped && !we_i) begin
case (adr_i)
O_CONTROL: dat_o = control_q;
O_OUTPUT: dat_o = output_q;
O_EVENTS: dat_o = events_q;
O_COMMAND: dat_o = cmd_last_q;
default: dat_o = '0;
endcase
end
end
assign control_o = control_q;
assign output_o = output_q;
assign events_o = events_q;
assign cmd_count_o = cmd_count_q;
assign cmd_last_o = cmd_last_q;
endmodule === SIM I - a command byte that was not selected ===
COMMAND lives in lane 0. Both designs get the same write:
a valid command value in DAT, with lane 0 NOT selected -
which is what a routine updating a neighbouring byte of
the same word produces.
SEL DAT correct ignores SEL
fires last fires last
0010 0x00000042 0 0x00000000 1 0x00000042
0001 0x00000042 1 0x00000042 1 0x00000042
Row 1: the command value is on the bus and lane 0 takes no
part. The correct design fires nothing. The design that
treats 'a write to COMMAND' as the trigger fires 0x42.
Row 2 selects lane 0 and both fire exactly once. The
difference is not whether a command can be issued - it is
whether an unrelated neighbouring write can issue one.
Note also the 'last' column. The correct design stores
dat & mask, so it records only the bytes delivered.Reading it
Row 1: SEL = 0010, DAT = 0x00000042. Lane 0 takes no part. The correct design fires zero times; the design that treats "a write to COMMAND" as the trigger fires once, recording 0x00000042.
The command was never delivered. It sat on DAT in a lane the transfer did not select, which is exactly the situation a neighbouring-byte write creates — and there are many reasons for software to write lane 1 of a word whose lane 0 happens to be a command port.
Row 2 selects lane 0 and both designs fire exactly once. The difference is not whether commands can be issued — both can. It is whether an unrelated write can issue one.
The last column separates the designs a second way. The correct design records dat & mask; on row 1 that is 0x00000000 because nothing was delivered. The broken design records raw DAT, so even its stored value describes bytes it was never given.
One mask, three registers, three different answers
8 cyclesOne transfer, one mask, three state rows moving in three different directions. The read/write register stores 0xFF; the write-1-clear register goes to 0x00 because 0xFF is a request to clear; the command counter increments because lane 0 participated at all.
None of that is visible on the bus. SEL, DAT and the termination are the same in all three cases — the divergence is entirely in what each register does with a lane it was given.
6. Simulation — SIM J: Every Pattern, Every Semantics
Sixteen select patterns against three register behaviours, each compared with the reference model, from a non-trivial starting state that the sweep itself keeps changing.
=== SIM J - every select pattern, every register type ===
16 patterns x 3 register behaviours, each against the
reference model, from a non-trivial starting state.
ordinary RW mismatches 0
write-1-clear mismatches 0
command fired wrongly 0
cases checked 48 mismatches 0
Three different meanings for 'this lane participates',
one mask, and no exceptions across the whole select space.Reading it
Forty-eight checks, three counters, all zero. Sixteen patterns times three behaviours, each against a model written from the definitions rather than from the design.
The counters are kept separate because they fail for different reasons. An ordinary-RW mismatch points at the merge. A W1C mismatch points at where the mask sits relative to the complement. A wrongly-fired command points at a missing condition, and collapsing the three into one number would lose the diagnosis.
The command check is not "did it fire" but "did it fire exactly when lane 0 participated". cmd_count == before + (sel[0] ? 1 : 0) catches both failures at once — firing when it should not, and failing to fire when it should. P7 and P8 are those two halves.
The W1C case re-seeds and then reads the state it actually reached rather than assuming the seed established a particular value. The hardware set is an OR, so it cannot clear what an earlier row left behind — a reference model that assumed a starting state would have been testing the assumption.
7. Verification
// ─────────────────────────────────────────────────────────────────────────
// wb_bank_props — what a select pattern promises about the OTHER register
// behaviours. These are all LOCAL: Wishbone does not reach inside a
// register, so every rule below belongs to this bank's datasheet.
// ─────────────────────────────────────────────────────────────────────────
module wb_bank_props #(
parameter int unsigned DW = 32,
parameter int unsigned GRAN = 8,
localparam int unsigned SELW = DW / GRAN
) (
input logic clk_i,
input logic rst_i,
input logic wr_i, // committed write to this bank
input logic [3:0] adr_i,
input logic [SELW-1:0] sel_i,
input logic [DW-1:0] dat_i,
input logic [DW-1:0] events_i,
input logic [DW-1:0] event_set_i,
input int unsigned cmd_count_i
);
default disable iff (rst_i);
localparam logic [3:0] O_EVENTS = 4'h2;
localparam logic [3:0] O_COMMAND = 4'h3;
// P5 — LOCAL REGISTER SEMANTICS.
// A write-1-clear lane that was not selected cannot clear anything, no
// matter what the write data carried there. This is the property that
// fails when a design applies the W1C rule to dat_i before masking it,
// and SIM H measures it with all-ones data.
genvar gn;
generate
for (gn = 0; gn < SELW; gn++) begin : g_w1c
property p_w1c_unselected;
@(posedge clk_i)
(wr_i && (adr_i == O_EVENTS) && !sel_i[gn] &&
(event_set_i[gn*GRAN +: GRAN] == '0)) |=>
(events_i[gn*GRAN +: GRAN] == $past(events_i[gn*GRAN +: GRAN]));
endproperty
a_w1c_unselected: assert property (p_w1c_unselected);
end
endgenerate
// P6 — LOCAL REGISTER SEMANTICS.
// Within a SELECTED lane, a zero in the write data clears nothing. Both
// conditions are needed, and this is the half that a mask-only
// implementation would satisfy while getting P5 wrong.
property p_w1c_zero_preserves;
@(posedge clk_i)
(wr_i && (adr_i == O_EVENTS) && (event_set_i == '0)) |=>
((events_i & $past(dat_i)) == ($past(events_i) & $past(dat_i) & '0));
endproperty
a_w1c_zero_preserves: assert property (p_w1c_zero_preserves);
// P7 — LOCAL REGISTER SEMANTICS, and the one wb_broken_cmd_bank violates.
// A side effect requires its lane to participate. A command value present
// on dat_i in a lane the transfer did not select was never delivered.
property p_command_requires_select;
@(posedge clk_i)
(wr_i && (adr_i == O_COMMAND) && !sel_i[0]) |=>
(cmd_count_i == $past(cmd_count_i));
endproperty
a_command_requires_select: assert property (p_command_requires_select);
// P8 — LOCAL REGISTER SEMANTICS.
// A selected command lane fires exactly once per committed write. Not
// zero, and not more — the repeated-side-effect failure Module 7 measured.
property p_command_fires_once;
@(posedge clk_i)
(wr_i && (adr_i == O_COMMAND) && sel_i[0]) |=>
(cmd_count_i == $past(cmd_count_i) + 1);
endproperty
a_command_fires_once: assert property (p_command_fires_once);
endmoduleEvery property here is LOCAL, and that is not a weakness of the property set — it is what the layering says. Wishbone binds the lanes to the data wires (RULE 3.100) and does not reach inside a register. A property about what a W1C bit does belongs to this bank's datasheet, which RULE 2.15 requires to exist.
P5 excludes the hardware-set case explicitly — the event_set_i == 0 guard. Without it the property would fail correctly whenever hardware set a bit in the same clock, and the temptation would then be to weaken it rather than notice it was aimed at the wrong moment.
P7 and P8 are a pair and neither is sufficient. P7 alone is satisfied by a design that never fires. P8 alone is satisfied by one that fires on everything. Together they pin the command to exactly the transfers that delivered it.
8. Failure Modes and Discriminating Evidence
Symptom: acknowledging one interrupt clears several.
Candidate causes. A W1C rule applied to DAT without masking — events & ~dat rather than events & ~(dat & mask).
Discriminating evidence. Whether the extra cleared bits lie in unselected lanes. If a byte write to the status register cleared bits in the other three bytes, the mask is outside the complement. SIM H's first three rows are this test, and they need all-ones data to be conclusive.
Likely RTL location: the parenthesisation of the clear expression.
Symptom: a peripheral executes a command nobody issued.
Candidate causes. A side effect conditioned on "a write to this offset" rather than on the command lane participating.
Discriminating evidence. SEL on the transfer that preceded the spurious command. A write to the same word with the command lane deselected is conclusive. The software that appears to be at fault is writing a neighbouring byte and is behaving correctly.
Likely RTL location: the side-effect condition, which is missing a term rather than containing a wrong one.
Symptom: a status bit that hardware is setting can never be cleared.
Candidate causes. Set and clear colliding, with set winning every time — which is this design's documented policy and correct if the condition is persistent.
Discriminating evidence. Whether the hardware condition is still true. A level-triggered source that has not gone away will re-set the bit in the same clock the write clears it. That is not a masking bug, and looking for one wastes the investigation.
Symptom: a full-word acknowledge works and a byte-wide one does not.
Candidate causes. Any masking defect at all, dormant when SEL is all-ones.
Discriminating evidence. That the symptom is conditional on SEL is the diagnosis. Chapter 13.3 makes the same point about ordinary registers: with every lane selected there is nothing for the mask to exclude.
Symptom: a command's recorded parameter is not what software wrote.
Candidate causes. The design stores raw DAT rather than dat & mask.
Discriminating evidence. Compare the recorded value against the lanes that were selected. Bytes present in the record but outside the mask were never delivered. SIM I's last column shows both behaviours side by side.
9. Common Mistakes
"W1C means every 1 in the write data clears its bit."
Wrong mental model: one condition.
What is true: two. The lane must participate and the delivered bit must be 1. SIM H's rows 1–3 write all-ones and clear only the selected lanes; rows 4–5 select everything and clear only where the data has ones.
"Command data in an unselected lane cannot hurt, because the bus did not select it."
Wrong mental model: the bus enforces the selection.
What is true: only if the target consults SEL. The bus carries the select pattern; it does not police what a slave does with it. SIM I measures a conformant slave firing a command from a lane it was never given.
"A register that reads back correctly is masking correctly."
Wrong mental model: read-back is a complete test.
What is true: read-back tests storage, not semantics. A W1C register that clears too much still reads back its new value faithfully. The test is whether the bits that changed are the ones that should have.
"SEL only matters to the data path."
Wrong mental model: masking is about bits.
What is true: a side effect is not a bit. The command case is a per-transfer decision, and no amount of masking the data expresses it — P7 is about a counter, not a word.
"Writing zero to a lane is a no-op."
Wrong mental model: participation depends on the data.
What is true: participation is decided by SEL alone. SIM G's row 5 writes 0x00000000 through one lane and that byte becomes zero. On a W1C register the same write clears nothing — same data, different semantics, and the mask did its job identically in both.
10. Interview Reasoning
Mask the data first, then apply the clear rule to what is left.
In one expression: next = old & ~(dat & mask). The mask is inside, against the data, before the complement.
Both conditions have to hold for a bit to clear. Its lane participated, and the delivered bit was 1. A bit in an unselected lane was never written, whatever DAT carried there, so the rule has nothing to act on.
Name the failure. Computing old & ~dat clears bits across the whole word. Measured: a byte-wide acknowledge that clears all four bytes of a status register — and it looks correct on a full-word clear, which is how most driver code acknowledges interrupts.
A strong answer adds the hardware side. Something must set these bits, and a set colliding with a clear needs a documented precedence. This design lets set win, because losing a real event is worse than reporting it twice — a local policy, stated.
11. Understanding Check
Because three of the four lanes took no part in the transfer.
SEL = 0001 selects lane 0 only. The mask is 0x000000ff, so dat & mask is 0x000000ff — the transfer delivered ones to one byte and nothing at all to the other three.
The clear rule then acts on what was delivered: events & ~0x000000ff leaves 0xffffff00.
The ones in DAT's upper bytes were on the wires and were never delivered. SEL decided that before the W1C rule saw anything.
This is the whole reason the mask goes inside the complement. Compute events & ~dat instead and all four bytes clear — and that design passes every full-word test, which is how most software acknowledges interrupts.
12. What Module 13 Established
SEL names lanes. What a lane means is the target's business.
13.1 — a bitmap, not a size. Width from granularity, binding to the data wires fixed by RULE 3.100, and byte numbering a system convention that RULE 2.15 requires a datasheet to record. SEL applies to reads as well as writes.
13.2 — a select pattern becomes lane write enables, two ways that compute the same function. A walking-one write changes one byte and leaves three bit-identical.
13.3 — the merge has two terms and the missing one is invisible. 0xA1B2C3D4 became 0x00000044 in a design whose mask was entirely correct, and agreed with the correct design on every full-word write.
13.4 — the address splits rather than shrinks. The two bits the bus does not carry become SEL. Eight of twelve size-and-offset combinations fit one transfer; misaligned and cross-word turned out to be different tests.
13.5 — one mask, three meanings. Mask first, semantics second. A status register survived a word of ones because three lanes took no part; a command fired from a lane that was never delivered.
The thread is a single discipline: decide participation before deciding meaning. Every defect measured in this module is that order collapsed — a merge that forgot the non-participants, a clear rule applied before the mask, a side effect that never asked.
13. What's Next
Sub-word access is complete. A transfer can now name a word, name the bytes of it that matter, and reach a target that interprets both correctly.
Every transfer in this course has still moved exactly one word, one address at a time, with CYC_O rising and falling around each one.
What does it cost to move a hundred of them, and what can be amortised?
Module 14 — Block Transfers keeps CYC_O asserted across many data phases and measures what the overhead per transfer actually was. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
Wait States
While a read waits, the master waits for an answer. While a write waits, it holds the data that will mutate the device. Payload coherence and one-shot commits are the two obligations that follow.
- Related topic
Register Bank Design
Four access policies B3 never names, expressed as one table parameter so adding a register is adding a row. The write-only side effect must fire once — the defect fires WAITS+1 times and is invisible at zero waits.
- Related topic
Slave Delays
A delayed slave must hold a request while it works. Two capture styles measured: one costs a clock of latency it can never recover, the other couples you to the master's conformance.
- Related topic
Transaction Restart
A retry needs at least one non-presented clock or it is not a second transfer at all. Measured across RETRY_DELAY, and against a target whose early commit turned one write into three.
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.
