Files
vt1200/harness/mame/INPUT_REPORT.md
T
arti 02acabb7ef harness: real LK401 keyboard + VSXXX-AA mouse devices on DUART2
Add the terminal's actual input parts to the harness as device models on the
already-modelled SCN2681 at DUART2: a DEC LK401 keyboard (channel A, 4800 8N1)
and a DEC VSXXX-AA mouse (channel B, 4800 8O1), both driven by MAME's host
input and wired bit-by-bit to the DUART (rx_a_w/rx_b_w and a_tx_cb/b_tx_cb).

Route: the docs, not MAME's lk201. MAME's lk201 device executes the 68HC05
firmware 23-001s9-00.bin, which is not in our ROM set (the downloaded zip only
carries the 8051 23-004M2-00.bin, MAME's unused second region), so route 1 is
unusable without sourcing that ROM. The models follow PROTOCOL.md: the keyboard
answers FD with the 4-byte self-test and reports documented keycodes honouring
the division modes; the mouse answers T with its 4-byte self-test and streams
3-byte incremental reports.

One physical-layer detail was required: the SCN2681 receiver mis-samples the
first byte after a receiver reset unless the line is explicitly idle-high
before the start bit. Both models now hold TxD at mark when idle (which is
faithful anyway) -- isolated evidence: bit-banging 0x55 into rx_a_w gives
RHRA=D5 with no idle-high, RHRA=55 with four idle-high bits.

Verified (VT1200_SKIP_LEVEL1=1, input_inject.lua): the OS reads the keyboard
keycode 0xC2 (A) and ~900 bytes of VSXXX reports (dx=+16/+32); after the
self-test it switches the mouse to stream mode; the X11 cursor arrow is visible
at frame 4400 and has moved by frame 4600.

Nothing was tuned for the level-0 I/O data-path test: with no env the boot
still fails it honestly and the regression baseline is unchanged
(PC=3FDCE400 samples=198, 3 PCs). VT1200_IO_PATH_STUB=1 (53/75) and the skip
run (Terminal Manager at frame 3000) are also unchanged. ROM md5 unchanged.

See harness/mame/INPUT_REPORT.md.
2026-10-16 15:33:30 +03:00

9.3 KiB
Raw Blame History

Keyboard & mouse as real devices on DUART2 — report

Adds the VT1200's actual input parts to the harness as device models on the already-modelled SCN2681 at DUART2: a DEC LK401 keyboard on channel A and a DEC VSXXX-AA mouse on channel B. Both are driven by MAME's host input and talk to the DUART through its genuine bit-level serial lines.

Status: works. The X11 cursor moves with the mouse and keystrokes reach the OS. Existing configurations are unchanged.


1. Route chosen: LK401 from the docs (route 2)

The brief offered two routes. Route 1 (MAME's own lk201 device + firmware ROM) is not usable, and the ROM check is decisive:

  • MAME's lk201 device (src/mame/dec/lk201.cpp) runs an M68HC05EG and loads 23-001s9-00.bin (8 KiB, CRC be293c51) from its lk201_cpu region.
  • The firmware zip downloaded during the protocol research (reference/keyboard-mouse/bitsavers-23-004M2-00_LK201AA_Firmware.zip) contains only 23-004M2-00.BIN (4096 bytes, SHA1 9744c561…, CRC 35dd04c6). That is exactly MAME's second ROM — the 8051 23-004m2.8051h.e1 — which the lk201 device does not execute (it is the red-LED LK201 variant; the device models the 6805 green-LED one).
  • So MAME's lk201 would need a ROM we do not have. Running it is impossible without sourcing 23-001s9-00.bin.

Route 2 it is: an HLE model of the documented LK401/LK201 host protocol from reference/keyboard-mouse/PROTOCOL.md, driving the real DUART wires. This also matches the actual fitted part (LK401) rather than an LK201, and needs no external ROM. Had route 1 been available it would have had the honest drawback of speaking LK201 keycodes — for this OS, which reads the keyboard over the protocol and does not care which family the keycodes came from, that would probably not have mattered in practice; but it is moot because the ROM is missing.

The mouse has no MAME device, so the VSXXX-AA is modelled either way (it is small).

2. What was implemented

Both devices live in harness/mame/vt1200.cpp (inside the owned driver, so the MAME tree gains no files), derive from device_buffered_serial_interface<>, and are wired bit-by-bit:

keyboard tx_handler  ->  duart2.rx_a_w        (device TX  -> DUART RX)
mouse    tx_handler  ->  duart2.rx_b_w
duart2.a_tx_cb / b_tx_cb ->  device rx_w      (DUART TX  -> device RX)

So the framing (keyboard 4800 8N1, mouse 4800 8O1) and the baud timing are the DUART's own; there is no byte-level shortcut.

LK401 keyboard (vt1200_lk401_device), protocol from PROTOCOL.md:

  • Answers FD with the documented 4-byte self-test 01 00 00 00 (and runs it 70 ms after power-up, as the real part does). AB returns the same ID bytes.
  • Parses the command set: D3, E9 (LK401 mode), 13 p/11 p (LEDs), keyclick/bell/auto-repeat commands, CB, 8B, and the division mode-set commands (acknowledged with BA).
  • Reports key transitions as single keycodes (> 0x40) from the documented table (netbsd-lk201-keycodes.html + elkay/docs/keymap.md), honouring each division's programmed mode (down-only / auto-repeat-down / down-up) and sending B3 (all-ups) when the last down-up key is released. B4 metronome ticks implement auto-repeat. LK401-only keys (RShift/LAlt/RAlt/RCompose) are gated on the E9 command.
  • Host input: one MAME keyboard port per key (:lk401:K0…K107) with PORT_CODE and PORT_CHAR, polled at 120 Hz.

VSXXX-AA mouse (vt1200_vsxxxaa_device), protocol from PROTOCOL.md §4:

  • Answers T (and power-up) with the 4-byte self-test report A0 02 00 00 (header 101 + revision 0; manufacturer 0; device code 0b0010 = mouse; error 0; no buttons).
  • Implements stream mode (R), prompt mode (D/P), and the S format command. Reports 80 | SX SY L M R, |dx|, |dy|.
  • Host input: relative IPT_MOUSE_X / IPT_MOUSE_Y plus three buttons; deltas are turned into incremental reports (Y inverted for DEC coordinates).

The one non-obvious fix: idle-high TxD

The SCN2681's device_serial_interface receiver has a phase dependency: if the first edge it sees after a receiver reset is the start bit, with no explicit idle-high beforehand, it synchronises late and mis-samples the byte. Isolated evidence — bit-banging 0x55 (8N1, 4800) straight into duart2.rx_a_w with the CPU halted:

preceding bits SRA RHRA
none 0D D5
4 idle-high 0D 55

The fix is faithful anyway: a UART's TxD idles at mark (high). Both device models now hold the line high whenever they are not mid-byte, which is what makes the link work. This is the only place a physical-layer detail had to be made explicit.

3. Verification

3.1 Does the cursor move / do keystrokes register? — YES

Run: VT1200_SKIP_LEVEL1=1 (the only configuration that reaches the OS), headless, with harness/mame/input_inject.lua injecting input.

  • Keystroke registers. The script presses and releases A (:lk401:K26) at frame 4450. The OS's DUART2 poller reads the keycode 0xC2 (the documented LK401 code for A) from channel A at emulated t=62.2856 s (2 R reg=3 val=00C2 pc=3FB42710). The protocol delivers keystrokes to the OS.
  • Mouse reports reach the OS. The script walks the mouse 16–32 units/frame. The OS reads ~900 bytes of well-formed VSXXX reports from channel B; the raw stream is 98 10 00 / 98 20 00 = header 100, SX=1, SY=1, no buttons, dx=+16/+32, dy=0.
  • The cursor moves. The X server's X11 pointer arrow is visible at (768,472) at frame 4400 and has left that location by frame 4600 (frames 4700 and 5000 are byte-identical, i.e. it settled). Evidence images: input_cursor_before_f4400.png, input_cursor_after_f4600.png (full frames, downscaled) and input_cursor_band_before_f4400.png / input_cursor_band_after_f4600.png (3× zoom of the memory-bar band the pointer sat on).
  • The mouse self-test now decodes cleanly and the OS switches the mouse to incremental stream mode (R, THRB) — before the idle-high fix the self-test was garbled (0x27…) and the OS never sent R.
  • After FD, the OS reads the keyboard self-test as 01 00 00 00 and the mouse self-test as A0 02 00 00.

The pointer's travel under continuous injection is small in absolute terms because it ends up clamped at the Terminal Manager window border; the point is that it is the X server's own cursor and it tracks the device.

3.2 Level-0 test outcome — unchanged (report only)

The level-0 I/O data-path outcome did not change. With no environment the boot still fails level 0's count-only 10-byte test and parks in the diagnostics menu; the regression baseline is byte-for-byte the same:

# no env, progress.lua at 200 frames
PC=3FDCE400 samples=198 (3 distinct PCs)

This is the expected, honest result and does not contradict reference/level0-10byte-puzzle.md: the keyboard's power-up self-test is 4 bytes (and is sent before the DUART channel is configured, so it is dropped), 0x11 0x8C and 0xE1 provoke no reply, and the test flushes the receiver — so the real device contributes 0 bytes to the count, never 10. Nothing was tuned to make that test pass.

3.3 Existing configurations still hold

configuration expected observed changed?
no env level 0 fails honestly, menu PC=3FDCE400 samples=198 (3 PCs) no
VT1200_IO_PATH_STUB=1 reaches level 1 PC=3FDCE400 samples=53 (75 PCs) no
VT1200_SKIP_LEVEL1=1 X11 weave + Terminal Manager frame 3000 = Terminal Manager no

ROM md5 b46a3e442ccb86134d2356bd70d22fd2 — unchanged.

No baseline moved, so no baseline change needs recording. In particular the keyboard's power-up transmission does not perturb the faithful boot: it happens at 70 ms, before the OS programs DUART2, and the level-0 test's flush would consume it anyway.

The stub run's level-1 outcome is also unchanged (still the DUART1 counter-ready interrupt storm), so the new DUART2 devices do not alter the level-1 path.

4. Caveats / what is not done yet

  • HLE, not firmware. The keyboard models what the LK401 firmware does; it does not execute it. If 23-001s9-00.bin (or an LK401 ROM) is ever sourced, route 1 could replace this.
  • Keycode source. Keycodes/divisions come from the NetBSD LK201 table and elkay's LK401 map; the LK401 firmware's exact ID bytes are unknown, so the self-test uses the documented healthy 01 00 00 00.
  • Auto-repeat is a simple 500 ms / 20 Hz model — enough to be correct in shape, not measured against hardware.
  • 0xB9 ambiguity. Per PROTOCOL.md it is "disable Ctrl-key click" (no reply); NetBSD also lists it as "inhibit transmission" (reply B7). The no-reply reading was chosen because the VT1200 OS never sends it.
  • The mouse reports are read by the OS's poller; the exact pointer acceleration/clamping is the terminal software's, not ours.

5. Files

  • harness/mame/vt1200.cpp — the two device models, input ports, and DUART2 wiring (the only source file changed).
  • harness/mame/input_probe.lua — lists the device ports/fields (the tags Lua needs).
  • harness/mame/input_inject.lua — drives the mouse and one key from Lua for the acceptance test.
  • harness/mame/input_cursor_*.png — the frame evidence above.
  • New opt-in knob VT1200_INPUT_LOG=<path> — per-byte device TX/RX trace.