mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
harness: skip run is STUCK at the X11 weave -- wall is unmodelled DUART2 (phantom ISR read at 0x06200005); add multi-timestamp dumps + console string resolver
The skip gate reaches the root weave and stops: video RAM and the rendered
frame are byte-identical at frames 3000/10000/30000/45000/60000 (42 s -> 14
min), and all 4 MiB of main RAM is identical at every sample except one byte
that oscillates. The PC histogram is tight: entered at frame ~611, dominated
by 0x3FB30390/0x3FB30110.
Wall: DUART2 (0x0620000, keyboard/mouse) is unmodelled, so the read-only ISR
at 0x06200005 is served by the board write-back latch and returns the value
the OS wrote to the write-only IMR (0x0033). The OS's DUART dispatcher
(0x3FB43800) masks 0x66 -> 0x22, so it services a phantom interrupt that can
never clear, spinning in the channel handler 0x3FB42410 and queue routine
0x3FB300F0. Forcing only 0x06200005 to 0 reproduces the full DUART2-mapped
run byte-for-byte; with VT1200_DUART2=1 the Terminal Manager dialog box from
the reference video appears at frame 3000 and the machine runs on.
Defaults unchanged: no-env still reaches HOST PORT - 15, skip still reaches
the weave. New opt-in knobs VT1200_DUMP_{FRAME,RAM_FRAMES,VRAM_FRAMES} and
resolve_console.py; progress.lua untouched.
This commit is contained in:
1 parent
855f3e85e9
commit
c06cf7096b
3 files changed
+439
-22
No files matched your search
@@ -0,0 +1,307 @@
|
||||
# VT1200 OS bring-up: is the skip run progressing or stuck?
|
||||
|
||||
Date: 2026-10-16. Scope: name the wall the `VT1200_SKIP_LEVEL1=1` run hits after
|
||||
the X11 root weave, with evidence that spans emulated time. ROM untouched
|
||||
(`md5 b46a3e442ccb86134d2356bd70d22fd2`).
|
||||
|
||||
**Verdict: STUCK, and the wall is named.** It is not slow — the framebuffer is
|
||||
byte-identical across millions of emulated cycles, while a single RAM byte
|
||||
oscillates. The wall is the **unmodelled DUART2** (keyboard/mouse, bit
|
||||
`0x0620000`): the OS's DUART interrupt dispatcher reads DUART2's read-only
|
||||
interrupt-status register (ISR) and gets back the value the OS itself wrote to
|
||||
the write-only interrupt-mask register (IMR) — same chip register address,
|
||||
`0x06200005` — so it services a **phantom interrupt that can never clear**. Mapping MAME's already-present `SCN2681` there clears it, and the
|
||||
GUI's Terminal Manager **dialog box** — the reference video's ~0:48 target —
|
||||
appears.
|
||||
|
||||
---
|
||||
|
||||
## 1. Progressing or stuck — evidence across time
|
||||
|
||||
One skip run, dumps at increasing frames (72 Hz: 3000 ≈ 42 s, 10000 ≈ 2.3 min,
|
||||
30000 ≈ 7 min, 60000 ≈ 14 min). A **frame** (rendered PPM) and the **video
|
||||
RAM** and **main RAM** were written at each sample.
|
||||
|
||||
| frame | ≈ emulated | frame PPM md5 | video RAM md5 | main RAM md5 |
|
||||
|---|---|---|---|---|
|
||||
| 3000 | 42 s | `511743ace0999c5fa60cfffcd8e7c130` | `5ce8ad80d78efc26146b95eeb214a785` | `95dccfd3ae6c43a9f61e8caa22c6f3ea` |
|
||||
| 10000 | 2.3 m | `511743ace0999c5fa60cfffcd8e7c130` | `5ce8ad80d78efc26146b95eeb214a785` | `600f29d7d74dc3cbd139708be71bd77d` |
|
||||
| 30000 | 7 m | `511743ace0999c5fa60cfffcd8e7c130` | `5ce8ad80d78efc26146b95eeb214a785` | `95dccfd3ae6c43a9f61e8caa22c6f3ea` |
|
||||
| 45000 | 10.4 m| `511743ace0999c5fa60cfffcd8e7c130` | `5ce8ad80d78efc26146b95eeb214a785` | `95dccfd3ae6c43a9f61e8caa22c6f3ea` |
|
||||
| 60000 | 14 m | `511743ace0999c5fa60cfffcd8e7c130` | `5ce8ad80d78efc26146b95eeb214a785` | `95dccfd3ae6c43a9f61e8caa22c6f3ea` |
|
||||
|
||||
* The **framebuffer is byte-identical** at 3000, 10000, 30000, 45000 and 60000:
|
||||
58,720,256 bytes of video RAM, same md5, five times, spanning **~14 minutes**
|
||||
of emulated time. The rendered frame is identical too (19.3158% lit — the
|
||||
root weave).
|
||||
* The 4 MiB of **main RAM differs by exactly one byte** between 3000 and 10000,
|
||||
at offset `0x829e2`: `0x50` → `0x52`. That is the only moving thing in all of
|
||||
memory. At 30000 **and again at 45000/60000 the whole 4 MiB is byte-identical
|
||||
to frame 3000** — the byte is back to `0x50`. So the machine is in a **limit
|
||||
cycle**, not advancing: a counter toggling inside a dead loop.
|
||||
* Net: no drawing, no new window, no changed state over ~14 emulated minutes.
|
||||
This is a deadlock, not slow progress. (`BRINGUP_LOG.md` already retired one
|
||||
false "it never paints" conclusion from a single sample; this conclusion is
|
||||
from five samples, the last 14 minutes after the first.)
|
||||
|
||||
### PC distribution over the whole run (progress.lua, 60000 frames)
|
||||
|
||||
143 distinct PCs sampled once per frame. The distribution is **tight**, and
|
||||
the loop begins at **frame ~611** (≈ 8.7 s) and never leaves:
|
||||
|
||||
```
|
||||
PC=3FB30390 samples=10020 first_frame=614
|
||||
PC=3FB30110 samples=9777 first_frame=611
|
||||
PC=3FB30370 samples=1834 first_frame=629
|
||||
PC=3FB42E50 samples=1589 first_frame=628
|
||||
PC=3FB42EF0 samples=1223 first_frame=618
|
||||
PC=3FB301F0 samples=1223 first_frame=615
|
||||
PC=3FB42710 samples=1223 first_frame=622
|
||||
PC=3FB30180 samples=1223 first_frame=620
|
||||
PC=3FB42F30 samples=1223 first_frame=613
|
||||
PC=3FB30240 samples=1222 first_frame=634
|
||||
PC=3FB42610 samples=1222 first_frame=632
|
||||
PC=3FB42760 samples=1160 first_frame=670
|
||||
```
|
||||
|
||||
All of `0x3FB30110`–`0x3FB30390` and `0x3FB42610`–`0x3FB42F30` is either the
|
||||
OS serial-channel interrupt handler or the queue routine it calls (see §3).
|
||||
|
||||
---
|
||||
|
||||
## 2. The wall, named
|
||||
|
||||
**PC / routine.** The hot code is the OS's DUART interrupt service:
|
||||
|
||||
| bit address | what |
|
||||
|---|---|
|
||||
| `0x3FB43800` | DUART interrupt dispatcher (ISR). Reads DUART1 and DUART2 status, masks `0x66`, calls the channel handler for each pending channel. |
|
||||
| `0x3FB42410` | the per-channel handler it calls (`calla` at `0x3FB439C0` and `0x3FB43AF0`). |
|
||||
| `0x3FB300F0` | the lock-protected byte-queue routine the handler calls (`calla` at `0x3FB42CD0`, `0x3FB42E20`); its tail `rets` at `0x3FB30390` is the single hottest sampled PC. |
|
||||
|
||||
**The address it polls and the value it gets.** `VT1200_BOARD_LOG` over the run
|
||||
records the distinct accesses of the stuck phase:
|
||||
|
||||
```
|
||||
W 06200005 pc=3F6F3C50 val=0033 ; OS writes DUART2 IMR = 0x33 (interrupt mask)
|
||||
R 06200005 pc=3FB43A20 val=0033 ; ISR read returns the SAME value
|
||||
R 06200001 pc=3FB42610 val=0098 ; DUART2 status
|
||||
W 06200002 pc=3FB426F0 val=0040 ; DUART2 control write
|
||||
R 06400005 pc=3FB43900 val=0009 ; DUART1 ISR (real chip: legitimate)
|
||||
```
|
||||
|
||||
On an SCN2681/MC68681, register 5 is the **ISR when read** and the **IMR when
|
||||
written** — two different registers at one address. DUART2 is not modelled, so
|
||||
the address falls through to the board-window write-back latch, which returns
|
||||
the value last written. The dispatcher therefore reads `0x0033` as the ISR on
|
||||
every dispatch; it masks it with `0x66`:
|
||||
|
||||
```
|
||||
0x33 & 0x66 = 0x22 != 0 -> "DUART2 has a pending interrupt" -> handler runs
|
||||
```
|
||||
|
||||
and the condition **can never clear**, because the "ISR" always reads back the
|
||||
IMR. That is the deadlock: the CPU spins in the channel handler and its queue
|
||||
routine, at `0x3FB300F0`–`0x3FB30390`, forever.
|
||||
|
||||
**What is missing.** A real DUART2 — the SCN2681/MC68681 at bit `0x0620000`
|
||||
(channel A keyboard, channel B mouse) — whose read-only ISR is distinct from its
|
||||
write-only IMR, and whose interrupts are delivered on INT2. Without it, the OS
|
||||
enables DUART2 interrupts it can never receive (§4.1) *and* sees a permanent
|
||||
phantom pending bit (§2).
|
||||
|
||||
MAME already contains that part, and the driver already wires it:
|
||||
|
||||
```sh
|
||||
VT1200_SKIP_LEVEL1=1 VT1200_DUART2=1 <mame> vt1200 ...
|
||||
```
|
||||
|
||||
With `VT1200_DUART2=1` the deadlock clears and the screen **changes between
|
||||
3000 and 10000** (39.99% → 39.90% lit; video-RAM md5 differs) — the machine is
|
||||
progressing.
|
||||
|
||||
### Causal proof: it is that one register value
|
||||
|
||||
Mapping the whole chip could have fixed many things at once. It did not need
|
||||
to: forcing **only** the read of `0x06200005` to `0` — nothing else changed —
|
||||
reproduces the DUART2 run **byte-for-byte**:
|
||||
|
||||
```sh
|
||||
env VT1200_SKIP_LEVEL1=1 VT1200_BOARD_MAP="0x06200005=0" <mame> vt1200 ...
|
||||
frame 3000 39.9879% lit, video-RAM md5 73c3c9a1f13378d3ccf1e092aa47fa60
|
||||
frame 10000 39.8986% lit, video-RAM md5 4ff38c8c782684c562369dc47fb8a781
|
||||
```
|
||||
|
||||
identical to the `VT1200_DUART2=1` run's md5s. So the wall is exactly this:
|
||||
the value returned by the read at bit `0x06200005` (DUART2 register 5, ISR),
|
||||
`0x0033` instead of a real ISR clear. The OS does *not* need DUART2 interrupts
|
||||
to reach the GUI — it only needs to stop seeing the phantom one.
|
||||
|
||||
---
|
||||
|
||||
## 3. The disassembly that pins it
|
||||
|
||||
```
|
||||
dispatcher 0x3FB438E0..0x3FB43AF0
|
||||
3fb438e0: move *a11(0x50),a7,0 ; a11 = 0x0640000 (DUART1); offset 0x50 bits = reg 5
|
||||
3fb43910: andi 0x66,a7 / jreq ; skip if no pending channel-A/B
|
||||
3fb439c0: calla 0x3fb42410 ; -> channel handler
|
||||
3fb43a00: move *a12(0x50),a7,0 ; a12 = 0x0620000 (DUART2); reg 5 again
|
||||
3fb43a40: andi 0x66,a7 / jreq ; <- (0x33 & 0x66) != 0, never skips here
|
||||
3fb43af0: calla 0x3fb42410 ; -> channel handler, every dispatch
|
||||
```
|
||||
|
||||
The channel handler `0x3FB42410` reads `*a6(0x10)` and `*a6(0x30)` (the DUART
|
||||
SRA/CSR) and calls the queue routine `0x3FB300F0`, whose body opens with a
|
||||
test-and-set lock (`move a12,*a7,0` on the global at `@0x1b3900+0x1c1`) and a
|
||||
ring-buffer head/tail compare — i.e. it is draining a serial channel's queue.
|
||||
|
||||
---
|
||||
|
||||
## 4. Prime suspects
|
||||
|
||||
### 4.1 Interrupts enabled but never received — **this is the wall, via DUART2**
|
||||
|
||||
The OS writes `IMR = 0x33` to DUART2 (`0x06200005`), enabling channel-A and
|
||||
channel-B TxRDY/Rx interrupts. DUART2 is unmapped, so **no DUART2 interrupt is
|
||||
ever delivered** (the `SCN2681` object exists in the machine config but is never
|
||||
addressed, so it never raises its INT2 line). The write-back latch makes it
|
||||
worse than "silent": the read-only ISR aliases the value just written to the
|
||||
write-only IMR, so the dispatcher sees a pending interrupt that never clears.
|
||||
Everything else (DUART1 on INT2, the display interrupt) is healthy — which is
|
||||
exactly the shape the brief predicted: a task that never runs because its
|
||||
interrupt never fires, with everything else looking fine.
|
||||
|
||||
### 4.2 LANCE Ethernet — not modelled, but **not** the wall
|
||||
|
||||
Not modelled; the window `0x0580000/0x0590000` serves it and reads return the
|
||||
board latch. The OS does drive it — a cluster of writes/reads at
|
||||
`0x3F9D2880`–`0x3F9D46D0` (the board log shows `W 05900000 … val=0000/0001/…`,
|
||||
`R 05800000 … val=0001`), and `LANCE`/`Ethernet Port Error` are in the ROM. But
|
||||
the OS **gives up after a bounded number of retries** and continues: in the
|
||||
DUART2 run the dialog reports
|
||||
|
||||
```
|
||||
Could not determine IP address after 5 retries
|
||||
TCP/IP is now down.
|
||||
```
|
||||
|
||||
which is the emulated analogue of the real terminal's `Ethernet Port Error - 24`
|
||||
banner. So LANCE degrades gracefully; it does not hang the boot.
|
||||
|
||||
### 4.3 DUART2 keyboard/mouse — **the wall** (see §2)
|
||||
|
||||
Note on the earlier, untrusted lead ("a queue scan reading DUART2 registers"):
|
||||
that part is **real and reproducible** — `R 06200005 pc=3FB43A20`,
|
||||
`R 06200001 pc=3FB42610` — but the same investigation's "no framebuffer
|
||||
updates" claim was wrong, so its *conclusion* (that this queue scan was benign)
|
||||
is the part to discard. This report treats the registers as data and lets the
|
||||
mapped-vs-unmapped experiment settle it, which it does.
|
||||
|
||||
### 4.4 NVRAM — read and used, not a wall
|
||||
|
||||
The dialog itself proves NVRAM is reached: it renders `Secure Reset Count: 0`,
|
||||
`User settings invalid.`, `System defaults loaded.`. No stall there.
|
||||
|
||||
---
|
||||
|
||||
## 5. Does the dialog box from the reference video appear?
|
||||
|
||||
**Yes — with DUART2 modelled.** This is an independent, checkable target and it
|
||||
is reached.
|
||||
|
||||
`reference/vt1200-boot-video/real_t52.png` shows the real machine's Terminal
|
||||
Manager V2.2 dialog (Ethernet Address / IP Address / LAT Host Name / Memory
|
||||
Remaining bar / Secure Reset Count / a banner at the bottom). The emulated
|
||||
`VT1200_SKIP_LEVEL1=1 VT1200_DUART2=1` run renders the same box:
|
||||
|
||||
* **frame 3000 (≈ 42 s)** — "Terminal Manager V2.2 Copyright © 1991 Digital
|
||||
Equipment Corporation", Icon Box / TM icons, Messages *"Using local fonts.
|
||||
User settings invalid. Using local fonts. System defaults loaded."*,
|
||||
Ethernet Address `00-00-00-00-00-00`, LAT Host Name `LAT_00000000000`,
|
||||
*Memory Remaining, Kilobytes* bar (3104), *Secure Reset Count: 0*, plus the
|
||||
terminal-manager mouse cursor over the root weave. Bottom banner:
|
||||
*"All Diagnostics Passed"*.
|
||||
* **frame 10000 (≈ 2.3 min)** — the same dialog, and the message area has
|
||||
advanced to *"Could not determine IP address after 5 retries / TCP/IP is now
|
||||
down."* — i.e. the machine is **still running** and reporting the (expected)
|
||||
absence of a LANCE.
|
||||
* **frame 30000 (≈ 7 min)** — identical to frame 10000 (same PPM and video-RAM
|
||||
md5). The machine is now **idle at the GUI**, which is the correct state: no
|
||||
keyboard/mouse device is attached to feed it events, so it just waits. Contrast
|
||||
the no-DUART2 case, which is stalled *before* the GUI and never changes at all.
|
||||
|
||||
So the boot path is healthy once DUART2 is present; the only difference from the
|
||||
real machine is the network (no LANCE) and the banner text (level 1 was skipped
|
||||
by the gate). Note the two "stable at 10000→30000" results are not the same
|
||||
thing: the DUART2 run made it *to* the interactive GUI and then idled, whereas
|
||||
the no-DUART2 run is byte-frozen at the root weave from frame ~611 onward.
|
||||
|
||||
---
|
||||
|
||||
## 6. Reproduce
|
||||
|
||||
```sh
|
||||
cd /home/arti/code/k-space/vt1200
|
||||
M=/home/arti/code/k-space/mame0289/vt1200
|
||||
R="-rompath harness/mame/roms -sound none -skip_gameinfo -video none -nothrottle"
|
||||
|
||||
# A. stuck, with time-samples (the §1 table). Do NOT attach progress.lua here:
|
||||
# its exit at frame N would race the frame-N dump. 60000 frames ≈ 860 s.
|
||||
env VT1200_SKIP_LEVEL1=1 \
|
||||
VT1200_DUMP_FRAMES=3000,10000,30000,45000,60000 \
|
||||
VT1200_DUMP_VRAM_FRAMES=3000,10000,30000,45000,60000 \
|
||||
VT1200_DUMP_RAM_FRAMES=3000,10000,30000,45000,60000 \
|
||||
VT1200_DUMP_DIR=/tmp/vt1200 $M vt1200 $R -seconds_to_run 950
|
||||
|
||||
# B. progressing, dialog appears
|
||||
env VT1200_SKIP_LEVEL1=1 VT1200_DUART2=1 \
|
||||
VT1200_DUMP_FRAMES=3000,10000 \
|
||||
VT1200_DUMP_DIR=/tmp/vt1200d2 $M vt1200 $R -seconds_to_run 160
|
||||
|
||||
# C. console transcript (firmware's own printer), resolved to strings
|
||||
env VT1200_SKIP_LEVEL1=1 $M vt1200 $R -debug -debugger none \
|
||||
-autoboot_script harness/mame/cap.lua -autoboot_delay 0 -debuglog
|
||||
python3 harness/mame/resolve_console.py debug.log vt1200_v22.bin
|
||||
```
|
||||
|
||||
New instrumentation added to `vt1200.cpp` for A/B (all opt-in, default off, so
|
||||
neither the no-env nor the skip baseline changes):
|
||||
`VT1200_DUMP_FRAMES`, `VT1200_DUMP_RAM_FRAMES`, `VT1200_DUMP_VRAM_FRAMES` —
|
||||
comma-separated lists, dumped at exactly those frames **in one run**.
|
||||
`resolve_console.py` resolves `cap.lua`'s `a9` message pointers to strings.
|
||||
|
||||
### Baselines (unchanged)
|
||||
|
||||
* **no env** → POST + level 1 to `LEVEL1/2 ERROR -> HOST PORT - 15` (verified
|
||||
this session with `cap.lua`; then `TYPE 'GO' TO CONTINUE`).
|
||||
* **`VT1200_SKIP_LEVEL1=1`** → the X11 root weave (`skip_frame3000.png`),
|
||||
byte-identical at 3000/10000/30000.
|
||||
* `progress.lua` was **not** changed; its no-env 200-frame baseline is intact.
|
||||
|
||||
### Not-changed on purpose
|
||||
|
||||
`VT1200_DUART2` is **not** promoted to a default. A no-env run *with*
|
||||
`VT1200_DUART2=1` leaves the faithful path entirely and drops into the level-0
|
||||
interactive self-test menu (`VERSION 2.08 - SELECTED TEST TYPE …`), which would
|
||||
break "no-env runs POST + level 1 to HOST PORT - 15". The GUI is therefore the
|
||||
documented combination `VT1200_SKIP_LEVEL1=1 VT1200_DUART2=1`.
|
||||
|
||||
---
|
||||
|
||||
## 7. What I did NOT establish
|
||||
|
||||
* The exact micro-dynamics of the loop (whether the handler is re-entered purely
|
||||
by a DUART1 TxRDY interrupt storm, or by the main loop re-polling). The
|
||||
*mechanism* — an ISR read that cannot clear — is proven by the board log and
|
||||
by the mapped/unmapped experiment; the cycle-accounting is not.
|
||||
* Whether MAME's `SCN2681` reproduces the real VT1200 DUART2 *exactly* (it is the
|
||||
right part family and the OS accepts it, but no keyboard/mouse device is
|
||||
attached, so RX behaviour is untested).
|
||||
* What happens after the dialog on real hardware ("light screen ~1:08"). With
|
||||
DUART2 the emulated run reaches the interactive GUI and then idles
|
||||
(identical 10000→30000), so I did not chase a post-dialog sequence; the "light
|
||||
screen" in the reference is most likely the video camera's CRT
|
||||
auto-exposure, not content.
|
||||
* NVRAM contents/initialisation policy — only that the OS reads it and falls
|
||||
back to system defaults.
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Resolve the a9 message pointers from a cap.lua debug.log capture.
|
||||
|
||||
cap.lua breaks at the firmware's console-print entry (bit 0x3FDE00C0) and logs
|
||||
`PRINT pc=.. a9=.. a11=.. a5=..` lines (they land in MAME's -debuglog file,
|
||||
i.e. `debug.log` in the CWD). a9 is a BIT address; the ROM image is byte-based
|
||||
at 0x07E40000, so the string bytes are at file offset (a9 >> 3) - 0x07E40000.
|
||||
|
||||
Usage: resolve_console.py <debug.log> [rom.bin]
|
||||
"""
|
||||
import sys
|
||||
|
||||
ROM_BASE_BYTE = 0x07E40000
|
||||
|
||||
|
||||
def main() -> int:
|
||||
log = sys.argv[1]
|
||||
rompath = sys.argv[2] if len(sys.argv) > 2 else "vt1200_v22.bin"
|
||||
with open(rompath, "rb") as f:
|
||||
rom = f.read()
|
||||
|
||||
seen = set()
|
||||
with open(log, "r", errors="replace") as f:
|
||||
for line in f:
|
||||
if "PRINT pc=" not in line:
|
||||
continue
|
||||
fields = dict(
|
||||
kv.split("=", 1) for kv in line.split() if "=" in kv
|
||||
)
|
||||
a9 = fields.get("a9")
|
||||
a11 = fields.get("a11")
|
||||
if a9 is None:
|
||||
continue
|
||||
bit = int(a9, 16)
|
||||
off = (bit >> 3) - ROM_BASE_BYTE
|
||||
key = (a9, a11)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
if 0 <= off < len(rom):
|
||||
end = rom.find(b"\x00", off)
|
||||
raw = rom[off:end if end != -1 else off + 80]
|
||||
txt = raw.decode("latin-1").replace("\n", "\\n")
|
||||
print(f"a9={a9} a11={a11} {txt!r}")
|
||||
else:
|
||||
print(f"a9={a9} a11={a11} <not a ROM pointer: off={off:#x}>")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+81
-22
@@ -63,6 +63,11 @@
|
||||
vt1200-ram-<n>.bin
|
||||
VT1200_DUMP_VRAM=<n> write video memory at frame <n> as
|
||||
vram-<n>.bin
|
||||
VT1200_DUMP_FRAMES=<list> comma-separated frames, all in ONE run, so the
|
||||
same machine can be sampled at several
|
||||
increasing times and the samples compared.
|
||||
Same for VT1200_DUMP_RAM_FRAMES and
|
||||
VT1200_DUMP_VRAM_FRAMES.
|
||||
VT1200_DUMP_DIR=<dir> directory for the dumps (default ".")
|
||||
VT1200_NO_RENDER=1 blank the screen instead of painting it
|
||||
|
||||
@@ -111,6 +116,7 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "vt1200_board.h"
|
||||
|
||||
@@ -163,9 +169,9 @@ public:
|
||||
const tms340x0_device::display_params *params);
|
||||
void render_active_line(bitmap_ind16 &bitmap, int scanline,
|
||||
const tms340x0_device::display_params *params);
|
||||
void dump_frame(screen_device &screen, bitmap_ind16 &bitmap);
|
||||
void dump_ram();
|
||||
void dump_vram();
|
||||
void dump_frame(screen_device &screen, bitmap_ind16 &bitmap, long frame);
|
||||
void dump_ram(long frame);
|
||||
void dump_vram(long frame);
|
||||
|
||||
// We drive the TMS34010 renderer ourselves so the frame dump does not
|
||||
// depend on the device's scanline-callback plumbing.
|
||||
@@ -224,8 +230,14 @@ private:
|
||||
// Video. Rendering is on by default now that the firmware reaches the
|
||||
// display code; VT1200_NO_RENDER=1 restores the old blank-everything
|
||||
// behaviour. VT1200_DUMP_FRAME=<n> writes that frame as a PPM.
|
||||
//
|
||||
// The *_FRAMES variants take a comma-separated list and dump at EXACTLY
|
||||
// each frame, so one run can sample the same machine at several increasing
|
||||
// times -- the instrument for telling "progressing" from "stuck": if two
|
||||
// samples minutes apart are byte-identical, nothing is being drawn.
|
||||
bool m_render = true;
|
||||
long m_dump_frame = -1;
|
||||
std::vector<long> m_dump_frames; // VT1200_DUMP_FRAMES=3000,10000,...
|
||||
std::string m_dump_dir = ".";
|
||||
|
||||
// VT1200_DPYLOG=<path>: dump the TMS34010 display params for the first
|
||||
@@ -239,11 +251,13 @@ private:
|
||||
// vt1200-ram-<frame>.bin so the framebuffer can be located offline.
|
||||
long m_dump_ram = -1;
|
||||
bool m_dumped_ram = false;
|
||||
std::vector<long> m_dump_ram_frames; // VT1200_DUMP_RAM_FRAMES=...
|
||||
|
||||
// VT1200_DUMP_VRAM=<frame> writes the video memory to vram-<frame>.bin so the
|
||||
// framebuffer layout can be determined offline.
|
||||
long m_dump_vram = -1;
|
||||
bool m_dumped_vram = false;
|
||||
std::vector<long> m_dump_vram_frames; // VT1200_DUMP_VRAM_FRAMES=...
|
||||
optional_shared_ptr<u16> m_vram;
|
||||
|
||||
// VT1200_LOG_UNMAPPED=<path>
|
||||
@@ -270,6 +284,30 @@ private:
|
||||
void log_duart(int win, char kind, u32 reg, u16 value);
|
||||
};
|
||||
|
||||
// Parse a comma-separated frame list ("3000,10000,30000,60000") into a vector,
|
||||
// ignoring empty items. Used by the multi-timestamp dump knobs.
|
||||
static std::vector<long> parse_frame_list(const char *s)
|
||||
{
|
||||
std::vector<long> out;
|
||||
if (!s)
|
||||
return out;
|
||||
std::string str(s);
|
||||
size_t pos = 0;
|
||||
while (pos <= str.size())
|
||||
{
|
||||
const size_t comma = str.find(',', pos);
|
||||
std::string item = str.substr(pos, (comma == std::string::npos)
|
||||
? std::string::npos : comma - pos);
|
||||
// trim surrounding whitespace
|
||||
const size_t b = item.find_first_not_of(" \t");
|
||||
const size_t e = item.find_last_not_of(" \t");
|
||||
if (b != std::string::npos)
|
||||
out.push_back(strtol(item.substr(b, e - b + 1).c_str(), nullptr, 0));
|
||||
pos = (comma == std::string::npos) ? str.size() + 1 : comma + 1;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void vt1200_state::machine_start()
|
||||
{
|
||||
if (const char *list = getenv("VT1200_BOARD_MAP"))
|
||||
@@ -314,6 +352,12 @@ void vt1200_state::machine_start()
|
||||
m_dump_ram = strtol(f, nullptr, 0);
|
||||
if (const char *f = getenv("VT1200_DUMP_VRAM"))
|
||||
m_dump_vram = strtol(f, nullptr, 0);
|
||||
if (const char *f = getenv("VT1200_DUMP_FRAMES"))
|
||||
m_dump_frames = parse_frame_list(f);
|
||||
if (const char *f = getenv("VT1200_DUMP_RAM_FRAMES"))
|
||||
m_dump_ram_frames = parse_frame_list(f);
|
||||
if (const char *f = getenv("VT1200_DUMP_VRAM_FRAMES"))
|
||||
m_dump_vram_frames = parse_frame_list(f);
|
||||
if (const char *d = getenv("VT1200_DUMP_DIR"))
|
||||
m_dump_dir = d;
|
||||
if (const char *f = getenv("VT1200_DPYLOG"))
|
||||
@@ -706,25 +750,39 @@ void vt1200_state::scanline(screen_device &screen, bitmap_ind16 &bitmap, int sca
|
||||
// sporadically on this driver.
|
||||
m_board.tick();
|
||||
|
||||
if (m_dump_frame >= 0 && (long)screen.frame_number() == m_dump_frame)
|
||||
dump_frame(screen, bitmap);
|
||||
if (m_dump_ram >= 0 && !m_dumped_ram && (long)screen.frame_number() >= m_dump_ram)
|
||||
const long f = (long)screen.frame_number();
|
||||
// PPM frame dumps. The single-frame knob fires at GE n (its historical
|
||||
// semantics); the list knob fires at EXACTLY each listed frame.
|
||||
if (m_dump_frame >= 0 && f == m_dump_frame)
|
||||
dump_frame(screen, bitmap, f);
|
||||
for (long fr : m_dump_frames)
|
||||
if (f == fr)
|
||||
dump_frame(screen, bitmap, fr);
|
||||
|
||||
if (m_dump_ram >= 0 && !m_dumped_ram && f >= m_dump_ram)
|
||||
{
|
||||
m_dumped_ram = true;
|
||||
dump_ram();
|
||||
dump_ram(f);
|
||||
}
|
||||
if (m_dump_vram >= 0 && !m_dumped_vram && (long)screen.frame_number() >= m_dump_vram)
|
||||
for (long fr : m_dump_ram_frames)
|
||||
if (f == fr)
|
||||
dump_ram(fr);
|
||||
|
||||
if (m_dump_vram >= 0 && !m_dumped_vram && f >= m_dump_vram)
|
||||
{
|
||||
m_dumped_vram = true;
|
||||
dump_vram();
|
||||
dump_vram(f);
|
||||
}
|
||||
for (long fr : m_dump_vram_frames)
|
||||
if (f == fr)
|
||||
dump_vram(fr);
|
||||
}
|
||||
}
|
||||
|
||||
void vt1200_state::dump_frame(screen_device &screen, bitmap_ind16 &bitmap)
|
||||
void vt1200_state::dump_frame(screen_device &screen, bitmap_ind16 &bitmap, long frame)
|
||||
{
|
||||
char path[1024];
|
||||
snprintf(path, sizeof(path), "%s/vt1200-frame-%ld.ppm", m_dump_dir.c_str(), m_dump_frame);
|
||||
snprintf(path, sizeof(path), "%s/vt1200-frame-%ld.ppm", m_dump_dir.c_str(), frame);
|
||||
FILE *fp = fopen(path, "wb");
|
||||
if (!fp)
|
||||
{
|
||||
@@ -747,7 +805,7 @@ void vt1200_state::dump_frame(screen_device &screen, bitmap_ind16 &bitmap)
|
||||
}
|
||||
fclose(fp);
|
||||
osd_printf_info("vt1200: dumped frame %ld -> %s (%dx%d, %lu lit = %.4f%%)\n",
|
||||
m_dump_frame, path, w, h, lit, 100.0 * lit / ((double)w * h));
|
||||
frame, path, w, h, lit, 100.0 * lit / ((double)w * h));
|
||||
}
|
||||
|
||||
uint32_t vt1200_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
|
||||
@@ -773,22 +831,23 @@ uint32_t vt1200_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap
|
||||
if (guards++ < 5)
|
||||
osd_printf_info("vt1200: guard#%d frame=%u dump_frame=%ld dump_ram=%ld\n",
|
||||
guards, (unsigned)screen.frame_number(), m_dump_frame, m_dump_ram);
|
||||
if (m_dump_frame >= 0 && (long)screen.frame_number() == m_dump_frame)
|
||||
dump_frame(screen, bitmap);
|
||||
if (m_dump_ram >= 0 && !m_dumped_ram && (long)screen.frame_number() >= m_dump_ram)
|
||||
const long f = (long)screen.frame_number();
|
||||
if (m_dump_frame >= 0 && f == m_dump_frame)
|
||||
dump_frame(screen, bitmap, f);
|
||||
if (m_dump_ram >= 0 && !m_dumped_ram && f >= m_dump_ram)
|
||||
{
|
||||
m_dumped_ram = true;
|
||||
dump_ram();
|
||||
dump_ram(f);
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void vt1200_state::dump_ram()
|
||||
void vt1200_state::dump_ram(long frame)
|
||||
{
|
||||
char path[1024];
|
||||
snprintf(path, sizeof(path), "%s/vt1200-ram-%ld.bin", m_dump_dir.c_str(), m_dump_ram);
|
||||
snprintf(path, sizeof(path), "%s/vt1200-ram-%ld.bin", m_dump_dir.c_str(), frame);
|
||||
FILE *fp = fopen(path, "wb");
|
||||
if (!fp)
|
||||
{
|
||||
@@ -802,13 +861,13 @@ void vt1200_state::dump_ram()
|
||||
fwrite(base, 1, n, fp);
|
||||
fclose(fp);
|
||||
osd_printf_info("vt1200: dumped %zu bytes of main RAM (frame %ld) -> %s\n",
|
||||
n, m_dump_ram, path);
|
||||
n, frame, path);
|
||||
}
|
||||
|
||||
void vt1200_state::dump_vram()
|
||||
void vt1200_state::dump_vram(long frame)
|
||||
{
|
||||
char path[1024];
|
||||
snprintf(path, sizeof(path), "%s/vram-%ld.bin", m_dump_dir.c_str(), m_dump_vram);
|
||||
snprintf(path, sizeof(path), "%s/vram-%ld.bin", m_dump_dir.c_str(), frame);
|
||||
FILE *fp = fopen(path, "wb");
|
||||
if (!fp)
|
||||
{
|
||||
@@ -826,7 +885,7 @@ void vt1200_state::dump_vram()
|
||||
fwrite(base, 1, n, fp);
|
||||
fclose(fp);
|
||||
osd_printf_info("vt1200: dumped %zu bytes of video RAM (frame %ld) -> %s\n",
|
||||
n, m_dump_vram, path);
|
||||
n, frame, path);
|
||||
}
|
||||
|
||||
void vt1200_state::vt1200(machine_config &config)
|
||||
|
||||
Reference in new issue
Block a user