harness: opt-in VT1200_IO_PATH_STUB stand-in for the level-0 keyboard-port test fixture

The level-0 I/O data path test writes one 0xE1 byte to DUART2-A THRA and
demands exactly 10 bytes back; it checks only the count and never compares
the content, and no documented keyboard/mouse/tablet message is 10 bytes.
Add an opt-in stand-in that watches for that 0xE1 write and feeds 10
arbitrary (0x00) bytes on the channel's receive path via the SCN2681's
public rx_fifo_push(), refilling the 4-deep FIFO on each RHRA read.

This is explicitly NOT a keyboard model: it stands in for an unknown test
fixture on the keyboard port. It replies only while the channel is polled
(IMR=0x00), so it raises no interrupt; with the stub on the run clears
level 0 and reaches level 1's HOST PORT - 15, but still no OS (SKIP_LEVEL1
remains the only route to X11). Default is unchanged: no env still fails
level 0 honestly (progress.lua samples=198/3 PCs); stub-on is 53/75 PCs.
See harness/mame/README.md.
This commit is contained in:
SDD committed 2026-10-16 14:31:20 +03:00
1 parent 215fd573db
commit f7caf9aed8
2 files changed
+173 -11

No files matched your search

+45 -6
View File
@@ -88,10 +88,33 @@ there is no expected-value table. The keyboard's `FD`/power-up reply is 4 bytes,
the mouse's `T` reply is 4, the tablet's 5 — none is 10. A faithful LK401 would not
help either: the init sequence's receiver-reset **flushes the FIFO**.
So no responder will be added. The real terminal *does* pass this test, so the
bytes exist on real hardware — they are simply not inferable from the ROM, and the
only honest way to close the question is to observe a real keyboard port at
power-up.
The real terminal *does* pass this test, so the bytes exist on real hardware — they
are simply not inferable from the ROM, and the only honest way to close the question
is to observe a real keyboard port at power-up. **No faithful responder can be
derived from any document.**
So instead there is an **opt-in stand-in**, `VT1200_IO_PATH_STUB=1`. Read what it
is carefully, because it is easy to misread:
* It is **not a keyboard model** and it is not any documented device. It stands in
for an **unknown test fixture on the keyboard port**.
* Its bytes are **arbitrary** (`0x00`). Nothing compares them: the routine stores
the received byte and never reads it back, and there is no expected-value table,
so only the **count** matters. Feeding a pattern would imply a protocol that does
not exist.
* It triggers on the exact thing the test does: the one `0xE1` write to DUART2-A
`THRA` (handler reg 3, window `0x0620000`, store at bit `3FDCE8B0`), then feeds
**10 bytes** back on the same channel's receive path via the SCN2681's own
`rx_fifo_push()`. It replies only while the channel is **polled** (`IMR = 0x00`,
the level-0 condition), so it cannot inject into the interrupt-enabled OS phase
or cause an interrupt there.
* It is **off by default**. A bare run still fails level 0 exactly as before and
says so. This is deliberate: the faithful configuration stays the default.
With the stub on the boot clears level 0 and reaches **level 1's `HOST PORT - 15`**
(and `PRINTER PORT - 16`), which is the faithful ceiling. It does **not** produce a
booted OS: level 1 still fails on the DUART fidelity ceiling, so
`VT1200_SKIP_LEVEL1=1` remains the only route to X11.
### How far the OS gets
@@ -123,7 +146,7 @@ behaviour**, and is **not** evidence about the real terminal's POST.
The skip is deliberately **not** the default: the real machine runs level 1, so
the faithful configuration stays the default and the switch is how we diverge.
## Environment knobs (16; down from 33)
## Environment knobs (17; down from 33)
All opt-in, and all off by default — there is no longer a "working incantation" to
remember, because everything the firmware needs to run is now the default.
@@ -132,7 +155,8 @@ Instrumentation: `VT1200_DUARTLOG`, `VT1200_BOARD_LOG`, `VT1200_BOARD_MAP`,
`VT1200_IRQLOG`, `VT1200_LOG_UNMAPPED`, `VT1200_DPYLOG` (+`_FRAMES`),
`VT1200_DUMP_FRAME` (+`_FRAMES`), `VT1200_DUMP_RAM` (+`_FRAMES`),
`VT1200_DUMP_VRAM` (+`_FRAMES`), `VT1200_DUMP_DIR`, `VT1200_NO_RENDER`.
Behaviour switch: `VT1200_SKIP_LEVEL1`.
Behaviour switches: `VT1200_SKIP_LEVEL1`, `VT1200_IO_PATH_STUB` (the level-0
test-fixture stand-in described above — not a keyboard model, off by default).
## Regression baseline
@@ -153,6 +177,21 @@ This has moved **twice**, both deliberately:
menu. That looks like a regression and is not one — it is the removal of a
false pass.
**The no-env value is unchanged by `VT1200_IO_PATH_STUB`** — stub off is the
default, so the `samples=198 (3 PCs)` line above still holds. The stub is a separate
observed configuration:
```
# VT1200_IO_PATH_STUB=1, progress.lua at 200 frames
PC=3FDCE400 samples=53 (75 distinct PCs)
```
That is a genuine move, not a false pass: with the count-only level-0 test
satisfied, the run leaves the level-0 wait and executes level-0/level-1 work, then
parks at **level 1's `HOST PORT - 15`** (verified from the console transcript:
`LEVEL1/2 TESTING **`, then `LEVEL1/2 ERROR -> HOST PORT - 15`). It is recorded as a
*different* baseline because it is opt-in; the default must stay `samples=198`.
It is a change detector, not a specification. If it moves without you meaning it
to, find out why.
+128 -5
View File
@@ -39,7 +39,10 @@
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).
(see OS_BRINGUP_REPORT.md). VT1200_IO_PATH_STUB=1 adds an OPT-IN
stand-in for the unknown test fixture on that port (it is NOT a
keyboard model) so the count-only level-0 test can pass; the default
stays faithful and still fails.
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
@@ -101,6 +104,26 @@
the two phases' own routings. See
reference/level1-bypass-analysis.md.
VT1200_IO_PATH_STUB=1 feed 10 bytes into DUART2 channel A after
the level-0 I/O data path test writes 0xE1
to THRA. This is a STAND-IN for an unknown
test fixture on the keyboard port -- it is
NOT a keyboard model, and the bytes are
arbitrary (0x00) because the firmware checks
only the COUNT (it stores the received byte
and never compares it, and it does not
enable loopback). No documented
keyboard/mouse/tablet message is 10 bytes
long, so no documented device can satisfy
this test; see reference/level0-10byte-puzzle.md.
The stub replies only while the channel is
polled (IMR = 0x00), so it cannot inject
into the interrupt-enabled OS phase or
raise an interrupt there. Unset (the
default) the level-0 test fails honestly
and the boot stops in the diagnostics
menu.
Video: the driver paints the active scanlines itself, reading the
framebuffer out of video RAM (see render_active_line).
@@ -224,6 +247,16 @@ private:
required_device<scn2681_device> m_duart2; // 0x0620000: keyboard(A)/mouse(B)
bool m_duart1_int2 = false; // set by VT1200_SKIP_LEVEL1
// VT1200_IO_PATH_STUB=1: opt-in stand-in for the unknown test fixture on
// the keyboard port, so level-0's count-only I/O data path test can pass.
// NOT a keyboard model. See iopath_stub_arm() and the README.
bool m_iopath_stub = false;
int m_iopath_supply = 0; // bytes still to feed for the current 0xE1
u8 m_duart2_imr = 0; // last value written to DUART2 IMR (reg 5)
bool m_iopath_irq_reported = false;
void iopath_stub_arm(); // on the level-0 0xE1 THRA write
void iopath_stub_refill(); // after a channel-A RHRA read
// 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.
@@ -420,10 +453,17 @@ void vt1200_state::machine_start()
m_duart1_int2 = true;
}
// VT1200_IO_PATH_STUB=1: feed >=10 bytes on DUART2-A after the level-0
// I/O data path test writes 0xE1 to THRA. Off by default: with it off the
// run still fails level 0 honestly and parks in the diagnostics menu.
if (getenv("VT1200_IO_PATH_STUB"))
m_iopath_stub = true;
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=on (SCN2681 @0620000 kbd/mouse), skip_level1=%s\n",
getenv("VT1200_SKIP_LEVEL1") ? "on (IP3 high, IRQ->INT2)" : "off (IP3 low, IRQ->INT1)");
logerror("vt1200: io_path_stub=%s\n", m_iopath_stub ? "on (DUART2-A reply to 0xE1)" : "off (faithful)");
}
void vt1200_state::machine_reset()
@@ -432,6 +472,11 @@ void vt1200_state::machine_reset()
fflush(m_log);
if (m_unmapped_log)
fflush(m_unmapped_log);
// The device resets IMR to 0; mirror that in our tracker and drop any
// in-flight stub reply.
m_duart2_imr = 0;
m_iopath_supply = 0;
m_iopath_irq_reported = false;
}
void vt1200_state::log_unmapped(const char *kind, u32 addr, const char *pc)
@@ -616,9 +661,14 @@ 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);
const u32 reg = u32(off) & 0xf;
const u16 v = m_duart2->read(reg);
// The stub tops the receive FIFO back up as RHRA (reg 3) is drained,
// so RXRDY stays asserted for all ten bytes (the FIFO is only 4 deep).
if (m_iopath_stub && reg == 3)
iopath_stub_refill();
log_pc_access('R', a, v);
log_duart(2, 'R', u32(off) & 0xf, v);
log_duart(2, 'R', reg, v);
return v;
}
return board_read(2, off);
@@ -629,14 +679,75 @@ 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));
const u32 reg = u32(off) & 0xf;
m_duart2->write(reg, u8(data & 0xff));
if (reg == 5)
m_duart2_imr = u8(data & 0xff);
// The level-0 I/O data path test's one data byte: 0xE1 to THRA. That
// is the trigger for the opt-in test-fixture stand-in (not a keyboard).
if (m_iopath_stub && reg == 3 && u8(data & 0xff) == 0xe1)
iopath_stub_arm();
log_pc_access('W', a, data);
log_duart(2, 'W', u32(off) & 0xf, data);
log_duart(2, 'W', reg, data);
return;
}
board_write(2, off, data);
}
// --- VT1200_IO_PATH_STUB: the level-0 test-fixture stand-in -----------------
//
// The level-0 I/O data path test (L3FDCE470) writes ONE byte, 0xE1, to DUART2-A
// THRA and then requires EXACTLY 10 bytes to come back on the same channel. It
// never checks their content, never sets loopback, and transmits nothing else.
// No documented LK401/mouse/tablet message is 10 bytes long, and the routine's
// receiver-reset flushes the FIFO, so no documented device can satisfy it; see
// reference/level0-10byte-puzzle.md. This is therefore a stand-in for an
// UNKNOWN TEST FIXTURE on the keyboard port -- NOT a keyboard model -- and the
// bytes are deliberately arbitrary (0x00): only the COUNT is compared.
//
// Mechanism: duart_channel::rx_fifo_push() (the public channel API) instead of
// synthesising a serial bit stream on rx_a_w. A bit-level UART model would add
// timing surface without adding fidelity, because there is no document to be
// faithful to and the firmware ignores the values. The SCN2681 receive FIFO is
// only MC68681_RX_FIFO_SIZE+1 = 4 deep, so arm() pre-fills it and refill() adds
// one byte after every RHRA read, keeping RXRDY asserted until the ten bytes
// have been drained. At this point IMR = 0x00 (written by the channel
// initialiser), so pushing receive data cannot raise the chip's IRQ:
// update_interrupts() drives the line only when ISR & IMR != 0.
void vt1200_state::iopath_stub_arm()
{
if (m_iopath_supply)
return; // a previous reply is still draining
// Only reply while the channel is polled (interrupts masked), which is
// exactly the level-0 condition. This keeps the stub from injecting into
// the interrupt-enabled OS phase (IMR = 0x33) and raising an interrupt
// there -- it is scoped to the one test it exists for.
if (m_duart2_imr != 0x00)
return;
m_iopath_supply = 10;
const int fifo_depth = MC68681_RX_FIFO_SIZE + 1;
const int prefill = (m_iopath_supply < fifo_depth) ? m_iopath_supply : fifo_depth;
for (int i = 0; i < prefill; i++)
m_duart2->m_chanA->rx_fifo_push(0x00, 0);
m_iopath_supply -= prefill;
osd_printf_info("vt1200: IO-PATH STUB: 0xE1 on DUART2-A -> feeding 10 arbitrary bytes\n");
}
void vt1200_state::iopath_stub_refill()
{
if (m_iopath_supply <= 0)
return;
if (m_duart2_imr != 0x00) // interrupts got enabled: stop, don't arm
{
m_iopath_supply = 0;
return;
}
m_duart2->m_chanA->rx_fifo_push(0x00, 0);
m_iopath_supply--;
}
// The DUART's IRQ output is a single open-drain line per chip. Wire it to the
// CPU's external interrupt input the firmware's vector trampoline expects:
// line 0 -> TMS34010_INT1 (INTPEND 0x0002), line 1 -> TMS34010_INT2 (0x0004).
@@ -658,6 +769,18 @@ void vt1200_state::duart1_irq_w(int state)
void vt1200_state::duart2_irq_w(int state)
{
// Under the stub, report the first DUART2 IRQ assertion with the ISR/IMR
// and how many injected bytes were outstanding. In the faithful level-0
// run this line MUST NOT appear -- IMR = 0x00 there, so feeding receive
// data cannot interrupt. (It can appear under VT1200_SKIP_LEVEL1, where
// the OS enables DUART2 interrupts for its own traffic.)
if (state && m_iopath_stub && !m_iopath_irq_reported)
{
m_iopath_irq_reported = true;
const u32 isr = m_duart2->read(5) & 0xff; // side-effect-free read
osd_printf_info("vt1200: IO-PATH STUB: DUART2 IRQ asserted (isr=%02X imr=%02X supply=%d)\n",
isr, m_duart2_imr, m_iopath_supply);
}
m_maincpu->set_input_line(1, state ? ASSERT_LINE : CLEAR_LINE);
}