From 2fd9286c1a5021860fa9cf4c61ed5f430d4058d8 Mon Sep 17 00:00:00 2001 From: SDD Date: Fri, 16 Oct 2026 11:22:56 +0300 Subject: [PATCH] harness: wire MAME SCN2681 at the DUART windows (host-port investigation) Adds SCN2681 devices at 0x0640000 (DUART1 = host/printer) and 0x0620000 (DUART2 = keyboard/mouse), gated on VT1200_DUART, with the remap verified as duart_register = handler_off (duart_byte = off*2, value in the low byte of the firmware's 16-bit move). Optional external TX->RX loopback (VT1200_DUART_LB1/2) models the loopback connectors; the monitor switch is driven on IP4-IP6. New VT1200_DUARTLOG gives a full non-deduplicated DUART access log. Result: the level-1 HOST PORT test is reached and executed against the real DUART, but still reports 'LEVEL1/2 ERROR -> HOST PORT - 15'. The test reads SRA/RHRA and requires a 5-byte external pattern (0xAA,0xFF,0xAA,0x55) plus SRA overrun, yet the firmware never writes the transmit register (0 writes to reg 3/B on either DUART in 1400 frames), so no internal or external loopback can supply it. See harness/mame/DUART_REPORT.md. Regression holds: no VT1200_* env -> PC=3FDCE400 samples=199. --- harness/mame/DUART_REPORT.md | 278 +++++++++++++++++++++++++++++++++++ harness/mame/duart_probe.lua | 24 +++ harness/mame/vt1200.cpp | 181 ++++++++++++++++++++++- 3 files changed, 482 insertions(+), 1 deletion(-) create mode 100644 harness/mame/DUART_REPORT.md create mode 100644 harness/mame/duart_probe.lua diff --git a/harness/mame/DUART_REPORT.md b/harness/mame/DUART_REPORT.md new file mode 100644 index 0000000..76582cf --- /dev/null +++ b/harness/mame/DUART_REPORT.md @@ -0,0 +1,278 @@ +# DUART — wiring MAME's SCN2681 at the host-port window + +Bounded task, 2026-10-16. Goal: make the firmware's level-1 **HOST PORT** test pass +(DUART1 channel A) by modelling the DUART with MAME's `SCN2681`, so that +`'LEVEL1/2 ERROR -> HOST PORT - 15 '` stops appearing in the console capture. + +Driver: `harness/mame/vt1200.cpp`. MAME: `/home/arti/code/k-space/mame0289`. +ROM: `harness/mame/roms/vt1200/vt1200_v22.bin`. +Acceptance instrument: `harness/mame/cap.lua`. + +--- + +## 0. Answer in one paragraph + +MAME's `SCN2681` **is** the right part and *is* wireable: the two windows are +byte-register DUARTs with `duart_register = handler_off` (verified below), and +they are now mapped to an `SCN2681` with TX wired back to RX. **The acceptance line +still appears** — but not because of the DUART model. The level-1 host-port test +reads DUART1 status/data (`SRA` at off `0x01`, `RHRA` at off `0x03`) and requires +the receiver to hold a **5-byte external pattern** (`0xAA,0xFF,0xAA,0x55,…`) with +**`SRA` overrun set** (`(SRA & 0xF0) == 0x10`). **The firmware never writes the DUART +transmit register anywhere on that path** (no write to off `0x03`, driver-side +logged), so no loopback — internal (MR2 loopback) or external (TX→RX) — can produce +that data. The same is true of the level-0 "I/O data path test", which writes `0xE1` +to DUART2's THRA and requires a **10-byte** reply. These are **host-protocol tests, +not passive loopback tests**: they need an external responder (the host, or a loopback +connector that is more than a passive TX→RX strap). That is the specific blocker. + +--- + +## 1. What I wired, and where + +Device: **MAME `SCN2681`** (Signetics 2681; same register model as the Motorola +`MC68681`, which is the "same family"). Reasons for `SCN2681` over `MC68681`: + +* The firmware uses only the **RD/WR-strobe** register interface; it never touches the + 68681-specific **Interrupt Vector Register** behaviour or IACK. The 2681 is the + strobe part, so it is the closer of the two; the 68681 would work identically here. +* Both share `duart_base_device`; the 68681 adds only IVR/DTACK/IACK. Nothing is lost. + +Windows and role (chosen from ROM strings and from which test the level-1 runner +drives — see §3): + +| window (map units) | device | channels | evidence | +|---|---|---|---| +| `0x0640000` | `SCN2681` `duart1` | A = **host**, B = **printer** | the level-1 host-port test writes this window's CRA/MR/CSRA (`06400002`, `06400000`, `06400001`) | +| `0x0620000` | `SCN2681` `duart2` | A = **keyboard**, B = **mouse** | ROM strings `E - DUART1, CHANNEL A (HOST)`, `H - DUART2, CHANNEL A (KEYBOARD)`, `I - DUART2, CHANNEL B (MOUSE)`; the level-0 I/O test drives this window | + +Both are gated on `VT1200_DUART` so the no-env baseline is byte-identical. The entries +cover only the DUART's 16 registers (32 bytes = `0x100` map units) and are registered +**after** the board-window model, so the rest of a window still falls through to the +model (a window can mix a DUART with other board registers, as the brief warned). + +The monitor-selection switch is modelled the way the hardware does it: it is +`IP4-IP6` of the DUART at `0x0640000D`, driven high (`0x70` = "VR150") with +`ip4_w/ip5_w/ip6_w`. Confirmed in the log: `R 0640000D … val=00F0`, and the firmware's +`andi 0x70` yields `0x70` (a legal monitor) rather than "Undefined monitor switch +setting". + +Loopback wiring: `a_tx_cb → rx_a_w` and `b_tx_cb → rx_b_w`, selectable per DUART +(`VT1200_DUART_LB1` for the host/printer DUART, `VT1200_DUART_LB2` for keyboard/mouse) +to model the service guide's loopback connectors. When MR2 bit 7:6 = `10` the chip +does its own **internal** loopback and does not call the TX callback, so this wiring is +harmless there and supplies **external** loopback for the normal-mode tests. + +New env knobs (all off by default; documented in the driver header): + +``` +VT1200_DUART=1 map the SCN2681s (both windows unless SEL says otherwise) +VT1200_DUART_SEL=1|2|12 which DUART ("1" = 0x0640000, "2" = 0x0620000; default 12) +VT1200_DUART_LB1=1 external TX→RX on DUART1 A/B (host/printer connector) +VT1200_DUART_LB2=1 external TX→RX on DUART2 A/B (keyboard/mouse connector) +``` + +--- + +## 2. The remap, and the evidence for it + +I did **not** assume it; I verified it, and it comes out as `duart_byte = handler_off * 2`, +i.e. `duart_register = handler_off`. + +Facts: + +* Map units are **bits** (`tms34010.h`: program space `data_width = 1`), and a handler + on an entry at `base` receives `(bitaddr − base) >> 4`. So byte offset within the + window = `handler_off * 2`. +* The datasheet register map has register *N* at byte *N* (for the registers in use: + `0x00` MR1A/MR2A, `0x01` SRA/CSRA, `0x02` CRA, `0x03` RHRA/THRA, `0x05` ISR/IMR, + `0x08` MR1B/MR2B, `0x09` SRB/CSRB, `0x0A` CRB, `0x0D` IP/OPCR, `0x0F` Stop/Reset OP). +* **Therefore register index = `dup_byte / 2` = `handler_off`.** + +The observed traffic fits only under that rule: + +* The firmware's generic DUART driver (`L3FA90960`, bit `0x3FA90960`) writes the command + sequence `0xCA,0x2A,0x3A,0x4A,0x5A,0x1A` to operand `+0x20` (→ off `0x02`) and the same + sequence to operand `+0xA0` (→ off `0x0A`). `0x02` = **CRA**, `0x0A` = **CRB**; the + values are coherent 68681 command nibbles (`0xC` enable Rx, `0x2` reset Rx, `0x3` reset + Tx, `0x4` reset errors, `0x5` reset break-change, `0x1` reset MR pointer). No other + `off` assignment makes those coherent. +* It then writes `MR1A = 0x13` and `MR2A = 0x07` to off `0x00` (MR pointer toggles), + `CSRA = 0x99` to off `0x01`, `MR1B/MR2B = 0x07` to off `0x08`, `CSRB = 0x99` to off + `0x09`, and the IMR value to off `0x05`. All match the MAME map. +* The host-port test writes `MR1A = 0x12`, `MR2A = 0x80` to off `0x00` (i.e. **local + loopback**: MR2 bit 7:6 = `10`), `CSRA = 0x88` to off `0x01`, and CRA commands + (`0x05` = enable Tx+Rx, `0x0A` = disable) to off `0x02` — then reads off `0x01` (SRA) + and off `0x03` (RHRA). The monitor switch at `0x0640000D` reads as `IP` (off `0x0D`), + bits 4-6, exactly as the service guide describes. +* The register value travels in the **low byte** of the firmware's 16-bit move (the + board log shows `val=0088` for CSRA), which is why the driver passes `data & 0xff`. + +Cross-check against the brief's own numbers: `byte = off*2` maps the brief's hot +`06400002` → byte `0x04` and `0640000A` → byte `0x14`, which the brief labels CRA and +CRB. `0x04` = register `0x02` = CRA, `0x14` = register `0x0A` = CRB. Consistent. + +(VT240, DEC's own terminal, wires the same part as `m_duart->read(offset >> 1)` on a +16-bit bus with `.umask16(0x00ff)` at `0172000` — the same "register = byte/2, value in +the low byte" convention. Independent corroboration.) + +--- + +## 3. Acceptance test — before / after + +**Baseline (board model only), 900 frames** (`cap.lua`), confirmed this session: + +``` +'LEVEL1/2 TESTING ** ' +'LEVEL1/2 ERROR -> HOST PORT - 15 ' +``` + +**With the DUART wired at the host window** — the config that actually reaches the +host-port test (DUART2 left to the board model so level 0 still passes): + +```sh +VT1200_VRAM=1 VT1200_BOARD_MODEL=handshake VT1200_BOARD_BUSY_TICKS=2 \ +VT1200_BOARD_SWITCH="0640000D:70:70" \ +VT1200_DUART=1 VT1200_DUART_SEL=1 \ + $MAME vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \ + -debug -debugger none -debuglog -autoboot_script harness/mame/cap.lua -autoboot_delay 0 +``` + +``` +'LEVEL1/2 TESTING ** ' +'LEVEL1/2 ERROR -> HOST PORT - 15 ' <-- STILL PRESENT +``` + +So the acceptance criterion is **not met**, and I can say exactly why (§4). + +For completeness, mapping **both** DUARTs (`VT1200_DUART_SEL` unset) changes the +transcript to the **interactive diagnostics menu** (`VERSION 2.08 - SELECTED TEST +TYPE:` + the A–Z test list). As the owner noted, that menu is what the firmware falls +into on a failure, and I confirm the specific failure: the **level-0 I/O data-path +test** (`L3FDCE470`) writes `0xE1` to DUART2's THRA and then requires **10 received +bytes**; a real DUART with no responder has none, so it diverts to the menu. That is +the same underlying requirement as §4, one level earlier. + +--- + +## 4. Why HOST PORT still fails — the specific, measured condition + +The host-port test is reached and executed against the real DUART. Driver-side +(`VT1200_BOARD_LOG`) shows the test's whole transaction against `0x0640000`, at PCs +`3FA71F00…3FA76390`: + +``` +W 06400002 val=003A / 002A / 004A / 001A ; CRA commands (reset Tx/Rx/err/MR ptr) +W 06400001 val=0088 ; CSRA +W 06400000 val=0012 ; MR1A +W 06400000 val=0080 ; MR2A = 0x80 -> local loopback +W 06400002 val=0005 ; CRA: enable Tx + Rx +W 06400002 val=000A ; CRA: disable Tx + Rx +R 06400001 val=0000 ; SRA +R 06400003 val=0000 ; RHRA +``` + +**There is no write to off `0x03` (THRA/RHRA) anywhere on this path** — the only +`06400003` line in the whole log is the read above. So the transmitter is never fed. + +I made that claim airtight with a **full, non-deduplicated** DUART access log +(`VT1200_DUARTLOG`), 1400 frames, both DUARTs: + +``` +total DUART1 accesses: 134 +writes to reg 3 (THRA): 0 +writes to reg B (THRB): 0 +reg-write histogram (DUART1): reg2=45 regA=24 regF=10 regE=7 + reg5=6 regD=4 reg0=4 reg1=2 reg4=1 +``` + +Every write is a mode/command/status register (MR, CSRA/CRA, ACR, IMR, CRB, OPCR, +OP bits). **No transmit register is ever written, on either channel of either DUART.** + +Breakpointing the test's own comparisons (`harness/mame/duart_probe.lua`) gives the +expected vs actual: + +``` +3FA761E0 SRA & 0xF0 -> 0x00 ; test requires 0x10 +3FA76390 RHRA -> 0x00 ; test requires 0x0A, then 0x1F, then 0x0A +``` + +The expected values come from a RAM table at bit `0x270220` (dumped from main RAM): +`0xAA, 0xFF, 0xAA, 0x55, …`, masked by the table at `0x270470` (`0x1F,…`), i.e. +`0xAA & 0x1F = 0x0A`, `0xFF & 0x1F = 0x1F`. Two details then fit together exactly: + +* the test first waits for a receive count to reach **5** (`*struct+0x320 ≥ 5`), and +* it requires the **overrun** bit (`SRA & 0xF0 == 0x10`) — which is what a 5-byte burst + into the 4-deep 68681 Rx FIFO produces. + +In other words the test expects a **5-byte external pattern** to have arrived on the +receiver, overflowing the FIFO, and then reads the status and the first bytes back. +Nothing in the firmware transmits that pattern, so the data can only come from off-chip. + +This is why **MAME's `SCN2681` is necessary but not sufficient**: the model is faithful, +but the test's data source is external. The owner's fallback of "wire TX back to RX" +cannot help, because there is no TX activity to loop. (Both were tried: internal +loopback is already set by the firmware via MR2=`0x80`; external TX→RX was wired with +`VT1200_DUART_LB1` — the transcript is identical.) + +### What would be needed (an honest statement, not done) + +To make the line disappear, something must play the **host / active loopback**: +e.g. a driver-side callback that drives `duart1->rx_a_w` with the expected serial +pattern (`0xAA,0xFF,0xAA,0x55,…`) in response to the receiver being enabled. That is a +model of the **host protocol**, not of the DUART, and it is exactly the kind of +hand-rolled stand-in the owner asked me to avoid unless the real device is +impractical. I did **not** add it. I am flagging it as the concrete remaining piece. + +--- + +## 5. Regression + +With **no** `VT1200_*` env vars set, `progress.lua` at 200 frames still reports: + +``` +PC=3FDCE400 samples=199 +``` + +All new behaviour is behind `VT1200_DUART` / `VT1200_DUART_SEL` / +`VT1200_DUART_LB1` / `VT1200_DUART_LB2`; the device entries are only registered when +`VT1200_DUART` is set, and the SCN2681s are instantiated but idle (no timer running, no +interrupt wired) otherwise. + +## 6. MAME-tree changes + +**None this task.** The only local MAME change remains the earlier, already-recorded +`REFCNT` edit in `src/devices/cpu/tms34010/tms34010.cpp` (with its revert note inline). +MAME's `mc68681.cpp` already implements the 68681's local loopback (MR2 bit 7:6 = `10`) +and the break-in-loopback behaviour; that is **stock upstream code**, not a local +modification (verified: `git status` in the MAME tree shows only `tms34010.cpp` and +`mame.lst` modified). + +## 7. What I could not determine + +* The **source** of the host-port test's expected pattern. Every transmit path I could + find in the ROM (`*aX(0x30)` writes at bits `3FA79C00`, `3FA7B8E0`, `3FA7BAA0`) is in + a *different* routine that is **not reached** by the level-1 host-port test. I could + not find a THRA write on the host-port path, and the driver-side log agrees. I do not + know whether on real hardware the pattern comes from the connected host or from a + loopback connector with active logic. +* Whether the VT1200's level-1 host-port test is *expected to pass* with nothing but a + passive loopback plug. The service guide says level-1 "completes within 25 seconds" + and mentions loopback connectors, but the firmware's own comparison (5-byte pattern + + overrun) implies an active responder. +* The DUART interrupt path. There is a DUART ISR in the ROM (`L3FA78D90`), and the + second host-port sub-test waits on a flag that an ISR would set; I did not wire + `irq_cb()` to the CPU, and did not establish whether the host-port test needs it. The + failing sub-test (`L3FA753D0`) does **not** wait on that flag — it reads the chip + directly — so the IRQ is not what fails it. + +## 8. Files added / changed + +* `harness/mame/vt1200.cpp` — `SCN2681` at `0x0640000` (host/printer) and `0x0620000` + (keyboard/mouse); `duart1_r/w`, `duart2_r/w` with `register = off`, `data & 0xff`; + optional external TX→RX loopback; monitor switch on `IP4-IP6`; all gated on + `VT1200_DUART*`. Doc block updated. Also `VT1200_DUARTLOG` (full, non-deduplicated + DUART access log — the proof in §4). +* `harness/mame/duart_probe.lua` — breakpoints on the host-port test's comparisons and + register writes (the evidence in §4). +* `harness/mame/DUART_REPORT.md` — this report. diff --git a/harness/mame/duart_probe.lua b/harness/mame/duart_probe.lua new file mode 100644 index 0000000..aff9b68 --- /dev/null +++ b/harness/mame/duart_probe.lua @@ -0,0 +1,24 @@ +local machine = manager.machine +local cpu = machine.devices[':maincpu'] +local dbg = machine.debugger +if not dbg then print("ERROR: need -debug"); machine:exit() return end +local function bp(addr, msg) + dbg:command(string.format('bpset %s,,{ printf "%s\\n", pc, a7, a9, a11, a13 ; g }', addr, msg)) +end +bp("3fa761e0", "SRA-highnibble pc=%08X a7=%08X a9=%08X a11=%08X a13=%08X") +bp("3fa76f30", "CSRA-WRITE pc=%08X a7=%08X a9=%08X a11=%08X a13=%08X") +bp("3fa76f50", "CSRA-WRITTEN pc=%08X a7=%08X a9=%08X a11=%08X a13=%08X") +bp("3fa76340", "RHRA-READ pc=%08X a7=%08X a9=%08X a11=%08X a13=%08X") +bp("3fa76390", "RHRA-CMP pc=%08X a7=%08X a9=%08X a11=%08X a13=%08X") +bp("3fa76010", "CRA-0A-pre pc=%08X a7=%08X a9=%08X a11=%08X a13=%08X") +-- also the registry writes by the wrapper +bp("3fa71f00", "CRA-3A pc=%08X a7=%08X a9=%08X a11=%08X a13=%08X") +bp("3fa721f0", "CSRA-tbl-write pc=%08X a7=%08X a9=%08X a11=%08X a13=%08X") +print("probe armed") +local n=0 +emu.register_frame_done(function() + n=n+1 + if n >= tonumber(os.getenv("CAP_MAX") or "1400") then print("done "..n); machine:exit() end +end, "duart-probe2") +dbg.visible_cpu = cpu +dbg.execution_state = 'run' diff --git a/harness/mame/vt1200.cpp b/harness/mame/vt1200.cpp index 4c8baef..59f7ddf 100644 --- a/harness/mame/vt1200.cpp +++ b/harness/mame/vt1200.cpp @@ -44,6 +44,16 @@ VT1200_BOARD_RAM=1 map the windows as plain read/write RAM instead (writes persist and read back). + VT1200_DUART=1 put MAME's SCN2681 on the two DUART windows + (0x0640000 = host/printer, 0x0620000 = + keyboard/mouse), with TX wired back to RX for + the self-test's loopback connector. Gated so + the no-env baseline is unchanged. + VT1200_DUART_SEL= which DUART(s): "1" = 0x0640000, "2" = + 0x0620000; default "12". + VT1200_DUART_LB1/LB2=1 external TX->RX loopback on DUART1 / DUART2. + VT1200_DUARTLOG= every DUART access (not de-duplicated). + 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. @@ -63,6 +73,7 @@ #include "emu.h" #include "cpu/tms34010/tms34010.h" #include "emupal.h" +#include "machine/mc68681.h" #include "screen.h" #include @@ -94,6 +105,8 @@ public: , m_screen(*this, "screen") , m_palette(*this, "palette") , m_mainram(*this, "mainram") + , m_duart1(*this, "duart1") + , m_duart2(*this, "duart2") , m_vram(*this, "vram") { } @@ -122,6 +135,12 @@ public: u16 board2_r(offs_t off); void board2_w(offs_t off, u16 data); u16 board3_r(offs_t off); void board3_w(offs_t off, u16 data); + // The two DUARTs, gated on VT1200_DUART. DUART1 = host (ch A) + + // printer (ch B) at window 0x0640000; DUART2 = keyboard (ch A) + mouse + // (ch B) at window 0x0620000. + 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); + private: void program_map(address_map &map) ATTR_COLD; @@ -134,6 +153,12 @@ private: required_device m_palette; required_shared_ptr m_mainram; + // The two DUARTs (Signetics SCN2681 / Motorola MC68681 family). The + // firmware's level-1 I/O tests drive them byte-per-register (register N at + // byte 2N, i.e. harness offset N), which MAME's 68681 model expects. + required_device m_duart1; // 0x0640000: host(A)/printer(B) + required_device m_duart2; // 0x0620000: keyboard(A)/mouse(B) + // The four undecoded board windows, owned by the standalone model in // vt1200_board.h so it can be unit-tested without MAME. The driver only // forwards its window callbacks and configures the model from the env knobs. @@ -220,6 +245,13 @@ private: u16 lowlog_r(offs_t off); void lowlog_w(offs_t off, u16 data); void log_low(char kind, u32 addr, u16 value); + + // VT1200_DUARTLOG= + // EVERY DUART access (not de-duplicated), so a write that happens once per + // iteration cannot hide. Used to prove whether the transmit register is + // ever written on the host-port test path. + FILE *m_duartlog = nullptr; + void log_duart(int win, char kind, u32 reg, u16 value); }; void vt1200_state::machine_start() @@ -332,6 +364,31 @@ void vt1200_state::machine_start() fprintf(m_lowlog, "# kind bitaddr pc value -- transparent low-RAM access log\n"); } + if (const char *p = getenv("VT1200_DUARTLOG")) + { + m_duartlog = fopen(p, "w"); + if (m_duartlog) + fprintf(m_duartlog, "# win kind reg val pc -- every DUART access (not de-duplicated)\n"); + } + + // With the real DUARTs in place, board-level signals that used to be faked + // by the window model come from the DUART's input port (read register 0x0D: + // IP0-IP6, bit 7 always set). The monitor-selection switch is IP bits 4-6; + // driving them high selects the VR150 CRT (0x70), matching the value the + // firmware looks for and the value the board model was configured with. + // IP2 stays low (the value the board model produced for the blit-status + // test at bit 0x3F204AA0). + if (getenv("VT1200_DUART")) + { + const char *sel = getenv("VT1200_DUART_SEL"); + if (!sel || strchr(sel, '1')) + { + m_duart1->ip4_w(1); + m_duart1->ip5_w(1); + m_duart1->ip6_w(1); + } + } + 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", @@ -340,6 +397,7 @@ void vt1200_state::machine_start() 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); + logerror("vt1200: duart=%s\n", getenv("VT1200_DUART") ? "on (SCN2681 @0640000 host/printer, @0620000 kbd/mouse, tx->rx loopback)" : "off"); } void vt1200_state::log_probe(const char *tag, char kind, u32 off, u32 base) @@ -575,12 +633,28 @@ void vt1200_state::program_map(address_map &map) map(BOARD_BASE[3], BOARD_BASE[3] + 0xffff).rw(FUNC(vt1200_state::board3_r), FUNC(vt1200_state::board3_w)); } + // The two SCN2681 DUARTs, gated on VT1200_DUART so the no-env baseline is + // untouched. Registered AFTER the board-window model so they win 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. + // VT1200_DUART_SEL picks which ("1" = 0x0640000 host/printer, "2" = + // 0x0620000 keyboard/mouse; default both). + if (getenv("VT1200_DUART")) + { + const char *sel = getenv("VT1200_DUART_SEL"); + std::string s(sel ? sel : "12"); + if (s.find('1') != std::string::npos) + map(VT1200_WIN3, VT1200_WIN3 + 0xff).rw(FUNC(vt1200_state::duart1_r), FUNC(vt1200_state::duart1_w)); + if (s.find('2') != std::string::npos) + map(VT1200_WIN2, VT1200_WIN2 + 0xff).rw(FUNC(vt1200_state::duart2_r), FUNC(vt1200_state::duart2_w)); + } + // Transparent low-RAM logging proxy, registered LAST so it wins its slice // of RAM. It forwards to the real mainram share and only records. if (getenv("VT1200_LOWLOG")) map(0x00000000, 0x00003fff).rw(FUNC(vt1200_state::lowlog_r), FUNC(vt1200_state::lowlog_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 // in both cases. @@ -627,6 +701,88 @@ void vt1200_state::board1_w(offs_t off, u16 data) { board_write(1, off, data); } void vt1200_state::board2_w(offs_t off, u16 data) { board_write(2, off, data); } void vt1200_state::board3_w(offs_t off, u16 data) { board_write(3, off, data); } +// --- DUARTs --------------------------------------------------------------- +// +// Remap. The map entry base is a BIT address and the handler receives +// (bitaddr - base) >> 4, so byte offset within the window = off * 2. The +// observed access pattern is a canonical 68681 setup (CRA writes of +// 0x3A/0x2A/0x4A/0x1A, CSRA=0x88/0x99, MR1A/MR2A at off 0) and those offsets +// line up with the datasheet register map only when register index = off: +// byte 2N (harness off N) with the register in the low byte of the 16-bit +// access. So: +// +// firmware operand 0x064000D0 -> bit addr -> harness off 0x0D +// -> byte offset 0x1A = register 0x0D (IP) +// +// i.e. duart_byte = handler_off * 2 and duart_register = handler_off. +// +// Each window is 16 registers (32 bytes = 0x100 map units); the entry only +// covers that, so anything else in the window still falls through to the +// board model below it (a window may mix a DUART with other board registers). +// The firmware presents the register value in the LOW byte of its 16-bit +// move (the board log shows value 0x0088 for CSRA), hence `data & 0xff`. + +u16 vt1200_state::duart1_r(offs_t off) +{ + const u32 a = VT1200_WIN3 | (u32(off) & 0xffff); + if ((off & 0xffff) < 0x10) + { + const u16 v = m_duart1->read(u32(off) & 0xf); + log_pc_access('R', a, v); + log_duart(1, 'R', u32(off) & 0xf, v); + return v; + } + return board_read(3, off); +} + +void vt1200_state::duart1_w(offs_t off, u16 data) +{ + const u32 a = VT1200_WIN3 | (u32(off) & 0xffff); + if ((off & 0xffff) < 0x10) + { + m_duart1->write(u32(off) & 0xf, u8(data & 0xff)); + log_pc_access('W', a, data); + log_duart(1, 'W', u32(off) & 0xf, data); + return; + } + board_write(3, off, data); +} + +u16 vt1200_state::duart2_r(offs_t off) +{ + const u32 a = VT1200_WIN2 | (u32(off) & 0xffff); + if ((off & 0xffff) < 0x10) + { + const u16 v = m_duart2->read(u32(off) & 0xf); + log_pc_access('R', a, v); + log_duart(2, 'R', u32(off) & 0xf, v); + return v; + } + return board_read(2, off); +} + +void vt1200_state::duart2_w(offs_t off, u16 data) +{ + const u32 a = VT1200_WIN2 | (u32(off) & 0xffff); + if ((off & 0xffff) < 0x10) + { + m_duart2->write(u32(off) & 0xf, u8(data & 0xff)); + log_pc_access('W', a, data); + log_duart(2, 'W', u32(off) & 0xf, data); + return; + } + board_write(2, off, data); +} + +void vt1200_state::log_duart(int win, char kind, u32 reg, u16 value) +{ + if (!m_duartlog) + return; + fprintf(m_duartlog, "%d %c reg=%X val=%04X pc=%08X\n", + win, kind, reg, value, (unsigned)m_maincpu->state_int(TMS34010_PC)); + fflush(m_duartlog); +} + void vt1200_state::scanline(screen_device &screen, bitmap_ind16 &bitmap, int scanline, const tms340x0_device::display_params *params) { @@ -801,6 +957,29 @@ void vt1200_state::vt1200(machine_config &config) // 256 entries so the TMS34010 renderer can encode multi-bit pixels; the // terminal's real output stage is a later question. PALETTE(config, m_palette).set_entries(256); + + // The two Signetics SCN2681 DUARTs (same family as the Motorola MC68681: + // 2681 = RD/WR strobes, 68681 = R/W + DTACK + interrupt vector). The + // VT1200 firmware drives them with the generic 68681 register map, and the + // level-1 host-port test uses the chip's own local loopback (MR2 bits 7:6 = + // 10), so the SCN2681 fits and needs no vector-register behaviour. + SCN2681(config, m_duart1, XTAL(3'686'400)); + SCN2681(config, m_duart2, XTAL(3'686'400)); + + // External loopback (TX wired back to RX) models the loopback connectors + // the service guide's port tests use. Selectable per DUART so the + // host/printer connector can be fitted without feeding the keyboard/mouse + // DUART looped-back bytes that the firmware would read as key presses. + if (getenv("VT1200_DUART_LB1")) + { + m_duart1->a_tx_cb().set(m_duart1, FUNC(scn2681_device::rx_a_w)); + m_duart1->b_tx_cb().set(m_duart1, FUNC(scn2681_device::rx_b_w)); + } + if (getenv("VT1200_DUART_LB2")) + { + m_duart2->a_tx_cb().set(m_duart2, FUNC(scn2681_device::rx_a_w)); + m_duart2->b_tx_cb().set(m_duart2, FUNC(scn2681_device::rx_b_w)); + } } INPUT_PORTS_START(vt1200)