Files
vt1200/harness/mame/FULL_BOOT_ESTIMATE.md
T

310 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```sh
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.