SDK games are native ARM code running on an RP2350 inside the cartridge. They do not run on the Vectrex's 6809 at all: the cartridge takes over the console's bus and draws by writing the same hardware registers the 6809 would.
The cartridge
The Ultimate Vectrex Multicart 2 (UVMC2) is an RP2350-based cartridge. Its stock firmware shows a menu, reads a .um2 image off the SD card into SRAM, and jumps into it. From that instant the firmware is gone: your image owns the machine, including clocks, pads, both cores and the bus.
| CPU | RP2350, dual Cortex-M33 at 150 MHz |
| Internal SRAM | 520 KB; a loaded image has about 496 KB for code + data + the command list |
| PSRAM | 8 MB on the QSPI bus (chip select CS1 = GPIO47), not mapped by the firmware |
| Cartridge edge | Wired straight to the RP2350's GPIOs |
| Audio | A PT8211 16-bit stereo DAC on a jack (UVMC2 only) |
The ~496 KB is the hard ceiling. Everything that is not write-once data has to fit there.
Debug Cart: the Vectrex Studio Debug Cart is also RP2350-based and runs the same SDK. Its BIOS provides its own menu, the SDK's services through syscalls and RTT output over the debug probe; it has no audio jack.
The .um2 file
A 20-byte little-endian header followed by the raw image:
offset size meaning
0 4 magic "2CMU"
4 4 version = 1
8 4 block count = 1
12 4 load address = 0x20000000
16 4 length, in 32-BIT WORDS (not bytes)
20 ... payloadsdk/tools/package_um2.py writes it and pads the payload to a word boundary. The length is in words, not bytes.
The payload must also contain an RP2350 IMAGE_DEF block within its first 4 KB, or the chip refuses to treat it as an image and boots into nothing. The pico-sdk emits one, which is the main reason the build goes through CMake.
Halt mode: why the game drives the VIA
A Vectrex draws with an analog beam steered by a VIA 6522 at $D000:
- Port A is the X/Y DAC (one 8-bit value at a time);
- Port B picks which integrator the DAC value is routed to (the mux) and carries
/RAMP; - Timer 1 decides how long the integrators run, which is the stroke's length;
- the shift register carries brightness, and
PCRcarries/ZEROand/BLANK.
A normal cartridge answers the 6809's ROM fetches. An SDK image cannot: its code is native ARM, so the 6809 has nothing to execute. Instead the cartridge asserts /HALT permanently and becomes the bus master, writing the VIA registers the 6809 would have written.
The one rule
The 6800-family bus does exactly one access per E period, and the two halves are not interchangeable:
E LOW (first half) the master changes the address. The VIA is not looking.
E HIGH (second half) the VIA decodes; address, CS and R/W must be STABLE.
E FALL (the edge) the write is latched.So: change the address during E low, hold it through E high. Measured on a console, presenting near the falling edge draws; presenting a bit later gives a black screen. A setup-time violation degrades gracefully; a phase violation fails outright.
The inverted E signal (~E) is on cartridge edge pin 12 and free-runs at 1.5 MHz. Its rising edge is E's falling edge, so one edge means both "the previous write landed" and "it is safe to present the next one". That is what the PIO program synchronises on (see Dual core, PIO and DMA).
One E period is 667 ns, so a 50 Hz frame is 30,000 bus cycles. That number is the budget for everything drawn in a frame, and no amount of CPU makes it larger.
GPIO map
GP0-7 D0-D7 GP8-21 A0-A13
GP22 PB6 GP23 /IRQ
GP24 A14 GP25 A15
GP26 R/W GP27 /HALT
GP29 /NMI GP31 CLK (~E)
GP34/35/36/39 SD card: SCK / MOSI / MISO / CS
GP47 PSRAM chip select (QMI CS1n, function F9)
GP43-45 PT8211 audio DAC (UVMC2)The SD pins were measured off the stock firmware over SWD, not read off the schematic: the schematic reading was off by two, and the symptom was a card that never answered.
No reset line, one button. The console's reset button is not on the cartridge edge, and the UVMC2's own button is the RP2350's BOOTSEL. The SDK still sees the console's reset through the VIA, whose timers stop while it is held (see Input).
PSRAM
There are 8 MB on CS1 that the firmware does not map. uvm2_psram_init() resets the chip, checks its ID and enables the XIP window at 0x11000000. Three things to know before you use it:
- Writes to
0x11000000are discarded silently unless the window is marked writable. Silent is the worst kind of failure. - Verify through the uncached alias,
0x15000000. The XIP cache is 16 KB: a 64 KB buffer verifies fine right after it is written (hot cache) and reads back wrong later. - It is good for write-once, read-once data at start-up (a romset, audio samples, a staging buffer) and bad for anything timing-critical. The command list stays in SRAM for exactly that reason.