Skip to content
VLSI Mentor

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:

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

SEL 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

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

Reading 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === 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**b2c3d40x**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 claimChapter 13.3 extends it to all sixteen patterns.

4. When the Write Lands

Selected lanes change once, at the acknowledged edge

8 cycles
Eight clock cycles showing a write to lane 1 only. Address, write-enable, select value two and write data are driven from cycle two through cycle four; cycle and strobe are asserted in cycles three and four; the acknowledge returns in cycle four. Three rows track the register's individual byte lanes: lane 1 changes from its old value to the new one after cycle four, while lane 0 and lane 2 hold their old values across the entire figure.SEL=0010 qualified: lane 1 onlySEL=0010 qualified: lane 1onlylane 1 takes 0x33; neighbours holdlane 1 takes 0x33;neighbours holdno second update from a held SELno second update from aheld SELCLK_ISEL_O02222222WE_OCYC_O/STB_OACK_Ilane 2 regb2b2b2b2b2b2b2b2lane 1 regc3c3c3c333333333lane 0 regd4d4d4d4d4d4d4d4t0t1t2t3t4t5t6t7

Two 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

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.