Mechanism quantified first, per byte over the 5-byte burst: start latency 2605, 15442,
20652, 20650, 20652 cycles -- i.e. 1 bit for the idle byte 0 only, then constant because
later characters are pinned by the previous character's end. It does NOT accumulate.
Implemented the authorised contained change (write_TX idle path begins the start bit
immediately; new start_now parameter in emu/diserial.{h,cpp}; buffered path and receive
side untouched; SCN2681 16x-clock citations recorded). Result: byte 0's latency 2605 -> 5
cycles, nothing else moved, and the outcome was identical -- same three failing checks,
same returns (L3FA71BC0=0x00900040, L3FA753D0=3, L3FA6E4B0=6), same console line.
DECISION: reverted. The deviation is real but the change does not move the symptom and is
only a partial fidelity fix (a faithful model would clock the transmitter at 16x). Keeping
a change that buys nothing adds noise. MAME tree is back to edit 2 (overrun latch) only
plus the edit-1 refutation note; emu/diserial.{h,cpp} unmodified.
DUART_REPORT.md now carries the full handover: established / ruled out / remaining
discrepancy / open deviations. Acceptance still fails; regression holds.
56 KiB
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 spacedata_width = 1), and a handler on an entry atbasereceives(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:
0x00MR1A/MR2A,0x01SRA/CSRA,0x02CRA,0x03RHRA/THRA,0x05ISR/IMR,0x08MR1B/MR2B,0x09SRB/CSRB,0x0ACRB,0x0DIP/OPCR,0x0FStop/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, bit0x3FA90960) writes the command sequence0xCA,0x2A,0x3A,0x4A,0x5A,0x1Ato operand+0x20(→ off0x02) and the same sequence to operand+0xA0(→ off0x0A).0x02= CRA,0x0A= CRB; the values are coherent 68681 command nibbles (0xCenable Rx,0x2reset Rx,0x3reset Tx,0x4reset errors,0x5reset break-change,0x1reset MR pointer). No otheroffassignment makes those coherent. - It then writes
MR1A = 0x13andMR2A = 0x07to off0x00(MR pointer toggles),CSRA = 0x99to off0x01,MR1B/MR2B = 0x07to off0x08,CSRB = 0x99to off0x09, and the IMR value to off0x05. All match the MAME map. - The host-port test writes
MR1A = 0x12,MR2A = 0x80to off0x00(i.e. local loopback: MR2 bit 7:6 =10),CSRA = 0x88to off0x01, and CRA commands (0x05= enable Tx+Rx,0x0A= disable) to off0x02— then reads off0x01(SRA) and off0x03(RHRA). The monitor switch at0x0640000Dreads asIP(off0x0D), 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=0088for CSRA), which is why the driver passesdata & 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):
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 bits3FA79C00,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 wireirq_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—SCN2681at0x0640000(host/printer) and0x0620000(keyboard/mouse);duart1_r/w,duart2_r/wwithregister = off,data & 0xff; optional external TX→RX loopback; monitor switch onIP4-IP6; all gated onVT1200_DUART*. Doc block updated. AlsoVT1200_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.
ORCHESTRATOR CORRECTION — §0 and §4 conclusions are superseded
Reviewed after delivery. The wiring and the remap in §1–§2 are correct and stand (I re-verified the remap against MAME's source, below). The conclusion in §0/§4 — that the host-port test "needs an external responder" and cannot pass without one — does not stand. Three grounds.
1. The service guide says this test is local loopback
docs/EK-V1000-SV-002_service-guide.txt:
Host Port Test — Verifies the internal data paths of the host port's universal asynchronous receiver transmitter (UART) by using its local loopback mode. If you also choose the external loopback test, this test checks the driver and receiver hardware as well.
External loopback is a separate opt-in selection ("External loopback: host port", bit 0). It is off by default. So the failing test must pass with no external equipment — which is what we independently know from the real terminal booting normally, and from level 0 already passing in this harness.
2. §4's own evidence is the loopback signature — with the transmit missing
§4 already establishes that the firmware sets MR2 = 0x80 (local loopback) and then
expects 5 bytes to have arrived and set overrun (SRA & 0xF0 == 0x10, and
STATUS_OVERRUN_ERROR = 0x10) in a 4-deep FIFO. A 5-byte burst overflowing a 4-deep
FIFO is exactly what transmitting 5 bytes in local loopback produces. And the ROM
table 0xAA,0xFF,0xAA,0x55 is far more plausibly the transmit pattern (a BIST
pattern) than an inbound host stream — no host sends that.
So the open question is not "who supplies the pattern". It is why the firmware never writes THRA.
3. MAME's semantics, verified (this changes what §2/§4 mean)
Read from src/devices/machine/mc68681.cpp:
duart_base_device::read/write(offs_t offset)dooffset &= 0xf— they take a register index 0..0xF directly. Channel A = regs0x0–0x3, channel B =0x8–0xB.- reg
0x3write = THRA, read = RHRA; reg0xBlikewise for channel B. - reg
0xDread = IP (the monitor switch). So §2's remap is right.
- reg
write_CR()takes the command from(data >> 4) & 0x07, and enables the channel fromBIT(data, 0). Consequences that contradict §2/§4's reading:- the init sequence
0xCA,0x2A,0x3A,0x4A,0x5A,0x1Ahas bit 0 = 0 in every value, i.e. every one of them disables; they are not "the command nibbles" in the low nibble sense; - the host-port test's
CRA=0x05is the enable (bit 0 set); the followingCRA=0x0Adisables again.
- the init sequence
recalc_framing()setsset_data_frame(1, (MR1 & 3) + 5, ...). So 5-bit characters requireMR1 & 3 == 0. §4 says the test writesMR1A = 0x12;0x12 & 3 == 2→ 7 bits. But §4 also says the expected received bytes are the pattern masked by0x1F(5 bits). Those two cannot both be true — one is misread, and resolving it tells us what the test is really doing.
4. Ruled out, so not the cause
- The DUARTs are registered after the board-window model (
vt1200.cpp630–651), so they win offs0–0xFand fall through to the board model above that. Not shadowed. - The DUART handlers log through the same
log_pc_access()as the board model (lines 731/744), andduart1_wcovers the whole window (< 0x10→ DUART, else board). So a THRA write would have appeared in theVT1200_BOARD_LOGtranscript too. "No write to06400003" is a real observation, not a logging gap.
5. Leading hypothesis for the missing transmit
The task did not wire m_duart1->irq_cb() to the CPU, and §7 notes both that the ROM
has a DUART ISR (L3FA78D90) and that transmit routines exist at
3FA79C00/3FA7B8E0/3FA7BAA0 but are not reached. MAME's TMS34010 receives
external interrupts through execute_set_input(): line 0 → INTPEND bit
TMS34010_INT1, line 1 → TMS34010_INT2 (tms34010.cpp 817–838), gated by REG_INTENB.
"Transmitter never fed" + "ISR not wired" + "transmit routines unreached" have one
obvious common cause: an ISR-driven transmit whose interrupt is never delivered.
Caveat to check first: an earlier note in this project records that INTENB never
enables, because the level-0 restart enters at 3F208060 with DINT. If the CPU really
runs with interrupts disabled during the tests, an IRQ-driven transmit cannot work
either — establish whether the test enables interrupts before relying on this.
FINAL FINDINGS — root cause found, acceptance NOT met (2026-10-16)
0. Status in one paragraph
Root cause found and fixed: the level-1 HOST PORT test drives its loopback with an
ISR, and the DUART interrupt was never wired to the CPU, so the transmit register
(THRA, reg 3) was never written. Wiring duart1->irq_cb() to the TMS34010's external
interrupt 1 makes THRA get written (0 -> 20 writes per run) and immediately fixes two
of the test's five checks: SRA & 0xF0 == 0x10 (overrun) and the three RHRA pattern
reads now pass. The acceptance line LEVEL1/2 ERROR -> HOST PORT - 15 still
appears. The remaining failure is a timing condition (below), not a wiring or a
modelling-of-the-device error.
Address conventions: Ghidra = 0x07E40000 + file; bit = 0x3F200000 + file*8;
a handler at base receives (bitaddr-base) >> 4. All numbers below are stated in the
convention named.
1. The root cause (verified)
- The firmware's fixed CPU vector table points INT1 (vector
0xffffffc0) and INT2 (0xffffffa0) at RAM trampolines:RLONG(0xffffffc0) = 0x00000F60, and bit0x0F60holdsJA 0x3FA92F40(a 3-word stub the firmware installs at boot). INT2 -> bit0x0F30->JA 0x3FA93070. This resolves the earlier "vector reads 0xF60, which is bogus" confusion: the vectors are fine, they point at RAM stubs. - Those stubs call the DUART ISR
L3FA78D90with(mask, struct, window)=(2, 0x270860, 0x6400000)for INT1. So DUART1's interrupt is expected on INT1. - The ISR is the only code anywhere that writes
THRA: the TxRDYA path at3FA79ED0doeswindow[reg3] = struct[i].0x360, andstruct[i].0x360(ISR base) is the test's pattern arraystruct[i].0x330. INTENBis enabled at the test:VT1200_*run showsINTENBgoing0x0400(display) at f154 and0x0006(INT1|INT2) at f474, i.e. the orchestrator's caveat is cleared — interrupts are on when the host-port test runs.
2. What I changed (kept)
harness/mame/vt1200.cpp:m_duart1->irq_cb()/m_duart2->irq_cb()-> newduart1_irq_w/duart2_irq_w, which callm_maincpu->set_input_line(0/1, ...). Gated onVT1200_DUART(no-env baseline untouched). This is the fix for the missing transmit./home/arti/code/k-space/mame0289/src/devices/machine/mc68681.cpp: one datasheet- backed timing correction, with a revert note inline. MAME assertedTxRDYmid-character ("not until the end of start bit time"); the datasheet says TxRDY is re-asserted at the THR->TSR transfer ("Data is transferred from the holding register to transmit shift register when it is idle or has completed transmission of the previous character. The TxRDY conditions are then asserted again"). Corrected to assert attra_complete(). This took the failure from two failing checks to one. (Caveat: for the idle case the datasheet's transfer is immediate, so this is exact only for the buffered case, which is the test's steady state.)
3. Evidence that the wiring is the fix
VT1200_DUARTLOG (non-deduplicated), 900 frames, before vs after:
before: writes to reg 3 (THRA): 0
after : writes to reg 3 (THRA): 20 (0xAA,0xFF,0xAA,0x55,0x00 ..., one per TxRDYA)
The test's own comparisons, breakpointed:
before SRA&0xF0 = 0x00 (needs 0x10) RHRA = 0x00 (needs 0x0A,0x1F,0x0A)
after SRA&0xF0 = 0x10 PASS RHRA = 0x0A,0x1F,0x0A PASS
So the "external responder" conclusion in §0/§4 is dead: the source of the pattern is the firmware's own DUART ISR, reached only when the DUART IRQ is wired.
4. The remaining failure — measured, with numbers
The host-port test's L3FA753D0 pass additionally snapshots SRA at every TxRDY
interrupt and stores it into the descriptor as struct[index].0x3b0 (ISR base), where
index = number of THRA writes so far. The test then requires:
struct[4].0x3b0(read as the test'sstruct+0x3c0) to have bit 1 = FFULL, andstruct[5].0x3b0(teststruct+0x3d0) to have bit 4 = OVERRUN.
i.e. the loopback receiver must be in lockstep with the transmitter: after k bytes are transmitted, k bytes must have been received.
Measured (cycles of the TMS34010, total_cycles()), final pass:
TxRDY IRQ T_k = 44592754, 44611168, 44629580, 44647992, 44666403, 44684815 (spacing 18414)
THRA write W_k = T_k + 185
RX start-bit sync = W_k + 2605 (= 1 bit time; bit = 2604)
RX rcv_complete = sync + 16926 (= 6.5 bit times, the bit-centre samples)
= W_k + 19531
So the received character is delivered 19531 cycles after its THRA write, while the
test's next snapshot is 18414 cycles later. The receiver is 1117 cycles (~0.43 bit)
behind per character, so at index5 the FIFO holds 3, not the required 4, and OVERRUN
is not yet set. Concretely: flags = 0x00000001 (only the struct[5] OVR check fails),
SRA snapshots = 0x0C,0x0C,0x0D,0x0D,0x0F,0x0F.
To pass, the delivery must be < 18229 cycles after the write.
5. MAME-vs-datasheet audit of the remaining margin
I checked each candidate against the SCC68681 datasheet. Do not tune these — one is a genuine bug, the others are faithful.
- FIFO depth / overrun threshold — MAME is FAITHFUL. The owner's candidate
(
rx_fifo_pushoverruns atSIZE+1 == 4) is correct: the datasheet says the FIFO is three characters, a 4th character is held in the receive shift register, and overrun occurs only on the 5th ("If the FIFO is full when a new character is received, that character is held in the receive shift register ... If an additional character is received while this state exists ... the overrun error status bit (SR[4]) will be set"). MAME'sSIZE+1guard models exactly that. Not the bug. - TxRDY timing — MAME was WRONG (fixed, §2). Removed one failing check.
- Overrun timing — MAME is WRONG, and this is the remaining margin. The datasheet
says SR[4] is set "upon receipt of the start bit of the new (overrunning)
character" — i.e. at the start of the 5th character (~
W+1.5bits), not at its completion. MAME sets it inrx_fifo_push(), i.e. at the 5th character's completion (W+7.5bits) — about a whole character late. Setting it at the overrunning character's start bit would moveSRA's OVR bit intostruct[5]'s snapshot with ~15k cycles of margin and should make the test pass. This is the concrete next step; it was not implemented here because it needs a "new character started" hook in the receive path (genericdiserial.cpp, or a DUART override), which I could not land and validate within budget without risking a green-on-a-guess. - Loopback receive clock — MAME deviates, but is not the immediate blocker. The
datasheet's local-loopback mode says "the transmit clock is used for the receiver";
MAME clocks the loopback receiver from the independent receive clock
(
set_rcv_rate+ the generic RX sampling, with a deliberate half-cycle start guard). This is the source of the 0.5-bit phase; I tried removing the guard and the received bytes became wrong (0x14,0x1E,0x14instead of0x0A,0x1F,0x0A), confirming the guard is what puts the samples on the bit centres. Do not remove it.
6. Acceptance / regression
- Acceptance: NOT MET. With
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,cap.luastill prints'LEVEL1/2 ERROR -> HOST PORT - 15 '. No next gate is reached. - Regression: HELD. With no
VT1200_*env vars,progress.luaat 200 frames:PC=3FDCE400 samples=199.
7. Artifacts
harness/mame/vt1200.cpp— IRQ wiring +VT1200_STRUCTLOG/VT1200_IRQLOG(transparent logging proxies) and a cycle stamp onVT1200_DUARTLOG.harness/mame/hp_trace.lua,hp_probe.lua,hp_probe2.lua,hp_snap.lua,hp_int.lua,hp_ret.lua,hp_dump.lua,hp_dump2.lua— the measurement scripts.harness/mame/hp_*.log,hp_*.out— the raw evidence quoted above.- MAME tree:
mc68681.cpp(TxRDY timing, revert note inline).diserial.cppleft unmodified.
8. UPDATE — overrun-latch timing fixed; acceptance still not met
Item 3 of §5 was implemented and is the second datasheet-backed MAME correction:
mc68681.h/mc68681.cpp: addedduart_channel::rx_w()(and routedrx_a_w/rx_b_wthrough it). When the RxD line falls (a start bit) whilerx_fifo_num == MC68681_RX_FIFO_SIZE + 1(FIFO full and the previous character still held), it latchesSTATUS_OVERRUN_ERROR. This is the datasheet's timing ("the overrun error status bit (SR[4]) will be set upon receipt of the start bit of the new (overrunning) character") instead ofrx_fifo_push()'s latch-at-completion. Revert note inline.
Effect on the test's own comparisons (the struct[4]/struct[5] SRA snapshots):
before RX-3c0 = 0x70005 (FFULL clear) RX-3d0 = 0x0007 (OVR clear) flags = 0x00000001
after RX-3c0 = 0x1F000F (FFULL set) RX-3d0 = 0x001F (OVR set) flags = 0x00000000
i.e. every check in L3FA753D0 (RXCHECK) now passes, and it proceeds to test the
next parameter combination — which it never did before. This confirms §5.3 was a
real MAME inaccuracy, not a guess.
The acceptance is still not met, and I did not resolve the next failure. The
host-port test's first sub-test L3FA71BC0 now returns 6, and the caller
(3FA84FE0) turns any non-zero return into the reported error. L3FA71BC0's own
check sites (3FA73500/3FA73580/3FA73610, which set its flag bits) were
breakpointed and never fired in 700 frames, so its return value does not come
from the path I read statically; that is the loose end. L3FA71BC0 is the DUART
baud/timer sub-test that runs before RXCHECK, so it is a separate condition from
the loopback data path.
Final state of the acceptance instrument (900 frames):
'LEVEL1/2 TESTING ** '
'LEVEL1/2 ERROR -> HOST PORT - 15 ' <-- unchanged
regression (no VT1200_* env): PC=3FDCE400 samples=199 HOLD
CAUSALITY AUDIT (four-config A/B) — the failure is PRE-EXISTING
Reviewed after the owner flagged that "the failure moved backwards" and that my two
MAME edits are both timing changes while the newly-blamed sub-test is a timer test.
Method: four configurations of the MAME tree (IRQ wiring kept in all four — without it
the test fails even earlier), built with harness/mame/hp_ab.sh <cfg>:
| cfg | IRQ | TxRDY edit | overrun edit | console | L3FA6E4B0 return |
|---|---|---|---|---|---|
| 1 | on | on | on | HOST PORT - 15 |
6 |
| 2 | on | reverted | on | HOST PORT - 15 |
6 |
| 3 | on | on | reverted | HOST PORT - 15 |
6 |
| 4 | on | reverted | reverted | HOST PORT - 15 |
6 |
Verdict: PRE-EXISTING. The console line and the caller's return are identical in all
four configurations, including cfg 4 where both MAME edits are reverted to stock. Neither
mc68681 edit causes or relocates the failure; they only change RXCHECK's own flag
word (Section UPDATE). Variant files are kept in harness/mame/mame_edits/ (*.cfg1,
*.cfg2, *.cfg3, *.upstream).
Methodology correction (important)
My earlier hp_ret.lua attached labels to the wrong rets addresses, so the return
values I quoted were mis-attributed. Verified against the disassembly, the correct
rets sites and their owning functions (prologue register mask) are:
rets |
function | return |
|---|---|---|
3FA71BB0 |
L3FA6E4B0 (the generic port-test routine) |
6 |
3FA753C0 |
L3FA71BC0 (baud/timer sub-test) |
0x00900040 |
3FA767D0 |
L3FA753D0 (RXCHECK sub-test) |
0x00000408 |
3FA77800 |
L3FA767E0 (XMITPASS sub-test) |
0 |
harness/mame/hp_ret2.lua uses the corrected addresses. So the owner's premise "6 comes
from L3FA71BC0" is off: L3FA71BC0 returns 0x00900040; the 6 is
L3FA6E4B0's own return.
What 6 actually is — not a failure code
L3FA6E4B0 (the routine the level-1 host-port caller invokes) ends its body by reducing
five descriptor fields to a 5-bit value (3FA718D0–3FA71B50) and returning it:
bit0 <- (struct+0x60 != 0) bit3 <- (struct+0xc0 != 0)
bit1 <- (struct+0x80 != 0) bit4 <- (struct+0xe0 != 0)
bit2 <- (struct+0xa0 != 0)
with struct+0x80/struct+0xa0/struct+0xc0/struct+0xe0 set from the four
sub-test calls (L3FA71BC0, L3FA753D0 x2 each). So:
6=0b00110— a bitmap of which sub-tests returned nonzero, not a device status code:L3FA71BC0non-zero andL3FA753D0non-zero;L3FA767E0returned 0, and the second-trio fields were never written (the caller aborts after the first trio).- The host-port caller (
3FA85000) ismove a8,a8 ; jreq <continue>: any nonzero value takes the error path, which returns0x400(3FA853A0).0x400is what the status dispatcher turns intoLEVEL1/2 ERROR -> HOST PORT - 15.
Unresolved, and flagged: the polarity is suspicious. L3FA753D0 (RXCHECK) returns
0x408 while all four of its own checks pass (flag word 0 — see UPDATE), so "sub-test
returned nonzero" is not the same as "sub-test failed its checks". Either its success
path leaves a stale a8, or the five fields are pass flags and the caller's test is
inverted relative to my reading. I could not settle this, so 6 should not be
treated as a plain failure code until it is.
The two leads
- Counter spuriously active — checked, not the cause.
ISRbit0x08(COUNTER_READY) is set from frame 1, but MAME sets it in the Start Counter Command read path (mc68681.cppcase 0x0e: start_ct(); set_ISR_bits(INT_COUNTER_READY)), and the firmware itself performs that read at boot (R 0640000E pc=3F200150). It is not masked into the IRQ (IMR= 7 = TxRDYA|RxRDYA|DeltaBreakA, no bit 3), and the DUART ISRL3FA78D90does not handle ISR bit 3, so it cannot disturb the test.ACRreads0x0000except when the sub-test writes0x60. - DUART clock — unverified. The service guide has no crystal/baud figure for the
DUART (
grep -i '3.6864|crystal|oscillator' docs/EK-V1000-SV-002_service-guide.txt→ nothing; its only "baud" hit is test 16 "Test all baud rates", a level-2+ test). 3.6864 MHz is the 68681 datasheet's nominal and remains the only evidence; I could not confirm it against the guide.
Acceptance / regression (cfg 1, committed state)
- Console:
LEVEL1/2 TESTING **thenLEVEL1/2 ERROR -> HOST PORT - 15— unchanged. - Regression (no
VT1200_*):PC=3FDCE400 samples=199— holds.
CORRECTION — edit 1 (TxRDY) was REFUTED and has been REVERTED
An independent worker read the fitted part's datasheet and checked both citations
(reference/mc68681-datasheet-verification.md, commit 031d30da67dd).
- Edit 1 (assert TxRDY at
tra_complete()/ the character boundary) — REFUTED. Signetics SCN2681 p.22, "Transmitter Disable Note": "TxRDY sets at the end of the 'start bit' time. It is during the start bit that the data in the transmit holding register is transferred to the transmit shift register." That is exactly what upstreamtra_callback()implemented. The p.7 sentence the edit cited is real but says nothing about when TxRDY is set. So upstream was faithful and the edit was a datasheet-contradicting change that happened to line up one snapshot. It has been reverted (the note inmc68681.cppnow records the refutation and says not to re-apply it). The claim in commit9902722165fc— "fix two DUART timing deviations" — is therefore half false; only edit 2 stands. - Edit 2 (latch overrun at the start bit) — CONFIRMED, quote and application. Kept.
- FIFO depth faithful — CONFIRMED. (My refutation of the owner's FIFO candidate also stands.)
Did reverting edit 1 relocate the failure to L3FA71BC0? No.
Config 2 = edit 1 reverted, edit 2 kept:
L3FA71BC0 (rets 3FA753C0) = 0x00900040
L3FA753D0 (rets 3FA767D0) = 0x00000003 (was 0x00000408 with edit 1)
L3FA767E0 (rets 3FA77800) = 0
L3FA6E4B0 (rets 3FA71BB0) = 6 (unchanged)
console = 'LEVEL1/2 ERROR -> HOST PORT - 15' (unchanged)
Neither the console nor the caller's value changes; the failure did not relocate. It was
never "in L3FA71BC0" — that came from the wrong-rets attribution corrected above.
The "ISR one byte ahead of the receiver" symptom — explained
With faithful TxRDY (config 2), the last pass measures (cycles, total_cycles()):
THRA writes 44592939, 44598330, 44611350, 44629580, 44647808
=> inter-write gap 5391, 13020, 18230, 18228 (char = 18226)
SRA at each TxRDY 0C, 04, 04, 05, 05, 07
=> index4 is NOT FFULL, index5 is NOT OVR
Cause: TxRDY re-asserts at the end of the start bit (faithful), so the ISR writes the next byte ~1 bit into the current character — i.e. the transmitter is ~1 byte ahead of the character boundary — while the loopback receiver only completes a character a full character after it was written. So the received count lags the transmit count, and the test's per-index snapshots (which expect lockstep) miss.
The reason the receiver lags is a real, independently-confirmed MAME deviation: in
local loopback the datasheet says (SCN2681 printed p.12, MR2A[7:6] = 10, item 2) "The
transmit clock is used for the receiver." MAME instead clocks the loopback receiver from
the independent receive clock (set_rcv_rate + the generic m_rcv_clock sampling
with its half-cycle start guard). The confirmation is already recorded in
reference/mc68681-datasheet-verification.md ("Incidental findings"): "MAME clocks the
loopback receiver from the independent receive clock — directionally correct against the
datasheet."
So the story closes: edit 1 masked the loopback-clock deviation by dragging the transmitter back to the receiver; with faithful timing the deviation is exposed. Edit 1 must not be restored. The next fix is to clock the loopback receiver from the transmit clock (SCN2681 p.12), which per the project's process needs the quoted sentence read independently before it is written.
The other step-5 leads
- DUART crystal.
reference/duart-datasheet/SCN2681-transputer.txtgives fCLK nominal 3.6864 MHz ("the crystal oscillator operates directly from a 3.6864MHz clock"; Table 3 is specified "Crystal or Clock = 3.6864MHz"), andreference/keyboard-mouse/...also shows a 3.6864 MHz part. That is the datasheet's nominal; the VT1200 board crystal itself is still unconfirmed (no schematic/guide figure). The driver'sXTAL(3'686'400)therefore remains an assumption, but a well-founded one. - CSRA-derived rates.
CSRA = 0x88->calc_baud(ch, false, 8)andcalc_baud(ch, true, 8). Induart_base_device::calc_baudtherxargument is unused and both nibbles index the samebaud_rate_ACR_*table, so TX and RX are both 2400 baud — equal. Not a mismatch. (Measured bit time ~415 us at 2400 baud confirms it.) - Character frame.
MR1A = 0x10->recalc_framinguses(MR1 & 3) + 5= 5 bits, no parity, 1 stop; the test's pattern arithmetic masks with0x1F. Consistent. - Counter lead — cleared.
ISRbit 0x08 is set by MAME's Start Counter Command read path (case 0x0e), and the firmware performs that read itself at boot. It is not inIMR(7) andL3FA78D90does not handle ISR bit 3, so it cannot disturb the test. - INT1 vs INT2 — immaterial. The two vector stubs differ only in their 3rd argument
(2 / 4), and
L3FA78D90never reads that argument (only*a13(0xffc0)/0xffe0).OPCRandOPRare written 0 on every transmit, so the discrete interrupt outputs (OP4/OP6) are not in use — the chip's single INTRN is the path, and the INT1 wiring is correct.
Acceptance / regression (current tree: edit 1 reverted, edit 2 kept)
- Console:
'LEVEL1/2 TESTING ** 'then'LEVEL1/2 ERROR -> HOST PORT - 15 '— still present; no next gate. - Regression (no
VT1200_*):PC=3FDCE400 samples=199— holds.
WHAT MAKES THE CALLER RAISE 0x400 — resolved empirically
The owner's gating worry (if the caller does not actually turn a nonzero L3FA6E4B0
return into 0x400, then fixing the sub-tests' checks cannot clear HOST PORT - 15)
is resolved. Breakpoints at the call site and at the handler, one run
(harness/mame/hp_disp.lua):
DISPATCH-READ code=0000FFFF <- 'LEVEL1/2 TESTING **'
CALL-L3FA6E4B0 <- call site 3FA84FB0
AFTER-CALL a8=00000006 <- call site 3FA84FE0 stores a8
CALLER-ERR-RET a8=00000006 a11=00000400 <- error path sets a11=0x400, returns it
DISPATCH-READ code=00000400
HOSTPORT-HANDLER code=00000400 <- handler 3FDE9D70 prints HOST PORT - 15
So the chain is confirmed at the call site, not from a register snapshot:
3FA84FB0 calla L3FA6E4B0;3FA84FE0 move a8, *a13(0x160)(the return);3FA85000 move a8,a8 ; 3FA85010 jreq L3FA853D0— any nonzero takes the error path; that path ends at3FA85360 movi 0x400,a11 ; 3FA85380 *a7(0x40)=a11 ; 3FA853A0 a8=a11.0x400is the HOST PORT dispatch code (measured live at3FDE9D70). My earlier static reading of the dispatcher'ssubichain suggested0x3F8; dis34'ssubirendering is misleading here and the live value is authoritative.
The quantity to zero is L3FA6E4B0's return value: it must be 0, i.e. all five
descriptor fields must be 0.
Polarity — resolved, and my earlier caveat was wrong
L3FA71BC0 returns *a13(0xc0) (its 32-bit flag word) and L3FA753D0 returns the same
word (3FA76550) or a9, which is set to that word at 3FA76440 and is untouched by the
success loop — so on success both return 0. Therefore L3FA6E4B0's bits are
failure bits, and "nonzero" does mean "this sub-test failed its checks".
The earlier "L3FA753D0 returns 0x408 while all its checks pass" was a misread: the
32-bit flag word is not the four loop flags alone — 3FA76480–3FA76520 also write a
4-bit field at a13(0xc4) and 2-bit fields at a13(0xc8/ca/cc), which are inside the
same 32-bit read. So 0x408 is a genuine failure, not a false positive. This also
removes the concern that the checks are the wrong quantity: they are the right one.
Is the local-loopback clock deviation the cause? No.
Mechanism, stated explicitly (the owner's requirement): CSRA = 0x88 selects the same
calc_baud value for TX and RX (the rx argument is unused in duart_base_device), so
the two clocks are the same rate (2400 baud). Clocking the loopback receiver from the
transmit clock therefore changes only the sampling phase/edges, not the rate — and it
does not change how many bit-times the receiver takes (6.5) nor when it finishes.
Measured, it is not even observable: the received bytes are correct
(0x0A,0x1F,0x0A = the pattern masked by 0x1F), so MAME already samples the TX bit
centres; and the completion is sync + 6.5 bits either way. Verdict: a real deviation
(SCN2681 p.12 item 2) but NOT the cause of the residual, and it cannot move the symptom
— so I did not change it rather than bank a green.
The measured residual is a ~1-bit write->receive offset: RX completion = W + 1.07 char while the write interval is 1.00 char, so the received byte misses the next
snapshot by 0.5 bit. Its source is the ~1 bit between a THRA write and the first
transmitted bit (sync = W + 2605, bit = 2604) — i.e. the transmit clock's first edge
is at 2*m_tra_rate, not m_tra_rate. I tried forcing it to m_tra_rate and to zero;
the first did nothing and the second corrupted the received bytes (sampling moved to
the bit boundaries), so the half-bit TX offset is load-bearing for the sampling phase.
Not banked; recorded as the open item.
The datasheet items checked against our model
- item 3 (TxDA held High) and item 4 (RxDA ignored): modelled —
tra_callback()does not drive the TX pin in loopback and feeds the bit torx_winstead; the external RxD input is unused in this mode. - item 5 (transmitter must be enabled, receiver need not be): matches —
rcv_complete()pushes whenrx_enabled || loopback, so the FIFO keeps filling with the receiver disabled, which is what the firmware'sCRA=0x05thenCRA=0x0A(bit1 = disable Rx, bit3 = disable Tx) sequence relies on.write_CR(0x0A)also disables the transmitter, and MAME lets the in-flight character finish (the tra timer is already running), so the 5-byte burst completes. - One minor deviation, not biting here:
write_CR's bit1 handler clearsRxRDY, whereas the datasheet says a receiver disable preserves the FIFO and its status.read_rx_fifore-derivesRxRDY, and the test's SRA check looks only at the high nibble (OVR), so it does not affect this test.
Acceptance / regression (current tree: edit 1 reverted, edit 2 kept)
- Console:
HOST PORT - 15— still present; no next gate. - Regression:
PC=3FDCE400 samples=199— holds.
ENUMERATION — the exact remaining failing checks (measurement only)
Broken on every flag-set site in both sub-tests (hp_flags.lua) and on the three
comparisons (hp_vals.lua), current tree (edit 1 reverted, edit 2 kept). Of the 18
flag-set sites in L3FA71BC0 and 4 in L3FA753D0, exactly three fire; every other
check in both sub-tests passes:
| sub-test | flag | comparison PC | quantity / expected | actual |
|---|---|---|---|---|
L3FA71BC0 |
c6 |
3FA73970 btst 5,a9 |
bit5 of struct[index].0x60 = 1 |
0x0001, 0x0009 -> bit5 = 0 (3 passes; 0x0021 passes) |
L3FA753D0 |
c1 |
3FA760E0 btst 1,a11 |
bit1 (FFULL) of struct+0x3c0 (index4 SRA snapshot) = 1 |
0x05 -> FFULL = 0 |
L3FA753D0 |
c0 |
3FA76050 btst 4,a11 |
bit4 (OVR) of struct+0x3d0 (index5 SRA snapshot) = 1 |
0x07 -> OVR = 0 |
Note: cE (3FA75060) also fires once but it is a summary (it copies *a13(0x40) when
any earlier flag is set), not an independent check. So L3FA71BC0 has one failing
condition and L3FA753D0 has two — the two status-snapshot checks. The direct
SRA/RHRA reads in L3FA753D0 pass (flags c2/c3 do not fire).
Delta at the two snapshot checks: at index4 the FIFO holds 1-2 (needs 3 for FFULL) and at index5 it holds 3 (needs 4 for OVR) — i.e. the receive count is one to two bytes behind the transmit count at the comparison instants.
TASK 2 — the transmit-start citation (for independent reading)
Question: when does the fitted part begin transmitting after a character is loaded into
the THR of an idle transmitter? The two sentences that bear on it
(reference/duart-datasheet/SCN2681-transputer.txt):
- p.7, "Transmitter Operation":
"Data is transferred from the holding register to transmit shift register when it is idle or has completed transmission of the previous character. The TxRDY conditions are then asserted again which means one full character time of buffering is provided."
- p.22, "Transmitter Disable Note":
"TxRDY sets at the end of the 'start bit' time. It is during the start bit that the data in the transmit holding register is transferred to the transmit shift register."
Reading: "when it is idle" implies no waiting for a character boundary for an idle
transmitter (the transfer is immediate), and p.22 places the transfer inside the start
bit — so the start bit must already be running when the transfer happens, i.e. the first
bit begins at (or within a fraction of a bit of) the write. Neither sentence states a
bit-count latency. MAME measurably inserts ~1 bit: measured RX sync = THRA write + 2605 cycles with a 2604-cycle bit (hp_duart_cfg2.log / hp_irq_cfg2.log). So the
candidate deviation is "MAME delays the first transmitted bit by ~1 bit", justified by
p.7's "when it is idle" — but that sentence does not by itself pin the number, so this
needs the independent read before it is written, exactly like the last two.
FULL PICTURE (bound reached — stopping here)
Established.
- The quantity to zero is
L3FA6E4B0's return (all five descriptor fields = 0); verified at the call site and at the handler (0x400). - The IRQ wiring is required and correct (THRA 0 -> 20 writes/run); the direct SRA/RHRA
checks pass; the overrun-latch edit (edit 2) is confirmed and clears the
RXCHECKflags it targets. - The exact three failing checks and their comparison PCs (table above).
Ruled out. external responder; FIFO depth (faithful); loopback-clock deviation as the
cause (phase-only, received bytes correct); the counter/COUNTER_READY (not in IMR, not
handled); INT1-vs-INT2 and the OP interrupt outputs (L3FA78D90 ignores its mask arg;
OPCR/OPR cleared); character frame width (MR1A=0x10 -> 5 bits matches the 0x1F mask);
CSRA rate mismatch (TX == RX == 2400).
Remaining discrepancy. A ~0.5-1 bit transmit-start latency (above), which at the
snapshot boundaries shows up as a 1-2 byte receive lag. Measured numbers: THRA writes at
t, t+0.3, t+1.0, t+2.0, t+3.0 char; RX completion at write + 1.07 char; snapshots at
the write instants.
Open MAME deviations. (1) transmit-start latency ~1 bit — candidate, citation not
crisp, not written; (2) loopback receive clock, SCN2681 p.12 item 2 — real, not the
cause, not written; (3) write_CR bit1 clears RxRDY where the datasheet preserves FIFO
status — minor, not biting; (4) COUNTER_READY set at the Start-Counter read — not biting;
(5) the pre-existing tms34010 REFCNT local edit.
Acceptance: HOST PORT - 15 still present. Regression: holds
(PC=3FDCE400 samples=199).
WHO WRITES +0x3c0 / +0x3d0, AND WHAT L3FA71BC0 ACTUALLY CHECKS
Direct writer evidence from the transparent descriptor-area proxy
(VT1200_STRUCTLOG, hp_struct.log), not inference:
W 270C50 pc=3FA79E50 val=0004 / 0005 <- test +0x3c0 = ISR struct[idx4].0x3b0 (SRA snapshot)
W 270C60 pc=3FA7A3C0 val=0004 / 0007 <- test +0x3d0 = ISR index5 SRA snapshot
W 2708F0 pc=3FA796D0 val=0001 / 0021 <- test struct[i].0x60 = ISR struct[i].0x90 (SRA snapshot)
All three are written by the DUART ISR L3FA78D90, and only there:
3FA79E30/3FA7A3A0are its TxRDYA path (*a7(0x3b0) = window reg1) — the per-index SRA snapshot. So test+0x3c0= the snapshot at index 4 and+0x3d0= the snapshot at index 5 (the test's descriptor is the ISR's descriptor +0x30, so test+0x3cN== ISRstruct[idxN].0x3b0). Bit 1 = FFULL, bit 4 = OVR.3FA79690is its RxRDYA path (*a11(0x90) = window reg1) — the received status for each character. Teststruct[i].0x60is exactly that field, which is why thec6comparison sees0x0001/0x0021.
So both sub-tests' failures are the ISR's SRA snapshots, and nothing else.
What L3FA71BC0 checks (my own evidence)
Broken on all 18 of its flag-set sites (hp_flags.lua): exactly one fires — c6.
The comparison is 3FA73970 btst 5, a9 with a9 = struct[i].0x60 (the ISR's received
status for character i), and jreq skips the set — so c6 is set when bit 5 is SET.
SRA bit 5 is PARITY ERROR. Observed: a9 = 0x0001 (no error) in most combinations,
and a9 = 0x0021 (= RxRDY | parity error) in 3 combinations, which are the 3 times
c6 fires.
So:
L3FA71BC0's remaining failure = a PARITY ERROR in the received status, in 3 of its combinations (the parity-enabledMR1settings) — a status mismatch, not a timing one. Itsc5check (bit 6 = framing error) does not fire.L3FA753D0's remaining failure = the ISR's index4/index5 snapshots lack FFULL / OVR — a receive-count lag.
These are two different mismatches (one status, one count), both read from SRA by the ISR. The single mechanism that could produce both is a receive-side mis-sampling/offset: a sub-bit offset shifts character completion (-> FFULL/OVR missing) and can flip the parity bit (-> parity error) while leaving the data bits correct (-> the RHRA compares still pass). That keeps the measured ~1-bit write->receive offset (§ earlier) as the leading candidate, but the parity error is a new, testable consequence of it and should be checked directly before attributing.
Owner's static reading of L3FA753D0 — verified, with corrections
Correct on all substantive points (the four checks, the 144 = 4x3x3x4 nested sweep, the
CRA sequence, the per-check flags, and the index record at +0xc4/c8/ca/cc on failure).
Two corrections:
- The PCs quoted are the loads (
3FA76030,3FA760C0,3FA76190,3FA76340); the actual comparisons are3FA76050(btst 4),3FA760E0(btst 1),3FA761E0(andi 0xf0 / subk 16) and3FA76390(cmp). - Flag bit encoding confirmed: each flag is a 1-bit field at bit address
a13 + 0xc0 + N, so the 32-bit return read gives bit N = flag+0xcN. Proof: the+0xc6store shows up in the proxy asW 001E30 pc=3FA739D0 val=0040— bit 6 of the word ata13(0xc0). Caveat: the 32-bit read also spans the word ata13+0xd0(bits 16-31). ForL3FA71BC0the return0x00900040has bits 20/23 set from that word, i.e. the return carries adjacent frame data as well as flags — so "return == 0" is stricter than "all checks pass" unless that word is zero. Worth confirming before treatingL3FA71BC0's return as a pure flag word.
LAST AUTHORISED TIMING CHANGE — tested, did not move the symptom, reverted
Mechanism quantified first (as required)
Per-byte, same 5-byte burst, current tree (hp_lat.log / hp_lat.out). Character start
= first transmitted bit (VTCHSTART); THRA write = VT1200_DUARTLOG:
| byte | THRA write | char start | start latency |
|---|---|---|---|
| 0 | 44592939 | 44595544 | 2605 |
| 1 | 44598330 | 44613772 | 15442 |
| 2 | 44611350 | 44632002 | 20652 |
| 3 | 44629580 | 44650230 | 20650 |
| 4 | 44647808 | 44668460 | 20652 |
So the transmit-start latency does not accumulate: it is 1 bit for the idle byte 0 only, then constant (~1.13 char) because every later character is pinned by the previous character's end (the buffered path). The owner's magnitude argument therefore predicted the deviation could not be the cause, and this measurement confirms it.
The change, tested
Implemented as authorised, contained to the idle path: write_TX's idle branch calls
transmit_register_setup(data, true), which schedules the first bit immediately instead of
at the next 1x tick (new start_now parameter in emu/diserial.{h,cpp}); the buffered
path and the whole receive side untouched. Citations recorded in the edit: SCN2681
"The clock outputs from the BRG are at 16X the actual baud rate", the 1x/16x external clock
note, CSRA = receiver/transmitter clock select, and the receiver's "each 16X clock for
7-1/2 clocks" sampling note.
Result (hp_lat2.log, hp_lat2.out): byte 0's start latency went 2605 -> 5 cycles, and
nothing else changed — bytes 1-4 stayed 15442/20652/20650/20652, the receive completions
moved with the writes, and the outcome was identical:
flag-set sites that fire: BC0/c6 x3, BC0/cE (summary) x1, RX/c0 x1, RX/c1 x1 (unchanged)
returns: L3FA71BC0 = 0x00900040, L3FA753D0 = 3, L3FA767E0 = 0, L3FA6E4B0 = 6
console: 'LEVEL1/2 ERROR -> HOST PORT - 15' (unchanged)
Decision: REVERTED (not kept)
The deviation is real and datasheet-backed, but the change does not move the symptom,
and it is only a partial fidelity fix — a fully faithful model would clock the transmitter
at 16x (the real part), which is a much larger change, and the buffered path would still
quantise to a bit. Keeping a partial change that buys nothing adds noise to the tree, so it
is reverted; the deviation and its citations are recorded here instead. The MAME tree is
back to: edit 2 (overrun latch) only, plus the inline refutation note for edit 1.
emu/diserial.{h,cpp} are unmodified.
HANDOVER — full picture
Established.
- Root cause of the original missing transmit: the DUART IRQ was unwired; wiring DUART1 -> INT1 makes THRA get written (0 -> 20/run) and fixes the direct SRA/RHRA checks.
- The quantity to zero is
L3FA6E4B0's return, verified at the call site and at the handler (0x400-> HOST PORT). - Exactly three checks still fail, all reading the DUART ISR's own SRA snapshots
(writers confirmed by proxy: ISR TxRDYA path
3FA79E30/3FA7A3A0for+0x3c0/+0x3d0, ISR RxRDYA path3FA79690forstruct[i].0x60):L3FA71BC0c6(3FA73970): the ISR's received status has SRA bit 5 = PARITY ERROR in 3 combinations (0x0021), clean (0x0001) otherwise.L3FA753D0c1/c0(3FA760E0/3FA76050): the index4/index5 snapshots lack FFULL/OVR — a receive-count lag at the snapshot boundary.
- The direct loopback data path is correct (RHRA compares pass; received bytes
0x0A,0x1F,0x0Aas expected).
Ruled out. external responder; FIFO depth (faithful); loopback-clock deviation as a cause; the counter/COUNTER_READY lead; INT1-vs-INT2 and the OP interrupt outputs; frame width; CSRA rate mismatch; and now the transmit-start latency (real, but non-accumulating and provably not the cause).
Remaining discrepancy. The ISR's SRA snapshots differ from the firmware's expectation in two ways: a ~1-byte receive-count lag, and a parity error in 3 parity-enabled combinations. Both are sub-bit phase/sampling artifacts, not data-path failures.
Open MAME deviations. (1) transmit-start 16x quantisation — real, tested, not the cause;
(2) loopback receive clock (SCN2681 p.12 item 2) — real, phase-only, not the cause;
(3) write_CR bit1 clears RxRDY where the datasheet preserves FIFO status — minor;
(4) COUNTER_READY set at the Start-Counter read — not biting; (5) pre-existing tms34010
REFCNT local edit.
Acceptance: LEVEL1/2 ERROR -> HOST PORT - 15 — still present; no next gate.
Regression: holds (PC=3FDCE400 samples=199).
Suggested next step (not this gate). The renderer is independent of this gate; the remaining host-port discrepancy is a sub-bit phase artifact in the DUART ISR's SRA snapshots and would need either a 16x-clock transmit model or a receive-phase rework to chase further.