Skip to content
VLSI Mentor

Wishbone · Module 8

Single Write Cycle

SINGLE READ / WRITE is one cycle type, not two. The cycle skeleton carries no data ports at all, because data is not cycle structure.

Chapter 8.1 counted a read: one bus cycle, one transfer, and a note that the two intervals coincided for a specific and conditional reason.

How does the same single-transfer cycle structure carry a write?

1. One Cycle Type, Two Directions

RULE 3.25 requires CYC_O asserted for the duration of "SINGLE READ / WRITE, BLOCK and RMW cycles". The slash is doing real work — the specification treats single reads and single writes as one entry in a list of three cycle shapes.

RULE 3.75 likewise: "All MASTER and SLAVE interfaces that support SINGLE READ or SINGLE WRITE cycles MUST conform to the timing requirements given in sections 3.2.1 and 3.2.2." Two subsections, one rule, one requirement.

What differs between §3.2.1 and §3.2.2 is entirely payload. The read section describes the slave presenting data with its acknowledge; the write section describes the master presenting data from the first edge and the slave latching it. Neither changes the cycle's shape.

The engineering consequence worth carrying into Chapter 8.3: if the skeleton is direction-agnostic for a single transfer, it is direction-agnostic for a block — which is why BLOCK READ and BLOCK WRITE are likewise one concept with two payload directions, and why WE_O can in principle differ between transfers inside one cycle.

2. Counting a Write Trace

A single write cycle

7 cycles
Seven clock cycles showing one Wishbone write with two wait states. The cycle signal and strobe signal both rise at the start of cycle two and both fall at the end of cycle four, so the bus cycle and the transfer occupy the same interval. Write enable is high throughout, in contrast to the read trace where it was low. The address holds word two and the data output holds the value zero zero zero zero zero zero zero f for all three presented cycles. The acknowledge appears only in cycle four, and the control register takes the new value at the edge ending that cycle.cycle starts AND transfer startscycle starts AND transferstartstransfer ends AND cycle endstransfer ends AND cycleendsCLK_ICYC_OSTB_OWE_OADR_O----0x20x20x2------------DAT_O----0000000F0000000F0000000F------------ACK_ICONTROL000000000000000000000000000000000000000F0000000F0000000Ft0t1t2t3t4t5t6
Figure 1 — one write cycle, drawn to the same scale as Chapter 8.1's read. Only WE_O and the payload row differ.

Compare it against Chapter 8.1's Figure 1 row by row.

CYC_O, STB_O and ACK_I are identical. ADR_O holds one value for the whole presentation in both. The markers land on the same edges.

WE_O is inverted. The read held it low; this holds it high. Both hold it constant, because Chapter 7.1's master ties it to a literal — a constant satisfies RULE 3.60's stability obligation more convincingly than a register does.

The payload row moved sides. The read had DAT_I carrying a value for exactly one cycle, gated by the slave's termination under RULE 3.65. The write has DAT_O carrying a value for the whole presentation, because RULE 3.60 qualifies it with STB_O — and §3.2.2 requires it valid until the edge following the strobe's negation.

The counts are the same. Bus cycles = 1. Transfers = 1. Wait cycles = 2.

3. RTL — Cycle Control, Isolated

Chapter 7.1's wb_write_master already performs a correct single write cycle, and Module 8 does not need another register bank. What it needs is the cycle-control logic on its own, so that Chapter 8.3 can extend exactly that and nothing else.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_single_cycle_ctrl — the cycle skeleton, direction-agnostic.
//
// PURPOSE. Strip a single-transfer master down to its CYCLE CONTROL: when
// CYC_O opens, when STB_O presents, when the cycle closes. Everything about
// payload — capture, byte lanes, commit, read-data capture — is deliberately
// absent, because none of it is cycle structure.
//
// This block is what Chapter 8.3 extends into a block controller and
// Chapter 8.4 extends into an RMW controller. Isolating it means those
// chapters change ONE thing rather than presenting a new master each time.
//
// DIRECTION-AGNOSTIC. we_o is a passthrough of the requested direction,
// held for the transfer. A read sets it low, a write high; the cycle logic
// below does not branch on it anywhere — which is Section 1's claim in
// executable form.
//
// CYC/STB. Driven from one register, which PERMISSION 3.40 permits because
// this controller never negates STB_O mid-transfer:
//   "If a MASTER doesn't generate wait states, then [STB_O] and [CYC_O]
//    MAY be assigned the same signal."
// Chapter 8.3 is where that precondition stops holding.
//
// Reset: SYNCHRONOUS, ACTIVE HIGH (RULES 2.30, 3.00, 3.20).
// ─────────────────────────────────────────────────────────────────────────
module wb_single_cycle_ctrl #(
  parameter int unsigned AW = 30
) (
  input  logic          clk_i,
  input  logic          rst_i,

  // local client side
  input  logic          req_i,
  input  logic          req_we_i,
  input  logic [AW-1:0] req_adr_i,
  output logic          acc_o,
  output logic          busy_o,
  output logic          done_o,
  output logic [1:0]    status_o,        // 0=ok 1=error 2=retry

  // Wishbone cycle-control outputs
  output logic          cyc_o,
  output logic          stb_o,
  output logic          we_o,
  output logic [AW-1:0] adr_o,
  input  logic          ack_i,
  input  logic          err_i,
  input  logic          rty_i
);
  localparam logic [1:0] ST_OK = 2'd0, ST_ERR = 2'd1, ST_RTY = 2'd2;

  // ── STATE ─────────────────────────────────────────────────────────────
  // active_q IS the bus cycle. One bit, and its lifetime is the cycle's
  // lifetime — which is exactly why Chapter 8.1's counts were 1 and 1.
  logic          active_q;
  logic          we_q;
  logic [AW-1:0] adr_q;

  assign acc_o  = req_i & ~active_q;
  assign busy_o = active_q;

  assign cyc_o = active_q;                 // PERMISSION 3.40
  assign stb_o = active_q;
  assign we_o  = we_q;
  assign adr_o = adr_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;
      done_o   <= 1'b0;
      status_o <= ST_OK;
    end else begin
      done_o <= 1'b0;

      if (acc_o) begin
        // ── CYCLE START and TRANSFER START, the same edge ──────────────
        // The two events coincide here. In a block controller they do not:
        // the cycle starts once and transfers start repeatedly.
        active_q <= 1'b1;
        we_q     <= req_we_i;
        adr_q    <= req_adr_i;
      end else if (active_q && terminated) begin
        // ── TRANSFER END and CYCLE END, the same edge ──────────────────
        // Clearing active_q negates BOTH qualifiers. A block controller
        // clears only the strobe here and keeps the cycle open.
        status_o <= ack_i ? ST_OK : (err_i ? ST_ERR : ST_RTY);
        active_q <= 1'b0;
        done_o   <= 1'b1;
      end
    end
  end
endmodule

Reading it

Purpose. Expose the cycle skeleton with no payload logic attached, so the two extensions in Chapters 8.3 and 8.4 are visibly small changes to one block.

Interface. A client request carrying only a direction and an address — no data ports at all, because data is not cycle structure. A real master composes this with the payload handling Chapter 7.1 built.

State. One active flag, a direction and an address. active_q is the bus cycle, and that identity is what Chapter 8.3 breaks.

Combinational logic. acc_o, and every bus output from state.

Sequential logic. Capture at acceptance; release at termination.

Cycle start. The edge following req_i & ~active_q. RULE 3.25's "no later than" is met by cyc_o and stb_o rising together.

Transfer start. The same edge — and the comment says so, because the coincidence is the thing being isolated.

Termination. Sampled as a level while active_q is set, per Chapter 5.4.

Next transfer. None. There is no mechanism for one, which is precisely what Chapter 8.3 adds.

Cycle end. The same edge as the termination.

Reset. Synchronous, active high; clearing active_q negates both qualifiers per RULE 3.20.

Failure modes. A controller that cleared active_q on a condition other than a termination would produce the RULE 3.25 duration violation Chapter 5.2 §2 measured.

Simplifications. No payload, no byte lanes, no read-data capture. Those are Modules 6 and 7's, and duplicating them here would obscure the one thing this block exists to show.

4. Simulation — One Cycle, One Transfer, Reversed

wb_write_master from Chapter 7.1 wrote CONTROL at word 2, with the Chapter 8.1 monitor attached, at zero and three wait states.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM B - single write cycle ===
  zero wait states:
    bus cycles begun        1
    transfers completed     1
    reads / writes          0 / 1
    wait cycles             0
    gaps (CYC & !STB)       0
    clocks with CYC high    1
    CONTROL after           0x0000000f
  with 3 wait states:
    bus cycles begun        1
    transfers completed     1
    reads / writes          0 / 1
    wait cycles             3
    gaps (CYC & !STB)       0
    clocks with CYC high    4
    CONTROL after           0x0000000f

Identical structure to Chapter 8.1's read, with the direction counter on the other side: 0 / 1 instead of 1 / 0.

The waits again changed duration, not content — four clocks of CYC_O, still one transfer.

gaps = 0 in both, the PERMISSION 3.40 signature. That is the number Chapter 8.3 changes first.

5. Failure Modes and Discriminating Evidence

Most cycle-level write failures are the read failures of Chapter 8.1 §6 unchanged, because the skeleton is the same. Two differ, and both come from the payload obligation's different shape.

Symptom: the write completes and the register holds a value from a later request.

Candidate causes. The master's payload did not hold for the full presentation — §3.2.2 requires it valid until the edge following the strobe's negation.

Discriminating evidence. Watch DAT_O and ADR_O across the whole strobe assertion. Any change before the termination is a RULE 3.60 violation, and Chapter 7.4 §5 measured the resulting corruption. There is no read equivalent of this failure — a read master holds an address but carries nothing that can be committed.

Symptom: the cycle looks correct and the register changed more than once.

Candidate causes. The slave's commit is gated on the request being presented rather than accepted.

Discriminating evidence. Count commits against terminations, not against presented cycles. Chapter 7.4 §5 measured four commits for one transfer. The monitor's transfers count is the correct denominator, which is why it counts terminations.

Symptom: WE_O changes while the transfer is outstanding.

Candidate causes. A master deriving the direction combinationally from a client input rather than from a captured copy.

Discriminating evidence. WE_O differing between two presented cycles of one transfer. A slave sampling the direction late performs the wrong operation entirely — a read that commits, or a write that returns data.

Likely RTL location. The we_o assignment. wb_single_cycle_ctrl drives it from we_q; Chapter 7.1's master ties it to a constant, which is stronger still.

6. Verification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_single_write_cycle_checker — cycle-shape properties, write direction.
//
// P1 and P2 are SPECIFICATION and are IDENTICAL to Chapter 8.1's — which is
// the chapter's thesis stated as code: the cycle-shape rules do not branch
// on direction. P3 is SPECIFICATION but write-specific. P4 is LOCAL POLICY
// and must be deleted for a block or RMW master.
// ─────────────────────────────────────────────────────────────────────────
module wb_single_write_cycle_checker #(
  parameter int unsigned AW = 30,
  parameter int unsigned DW = 32
) (
  input logic            clk_i,
  input logic            rst_i,
  input logic            cyc_o,
  input logic            stb_o,
  input logic            we_o,
  input logic [AW-1:0]   adr_o,
  input logic [DW-1:0]   dat_o,
  input logic [DW/8-1:0] sel_o,
  input logic            ack_i,
  input logic            err_i,
  input logic            rty_i
);
  default disable iff (rst_i);

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

  // P1 — SPECIFICATION (RULE 3.25). Identical to Chapter 8.1's P1.
  property p_stb_inside_cyc;
    @(posedge clk_i) stb_o |-> cyc_o;
  endproperty
  a_stb_inside_cyc : assert property (p_stb_inside_cyc)
    else $error("RULE 3.25: STB_O asserted outside a bus cycle");

  // P2 — SPECIFICATION (RULE 3.25, duration). Identical to Chapter 8.1's P2.
  property p_cyc_spans_outstanding;
    @(posedge clk_i) (cyc_o && stb_o && !terminated) |=> cyc_o;
  endproperty
  a_cyc_spans_outstanding : assert property (p_cyc_spans_outstanding)
    else $error("RULE 3.25: CYC_O negated with a transfer outstanding");

  // P3 — SPECIFICATION (RULE 3.60, and §3.2.2 via RULE 3.75). The WRITE
  //      payload holds for the whole presentation. This has no read
  //      counterpart: a read master carries no payload to hold.
  property p_write_payload_held;
    @(posedge clk_i) (cyc_o && stb_o && we_o && !terminated)
      |=> ($stable(adr_o) && $stable(dat_o) && $stable(sel_o) && $stable(we_o));
  endproperty
  a_write_payload_held : assert property (p_write_payload_held)
    else $error("RULE 3.60 / 3.2.2: write payload moved while outstanding");

  // P4 — LOCAL POLICY, single-transfer masters only. Deleted in 8.3 / 8.4.
  property p_one_transfer_per_cycle;
    @(posedge clk_i) (cyc_o && stb_o && terminated) |=> !cyc_o;
  endproperty
  a_one_transfer_per_cycle : assert property (p_one_transfer_per_cycle)
    else $error("LOCAL: cycle outlived its transfer in a single-transfer master");
endmodule

P1 and P2 being byte-identical to Chapter 8.1's is the chapter's argument, not an oversight. The cycle-shape rules genuinely do not branch on direction, so a conformance checker for cycle structure can be written once and bound to a master of either kind.

P3 is where the checkers diverge, and its read counterpart is a different property entirely — Chapter 6.3's capture-window check, which constrains when a master may believe rather than what it must sustain.

Tooling limitation. Icarus Verilog has no SVA support and cannot execute any of these. They are reviewed by inspection; the controller is elaborated and the simulation above was run.

7. Common Mistakes

"A write cycle is structurally different from a read cycle."

Wrong mental model: the direction changes the cycle shape.

Concrete bug: cycle-control logic that branches on WE_O — a separate state machine per direction, doubling the states that can be wrong and diverging under maintenance.

Observable evidence: a master where reads work and writes hang, or vice versa, with identical-looking cycle logic in two places.

Correct model: RULE 3.25 names "SINGLE READ / WRITE" as one cycle type. wb_single_cycle_ctrl does not branch on direction anywhere.

"WE_O can be set up any time before the acknowledge."

Wrong mental model: the direction only matters when the slave responds.

Concrete bug: a direction derived combinationally from a client input that changes mid-transfer.

Observable evidence: a slave performing the wrong operation — committing on what was meant to be a read.

Correct model: RULE 3.60 qualifies WE_O with STB_O, so it must be stable for the whole presentation. A combinational slave samples it whenever it is ready.

"Both cycles hold their payload the same way."

Wrong mental model: symmetry all the way down.

Concrete bug: a block master that releases write data as soon as the first transfer of a block is acknowledged, having reasoned from the read case where the slave's data is valid for one cycle.

Observable evidence: the second and later transfers of a block committing stale or wrong values.

Correct model: the read's payload obligation is a one-cycle window on the slave's output (RULE 3.65); the write's is a sustained duration on the master's (RULE 3.60, §3.2.2). Section 2's callout draws the distinction.

8. Interview Reasoning

Two things: the direction bit, and which side sustains the payload. The cycle skeleton is the same.

What is identical. CYC_O opens the cycle and spans it, STB_O presents one transfer, a termination ends that transfer, and the master then releases. RULE 3.25 requires CYC_O asserted for the duration of "SINGLE READ / WRITE" — one entry in the list, not two — and RULE 3.75 points both directions at the same pair of timing sections.

What differs, first: WE_O. Negated for a read, asserted for a write, and stable for the whole presentation either way under RULE 3.60.

What differs, second and more interestingly: the shape of the payload obligation. On a read the slave drives data and RULE 3.65 makes it meaningful for exactly one cycle — the termination cycle. On a write the master drives data and RULE 3.60 makes it meaningful for the whole presentation, with §3.2.2 requiring it valid until the edge after the strobe negates.

So one is a window the master must hit and the other is a duration the master must sustain. Those are different obligations that happen to occupy the same cycle structure.

Why the symmetry is worth stating positively. Cycle-control logic should not branch on direction. A master with two state machines — one for reads, one for writes — has doubled the surface that can be wrong and will drift apart under maintenance. The controller I would write takes direction as a passthrough and never tests it.

And the consequence for what comes next. Because the skeleton is direction-agnostic for one transfer, it is direction-agnostic for several — which is why BLOCK READ and BLOCK WRITE are one concept, and why WE_O could in principle differ between transfers inside a single cycle.

9. Understanding Check

10. What's Next

Two cycle types counted, both 1 and 1, and a clear account of which parts of the structure the direction bit does and does not touch.

Every trace so far has had CYC_O and STB_O rising and falling together, because every master so far met PERMISSION 3.40's precondition. The specification's own description of CYC_O describes something else entirely — a signal asserted during the first data transfer that remains asserted until the last.

What changes when one bus cycle contains several transfers?

Chapter 8.3 — Block Transfer Cycle 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.