Wishbone · Module 13
Byte Enables
A select pattern becomes write-enable logic two different ways. Measured on a walking-one write against a register whose four bytes all differ.
Chapter 13.1 established what SEL means and turned it into a mask. A mask is not a register.
How does a select pattern become logic that writes some flip-flops and leaves others alone?
1. The Qualified Write Condition
Before any lane logic, the transfer has to be a write that is actually happening. Three conditions, in the order Module 7 and Module 12 established:
xfer = CYC_I && STB_I the transfer is presented (RULE 3.35)
mapped = this offset exists here the target owns it
wr = xfer && mapped && WE_I and it is a writeSEL is not in that list, and that is deliberate. A select pattern does not decide whether a write happens — it decides which lanes of one that is already happening carry data. A write with SEL = 0000 is still a write; it delivers nothing.
SEL_I is meaningful only inside the qualified transfer. RULE 3.60 makes the master qualify ADR_O, DAT_O(), SEL_O(), WE_O and the tags with STB_O, so a select pattern outside a presented transfer names nothing — which Chapter 13.1's waveform shows directly.
2. RTL — The Two Styles
// ─────────────────────────────────────────────────────────────────────────
// Three implementations of "write the selected lanes of one register".
// The first two are correct and equivalent; the third is the defect.
//
// All three are driven identically in Chapter 13.3, so every measured
// difference has exactly one cause.
// ─────────────────────────────────────────────────────────────────────────
// ── STYLE A — per-lane conditional update ───────────────────────────────
// Each lane has its own enable. This is the shape Chapter 4.7 introduced,
// and it is the one that reads as a direct transcription of what SEL means:
// a lane participates, or it does not exist as far as this transfer is
// concerned.
//
// The unselected lanes are not written with anything. They are not written.
module wb_lane_enable_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, // already qualified by the caller
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;
always_ff @(posedge clk_i) begin
if (rst_i) begin
q <= DW'(RESET);
end else if (we_i) begin
for (int unsigned n = 0; n < SELW; n++)
if (sel_i[n]) q[n*GRAN +: GRAN] <= dat_i[n*GRAN +: GRAN];
end
end
assign q_o = q;
endmodule
// ── STYLE B — expanded-mask merge ───────────────────────────────────────
// The same function written as one word-wide expression:
//
// next = (old & ~mask) | (dat & mask)
//
// (old & ~mask) keeps every bit the transfer did not claim
// (dat & mask) supplies every bit it did
//
// The two terms are disjoint by construction, so the OR is a concatenation
// rather than a combination — nothing can contribute to the same bit twice.
//
// NEITHER STYLE IS UNIVERSALLY BETTER. Style A reads closer to the
// protocol; style B composes with the register semantics in Chapter 13.5,
// where the mask has to be applied BEFORE a W1C or command operation rather
// than instead of it. Chapter 13.3 proves they are the same function by
// running both against every select pattern.
module wb_masked_merge_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);
else if (we_i) q <= (q & ~mask) | (dat_i & mask);
end
assign q_o = q;
endmoduleReading it
In style A the unselected lanes are not assigned. Not assigned zero, not assigned their own old value — the if simply does not fire, and a flip-flop with no enable holds. That is the property Chapter 13.1's P3 states.
In style B the two terms are disjoint by construction. (q & ~mask) and (dat & mask) cannot both contribute to one bit, because mask and ~mask partition the word. The | is a concatenation, not a combination — which is why it is safe here and was not safe in Chapter 12.4's response mux, where the terms overlapped.
Style B instantiates the mask expander rather than writing the replication inline. One place computes the lane binding, and Chapter 13.1's exhaustive audit covers it.
3. Simulation — SIM B: One Lane at a Time
A walking-one write. CONTROL resets to 0xA1B2C3D4 — four bytes that all differ — and every write supplies 0x11223344, which differs from the old word in every lane. Any leak between lanes is therefore visible in the result, not merely possible.
=== SIM B - one lane at a time ===
CONTROL starts at 0xa1b2c3d4. Every write supplies 0x11223344,
which differs in all four lanes, so any leak is visible.
SEL mask old new changed
0001 0x000000ff 0xa1b2c3d4 0xa1b2c344 lane 0
0010 0x0000ff00 0xa1b2c3d4 0xa1b233d4 lane 1
0100 0x00ff0000 0xa1b2c3d4 0xa122c3d4 lane 2
1000 0xff000000 0xa1b2c3d4 0x11b2c3d4 lane 3
Each row changes exactly one byte. The other three are
bit-identical to the row's own 'old' column.Reading it
Read the old and new columns one hex digit pair at a time.
Row 1, SEL = 0001: 0xa1b2c3**d4** → 0xa1b2c3**44**. The low byte took 0x44 from the written word. a1, b2 and c3 are bit-identical.
Row 4, SEL = 1000: 0x**a1**b2c3d4 → 0x**11**b2c3d4. The high byte took 0x11. The other three are untouched.
The register is reset before each row, so every row starts from the same 0xA1B2C3D4 and the four rows together are a complete walking-one test. Four lanes, four writes, four single-byte changes, no interaction.
The mask column is the bridge between the two chapters. 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 — one lane's worth of ones, positioned by the select bit. Comparing the mask against the changed byte in the same row is the whole of what byte enables do.
And the two styles agreed on every row. The testbench checks q_merge === q_lane after each write; any disagreement would have printed. That is a check, not a claim — Chapter 13.3 extends it to all sixteen patterns.
4. When the Write Lands
Selected lanes change once, at the acknowledged edge
8 cyclesTwo of the three state rows are flat across the whole figure. That is the point of the waveform and it is not something a table shows as forcefully — the neighbouring bytes are not restored after being disturbed; they are never disturbed.
Lane 1 changes exactly once. SEL being a bitmap says nothing about when; the commit model from Module 7 does, and it is unchanged by sub-word access.
5. Failure Modes and Discriminating Evidence
Symptom: a byte write updates the right byte and corrupts a neighbour.
Candidate causes. Lane slicing off by one, or a mask replication width that is not the granularity.
Discriminating evidence. SIM B's walking-one test, read as four rows. A consistent one-lane displacement across all four rows is a slicing bug; an inconsistent pattern is an expansion bug. Both need a starting word whose bytes differ — from 0x00000000 every failure looks like a success.
Likely RTL location: the +: slice bounds, or the replication count.
Symptom: a byte write updates all four bytes.
Candidate causes. SEL ignored entirely — a whole-word assignment under the write condition.
Discriminating evidence. Whether the unselected lanes took DAT's values or zero. Taking DAT means the select is not consulted at all; taking zero is the different defect Chapter 13.3 measures, and the two have different fixes.
Symptom: the register changes when no write was issued.
Candidate causes. The update qualified on SEL or WE without CYC_I && STB_I.
Discriminating evidence. The state at a clock where SEL is driven but no strobe is. Chapter 13.1's waveform shows that cycle; a register that moves there is reacting to an unqualified signal, which RULE 3.60 says is meaningless.
Symptom: writes work on some lanes and not others.
Candidate causes. A loop bound of SELW-1 instead of SELW, or a mask built for a different port width.
Discriminating evidence. Which lanes fail, and whether it is always the same one. The top lane failing consistently is an off-by-one in the loop; SIM A's exhaustive audit catches it before any register is involved, which is the cheaper place to find it.
6. Common Mistakes
"SEL is the write enable."
Wrong mental model: one signal decides whether the write happens.
What is true: WE_I with a qualified strobe decides that; SEL decides which lanes carry data. A write with SEL = 0000 is a write that delivers nothing — it still terminates, still costs a clock, still is a write.
"Writing 0x00 to a lane means not writing it."
Wrong mental model: zero is an absence.
What is true: zero is a value. SIM B's fourth row in Chapter 13.5 writes 0x00000000 through one lane and that lane becomes zero, while the others keep their contents. Participation is decided by SEL, never by the data.
"Per-lane enables and the mask merge are different designs."
Wrong mental model: the styles differ functionally.
What is true: they are the same function, checked on every write in SIM B and on all sixteen patterns in Chapter 13.3. What differs is what they compose with — the mask is reusable by a W1C rule, and the per-lane form is not.
"A byte-enabled register costs four times the logic."
Wrong mental model: per-lane means per-lane hardware everywhere.
What is true: this chapter cannot say what it costs, because no synthesis ran. The two styles describe the same behaviour; what a tool builds from them is a question for a synthesis report, not for a reading of the source.
7. Interview Reasoning
Qualify the write first, then apply the select per lane.
The qualification is three terms and SEL is not among them: CYC_I && STB_I for a presented transfer, a mapped offset, and WE_I. SEL does not decide whether a write happens.
Then either style works. Per-lane: if (sel[n]) q[n*8 +: 8] <= dat[n*8 +: 8]. Masked merge: q <= (q & ~mask) | (dat & mask), with mask the select expanded one lane at a time.
The property to state out loud is the one about unselected lanes. They are not written — not written with zero, not written with whatever DAT carries. Those bytes belong to other software, and the register is the only thing protecting them.
A strong answer adds why you would choose the masked form: it produces a mask that a write-1-clear or side-effect rule can be built on top of, which the per-lane form does not. Chapter 13.5 needs exactly that.
8. Understanding Check
From the granularity: each select bit is replicated across its lane's width.
The port is 32 bits with 8-bit granularity, so a lane is eight bits and a set select bit becomes eight ones. SEL[1] covers DAT[15:8], which is the second byte from the bottom — 0x0000ff00.
The replication count is the granularity, not a constant. With 16-bit granularity a set select bit would produce sixteen ones, and there would be only two lanes to produce them for.
And the position comes from RULE 3.100, which binds SEL(1) to DAT(15..08). Neither the width nor the position is a choice the design made — both follow from the port's declared parameters.
9. What's Next
Byte enables work: a select pattern becomes lane write enables, selected lanes take the data, and neighbouring bytes are never touched.
All of that was measured on designs that already get it right. The interesting question is what the preservation actually costs to get wrong.
What exactly does a partial write have to preserve, and what happens to a design that masks the data and forgets the rest?
Chapter 13.3 — Partial Writes derives the merge equation, measures the register that drops half of it, and sweeps all sixteen select patterns including the ones no size encoding can express. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
CPU to Peripheral Communication
A CPU reaches hardware outside itself by reading and writing addressed locations, and a peripheral is hardware it cannot execute. Everything a driver does has to be expressed as a read or a write of a location the peripheral answers for — and once more than a couple of peripherals exist, wiring each one to the core separately stops scaling. That is the problem an on-chip bus is the answer to.
- Related topic
Data Transfer
Selection routes a request; it does not move a value. Write data fans out, read data fans in through a multiplexer that every target widens, byte lanes decide which parts of a word participate, and a register narrower than the bus has to be placed on a lane rather than merely connected.
- Related topic
The Slave Interface
A Wishbone slave never initiates. It observes a qualified request, interprets a local offset, and ends the transfer with exactly one of three terminations. Everything that makes it reusable comes from what it refuses to know: its own base address, the topology, and which master is asking.
- Related topic
WE_O
One bit decides which data path carries the meaningful value. It is a direction selector and never a write trigger, and a slave that ignores it writes a register on every read with the previous write's data.
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.
