Skip to content
VLSI Mentor

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:

layerdecided byWishbone's involvement
which lanes participateSEL_Inormative — RULE 3.100 binds the lanes
what participation meansthe registernone — 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

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

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

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

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === 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 cycles
Eight clock cycles of a single write with select equal to binary 0001, carrying all-ones data. Three register state rows respond differently to the same transfer: the ordinary read-write register's low byte takes the value ff while its upper bytes hold; the write-one-clear register's low byte goes to zero while its upper bytes hold; and the command counter increments by one because lane zero participated. All three changes happen at the same acknowledged edge.one mask: lane 0, value 0xFFone mask: lane 0, value0xFFstore 0xFF / clear to 0 / fire oncestore 0xFF / clear to 0 /fire onceupper lanes of both registers: heldupper lanes of bothregisters: heldCLK_ISEL_O01111111DAT_O0ffffffffffffffCYC_O/STB_OACK_IRW lane 0d4d4d4d4ffffffffW1C lane 0ffffffff00000000cmd count00001111t0t1t2t3t4t5t6t7

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

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

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

Every 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 0x000000ffthe 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

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.