Skip to content
VLSI Mentor

Wishbone · Module 19

CPU Integration

Byte enables generated from size and offset, misalignment as stated local policy, ERR as a documentation obligation rather than a behavioural rule, and an interrupt that reached the core while every bus control signal stayed bit-identical for thirty-two clocks.

This is the chapter about the boundary no specification describes. Chapter 19.3 showed three cores solving it three ways. This one builds it and measures every obligation it carries.

A core issues a byte-addressed, sized request. Wishbone carries a word-addressed cycle with lane selects. What exactly has to happen in between, and which parts of it can you look up?

1. The Adapter, Stated Before It Is Built

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_dbus_adapter — SEAM 1, the load/store half.
//
// A core's native valid/ready request becomes a conformant Wishbone MASTER
// cycle. Neither specification describes this module: Wishbone describes a
// MASTER port and the RISC-V ISA describes instructions, and the thing in
// between is the integrator's.
//
// ── THE FIVE OBLIGATIONS ────────────────────────────────────────────────
//
// 1. HOLD THE REQUEST UNTIL IT IS ANSWERED.
//    A request that has reached the bus belongs to the bus. Nothing the
//    core does afterwards - including stalling - may withdraw it. REQ_DROP
//    is this rule removed and Chapter 19.3 measures it.
//
// 2. CYC_O FOR THE DURATION (RULE 3.25).
//    "MASTER interfaces MUST assert [CYC_O] for the duration of SINGLE
//    READ / WRITE, BLOCK and RMW cycles. [CYC_O] MUST be asserted no later
//    than the rising [CLK_I] edge that qualifies the assertion of [STB_O]."
//
// 3. BYTE ADDRESS -> WORD ADDRESS + SEL.
//    The core addresses bytes; the bus addresses words. SEL_O's own
//    description says "The array boundaries are determined by the
//    granularity of a port", so with 32-bit port size and 8-bit
//    granularity there are four select bits and each names one byte lane.
//    SEL_O is in RULE 3.60's set that STB_O qualifies, which makes
//    generating it correctly this module's job and nobody else's.
//
//      size  addr[1:0]   SEL_O    meaning
//      word     00       1111     all four lanes
//      half     00       0011     lanes 1..0
//      half     10       1100     lanes 3..2
//      byte     00       0001
//      byte     01       0010
//      byte     10       0100
//      byte     11       1000
//
// 4. RETURN THE RIGHT LANE.
//    A byte read returns one lane, right-aligned and zero-extended. Sign
//    extension is the core's business, not the bus adapter's, and this
//    module does not do it.
//
// 5. AN ERROR REACHES THE CORE.
//    ERR_I terminates the cycle and is reported. What the core then does is
//    NOT dictated by Wishbone - RULE 2.15 requires the master's DATASHEET
//    to describe it. ERR_SWALLOWED is this obligation removed.
//

Obligation 3 is the one that repays close reading. SEL_O's own description in B3 says "The array boundaries are determined by the granularity of a port" — so a 32-bit port with 8-bit granularity has four select bits, each naming one byte lane. And RULE 3.60 puts SEL_O in the set of signals STB_O qualifies.

Put those two together and you get: the width is specified, the meaning of each bit is specified, and which bits your half-word at offset 2 should assert is not. That table in the header is this adapter's datasheet entry, and writing it down is the whole of RULE 2.00.

2. Generating the Selects

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  logic [SW-1:0] sel_good;
  always_comb begin
    case (req_size_i)
      2'd0: sel_good = 4'b0001 << off;          // byte: one lane
      2'd1: sel_good = off1 ? 4'b1100           // half: a lane pair
                            : 4'b0011;
      default: sel_good = 4'b1111;              // word: all four
    endcase
  end

  // DEFECT: every access presented as a full-word write. A byte store then
  // overwrites its three neighbours, and Chapter 19.3 measures the damage.
  logic [SW-1:0] sel_use;
  assign sel_use = SEL_IGNORED ? {SW{1'b1}} : sel_good;

  // ── write data placed in its lane ──
  // The core supplies the value right-aligned; the bus expects it in the
  // lane SEL names. Getting this wrong is invisible on word accesses.
  logic [DW-1:0] wdat_lane;
  always_comb begin
    case (req_size_i)
      2'd0:    wdat_lane = {4{wb0}};
      2'd1:    wdat_lane = {2{wh0}};
      default: wdat_lane = req_wdata_i;
    endcase
  end

Two separate things are happening there and they are easy to conflate. sel_good says which lanes. wdat_lane says where in the 32-bit word the core's data has to be placed so that those lanes pick it up. A byte write replicates the byte four times and lets SEL_O choose which copy lands — because the slave takes its data from the lane the select names, not from the bottom of the bus.

3. SIM E — SEL Is a Granularity Map, Not a Size Code

The defect SEL_IGNORED is one line:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  logic [SW-1:0] sel_use;
  assign sel_use = SEL_IGNORED ? {SW{1'b1}} : sel_good;

It is a plausible mistake. A 32-bit port, four lanes, always assert all four — it works perfectly for every word access, and every word access is most of them.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM E - SEL is a granularity map, not a size code ===
    the CSR scratch register at byte 0x3000 is plain storage
    with SEL_O honoured per lane. SEL_O's own description:
    "The array boundaries are determined by the granularity
    of a port."  32-bit port, 8-bit granularity, four lanes.

      access             SEL   scratch after
                               correct      SEL_IGNORED
      byte 0xAB -> +0    0001  0x000000ab   0xabababab
      byte 0xCD -> +2    0100  0x00cd00ab   0xcdcdcdcd
      half 0x1234 -> +0  0011  0x00cd1234   0x12341234
      half 0x5678 -> +2  1100  0x56781234   0x56785678

      read back          correct      SEL_IGNORED
      byte at +0         0x00000034   0x00000078
      byte at +2         0x00000078   0x00000078
      half at +2         0x00005678   0x00005678
      word at +0         0x56781234   0x56785678

    SEL did not match the size:  correct 0   broken 14
    -> the broken adapter presents every access as a full
       word, so a byte store overwrites its three neighbours
       and the register that held two independent fields no
       longer can.

Follow the correct column down. A byte at offset 0, then a byte at offset 2, then a half at offset 0, then a half at offset 2 — and each one leaves its neighbours alone, so the register accumulates four independent writes into 0x56781234.

Now the broken column. Every write splattered across all four lanes. The final value is right only by accident of the last write being a half-word replicated. A register holding two independent fields cannot survive this, and nothing on the bus was violated: the broken adapter drove a legal SEL_O, stable and qualified by STB_O, exactly as RULE 3.45 requires.

4. Misalignment Is a Local Policy, Stated

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign bad_align = (req_size_i == 2'd2 && off != 2'b00)
                  || (req_size_i == 2'd1 && off[0] != 1'b0);
  assign misaligned_o = req_valid_i && bad_align;

This adapter refuses a misaligned access at the seam and issues no bus cycle at all. That is a choice, and the header says so:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── MISALIGNMENT IS A LOCAL POLICY ──────────────────────────────────────
// A halfword at an odd byte address, or a word not at a multiple of four,
// is refused HERE: the adapter answers the core with an error and issues
// NO bus cycle at all. That is a choice. A different adapter could split
// the access into two cycles, or pass it down and let a slave complain.
// B3 says nothing about any of this. The choice is stated so it can be
// measured, and Chapter 19.4 measures it.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    misalignment, a LOCAL POLICY of this adapter
      half at byte +1    trap 1   bus transfers added 0
      -> refused at the seam. No cycle reached the bus at
         all, which is a choice: an adapter could split it,
         or pass it down. B3 says nothing about any of this.

Zero bus transfers were added. The trap came from the adapter, not from a slave, and a bus trace of that access is empty. A different adapter could split the access into two cycles, or pass it down and let a slave complain — all three behaviours are conformant, and a system built from two of them will behave inconsistently without anything being wrong.

5. SIM F — An Error the Core Has To Hear About

ERR_SWALLOWED removes obligation 5:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign rsp_ready_o = busy_q && term;
  assign rsp_rdata_o = dat_i;
  assign rsp_err_o   = ERR_SWALLOWED ? 1'b0 : (busy_q && err_i);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM F - an error the core has to hear about ===
    a word write to the boot ROM at byte 0x0000. The ROM
    answers ERR_O because the request is wrong for it, which
    is Chapter 11.2's test rather than a deferral.

    measure                          correct   ERR_SWALLOWED
      ROM writes refused                 1            1
      traps raised at the core           1            0
      trap disagreed with the error      0            0
      bus transfers on the data port     1            1

    Both slaves refused. Both buses behaved identically. The
    difference is entirely at the seam, and the core in the
    second column is about to act on a write that did not
    happen.

    B3 does not say a core must trap. RULE 2.15 item 4 says
    the MASTER's DATASHEET "MUST describe how it reacts in
    response to the signal". The obligation is to write it
    down, and this adapter's answer is: raise trap, commit
    nothing.

Read the table carefully, because the interesting column is the one that is identical. Both systems refused the write. Both performed exactly one bus transfer. Both buses behaved identically. The difference is entirely at the seam — and the core in the second column is about to carry on as though a write succeeded.

6. SIM G — The Interrupt, and What the Bus Did

This is the module's headline measurement.

The same stimulus is run twice. In the first run a GPIO input edge raises the interrupt while the core is working; in the second run it does not. Every shared-bus signal is folded into a rolling signature on every clock, so two runs can be compared as one integer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // TWO signatures, because they answer two different questions.
  //
  // sig_ctrl covers only the signals that decide WHAT TRANSFERS HAPPEN:
  //   cyc, stb, we, adr, sel, ack, err, and the write data.
  // sig_full adds the READ-DATA lines, which are a slave's internal state
  //   presented on a wire.
  //
  // For Chapter 19.4's question - did the interrupt change the bus? - the
  // first is the honest instrument. A peripheral whose status register now
  // reads differently has not changed the bus; it has changed itself, and
  // its read port shows that on every clock whether anybody is addressing
  // it or not. Reporting both, and saying which is which, is more useful
  // than picking one.
  logic [31:0] acc_c_q, acc_f_q;
  logic [31:0] fold_c, fold_f;

  assign fold_c = dat_i
                ^ {{(32-AW){1'b0}}, adr_i}
                ^ {{(32-SW){1'b0}}, sel_i}
                ^ {27'd0, err_i, ack_i, we_i, stb_i, cyc_i};
  assign fold_f = fold_c ^ rdat_i;

  assign sig_ctrl_o = acc_c_q;
  assign sig_full_o = acc_f_q;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      acc_c_q <= 32'h1357_9BDF; acc_f_q <= 32'h1357_9BDF; clocks_o <= 0;
    end else begin
      // rotate then mix: order-sensitive, so a one-clock difference cannot
      // be cancelled by a later one
      acc_c_q  <= {acc_c_q[30:0], acc_c_q[31]} ^ fold_c ^ 32'h9E37_79B9;
      acc_f_q  <= {acc_f_q[30:0], acc_f_q[31]} ^ fold_f ^ 32'h9E37_79B9;
      clocks_o <= clocks_o + 1;

Two signatures, because there are two different questions.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM G - what the bus did when the interrupt fired ===
    the same stimulus is run twice. In the first run a GPIO
    input edge raises the interrupt; in the second it does
    not. Every shared-bus signal is folded into a rolling
    signature every clock, so two identical signatures mean
    two identical bus traces.

    run              edges  irq  clocks  control sig  full sig
      interrupt fires    1     1     32   0x1ded2bc0   0x01420933
      interrupt quiet    0     0     32   0x1ded2bc0   0x02420933

    CONTROL SIGNALS: IDENTICAL over all 32 clocks.
    WITH READ DATA:  different.

    An interrupt was raised, reached the core, and was
    counted - and CYC, STB, WE, ADR, SEL, ACK, ERR and the
    write data were bit-identical on every one of those
    clocks. No transfer happened differently.

    The full signature DOES differ, and that is worth saying
    plainly rather than hiding: the GPIO's read port carries
    its own flag register, and that register now holds a
    different value. The peripheral changed. The bus did
    not. A slave's read lines are its state on a wire, and
    they mean nothing until a termination qualifies them -
    which is Chapter 16.3's point arriving here.

    The word "interrupt" appears five times in B3 and not
    once as an interface signal.
The interrupt path compared with the bus path. The timer slave and the GPIO slave each sit on the Wishbone bus and are reached by ordinary read and write cycles through the arbiter and split tree. Separately, each drives a plain interrupt wire that is combined and delivered straight to the core, bypassing the arbiter, the split tree and the shared bus entirely. Nothing on the interrupt path is a Wishbone signal, and no Wishbone signal changes when the interrupt moves.coreirq_i is an inputadaptersSEAM 1arbiter +splitthe shared bustimerSTATUS is W1CGPIOISTAT is W1CCYC STB ACKirqirq12

The two irq edges cross the entire figure without touching a single box in the middle. That is not a drawing convenience — it is the measurement in SIM G. The path exists, it carries information to the core, and it is invisible to every instrument that watches the bus.

Thirty-two clocks, bit-identical on every control signal. CYC, STB, WE, ADR, SEL, ACK, ERR and the write data were the same in both runs. An interrupt was raised, reached the core, and was counted — and not one transfer happened differently.

7. Why There Is No Rule To Cite Here

B3 contains the word interrupt five times. They are: "Interrupt vectors" as an example of a user-defined TAG; "interrupt acknowledge" twice, as an example of a cycle type a TAG could discriminate; and "uninterruptible" twice, in the LOCK descriptions.

Not one of them is an interface signal. There is no IRQ_I, no INT_O, no recommendation, no permission, no observation. The wire from the timer to the core in this module's SoC is a wire somebody drew.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//   offset 2  COUNT   RO   a write is answered ERR_O
//   offset 3  STATUS  [0] expired  W1C
//
// ── THE INTERRUPT IS NOT A BUS SIGNAL ───────────────────────────────────
// irq_o is a wire to the core. It is not CYC_O, not ACK_O, not ERR_O, and
// it appears in no B3 signal list - the word "interrupt" occurs five times
// in the whole specification and not once as an interface signal. A core
// may be interrupted with no transfer in flight, and a transfer may
// complete with no relationship to any interrupt. Chapter 19.4 measures
// both directions of that independence.
//
// IRQ_VIA_BUS is the defect where irq_o is removed and the condition is
// only discoverable by polling STATUS - which is a legitimate design if it
// is what you meant, and a silent latency bug if it is not.

IRQ_VIA_BUS is the defect where that wire is removed and the condition is discoverable only by polling STATUS. Note what the header says about it: that is a legitimate design if it is what you meant. Polling is a real strategy with real trade-offs. It becomes a bug only when the rest of the system was built assuming a line that is not there — which is exactly the failure a negative control can catch and a protocol checker cannot. Chapter 19.5 §7 runs it.

8. The Same Invariants as Assertions

Every property this chapter measures has been written in SystemVerilog Assertions as well:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // ── SEAM 1 ────────────────────────────────────────────────────────────

  // A request that has been accepted at the seam must not be withdrawn
  // because the core stopped being ready. Readiness is about the ANSWER.
  property p_request_not_withdrawn;
    @(posedge clk_i)
      (cyc_i && !(ack_i || err_i) && core_stall_i) |=> cyc_i;
  endproperty
  a_request_not_withdrawn: assert property (p_request_not_withdrawn);

  // One request produces at most one bus termination.
  property p_one_termination;
    @(posedge clk_i)
      (cyc_i && stb_i && (ack_i || err_i)) |=> !(ack_i && stb_i && cyc_i);
  endproperty
  a_one_termination: assert property (p_one_termination);

  // The address presented on the bus is the core's address shifted, and it
  // does not move while the phase is unanswered. B3 RULE 3.50 requires the
  // stability; the shift is the adapter's own arithmetic and is nobody's
  // rule but the designer's.
  property p_address_stable;
    @(posedge clk_i)
      (cyc_i && stb_i && !(ack_i || err_i)) |=> $stable(adr_i);
  endproperty
  a_address_stable: assert property (p_address_stable);

One property in that file is a cover, not an assert, and the reason is the chapter's thesis:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // There is nothing to assert about irq_i against the bus, and that is
  // the point. It may rise on any clock, inside a phase or outside one,
  // and no Wishbone signal constrains it. The property below is written
  // as a COVER rather than an ASSERT for exactly that reason: it records
  // that the case happened, and claims nothing about what must follow.
  property p_irq_inside_phase;
    @(posedge clk_i) $rose(irq_i) && cyc_i && stb_i && !(ack_i || err_i);
  endproperty
  c_irq_inside_phase: cover property (p_irq_inside_phase);

There is nothing to assert about the interrupt against the bus. It may rise on any clock, inside a phase or outside one, and no Wishbone signal constrains it. A cover records that the case occurred and claims nothing about what must follow — which is the only honest thing an assertion language can say about a wire two specifications both decline to mention.

9. What Seam 1 Costs, Summarised

obligationwhat removing it looks likecaught by
hold the requestthree cycles vanish, the core gets the right answer anywaya seam counter, not a bus checker
SEL from sizea byte store overwrites three neighbourscomparing SEL against the core's request
error reaches the corethe core proceeds after a failed writecounting traps against refusals
the interrupt wirelatency, silentlynothing on the bus at all

Not one row in that table is a Wishbone protocol violation. Chapter 19.5 §7 runs all four defects plus two more past a full conformance-style checker set and publishes the matrix.

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.