forked from arti/vt1200
180 lines
7.4 KiB
Markdown
180 lines
7.4 KiB
Markdown
# VT1200 execution harness (MAME)
|
||
|
||
A minimal MAME machine that loads the VT1200 firmware into a TMS34010 CPU so
|
||
we can **run it under MAME's debugger**: instruction trace, breakpoints,
|
||
single-stepping, and direct memory watch. It is a *visibility* tool, not an
|
||
accurate VT1200 emulation.
|
||
|
||
Everything in this directory is source-of-truth in this repo; the MAME source
|
||
tree is disposable and lives outside the project
|
||
(`/home/arti/code/k-space/mame0289`).
|
||
|
||
```
|
||
harness/mame/
|
||
├── vt1200.cpp # the MAME driver (symlinked into the MAME tree)
|
||
├── build.sh # clone-safe build wrapper
|
||
├── *.lua # probe / capture / dump scripts (many; see the reports)
|
||
└── roms/vt1200/ # MAME ROM dir (symlink to ../../vt1200_v22.bin)
|
||
```
|
||
|
||
Reports in this directory:
|
||
|
||
| file | what it records |
|
||
|---|---|
|
||
| `GATE_CLEANUP_REPORT.md` | promoting the working config to the default; retiring the settled env gates |
|
||
| `RENDER_REPORT.md` | the active-pixel scanline renderer, and the geometry it derived |
|
||
| `DUART_REPORT.md` | the DUARTs: wiring, the ISR-driven transmit, the datasheet-verified model edits |
|
||
| `BRINGUP_LOG.md` | the whole bring-up narrative, **including withdrawn claims** — read it sceptically |
|
||
| `ROMPATCH_REPORT.md` | runtime ROM patching (debug only — see the caveat) |
|
||
|
||
## Address model (the important part)
|
||
|
||
The TMS34010 is **bit-addressed**: a register/PC holds a bit address, and a
|
||
pointer `P` refers to physical byte `P >> 3`. MAME models this with a program
|
||
space of `memory_access<32, 1, 3>`, i.e. the **address-map unit is 1 bit**
|
||
(a 16-bit word spans 16 address units). Source: `tms34010.h` — note the
|
||
TMS34020 differs (`memory_access<32, 2, 3>`), so this is *not* interchangeable
|
||
with the 34020. Therefore:
|
||
|
||
* Address-map entries here are **bit addresses**, not byte addresses.
|
||
* `read_word(0x3f200000)` returns ROM bytes 0..1 (physical byte 0x07e40000).
|
||
* The reset vector is read from logical bit `0xffffffe0`, which maps to the
|
||
last 4 bytes of the image (file offset `0x1bfffc`).
|
||
|
||
The driver maps:
|
||
|
||
| region | bit range | bytes |
|
||
|---|---|---|
|
||
| RAM | `0x00000000`–`0x001fffff` | 4 MiB at byte 0 |
|
||
| ROM | `0x3f200000`–`0x3fffffff` | 1.75 MiB at byte 0x07e40000 |
|
||
| ROM aliases | `0x7f…`, `0xbf…`, `0xff200000`–`0xffffffff` | same ROM (top 2 logical bits not decoded) |
|
||
|
||
The `0xff…` alias is what makes the reset-vector page resolve.
|
||
|
||
### What is modelled by default
|
||
|
||
This **changed** — see `GATE_CLEANUP_REPORT.md`. The working configuration used
|
||
to be a six-variable incantation; it is now the default, and a bare
|
||
`vt1200 vt1200` with **no environment** runs it.
|
||
|
||
* Video RAM at bit `0x04000000` — required for the level-1 VIDEO MEMORY test.
|
||
* The board-window handshake model (two-tick busy bit).
|
||
* MAME's `SCN2681` DUART at **DUART1** (`0x0640000`, host/printer), with input
|
||
port **IP4–IP6 held high** — that is how the driver reads the VR150 monitor
|
||
switch (`0x70`). The level-1 host-port transmit is **ISR-driven**, so a real
|
||
DUART is required here, not a convenience.
|
||
* A mono palette (pens 0/1 = black/white) and an active-pixel scanline renderer
|
||
that reads VRAM. See `RENDER_REPORT.md`.
|
||
|
||
A bare run progresses through POST and level 1 and stops at `HOST PORT - 15`,
|
||
then **wedges in the system-board LED loop** — a level-1 failure is *fatal*, not
|
||
merely reported, so the OS is unreachable while it fails.
|
||
|
||
### What is not modelled
|
||
|
||
**LANCE Ethernet**, and **DUART2** (`0x0620000`, keyboard/mouse — opt in with
|
||
`VT1200_DUART2=1`). The OS is therefore not usable yet: it is reached, but it
|
||
does not paint.
|
||
|
||
### Getting past level 1
|
||
|
||
`VT1200_SKIP_LEVEL1=1` drives DUART1's **IP3** high, which the firmware takes as
|
||
a signal to jump to the OS cold-start and skip **all 15** level-1 tests. Note
|
||
what this is: a *modelled board input*, and the real machine reads IP3 **low**.
|
||
So a level-1-skipped run is evidence about the **OS, graphics and crash
|
||
behaviour**, and is **not** evidence about the real terminal's POST.
|
||
|
||
The skip is deliberately **not** the default: the real machine runs level 1, so
|
||
the faithful configuration stays the default and the switch is how we diverge.
|
||
|
||
## Environment knobs (14; down from 33)
|
||
|
||
All opt-in, all off by default, so a bare run is the working configuration.
|
||
Instrumentation: `VT1200_DUARTLOG`, `VT1200_BOARD_LOG`, `VT1200_BOARD_MAP`,
|
||
`VT1200_IRQLOG`, `VT1200_LOG_UNMAPPED`, `VT1200_DPYLOG` (+`_FRAMES`),
|
||
`VT1200_DUMP_FRAME`, `VT1200_DUMP_RAM`, `VT1200_DUMP_VRAM`, `VT1200_DUMP_DIR`,
|
||
`VT1200_NO_RENDER`. Behaviour switches: `VT1200_SKIP_LEVEL1`, `VT1200_DUART2`.
|
||
|
||
## Regression baseline
|
||
|
||
`progress.lua` at 200 frames, **no env**:
|
||
|
||
```
|
||
PC=3FDCE400 samples=53 (78 distinct PCs)
|
||
```
|
||
|
||
This value **changed** when the working config became the default — previously
|
||
`PC=3FDCE400 samples=199` (a single PC), because a no-env run used to wedge at
|
||
the hardware-ready gate. The new value is *better*: a bare run now executes real
|
||
POST work. It is a change detector, not a specification — if it moves without
|
||
you meaning it to, find out why.
|
||
|
||
## Build
|
||
|
||
MAME source is required. A shallow 0.289 checkout is large and slow to fetch.
|
||
|
||
```sh
|
||
git clone --depth 1 --branch mame0289 \
|
||
https://github.com/mamedev/mame.git /home/arti/code/k-space/mame0289
|
||
|
||
harness/mame/build.sh # symlinks the driver in and runs make
|
||
```
|
||
|
||
`build.sh` runs `make -j$JOBS SUBTARGET=vt1200
|
||
SOURCES=src/mame/dec/vt1200.cpp`, which builds a single-driver binary
|
||
`/home/arti/code/k-space/mame0289/vt1200`.
|
||
|
||
The MAME tree is large and the build is **memory-hungry**: `build.sh`
|
||
defaults to **`-j8`**, not all cores. Override with `JOBS=4
|
||
harness/mame/build.sh` on a small/low-memory machine. Progress is written to
|
||
`harness/mame/build.log`.
|
||
|
||
## Run
|
||
|
||
Headless execution and inspection use MAME's Lua debugger API from
|
||
`-autoboot_script`. (`-debugscript` only works when a debugger UI drives the
|
||
console, so it is not usable headless.)
|
||
|
||
```sh
|
||
cd /home/arti/code/k-space/vt1200
|
||
M=/home/arti/code/k-space/mame0289/vt1200
|
||
|
||
# trace execution from the reset vector
|
||
$M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \
|
||
-debug -debugger none \
|
||
-autoboot_script harness/mame/trace.lua -autoboot_delay 0
|
||
|
||
# demonstrate the pcfReadBitmaps n*4 overflow (OVF_N overrides n, 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
|
||
```
|
||
|
||
The Lua API exposes `manager.machine.debugger`, `device.debug` (step, go,
|
||
bpset, wpset), CPU registers (`cpu.state['A0'].value`), memory
|
||
(`cpu.spaces['program']:read_u32/write_u32`), and frame hooks.
|
||
|
||
### Trace from the reset vector (`trace.lua`)
|
||
|
||
Prints the reset PC (expect `3F200040`), disassembles the power-on code to
|
||
`reset.dasm`, and writes an instruction trace of the first two frames to
|
||
`vt1200-reset.trace`.
|
||
|
||
### Exercise the `pcfReadBitmaps` overflow (`overflow.lua`)
|
||
|
||
Instead of booting the parser we jump directly into its arithmetic block with
|
||
a crafted in-memory `PCF_CharInfo`:
|
||
|
||
```
|
||
a9 = 0x00040000 ; fake pCS; pCS->nChars at a9+0x270
|
||
a13 = 0x00060000 ; fake arg blk; n (from pcfReadInt32) at a13+0x20
|
||
write_u32(0x00040270, 0) ; nChars == 0
|
||
write_u32(0x00060020, n) ; n
|
||
pc = 3f984e70
|
||
break at 3f985060 ; just after 'sll 0x2,a11'
|
||
```
|
||
|
||
This is possible because the font parser's field moves use **field 1**, which
|
||
`SETF 0,0,1` leaves at 32 bits, so `n` is a full 32-bit `pcfReadInt32` value.
|
||
See `FINDINGS.md` for results and the rest of the analysis.
|