harness: configurable board model busy timings + findings

This commit is contained in:
SDD committed 2026-10-16 01:59:57 +03:00
1 parent a2f19b2d29
commit 2d7747a20d
3 files changed
+172 -1

No files matched your search

+123
View File
@@ -17,6 +17,9 @@ handshake the firmware waits on and modelling it correctly.
| `VT1200_BOARD_DEFAULT=<v>` | value for addresses with no override |
| `VT1200_BOARD_MODEL=handshake` | write-back registers **plus a self-clearing busy bit** |
| `VT1200_BOARD_BUSY_TICKS=<n>` | 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=<n>` | busy ticks for window `0x06200000` only; applied after `BUSY_TICKS`, so it overrides it for that window |
| `VT1200_BOARD_BUSY_W3=<n>` | busy ticks for window `0x06400000` only; likewise |
| `VT1200_BOARD_DMA_TICKS=<n>` | completion time (ticks) of a command written to the `0x05900000` DMA command port; 0 leaves the pair as a plain latch |
| `VT1200_BOARD_SWITCH=<addr>:<mask>:<value>` | monitor switch: bits 4-6 of `0x0640000D` read as `<value>` (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.
+23
View File
@@ -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;
}
+26 -1
View File
@@ -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=<n> busy ticks for every window (default 0).
VT1200_BOARD_BUSY_W2=<n> busy ticks for 0x06200000 only; applied
after BUSY_TICKS, so it overrides it.
VT1200_BOARD_BUSY_W3=<n> busy ticks for 0x06400000 only; likewise.
VT1200_BOARD_DMA_TICKS=<n> 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);
}