diff --git a/harness/mame/BRINGUP_LOG.md b/harness/mame/BRINGUP_LOG.md index d525a1c..32e1bbd 100644 --- a/harness/mame/BRINGUP_LOG.md +++ b/harness/mame/BRINGUP_LOG.md @@ -17,6 +17,9 @@ handshake the firmware waits on and modelling it correctly. | `VT1200_BOARD_DEFAULT=` | value for addresses with no override | | `VT1200_BOARD_MODEL=handshake` | write-back registers **plus a self-clearing busy bit** | | `VT1200_BOARD_BUSY_TICKS=` | busy-bit tick count for `VT1200_BOARD_MODEL=handshake` (the busy bit is fixed to bit 0 of the `+0x10` STATUS register) | +| `VT1200_BOARD_BUSY_W2=` | busy ticks for window `0x06200000` only; applied after `BUSY_TICKS`, so it overrides it for that window | +| `VT1200_BOARD_BUSY_W3=` | busy ticks for window `0x06400000` only; likewise | +| `VT1200_BOARD_DMA_TICKS=` | completion time (ticks) of a command written to the `0x05900000` DMA command port; 0 leaves the pair as a plain latch | | `VT1200_BOARD_SWITCH=::` | monitor switch: bits 4-6 of `0x0640000D` read as `` (raw bits, or a position 0-7) | | `VT1200_BOARD_RAM=1` | windows behave as plain read/write RAM | | `VT1200_NO_RENDER=1` | restore the old blank-everything video behaviour | @@ -619,3 +622,123 @@ ignored instead of silently no-oping. No regression: with no env vars the wedge is still `PC=3FDCE400 samples=199`, and the handshake config (`MODEL=handshake BUSY_TICKS=2 SWITCH=0640000D:70:70`) still advances to 56 distinct PCs by frame 600. + +## Register model (2026-10-16) + +### The model is now configurable, per window + +`VT1200_BOARD_BUSY_W2`/`_W3` set the busy count for `0x06200000`/`0x06400000` +individually, and `VT1200_BOARD_DMA_TICKS` sets the completion time of the +`0x05800000`/`0x05900000` DMA pair. All three are **modifiers** of +`VT1200_BOARD_MODEL=handshake`, applied after `VT1200_BOARD_BUSY_TICKS` so a +per-window value wins for its window; like `BUSY_TICKS`/`SWITCH` they need one +of the on-switches (`MODEL`/`LOG`/`MAP`/`DEFAULT`) to map the windows at all. + +The three knobs provably reach the model — the `-log` line reports the parsed +values (`busy_w2=3 busy_w3=4 dma_ticks=5`), and a differential run shows they +change behaviour: `BUSY_TICKS=0` alone → stuck at `PC=3FDCE400`, 5 distinct; +`BUSY_TICKS=0 BUSY_W2=1` → 56 distinct at `PC=3F208950`; `BUSY_TICKS=0 +BUSY_W3=1` → still `PC=3FDCE400`, 5 distinct. So the knobs are wired *and* the +critical handshake is isolated to the `0x06200000` window: arming only WIN2 +leaves gate 1, arming only WIN3 does not. + +### Sweep: `BUSY_W2` in {1,2,4,8} + +`W3=2`, `DMA=2`, `MODEL=handshake`, `SWITCH=0640000D:70:70`, +`coverage.lua` at 900 frames / 300-frame window: + +| `BUSY_W2` | `total_distinct` @ f900 | frame-900 `PC` | +|---|---|---| +| 1 | 59 | `3F208B20` | +| 2 | 59 | `3F208B20` | +| 4 | 59 | `3F208B20` | +| 8 | 59 | `3F208B20` | + +**The sweep is flat.** No `BUSY_W2` value moves the firmware further than any +other, and none leaves the park loop. The reason is structural, not a bug: +`tick()` runs **once per frame** (last visible scanline), while the gate polls +are tight in-frame `DSJS`/branch spins. Any `n >= 1` arms the bit for the whole +frame and clears it at the next frame boundary, so `1`, `2`, `4` and `8` are +indistinguishable; only `0` differs (it never arms, and the firmware stays at +the gate-1 poll). **For this firmware the busy *presence* matters; the busy +*duration* is not a tunable.** + +The other two knobs are also inert at this stall, which rules them out as the +missing input: + +| config | `total_distinct` @ f900 | frame-900 `PC` | +|---|---|---| +| boundary (`W2=2 W3=2 DMA=2`) | 59 | `3F208B20` | +| `DMA=0` | 59 | `3F208B20` | +| `DMA=64` | 59 | `3F208B20` | +| `W3=0` | 59 | `3F208B20` | + +### Where the firmware actually sits + +`progress.lua` at 900 frames on the boundary config (59 distinct PCs) shows the +loop: `PC=3F2088B0` (414 samples, from frame 91), then `3F208A80` (138, from +f671), `3F208950` (138, f505), `3F208B20` (78, f809), `3F2089F0` (28, f574), +`3F208BC0` (14, f878). That is bit `0x3F2088B0`–`0x3F208BC0` = **file +`0x1116`–`0x1178`**, in the *reset/POST bank*, and it is not the display/blit +loop. The access log from the same config shows exactly what that loop does: + +``` +W 0640000D pc=3F208830 val=0000 +W 0640000F pc=3F208880 val=00FF +R 0640000E pc=3F208920 val=0001 +R 0640000F pc=3F2089C0 val=00FF +``` + +i.e. it pokes `0x0640000D`, pokes `0x0640000F`, then reads `0x0640000E`/`F`, and +spins — the POST video/refresh delay routine already identified in gate 4. New +PCs are still appearing as late as frame 878, so it is *cycling*, not frozen, +but over a tiny footprint and long past the point the real machine is in +level 1. + +### `INTENB` — still never non-zero + +`intr.lua`, boundary config, 900 frames: + +``` +f 1 INTENB CHANGED -> 0000 (DI=0) +f 2 INTPEND CHANGED -> 0400 +f 300 DPYCTL=E008 INTENB=0000(DI=0) INTPEND=0400 DI=1 +f 900 DPYCTL=E008 INTENB=0000(DI=0) INTPEND=0400 DI=1 +distinct INTENB values seen: 0000 (900 frames) +``` + +`INTENB` never becomes non-zero, so the level-0 boundary is **not** crossed: +the firmware has still not reached interrupt setup. `INTPEND=0400` (display IRQ +pending) throughout confirms MAME is generating the interrupt and the firmware +simply has not enabled it. **No busy window and no DMA timing changes this** — +the model is still missing an input. + +### Which window/offset still looks unmodelled + +1. **`0x5600000` (byte `0xAC0000`) — not mapped at all.** An independent sweep + of the address space (`reference/memory-space-map.md`, row 4) found this + block touched ~**110 times** with offsets `+0x00 +0x10 +0x20 +0x30 +0x80 + +0x100 +0x200 +0x380`, on the **display / X-server init** path (writer near + the `no screens found` strings). It is the most-touched region the driver + does not model, and it is a prime suspect. Caveat: its writer is on the + X-server init path, which this firmware has **not reached** (it is still in + level 0 POST), so it is a plausible *future* blocker as much as a current + one. Modelling it is explicitly out of this task's scope. +2. **`0x0640000E` / `0x0640000F` — read in the park loop, modelled only as + write-back latches.** The loop reads them expecting some hardware state; the + model returns the last value written. That is the narrowest "missing input" + at the current park point. +3. **`0x0640000D` bit 2** (gate 4's "graphics/blit complete") is still not + modelled — only bits 4-6 (the monitor switch) are. +4. Rows 7 and 10 of the memory-space map — candidate NVRAM at `0x6000000` and + the Ethernet MAC ROM at `0x6800000` — are unmapped; level 0's LANCE test and + level 1's NVRAM test sit behind them. + +### Verdict + +The configurable model is in place and the knobs are wired and effective, but +the sweep is honestly **flat**: the busy *duration*, the W3 busy count and the +DMA completion time do not move the firmware. The model is still missing an +input, and the strongest candidates are the entirely-unmapped `0x5600000` block +and the `0x0640000E`/`F` read semantics. No claimed improvement is made this +round. diff --git a/harness/mame/test_vt1200_board.cpp b/harness/mame/test_vt1200_board.cpp index 225603b..a1be8e6 100644 --- a/harness/mame/test_vt1200_board.cpp +++ b/harness/mame/test_vt1200_board.cpp @@ -100,6 +100,28 @@ static void test_dma_pair_command_completes(void) CHECK(b.read(VT1200_WIN1) == 0x0000); // completed } +// The two timers the sweep tunes (window busy and DMA completion) must advance +// on the same tick(), or a later slower timer would silently stretch the other. +static void test_one_tick_advances_window_and_dma_timers(void) +{ + vt1200_board b; + b.set_model_handshake(true); + b.set_window_busy_ticks(VT1200_WIN2, 2); + b.set_dma_ticks(2); + + b.write(VT1200_WIN2 + VT1200_OFF_CMD, 0x0001); // arms WIN2's busy bit + b.write(VT1200_WIN0, 0x0000); // data port first + b.write(VT1200_WIN1, 0x0042); // then a DMA command + + b.tick(); // one frame: both advance + CHECK((b.read(VT1200_WIN2 + VT1200_OFF_STATUS) & 1) == 1); + CHECK(b.read(VT1200_WIN1) == 0x0042); + + b.tick(); + CHECK((b.read(VT1200_WIN2 + VT1200_OFF_STATUS) & 1) == 0); + CHECK(b.read(VT1200_WIN1) == 0x0000); +} + int main(void) { test_default_read(); @@ -110,6 +132,7 @@ int main(void) test_monitor_switch_accepts_raw_bits_or_position(); test_switch_mix_only_when_configured(); test_dma_pair_command_completes(); + test_one_tick_advances_window_and_dma_timers(); std::printf(failures ? "%d FAILURE(S)\n" : "all board tests passed\n", failures); return failures ? 1 : 0; } diff --git a/harness/mame/vt1200.cpp b/harness/mame/vt1200.cpp index a037c13..5d56623 100644 --- a/harness/mame/vt1200.cpp +++ b/harness/mame/vt1200.cpp @@ -43,6 +43,18 @@ VT1200_BOARD_RAM=1 map the windows as plain read/write RAM instead (writes persist and read back). + The remaining board knobs refine VT1200_BOARD_MODEL=handshake. They are + modifiers, not on-switches: one of the knobs above must also be set, or + the windows stay unmapped and they do nothing. + + VT1200_BOARD_BUSY_TICKS= busy ticks for every window (default 0). + VT1200_BOARD_BUSY_W2= busy ticks for 0x06200000 only; applied + after BUSY_TICKS, so it overrides it. + VT1200_BOARD_BUSY_W3= busy ticks for 0x06400000 only; likewise. + VT1200_BOARD_DMA_TICKS= completion time of a command written to + the 0x05900000 DMA command port; 0 (the + default) leaves the pair as a plain latch. + See FULL_BOOT_ESTIMATE.md section 4b for measured results. ***************************************************************************/ @@ -178,6 +190,16 @@ void vt1200_state::machine_start() if (const char *t = getenv("VT1200_BOARD_BUSY_TICKS")) m_board.set_busy_ticks((int)strtol(t, nullptr, 0)); + // Per-window refinements of the busy count: W2/W3 target the two big + // windows individually. Applied after BUSY_TICKS so they win for their + // window, and the DMA pair's completion time is its own knob. + if (const char *t = getenv("VT1200_BOARD_BUSY_W2")) + m_board.set_window_busy_ticks(VT1200_WIN2, (int)strtol(t, nullptr, 0)); + if (const char *t = getenv("VT1200_BOARD_BUSY_W3")) + m_board.set_window_busy_ticks(VT1200_WIN3, (int)strtol(t, nullptr, 0)); + if (const char *t = getenv("VT1200_BOARD_DMA_TICKS")) + m_board.set_dma_ticks((int)strtol(t, nullptr, 0)); + // The monitor-selection switch lives in bits 4-6 of 0x64000D0. The model // mixes those input bits with the port's write-back output latches; the // value may be the raw register bits (e.g. 70) or a position 0-7. Any @@ -206,9 +228,12 @@ void vt1200_state::machine_start() if (const char *d = getenv("VT1200_DUMP_DIR")) m_dump_dir = d; - logerror("vt1200: board model=%s busy_ticks=%s switch=%s log=%s render=%d dump=%ld\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", + getenv("VT1200_BOARD_BUSY_W2") ? getenv("VT1200_BOARD_BUSY_W2") : "-", + getenv("VT1200_BOARD_BUSY_W3") ? getenv("VT1200_BOARD_BUSY_W3") : "-", + getenv("VT1200_BOARD_DMA_TICKS") ? getenv("VT1200_BOARD_DMA_TICKS") : "0", getenv("VT1200_BOARD_SWITCH") ? getenv("VT1200_BOARD_SWITCH") : "off", m_log ? "on" : "off", m_render ? 1 : 0, m_dump_frame); }