Skip to content
VLSI Mentor

Wishbone · Module 23

Memory Controller Design

Wishbone Classic has no stall signal, so a controller stealing cycles for refresh is indistinguishable from a slow memory. Thirty per cent of the bus's waiting went to work it never requested.

Every slave in this curriculum so far has been reactive: it is asked, it answers, and between requests it does nothing. wb_shared_ram, reused from Chapter 16.4 through Module 22, is an array, a wait counter and an [ACK_O].

A memory controller is not like that. It has work of its own — refresh, scrubbing, calibration, training — that the bus never asked for and cannot see. And Wishbone Classic gives it exactly one way to say so.

There is no stall signal in Wishbone Classic. No READY, no VALID to withdraw, no back-pressure channel. A slave that needs time says so by withholding all three terminations, and that is the whole vocabulary.

1. Model Versus Controller

wb_shared_ram (a model)wb_mem_ctrl (a controller)
purposeproduce traffic for measurementbe the subject of the measurement
latencya fixed WAITS countera state machine doing real work
agenda of its ownnonerefresh, on its own timer
can it collide with a transfer?noyes, and that is the interesting case

A model is exactly right for generating traffic and exactly wrong as a teaching example of a controller, because nothing inside it ever wants the bus for itself.

2. The Only Thing Classic Lets A Slave Say

The comment at the top of the module is the design rationale, and it is short because the protocol leaves so little room:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── WHAT THE BUS CAN AND CANNOT SEE ─────────────────────────────────────
// There is no stall signal in Wishbone Classic. A slave that needs time
// says so by withholding all three terminations, and RULE 3.60 obliges
// the master to hold the entire request still while it waits. So a
// refresh is INDISTINGUISHABLE FROM A SLOW MEMORY from the master's side -
// the wait states look identical.

RULE 3.60 is what makes this survivable. Because the master must hold [ADR_O], [DAT_O()], [SEL_O()] and [WE_O] still for as long as [STB_O] stands unanswered, a slave can take an arbitrary number of clocks and come back to a request that has not moved. Without that rule, withholding the termination would be useless — the question would be gone by the time the slave was ready to answer it.

Compare what other protocols spend on the same problem, which Chapter 20.3 and Chapter 21.2 measured:

protocolhow a slave says "not yet"cost
Wishbone Classicwithhold [ACK_O]nothing — no extra wire
AXIde-assert READY on the relevant channela READY per channel, five channels
APBassert PSLVERR-adjacent PREADY lowone wire, but only in the ACCESS phase

Wishbone's answer costs zero signals, and the price is that the bus cannot distinguish why it is waiting. Which is the next section.

3. The Measurement: Refresh Is Invisible

Two controllers, identical but for REFRESH_EVERY. Same twelve accesses.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      rig              acks  refreshes  work clks  stolen clks
      refresh off       12       0          24          0
      refresh on        12       6         24          10

Both completed all twelve. Both were conformant. One took ten extra clocks doing something the bus never requested.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    WHAT THE MASTER CAN SEE
      work clocks   (the access itself)   24
      stolen clocks (the refresh)         10
      -> the CONTROLLER can tell them apart and the BUS
         cannot. That asymmetry is why a memory controller
         exports counters.

That asymmetry is the reason real memory controllers export performance counters. The information exists; the protocol has no field to carry it.

Chapter 22.2 decomposed transfer latency into ISSUE, PRESENT, ANSWER and RECOVER. Refresh clocks and access clocks both land in PRESENT, and nothing in that decomposition can separate them — which is a real limit of that method, stated here rather than glossed.

A Wishbone request that collides with a refresh. On cycle 0 the master asserts CYC_I and STB_I with a stable address. The controller enters its WORK state and begins the access. On cycle 2 a refresh becomes due while the access is still in flight — a collision. The correct controller records the collision and continues, asserting ACK_O on cycle 4 when the access completes, then refreshes afterwards. From the master's side cycles 1 through 3 are simply wait states, identical in appearance to a slow memory, because Wishbone Classic has no way to distinguish them.refresh due mid-access — a collisionrefresh due mid-access — acollisionaccess finishes first — RULE 3.50access finishes first —RULE 3.50CLK_ICYC_ISTB_IstateWORKWORKWORKWORKWORKWORKRFSHRFSHRFSHIDLErfsh dueACK_Ot0t1t2t3t4t5t6t7t8t9

4. Three States, And Which One Refresh Is Allowed To Win

The controller has three states, and the entire design is the question of which state a refresh is allowed to interrupt.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  localparam logic [1:0] M_IDLE = 2'd0,
                         M_WORK = 2'd1,
                         M_RFSH = 2'd2;

In M_IDLE nothing is in flight, so a refresh takes priority over accepting a new command without argument:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
        // ── IDLE: free. Take a refresh if one is due, otherwise take a
        //    command. Refresh wins here because nothing is in flight. ──
        M_IDLE: begin
          if (refresh_due) begin
            st_q     <= M_RFSH;
            cnt_q    <= REFRESH_LEN[7:0];
            rtimer_q <= 16'd0;
            nrf_q    <= nrf_q + 16'd1;
          end else if (xfer) begin

Refresh wins in IDLE and must lose in WORK. That single asymmetry is the correct design, and Section 5 shows what the other choice costs.

In M_RFSH, a presented request simply goes unanswered — and the controller counts the fact rather than hiding it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
        // ── RFSH: the controller is busy with itself. Any presented
        //    request simply does not get answered, which is the only
        //    thing Classic lets a slave say. ──
        default: begin
          if (xfer) nsteal_q <= nsteal_q + 16'd1;

There is a subtle trap in the parameters themselves, found while building this chapter's negative control. Set REFRESH_EVERY shorter than REFRESH_LEN + ACCESS_LAT and the controller returns from each refresh to find the next one already due.

One request, presented and held for 200 clocks — which is exactly what RULE 3.60 obliges a conformant master to do:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM K - a refresh schedule that never yields ===

      REFRESH_EVERY  REFRESH_LEN  ACCESS_LAT   acks  refreshes  viol
            6             2           4          20       21       0
            3             2           4           1       50       0

      READ THE 1 CAREFULLY - IT IS NOT ZERO.
      The one access that completed is the one that began
      BEFORE the refresh timer first came due. After that
      the controller returns from every refresh to find
      the next already due, and never reaches M_WORK
      again. It is starved, not dead.

One access in two hundred clocks against twenty, and zero protocol violations.

The 1 is the dangerous part. A controller that answered nothing would be found in minutes. This one answers — once — so a smoke test asking "did the memory come back?" gets a yes, and throughput is down twentyfold with every check in this module still passing.

A refresh schedule is a real-time constraint disguised as a parameter, and nothing in the protocol, the elaborator or the conformance monitor will tell you that you have set it wrong.

Inside the memory controller. The Wishbone port supplies CYC_I and STB_I, which are ANDed into a single transfer-active signal, and the address, write data and byte selects. A free-running refresh timer counts clocks since the last refresh and raises a refresh-due flag when it reaches the configured period. The three-state control machine takes both inputs: in IDLE it starts a refresh if one is due and otherwise accepts a command; in WORK it counts down the access latency and, critically, ignores a refresh that becomes due so that the in-flight access is finished first; in REFRESH it counts down the refresh length and answers nothing at all. The termination logic asserts ACK_O only when the machine is in WORK with its counter exhausted and the request is still presented. Four counters observe what the bus cannot see: refreshes taken, work clocks, stolen clocks, and collisions.Wishbone portCYC_I, STB_I, ADR_I, DAT_I,SEL_IxferCYC_I AND STB_I — RULE 3.30refresh timerfree-running, raisesrefresh_dueIDLE / WORK / RFSHrefresh wins in IDLE, losesin WORKstorage arraywritten per byte lane onACKACK_OWORK and counter zero andpresentedcounterswork, stolen, collisions,refreshes12

The arithmetic of stolen time

For the measured rig — twelve accesses, ACCESS_LAT clocks each, refreshing six times:

quantityclocksshare
work (the accesses themselves)2470%
stolen (refresh, with a request standing)1030%
total the bus waited34100%

Thirty per cent of the bus's waiting was spent on something it never asked for. A designer profiling this system from the master's side sees a slave with an average of 2.8 wait states and no way at all to discover that 0.8 of them are refresh. The counters in Section 6 exist because that number is otherwise unobtainable.

4. The Collision, Which Is The Whole Chapter

A refresh becoming due while a transfer is in flight is where the design decision lives. The controller can finish the access and refresh afterwards, or abandon the access and refresh now.

RULE 3.50 decides it:

"SLAVE interfaces MUST be designed so that the [ACK_O], [ERR_O], and [RTY_O] signals are asserted and negated in response to the assertion and negation of [STB_I]."

A request that is still presented is still owed an answer. So the correct controller records the collision and carries on:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
        // ── WORK: the access is in flight. A refresh that becomes due
        //    now is a COLLISION. The correct controller records it and
        //    waits; REFRESH_PREEMPTS abandons the transfer. ──
        M_WORK: begin
          if (refresh_due) ncol_q <= ncol_q + 16'd1;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    THE COLLISION
      refreshes that became due mid-transfer: 16
      -> the correct controller FINISHED the access and
         refreshed afterwards. RULE 3.50 is why: the
         terminations are "asserted and negated in
         response to the assertion and negation of
         [STB_I]", and a request still presented is still
         owed an answer.

Sixteen collisions across twelve transfers, every one of them handled by finishing first. The refresh timer keeps counting; the refresh happens late; nothing is lost.

5. The Defect, And What It Proves About Conformance

REFRESH_PREEMPTS does the other thing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
          if (REFRESH_PREEMPTS && refresh_due) begin
            // the defect: the master is still presenting, and will now
            // wait forever, because nothing will ever answer it
            st_q     <= M_RFSH;
            cnt_q    <= REFRESH_LEN[7:0];

Run it against a master that gives up when the correct controller answers, and the result is stark:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      AND THE ROW THAT MATTERS MOST: mem ctrl, PREEMPTS
      completed 0 of 12 transfers against the correct
      controller's 12, and scored 0 protocol violations.
      ZERO. 12 TRANSFERS WERE SILENTLY LOST AND THE BUS
      STAYED LEGAL THE WHOLE TIME.

Zero protocol violations. Twelve lost transfers. Every one of the five Classic rules is satisfied, and the conformance monitor reports the defective controller as clean.

Be precise about why, because the obvious explanation is wrong:

It is not a hang. The controller abandons the access, refreshes, returns, and starts the access over. Against a master that holds its request — which RULE 3.60 obliges it to do — the transfer would complete late, not never. Against this testbench's master, which withdrew [STB_I] as soon as the correct controller answered, nothing was owed and nothing was reported, because RULE 3.50 ties the termination to the negation of [STB_I] as much as the assertion.

So the same defect is a latency bug against one master and a data-loss bug against another, and no rule in B3 distinguishes them.

what B3 provideswhat it does not
the termination must answer the requestany bound on how long that takes
the request must be held while unansweredany timeout
one termination at a timeany way to report "I gave up"

B3 specifies no timeout of any kind. Chapter 12.6 builds the master-side watchdog that this absence forces on you — and the reason it belongs to the master is that the slave has no signal with which to raise the alarm.

6. What The Controller Exports, And Why

Because the bus cannot see any of this, the controller says it out of band:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // observation - this is what makes the chapter measurable
  output logic          refreshing_o,
  output logic [15:0]   refreshes_o,
  output logic [15:0]   acks_o,
  output logic [15:0]   stolen_clocks_o,   // wait clocks caused by refresh
  output logic [15:0]   work_clocks_o,     // wait clocks caused by access
  output logic [15:0]   collisions_o       // refreshes due mid-transfer

stolen_clocks_o and work_clocks_o are the same thing to the bus and different things to the designer. That distinction is the entire justification for the port list above, and it is the shape every real DRAM controller's performance-counter block takes.

An honest note on the counters themselves: they are simulation instrumentation. In silicon they would be readable registers — which is a register bank, which is Chapter 23.3, and wiring them up is left there rather than duplicated here.

7. Conformance Under Stress

Both controllers, monitored throughout:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    CONFORMANCE
      rig            violations  phases
      refresh off        0          12
      refresh on         0          12
      -> both clean. Stealing cycles is not a violation;
         it is the only vocabulary Classic gives a slave
         for "not yet".

Stealing ten clocks from the bus is not a protocol violation. That deserves emphasis because it is counter-intuitive: a slave that makes every access 80% slower is fully conformant, and a conformance suite will never say a word about it. Conformance and performance are orthogonal, and Chapter 18.3 made the same point from the other direction when Module 17's checker passed five broken interconnects.

8. The Datasheet

itemvalueauthority
port size32-bitRULE 2.15
granularity8-bit via [SEL_I()]RULE 2.15
cycle typesSINGLE READ, SINGLE WRITERULE 2.15
[ERR_O]never assertedlocal policy
[RTY_O]never assertedlocal policy — see below
access latencyACCESS_LAT clockslocal policy
refreshREFRESH_LEN clocks every REFRESH_EVERYlocal policy
worst-case latencyACCESS_LAT + REFRESH_LENderived, and B3 bounds nothing

[RTY_O] deserves a word. A controller that is busy refreshing is precisely the situation [RTY_O] describes — "not now, ask again". This one does not use it, and the reason is a trade worth naming: [RTY_O] returns the bus to the master immediately, which is better for a shared bus with other traffic and worse for a single master that will simply retry and burn a full cycle doing it. Wait states keep the master parked; retry sends it away. Neither is more conformant. The datasheet has to say which you built.

9. What This Controller Is Not

Stated plainly, because "memory controller" invites assumptions:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── WHAT THIS MODULE DELIBERATELY DOES NOT DO ───────────────────────────
//   no banking, no row/column addressing, no precharge, no CAS latency
//   no read/write turnaround penalty
//   no burst or CTI support

Those belong to a DRAM controller and to the DDR track. This module teaches one thing: that a slave with its own agenda still owes the bus an answer, and how Classic lets it say "not yet".


Next: Chapter 23.5 — Address Decoder RTL leaves the slave entirely. B3 never describes a decoder, but it does say whose job it is — and then declines to say what should happen to an address that belongs to nobody.

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.