Skip to content
VLSI Mentor

Wishbone · Module 4

DAT_I

A master's DAT_I is returned read data; a slave's is incoming write data. Neither may be believed without the condition that qualifies it — and sampling one cycle late returns the previous transaction's value.

Chapter 4.4 covered what an interface must drive. This chapter is the other half: what an interface may believe.

When an interface receives DAT_I, what does that data mean, and when is it safe to use?

1. The Master's Input: Read Data

A master's DAT_I is driven by whichever slave the INTERCON selected, and Chapter 3.7 established the window: read data is meaningful in the cycle the termination is asserted, and is not held afterwards.

And the specification says so directly. RULE 3.65"SLAVE interfaces MUST qualify the following signals with [ACK_O], [ERR_O] or [RTY_O]: [DAT_O()]." A slave's read data is qualified by its own termination, which is precisely the statement that read data is meaningful in the termination cycle and not outside it.

Why it is not held afterwards. Chapter 4.4 §3 showed a slave driving '0 whenever it is not producing read data — the merged-return-path convention. The moment the transfer terminates and the strobe drops, the selected slave stops producing and the return path reverts to zero. There is nothing to hold.

So the master has exactly one opportunity. Capture in the termination cycle or lose the value.

And it must not capture outside it. A master that registers DAT_I unconditionally stores whatever the return path happens to carry — zero between transfers, or another transfer's data. The symptom is the clean off-by-one Chapter 2.3 §7 identified: every read returns the previous read's value.

2. The Slave's Input: Write Data

A slave's DAT_I is the master's write data, and its qualifying condition has three parts rather than one.

A qualified transferCYC_I & STB_I. RULE 3.30 forbids a slave from responding to any slave signal while CYC_I is negated, and RULE 3.35 requires its termination to be generated from the AND of CYC_I and STB_I. Without both, the slave is looking at residue.

A writeWE_I asserted. Chapter 4.4 §5 showed the master's DAT_O still carrying stale write data during a read, because RULE 3.60 qualifies it with STB_O alone and not with WE_O. A slave that consumes DAT_I without checking WE_I writes a register during every read, with the previous write's value.

A legal target within the slave — the offset must exist and be writable. Otherwise the slave is writing somewhere it should be reporting an error about.

Three conditions, three distinct bugs if any is dropped, and Section 6 works through each.

3. RTL — Both Receiving Sides

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_dat_master_in — the MASTER's DAT_I: capturing returned read data.
//
// PURPOSE. Show the capture condition precisely, and make the failure
// modes of getting it wrong visible by contrast.
//
// Reset: SYNCHRONOUS, ACTIVE HIGH (RULES 2.30, 3.00).
// ─────────────────────────────────────────────────────────────────────────
module wb_dat_master_in #(
  parameter int unsigned AW = 30,
  parameter int unsigned DW = 32
) (
  input  logic          clk_i,
  input  logic          rst_i,

  // Local client side
  input  logic          go_i,
  input  logic          we_i,
  input  logic [AW-1:0] adr_i,
  input  logic [DW-1:0] wdat_i,
  output logic          done_o,        // one-cycle pulse
  output logic [DW-1:0] rdat_o,        // valid while done_o, then held
  output logic          ok_o,          // 1 = terminated with ACK

  // Wishbone MASTER interface
  output logic          cyc_o,
  output logic          stb_o,
  output logic          we_o,
  output logic [AW-1:0] adr_o,
  output logic [DW-1:0] dat_o,
  input  logic [DW-1:0] dat_i,         // MASTER DAT_I = READ data
  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] dat_q;

  assign cyc_o = active_q;
  assign stb_o = active_q;
  assign we_o  = we_q;
  assign adr_o = adr_q;
  assign dat_o = dat_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;
      dat_q    <= '0;
      rdat_o   <= '0;
      ok_o     <= 1'b0;
      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;
          adr_q    <= adr_i;
          dat_q    <= wdat_i;
        end
      end else if (terminated) begin
        active_q <= 1'b0;
        done_o   <= 1'b1;
        ok_o     <= ack_i;

        // ── THE CAPTURE ──────────────────────────────────────────────
        // Three conditions, and each is load-bearing:
        //
        //   active_q   we are in the cycle the transfer is PRESENTED, so
        //              the selected slave is still driving the return path
        //   ack_i      the termination is SUCCESS. On ERR_I there is no
        //              defined read value; on RTY_I nothing happened.
        //   !we_q      this was a read. A write has no read data, and
        //              capturing on a write stores whatever the fabric
        //              reverted to.
        //
        // Capturing outside this exact coincidence is the off-by-one that
        // makes every read return the PREVIOUS read's value.
        if (ack_i && !we_q) rdat_o <= dat_i;
      end
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_dat_slave_in — the SLAVE's DAT_I: consuming incoming write data.
//
// PURPOSE. Make the three qualifying conditions explicit and separable, so
// that dropping any one of them has a nameable consequence (Section 6).
// ─────────────────────────────────────────────────────────────────────────
module wb_dat_slave_in #(
  parameter int unsigned OFF_AW = 10,
  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,      // SLAVE DAT_I = WRITE data
  output logic [DW-1:0]     dat_o,
  output logic              ack_o,
  output logic              err_o
);
  localparam logic [OFF_AW-1:0] W_CTRL    = 'd0;
  localparam logic [OFF_AW-1:0] W_SCRATCH = 'd1;
  localparam logic [OFF_AW-1:0] W_STATUS  = 'd2;   // read-only

  logic [DW-1:0] ctrl_q, scratch_q;

  // ── Condition 1: a qualified transfer (RULES 3.30 and 3.35).
  logic xfer;
  assign xfer = cyc_i & stb_i;

  // ── Condition 3: the offset exists and this operation is legal there.
  logic legal_off, write_ro;
  always_comb begin
    unique case (adr_i)
      W_CTRL, W_SCRATCH, W_STATUS: legal_off = 1'b1;
      default:                     legal_off = 1'b0;
    endcase
  end
  assign write_ro = xfer & we_i & (adr_i == W_STATUS);

  assign err_o = xfer & (~legal_off | write_ro);
  assign ack_o = xfer & ~err_o;

  // ── All three conditions, in one named term. Naming it once and using
  //    it everywhere is what keeps a later edit from adding a write path
  //    that forgets one of them.
  //    Condition 2 — we_i — is the one whose absence is least obvious and
  //    most damaging (Chapter 4.6).
  logic write_ok;
  assign write_ok = xfer & we_i & legal_off & ~write_ro;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      ctrl_q    <= '0;
      scratch_q <= '0;
    end else if (write_ok) begin
      unique case (adr_i)
        W_CTRL:    ctrl_q    <= dat_i;
        W_SCRATCH: scratch_q <= dat_i;
        default:   ;
      endcase
    end
  end

  always_comb begin
    dat_o = '0;
    if (xfer && !we_i && legal_off) begin
      unique case (adr_i)
        W_CTRL:    dat_o = ctrl_q;
        W_SCRATCH: dat_o = scratch_q;
        W_STATUS:  dat_o = {{(DW-1){1'b0}}, 1'b1};
        default:   dat_o = '0;
      endcase
    end
  end
endmodule

Reading the pair

Purpose. Both modules consume a DAT_I, and the interesting content is entirely in when.

Ownership. Neither module drives its dat_i; both read it. The master drives the request, the slave drives the response.

Combinational logic. The master has almost none — the capture is sequential. The slave computes four terms: the qualified transfer, offset legality, the read-only violation and the combined write_ok.

Sequential logic. The master holds the request, the captured read data, the success flag and a one-cycle done_o. The slave holds two application registers.

Timing, for a read that waits two cycles: the request is latched at the go_i edge; active_q is high for three cycles; ack_i arrives on the third; rdat_o is loaded on that same edge and done_o pulses in the following cycle.

Qualification. Master: active_q && ack_i && !we_q. Slave: xfer && we_i && legal_off && !write_ro. Both are conjunctions, and every term earns its place.

Reset. Synchronous, active high. The master's qualifiers are negated; the slave's registers cleared as an educational choice.

Simplifications. No byte lanes yet — Chapter 4.7 adds them to exactly this structure. No retry handling beyond recording that the transfer ended.

Failure modes. Section 6 walks each condition.

4. Waveform — The One Cycle That Counts

DAT_I: valid in the termination cycle, and nowhere else

9 cycles
A Wishbone master performing a read over nine clock cycles. In cycle one the master asserts both qualifiers with write enable low, beginning a read. The slave is busy through cycles one, two and three, during which the returned data input carries zero because no slave is producing read data. In cycle four the slave asserts acknowledge and drives the read value on the same cycle; that is the only cycle in which the returned data is meaningful. The master captures it on that edge, and its captured register shows the value from cycle five onward. From cycle five the return path has reverted to zero because the strobe has dropped and the selected slave has stopped producing.read presented; DAT_I means nothing yetread presented; DAT_I meansnothing yetACK + data: the ONLY valid cycleACK + data: the ONLY validcyclecaptured; return path already revertedcaptured; return pathalready revertedCLK_ISTB_OWE_OACK_IDAT_I0x00000x00000x00000x00000xBEEF0x00000x00000x00000x0000rdat_o0x00000x00000x00000x00000x00000xBEEF0xBEEF0xBEEF0xBEEFt0t1t2t3t4t5t6t7t8
Figure 1 — a read that waits. The return path is meaningless until the termination cycle and reverts immediately after.

Cycles 1 to 3 are the reason blind sampling fails. DAT_I reads as zero, and a master registering it every cycle would store zero three times before the real value arrived.

Cycle 5 is the reason late sampling fails. The strobe has dropped, the selected slave has stopped producing, and the return path is back to zero. A capture one cycle after ACK_I stores nothing at all — or, in a busier system, the next transfer's data.

Cycle 4 is the whole window, and it is one cycle wide.

5. Failure Modes and Discriminating Evidence

Symptom: every read returns the previous read's value.

Candidate causes. The master captures one cycle after the termination.

Discriminating evidence. Put ACK_I, DAT_I and the master's capture strobe on one waveform. If the capture edge is one after the acknowledge edge, that is conclusive — and the perfect off-by-one across every read is itself close to diagnostic.

Likely RTL location. The capture condition — typically if (ack_i) placed where active_q has already cleared.

Property. P1 in Section 7.

Symptom: reads return zero regardless of what the slave holds.

Candidate causes. The master captures unconditionally every cycle, so the last value stored is the post-transfer zero. Or it captures while the transfer is presented but before termination.

Discriminating evidence. Check whether the slave's DAT_O ever carried the right value in the termination cycle. If it did and the master has zero, the capture condition is wrong rather than the slave.

Likely RTL location. A capture with no condition, or one gated on stb_o alone.

Symptom: a slave register changes when software reads it.

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

Discriminating evidence. Read a writable register and check whether it changed. The value it takes will be the previous write's data, because Chapter 4.4 §5 showed the master leaves that on DAT_O during a read. That fingerprint identifies the cause immediately.

Likely RTL location. The write branch's condition — the missing we_i term.

Property. P3.

Symptom: a slave register changes with no software access at all.

Candidate causes. The write path omits CYC_I, STB_I, or both, so the slave acts on bus residue.

Discriminating evidence. Trigger on the register changing and look at cyc_i and stb_i in that cycle. Either low is conclusive.

Likely RTL location. The xfer term, or a write branch that bypasses it.

Symptom: a write to a read-only register reports an error and takes effect anyway.

Candidate causes. write_ok omits the read-only condition.

Discriminating evidence. Write to the read-only offset, confirm ERR_O, then read back. Both true is conclusive, and it is the worst outcome of the four because software was told the access failed.

Property. P4.

6. Verification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_dat_i_checker — input-side data properties.
//
// P1 and P2 are LOCAL DESIGN POLICY of this master, not Wishbone rules: the
// specification does not dictate when a master captures read data. They are
// still worth asserting, because the policy is the correct one and its
// violation is the commonest read bug there is.
//
// P3 and P4 are closer to SPECIFICATION: RULE 3.30 forbids a slave from
// responding to slave signals while CYC_I is negated, so acting on
// unqualified data violates it directly; and changing state on a transfer
// the slave itself reported as an error contradicts the termination given.
// ─────────────────────────────────────────────────────────────────────────
module wb_dat_i_checker #(
  parameter int unsigned DW = 32
) (
  input logic          clk_i,
  input logic          rst_i,
  // Master side (white-box)
  input logic          m_stb_o,
  input logic          m_we_o,
  input logic          m_ack_i,
  input logic [DW-1:0] m_rdat_q,
  // Slave side (white-box)
  input logic          s_cyc_i,
  input logic          s_stb_i,
  input logic          s_we_i,
  input logic          s_err_o,
  input logic [DW-1:0] s_ctrl_q
);
  default disable iff (rst_i);

  // P1 — LOCAL POLICY. The captured read register changes only on the edge
  //      of a successful READ termination. Catches both the off-by-one and
  //      the unconditional capture.
  property p_capture_only_on_read_ack;
    @(posedge clk_i) $changed(m_rdat_q) |-> $past(m_stb_o && m_ack_i && !m_we_o);
  endproperty
  a_capture_only_on_read_ack : assert property (p_capture_only_on_read_ack)
    else $error("read data captured outside a successful read termination");

  // P2 — LOCAL POLICY. A write must not disturb the captured read value.
  property p_write_does_not_capture;
    @(posedge clk_i) (m_stb_o && m_we_o && m_ack_i) |=> $stable(m_rdat_q);
  endproperty
  a_write_does_not_capture : assert property (p_write_does_not_capture)
    else $error("a write changed the captured read register");

  // P3 — SPECIFICATION-derived (RULE 3.30). Slave application
  //      state changes only under a qualified WRITE. Catches both the
  //      missing we_i term and the missing cyc_i/stb_i term.
  property p_state_only_on_qualified_write;
    @(posedge clk_i) $changed(s_ctrl_q) |-> $past(s_cyc_i && s_stb_i && s_we_i);
  endproperty
  a_state_only_on_qualified_write : assert property (p_state_only_on_qualified_write)
    else $error("slave state changed outside a qualified write");

  // P4 — SPECIFICATION-derived. A transfer the slave terminated with ERR_O
  //      must not have changed state. Reporting failure and acting anyway
  //      is the worst of the four failures in Section 5.
  property p_errored_write_changes_nothing;
    @(posedge clk_i) $changed(s_ctrl_q) |-> !$past(s_err_o);
  endproperty
  a_errored_write_changes_nothing : assert property (p_errored_write_changes_nothing)
    else $error("state changed on a transfer terminated with ERR_O");
endmodule

The P1/P3 labelling matters. The specification does not say when a master must capture read data — it says read data accompanies the termination, and what the master does with it is the master's design. P1 and P2 are therefore house policy, correct policy, and still not conformance requirements. P3 and P4 derive from the qualification model and are enforceable more broadly.

$past is used in P1, P3 and P4 because the registers change at an edge and the conditions that authorised the change were true before it — the same shift Chapter 2.4 §9 explained.

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

7. Common Mistakes

"Read data can be sampled whenever it is present."

Wrong mental model: the slave puts a value on the bus and the master picks it up at leisure.

Concrete bug: a master registering DAT_I every cycle, or one cycle after the acknowledge.

Observable evidence: every read returning zero, or every read returning the previous read's value — a perfect off-by-one that looks like a software bug.

Correct model: read data is meaningful in the termination cycle and is not held. The selected slave stops driving when the strobe drops, and a slave that follows the convention in Chapter 4.4 §3 reverts to zero.

"Write data is write data, so a slave can consume DAT_I on any transfer."

Wrong mental model: if a transfer is qualified, the data is for me.

Concrete bug: a write path gated on CYC_I & STB_I but not WE_I. Every read writes a register, with the previous write's value.

Observable evidence: a register that changes when software reads it, taking a value that was written earlier.

Correct model: three conditions — a qualified transfer, a write, and a legal writable offset. Each is separable and each has its own failure.

"Capturing on any termination is close enough."

Wrong mental model: the transfer ended, so take the data.

Concrete bug: capturing on ERR_I or RTY_I. On an error there is no defined read value; on a retry the transfer did not happen at all.

Observable evidence: a master reporting success with meaningless data, or acting on a value from a transfer that was refused.

Correct model: read data accompanies a successful termination. Chapter 4.11 and Chapter 4.12 develop the other two, and a master must branch on which arrived.

8. Interview Reasoning

In the cycle its transfer is terminated successfully, and only then.

Three conditions coincide: the transfer is still presented, so the selected slave is still driving the return path; the termination is ACK_I rather than ERR_I or RTY_I, so there is a defined value; and the transfer was a read.

Why not earlier: while the slave is working it is producing nothing, and a slave that follows the merged-return-path convention drives zero. Sampling during the wait stores zeros.

Why not later: once the transfer terminates the master drops its strobe, the selected slave stops producing, and the return path reverts. A capture one cycle after the acknowledge stores zero, or in a busier system the next transfer's data.

The symptom that identifies this bug instantly: every read returns the previous read's value. A perfect, consistent off-by-one across every read points at the capture edge and nothing else — and it looks like a software bug, which is why it costs time.

The design consequence worth naming: the capture belongs inside the state that represents an outstanding transfer. Writing if (ack_i) … outside that state is the same line of code and a different meaning.

9. Understanding Check

10. What's Next

Both data paths are now settled on both sides: what must be driven, and when a received value may be believed.

Three of those four rules depended on one bit that has been used throughout and never examined. It decides whether the master's data path or the slave's carries the meaningful value, and a slave that ignores it writes a register on every read.

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

Chapter 4.6 — WE_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.