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:
SDD committed 2026-10-16 13:39:37 +03:00
1 parent 855f3e85e9
commit c06cf7096b
3 files changed
+439 -22

No files matched your search

+307
View File
@@ -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.
+51
View File
@@ -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
View File
@@ -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)