Files
vt1200/harness/mame/LANCE_LOOPBACK_REPORT.md
arti b6da987aae harness: dump the LANCE DMA; the C - LANCE TEST's failing frame carries no FCS by design
The failing loopback case is a negative test: its TX buffer deliberately has
no FCS, and the test expects a CRC error.  The DTCR=1 case that does carry an
FCS is delivered intact and passes, so the DMA is not dropping four bytes.
The sole remaining mismatch is one bit: the test wants RMD1=0x6B00
(ERR|FRAM|CRC|STP|ENP) and MAME's am79c90 reports 0x4B00 -- it never sets
RMD1_FRAM.  No MAME-tree change made; reported for a second reader.
2026-10-16 17:12:02 +03:00

7.5 KiB

C — LANCE TEST: the transmit buffer, the FCS, and the remaining FRAM bit

Date: 2026-10-16. Firmware vt1200_v22.bin md5 b46a3e442ccb86134d2356bd70d22fd2 (unmodified — everything below is observed at runtime; no ROM byte was changed, and nothing in the MAME tree was changed). Follows reference/lance-loopback-fcs.md (the FCS condition is settled and MAME is right on it).

Verdict (short)

  1. The firmware does NOT append an FCS to the frame that fails. It is deliberate, not a DMA truncation. The C — LANCE TEST runs four internal loopback cases; the failing one is a negative test that is supposed to send a frame with no FCS and supposed to see a CRC error.
  2. The DMA path is correct. The chip reads exactly the bytes the CPU wrote, at the descriptor's address and the descriptor's length; there is no 4-byte discrepancy. RCV descr 1 = 0x4B00 is the chip's honest CRC error on that deliberately-unterminated frame.
  3. The only remaining mismatch is one bit. The test expects RMD1 = 0x6B00 (ERR|FRAM|CRC|STP|ENP); MAME reports 0x4B00 (ERR|CRC|STP|ENP). MAME's am79c90 never sets RMD1_FRAM. That single bit is the sole blocker, and it is a chip-model question, not a DMA question.
  4. Per both datasheets FRAM is not valid in internal loopback mode, so MAME's omission matches the book — the firmware's expectation does not. I did not edit the MAME tree.

How the transmit buffer was read (new knob)

VT1200_LANCE_DMA_LOG=<path> logs every LANCE bus-master DMA word access with the LANCE byte address, the value, the byte lanes and the PC:

# kind byteaddr value mask pc cyc t -- every LANCE bus-master DMA word access
# 'r' = chip read (dma_in), 'w' = chip write (dma_out)
r 00FF60 0044 FFFF pc=3FDDCDC0 ...   <- INIT block word 0: mode = 0x0044
...
r 00FF24 FFE6 FFFF pc=3FDCE400 ...   <- TMD2 = 0xFFE6 -> length 26
r 010F78 0008 FFFF pc=3FDCE400 ...   <- TX buffer, word 0
...

The chip's byte address B is main-RAM word B>>1; this is the same mapping the driver already used, now visible per access. The trace below is the whole story.

The four cases the test runs, and what it expects

The test driver is at bit 0x3FDD9400; its frame builder is at bit 0x3FDDD040 (Ghidra FUN_rom_07fbba08). The key argument is field (the third stack argument), which gates the software CRC append:

0x3FDDDA90: MOVE *A13(FF60h),A7,0   ; A7 = field
0x3FDDDAB0: JREQ 0x3FDDDD00         ; field == 0  -> skip the CRC append
...
0x3FDDDB30: CALLA 0x3FDDBD10        ; field != 0  -> software CRC32, append 4 bytes

The table the test builds (at A13+0x60, stride 0x30):

case mode meaning field expected RMD1 & 0xFF00 observed
0 0x44 `LOOP INTL` — chip generates+appends the CRC; receiver doesn't check 0 0x0300
1 0x4C `LOOP INTL DTCR` — firmware appends a correct FCS; receiver checks 1
2 0x4C `LOOP INTL DTCR` — firmware appends nothing; receiver checks 0
3 0x54 `LOOP INTL COLL` — forced collision 0

So case 2 is designed to fail the CRC (no FCS), and the test expects exactly that — with a framing bit. Case 3 and the rest of the test never run because case 2's check aborts first (ERROR RESULT = 0x50, which the display reads).

Evidence: the FCS the firmware does build is a correct standard CRC32

In case 1 the DMA trace shows the chip reading a 30-byte buffer ending 55 79 C6 95:

r 00FF24 FFE2  ...   TMD2 -> length 30
r 010F78 0008 / 162B / 5A4B / 0008 / 162B / 5A4B / 0000 / AAAA x6 / 5579 / C695

Bytes 79 55 95 C6 = 0xC6955579 little-endian. zlib.crc32() over the 26-byte frame 08 00 2b 16 4b 5a 08 00 2b 16 4b 5a 00 00 AA*12 is 0xC6955579, and the residue over frame+FCS is 0x2144DF1C, i.e. ~residue = 0xDEBB20E3 = FCS_RESIDUE. The firmware's FCS is exactly right, and our DMA delivers it intact — case 1 passes. This is the control that rules out "our DMA eats the last four bytes".

In case 2 the same frame is sent with field=0 and length 26, ending AA AA AA AA — no FCS — and the CRC check fails, as it must.

Evidence: the byte↔bit translation is right for the frame too

The task asked to re-check o>>1 for the buffer specifically. The trace shows the chip reading the TX buffer at byte 0x010F78 (TMD0 0x0F78 + HADR 0x01) and the CPU writing it at the same place; every word is the word the CPU wrote. The same mapping carried the INIT block and the descriptor write-backs exactly (w 00FEE2 0300/4B00 on the RX descriptor word). No shift, no lane swap.

The remaining blocker: RMD1_FRAM

  • the test compares RMD1 & 0xFF00 with 0x6B00 (bit 0x7FBB436 / 0x3FDDA1B0, CMPI 0x6b00,A5);
  • MAME's am79c90.cpp sets RMD1_ERR | RMD1_CRC (0x4800) on a failed FCS and RMD1_ENP; it sets RMD1_FRAM (0x2000) nowhere (grep in both 0.289 and current upstream master);
  • the Am7990 Technical Manual (p. 1-12, 1-25/1-26) and the Am79C90 datasheet both say FRAM is not valid in internal loopback mode, and FRAM requires dribbling bits plus a CRC error.

Diagnostic (runtime only, on the host-side region buffer; the ROM file is untouched): changing that one immediate from 0x6B00 to 0x4B00 — i.e. letting the check pass without the FRAM bit — makes the run continue through case 3 and return to the diagnostics-menu idle PC=0x3FDCE400 instead of the error-blink loop PC=0x3F2088B0. So the FRAM bit is the only thing standing between this model and a clean C — LANCE TEST.

I did not make the equivalent MAME change. A change that sets RMD1_FRAM whenever the DTCR-loopback FCS check fails would contradict the two quoted sentences above, and there is no datasheet sentence supporting it. It is reported here for a second reader, not applied.

What this means for the standing hypotheses

hypothesis verdict
"the firmware appends the FCS and our DMA drops the last four bytes" refuted — the failing frame is built without an FCS on purpose (case 2, field=0); the passing DTCR=1 case proves the DMA carries an FCS when one exists
"a four-byte length discrepancy makes the check read the wrong four bytes" refuted — descriptor length and delivered byte count agree exactly, case by case
"the descriptor write-back we observe is not MAME's" confirmed intact — dma_out writes MAME's m_rx_md[1] verbatim
remaining mismatch RMD1_FRAM is never set by MAME

If the real terminal passes C — LANCE TEST, the real Am7990/Am79C90 must set FRAM on this CRC error despite the datasheet; if it does not, the test's 0x6B00 expectation is itself wrong and the terminal reports the same error we do. Either way the DMA is not the fault.

Reproduction

cd /home/arti/code/k-space/vt1200
M=/home/arti/code/k-space/mame0289/vt1200
MENU_KEYS=C,Z MENU_AT=300 MENU_LAST=3200 \
VT1200_LANCE_DMA_LOG=/tmp/dma.log \
  $M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \
     -debug -debugger none -autoboot_script harness/mame/menu_keys.lua -autoboot_delay 0

Baselines are untouched by this work (the new knob is null unless set): progress.lua at 200 frames is still samples=198 with no env and samples=53 with VT1200_IO_PATH_STUB=1, and the VT1200_SKIP_LEVEL1=1 frame-3000 dump still reproduces duart2_default_tm_frame3000.png (AE = 1826, the cursor).