Skip to content
VLSI Mentor

Wishbone · Module 1

Need for Standardized Interconnects

An address map answers where a register lives. It says nothing about which wires carry the request, when they are valid, how the target reports completion, or what happens on an error. Three peripherals with three private interfaces produce three adapters, three verification efforts and three ways to be wrong — which is the argument for standardising the interface rather than the map.

Chapter 1.2 finished with a system whose software and hardware agree completely on addresses. The UART is at 0x4000_1000, its status register is at offset 0x04, the decode is exhaustive, the map is documented. Every question about location is settled.

Now take that system and try to actually build it, and a different problem appears immediately. The core has an address, a direction and possibly some data, and it needs to hand them to a block designed by somebody else. Which wires? Held for how long? How does the core learn the access finished? What if it did not?

None of that is in the address map, and none of it is optional.

1. What the Address Map Did Not Settle

Chapter 1.1 derived the information that has to cross the boundary: an address, a direction, write data, byte granularity, read data, a completion and an error indication. Chapter 1.2 gave the address a meaning.

Every other item on that list is still undefined. Not "defined elsewhere" — undefined, in the sense that two engineers reading the same specification would make different, equally reasonable choices.

QuestionWhy the map cannot answer it
Which signal says an access is happening now?A map names locations, not events
How long must the address stay stable?A map has no notion of time
Is read data valid in the same cycle, or the next?Latency is a property of the block, not the location
How does the target say done?Completion is a signal, and the map has no signals
What does the initiator do while waiting?Waiting is a behaviour, not an address
How is a failed access distinguished from a slow one?Both happen at the same address
Is data byte-enabled, and how?Granularity is a transfer property
Is reset synchronous or asynchronous, active high or low?Nothing in a map mentions reset
What may change while a request is outstanding?This is the hardest one, and it is pure protocol

The last row is worth pausing on, because it is the one that separates a working interface from a nearly-working one. If a target latches the address on the cycle it sees the request, and the initiator assumes it may change the address as soon as it has issued the request, both designs are internally consistent and the pair is broken. No amount of address-map correctness helps.

That class of disagreement — where each side is individually reasonable — is exactly what a protocol specification exists to eliminate, and it is why "we both read the memory map" is not a sufficient integration review.

2. A System Where Everyone Invented Their Own

Concretely: take the three peripherals from Chapter 1.1, and suppose each was written by a different engineer, each solving the interface problem sensibly and independently.

A processor core on the left must reach three peripherals on the right, but each peripheral presents a different private interface. The UART uses a request and acknowledge pair. The GPIO uses an enable signal with a combinational ready and returns read data in the same cycle. The timer uses chip-select with separate read and write strobes and no completion signal at all, requiring the initiator to know its fixed latency. Because the three interfaces disagree, the core cannot connect to any of them directly: a separate adapter must be written and verified for each one, and the adapters are shown between the core and the peripherals.CPU coreone request portAdapter Areq / ack timingAdapter Benable / readyAdapter Ccounts latencyUARTreq / ack, 1-cycle pulseGPIOenable, combinationalTimercs + rd/wr, no completion3 interfaces = 3 ofeverythingadapters, testbenches,review12
Figure 1 — one core, three peripherals, three private interfaces, three adapters that must be written and verified separately.

Nothing in that picture is anybody's mistake. Each peripheral's interface is defensible on its own terms, and each author picked the shape that suited their block. The defect is emergent: it exists only in the composition, and it appears at integration time, which is the worst possible moment to discover it.

3. RTL 1 — Three Reasonable Interfaces That Cannot Be Connected

Here are the three blocks, reduced to the part that matters. Read them as three answers to the same question.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Peripheral A — UART. Request / acknowledge, with a one-cycle ack pulse.
//
// Interface contract (as its author would document it):
//   • Assert `req` with `addr`, `we` and `wdata` valid.
//   • Hold all of them stable until `ack` is seen.
//   • `ack` is a ONE-CYCLE PULSE. On a read, `rdata` is valid in the same
//     cycle `ack` is high, and is not held afterwards.
//   • `req` must be deasserted for at least one cycle between transfers.
// ─────────────────────────────────────────────────────────────────────────
module uart_adhoc #(
  parameter int unsigned AW = 4,
  parameter int unsigned DW = 32
) (
  input  logic          clk,
  input  logic          rst_n,          // asynchronous, active low
  input  logic          req,
  input  logic          we,
  input  logic [AW-1:0] addr,
  input  logic [DW-1:0] wdata,
  output logic [DW-1:0] rdata,
  output logic          ack
);
  localparam logic [AW-1:0] REG_DATA   = 4'h0;
  localparam logic [AW-1:0] REG_STATUS = 4'h4;

  logic [DW-1:0] tx_holding;
  logic          tx_ready;
  logic          req_q;

  // One-cycle ack pulse: assert on the first cycle of a request only.
  assign ack = req & ~req_q;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) req_q <= 1'b0;
    else        req_q <= req;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      tx_holding <= '0;
    end else if (req && !req_q && we && (addr == REG_DATA)) begin
      tx_holding <= wdata;
    end
  end

  // Read data is combinational and only meaningful while `ack` is high.
  always_comb begin
    unique case (addr)
      REG_DATA:   rdata = tx_holding;
      REG_STATUS: rdata = {{(DW-1){1'b0}}, tx_ready};
      default:    rdata = '0;
    endcase
  end

  // Stand-in for the real transmitter; not the subject here.
  assign tx_ready = 1'b1;
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Peripheral B — GPIO. Enable / ready, with combinational read data.
//
// Interface contract (as ITS author would document it):
//   • Assert `en`; the access happens on the clock edge where `en` is high.
//   • `ready` is a LEVEL, not a pulse, and is high whenever the block can
//     take an access — which for this block is always.
//   • On a read, `rdata` is valid COMBINATIONALLY from `addr`, whether or
//     not `en` is asserted.
//   • Back-to-back accesses on consecutive cycles are legal.
// ─────────────────────────────────────────────────────────────────────────
module gpio_adhoc #(
  parameter int unsigned AW = 4,
  parameter int unsigned DW = 32,
  parameter int unsigned PINS = 8
) (
  input  logic            clk,
  input  logic            rst_n,        // asynchronous, active low
  input  logic            en,
  input  logic            we,
  input  logic [AW-1:0]   addr,
  input  logic [DW-1:0]   wdata,
  output logic [DW-1:0]   rdata,
  output logic            ready,
  output logic [PINS-1:0] pin_out,
  input  logic [PINS-1:0] pin_in
);
  localparam logic [AW-1:0] REG_DIR = 4'h0;
  localparam logic [AW-1:0] REG_OUT = 4'h4;
  localparam logic [AW-1:0] REG_IN  = 4'h8;

  logic [PINS-1:0] dir_q, out_q, in_q;

  assign ready = 1'b1;                       // never busy

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      dir_q <= '0;
      out_q <= '0;
    end else if (en && we) begin
      unique case (addr)
        REG_DIR: dir_q <= wdata[PINS-1:0];
        REG_OUT: out_q <= wdata[PINS-1:0];
        default: ;                            // REG_IN is read-only
      endcase
    end
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) in_q <= '0;
    else        in_q <= pin_in;               // one flop; a real block
  end                                         // would synchronise properly

  always_comb begin
    unique case (addr)
      REG_DIR: rdata = {{(DW-PINS){1'b0}}, dir_q};
      REG_OUT: rdata = {{(DW-PINS){1'b0}}, out_q};
      REG_IN:  rdata = {{(DW-PINS){1'b0}}, in_q};
      default: rdata = '0;
    endcase
  end

  assign pin_out = out_q & dir_q;
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Peripheral C — Timer. Chip select with separate strobes, and NO
// completion signal at all.
//
// Interface contract (as ITS author would document it):
//   • Assert `cs` together with exactly one of `rd` / `wr`.
//   • A write takes effect on that clock edge.
//   • On a read, `rdata` is REGISTERED and therefore valid ONE CYCLE
//     AFTER the cycle in which `cs & rd` was asserted.
//   • The block is never busy, so it reports nothing back. The initiator
//     is expected to know the one-cycle read latency.
// ─────────────────────────────────────────────────────────────────────────
module timer_adhoc #(
  parameter int unsigned AW = 4,
  parameter int unsigned DW = 32
) (
  input  logic          clk,
  input  logic          rst,            // SYNCHRONOUS, ACTIVE HIGH
  input  logic          cs,
  input  logic          rd,
  input  logic          wr,
  input  logic [AW-1:0] addr,
  input  logic [DW-1:0] wdata,
  output logic [DW-1:0] rdata,
  output logic          irq
);
  localparam logic [AW-1:0] REG_RELOAD = 4'h0;
  localparam logic [AW-1:0] REG_CTRL   = 4'h4;
  localparam logic [AW-1:0] REG_STATUS = 4'h8;

  logic [DW-1:0] reload_q, count_q;
  logic          enable_q, expired_q;

  always_ff @(posedge clk) begin
    if (rst) begin
      reload_q  <= '0;
      enable_q  <= 1'b0;
      count_q   <= '0;
      expired_q <= 1'b0;
    end else begin
      if (cs && wr) begin
        unique case (addr)
          REG_RELOAD: reload_q <= wdata;
          REG_CTRL:   enable_q <= wdata[0];
          REG_STATUS: expired_q <= expired_q & ~wdata[0];   // write-1-to-clear
          default:    ;
        endcase
      end
      if (enable_q) begin
        if (count_q == '0) begin
          count_q   <= reload_q;
          expired_q <= 1'b1;
        end else begin
          count_q <= count_q - 1'b1;
        end
      end
    end
  end

  // Registered read data — valid the cycle AFTER the access.
  always_ff @(posedge clk) begin
    if (rst) begin
      rdata <= '0;
    end else if (cs && rd) begin
      unique case (addr)
        REG_RELOAD: rdata <= reload_q;
        REG_CTRL:   rdata <= {{(DW-1){1'b0}}, enable_q};
        REG_STATUS: rdata <= {{(DW-1){1'b0}}, expired_q};
        default:    rdata <= '0;
      endcase
    end
  end

  assign irq = expired_q & enable_q;
endmodule

Reading the three together

Each one is competent. None has a latch, none has a missing reset, none has an incomplete case. If you reviewed any of them alone you would sign it off.

Now list the ways they disagree, because the list is longer than it looks:

DimensionUARTGPIOTimer
Request signalreqencs + rd/wr
Direction encodingweweseparate strobes
Completionack, one-cycle pulseready, levelnone
Read-data validitysame cycle as ackcombinational, alwaysone cycle after
Back-to-back accessesillegal — needs an idle cyclelegallegal
Reset polarityactive lowactive lowactive high
Reset synchronicityasynchronousasynchronoussynchronous
Error reportingnonenonenone

Eight dimensions, and the three blocks agree on fewer than half. Two of those disagreements — the reset ones — are not even about the bus; they are about the most basic assumption a block makes about the system it lands in, and they are invisible in a memory map, invisible in a functional simulation that never asserts reset mid-run, and fatal in silicon.

4. RTL 2 — What Integration Actually Costs

The three blocks cannot be wired to one initiator port, so something must translate. Here is the adapter for the timer alone — the simplest of the three, because the timer is never busy.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Adapter: generic request/done port  →  timer_adhoc's cs/rd/wr interface.
//
// The whole job of this module is to manufacture the completion signal the
// timer does not provide, by knowing its fixed one-cycle read latency. That
// knowledge lives HERE, in glue, rather than in the peripheral — which is
// precisely the problem, because it is not checkable and not reusable.
// ─────────────────────────────────────────────────────────────────────────
module timer_adapter #(
  parameter int unsigned AW = 4,
  parameter int unsigned DW = 32
) (
  input  logic          clk,
  input  logic          rst_n,          // system reset: async, active low

  // Generic initiator-facing port
  input  logic          req,
  input  logic          we,
  input  logic [AW-1:0] addr,
  input  logic [DW-1:0] wdata,
  output logic [DW-1:0] rdata,
  output logic          done,

  // Timer-facing port
  output logic          t_rst,          // the timer wants SYNC, ACTIVE HIGH
  output logic          t_cs,
  output logic          t_rd,
  output logic          t_wr,
  output logic [AW-1:0] t_addr,
  output logic [DW-1:0] t_wdata,
  input  logic [DW-1:0] t_rdata
);
  // ── Reset conversion. The system reset is asynchronous and active low;
  //    the timer wants a synchronous active-high reset. Releasing it
  //    synchronously to `clk` is what keeps the timer's first cycle
  //    deterministic — an asynchronously released reset into synchronous
  //    logic is a recovery/removal timing violation waiting to happen.
  logic rst_sync_q, rst_sync_qq;
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      rst_sync_q  <= 1'b1;
      rst_sync_qq <= 1'b1;
    end else begin
      rst_sync_q  <= 1'b0;
      rst_sync_qq <= rst_sync_q;
    end
  end
  assign t_rst = rst_sync_qq;

  // ── Request translation. Strobes are asserted for exactly the first
  //    cycle of a request, because the timer would otherwise apply the same
  //    write on every cycle the request is held.
  logic req_q;
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) req_q <= 1'b0;
    else        req_q <= req;
  end

  logic first_cycle;
  assign first_cycle = req & ~req_q;

  assign t_cs    = first_cycle;
  assign t_rd    = first_cycle & ~we;
  assign t_wr    = first_cycle &  we;
  assign t_addr  = addr;
  assign t_wdata = wdata;

  // ── Completion synthesis. A write is finished on the cycle it is applied.
  //    A read is finished one cycle later, because that is when the timer's
  //    registered read data becomes valid. This delay is not observable
  //    from any signal — it is the datasheet, transcribed into logic.
  logic read_pending_q;
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) read_pending_q <= 1'b0;
    else        read_pending_q <= first_cycle & ~we;
  end

  assign done  = (first_cycle & we) | read_pending_q;
  assign rdata = t_rdata;
endmodule

What this module teaches

The engineering problem it solves is not a hard one — it is a translation problem. And that is the point: it is 60 lines of entirely uninteresting logic whose only purpose is to reconcile two conventions that never needed to differ.

The interface assumptions it encodes, and where each came from:

  • The timer's read latency is exactly one cycle. Source: the timer's datasheet. Checkable by: nothing. If a future revision registers its outputs once more, this adapter is silently wrong.
  • A write completes immediately. Source: the same paragraph.
  • Strobes must be one cycle wide. Source: inference from the RTL, because a held wr would rewrite the register every cycle. This assumption is not in any document — it is a property the adapter's author had to discover by reading someone else's code.
  • The timer's reset is synchronous and active high. Source: the port name and, if you are lucky, a comment.

The sequential behaviour worth tracing. req rises; first_cycle is high for exactly one cycle; on a read, t_cs and t_rd pulse, read_pending_q is set, and done rises one cycle later while t_rdata is valid. On a write, done rises in the same cycle as the strobe.

How it fails. Three ways, all quiet:

  • If the initiator drops req before done, read_pending_q still asserts done a cycle later against a request that no longer exists.
  • If the initiator issues back-to-back requests without req going low between them, first_cycle never fires again and the second access silently never happens.
  • If the timer is later placed behind a register slice for timing closure, its latency becomes two cycles and done now rises while t_rdata is still stale. The system reads plausible garbage.

What is deliberately simplified. There is no error path, no byte enables, and no handling of a request that is withdrawn. Adding them makes the adapter bigger without changing its lesson.

Now multiply. This is one adapter for one peripheral in one system. The UART needs a different one, the GPIO a third. Each needs its own testbench, because there is no shared notion of correct behaviour to test against. Each needs its own review. And when this SoC is superseded, none of the three is reusable, because the next core's port will not be this one either.

5. The Cost Is Multiplicative, Not Additive

The instinct is to treat interface glue as a fixed tax: n peripherals, n adapters. It is worse than that, and seeing why is what turns this from an annoyance into an architectural argument.

Integrating one block against one initiator is a pairing, not a property of either. With m initiator conventions and n peripheral conventions, the number of distinct translations that might have to exist is m × n. A single project sees a slice of that; an ecosystem of independently written IP sees the whole grid — and Chapter 1.6 is about what happens when that ecosystem is thousands of authors wide.

A standard interface collapses the grid to a line. Each block is written once against the standard, and any standard-conforming initiator can use any standard-conforming target. m + n pieces of work replace m × n.

What multipliesWith private interfacesWith one standard
Adapters to writeone per (initiator, target) pairnone
Interface testbenchesone per interfaceone, reused
Assertion setsone per interfaceone, reused
Bus monitors for debugone per interfaceone, reused
Interface documents to readone per blockone, once
Reviewers who know the rulesper blockper project
Ways to be subtly wrongone set per interfaceone set, and it is written down

The last row is the one engineers underrate. A standard does not merely save effort; it concentrates the effort. One interface used twenty times gets twenty times the scrutiny, and every bug found in it is a bug found for every user. Twenty bespoke interfaces each get one project's attention, and each carries its own private set of undiscovered corner cases.

6. Failure Modes and How to Tell Them Apart

Ad-hoc integration produces a characteristic family of bugs. They are worth knowing by shape, because they recur in every system that skips a common interface, and because the symptoms overlap while the causes do not.

Symptom: a read returns a plausible but wrong value.

Candidate causes. A latency disagreement — the initiator samples one cycle early or late. A completion-semantics disagreement — a level read as a pulse, so the access "finished" before it started. A read-data lifetime disagreement — the target drove valid data for one cycle and the initiator captured it two cycles later.

Discriminating evidence. Put the target's read-data bus and the initiator's capture strobe on the same waveform and compare edges. If valid data appears but is captured on the wrong edge, it is latency. If the capture strobe fires before the target's strobe at all, it is completion semantics. If the data was right and then decayed before capture, it is lifetime. One waveform with two signals splits three causes.

Symptom: the access hangs.

Candidate causes. Waiting on a completion the target never produces — the timer case. Waiting on a completion the target produced before the initiator was looking. An unmapped address, so no target responded at all.

Discriminating evidence. Look at the target's select. If no target was selected, it is decode — a Chapter 1.2 problem, not an interface problem. If a target was selected and its completion signal did pulse, the initiator missed it and the bug is in the handshake reading. If the completion never asserted, the target is genuinely not answering.

Symptom: the first access after reset behaves differently from every later one.

Candidate causes. Reset polarity mismatch, so a block was never reset. Reset synchronicity mismatch, so a block left reset on a different edge from its neighbours. A request that was already asserted when reset released.

Discriminating evidence. This one is cheap: look at the reset signals themselves, at the instant of release, for every block. A block whose internal state is non-zero while the system reset is still asserted was never reset. A block that starts one cycle after its neighbours had its reset resynchronised somewhere.

Symptom: it works in simulation and fails on hardware.

Candidate causes. An assumption that held in a zero-delay simulation and does not hold with real timing — most often a combinational path from a request to a completion that the synthesiser could not close, or a strobe that is glitching.

Discriminating evidence. Check whether any completion path is combinational from the request. A target whose ready or done is a combinational function of the initiator's request creates a loop the moment the initiator's request is a combinational function of the completion. The GPIO above has exactly this shape: ready is a constant, which is safe, but an author who made it en & something would have built a trap.

The general search strategy, which is the part worth keeping: establish which side of the interface is wrong before establishing what is wrong with it. One observation — did the target see a legal request, and did it produce a legal response? — splits every one of the symptoms above into two halves, and it costs one waveform.

7. Verification — Properties That Are Only Writable Against a Standard

Here is an argument that is easy to miss: a standard interface makes certain properties expressible that are otherwise not expressible at all.

Assertions about a bus have to name signals. If every peripheral has different signals, an assertion can only be written per peripheral, by whoever wrote that peripheral, in whatever style they chose. Nothing checks the system.

With one interface, the same properties are written once and bound everywhere:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Interface properties, written once against a COMMON request/done port and
// bound to every target in the system. None of these are Wishbone rules —
// they are the generic architectural claims this chapter has made, made
// checkable. Wishbone's own protocol properties belong to Module 26.
// ─────────────────────────────────────────────────────────────────────────
module generic_port_checker #(
  parameter int unsigned AW = 32,
  parameter int unsigned DW = 32
) (
  input logic          clk,
  input logic          rst_n,
  input logic          req,
  input logic          done,
  input logic          we,
  input logic [AW-1:0] addr,
  input logic [DW-1:0] wdata
);
  default disable iff (!rst_n);

  // P1 — request information is STABLE while the access is outstanding.
  //      This is the disagreement from Section 1 that broke pairs of blocks
  //      which were each individually reasonable.
  property p_addr_stable;
    @(posedge clk) (req && !done) |=> $stable(addr) && $stable(we);
  endproperty
  a_addr_stable : assert property (p_addr_stable)
    else $error("address or direction changed while a request was outstanding");

  // P2 — write data is stable for the same window. Split from P1 so a
  //      failure names the actual culprit instead of a generic "something
  //      moved" message.
  property p_wdata_stable;
    @(posedge clk) (req && we && !done) |=> $stable(wdata);
  endproperty
  a_wdata_stable : assert property (p_wdata_stable)
    else $error("write data changed while a write was outstanding");

  // P3 — a request is not withdrawn before it completes. This is exactly
  //      the timer adapter's first failure mode from Section 4.
  property p_req_held;
    @(posedge clk) (req && !done) |=> req;
  endproperty
  a_req_held : assert property (p_req_held)
    else $error("request withdrawn before completion");

  // P4 — completion only ever occurs inside a request. A `done` with no
  //      `req` is the level-read-as-pulse bug, caught at its source.
  property p_done_implies_req;
    @(posedge clk) done |-> req;
  endproperty
  a_done_implies_req : assert property (p_done_implies_req)
    else $error("completion asserted with no outstanding request");
endmodule

Why these four and not more. Each corresponds to a failure this chapter has already shown, which is the bar an assertion should clear — a property that does not correspond to a way the design can be wrong is a property that will only ever fire on a testbench bug.

P1 and P2 encode the "what may change while a request is outstanding" row from Section 1 — the disagreement that makes two individually-correct blocks incompatible. P3 catches the initiator side of the timer adapter's hazard. P4 catches the level-versus-pulse confusion at the moment it happens, rather than three cycles later when a wrong value has already been consumed.

What is deliberately not asserted here. Nothing about how long a target may take, because that is a policy choice rather than a correctness rule, and nothing about error signalling, because this generic port has none. Both become assertable once a real protocol defines them — which is the point.

The structural observation: these four properties are the same four for every target in the system. Written against ad-hoc interfaces they would be twelve properties in three styles, and in practice they would be zero.

8. What a Standard Interface Does Not Fix

A chapter arguing for standardisation owes an honest account of what it buys and what it does not, or it is advocacy rather than engineering.

Still your problem after standardising:

  • The address map. A bus standard defines the interface, not where blocks live. Chapter 1.2's entire contract remains yours.
  • Topology and performance. A standard interface says nothing about how many transfers per second the fabric sustains, how many initiators it arbitrates, or how deep the multiplexing is.
  • Whether the peripheral is any good. Conformance is about the interface. A conformant block can still have a broken timer, an unsafe clock-domain crossing, or an undocumented erratum.
  • Semantic agreement. Two blocks can exchange transfers flawlessly and still disagree about what a register means.
  • Clock and reset domains. A standard fixes a reset's polarity and synchronicity; it does not tell you which domain a block belongs in.

And there is a real cost. A general interface carries signals a given block does not need, imposes a handshake a trivially fast block would not require, and adds decode and multiplexing delay to every access. For one core and one tightly-coupled accelerator, a private interface is genuinely smaller and faster — the point Chapter 1.1 made about dedicated ports still stands.

The trade, stated once: a standard interface exchanges per-access efficiency for composability. That is a good trade when blocks come from different places and the system will change, and a poor one when there are two blocks and neither will move.

9. Common Misconceptions

"If we publish a good memory map, integration is documented."

The wrong model: the map is the interface.

What it costs: an integration review that checks addresses, signs off, and discovers at bring-up that a target latches on a different edge from what the initiator assumed. The review looked at the artefact that existed rather than the one that mattered.

The corrected model: the map answers where; a protocol answers how. A review needs both documents, and the second is the one most projects do not have.

"We only have three peripherals, so glue is cheaper than adopting a standard."

The wrong model: the cost is the adapters.

What it costs: three adapters is a day. Three interface verification environments is a month, so they do not get built, and the defects they would have caught are found in the lab instead.

The corrected model: count the verification, the monitors, the assertions and the documentation, not the RTL. And count the next system too, because none of the three adapters survives into it.

"An interface standard means every block works with every other block."

The wrong model: conformance implies interoperability implies correctness.

What it costs: an integration that connects cleanly and behaves wrongly, with everyone confident the interface was the risk and it was handled.

The corrected model: a standard removes one class of failure — the mechanical one — completely. It removes none of the semantic ones. Section 8 is the list of what you still own.

"A standard is slower because it is general."

The wrong model: generality always costs performance, so a private interface is the performance choice.

What it costs: an architecture that hand-builds interfaces for blocks whose access rate is a handful of register writes per second, spending engineering effort where no performance exists to be won.

The corrected model: it can cost performance, and where the access rate matters that cost is real and should be measured. For the low-rate control path that most peripherals live on, the cost is unmeasurable and the composability is free. Recognising which of the two a block is on is the actual skill.

10. Interview Reasoning

Because an address map is a statement about location and an interface is a statement about time and signalling, and neither implies the other.

What a strong answer enumerates: the map does not say which wire indicates an access is in progress, how long the address must be held, whether read data is valid in the same cycle or a later one, how the target reports completion, what the initiator does while waiting, how an error is distinguished from a slow success, or what may legally change while a request is outstanding.

The observation that shows real experience: the dangerous disagreements are the ones where both sides are individually reasonable. If a target latches the address when it sees the request and the initiator believes it may change the address immediately after issuing it, neither block is wrong and the pair does not work. A protocol specification exists precisely to make one of those two readings the only legal one.

11. Understanding Check

12. What's Next

This chapter established that an address map and an interface protocol are independent agreements, that private interfaces multiply rather than add, and that the multiplication falls hardest on verification — the part of the work that quietly does not happen.

That argument is strongest in exactly one situation: when the blocks come from different places.

Inside one company, with one team and one core, a house interface is a defensible choice. Everyone can be told the rules, the reviews catch the mistakes, and the interface evolves with the projects that use it. The argument for a published standard gets its force from a situation the chapter has assumed without examining — that the UART, the GPIO and the timer were written by people who have never met, may no longer be reachable, and were not designing for this system.

If reusable hardware blocks are going to come from an open ecosystem rather than from one team, what does that ecosystem actually have to supply beyond the RTL itself — and why is publishing source not sufficient?

Chapter 1.4 — The Open Hardware Movement takes that up: what makes a hardware block genuinely reusable, why source availability and reusability are different properties, and where open IP is honestly weaker than its advocates claim. 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.