fpga/rtl/ — the board-independent core¶
The files are not peers:
| File | What it is |
|---|---|
p8x_cpu.v |
the machine. Shared verbatim by the simulation and the board. |
gfx.v |
the graphics display — registers and the drawing engine. Since the SDRAM stages the framebuffer is NOT in here: pixels leave through the arbiter port (e_*) into the in-package SDRAM, and there is no palette (RGB565 direct colour, stage 6). Shared verbatim. |
mdu_core.v |
THE muldiv datapath — the one silicon definition of the signed (a*b)/c contract (stage 8a), instantiated by both wrappers below. |
p8x_mdu.v |
the CPU-facing MDU register wrapper at $FF30 around mdu_core. |
p8x_geom.v |
the geometry engine ($FF40, stage 8b): parameter file, SDRAM edge-list walker, transform + clip + project via its own mdu_core, drawing by mastering gfx.v's registers; owns the page-flip state. |
video_rgb.v |
the pre-SDRAM 480x272 scanout. Superseded on the board by ../tang-nano-20k/sdram/sdram_video.v; kept for the simulation wrappers. |
p8x_soc.v |
a simulation-only wrapper. The board does not use this. |
(The old sc_en single-port-framebuffer contract died with the BSRAM
framebuffer; the SDRAM stack's sharing story — arbiter, stream port,
priorities — lives in ../tang-nano-20k/sdram/
and its STAGE*.md design docs.)
gfx.v is a transliteration of the gpu_* functions in emulator/p8xemu.c, and
the same rule applies to it as to the CPU: the emulator is the golden model, so a
cleverer Bresenham that lights a different pixel is a bug. The GL RTL
battery (emulator/test/c_gl_rtl_test.sh) byte-compares the frames the two
produce, streaming identical GL bytes through the walker into this engine.
Unlike the CPU, the graphics device cannot be cycle-diffed. The emulator draws
instantaneously and never raises BUSY; the RTL takes thousands of clocks and does.
A program that polls GSTAT therefore reads different values on the two by
design, so their CPU traces legitimately diverge. The framebuffer is what must
agree, and nothing else about the engine's timing is visible to software.
p8x_cpu.v is a direct transliteration of the emulator's microcycle loop — the
74181 model, the two-stage shifter, sign-bit V, the condition mux off the previous
word's FCOND, DOE/DLD/PSEL as muxes rather than tri-state. It is
deliberately flat: the TTL cards are regions of one module, not sub-modules,
so its state maps one-to-one onto the emulator's for the cycle-by-cycle diff. See
../docs/architecture.md for that mapping.
p8x_soc.v gives the CPU async-read arrays and modelled I/O, so one microcycle is
one clock. The board cannot do that — block RAM is synchronous and a microcycle
needs two dependent reads (the microcode word first, because its PSEL field
picks the pointer that drives mem_addr, and only then the memory byte). The board
top is ../tang-nano-20k/rtl/p8x_top.v, which
runs three fabric phases per microcycle and gates the core with cen.
So: cen must be tied high by any wrapper that presents single-cycle memory.
p8x_soc.v does exactly that.
Changing anything here¶
p8x_cpu.v is what the co-sim verifies, and it is shared, so an edit reaches both
the simulator and the board. Re-run all three co-sims — they are the regression
test, and a divergence names the exact microcycle:
../sim/run.sh 20000 # monitor boot
../sim/run.sh 60000 isa_test.asm # the original 88 opcodes
../sim/run.sh 200000 "" console_in.txt # driven monitor + console diff
(Graphics: emulator/test/c_gl_rtl_test.sh, the GL battery.)
Touching gfx.v or video_rgb.v additionally needs the two board benches, which
cover what the co-sim structurally cannot:
cd ../tang-nano-20k/sim
iverilog -g2012 -o tb.vvp ../../rtl/video_rgb.v tb_video.v && vvp tb.vvp
iverilog -g2012 -o tb.vvp ../../rtl/video_rgb.v tb_scanout.v && vvp tb.vvp
tb_scanout.v checks which framebuffer pixel reaches which panel pixel.
gfx.sh only checks framebuffer contents and tb_video.v only frame shape,
so the mapping between them went untested — and a shift-width bug that blanked
half of every byte reached hardware through exactly that gap.
The co-sim's hold pattern is irregular on purpose. The engine's pixel loop is six cycles, so the board's regular one-in-three hold has a fixed phase relationship with it: a given collision either always happens or never does, and a bug depending on one can be invisible in simulation while failing half the time on hardware. Caveat, recorded honestly: reintroducing a known pending-write bug did not make the frame diff fail, with either pattern, so this coverage is unproven.
Then rebuild the board — the co-sim cannot catch anything that is purely about the substrate, such as timing closure, block-RAM inference, or the phase sequencer:
../tang-nano-20k/build.sh cpu # p8x_cpu.v changes
../tang-nano-20k/build.sh lcd # ... or gfx.v / video_rgb.v: the `cpu`
# target does NOT compile those two
That distinction matters. gfx.v and video_rgb.v live here, but only the lcd
target passes them to yosys, so build.sh cpu after editing the graphics
rebuilds a bitstream that does not contain your change — and loads it without
complaint. Substrate bugs that only the board shows have been found this way more
than once: block RAM inferring as true dual port (8 blocks, would not place) and
a register driven from two always blocks, neither visible in simulation.
Keep board-specific things out of this directory: pins, PLLs, BRAM style, and
peripherals belong in ../tang-nano-20k/. The point of the split is that the CPU
the co-sim proves is the identical file the board runs.