Architecture
Retro-Active is building toward a common bus standard so that peripheral cards, backplanes, and host-CPU boards from different contributors can plug together regardless of how each one is built — discrete logic, PAL/GAL, FPGA, or whatever comes next. This page is an early draft of that standard: it documents where we are today, the thinking behind the current signals, and where the open questions still live. Expect it to change as the community weighs in.
A Protocol, Not a Pinout DRAFT
Most retro expansion buses — the Apple II slot, the original IBM PC ISA bus, and a lot of homebrew 68k buses — are essentially the host CPU’s pins brought out to a connector. That makes cards simple, but it locks the bus to one CPU family forever. A card designed for a 6502 machine can’t drop into a 68000 machine and vice versa, even when the card’s actual job (driving a UART, blitting pixels, talking to an SD card) has nothing to do with the host CPU.
Retro-Active’s bus is closer in spirit to PCI: it defines its own signals, its own timing, and its own transactions, independent of any specific CPU. Each host connects through a small bridge — a handful of 74-series chips, a PAL/GAL, or a simple FPGA block — that translates the host’s native cycles into bus transactions. The bridge is the only CPU-specific part. Past it, every card behaves identically.
We’re not building PCI. The complexity target matches the era — simpler timing, slower clocks, a smaller signal count, and bridges you can draw on a napkin. But the core idea — a CPU-independent protocol with a bridge per host — is what makes the same card work with an 8-, 16-, or 32-bit host, and what lets one driver (recompiled for each CPU via GCC) target them all.
Design Goals
- Implementation-neutral — any build that honours the timing is legal: plain 74-logic cards, PAL/GAL decoders, or FPGAs.
- Host-CPU independent — the bus doesn't assume 6502, 68000, Z80, 8086, or anything else. A small bridge per CPU does the translation.
- Vintage-friendly — tolerant of slow clocks, synchronous or asynchronous host timing, and 8-, 16-, or 32-bit host data paths.
- Modest signal count — few enough lines to fit on a reasonable backplane connector, and possible to draw out by hand.
- Portable drivers — the same C driver (recompiled for the host CPU) works across every system that speaks the bus.
- Open specification — every signal, every register, every timing diagram published under a permissive license.
Signal Lines (Planned)
The bus splits into two kinds of lines: shared lines that run across the entire backplane and every slot sees in common, and per-slot lines that each slot connector carries on its own pins. The tables below are the current working sketch, drawn from the reference implementation. Widths, directions, and even the existence of some lines are likely to change once more than one card is prototyped on more than one host.
Shared backplane lines — one wire, every slot sees the same value:
| Signal | Direction | Width | Purpose |
|---|---|---|---|
CLK | host → cards | 1 | System clock (25 MHz reference) |
RESET_N | host → cards | 1 | Active-low global reset |
ADDR | host → cards | 24 | Address bus (16 MB window) |
DATA | bidir | 32 | Data bus (driven by the host on writes, by the selected slot on reads) |
WSTRB | host → cards | 4 | Write strobe, one bit per byte lane |
RSTRB | host → cards | 1 | Read strobe |
READY | cards → host | 1 | Transaction complete / wait-state release (wire-OR; only the selected slot drives) |
Per-slot connector pins — one wire per physical slot position:
| Signal | Direction | Width | Purpose |
|---|---|---|---|
SLOT_SEL_n | backplane → card | 1 | Active-low chip-select for this slot, decoded from ADDR[19:16] by the backplane |
IRQ_n | card → host | 1 | Active-low interrupt request owned exclusively by this slot |
One open design choice: the bus can either have a fixed native width (with bridges narrowing or widening for smaller or larger hosts, like PCI does) or scale with the host. The current sketch shows a 32-bit data bus; an 8-bit host’s bridge would serialise accesses over multiple cycles. Feedback welcome.
Bridging to a Host CPU
Every host talks to the bus through a bridge. The bridge waits for the CPU to start a memory or IO cycle aimed at a bus address range, translates that cycle into a bus transaction (address, direction, width, data if writing), drives the bus signals with the right timing, and returns data and any wait states back to the CPU when the bus answers.
For slow hosts (a 1 MHz 6502, a 4 MHz Z80) the bridge can be a handful of gates and a PAL. For faster hosts (a 10 MHz 68000, a 25 MHz 386) a bundle of 74-series parts or a small FPGA handles the pipelining. The bridge absorbs every CPU-specific quirk so the rest of the system doesn’t have to care.
Slots and Backplane
The bus is organised around a physical backplane with up to 16 slots. Each slot is identified by a number in the range 0–15 and gets a 64 KB register window in the host's address space.
Slot numbers are encoded directly into the upper bits of the bus address:
| Address bits | Purpose |
|---|---|
ADDR[23:20] | IO-region tag — must be 0x4 for a card cycle |
ADDR[19:16] | Slot number (0 – 15) |
ADDR[15:0] | Per-slot register space (64 KB per card) |
The backplane decodes ADDR[19:16] through a single
74LS154 (4-to-16 line decoder) to produce 16 active-low
SLOT_SEL_n lines, one per connector. The decoder is
enabled only when ADDR[23:20] equals the IO tag and at
least one strobe (WSTRB or RSTRB) is active.
Cards therefore do not need to decode the address themselves;
they just wait for SLOT_SEL_n to go low and use
ADDR[15:0] to pick a register. That keeps the card's
decode logic to a single AND gate or two.
Each card exposes a small bank of memory-mapped registers behind its
slot window. A 16-bit register offset gives plenty of room —
typical cards use only a handful of registers near the bottom of the
window and leave the rest reserved. Writes are accepted via
WSTRB; reads are driven onto DATA on the
same cycle READY is asserted.
See the backplane reference document for the full slot pinout, the decoder schematic, and a connector-pin budget.
Interrupts
Each slot owns its own IRQ_n line back to the host. That
gives up to 16 independently-reported interrupt sources on a 16-slot
backplane — no polling required to tell cards apart, no
wire-ORing, no daisy-chain priority schemes. A card pulls its own
IRQ_n low whenever it has something to report, and
releases it when the host acknowledges.
What happens on the host side depends on the CPU. Each host bridge is paired with a small interrupt controller peripheral that aggregates the per-slot IRQ lines into whatever the CPU's interrupt pins expect:
- 6502 (and similar single-IRQ CPUs): the 16
IRQ_nlines feed an interrupt controller that ORs them onto the CPU's/IRQBpin and exposes a status register telling the CPU which slot fired. The FemtoRV reference implementation already uses this pattern with 32 sources; generalising to 16 slots is a strict subset. - 68000 (and other priority-encoded CPUs): the 16
IRQ_nlines feed a74LS148(or two cascaded for 16 inputs) which produces a 3-bit priority code wired directly to/IPL[2:0]. Slot assignment maps priority levels 1–7 across the 16 slots however the builder likes — typically, low-numbered slots get lower-priority interrupts.
The interrupt controller is a required host-side peripheral — it's part of the platform every bridge assumes — but it lives outside the bridge proper. That separation keeps the bridge's chip count honest (nine chips for a 6502, twelve for a 68k) regardless of how many interrupt sources the system ultimately supports, and it lets a builder put the interrupt controller on its own card if they prefer.
Within a card, multiple internal interrupt sources collapse onto the
single IRQ_n pin via a simple wire-OR or open-drain gate.
The host's interrupt controller handles the "who raised me?" question
at slot granularity; anything finer (which ring buffer filled? which
timer expired?) is the card's responsibility, usually answered by a
read of one of its own status registers.
Example Memory Map
This is one valid memory layout — the one used by the
reference implementation. Builders are free to map memory however they
want, as long as the IO region's slot encoding
(ADDR[23:20] = 0x4, ADDR[19:16] = slot) and
the per-slot register window match the bus spec. The diagram and table
below show what ships on the current reference board today: a 24-bit
address space split between boot ROM, memory-mapped IO, kernel/program
RAM, a framebuffer, and a stack at the top of RAM.
Zooming in on the IO region: 0x400000 is slot 0's base,
0x410000 is slot 1's base, and so on through
0x4F0000 for slot 15. Each card's registers live in the
low 64 KB of its window.
| Range | Size | Purpose |
|---|---|---|
0x000000 – 0x007FFF | 32 KB | BRAM: BIOS / boot ROM |
0x400000 – 0x4FFFFF | 1 MB | IO region — 16 slots × 64 KB each, slot n at 0x4n0000 |
0x800000 – 0x80FFFF | 64 KB | Kernel space |
0x810000 – 0x9FFFFF | ~2 MB | Program space |
0xA00000 – 0xA7CFFF | 512 KB | Framebuffer (640 × 400 × 16 bpp) |
0xA7D000 – 0xEFFFFF | ~4.5 MB | Free heap |
0xF00000 – 0xFFFFF0 | 1 MB | Stack (SDRAM, grows down) |
Reference Implementation
The first working example of this architecture is a Colorlight i5 board (ECP5) running the FemtoRV RV32IMFC soft core at 25 MHz, with an HDMI GPU, a 4-voice 4-operator FM synthesizer plus sampled audio, a PS/2 keyboard controller, and an SD card FAT32 driver. This implementation is FPGA-based because that’s the fastest way to iterate on the spec today — but it’s an implementation, not the spec. A discrete-logic reference build is planned alongside it so the two can keep each other honest: anything that’s easy in the FPGA but impossible in 74-logic needs to change.
Source for the reference implementation lives in the
benpayne/learn-fpga
repository (branch colorlight_i5_support).
Standardization Roadmap
- Phase 1 — document the current bus as-is, warts and all, so others can reproduce it.
- Phase 2 — collect community feedback: what breaks on a 6502 bridge that works on a 68k? Is 16 slots enough, or should the slot field be wider? Does the interrupt controller belong on its own slot card, or on the host board?
- Phase 3 — publish a v1.0 specification with fixed signal names, timing, register conventions, and connector pinout (nominally DIN 41612 96-pin, to match the ~75-signal-count budget with headroom for ground and power).
- Phase 4 — reference cards, bridges, and a devkit: a minimal 4-slot backplane, a bridge per CPU family, a host-side interrupt controller, plus one or two peripheral cards anyone can build.