Skip to content
VLSI Mentor

Wishbone · Module 4

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.

Four chapters have used one bit without examining it. Chapter 4.4 needed it to know which side's output mattered; Chapter 4.5 needed it in both capture conditions.

How does one bit change the meaning of both data paths — and why must it never, by itself, cause a register to be written?

1. Direction Is a Property of the Transfer

Chapter 2.3 established that a bus needs a direction bit because both endpoints can drive data and only one of them should be believed at a time. Wishbone spends exactly one wire on it.

The WE_O signal description"The write enable output [WE_O] indicates whether the current local bus cycle is a READ or WRITE cycle. The signal is negated during READ cycles, and is asserted during WRITE cycles." And RULE 3.60 puts it in the list of master outputs that must be qualified with STB_O.

That is the whole content, and the economy is deliberate: a single bit, meaningful only under the strobe, decided by the master before the transfer begins. Note that the direction semantics come from the signal description rather than from a numbered rule — there is no "RULE n: WE_O means write" to cite, and a claim that there is should be checked.

What follows from that, as engineering consequence rather than specification:

Direction is per-transfer, not per-cycle. A master sets it when it presents a transfer and holds it until termination — the same obligation ADR_O carries, and from the same source: RULE 3.60 lists ADR_O, DAT_O(), SEL_O() and WE_O together as signals the master must qualify with STB_O (Chapter 4.3 §3). Changing direction mid-transfer would change what the slave is being asked to do while it is doing it.

There is no third state. No "read-modify-write" transfer, no "exchange" that moves data both ways. A read-modify-write sequence is a read transfer followed by a write transfer, and Chapter 4.9 explains what keeps another master from intervening between them.

The slave does not choose. A slave may refuse a direction — a read-only register answering a write with ERR_O — but it cannot reinterpret one. Answering a write by performing a read is not a legal response; refusing it is.

2. What Each Side Does With It

The master drives it and uses it internally. Chapter 4.5 §3 showed !we_q in the read-capture condition: a master must not capture read data during a write, because there is none.

The slave receives it and gates three things:

Its write path — state changes only under a qualified write. This is the one that breaks.

Its read path — a slave need only produce read data on a read. Driving '0 otherwise is the convention from Chapter 4.4 §3.

Its legality check — a write to a read-only location is an error; a read of the same location is fine. The offset alone does not determine legality; the offset and the direction do.

3. Why WE_I Alone Must Never Cause a Write

This is the section the chapter exists for.

WE_I is a level, not an event. It is a bit on a wire whose value is only meaningful within a qualified transfer. Between transfers it holds whatever the last master left there — masters are not required to clear it, and RULE 3.60 asks only that it be qualified with STB_O, never that it be cleared when the strobe is low.

So a slave whose write path is gated on WE_I alone writes a register on every cycle the line happens to be high, including cycles when no transfer exists at all.

The correct condition is a conjunction, and each term excludes a different wrong cycle:

TermExcludes
CYC_I & STB_Icycles with no transfer — bus residue
WE_Iread transfers
a legal, writable offsettransfers aimed elsewhere, or at read-only state

Drop CYC_I & STB_I: registers change with no software access at all.

Drop WE_I: registers change on every read, taking the previous write's value — the fingerprint from Chapter 4.5 §5.

Drop the offset check: writes land in the wrong register, or in one that reported an error.

4. RTL — Direction on Both Sides

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_we_master — a master issuing both directions from one request port.
//
// PURPOSE. Show that direction is captured once, held for the transfer, and
// then used internally to decide what the master does with the result.
//
// Reset: SYNCHRONOUS, ACTIVE HIGH (RULES 2.30, 3.00).
// ─────────────────────────────────────────────────────────────────────────
module wb_we_master #(
  parameter int unsigned AW = 30,
  parameter int unsigned DW = 32
) (
  input  logic          clk_i,
  input  logic          rst_i,

  input  logic          go_i,
  input  logic          we_i,          // 1 = write, 0 = read
  input  logic [AW-1:0] adr_i,
  input  logic [DW-1:0] wdat_i,
  output logic          done_o,
  output logic [DW-1:0] rdat_o,

  output logic          cyc_o,
  output logic          stb_o,
  output logic          we_o,          // qualified with stb_o, RULE 3.60
  output logic [AW-1:0] adr_o,
  output logic [DW-1:0] dat_o,
  input  logic [DW-1:0] dat_i,
  input  logic          ack_i,
  input  logic          err_i,
  input  logic          rty_i
);
  logic          active_q, we_q;
  logic [AW-1:0] adr_q;
  logic [DW-1:0] wdat_q;

  assign cyc_o = active_q;
  assign stb_o = active_q;
  assign we_o  = we_q;                 // held stable for the whole transfer
  assign adr_o = adr_q;

  // NOTE — dat_o is driven from wdat_q UNCONDITIONALLY, including during a
  // read. That is legal: RULE 3.60 qualifies DAT_O with STB_O, not with WE_O.
  // It is also the behaviour that punishes a slave which ignores WE_I, and
  // it is left in deliberately rather than papered over, because a master
  // that clears DAT_O on reads hides the bug in every slave it talks to.
  assign dat_o = wdat_q;

  logic terminated;
  assign terminated = ack_i | err_i | rty_i;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      active_q <= 1'b0;                // RULE 3.20
      we_q     <= 1'b0;
      adr_q    <= '0;
      wdat_q   <= '0;
      rdat_o   <= '0;
      done_o   <= 1'b0;
    end else begin
      done_o <= 1'b0;
      if (!active_q) begin
        if (go_i) begin
          active_q <= 1'b1;
          we_q     <= we_i;            // direction decided ONCE, here
          adr_q    <= adr_i;
          wdat_q   <= wdat_i;
        end
      end else if (terminated) begin
        active_q <= 1'b0;
        done_o   <= 1'b1;
        // we_q selects what the RESULT means: a read yields data, a write
        // yields only completion (Chapter 4.5 Section 3).
        if (ack_i && !we_q) rdat_o <= dat_i;
      end
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_we_slave — a slave that gates BOTH paths and its legality check on
// direction, using a single named term for every write path.
//
// The three-register map deliberately includes one read-only and one
// write-only location, so that direction changes legality in both ways.
// ─────────────────────────────────────────────────────────────────────────
module wb_we_slave #(
  parameter int unsigned OFF_AW = 8,
  parameter int unsigned DW     = 32
) (
  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 [DW-1:0]     dat_i,
  output logic [DW-1:0]     dat_o,
  output logic              ack_o,
  output logic              err_o,
  output logic [DW-1:0]     ctrl_o,     // observable application state
  output logic              kick_o      // write-only command pulse
);
  localparam logic [OFF_AW-1:0] W_CTRL   = 'd0;   // read / write
  localparam logic [OFF_AW-1:0] W_STATUS = 'd1;   // read-only
  localparam logic [OFF_AW-1:0] W_CMD    = 'd2;   // write-only

  logic [DW-1:0] ctrl_q;
  assign ctrl_o = ctrl_q;

  logic xfer;
  assign xfer = cyc_i & stb_i;                     // RULES 3.30 & 3.35

  logic known_off;
  always_comb begin
    unique case (adr_i)
      W_CTRL, W_STATUS, W_CMD: known_off = 1'b1;
      default:                 known_off = 1'b0;
    endcase
  end

  // ── DIRECTION CHANGES LEGALITY IN BOTH DIRECTIONS ──────────────────────
  // A write to a read-only offset is illegal; a read of it is fine.
  // A read of a write-only offset is illegal; a write to it is fine.
  // The offset alone never decides. The offset AND we_i decide.
  logic illegal_dir;
  assign illegal_dir = xfer & known_off &
                       (( we_i & (adr_i == W_STATUS)) |
                        (~we_i & (adr_i == W_CMD)));

  assign err_o = xfer & (~known_off | illegal_dir);
  assign ack_o = xfer & ~err_o;

  // ── THE SINGLE NAMED WRITE CONDITION ───────────────────────────────────
  // Every write path below uses this term and nothing else. That is the
  // structural defence described in Section 3: there is only one term to
  // use, so a later edit cannot add a path that forgets one of the four.
  logic write_ok;
  assign write_ok = xfer & we_i & known_off & ~illegal_dir;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      ctrl_q <= '0;
      kick_o <= 1'b0;
    end else begin
      kick_o <= 1'b0;                   // default: one-cycle pulse
      if (write_ok) begin
        unique case (adr_i)
          W_CTRL: ctrl_q <= dat_i;
          W_CMD:  kick_o <= 1'b1;       // command, not storage
          default: ;
        endcase
      end
    end
  end

  // Read path: gated on a READ, so the slave produces nothing during a
  // write and the merged return path stays clean (Chapter 4.4 Section 3).
  always_comb begin
    dat_o = '0;
    if (xfer && !we_i && known_off) begin
      unique case (adr_i)
        W_CTRL:   dat_o = ctrl_q;
        W_STATUS: dat_o = {{(DW-2){1'b0}}, 1'b1, 1'b0};
        default:  dat_o = '0;           // W_CMD read is illegal, err_o set
      endcase
    end
  end
endmodule

Reading the pair

Purpose. The master shows direction being decided once and used twice — on the wire and internally. The slave shows it gating three separate things.

Ownership. The master drives WE_O; the slave never drives it.

Combinational logic. Master: almost none. Slave: the qualified transfer, offset recognition, the direction-legality term, the two termination outputs, the single write_ok, and the read multiplexer.

Sequential logic. Master: request registers, direction, captured read data, done_o. Slave: one application register and one command pulse.

Timing. we_q is loaded at the go_i edge and is stable for the whole transfer — no mid-transfer change is possible, because nothing else writes it while active_q is high.

Qualification. write_ok is the only write condition in the module. illegal_dir is what makes W_STATUS and W_CMD differ from W_CTRL.

Reset. Synchronous, active high, per RULES 2.30 and 3.00.

Simplifications. No byte lanes — Chapter 4.7 adds them, and the interaction with WE_O is the substance of that chapter. No wait states.

Failure modes. Section 5.

5. Waveform — The Missing WE_I Bug, Traced

WE_O: the read that writes

9 cycles
Nine clock cycles showing a write transfer followed by a read transfer to the same slave. In cycles one and two the master presents a write with write enable high and data output carrying the value AAAA, and the slave acknowledges. From cycle three onward the master leaves AAAA on its data output because rule three point six zero does not require it to be cleared. In cycles four and five the master presents a read to the same offset with write enable low. A correct slave leaves its control register at AAAA throughout. The lower trace shows a broken slave whose write condition omits write enable: during the read in cycle five it writes its control register again with the value still sitting on the data output, so the register takes AAAA rather than being left alone, and the corruption is invisible because the value happens to match.write: WE high, AAAA landswrite: WE high, AAAA landsDAT_O still holds AAAA — legalDAT_O still holds AAAA —legalread: broken slave writes againread: broken slave writesagainCLK_ISTB_OWE_ODAT_O0x00000xAAAA0xAAAA0xAAAA0xAAAA0xAAAA0xAAAA0xAAAA0xAAAAACK_Ictrl (good)0x00000x00000x00000xAAAA0xAAAA0xAAAA0xAAAA0xAAAA0xAAAActrl (bad)0x00000x00000x00000xAAAA0xAAAA0xAAAA0xAAAA0xAAAA0xAAAAt0t1t2t3t4t5t6t7t8
Figure 1 — a write followed by a read. The lower trace is a slave whose write path omits WE_I; the read corrupts CTRL with the previous write's value.

Cycle 4 is the legal part. The write has finished and the master still has 0xAAAA on DAT_O. RULE 3.60 qualifies that output with STB_O alone, so there is nothing wrong here — and the master in Section 4 does exactly this, deliberately.

Cycle 6 is the bug. The broken slave's write condition is xfer & known_off with no we_i, so a read satisfies it. The register is written with whatever DAT_I carries, which is the previous write's value.

Cycle 8 is why it survives testing. The register took 0xAAAA — the value it already held. A read-back test sees the right answer. Insert a write to a different register between the two accesses and the same read now corrupts ctrl with that unrelated value, which is the point at which the bug finally becomes visible.

6. Failure Modes and Discriminating Evidence

Symptom: a register changes when software reads it.

Candidate causes. The slave's write path omits WE_I.

Discriminating evidence. Write value X to a different register, then read the suspect one, then read it again. If the second read returns X, that is conclusive — the value migrated from an unrelated write, which no other bug produces.

Likely RTL location. The write condition. Look for cyc_i & stb_i without we_i.

Property. P1 in Section 7.

Symptom: writes to a read-only register succeed silently.

Candidate causes. The legality check tests the offset but not the direction, so W_STATUS is treated as an ordinary register.

Discriminating evidence. Write to it and check ERR_O in the same cycle. No error asserted means the check is missing entirely rather than being bypassed.

Likely RTL location. The illegal_dir term, or its absence.

Symptom: a command register triggers its action on reads as well as writes.

Candidate causes. The same missing WE_I, on a command pulse rather than a storage register. This is worse than the storage case because there is no state to inspect — the action simply happens.

Discriminating evidence. Trigger on the command pulse and check we_i in the cycle before. Low is conclusive.

Likely RTL location. The W_CMD branch's condition.

Property. P2.

Symptom: a master's read result is sometimes the value it last wrote.

Candidate causes. The master's capture condition omits !we_q, so a write termination overwrites the captured read register with whatever the fabric returns.

Discriminating evidence. Perform a read, then a write, then examine the captured register without reading again. If it changed, the master is the problem, not the slave.

Likely RTL location. The master's capture, per Chapter 4.5 §3.

7. Verification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_we_checker — direction properties.
//
// P1, P2, P4 are SPECIFICATION-derived: they follow from the WE_O signal
// description's definition of direction together with RULE 3.30, which
// forbids a slave from responding to slave signals while CYC_I is negated.
// P3 is LOCAL DESIGN POLICY — RULE 3.60 requires WE_O to be QUALIFIED with
// STB_O, and reading that as a stability obligation across a multi-cycle
// transfer is this design's interpretation, not a stated rule.
// ─────────────────────────────────────────────────────────────────────────
module wb_we_checker #(
  parameter int unsigned DW = 32
) (
  input logic          clk_i,
  input logic          rst_i,
  input logic          cyc_i,
  input logic          stb_i,
  input logic          we_i,
  input logic          ack_o,
  input logic          err_o,
  input logic [DW-1:0] ctrl_q,
  input logic          kick_o
);
  default disable iff (rst_i);

  // P1 — SPECIFICATION-derived. Stored state changes only under a qualified
  //      WRITE. This is the property that catches the missing-we_i bug, and
  //      it catches it on the FIRST read, long before a read-back test would.
  property p_state_needs_qualified_write;
    @(posedge clk_i) $changed(ctrl_q) |-> $past(cyc_i && stb_i && we_i);
  endproperty
  a_state_needs_qualified_write : assert property (p_state_needs_qualified_write)
    else $error("register changed outside a qualified write");

  // P2 — SPECIFICATION-derived. A command pulse is a side effect, and the
  //      same reasoning applies: a read must not cause one. Worth asserting
  //      SEPARATELY from P1 because a command leaves no state to inspect.
  property p_command_needs_qualified_write;
    @(posedge clk_i) kick_o |-> $past(cyc_i && stb_i && we_i);
  endproperty
  a_command_needs_qualified_write : assert property (p_command_needs_qualified_write)
    else $error("command pulse issued outside a qualified write");

  // P3 — LOCAL POLICY. Direction is stable while a transfer is presented
  //      and unterminated. Catches a master that recomputes we_o from a
  //      combinational source that can change mid-transfer.
  property p_we_stable_during_transfer;
    @(posedge clk_i) (cyc_i && stb_i && !ack_o && !err_o) |=> $stable(we_i);
  endproperty
  a_we_stable_during_transfer : assert property (p_we_stable_during_transfer)
    else $error("WE changed while a transfer was outstanding");

  // P4 — SPECIFICATION-derived. A transfer the slave refused must not have
  //      changed state. Direction-illegal accesses are refused, so this is
  //      what makes the read-only register actually read-only.
  property p_refused_changes_nothing;
    @(posedge clk_i) ($changed(ctrl_q) || kick_o) |-> !$past(err_o);
  endproperty
  a_refused_changes_nothing : assert property (p_refused_changes_nothing)
    else $error("state changed on a transfer terminated with ERR_O");
endmodule

P1 and P2 are the same idea applied to two kinds of effect, and separating them is deliberate. P1 protects storage, which a test can read back. P2 protects actions, which a test cannot — a command that fired has no residue to inspect, so the assertion is the only place the violation is visible.

P3's antecedent excludes terminated cycles because the master is entitled to change direction for the next transfer on the cycle after a termination.

Tooling limitation. Icarus has no SVA support; reviewed by inspection only.

8. Common Mistakes

"WE_I means write, so a slave can write when it sees it."

Wrong mental model: WE_I is a command that arrives.

Concrete bug: a write path gated on WE_I without the qualifiers, acting on whatever the line holds between transfers.

Observable evidence: registers changing with no software access, often at reset release when bus lines settle.

Correct model: WE_I is a level that is only meaningful inside a qualified transfer. It selects a direction; the qualifiers decide whether there is anything to select for.

"Leaving old data on DAT_O during a read is sloppy and should be fixed."

Wrong mental model: a master should clear its output when it is not writing.

Concrete bug: none in the master — but "fixing" it hides a real bug in every slave the master talks to, because the missing-WE_I slave now captures zero instead of a recognisable value and the corruption becomes harder to identify rather than less frequent.

Observable evidence: a system that works with one master and fails with another, which is an integration nightmare to diagnose.

Correct model: RULE 3.60 qualifies DAT_O with STB_O alone. Masters may leave stale data there, so slaves must be correct against that. Fix the slave.

"A read-back test proves the write path is correctly qualified."

Wrong mental model: write X, read X back, the path is right.

Concrete bug: the missing-WE_I slave passes this test, because the read re-writes the register with the value the preceding write left on DAT_O — the same value.

Observable evidence: a green test suite and a field failure when the access order changes.

Correct model: the test must interleave — write A to register 1, write B to register 2, read register 1, read register 1 again. Or assert P1, which catches it on the first read with no test design at all.

9. Interview Reasoning

The symptom names the cause. Corruption correlated with reads rather than writes points at a write path that is not gated on WE_I.

The mechanism. The polling loop reads the status register. The master presents that read with WE_O low — but RULE 3.60 qualifies the master's DAT_O with STB_O only, so whatever it last wrote is still sitting on that bus. The slave's write condition, missing we_i, is satisfied by the read. It writes.

Why the control register specifically. If the slave decodes the offset correctly, it writes whichever register the read addressed — so polling status corrupts status. If control is what changes, the offset decode is shared between paths in a way that maps the read's offset onto the control register, which is a second bug worth checking.

The one observation that confirms it. Write a distinctive value to an unrelated register, then read the suspect one twice. If the second read returns that distinctive value, the migration is proven — no other mechanism moves a value from one register to another across a read.

Why it was not caught earlier. A read-back test writes then immediately reads, so the value the read re-writes is the value that was supposed to be there. The test passes. It takes an interleaved access pattern, or assertion P1, to expose it.

The fix, and the structural version of the fix. Add we_i. Then name the full conjunction once — qualified transfer, write, legal writable offset — and use that single term for every write path in the module, so the next register added cannot reintroduce the bug.

10. Understanding Check

11. What's Next

Direction is settled. Address, both data paths and direction now all have their meaning and their window.

But every write so far has been the full data width. Real software writes single bytes into registers that hold four of them, and the bus has no narrower transfer to offer — the address still points at a whole word.

How does a 32-bit bus write one byte without disturbing the other three?

Chapter 4.7 — SEL_O answers it. 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.