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.
This commit is contained in:
SDD committed 2026-10-16 11:22:56 +03:00
1 parent 1c0a9b2b64
commit 2fd9286c1a
3 files changed
+482 -1

No files matched your search

+278
View File
@@ -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.
+24
View File
@@ -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'
+180 -1
View File
@@ -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=<s> which DUART(s): "1" = 0x0640000, "2" =
0x0620000; default "12".
VT1200_DUART_LB1/LB2=1 external TX->RX loopback on DUART1 / DUART2.
VT1200_DUARTLOG=<path> 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 <cstdio>
@@ -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<palette_device> m_palette;
required_shared_ptr<u16> 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<scn2681_device> m_duart1; // 0x0640000: host(A)/printer(B)
required_device<scn2681_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=<path>
// 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)