# VT1200 execution harness — results Companion to `README.md` in this directory. This records what was built, the exact commands, the evidence, and what was learned about the two font questions. ## 1. What was set up A minimal MAME machine for the VT1200 firmware, built as a single-driver subtarget of MAME 0.289: | File | Purpose | |---|---| | `harness/mame/vt1200.cpp` | the driver (TMS34010 + RAM + ROM) | | `harness/mame/build.sh` | build wrapper (`-j8`, registers the driver) | | `harness/mame/trace.lua` | headless run: trace execution from the reset vector | | `harness/mame/overflow.lua` | headless run: exercise `pcfReadBitmaps` `n*4` | | `harness/mame/roms/vt1200/` | MAME ROM dir (symlink to `vt1200_v22.bin`) | MAME source lives outside the project (disposable) at `/home/arti/code/k-space/mame0289` (shallow clone of tag `mame0289`). The driver is deliberately incomplete: only the CPU, 4 MiB of RAM at bit address 0, and the ROM are modelled. LANCE Ethernet, the DUARTs and the board peripherals are **not** modelled, so the terminal never boots to the X server. That is fine: the goal is execution visibility, and reset + breakpoints + single-step + trace are enough. ### Address model (the thing that had to be right) `memory_access<32,16,3>` ⇒ the address-map unit is **1 bit**. The ROM is mapped at bit `0x3f200000` (its byte base `0x07e40000 × 8`), and the reset-vector page at logical bit `0xffffffe0` is covered by mirroring the ROM into the top logical quadrant `0xff200000`–`0xffffffff`. The reset vector (`0x3f200040`) lands on ROM file offset `0x1bfffc`. ## 2. Build and run ```sh # build (memory-hungry; defaults to -j8) cd /home/arti/code/k-space/vt1200 harness/mame/build.sh # -> /home/arti/code/k-space/mame0289/vt1200 # run: trace from the reset vector M=/home/arti/code/k-space/mame0289/vt1200 $M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \ -debug -debugger none -autoboot_script harness/mame/trace.lua -autoboot_delay 0 # run: pcfReadBitmaps n*4 overflow (OVF_N optional, decimal) $M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \ -debug -debugger none -debuglog -seconds_to_run 5 \ -autoboot_script harness/mame/overflow.lua -autoboot_delay 0 ``` Notes on MAME headless debugging: `-debugscript` does **not** work without a debugger UI (the console only processes scripts while the debugger UI drives the loop). The reliable headless route is the Lua debugger API (`manager.machine.debugger`, `device.debug`, `emu.register_frame_done`) driven from `-autoboot_script`. ## 3. Evidence it executes the firmware `trace.lua` prints the reset PC and writes an instruction trace of the first two frames (`harness/mame/vt1200-reset.trace.gz`, 650 815 lines; a head excerpt is in `reset-trace.head.txt`): ``` [space] program shift=3 data_width=16 PC at harness start = 3F200040 (expect 3F200040) after 2 frames: PC=3FDF99F0 ``` First instructions of the trace (bit addresses; byte = /8): ``` 3F200040: DINT 3F200050: SETF 10h,0,0 3F200060: SETF 20h,0,1 3F200070: CLR A0 3F200080: MOVE A0,@64000D0h,0 ; board peripheral 3F2000B0: MOVI FFh,A0 3F2000D0: MOVB A0,@64000F0h 3F200100: MOVI Fh,A0 3F200120: MOVB A0,@64000E0h 3F200150: CLR A0 3F200160: MOVE A0,@62000D0h,0 ; board peripheral 3F200190: MOVI 4h,A0 3F2001B0: MOVE A0,@62000F0h,0 3F2001E0: CLR A8 3F2001F0: MOVI 1h,A0 3F200220: MOVB A0,@64000F0h 3F200250: CLR A0 3F200260: MOVE A0,@C00000B0h,0 ; CONTROL 3F200290: MOVE A0,@C0000080h,0 ; DPYCTL 3F2002C0: MOVE A0,@C0000100h,0 ; HSTCTLH ... ``` This matches the known power-on code (`DINT; SETF 16,0,0; SETF 0,0,1; CLR A0 …`) and the peripheral/CONTROL writes documented in `MEMORY_MAP.md`. The CPU then runs several hundred thousand instructions of POST and ends the second frame at `0x3fdf99f0` (`PIXBLT B,XY`). ### In-emulator `pcfReadBitmaps` overflow `overflow.lua` jumps straight into the arithmetic block and stops after the shift. Measured results (register `a11` is the argument to `fosAlloc`): | n (input) | `n*4` / fosAlloc arg | note | |---|---|---| | `0x00001000` | `0x00004000` | control, no wrap | | `0x10000001` | `0x40000004` | wraps | | `0x40000000` | `0x00000000` | `fosAlloc(0)`, then 4 GiB of writes | | `0x40000001` | `0x00000004` | `fosAlloc(4)`, then 4 GiB of writes | ``` RESULT n=40000001 a11_fosAlloc_arg=00000004 nChars=40000001 ``` ## 4. Findings ### 4.1 `pcfReadBitmaps` arithmetic and `num_chars*4` (confirmed) The firmware's `pcfReadBitmaps` is at bit `0x3f984e70` (byte `0x0f09ce`). Disassembly (bit addresses) of the relevant block: ``` 3f984e70: move *a9(0x270),a11,1 ; a11 = pCS->nChars 3f984e90: jrne L3F984EE0 ; nChars != 0 -> just cross-check n 3f984ea0: move *a13(0x20),*a9(0x270),1 ; nChars == 0 -> nChars = n ... 3f985030: move *a13(0x20),a11,1 ; a11 = n (pcfReadInt32 result) 3f985050: sll 0x2,a11 ; a11 = n*4 <-- NO overflow check 3f985090: calla L3F703D90 ; fosAlloc(n*4, 1) 3f9850c0: move a8,*a13(0x40),1 ; pCS->pBitOffsets = result ... 3f985260: (loop i = 0 .. n-1) ; writes n CARD32 entries 3f985290: move a10,a7 ; a7 = i 3f9852a0: sll 0x5,a7 ; i*32 bits = i*4 bytes 3f9852d0: add a11,a7 ; dest = offsets + i*4 3f985310: calla L3F96EEE0 ; read 4 bytes from file into dest ``` There is **no bound check on `n`** between the read and the multiply, exactly matching X11R4 `reference/x11/r4/contrib/server/pcf/fonts/lib/pcf/pcfrbitmaps.c` (`offsets = fosAlloc(n*sizeof(CARD32)); for (i=0;i copy it ... 3f5eeda0: movi 0x3f5f0c98,a8 ; "tftp_read: bufsize (%d) too small for %d bytes\n" 3f5eee50: movk 3,a9 ; TFTP error code 3 (ENOSPACE) 3f5eee90: calla L3F5EF610 ; send error packet ``` So ENOSPACE is not a disk error: it is `tftp_read` rejecting a datagram because the destination buffer's remaining space is smaller than the payload. The observed 1024-byte cutoff (works ≤ 2 blocks, fails on the 3rd) implies the font receive buffer is ~1 KiB. `tftp_read` itself uses a 0x204-byte (516 = 512+4) datagram buffer (`movi 0x204` at `3f5ed5e0`), so the ~1 KiB limit is in the *file accumulation* buffer, not the packet buffer. Pinning the exact size needs a breakpoint on `3f5eed30` while a live transfer runs, or a small additional harness. ## 5. Q1 — why the small synthetic font is rejected Not yet answered with certainty. What is established: * The firmware contains a full X11R4-era PCF reader. The dispatcher (`pcfReadFont`) is around bit `0x3f98bxxx`; it prints `Failed to read PCF format properties/accelerators/encodings/metrics/bitmaps` when a table reader returns failure, and the per-reader strings (`unknown bitmap format 0x%x`, `bad count in pcfReadBitmaps (expected %d, got %d)`, `bad packed/unpacked metrics, glyph=…`, `pcfReadBitmaps called with bitmaps loaded`, …) all match the R4 sources. * The `fonts.dir` path also has a name check: `font name different: name in path file='%s'` / `name in font file='%s'` (bit `0x3f990538`), a strong candidate for "name listed" but "OpenFont unmatched". * A freshly generated `baseline.pcf` has all nine tables and version `0x70636601`, so the version/TOC checks should pass; the failure is one of the per-table or name checks. **Why it isn't answered yet:** invoking the parser requires the `fos` file abstraction (`fosRead` at `0x3f96eee0`, `fosAlloc` at `0x3f703d90`, `fosFilePtr` at `*a13(0xffe0)`), which the boot path sets up only after the network/TFTP fetch. The harness can run the CPU, but reaching `pcfReadFont` means either booting far enough (needs LANCE/DUART models — out of reach) or a small purpose-built call stub. ## 6. Blockers and remaining effort * **Q1 needs a call harness.** Cheapest next step: give the driver a writable overlay at the `fosRead`/`fosAlloc` addresses, have Lua write a tiny TMS34010 stub there that serves bytes from a RAM copy of `baseline.pcf`, then set PC to the PCF loader with a crafted `fosFilePtr`. That lets us single-step the exact bail path. Effort: moderate (hand-encoding a handful of TMS34010 instructions, or assembling with `as34`). * **The ~1 KB limit** can be pinned by breaking at `0x3f5eed30` during a real transfer, or by the same call harness feeding oversized data. * **Full boot** (X server, network) needs LANCE + DUART + video models; that is a large, separate project and was explicitly out of scope. * The driver is a visibility tool, not an accurate VT1200: the CRT timing, palette and peripherals are stubs. ## 7. Tree layout (new files) ``` harness/mame/ vt1200.cpp driver (source of truth; symlinked into MAME) build.sh build wrapper (JOBS=8 default) trace.lua reset-vector trace overflow.lua pcfReadBitmaps n*4 harness reset.dasm disassembly of the reset vector window reset-trace.head.txt first 60 traced instructions vt1200-reset.trace.gz full 650k-instruction trace roms/vt1200/vt1200_v22.bin -> ../../vt1200_v22.bin harness/dis/ fontregion-v22.txt dis34 of the font code tftp-v22.txt dis34 of the TFTP client full-v22.txt.gz dis34 of the whole ROM ```