Skip to content
VLSI Mentor

Wishbone · Module 23

Register Bank Design

Four access policies B3 never names, expressed as one table parameter so adding a register is adding a row. The write-only side effect must fire once — the defect fires WAITS+1 times and is invisible at zero waits.

Chapter 19.5 built a register bank the way almost every real one starts life: four registers, four behaviours, each hand-written in its own arm of a case statement. It was correct. It also does not scale, and the way it fails to scale is instructive — adding a fifth register means editing three separate case statements and hoping you found all of them.

This chapter builds the same peripheral as a generator. The access policies become a parameter, the behaviour is derived from it, and adding a register is adding a row to a table.

That difference — a peripheral versus a peripheral generator — is the entire content of this chapter, and it is worth more than any individual register it produces.

1. None Of This Vocabulary Is In Wishbone B3

Before writing a line, be clear about where the authority comes from, because for this chapter there almost isn't any.

B3 describes cycles, not registers. It never says what a write to a read-only location should do. It never defines write-one-to-clear. It does not contain the concept of a register bank at all. Search it for "read-only" and you will find nothing relevant.

So the four policies below are this module's vocabulary, invented here, and the one rule that applies is the one that makes you write them down:

RULE 2.00: "Each WISHBONE compatible IP core MUST include a WISHBONE DATASHEET as part of the IP core documentation."

policyread returnswrite doesin B3?
ACC_RWthe registerstores, per byte laneno
ACC_ROthe registerrefused with [ERR_O]no
ACC_W1Cthe registera 1 clears that bit; a 0 leaves itno
ACC_WOzerofires a one-clock strobe, stores nothingno

Four rows, four times "no". An integrator who receives this core and is not given that table cannot use it, and RULE 2.00 is why handing it over is an obligation rather than a courtesy.

2. The Table Is The Design

The policies are a packed vector of 2-bit codes, low index first:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── THE TABLE ───────────────────────────────────────────────────────────
// ACCESS is a packed array of 2-bit codes, low index = offset 0:
//
//   localparam logic [2*N-1:0] MY_ACCESS = { ACC_WO,   // offset 3
//                                            ACC_W1C,  // offset 2
//                                            ACC_RO,   // offset 1
//                                            ACC_RW }; // offset 0

Packed, not unpacked, and for a boring reason worth stating: Icarus Verilog -g2012 will not accept an unpacked array as a module parameter. A packed vector survives every tool this curriculum has used. Portability beat elegance, and that is the right way round for a core you intend somebody else to instantiate.

Reading a policy out of the table is one line:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // the policy for the addressed register, read out of the table
  logic [1:0] pol;
  assign pol = ACCESS[2*off +: 2];

Everything downstream is derived from pol. There is no case statement over register names anywhere in the module.

The table-driven register bank. A Wishbone request arrives carrying address, write data, byte selects and the write-enable. The low address bits form an offset, which indexes two things in parallel: the ACCESS table, which yields a two-bit policy code for that register, and the register file itself. The policy code drives three decisions. The termination logic decides between ACK_O and ERR_O, refusing a write to a read-only or out-of-range register. The read multiplexer decides whether to return the live register or zero, returning zero for a write-only register. The write logic decides whether to store per byte lane, to clear bits for a write-one-to-clear register, or to store nothing and fire a side-effect strobe for a write-only register. A separate hardware-set port can set bits in a write-one-to-clear register at any time, independent of the bus.Wishbone requestADR_I, DAT_I, SEL_I, WE_IoffsetADR_I[3:0] — partial decodeACCESS table2 bits per registerregister fileN registersterminationACK_O or ERR_O — RULE 3.45read muxlive value, or zero for WOwrite logicstore / clear / firehardware set portsets W1C bits off-bus12

3. Refusing A Write Is A Local Policy, Not A Rule

A write to a read-only register gets [ERR_O]:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // A write to a read-only register is refused. That is a LOCAL POLICY -
  // PERMISSION 3.20 says "This specification does not dictate what the
  // MASTER does in response to [ERR_I]", and says nothing at all about
  // when a SLAVE should raise it.
  logic refuse;
  assign refuse = xfer && waited && we_i && (!in_range || (pol == ACC_RO));

Read that comment carefully, because it makes two separate points. PERMISSION 3.20 declines to say what a master does with [ERR_I]. And B3 never says when a slave should raise [ERR_O] in the first place — that absence is larger and less often noticed.

Three defensible policies exist for a write to a read-only register, and this bank implements the first:

  1. [ERR_O] — the write is reported. The master finds out.
  2. [ACK_O] and discard — the write silently vanishes. Common in real silicon, and it is why "I wrote it and it didn't take" is a recurring peripheral bug report.
  3. [ACK_O] and store anyway — the register was never really read-only.

Policy 1 is the only one that tells anybody. It is still a choice, and the datasheet has to name it.

[ERR_O] and [ACK_O] are then mutually exclusive by construction, which is RULE 3.45 satisfied structurally rather than by inspection:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // RULE 3.45: one-hot. ack and err are mutually exclusive by construction.
  assign ack_o = xfer && waited && !refuse;
  assign err_o = refuse;

4. Byte Lanes Are Not Optional

RULE 3.60 puts [SEL_O()] in the set of signals [STB_O] qualifies, alongside [ADR_O], [DAT_O()] and [WE_O]. A master that asserts [SEL_O()] = 4'b0010 is asking for one byte to change, and a slave that writes all four has corrupted three of them.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
          ACC_RW:
            for (b = 0; b < SW; b = b + 1)
              if (sel_i[b]) reg_q[off[2:0]][b*8 +: 8] <= dat_i[b*8 +: 8];

The simulation checks it end to end — write a full word, then a single lane, then read back:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      access                       op  expected    got         err  verdict
      write RW word                wr  0x00000000  0x00000000  ok   PASS
      read  RW                     rd  0x11223344  0x11223344  ok   PASS
      write RW byte lane 1         wr  0x00000000  0x00000000  ok   PASS
      read  RW after lane          rd  0x1122ee44  0x1122ee44  ok   PASS

0x11223344 became 0x1122ee44. One lane moved; three did not. Chapter 19.4 measured what it costs a system when a master cannot express a single-lane write and has to do read-modify-write instead.

5. Write-One-To-Clear, And The Race Inside It

ACC_W1C is the policy that exists because of interrupts. Hardware sets a flag; software clears it by writing a 1 to that bit; writing a 0 must leave it alone, so that a read-modify-write cannot destroy a flag that arrived between the read and the write.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
          ACC_W1C:
            // writing a 1 clears; writing a 0 leaves alone. Lane 0 only,
            // which is a stated LOCAL POLICY and matches Chapter 19.5.
            if (sel_i[0])
              reg_q[off[2:0]][7:0] <= reg_q[off[2:0]][7:0] & ~dat_i[7:0];
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    W1C: hardware sets, the bus clears
      hardware set bits 3 and 1        reg now 0x0a
      write 0x02 (clear bit 1)         reg now 0x08
      write 0x00 (a plain write)       reg now 0x08
      -> writing zero left it set. That is the whole
         point: a read-modify-write cannot accidentally
         clear a flag it did not know about.

Now the hard part. Hardware can set a bit on the same clock the bus is clearing one. Get this wrong and interrupts disappear under load and nowhere else.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      // hardware sets W1C bits whether or not the bus is looking. A flag
      // set on the same clock the bus clears it must SURVIVE - hardware
      // wins - which is why this is one expression and not two
      // assignments. Chapter 19.5 found the two-assignment version
      // silently losing events.
      if (set_i != 8'd0 && set_is_w1c)
        reg_q[set_idx_i[2:0]][7:0] <=
          reg_q[set_idx_i[2:0]][7:0] | set_i;

The two-assignment version — clear in one if, set in another — is not a race in simulation. It is a defined, deterministic, silent loss: the last non-blocking assignment to a variable wins, so whichever branch is written second overwrites the other. Write the clear second and every event arriving during a clear is destroyed. Nothing reports it. The bus transaction succeeded.

Hardware must win, and it wins here because it is the later assignment in the same always_ff — a deliberate ordering, not an accident.

6. The Side Effect Must Fire Exactly Once

ACC_WO stores nothing and produces a one-clock strobe. A FIFO push, a DMA kick, a "transmit this byte" command. It must fire once per write, and getting that wrong is the defect this chapter reproduces on purpose.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // The side effect fires on the ACCEPTED clock. FIRE_ON_STB moves it to
  // the presented clock and fires WAITS+1 times - Chapter 19.5's defect.
  logic is_wo_write;
  assign is_wo_write = xfer && we_i && in_range && (pol == ACC_WO);
  assign fire_o      = FIRE_ON_STB ? is_wo_write : (ack_o && we_i
                                     && in_range && (pol == ACC_WO));

The difference is ack_o versus xferthe clock the transfer was accepted versus every clock it was presented. With zero wait states those are the same clock and the two versions are indistinguishable:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    WO: the side effect fires ONCE, on the accepted clock
      zero wait states   fires 1   (correct)
      two  wait states   fires 1   (correct)
      FIRE_ON_STB        fires 3   (the Ch 19.5 defect)

Three side effects for one write. Three bytes transmitted. Three DMA descriptors consumed. And the count is exactly WAITS + 1, so the bug's severity is set by the slave's own latency — it gets worse precisely when the system is under load.

Zero wait states hides it completely. This is the recurring shape of every wait-state defect in this curriculum: the fast path is the one that tests clean.

A write-only register with two wait states, comparing the correct side-effect strobe against the FIRE_ON_STB defect. CYC_I and STB_I are asserted from cycle 0 and held through cycle 3 because RULE 3.60 obliges the master to hold the request while it is unanswered. The slave asserts ACK_O only on cycle 3, after two wait states. The correct fire strobe pulses once, on cycle 3, the clock the transfer was accepted. The defective strobe pulses on cycles 1, 2 and 3 — every clock the request was presented — producing three side effects for one write.presented — defect fires herepresented — defect fireshereaccepted — correct fires hereaccepted — correct fireshereCLK_ISTB_IWE_IACK_Ofire okfire badt0t1t2t3t4t5t6t7t8t9

7. Proving The Parameterisation Actually Parameterises

A generator that has only ever been instantiated once is a hand-written module with extra syntax. So the same RTL was instantiated twice — four registers and six — with nothing changed but the table and N:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM E - the same RTL, six registers ===
    TBL6 adds two rows and changes N. No other edit.

      offset  policy   4-reg bank        6-reg bank
        0       RW       ok                ok
        1       RO       ok                ok
        2       W1C      ok                ok
        3       WO       ok                ok
        4       W1C      ok                ok
        5       RW       ok                ok

      acks    4-reg 21   6-reg 22
      refused 4-reg 2   6-reg 1

Offsets 4 and 5 are out of range for the four-register bank and in range for the six-register one. The four-register instance refused two accesses; the six-register instance refused one. Same RTL, no edits. Chapter 19.5's hand-written bank would have needed three case statements changed in step to achieve that, and the failure mode of changing two of the three is a register that reads correctly and writes nowhere.

8. The Datasheet, Which Is The Deliverable

RULE 2.15 requires the datasheet to state the port size, the granularity, the supported cycle types, and the master's reaction to [ERR_I] and [RTY_I]. For this bank:

itemvalue
port size32-bit
granularity8-bit, [SEL_I()] honoured on ACC_RW
cycle typesSINGLE READ, SINGLE WRITE
[RTY_O]never asserted — a register bank is never busy
[ERR_O]asserted on a write to ACC_RO and on any out-of-range access — local policy
ACC_W1C granularitylane 0 only — local policy, matches Chapter 19.5
wait statesWAITS, default 0

Two rows of that table say "local policy" and one says "never". Those three rows are the ones an integrator actually needs, and they are exactly the rows that B3 cannot supply.

9. What This Bank Does Not Do

  • No [RTY_O]. Stated above; a register bank has nothing to be busy with.
  • No burst or [CTI_I()]. Classic only.
  • No shadowing. A read returns the live register, not a snapshot. A counter read while it increments returns a value that was true for one clock.
  • No address decoding. The bank uses [ADR_I][3:0] and trusts the interconnect for the rest — which is B3's own Partial Address Decoding model, and Chapter 23.5 builds the other half.

Next: Chapter 23.4 — Memory Controller Design builds a slave with an agenda of its own. A register bank answers when asked; a memory controller sometimes needs the bus to wait while it does something the bus never asked for — and Wishbone Classic gives it exactly one way to say so.

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.