From 1ca48177459777cd4a88521d2c4f5604dae91052 Mon Sep 17 00:00:00 2001 From: SDD Date: Fri, 16 Oct 2026 13:59:09 +0300 Subject: [PATCH] harness: model DUART2 by default (drop the VT1200_DUART2 gate) The real VT1200 board has a DUART2 (SCN2681) at 0x0620000 for the keyboard (ch A) and mouse (ch B) with INT2 wired. Map it unconditionally instead of opting in via VT1200_DUART2; the OS's DUART interrupt dispatcher needs a real read-only ISR distinct from the write-only IMR. Verified with frame dumps: with ONLY VT1200_SKIP_LEVEL1=1 the Terminal Manager V2.2 dialog now appears (frame 3000; VRAM md5 73c3c9a1..., identical to the old skip+DUART2 run). This does NOT keep the faithful no-env path: level 0's I/O data path test (L3FDCE470) transmits 0xE1 to DUART2-A and drains the receiver until it has read 10 bytes (SRA RxRDY). The board write-back latch used to alias CSRA/RHRA and faked that (baseline first divergence: R 06200001 pc=3FDCE670 = 0x0099 vs the real chip's 0x000C), so the firmware dropped into the interactive diagnostics menu. No faithful device we have (LK401 sends at most 4 bytes) supplies the 10 bytes; an external TX->RX loopback does not change the transmit count. See OS_BRINGUP_REPORT.md section 8 (DONE_WITH_CONCERNS). The OS keyboard/mouse driver does initialise: skip-phase OS code programs DUART2 (MR1A=0x13, CSRA=0x99, IMR=0x33, ...) and sends FD to the keyboard plus the documented LED/click/bell/auto-repeat commands, and 'T' to the mouse. --- harness/mame/OS_BRINGUP_REPORT.md | 151 +++++++++++++++++++ harness/mame/duart2_default_tm_frame3000.png | Bin 0 -> 4507 bytes harness/mame/vt1200.cpp | 37 +++-- 3 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 harness/mame/duart2_default_tm_frame3000.png diff --git a/harness/mame/OS_BRINGUP_REPORT.md b/harness/mame/OS_BRINGUP_REPORT.md index dc7fdd1..d5e3ffa 100644 --- a/harness/mame/OS_BRINGUP_REPORT.md +++ b/harness/mame/OS_BRINGUP_REPORT.md @@ -14,6 +14,13 @@ the write-only interrupt-mask register (IMR) — same chip register address, GUI's Terminal Manager **dialog box** — the reference video's ~0:48 target — appears. +> **2026-10-16 update (§8): DUART2 is now modelled by default.** The +> `VT1200_DUART2` gate is gone; a bare `VT1200_SKIP_LEVEL1=1` run now reaches +> the Terminal Manager (§8.1). This *does* break the faithful no-env path: +> level 0's I/O data path test needs received data on DUART2-A that the old +> board write-back latch faked (§8.2). Read §8 before relying on the +> "no-env → HOST PORT - 15" baseline elsewhere in this file. + --- ## 1. Progressing or stuck — evidence across time @@ -305,3 +312,147 @@ documented combination `VT1200_SKIP_LEVEL1=1 VT1200_DUART2=1`. auto-exposure, not content. * NVRAM contents/initialisation policy — only that the OS reads it and falls back to system defaults. + +--- + +# 8. DUART2 promoted to a default — what it fixes and what it breaks + +Date: 2026-10-16 (later). Change: `harness/mame/vt1200.cpp` maps MAME's +`SCN2681` at `0x0620000` (keyboard ch A / mouse ch B) **unconditionally**; the +`VT1200_DUART2` gate is deleted. ROM untouched (`md5 +b46a3e442ccb86134d2356bd70d22fd2`). + +## 8.1 The OS now reaches the GUI with only `VT1200_SKIP_LEVEL1=1` + +One run, no `VT1200_DUART2` anywhere: + +```sh +env VT1200_SKIP_LEVEL1=1 VT1200_DUMP_FRAMES=3000,10000 \ + VT1200_DUMP_VRAM_FRAMES=3000,10000 VT1200_DUMP_DIR= vt1200 ... +``` + +| frame | lit | video-RAM md5 | +|---|---|---| +| 3000 | 461172 (39.9879%) | `73c3c9a1f13378d3ccf1e092aa47fa60` | +| 10000 | 460143 (39.8986%) | `4ff38c8c782684c562369dc47fb8a781` | + +Video-RAM md5s identical to the report's `VT1200_SKIP_LEVEL1=1 VT1200_DUART2=1` +run — a bare skip run now *is* that run. **The dumped frame (I rendered +`vt1200-frame-3000.ppm` to `duart2_default_tm_frame3000.png`) shows the +Terminal Manager V2.2 box** — title bar "Terminal Manager V2.2: Copyright © +1991 Digital Equipment Corporation", Session/Create/Customize/Print Screen, +Messages ("Using local fonts. / User settings invalid. / System defaults +loaded."), Ethernet Address `00-00-00-00-00-00`, LAT Host Name +`LAT_00000000000`, the Memory Remaining bar (3104), Secure Reset Count: 0, the +mouse cursor, and the bottom banner "All Diagnostics Passed". This matches +`reference/vt1200-boot-video/real_t52.png`. Frame 10000 differs, so the machine +is still running (not frozen). + +## 8.2 Level 0: the earlier claim is **REAL** — and the exact test is named + +The claim "a real DUART2 diverts level 0 to the interactive diagnostics menu" +**holds**. The inference that it "looks wrong" came from the *skip* run, but +`VT1200_SKIP_LEVEL1` skips level 0 entirely, so that run says nothing about it. + +* **Console, no env at all now** (`cap.lua`): the transcript is no longer + `LEVEL1/2 TESTING` / `LEVEL1/2 ERROR -> HOST PORT - 15`; it is the + **interactive diagnostics menu** — `VERSION 2.08 - SELECTED TEST TYPE:` and + the A–Z test list (`D - KEYBOARD TEST`, `H - DUART2, CHANNEL A (KEYBOARD) + TEST`, …). So the faithful path is **broken** by this change. +* **Exact test.** Level-0's **I/O data path test**, routine `L3FDCE470`, called + from `3F3DFA80` with window base `0x0620000` (DUART2 ch A). It runs at + `cyc≈1.494 M`, *before* the baseline's `LEVEL1/2 TESTING` print at + `cyc=2.119 M` (confirmed with a cycle-stamped printer breakpoint), i.e. it is + a level-0 test, not level 1. +* **What it waits for.** It initialises the channel (`MR1A=0x13, MR2A=0x07, + CSRA=0x99, CRA enable`, *normal* mode — **not** local loopback), transmits + `0xE1` to THRA (`pc=3FDCE8D0`), and then drains the receiver: loop + `3FDCE6C0`/`3FDCE930` reads RHRA, delays 100, and repeats **while SRA bit 0 + (RXRDY) is set**, up to **10 bytes**; the pass branch is taken at + `3FDCEAE0` only when the count is exactly 10, otherwise it falls through to + the menu display. With a real chip and nothing on the wire, RXRDY never + sets (`SRA=0x000C` = TXRDY|TXEMT, bit 0 clear) and the test fails. +* **Why the baseline passed.** The first differing access is + `R 06200001 pc=3FDCE670`: baseline (board model) returned `0x0099` — the + value last written to CSRA, whose bit 0 is set — so the driver drained 10 + copies of the latched RHRA `0x008C` and hit count 10. A real chip returns + `0x000C`. The write-back latch was an **accidental loopback** that faked the + test; modelling the real device removes the fake and exposes the gap. +* **The LK401 model would not fix it.** The firmware transmits only three bytes + before this test (`0x11 0x8C` = LEDs off, then `0xE1` = disable auto-repeat); + none of these has a documented LK401 reply, and the keyboard's power-up / + `FD` self-test report is **4 bytes** (`reference/keyboard-mouse/PROTOCOL.md` + §3.5; `vt100net-lk401.txt` §3.3.4.6). The test drains until it has read + **10**. I also wired an external DUART2-A TX→RX loopback + (`a_tx_cb`→`rx_a_w`, the old `VT1200_DUART_LB2`) as a diagnostic: the + firmware's transmit count is still 3 and the menu still appears. So no + faithful device/protocol model we have supplies the 10 bytes; restoring the + no-env path would require re-introducing a read-back fake, which is exactly + what this change removes. + +## 8.3 Does the OS keyboard/mouse driver initialise? — **Yes** + +`VT1200_SKIP_LEVEL1=1` with `VT1200_DUARTLOG`, first 40 s: OS code (`pc` in the +OS range, e.g. `3F6F3990`…`3F6F3CA0`, then `3FB43550`) drives `0x0620xxx`: + +``` +W 0620000D=0000 OP, W 0620000E=000C, W 0620000F=0004 ; output port setup +W 06200002=002A/003A/005A/0010 (CRA) , W 0620000A … ; channel resets +W 06200000=0013 MR1A , W 06200008=0007 MR1B +W 06200004=0060 ACR +W 06200001=0099 CSRA , W 06200009=0099 CSRB +W 06200000=0007 MR2A , W 06200008=0007 MR2B +W 06200005=0033 IMR (both channels Rx/Tx) +W 06200002=0009 CRA , W 0620000A=0009 CRB ; enable Tx+Rx+A +… then the driver's command traffic (pc=3FB43550): +W 06200003=00FD FD -> keyboard: jump to power-up / self-test +W 06200003=0013 0080, 001B 0086, 0023 0084, 00E3 ; LED / click / bell / auto-repeat +W 0620000B=0054 'T' -> mouse: self-test & identify +``` + +Every one of those keyboard bytes is a documented LK401 command and `'T'` is +the documented VSXXX mouse self-test (`PROTOCOL.md` §3.3, §4.2) — so the +driver initialises **and speaks the real protocol**. **Input still does not +work** (no keyboard/mouse is attached, and nothing is faked); the chip receives +no reply, which is the honest state. + +## 8.4 Baselines + +* **no env** → **CHANGED**: no longer POST + level 1 to `HOST PORT - 15`; now + level-0 I/O data path fails and the machine sits in the diagnostics menu + (§8.2). `progress.lua` 200 frames: **3 distinct PCs**, hot + `PC=3FDCE400 samples=198` (the menu's key-wait delay loop). This replaces the + `PC=3FDCE400 samples=53 / 78 distinct PCs` baseline recorded in + `GATE_CLEANUP_REPORT.md`; the baseline is a change detector, and this change + is deliberate. +* **`VT1200_SKIP_LEVEL1=1`** → **intact and extended**: the X11 weave still + paints first and the run now progresses through it to the Terminal Manager + (§8.1). Frame/VRAM md5s unchanged from the old skip+DUART2 run. +* `progress.lua` unchanged. + +## 8.5 Reproduce + +```sh +M=/home/arti/code/k-space/mame0289/vt1200 +R="-rompath harness/mame/roms -sound none -skip_gameinfo -video none -nothrottle" + +# GUI with ONLY the skip gate ( must exist): +env VT1200_SKIP_LEVEL1=1 VT1200_DUMP_FRAMES=3000 \ + VT1200_DUMP_DIR= $M vt1200 $R -seconds_to_run 60 + +# Level-0 divert, with no env at all: +env CAP_MAX=2500 $M vt1200 -rompath harness/mame/roms -sound none \ + -skip_gameinfo -video none -nothrottle -debug -debugger none -debuglog \ + -autoboot_script harness/mame/cap.lua -autoboot_delay 0 -seconds_to_run 60 +python3 harness/mame/resolve_console.py debug.log harness/mame/roms/vt1200/vt1200_v22.bin + +# OS keyboard/mouse driver init: +env VT1200_SKIP_LEVEL1=1 VT1200_DUARTLOG=harness/mame/scratch/duart_skip.log \ + $M vt1200 $R -seconds_to_run 40 +``` + +**Bottom line.** The faithfulness fix works and the GUI is now reachable with +one env var — but it invalidates the "no env reaches `HOST PORT - 15`" +constraint, for a real, named reason (level-0 I/O data path test needs 10 +received bytes on DUART2-A). The previous no-env baseline only reached level 1 +because the board latch faked that test. I did **not** fake it back. diff --git a/harness/mame/duart2_default_tm_frame3000.png b/harness/mame/duart2_default_tm_frame3000.png new file mode 100644 index 0000000000000000000000000000000000000000..7b568bd4a425981883fe9acdc7141e26b5a0d5b8 GIT binary patch literal 4507 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 literal 0 HcmV?d00001 diff --git a/harness/mame/vt1200.cpp b/harness/mame/vt1200.cpp index 0532ab6..97a3c96 100644 --- a/harness/mame/vt1200.cpp +++ b/harness/mame/vt1200.cpp @@ -32,8 +32,16 @@ the reset-vector page at logical bit 0xffffffe0 resolves to the last four bytes of the image (file offset 0x1bfffc). - What is NOT modelled: LANCE Ethernet, DUART2 (0x0620000 = keyboard/mouse; - opt in with VT1200_DUART2=1), and the board system logic beyond the + * MAME's SCN2681 at DUART2 (0x0620000 = keyboard (ch A) / mouse (ch B)), + wired to INT2. This is modelled unconditionally because the real + board has it and the OS's DUART interrupt dispatcher needs a real + chip. NOTE: with a real DUART2 and nothing attached to the keyboard + port, the *faithful* (no-env) boot now fails the level-0 I/O data path + test and drops into the interactive diagnostics menu; before DUART2 + was modelled the board write-back latch accidentally faked that test + (see OS_BRINGUP_REPORT.md). + + What is NOT modelled: LANCE Ethernet and the board system logic beyond the register model. The firmware therefore still stops at the level-1 HOST PORT test and wedges in the system-board LED loop; that is expected. @@ -96,11 +104,8 @@ Video: the driver paints the active scanlines itself, reading the framebuffer out of video RAM (see render_active_line). - VT1200_DUART2=1 additionally maps MAME's SCN2681 at 0x0620000 - (keyboard/mouse). It is off by default because mapping both DUARTs - diverts the level-0 keyboard path. - - See FULL_BOOT_ESTIMATE.md section 4b for measured results. + See FULL_BOOT_ESTIMATE.md section 4b for measured results and + OS_BRINGUP_REPORT.md for the DUART2 / level-0 analysis. ***************************************************************************/ @@ -186,8 +191,8 @@ public: u16 board3_r(offs_t off); void board3_w(offs_t off, u16 data); // The two DUARTs. DUART1 = host (ch A) + printer (ch B) at window - // 0x0640000 and is always mapped; DUART2 = keyboard (ch A) + mouse (ch B) - // at window 0x0620000 is opt-in via VT1200_DUART2. + // 0x0640000; DUART2 = keyboard (ch A) + mouse (ch B) at window 0x0620000. + // Both are mapped unconditionally. u16 duart1_r(offs_t off); void duart1_w(offs_t off, u16 data); u16 duart2_r(offs_t off); void duart2_w(offs_t off, u16 data); @@ -417,8 +422,7 @@ void vt1200_state::machine_start() logerror("vt1200: board model=handshake busy_ticks=2 switch=duart-ip4/5/6 log=%s render=%d dump=%ld\n", m_log ? "on" : "off", m_render ? 1 : 0, m_dump_frame); - logerror("vt1200: duart1=on (SCN2681 @0640000 host/printer), duart2=%s skip_level1=%s\n", - getenv("VT1200_DUART2") ? "on (kbd/mouse)" : "off", + logerror("vt1200: duart1=on (SCN2681 @0640000 host/printer), duart2=on (SCN2681 @0620000 kbd/mouse), skip_level1=%s\n", getenv("VT1200_SKIP_LEVEL1") ? "on (IP3 high, IRQ->INT2)" : "off (IP3 low, IRQ->INT1)"); } @@ -505,11 +509,14 @@ void vt1200_state::program_map(address_map &map) // board-window model so it wins the register slice (map precedence is // last-wins); the model still serves any other register in the same window. // Base in map units is the bit address; 16 registers at 2 bytes each = 32 - // bytes = 0x100 bit units. DUART2 (0x0620000 = keyboard/mouse) is opt-in - // via VT1200_DUART2: mapping both diverts the level-0 keyboard path. + // bytes = 0x100 bit units. DUART2 (0x0620000 = keyboard/mouse) is mapped + // the same way, unconditionally. map(VT1200_WIN3, VT1200_WIN3 + 0xff).rw(FUNC(vt1200_state::duart1_r), FUNC(vt1200_state::duart1_w)); - if (getenv("VT1200_DUART2")) - map(VT1200_WIN2, VT1200_WIN2 + 0xff).rw(FUNC(vt1200_state::duart2_r), FUNC(vt1200_state::duart2_w)); + // DUART2 (0x0620000 = keyboard/mouse) is mapped unconditionally: the real + // board has it, and the OS's DUART dispatcher requires a real read-only ISR + // distinct from the write-only IMR (the board write-back latch aliased them, + // producing a phantom interrupt that wedged the OS at the X11 weave). + map(VT1200_WIN2, VT1200_WIN2 + 0xff).rw(FUNC(vt1200_state::duart2_r), FUNC(vt1200_state::duart2_w)); } // MAME may hand us either an entry-relative offset (0..0xffff) or the absolute // address; BOARD_BASE is 0x10000-aligned, so ORing the low 16 bits is correct