Files
vt1200/harness/mame/FULL_BOOT_ESTIMATE.md

14 KiB
Raw Permalink Blame History

How much work is a "full boot" of the VT1200 in MAME?

Investigated 2026-10-15/16. Companion to FINDINGS.md (which covers the harness that exists today and the font questions). This file answers: what would it take to make the MAME driver boot this machine as opposed to merely executing its firmware?

Short answer: a multi-week project, dominated by one unknown — decoding the board's custom peripheral windows. Everything else has a device model in MAME already. Below is the evidence and the breakdown.


1. What "full boot" means here

The terminal boots to an X server + the VTE terminal emulator + a LAT client + a 4.3BSD TCP/IP stack. "Full boot" = the firmware completes POST, initialises video, the DUARTs, the LANCE and the NVRAM, and reaches the point where it is listening on the network and driving the screen. That is exactly the state in which the crash we have been chasing happens — i.e. it is not just a debugging convenience. It is the condition needed to reproduce the LAT/announcement crash offline.

A weaker goal — "run specific firmware routines" (e.g. the font parser) — is far cheaper and is covered in §6.


2. Hardware inventory (derived, not guessed)

The ROM ships a self-test menu that enumerates the board, which gives a clean device list (each entry is the test's bit in the selection mask, not an address):

C - LANCE TEST                        (00008000h)
D - KEYBOARD TEST                     (00004000h)
E - DUART1, CHANNEL A (HOST) TEST     (00000400h)
F - DUART1, CHANNEL B (PRINT) TEST    (00000800h)
G - DUART1 TIMER TEST                 (00080000h)
H - DUART2, CHANNEL A (KEYBOARD) TEST (00001000h)
I - DUART2, CHANNEL B (MOUSE) TEST    (00002000h)
J - DUART2 TIMER TEST                 (00100000h)
K - MOUSE/TABLET TEST                 (00010000h)
L - VIDEO TEST                        (00020000h)
N - VIDEO MEMORY TEST                 (00000100h)
M - ETHERNET ROM TEST                 (00040000h)
A[x]/Y - ROM TEST (x = bank select)/NVM

So the board is:

block notes
LANCE Ethernet AM79C90-class; 4.3BSD-derived stack above it (lance_inet_interface.c, lpd_lance_devdriver.c)
2× DUART DUART1 = host + printer; DUART2 = keyboard + mouse; each has 2 timers. MC68681 / SCN2681 class
Ethernet ROM MAC address storage
NVM non-volatile setup memory (battery-backed)
Video TMS34010 on-chip display controller + VRAM + an output stage
Keyboard, mouse/tablet serial, behind DUART2
Custom ASIC(s) the undecoded windows in §4

Interrupt sources documented in the service guide: Display, Host Interface, Interrupt 1, Interrupt 2, NMI, Trap, Window Violation, plus LANCE, DUART (2 ch + 2 timers) and video clock.


3. What MAME already gives us (this is the good news)

Everything needed exists as a device model, and there are two near-perfect templates:

need MAME device template driver
CPU tms34010 (incl. display controller) already used here
LANCE Ethernet am79c90 dec/kn01.cpp (DEC PMAX) — shows the DMA callback wiring
DUART ×2 scn2681 / mc68681 ncd/ncd68k.cpp
NVRAM nvram, eepromser, ds1215 (phantom clock) many
Video RAMDAC bt478_device, ramdac family ncd/ncd68k.cpp
Keyboard pc_kbdc, or a plain serial model ncd/ncd68k.cpp

ncd/ncd68k.cpp is the closest analogue in MAME: it is an NCD X terminal with LANCE + SCN2681 + VRAM + RAMDAC + EEPROM + keyboard. Anyone doing this work should start from that file and from dec/kn01.cpp.

So the chipset is not the hard part. Nothing here needs a new device model.


4. The one real unknown: the board's custom windows

MEMORY_MAP.md already flags this, and it is the dominant cost:

Board peripherals appear around byte 0x00B00000, 0x00C40000, 0x00C80000 (bit 0x5800000, 0x6200000, 0x6400000). POST writes @0x62000d0/f0 and @0x64000d0/e0/f0; the two identical windows at 0xC40000/0xC80000 are presumed to be the two DUARTs. Device decoding not confirmed.

The reset vector (file offset 0x08) is literally:

3F200080: MOVE A0,@64000D0h,0     ; before anything else
3F2000D0: MOVB A0,@64000F0h
3F200120: MOVB A0,@64000E0h
3F200160: MOVE A0,@62000D0h,0
3F2001B0: MOVE A0,@62000F0h,0
3F200260: MOVE A0,@C00000B0h,0    ; CONTROL
3F200290: MOVE A0,@C0000080h,0    ; DPYCTL

So two opaque windows (0x6200000, 0x6400000) are programmed before the CPU's own display registers, i.e. they are essential. They are presumably the system-logic ASIC (interrupt controller, video timing, NVM chip-select, LAN chip-select, maybe a watchdog). Nobody has decoded them yet.

Work required: reverse-engineer those register sets from POST + the diagnostics code. This is ordinary RE with good anchors (the diagnostic tests name each block), but it is open-ended: the register count is unknown until it is done.

Risk: high, and it is the item that decides the schedule. Everything else can be estimated; this cannot.


4b. Can we just stub the unknown windows? (tested)

Yes — and it is cheap — but it does not produce a boot. Tested 2026-10-16.

The driver now takes VT1200_BOARD_STUB:

value behaviour of the 0x5800000 / 0x6200000 / 0x6400000 windows
unset windows stay unmapped — identical to the pre-experiment driver
0 windows mapped, every read returns 0 (same visible result as unset)
0xffff every read returns all-ones, i.e. every polled "ready" bit asserted
ram windows behave as read/write RAM — writes persist and read back

Results, 1200 frames each — three different places to get stuck, none of them a boot:

stub parked at what that code is
0 PC=3FDCE400 (file 0x179C80) the 0x6200000 ready-poll (see below)
0xffff PC=3F2082C0 (file 0x1058) POST video/refresh delay loop, JR 8060h
ram PC=3F2088B0 (file 0x1116) a third, different gate

What the ready-poll actually is

stackwalk.lua dumps the stack while parked in the delay loop, which is how the first gate was identified. The delay routine is fine (it gets count 5968 × multiplier 100 ≈ 600k iterations ≈ 45 ms); it is simply called in a poll loop:

bcf210: MOVI 64h,A11                 ; 100
bcf240: CALLA 3FDCE1C0h              ; delay(100)
bcf270: MOVE *A13(FF80h),A9,1        ; A9 = 0x06200000
bcf2a0: MOVE *A9(10h),A11,0          ; read byte @ 0x06200010
bcf2c0: BTST 0h,A11                  ; test bit 0
bcf2d0: JREQ BCF200h                 ; not ready -> delay + retry

i.e. while (!(read8(0x06200010) & 1)) delay(100); — a hardware-ready poll on the undecoded window. Note the CPU's own registers (CONTROL, HSTCTLH) are modelled by MAME's tms34010, so those reads were already real; the gate is purely the board ASIC.

The conclusion that matters

Stubbing is a good diagnostic and a poor shortcut:

  • It is cheap (≈20 lines in the driver) and it does move the firmware — from the ready-poll into the POST video phase, and onwards under a different policy.
  • But each policy lands somewhere different and still looping. Blindly asserting "ready" does not walk the boot forward; it selects a different wrong path.
  • Passing a gate correctly still requires knowing what the register means.

The useful part: iterating targeted per-gate stubs enumerates the gates. In one afternoon, one known gate became three distinct stuck points. If that enumeration is done systematically, item 1 above stops being "decode an unknown ASIC" and becomes "decode the specific registers these N gates touch" — a bounded, much smaller job. That is the recommended way to attack item 1.

The experimental stub is committed in vt1200.cpp and defaults to the old behaviour, so nothing regresses if VT1200_BOARD_STUB is unset.


5. Where the boot stops today (measured, not assumed)

harness/mame/progress.lua (new) samples the PC once per emulated frame and reports the hottest PCs. Two runs:

  • 400 frames, and
  • 3000 frames (≈ 10 minutes of emulated time at this driver's ~4.9 Hz frame rate).

Result — the CPU sits in one instruction:

  PC=3FDCE400  samples=2976 of 3000   first_frame=1
  PC=3FDCE1C0  samples=1              first_frame=2848   <- routine entry
  PC=3FDCE460  samples=1              first_frame=2736   <- its RETS

Disassembled (PC 0x3FDCE400 -> byte 0x07FB9C80 -> file 0x179C80), the routine is:

bce1c0: MMTM SP,A9,A11,A13           ; entry
bce210: MOVE *A13(FFE0h),A9,1        ; A9 = caller's argument
bce230: MOVI C00000B0h,A11           ; CONTROL
bce2d0: MOVE *A13(20h),A11,1
bce300: MOVE *A11,A11,0              ; read CONTROL
bce310: BTST Fh,A11                  ; test bit 15
bce330: MOVE *A13(40h),A11,1         ; HSTCTLH  (C0000100)
bce350: MOVE *A11,A11,0              ; read HSTCTLH
bce360: BTST Eh,A11                  ; test bit 14
bce380: MOVK 1Eh,A11                 ; multiplier = 30
bce3b0: MOVI 64h,A11                 ; ... or 100
bce3e0: MPYS A11,A9                  ; A9 = argument * multiplier
bce400: DSJS A9,BCE400h              ; <-- THE SPIN: ~2^32 iterations
bce460: RETS 2h

Reading: this is a timed-wait / calibration helper. One call to it takes ~2,700 frames ≈ 9 minutes of emulated CPU time, because the multiplier/count inputs come from peripheral status that the current driver stubs to 0xFFFF — so the loop runs on garbage. The real machine boots in seconds, so this is not "slow firmware", it is "missing hardware".

This is a useful, concrete marker: the first thing that must be modelled is whatever feeds this routine, and we cannot get past early POST without it. It also confirms the driver's own note that unmapped reads returning 0xffff are being acted upon.


6. Work breakdown and estimate

Ordered by dependency. Effort is for one engineer already familiar with this codebase and MAME, working part-time, and excludes the §6b shortcut.

# item effort uncertainty
1 Decode the 0x6200000 / 0x6400000 windows (interrupt controller, chip selects, video timing, NVM) 1–3 weeks high — this is the schedule
2 Wire LANCE: address, DMA, IRQ (dec/kn01.cpp pattern) 2–4 days low
3 Wire 2× DUART + their 4 timers + IRQs 2–3 days low
4 NVRAM + Ethernet ROM (MAC) 1–2 days low (needs correct contents)
5 Video: display params, VRAM mapping, palette/output stage; get a non-blank screen 3–7 days medium (TMS34010 display is modelled; the board video path is not)
6 Interrupt plumbing: TMS34010 INTENB/INTPEND + the external controller 2–4 days medium — depends on #1
7 Keyboard + mouse so the terminal is usable 2–4 days low–medium
8 Boot debugging to "network up + screen up" 1–3 weeks high (integration)
9 Bonus: make the LAN path work end-to-end against latd 3–7 days medium

Total: roughly 6–12 weeks part-time, with the spread driven almost entirely by items 1 and 8. Nothing in it is research-grade; it is a large, ordinary bring-up task with one genuinely unknown block.

What would make it collapse to something much smaller

  • If the custom windows turn out to be a few dozen registers and the diagnostics code documents them (quite likely — the self-test knows every register it pokes), item 1 could be days rather than weeks.
  • If the boot turns out not to need the LANCE before reaching video, items 2 and 9 can be deferred, and the terminal becomes usable on-screen much sooner.

What would blow it up

  • The custom ASIC turning out to contain a DMA engine or a video path with undocumented timings (plausible for the 0x6200000 writes at reset).
  • POST validating device responses in ways that require behavioural accuracy, not just register presence.

6b. The much cheaper alternative (and what I'd actually do)

For this project's real questions — the font parser, the VTE, the LAT client — a full boot is not the cheapest route, and probably not needed at all.

A call harness gets you inside a specific routine in days:

  • put a writable overlay at fosRead (0x3f96eee0) / fosAlloc (0x3f703d90),
  • have Lua write a small TMS34010 stub that serves bytes from a RAM copy of the file under test (a .pcf, an SNF, a LAT session buffer),
  • set PC to the routine of interest with a crafted argument block.

That is how the pcfReadBitmaps overflow was already demonstrated in-emulator (overflow.lua does exactly this pattern, jumping straight into the arithmetic with fabricated state). Extending it to pcfReadFont is the cheapest way to answer the outstanding font question.

Recommendation: do the call harness now; treat the full boot as an optional parallel project, and only start it if you want offline reproduction of the LAT crash — in which case item 1 (the custom-window decode) is the thing to schedule.


7. Reproduce

cd /home/arti/code/k-space/vt1200
M=/home/arti/code/k-space/mame0289/vt1200

# where does the boot get, and where does it spin?
PROG_FRAMES=3000 $M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \
    -debug -debugger none -autoboot_script harness/mame/progress.lua -autoboot_delay 0

# hardware inventory (the self-test menu)
strings -n 6 vt1200_v22.bin | grep -E "TEST ?\(|Selftest"

# disassemble the spin site (byte offsets; printed address = byte*8)
python3 lat/notes/ua.py 0x179c30 0x179cd0

Note: Lua print() goes to stdout; debugger printf/-debuglog output goes to ./debug.log. Two different streams — easy to lose an afternoon to that.