An FPGA twin with eight card slots

/ DINO / updated 2026-10-30 fpga twin versa cards verilog

DINO runs on an FPGA again. It’s a new twin, generated from the same KiCad netlists the Rev 1.0 board is routed from: one Verilog instance per chip, wired by pin number, so a pin that’s wrong in the drawing is wrong here too. It runs on a Lattice ECP5-5G Versa board at 1.0 MHz CLK, with the Versa’s DIP switches as card zero, its LEDs as OB and its USB serial wired to the serial card.

The mainboard’s eight slot sockets, J1 to J8, are eight copies of one card interface. A card drawn in KiCad and a card written by hand in Verilog plug in the same way. The first hand-written one is a card that answers DE AD BE EF, and SCELBAL reads it with PEEK.

SCELBAL on the Versa reading the DEADBEEF card in slot J3: 222 173 190 239 is DE AD BE EF. 0x5001 and 0x6000 read 255.
SCELBAL on the Versa reading the DEADBEEF card in slot J3: 222 173 190 239 is DE AD BE EF. 0x5001 and 0x6000 read 255.

So there are three DINOs now: the breadboard, netsim (the same netlist simulated with gate delays in Icarus) and this one. The FPGA twin proves the logic of the drawing, not its timing. Gates are zero-delay and everything moves on a 48 MHz system clock, so the analog stuff that bit the breadboard can’t show up here.

Why bother while the boards are at fab

Rev 1.0 is at PCBWay. I want to work on the next cards (CF, maybe a floppy hack) before there’s copper to plug them into, and anything I prove in RTL has to have a known route to a real card. That means the twin’s slots have to look like the real backplane, pin for pin.

The first build ran at 93.75 kHz and I thought DINO was slow

The first version of the twin divided 12 MHz down to a 93.75 kHz CPU clock, about 11 times slower than the breadboard’s 1.024 MHz. I didn’t catch that it was a slowdown and read the sluggishness as DINO’s. A SCELBAL line with a float PRINT took 2.5 minutes. Serial stress tests failed all over the place: any line over 15 characters typed back to back lost everything past the 16-byte receive ring.

Moving the system clock to 48 MHz put CLK at 1.0 MHz, and most of those failures went away:

Test93.75 kHz1.0 MHz
One line typed back to back, up to 73 charsdropped past 15pass
Program pasted, 0 or 2 ms per chardropsdrops
Program pasted, 5 or 10 ms per chardropspass
Program pasted, 20 or 50 ms per charnot runpass
200 lines of PRINT I,I*Itest timed outpass, 5129 bytes
Typing while it printstest timed outpass
Ctrl-C out of GOTO 10, warm start, LISTpasspass

Pasting at 0 to 2 ms per character still drops input, because the 16-byte ring has no flow control. Expect the same on the real machine. A terminal set to 5 ms per character pastes cleanly.

With input echo-paced, SCELBAL on the Versa is byte-identical to the original 1974 SCELBAL running on an 8008 emulator: 11 sessions, 2409 bytes. PROG_isa reads 0xB4.

One card interface for all eight slots

Every card, generated or hand-written, has exactly the same ports. For each of the 37 edge pins that isn’t a power rail, named by pin number and signal (e8_W0, e39_M11), there’s what the rest of the machine drives on the pin and whether anything does, plus what the card drives and whether it does. There’s no inout anywhere, so the whole thing synthesizes, and any time two things drive the same net the twin raises a contention flag.

SlotCardKindWindow
J1card zero, DIP switchKiCad0x4000
J2serial card, 16550KiCad0x4800
J3DEADBEEFhand-written Verilog0x5000
J4-J8empty

A slot is a position, not an address. Each card decodes its own 2K window with a ‘138 on M11-M13, the same way the drawn cards do, so the same card works from J3 or J8. Adding a card is a Verilog file copied from the prototype card, an entry with its oracle model, and one line in the slot table.

DEADBEEF in J3

The DEADBEEF card returns DE AD BE EF at 0x5000, 0x5555, 0x52AA and 0x57FF and 0xFF everywhere else in its window. Between those four addresses every one of M0-M10 toggles, so a stuck or swapped address line reads a wrong byte. A ROM image reads the four cells, two 0xFF cells and one address in an empty slot’s window, and shows 0x5A on OB if all seven match. On the Versa the LEDs showed 0x5A.

The serial card got its edge connector

The serial card’s schematic never had an edge connector. Its bus signals were just labels. I added J2, a DINO_BUS_EDGE, with each label the sheet already had sitting on its pin. The netlist didn’t change apart from J2, and ERC went from 34 items to 3; the 3 left were there before.

The serial card's new edge connector, J2. Each bus label the sheet already had sits on its pin; the rails are global labels and everything the card doesn't take is no-connect.
The serial card’s new edge connector, J2. Each bus label the sheet already had sits on its pin; the rails are global labels and everything the card doesn’t take is no-connect.

Mandelbrot on the Versa

The Mandelbrot RAM program from 10-06 loads with L and runs with G 8100, same as on the breadboard, and the picture is byte-identical to the reference model.

G 8100 on the FPGA twin, Versa board, 1.0 MHz CLK. 2706 bytes, identical to the reference model.
G 8100 on the FPGA twin, Versa board, 1.0 MHz CLK. 2706 bytes, identical to the reference model.

It took 8.95 s from G to the prompt, which matches the 8,717,660 T-states the oracle counts for the run.

What checks it

CheckResult
Every coverage image, 3 power-up seeds, against the oracle132/132, no contention
Chip pins against all three netlists, every part model against netsim’s32/32
Slots, cards, card interface, oracle with the slot table37/37
Deliberately planted faults (pin swaps, a crossed edge pin, a miswired slot, part model bugs)17/17 caught
Whole host test suite725 passed
Versa bitstream timing123.6 MHz Fmax against a 48 MHz clock

Rebuilding the twin around the slots left every result from before it byte-identical, down to the tick counts.

The breadboard runs these programs, and netsim and the FPGA twin run them from the Rev 1.0 netlist and agree, so I feel good enough about Rev 1.0 to order the boards.

Open threads