diff --git a/harness/mame/OS_BRINGUP_REPORT.md b/harness/mame/OS_BRINGUP_REPORT.md new file mode 100644 index 0000000..dc7fdd1 --- /dev/null +++ b/harness/mame/OS_BRINGUP_REPORT.md @@ -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 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" 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. diff --git a/harness/mame/resolve_console.py b/harness/mame/resolve_console.py new file mode 100644 index 0000000..6863bd2 --- /dev/null +++ b/harness/mame/resolve_console.py @@ -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 [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} ") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/harness/mame/vt1200.cpp b/harness/mame/vt1200.cpp index 1dfa222..0532ab6 100644 --- a/harness/mame/vt1200.cpp +++ b/harness/mame/vt1200.cpp @@ -63,6 +63,11 @@ vt1200-ram-.bin VT1200_DUMP_VRAM= write video memory at frame as vram-.bin + VT1200_DUMP_FRAMES= 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= directory for the dumps (default ".") VT1200_NO_RENDER=1 blank the screen instead of painting it @@ -111,6 +116,7 @@ #include #include #include +#include #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= 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 m_dump_frames; // VT1200_DUMP_FRAMES=3000,10000,... std::string m_dump_dir = "."; // VT1200_DPYLOG=: dump the TMS34010 display params for the first @@ -239,11 +251,13 @@ private: // vt1200-ram-.bin so the framebuffer can be located offline. long m_dump_ram = -1; bool m_dumped_ram = false; + std::vector m_dump_ram_frames; // VT1200_DUMP_RAM_FRAMES=... // VT1200_DUMP_VRAM= writes the video memory to vram-.bin so the // framebuffer layout can be determined offline. long m_dump_vram = -1; bool m_dumped_vram = false; + std::vector m_dump_vram_frames; // VT1200_DUMP_VRAM_FRAMES=... optional_shared_ptr m_vram; // VT1200_LOG_UNMAPPED= @@ -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 parse_frame_list(const char *s) +{ + std::vector 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)