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.
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)
- The firmware does NOT append an FCS to the frame that fails. It is
deliberate, not a DMA truncation. The
C — LANCE TESTruns 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. - 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 = 0x4B00is the chip's honest CRC error on that deliberately-unterminated frame. - The only remaining mismatch is one bit. The test expects
RMD1 = 0x6B00(ERR|FRAM|CRC|STP|ENP); MAME reports0x4B00(ERR|CRC|STP|ENP). MAME'sam79c90never setsRMD1_FRAM. That single bit is the sole blocker, and it is a chip-model question, not a DMA question. - 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 & 0xFF00with0x6B00(bit0x7FBB436/0x3FDDA1B0,CMPI 0x6b00,A5); - MAME's
am79c90.cppsetsRMD1_ERR | RMD1_CRC(0x4800) on a failed FCS andRMD1_ENP; it setsRMD1_FRAM(0x2000) nowhere (grepin both 0.289 and current upstreammaster); - 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).