From 5ef8712960b7e43f4d1a1e71db6db7356d268121 Mon Sep 17 00:00:00 2001 From: SDD Date: Fri, 16 Oct 2026 10:55:44 +0300 Subject: [PATCH] harness: identify the level-1 video-memory test that raises code 0x100 (VIDEO MEMORY-22) The dispatch status is 0x100 (printed as '22' in the message). A driver-side transparent low-RAM write log (VT1200_LOWLOG) shows the slot at bit 0x16F0 is written by the store at 3FDF9090 from a13(0x10), which L3FA7C050 sets to the literal 0x100 when the memory test L3F2018B0 returns non-zero. L3F2018B0 is the level-1 video-memory test (base bit 0x04000000, count 0x20000). Its failing checks: the 16-bit walking-bit read-back at 0x04000000 (baseline, unmapped) and, with video RAM mapped, the REFCNT (0xC00001F0) 'must change' check. --- harness/mame/CODE22_REPORT.md | 349 ++++++++++++++++++++++++++++++++++ harness/mame/code22_args.lua | 33 ++++ harness/mame/code22_fail.lua | 45 +++++ harness/mame/code22_final.lua | 37 ++++ harness/mame/code22_probe.lua | 32 ++++ harness/mame/code22_walk.lua | 43 +++++ harness/mame/vt1200.cpp | 57 ++++++ 7 files changed, 596 insertions(+) create mode 100644 harness/mame/CODE22_REPORT.md create mode 100644 harness/mame/code22_args.lua create mode 100644 harness/mame/code22_fail.lua create mode 100644 harness/mame/code22_final.lua create mode 100644 harness/mame/code22_probe.lua create mode 100644 harness/mame/code22_walk.lua diff --git a/harness/mame/CODE22_REPORT.md b/harness/mame/CODE22_REPORT.md new file mode 100644 index 0000000..2f16b26 --- /dev/null +++ b/harness/mame/CODE22_REPORT.md @@ -0,0 +1,349 @@ +# CODE 22 — which level-1 test raises it, and why + +Bounded investigation, 2026-10-16. Subagent task: identify the level-1 test that +raises the "VIDEO MEMORY - 22" status and the condition that raises it. The task +is **identification only** — level 1 is not made to pass. + +Driver: `harness/mame/vt1200.cpp` (env-gated additions, see §7). +MAME: `/home/arti/code/k-space/mame0289`. ROM: `harness/mame/roms/vt1200/vt1200_v22.bin`. + +--- + +## 0. Answer in one paragraph + +The failing test is the **level-1 video-memory self-test at bit `0x3F2018B0`** +(file `0x000316`, Ghidra `0x07E40316`), which the level-1 runner calls with the +screen-memory base **bit `0x04000000`** (arg3) and a count of **`0x20000`** +(arg4). It does a 16-bit walking-bit data-bus check on the first word at +`0x04000000`, then a 32-bit pattern check across the region, then a DRAM +refresh-counter check. When any of these fails it returns a non-zero status +(`0x19` = walking-bit / `0x0E` = REFCNT in the runs below). The caller at +`0x3FA7C050` turns *any* non-zero return into the literal constant **`0x100`** +(at bit `0x3FA7DE30`, file `0x10FBC6`). That `0x100` is written into the frame +slot `a13-0x20` (bit `0x16F0`) by the store at bit `0x3FDF9090`, and the error +dispatcher at bit `0x3FDF0630` maps **`0x100` → handler `0x3FDE7F00`** +(Ghidra `0x07FBCFE0`), which prints the string `LEVEL1/2 ERROR -> VIDEO MEMORY - 22`. + +**Note on the number:** the dispatcher's *status code* for this error is +`0x100`; `22` is a constant in the message string, as the bring-up log already +recorded for `0x40000C`. So "status code 22" = dispatcher code `0x100`. + +The condition that makes the test fail depends on which hardware is modelled: + +* **Default (committed) driver, no video RAM mapped** — the walking-bit check + fails: writes of `1,2,4,…,0x8000` to bit `0x04000000` read back `0`. +* **With video RAM mapped (`VT1200_VRAM=1`)** — the walking-bit and pattern + checks now pass; the failure moves to the **DRAM refresh-counter (REFCNT) + check**: `REFCNT` (`0xC00001F0`) returns the same value (`0x0E74`) on two + consecutive reads, which the firmware treats as an error. + +So the *residual* condition — the one that still holds after "video memory is +mapped and round-trips", which is exactly the state the bring-up log describes — +is the **REFCNT check**. + +--- + +## 1. Method + +The stack-walk approach in `dispatch_walk.lua` only sampled 8 stack words and +reached the reporting framework, so it was replaced with a **write watch on the +status slot**, done driver-side (no watchpoints — `wpset` is a dead end here). + +### 1a. The dispatcher reads a 32-bit code from bit `0x16F0` + +`harness/mame/code22_probe.lua`, run with the level-1 config +(`VT1200_BOARD_MODEL=handshake VT1200_BOARD_BUSY_TICKS=2 +VT1200_BOARD_SWITCH=0640000D:70:70`), breaks at the dispatcher read and at the +next instruction: + +``` +AT-READ pc=3FDF0630 a13=00001710 a14=00001770 a8=00000001 d@16F0=0000FFFF +AT-NEXT pc=3FDF0650 a8=0000FFFF d@16F0=0000FFFF +AT-READ pc=3FDF0630 a13=00001710 a14=00001770 a8=00000100 d@16F0=00000100 +AT-NEXT pc=3FDF0650 a8=00000100 d@16F0=00000100 +``` + +Two facts fall out: + +* A `bpset` fires **before** its instruction executes (the `a8` at `AT-READ` + is stale; the loaded code is the value at `AT-NEXT`). +* The code the dispatcher reads is the dword at bit `a13-0x20` = `0x16F0`. + Hit 1 = `0xFFFF` → handler `0x3FDEFF60` prints `LEVEL1/2 TESTING **`; + hit 2 = `0x100` → handler `0x3FDE7F00` prints `LEVEL1/2 ERROR -> VIDEO MEMORY - 22`. + +The two hits and their order match the captured console transcript exactly. + +Confirming the switch arithmetic with `dis34` (its `subi` operand is the amount +actually subtracted, unlike Ghidra's rendering): + +``` +3fdf07e0: subi 0x78, a8 ; jaeq L3FDE7A00 ; code 0x080 +3fdf0820: subi 0x80, a8 ; jaeq L3FDE7F00 ; code 0x100 <- VIDEO MEMORY-22 +3fdf0860: subi 0x100,a8 ; jaeq L3FDE8150 ; code 0x200 +... +3fdf0a20: subi 0x7fff,a8; jaeq L3FDEFF60 ; code 0xFFFF <- LEVEL1/2 TESTING +``` + +### 1b. Driver-side write watch finds the writer + +New env-gated knob `VT1200_LOWLOG=`: a **transparent** logging proxy over +bit `0x0000-0x3FFF` that forwards reads/writes to the real `mainram` share and +records every distinct `(addr, pc, value, kind)`. It does not perturb execution +(regression verified, §6). + +Command: + +```sh +M=/home/arti/code/k-space/mame0289/vt1200 +VT1200_BOARD_MODEL=handshake VT1200_BOARD_BUSY_TICKS=2 \ +VT1200_BOARD_SWITCH="0640000D:70:70" VT1200_LOWLOG=/tmp/opencode/code22/low.log \ +CP_FRAMES=300 timeout 300 $M vt1200 -rompath harness/mame/roms \ + -video none -sound none -nothrottle -debug -debugger none -debuglog \ + -autoboot_script harness/mame/code22_probe.lua -autoboot_delay 0 +``` + +Relevant lines (the only writer of `0x100` to the status slot): + +``` +W 001590 pc=3FDF8BF0 val=0100 +R 001590 pc=3FDF8D80 val=0100 +R 001590 pc=3FDF8FB0 val=0100 +R 001590 pc=3FDF90B0 val=0100 +W 0016F0 pc=3FDF90B0 val=0100 <-- the status slot is loaded here +R 0016F0 pc=3FDF0630 val=0100 <-- the dispatcher reads it +R 0016F0 pc=3FDF0650 val=0100 +``` + +(`pc` is reported as the *next* instruction, i.e. after the displacement word; +`pc=3FDF90B0` is the store at `0x3FDF9090`, `pc=3FDF8BF0` is the store at +`0x3FDF8BD0`.) + +--- + +## 2. The call chain, bottom to top + +Bits and file offsets: + +| bit | file | what | +|---|---|---| +| `3FDF0630` | `0x17E0C6` | dispatcher reads code from `a13-0x20` (Ghidra `07FBE0C6`) | +| `3FDE7F00` | `0x17CFE0` | VIDEO MEMORY-22 handler (Ghidra `07FBCFE0`) | +| `3FDF9090` | `0x17F212` | `move *a13(0x10), *a14+, 1` — loads status into the dispatch frame | +| `3FDF8FB0` | `0x17F1F6` | `jaeq L3FDF86B0` — "if result == 0, retry" | +| `3FDF8BD0` | `0x17F17A` | `move a8, *a13(0x10), 1` — stores the test's return value | +| `3FDF8BA0` | `0x17F174` | `calla L3FA7C050` — the sub-test | +| `3FA7DE30` | `0x10FBC6` | `movi 0x100, a7` — turns any failure into code 0x100 | +| `3FA7DD70` | `0x10FBAE` | `calla L3F2018B0` — the memory test | +| `3F2018B0` | `0x000316` | the level-1 video-memory test | +| `3F201C10` | `0x000382` | walking-bit read-back | +| `3F201C20` | `0x000384` | walking-bit compare (`xor`) | +| `3F201C80` | `0x000390` | walking-bit failure exit | +| `3F201E90` | `0x0003D2` | REFCNT read | +| `3F201ED0` | `0x0003DA` | REFCNT compare / error branch | +| `3F2070C0` | `0x000E18` | REFCNT error handler (`movi 0xe,a8`) | +| `3F208060` | `0x00100C` | restart entry (`DINT`) the task referenced | + +### 2a. The runner decides the code (bit `0x3FDF8A90` region) + +``` +3fdf8ba0: calla L3FA7C050 +3fdf8bd0: move a8, *a13(0x10), 1 ; store the sub-test's return value +... +3fdf8f90: move *a13(0x10), a11, 1 +3fdf8fb0: jaeq L3FDF86B0 ; 0 => no error, loop back +3fdf8fd0: sub a9, a9 +... +3fdf9090: move *a13(0x10), *a14+, 1 ; pass it on as the error code +3fdf90b0: calla L3FDE55F0 ; -> report/dispatch +``` + +### 2b. `L3FA7C050` maps any non-zero failure to `0x100` + +``` +3fa7dd70: calla L3F2018B0 +3fa7dda0: move a8, a8 +3fa7ddb0: jreq L3FA7DEA0 ; 0 => continue +... +3fa7de30: movi 0x100, a7 ; <-- the literal status code +3fa7de50: move a7, *a11(0x40), 1 +3fa7de70: move a7, a8 +3fa7de80: jauc L3FA900A0 ; return 0x100 +``` + +So the internal status code is set at bit `0x3FA7DE30` (file `0x10FBC6`), and +the condition is simply **"the memory test returned non-zero"**. + +### 2c. `L3F2018B0` is the level-1 video-memory test + +Loaded arguments (breakpoint at `0x3F201990`): + +``` +ARGS a13=00000000 a7=00000000 a3=04000000 a4=00020000 +``` + +* `a3 = 0x04000000` — the walking-bit / pattern target base (screen memory). +* `a4 = 0x00020000` — iteration count. + +Walking-bit loop (16 iterations, single word at `a5 = a3`): + +``` +3f201b20: movi 0x1, a8 +3f201b60: movi 0x10, a9 +L3F201B80: +3f201b80: clr a11 +3f201b90: move a11, *a5, 0 ; write 0 +3f201ba0: move a8, *a5, 0 ; write walking bit + (nops) +3f201c10: move *a5, a11, 0 ; read back +3f201c20: xor a8, a11 ; a11 = read ^ expected +3f201c30: or a11, a12 ; accumulate failures +3f201c40: sll 0x1, a8 +3f201c50: dsjs a9, L3F201B80 +3f201c60: or a12, a12 +3f201c70: jreq L3F201CF0 +3f201c80: move a12, *a13(0x100), 1 ; <-- FAILURE exit +3f201ca0: movi 0x19, a8 ; status 25 +3f201cd0: jauc L3F207200 ; return +``` + +DRAM refresh-counter check (later in the same routine): + +``` +3f201e30: movi 0xea60, a3 +3f201e60: move @0xc00001f0, a0, 0 ; REFCNT +L3F201E90: +3f201e90: move @0xc00001f0, a6, 0 ; REFCNT again +3f201ec0: cmp a0, a6 +3f201ed0: jaeq L3F2070C0 ; equal => FAILURE exit +3f201ef0: move a6, a0 +3f201f00: dsjs a3, L3F201E90 +... +3f2070c0: movi 0xe, a8 ; status 14 +3f2070f0: move a2, a1 +3f207100: jruc L3F207200 ; return +``` + +`L3F207200` is the common error return: it stores `a8` into `a13(0xc0)` / +`a13(0x40)`, then at `3f2073d0` `or a8,a8; jreq L3F207510` returns `a8` +non-zero. (When `sp == 0` it instead `jauc L3F208060`, the `DINT` restart entry.) + +--- + +## 3. Observed failures (raw evidence) + +`harness/mame/code22_final.lua`, 300 frames. + +**Default driver (no video RAM mapped):** + +``` +ARGS a13=00000000 a7=00000000 a3=04000000 a4=00020000 +WALK-CMP read=00000000 expected=00000001 +WALK-CMP read=00000000 expected=00000002 +... (every bit reads 0) ... +WALK-FAIL a12=0000FFFF a8=00010000 a11=00008000 +MEMTEST-RET a8=00000019 +C050-RET-0x100 a8=00000019 +``` + +**With `VT1200_VRAM=1` (video RAM mapped 0x04000000-0x1fffffff):** + +``` +ARGS a13=00000000 a7=00000000 a3=04000000 a4=00020000 +WALK-CMP read=00000001 expected=00000001 +WALK-CMP read=00000002 expected=00000002 +... (every walking bit reads back correctly) ... +REFCNT-CMP a0=00000E74 a6=00000E74 +MEMTEST-RET a8=0000000E +C050-RET-0x100 a8=0000000E +``` + +(i.e. `a0 == a6` — REFCNT did **not** change between the two reads.) + +Additional checks with a targeted failure-branch probe +(`harness/mame/code22_fail.lua`, breakpoints on each error exit of +`L3F2018B0`): + +* no-VRAM run: only `WALKING-BIT-MISMATCH` → `L3F207200(walkerr)` → + `L3F2073D0` → `L3FA7DE30`. +* `VT1200_VRAM=1` run: only `HANDLER-3F2070C0-REFCNT-eq` → `L3F207200(a8=0x0e)` + → `L3F2073D0` → `L3FA7DE30`; the walking-bit branch never fires. + +--- + +## 4. What the condition actually is + +The video-memory test requires, in order: + +1. **A readable/writable region at bit `0x04000000`.** The 32-bit pattern phase + walks `a4 = 0x20000` dwords with post-increment — i.e. **512 KiB** of video + memory at bit `0x04000000`. With no mapping, reads return 0 and the very + first walking-bit compare fails (`a12=0xFFFF`, all 16 bits wrong). +2. **The DRAM refresh counter `REFCNT` (`0xC00001F0`) to change between two + consecutive reads.** MAME returns `(total_cycles()/16) & 0xfffc`, which is + unchanged across the ~2 instructions between `0x3F201E60` and `0x3F201E90` + (both read `0x0E74`). The firmware treats "unchanged" as an error + (`jaeq L3F2070C0`, status `0x0E`). + +Both produce the same outcome: `L3F2018B0` returns non-zero → +`L3FA7C050` returns literal `0x100` → dispatcher code `0x100` → +`LEVEL1/2 ERROR -> VIDEO MEMORY - 22`. + +**Reconciling with the bring-up log's claim** ("video memory is mapped and +round-trips, yet the test still fails"): reproduced. With +`VT1200_VRAM=1` the walking-bit and pattern checks pass and the failure is +*purely* the REFCNT check. That is the remaining requirement. + +Confidence: + +* The chain (dispatcher code 0x100 → handler 0x3FDE7F00; writer of the slot at + `0x3FDF9090`; source `L3FA7C050`; `movi 0x100` at `0x3FA7DE30`; memory test + `L3F2018B0`; branch that fires) — **high**, all measured, both directions. +* The walking-bit condition (reads return 0 when the region is unmapped) — + **high**. +* The REFCNT condition (two consecutive `0xC00001F0` reads equal → error) — + **high** as observed behaviour; the *interpretation* that this is the + intended hardware requirement (vs. a MAME REFCNT granularity artifact) is + **medium**. + +--- + +## 5. What I could not determine + +* The exact original hardware REFCNT tick rate, i.e. whether the firmware's + "must change between two consecutive reads" is satisfiable on real hardware + and merely too slow in MAME. MAME's `REFCNT` is `(total_cycles()/16) & 0xfffc` + (`src/devices/cpu/tms34010/tms34010.cpp:1573`). +* Whether the status slot at bit `0x16F0` is a true global or the frame slot + `a13-0x20` of the `L3FDE55F0` dispatch frame. `a13` is a constant `0x1710` at + the dispatcher in every observed run, and `*a13(0xffe0)` is the standard + first-argument access (7325 read sites), so the slot behaves as a fixed + address either way. +* Which level-1 test *number* (service-guide ordering) `L3FA7C050` corresponds + to beyond "video memory": it is the routine that calls the memory test on the + `0x04000000` region and reports `VIDEO MEMORY`. + +--- + +## 6. Regression + +With no `VT1200_*` env var set, `progress.lua` at 200 frames still prints: + +``` +PC=3FDCE400 samples=199 +``` + +All new instrumentation is env-gated (`VT1200_LOWLOG`). The proxy is +transparent (forwards to the real `mainram` share), so even with `VT1200_LOWLOG` +set the boot path is unchanged. + +--- + +## 7. Files added / changed + +* `harness/mame/vt1200.cpp` — added `VT1200_LOWLOG=`: a transparent, + env-gated logging proxy over bit `0x0000-0x3FFF` (forwards to `mainram`). +* `harness/mame/code22_walk.lua` — dump registers + stack at the dispatcher. +* `harness/mame/code22_probe.lua` — resolve before/after semantics + slot value. +* `harness/mame/code22_fail.lua` — break on each `L3F2018B0` error exit. +* `harness/mame/code22_args.lua` — print memory-test args + walking-bit/REFCNT values. +* `harness/mame/code22_final.lua` — compact evidence capture for this report. diff --git a/harness/mame/code22_args.lua b/harness/mame/code22_args.lua new file mode 100644 index 0000000..1dd154d --- /dev/null +++ b/harness/mame/code22_args.lua @@ -0,0 +1,33 @@ +-- code22_args.lua +-- +-- At the memory test L3F2018B0, print the four loaded arguments and, at its +-- walking-bit failure compare, the pointer a5 and the values involved. +-- +-- CA_FRAMES=300 vt1200 ... -autoboot_script harness/mame/code22_args.lua + +local machine = manager.machine +local cpu = machine.devices[':maincpu'] +local dbg = machine.debugger +if not dbg then print("ERROR: no debugger"); machine:exit(); return end + +dbg:command('bpset 3f201990,,{ printf "MEMTEST-ARGS a13=%08X a7=%08X a3=%08X a4=%08X a14=%08X\\n", a13, a7, a3, a4, a14 ; g }') +dbg:command('bpset 3f201ba0,,{ printf "WALK-WRITE a5=%08X a8=%08X\\n", a5, a8 ; g }') +dbg:command('bpset 3f201c10,,{ printf "WALK-READ a5=%08X a11=%08X expect=%08X\\n", a5, a11, a8 ; g }') +dbg:command('bpset 3f201c80,,{ printf "WALK-FAIL a5=%08X a12=%08X a8=%08X a11=%08X\\n", a5, a12, a8, a11 ; g }') +dbg:command('bpset 3f201e60,,{ printf "REFCNT-INIT a0=%08X\\n", a0 ; g }') +dbg:command('bpset 3f201e90,,{ printf "REFCNT-READ a6=%08X\\n", a6 ; g }') +dbg:command('bpset 3f201ed0,,{ printf "REFCNT-CMP a0=%08X a6=%08X\\n", a0, a6 ; g }') +print("breakpoints armed") + +local maxframes = tonumber(os.getenv("CA_FRAMES") or "300") +local n = 0 +emu.register_frame_done(function() + n = n + 1 + if n >= maxframes then + print("done (" .. n .. " frames)") + machine:exit() + end +end, "vt1200-ca") + +dbg.visible_cpu = cpu +dbg.execution_state = 'run' diff --git a/harness/mame/code22_fail.lua b/harness/mame/code22_fail.lua new file mode 100644 index 0000000..2d1baa0 --- /dev/null +++ b/harness/mame/code22_fail.lua @@ -0,0 +1,45 @@ +-- code22_fail.lua +-- +-- L3FA7C050 returns code 0x100 (-> VIDEO MEMORY-22) when the memory test +-- L3F2018B0 returns nonzero. Find which failure branch inside L3F2018B0 is +-- taken by breaking on each of its error exits. +-- +-- CF_FRAMES=300 vt1200 ... -autoboot_script harness/mame/code22_fail.lua + +local machine = manager.machine +local cpu = machine.devices[':maincpu'] +local dbg = machine.debugger +if not dbg then print("ERROR: no debugger"); machine:exit(); return end + +-- address -> literal label (embedded in the printf, no %s) +local targets = { + { "3f201c80", "WALKING-BIT-MISMATCH" }, + { "3f207080", "HANDLER-3F207080-a2eqa0" }, + { "3f2064c0", "HANDLER-3F2064C0-a2nea0" }, + { "3f2070c0", "HANDLER-3F2070C0-REFCNT-eq" }, + { "3f207050", "HANDLER-3F207050-a1eqa0" }, + { "3f207180", "HANDLER-3F207180-a2nea0-2" }, + { "3f206910", "HANDLER-3F206910-a1nea0" }, + { "3f207200", "HANDLER-3F207200-walkerr" }, + { "3f2073d0", "HANDLER-3F2073D0-spne0" }, + { "3fa7de30", "L3FA7C050-RET-0x100" }, +} +for _, t in ipairs(targets) do + dbg:command(string.format( + 'bpset %s,,{ printf "%s pc=%%08X a8=%%08X a9=%%08X a11=%%08X\\n", pc, a8, a9, a11 ; g }', + t[1], t[2])) +end +print("breakpoints armed: " .. #targets) + +local maxframes = tonumber(os.getenv("CF_FRAMES") or "300") +local n = 0 +emu.register_frame_done(function() + n = n + 1 + if n >= maxframes then + print("done (" .. n .. " frames)") + machine:exit() + end +end, "vt1200-cf") + +dbg.visible_cpu = cpu +dbg.execution_state = 'run' diff --git a/harness/mame/code22_final.lua b/harness/mame/code22_final.lua new file mode 100644 index 0000000..e102b06 --- /dev/null +++ b/harness/mame/code22_final.lua @@ -0,0 +1,37 @@ +-- code22_final.lua -- clean evidence capture for CODE22_REPORT.md +-- +-- Prints, in order: the memory test's loaded args, the walking-bit read vs +-- expected, the walking-bit failure exit, the DRAM refresh-counter compare, +-- the memory test's return value, and L3FA7C050's final return. +-- +-- CFF_FRAMES=300 vt1200 ... -autoboot_script harness/mame/code22_final.lua + +local machine = manager.machine +local cpu = machine.devices[':maincpu'] +local dbg = machine.debugger +if not dbg then print("ERROR: no debugger"); machine:exit(); return end + +local function bp(addr, fmt, regs) + dbg:command(string.format('bpset %s,,{ printf "%s\\n", %s ; g }', addr, fmt, regs)) +end + +bp("3f201990", "ARGS a13=%08X a7=%08X a3=%08X a4=%08X", "a13, a7, a3, a4") +bp("3f201c20", "WALK-CMP read=%08X expected=%08X", "a11, a8") +bp("3f201c80", "WALK-FAIL a12=%08X a8=%08X a11=%08X", "a12, a8, a11") +bp("3f201ed0", "REFCNT-CMP a0=%08X a6=%08X", "a0, a6") +bp("3fa7dda0", "MEMTEST-RET a8=%08X", "a8") +bp("3fa7de30", "C050-RET-0x100 a8=%08X", "a8") +print("breakpoints armed") + +local maxframes = tonumber(os.getenv("CFF_FRAMES") or "300") +local n = 0 +emu.register_frame_done(function() + n = n + 1 + if n >= maxframes then + print("done (" .. n .. " frames)") + machine:exit() + end +end, "vt1200-cff") + +dbg.visible_cpu = cpu +dbg.execution_state = 'run' diff --git a/harness/mame/code22_probe.lua b/harness/mame/code22_probe.lua new file mode 100644 index 0000000..62d8ba5 --- /dev/null +++ b/harness/mame/code22_probe.lua @@ -0,0 +1,32 @@ +-- code22_probe.lua +-- +-- Resolve two mechanics at the level-1 error dispatcher: +-- (1) does a bpset fire BEFORE or AFTER its instruction executes? +-- (2) what 32-bit value does the dispatcher read from *a13(0xffe0)? +-- +-- We break at 0x3FDF0630 (the read) and at 0x3FDF0650 (the next instruction, +-- jreq) and print a8 / a13 / the dword at the read address. +-- +-- CP_FRAMES=300 vt1200 ... -autoboot_script harness/mame/code22_probe.lua + +local machine = manager.machine +local cpu = machine.devices[':maincpu'] +local dbg = machine.debugger +if not dbg then print("ERROR: no debugger"); machine:exit(); return end + +dbg:command('bpset 3fdf0630,,{ printf "AT-READ pc=%08X a13=%08X a14=%08X a8=%08X d@16F0=%08X\\n", pc, a13, a14, a8, d@16F0 ; g }') +dbg:command('bpset 3fdf0650,,{ printf "AT-NEXT pc=%08X a8=%08X d@16F0=%08X\\n", pc, a8, d@16F0 ; g }') +print("breakpoints armed") + +local maxframes = tonumber(os.getenv("CP_FRAMES") or "300") +local n = 0 +emu.register_frame_done(function() + n = n + 1 + if n >= maxframes then + print("done (" .. n .. " frames)") + machine:exit() + end +end, "vt1200-cp") + +dbg.visible_cpu = cpu +dbg.execution_state = 'run' diff --git a/harness/mame/code22_walk.lua b/harness/mame/code22_walk.lua new file mode 100644 index 0000000..4a0c860 --- /dev/null +++ b/harness/mame/code22_walk.lua @@ -0,0 +1,43 @@ +-- code22_walk.lua +-- +-- Breaks at the level-1 error dispatcher read (bit 0x3FDF0630) and dumps: +-- * registers sp / a13 / a14 / a8 / a9 +-- * the word the dispatcher reads: *a13(0xffe0) = memory at (a13 - 0x20) +-- * a window of low RAM around the alleged slot a13-0x20 +-- * the whole task stack, sp .. 0x5400, one line per word +-- +-- All memory addresses in the debugger are BIT addresses; a word is 0x10 +-- bits apart. Only plain (unparenthesised) expressions are used: a +-- `d@(sp+20h)` action silently breaks bpset. +-- +-- CW_FRAMES=300 vt1200 ... -debug -debugger none \ +-- -autoboot_script harness/mame/code22_walk.lua + +local machine = manager.machine +local cpu = machine.devices[':maincpu'] +local dbg = machine.debugger +if not dbg then print("ERROR: no debugger"); machine:exit(); return end + +local act = "{ " + .. 'printf "DISP sp=%08X a13=%08X a14=%08X a8=%08X a9=%08X a11=%08X\\n", sp, a13, a14, a8, a9, a11 ; ' + .. 'printf "SLOT d@16E0=%04X d@16F0=%04X d@1700=%04X d@1710=%04X d@1720=%04X d@1730=%04X\\n", d@16E0, d@16F0, d@1700, d@1710, d@1720, d@1730 ; ' + .. 'printf "S00 %04X %04X %04X %04X %04X %04X %04X %04X\\n", d@5280, d@5290, d@52A0, d@52B0, d@52C0, d@52D0, d@52E0, d@52F0 ; ' + .. 'printf "S01 %04X %04X %04X %04X %04X %04X %04X %04X\\n", d@5300, d@5310, d@5320, d@5330, d@5340, d@5350, d@5360, d@5370 ; ' + .. 'printf "S02 %04X %04X %04X %04X %04X %04X %04X %04X\\n", d@5380, d@5390, d@53A0, d@53B0, d@53C0, d@53D0, d@53E0, d@53F0 ; ' + .. 'g }' + +dbg:command("bpset 3fdf0630,," .. act) +print("breakpoint armed at 3fdf0630") + +local maxframes = tonumber(os.getenv("CW_FRAMES") or "300") +local n = 0 +emu.register_frame_done(function() + n = n + 1 + if n >= maxframes then + print("done (" .. n .. " frames)") + machine:exit() + end +end, "vt1200-cw") + +dbg.visible_cpu = cpu +dbg.execution_state = 'run' diff --git a/harness/mame/vt1200.cpp b/harness/mame/vt1200.cpp index 471bb08..4c8baef 100644 --- a/harness/mame/vt1200.cpp +++ b/harness/mame/vt1200.cpp @@ -208,6 +208,18 @@ private: u16 unmapped_r(offs_t off); void unmapped_w(offs_t off, u16 data); void log_unmapped(const char *kind, u32 addr, const char *pc); + + // VT1200_LOWLOG= + // A *transparent* logging proxy over the bottom of main RAM (bit + // 0x00000000-0x00003fff). It reads/writes the real mainram share, so it + // does not perturb the machine; it just records every distinct + // (addr, pc, value, kind) seen. Used to catch the write that loads the + // level-1 error code into the frame slot the dispatcher reads. + FILE *m_lowlog = nullptr; + std::map, u32> m_lowlog_seen; // (addr,pc,val,kind) -> count + u16 lowlog_r(offs_t off); + void lowlog_w(offs_t off, u16 data); + void log_low(char kind, u32 addr, u16 value); }; void vt1200_state::machine_start() @@ -313,6 +325,13 @@ void vt1200_state::machine_start() fprintf(m_vramlog, "# kind off value pc -- video-memory accesses\n"); } + if (const char *p = getenv("VT1200_LOWLOG")) + { + m_lowlog = fopen(p, "w"); + if (m_lowlog) + fprintf(m_lowlog, "# kind bitaddr pc value -- transparent low-RAM access log\n"); + } + logerror("vt1200: board model=%s busy_ticks=%s busy_w2=%s busy_w3=%s dma_ticks=%s switch=%s log=%s render=%d dump=%ld\n", getenv("VT1200_BOARD_MODEL") ? getenv("VT1200_BOARD_MODEL") : "off", getenv("VT1200_BOARD_BUSY_TICKS") ? getenv("VT1200_BOARD_BUSY_TICKS") : "0", @@ -397,6 +416,8 @@ void vt1200_state::machine_reset() fflush(m_log); if (m_unmapped_log) fflush(m_unmapped_log); + if (m_lowlog) + fflush(m_lowlog); } void vt1200_state::log_unmapped(const char *kind, u32 addr, const char *pc) @@ -430,6 +451,37 @@ void vt1200_state::unmapped_w(offs_t off, u16 data) log_unmapped("W", (u32)off, pc); } +// Transparent low-RAM logging proxy. It sits over bit 0x0000-0x3fff (the +// frame/argument block the self-test uses) and forwards reads/writes to the +// real mainram share, so it changes nothing about execution. Base is 0, so +// the delegate offset is (bitaddr >> 4) == the u16 index into mainram. +void vt1200_state::log_low(char kind, u32 addr, u16 value) +{ + if (!m_lowlog) + return; + const u32 pc = (u32)m_maincpu->state_int(TMS34010_PC); + auto key = std::make_tuple(addr, pc, (u32)value, (u32)(u8)kind); + if (m_lowlog_seen.find(key) == m_lowlog_seen.end()) + { + m_lowlog_seen.emplace(key, 1); + fprintf(m_lowlog, "%c %06X pc=%08X val=%04X\n", kind, addr, pc, value); + fflush(m_lowlog); + } +} + +u16 vt1200_state::lowlog_r(offs_t off) +{ + const u16 v = m_mainram[(u32)off & 0x3ff]; + log_low('R', (u32)off << 4, v); + return v; +} + +void vt1200_state::lowlog_w(offs_t off, u16 data) +{ + m_mainram[(u32)off & 0x3ff] = data; + log_low('W', (u32)off << 4, data); +} + void vt1200_state::program_map(address_map &map) { // Catch-all for anything we do not model -- registered FIRST so every region @@ -522,6 +574,11 @@ void vt1200_state::program_map(address_map &map) map(BOARD_BASE[2], BOARD_BASE[2] + 0xffff).rw(FUNC(vt1200_state::board2_r), FUNC(vt1200_state::board2_w)); map(BOARD_BASE[3], BOARD_BASE[3] + 0xffff).rw(FUNC(vt1200_state::board3_r), FUNC(vt1200_state::board3_w)); } + + // Transparent low-RAM logging proxy, registered LAST so it wins its slice + // of RAM. It forwards to the real mainram share and only records. + if (getenv("VT1200_LOWLOG")) + map(0x00000000, 0x00003fff).rw(FUNC(vt1200_state::lowlog_r), FUNC(vt1200_state::lowlog_w)); } // MAME may hand us either an entry-relative offset (0..0xffff) or the absolute