SKIP_WCS_LOAD — Preload the WCS and Skip the Microcode Load Phase¶
Full path: Verilog/docs/skip-wcs-load.md
Last updated: 2026-09-28
How to preload the Writable Control Store (WCS) directly and skip the ~573K-cycle
runtime microcode load, behind one compile-time define SKIP_WCS_LOAD. Derived
from a full trace of the LCS_n load state machine, the WCS RAM structure, and the
PROM->WCS data format.
Why do this¶
- Faster boot + faster sim — removes Phase 1 (the
CSAramp 0->017777, ~573K sim ticks every run). - Tang Nano 20K fit — if the load is skipped, the microcode PROM
(
CPU_CS_PROM_19) is never read, so its ~512 Kbit BRAM can be dropped. Combined with the WCS staying in BSRAM, this is what makes the microcode fit the Tang's 828 Kbit (seefpga/tang-nano-20k/BSRAM-BUDGET.md). - NOT a timing fix — it removes a load phase, not a slow path. It is the default on every FPGA build: Nexys, Tang, Basys3, MiSTer, MEGA65, QMTECH and Cmod A7.
How the load phase works (so we can skip it safely)¶
The load is a pure hardware sequence, not microcode:
- The LCS latch in PAL/PAL_44403C.v:73-83 is set by MR (master reset) ->
LCS_n=0 = loading.
- While loading, the CGA sequencer's hardware counter ramps the microaddress
0->017777; at each step PROM data (BLCS_n=0 selects PROM) flows PROM -> TCV ->
WCS, written by the LCS-gated write strobe (PAL_44305D.v:59-60).
- Loading ends when the address wraps 017777->0 (the LUA12 1->0 edge): the only
latch combination not held, so LCS<=0 -> LCS_n=1 = execute.
- At execute, the microcode PC starts at 0 (CGA_MIC_MASEL.v:143-149,
regIW<=0 on MR_n). Address o02001 is not RTL — it is the branch target
inside the microword at WCS address 0. So after load, PC=0 -> microcode ->
o02001.
Key insight for skipping: force the LCS latch to never set. Then LCS_n=1
from cycle 0, BLCS_n=1 (PROM disconnected, normal WCS path), the hardware ramp
never runs, and the load write-strobe is dead. MR_n must still fire (it
resets regIW->0, presets the counter, and drives all master-clear init). PC
starts at 0 and the pre-loaded microword at 0 branches to o02001 exactly as after
a real load.
The WCS is 32 discrete RAM chips (not one array)¶
CPU_CS_WCS_21_22.v instantiates 32 IDT6168A_20 chips (each 4096x4):
- 2 banks: 16C..31C = lower (LUA 0..4095), 16D..31D = upper (LUA 4096..8191)
(bank selected by LUA12 via ELOW_n/EUPP_n).
- 16 nibble slices per bank: chip 16+j holds bits [63-4j : 60-4j]
(16->[63:60] ... 31->[3:0]).
- 1-cycle registered read latency is load-bearing (a combinational read breaks
the TVEC dispatch loop) — preload must not change it.
So the preload needs the 64-bit image demuxed into 32 nibble-wide 4096-deep files.
Data format (PROM -> 64-bit microword) — verified¶
AM27256_45132L.hex= LO byte (7:0),AM27256_45133L.hex= HI byte (15:8), each 32768 bytes, byte indexidx = LUA*4 + RF.- 16-bit read
P(LUA,RF) = (hi[idx]<<8) | lo[idx]. - 64-bit word
= { P(RF=3), P(RF=2), P(RF=1), P(RF=0) }(RF=0 -> [15:0], RF=3 -> [63:48]); straight concatenation, no bit remapping PROM->TCV->WCS.
Generator (done, validated)¶
Code/Microcode/gen_wcs_image.py produces, into
Code/Microcode/wcs/:
- wcs_image.hex — 8192 x 64-bit (unified; for inspection / a single-BRAM WCS).
- wcs_<16..31><C|D>.hex — 32 per-chip nibble files (4096 x 4-bit each).
Validated against the real PROMs: 8192 non-zero words; word[0x0401] (o02001) =
1b80008780203050. Re-run whenever the microcode hex changes.
Two image sets (decided 02-SEP-2026). The boards preload the raw PROM
words from Code/Microcode/wcs/. The simulators use
gen_wcs_image.py --sim, which writes Code/Microcode/wcs-sim/ with the
2024 simulator patch of word 0o2002 (a 64x shorter power-on wait, nothing
else). make test-microcode-sync checks every copy against the set its
directory must hold. Details: docs/nd120-facts.md.
Implementation (three RTL edits + build wiring)¶
-
Preserves BRAM inference (Xilinx + Gowin) and the 1-cycle read latency.Shared/support/IDT6168A_20.v— add preload capability: -
CPU-BOARD-3202/circuit/CPU_CS_WCS_21_22.v— pass a per-chipINIT_FILEto each of the 32 instances, under the define: -
PAL/PAL_44403C.v:73-83— never set the LCS latch when skipping: -
Build wiring — add
-DSKIP_WCS_LOADand put the 32wcs_*.hexfiles on the$readmemhsearch path (copy intosim/,runSim/, and the Vivado/Gowin project dir, like the existing microcode hex). KeepMR_nuntouched. -
(Tang bonus) with
SKIP_WCS_LOAD, alsoifdefout the PROM BRAM inCPU_CS_PROM_19.v(never read) to reclaim ~512 Kbit.
Validation¶
Run the sim with -DSKIP_WCS_LOAD:
- The Phase-1 load ramp (CSA 0->017777, ~cycles 16415..573403) must vanish.
- Execution must start at CSA=0 and reach o02001 almost immediately, then follow
the golden boot path (docs/boot-golden-spec.md) identically from there.
- Diff the post-o02001 CSA path against a normal-boot run: must match.
Risks¶
- Keep
MR_nactive — only neutralise theLCSlatch. SuppressingMR_nbreaksregIWreset, counter preset, and master-clear init. - WCS must be preloaded or the CPU executes garbage — tie the preload and the skip to the same define.
- Start PC = 0, not o02001 — forcing PC to o02001 would skip the master-clear microcode at address 0.
- Confirm no consumer latches a "load happened" flag it later needs (
LCS_nconsumers — MMU,PAL_44305D, CGAILCSN— all expect steady-stateLCS_n=1post-load, so forcing it is fine).
Source references¶
PAL/PAL_44403C.v:73-83,92,100(LCS state machine)CPU_CS_16.v:82,95,111-159(PROM->TCV->WCS routing, WCS instance)CPU_CS_WCS_21_22.v(32 IDT6168A chips),Shared/support/IDT6168A_20.v:58-59CGA_MIC_MASEL.v:143-149(PC reset to 0),CPU_CS_TCV_20.v:42-76(RF->slice)Code/Microcode/gen_wcs_image.py(image generator)