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."
| policy | read returns | write does | in B3? |
|---|---|---|---|
ACC_RW | the register | stores, per byte lane | no |
ACC_RO | the register | refused with [ERR_O] | no |
ACC_W1C | the register | a 1 clears that bit; a 0 leaves it | no |
ACC_WO | zero | fires a one-clock strobe, stores nothing | no |
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:
// ── 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 0Packed, 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:
// 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.
3. Refusing A Write Is A Local Policy, Not A Rule
A write to a read-only register gets [ERR_O]:
// 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:
[ERR_O]— the write is reported. The master finds out.[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.[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:
// 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.
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:
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 PASS0x11223344 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.
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]; 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.
// 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.
// 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 xfer — the 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:
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.
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:
=== 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 1Offsets 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:
| item | value |
|---|---|
| port size | 32-bit |
| granularity | 8-bit, [SEL_I()] honoured on ACC_RW |
| cycle types | SINGLE 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 granularity | lane 0 only — local policy, matches Chapter 19.5 |
| wait states | WAITS, 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
Related tutorials
- Related topic
Data Masking
Mask first, register semantics second. Measured: a command firing from a lane the transfer never delivered, and a status register surviving a word of ones.
- Related topic
CPU to Peripheral Communication
A CPU reaches hardware outside itself by reading and writing addressed locations, and a peripheral is hardware it cannot execute. Everything a driver does has to be expressed as a read or a write of a location the peripheral answers for — and once more than a couple of peripherals exist, wiring each one to the core separately stops scaling. That is the problem an on-chip bus is the answer to.
- Related topic
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.
- Related topic
FPGA Design Challenges
Six peripherals and two masters on one FPGA is where on-chip integration stops being theory. The decode that must be exhaustive and one-hot, the read multiplexer that grows with every target and carries the critical path, the latency and reset conventions that refuse to agree, and the point at which the fabric rather than the peripherals starts failing timing.
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.
