Files
vt1200/harness/mame/LEVEL1_CONTINUE_REPORT.md
arti 41454d5d4c harness: the stub level-1 run continues past both DUART failures; correct the "fatal/LED loop" claim
The stub run does not park at HOST PORT - 15. Both DUART failures are reported,
the board LED blinks once (finite L3F208660), TYPE 'GO' TO CONTINUE is printed
(no input is read), and the level-1 runner continues ('CONTINUING THE TESTS').
The runner never returns to 3F204440, so 3F208060/3F208650 are never executed.

The run then wedges at frame ~2839 in a DUART1 counter-ready interrupt storm:
the ISR polls DUART1 reg 5 (bit 0x06400050) and always reads 0x08; the counter
runs in timer mode (ACR=0x60) with CTUR/CTLR never programmed, so MAME re-arms
at 1 tick and the interrupt re-asserts every ~229 CPU cycles. Frames at 3500 /
6000 / 12000 are byte-identical; RAM differs by one byte.

Corrects README.md and annotates GATE_CLEANUP_REPORT.md, BRINGUP_LOG.md and
DUART_REPORT.md. Adds a console+PC/milestone instrument and the finding report.
2026-10-16 14:56:47 +03:00

8.5 KiB
Raw Permalink Blame History

The stub run does NOT park at HOST PORT — it continues, then dies in a DUART1 counter storm

Date: 2026-10-16. Configuration: ./run.sh stub (VT1200_IO_PATH_STUB=1), no other env. ROM vt1200_v22.bin md5 b46a3e442ccb86134d2356bd70d22fd2 (unmodified).

This report corrects a claim that stood as fact in reference/level1-bypass-analysis.md Q4 and shaped the wording of harness/mame/README.md: "a level-1 failure is fatal: the runner returns nonzero → 3F204440 jane L3F208060 → an infinite board-LED loop at 3F208650 jruc L3F208060." Measured, that is wrong for the DUART failures. (The file reference/level1-bypass-analysis.md is outside this task's owned paths, so its Q4 text is not edited here; this report is the correction of record and the owner should annotate it in place.)

All timings are screen frames (the firmware's own refresh), sampled once per emulated frame. Instrumentation: harness/mame/console_frames.lua, harness/mame/wall_hist.lua, and breakpoints on the sites named below; frames and VRAM/RAM dumped via the driver's VT1200_DUMP_* knobs. Console output is the firmware's own printer (0x3FDE00C0), captured to MAME debug.log with -debuglog; the on-screen text is from the frame dumps.

Timeline (across time, not one snapshot)

frame what happens
0–1 level 0 passes (stub); level-1 runner entered (3FDF72D0, called from 3F204400); LEVEL1/2 TESTING ** printed
470–481 HOST PORT test runs; it enables DUART1 interrupts (IMR = 0x07→0x01→0x07, channel A)
484 HOST PORT fails: dispatcher 3FDF0630 code 0x400 → handler 3FDE9D70 prints LEVEL1/2 ERROR -> HOST PORT - 15, dumps BAUD ERROR 00900040 / FRAME & OVERRUN ERROR 00030000 / INTERRUPT REG BIT 00000002, prints BLINKING SYSTEM BOARD LED, calls L3F208660 (one finite two-digit blink, error code 0x15)
484–1534 the blink's dsjs delay loops dominate the CPU (hottest PC 3F2088B0, etc.)
1534 handler prints TYPE 'GO' TO CONTINUE, then jauc L3FDF1010
1534→1627 L3FDF1010 takes the return branch (status word *A13(0xFFA0) is zero) → the runner continues to the next test
1613–1624 PRINTER PORT test runs; enables DUART1 channel-B interrupts (IMR = 0x70→0x10→0x70)
1627 PRINTER PORT fails: code 0x800 → LEVEL1/2 ERROR -> PRINTER PORT - 16, same three diagnostics, BLINKING SYSTEM BOARD LED, L3F208660 blink (code 0x16); on-screen text reads CONTINUING THE TESTS
2759 TYPE 'GO' TO CONTINUE again; returns via L3FDF1010; runner continues and draws the weave
2839 the next level-1 test enables only the DUART1 counter-ready interrupt: L3FA78A10 writes IMR = 0x08 (last IMR write of the run)
2839→end DUART1 counter-ready interrupt storm. Frame samples are 100 % inside the DUART1 interrupt path (00000F60 INT1 trampoline → 3FA92F40 → dispatcher L3FA78D90); the DUART1 IRQ re-asserts every ~229 CPU cycles
3500, 6000, 12000 frame dumps are byte-identical; RAM at 3500 vs 12000 differs in one byte (offset 0x4E105). Still storming at frame 20000

So the owner's two observations are exact: (1) after each DUART failure, after a finite delay (~1050 frames ≈ 15 s of the blink), it continues; (2) then it gets stuck — at frame 2839.

The "continues" is not a timeout and not the prompt

The error handler L3FDE9D70 ends

3fdea470: move a11, *a14+, 1
3fdea490: calla L3F208660          ; ONE finite blink of the LED error code
3fdea4e0: movi 0x3fdcd2a8, a9      ; "TYPE 'GO' TO CONTINUE"
3fdea520: calla L3FDE00C0          ; print it
3fdea550: jauc  L3FDF1010

and the shared exit is

L3FDF1010:
3fdf1010: move *a13(0xffa0), a11, 1
3fdf1030: jreq L3FDF10A0           ; status == 0  -> skip the blink, return
3fdf1040: move a14, -*sp, 1
3fdf1070: calla L3FDF1270          ; status != 0  -> the INFINITE LED blink
L3FDF10A0:
3fdf10a0: ... rets 2

Facts:

  • 3F208060 / 3F208650 are never executed in this run. Breakpoints on both fired 0 times over 20 000 frames; so did 3F204440 (the boot loop's jane L3F208060) and 3FDF1070 (the call into the truly infinite blink L3FDF1270).
  • The blink that is seen (3F2088B0/3F208950/3F208A80/3F208B20) is inside L3F208660, a finite one-shot blink. The earlier "LED loop" conclusion came from sampling a 900–3000 frame window that ended during the second blink and reading its dominant PC as a wedge.
  • L3FDF1270 is an infinite loop (3fdf1a80: jruc L3FDF13B0), but it is only entered when the status word is nonzero. In the observed DUART path the status word is zero, so L3FDF1010 returns and the runner proceeds.
  • TYPE 'GO' TO CONTINUE is printed, not waited on: nothing reads DUART2, the keyboard, or any input after it, and no input is faked. The runner continues on its own. There is no keypress and no timeout.

What is actually true: a level-1 failure is reported and the runner continues; it is not by itself fatal. A level-1 failure becomes an infinite blink only via L3FDF1010 → L3FDF1270, i.e. when the status word *A13(0xFFA0) is nonzero, which did not happen for these two DUART failures. The runner also never returns to the boot loop (3F204440 never fires), so 3F208060 plays no part in this path at all.

The wall, named

In the steady state (frames 2839 → 20 000) the only DUART accesses are a repeating pair, ~229 CPU cycles apart:

logged PC instruction access value
3FA78E30 3FA78E10 move *a11(0x50), a7, 0 DUART1 ISR, reg 5, firmware bit address 0x06400050 0x0008 (bit 3 = counter/timer ready)
3FA7B5E0 3FA7B5C0 move *a7(0xf0), a7, 0 DUART1 reg 0xF, firmware bit address 0x064000F0 (write-only; reads 0xFF) 0x00FF

(The logged PC trails the memory instruction by one word — TMS34010 prefetch — so the reading instruction is the one quoted.)

  • L3FA78D90 (the DUART1 ISR dispatcher) computes pending = ISR & IMR into RAM 0x270820, tests bit 2, then 6, then 1, later bit 3 at 3FA7B4D0, clears the software bit, and reads reg 0xF. But the hardware ISR bit 3 is set again before the read even retires, and reti re-enters immediately.
  • Cause: the counter is in timer mode — L3FA71CB0 writes ACR = 0x60 (reg 4) once — and CTUR/CTLR (regs 6/7) are never written, so the reload value is 0. MAME's mc68681 re-arms with std::max(CTR, 1) = 1 tick at the 3.6864 MHz DUART clock (271 ns), so the counter-ready interrupt is re-asserted far faster than the ~229-cycle ISR can clear it. The interrupt is therefore permanently pending and the CPU never runs mainline code again.
  • What it waits for: ISR bit 3 (0x08) to be clear, i.e. ISR & IMR == 0. It never is.
  • What is missing: a sane counter/timer period. With the reload left at 0, the model produces the fastest possible tick rather than a long one; the firmware's counter interrupt becomes a storm. This lives in the MAME mc68681 model (devices/machine/mc68681.cpp), which is out of scope here (MAME tree is not owned by this task) — reported, not changed. The local MAME edits (harness/mame/mame_edits/) are unrelated (Rx overrun latch, TxRDY timing); this is upstream max(CTR,1) behaviour.

Next wall

DUART1 counter-ready interrupt storm. CPU is 100 % inside the DUART1 INT1 ISR; it polls DUART1 ISR (reg 5, bit 0x06400050) and always reads 0x08 (counter ready); the counter runs in timer mode (ACR = 0x60) with an unprogrammed reload (CTUR/CTLR = 0), so the interrupt re-arms every ~229 CPU cycles and can never be cleared. Nothing else executes; the screen has been frozen since frame 2839.

Reproduce:

M=/home/arti/code/k-space/mame0289/vt1200
# transcript + milestones (writes debug.log)
VT1200_IO_PATH_STUB=1 CF_FRAMES=20000 $M vt1200 -rompath harness/mame/roms \
  -video none -sound none -nothrottle -debug -debugger none -debuglog \
  -autoboot_script harness/mame/console_frames.lua -autoboot_delay 0
# final-state histogram, plus DUART access log
VT1200_IO_PATH_STUB=1 W_START=5000 W_END=12000 \
  VT1200_DUARTLOG=/tmp/duart.log $M vt1200 -rompath harness/mame/roms \
  -video none -sound none -nothrottle -debug -debugger none \
  -autoboot_script harness/mame/wall_hist.lua -autoboot_delay 0
# frames at increasing times
VT1200_IO_PATH_STUB=1 VT1200_DUMP_FRAMES=500,1627,2800,3500,12000 \
  VT1200_DUMP_DIR=/tmp VT1200_DUMP_VRAM_FRAMES=500,2800,12000 \
  $M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \
  -seconds_to_run 200