Milestone 1 of docs/superpowers/plans/2026-10-16-lance-ethernet.md.
Modelled:
* MAME am79c90 (AM79C90) at the firmware's two windows -- bit 0x5900000 is
RAP, 0x5800000 is RDP; every other offset in those windows still falls
through to the board model.
* the Ethernet address ROM at bit 0x6800000, as EIGHT bytes. The plan said
six; the firmware's reader (0x3FDD7520) actually loops i = 0..7 and then
validates a folded ones-complement checksum over bytes 0..5 against
bytes 6..7 (0x3FDD78B0). For the MAC 08:00:2b:16:4b:5a that is 0xC186, so
the ROM is 08 00 2b 16 4b 5a c1 86. A six-byte ROM returns expected = 0
and fails LEVEL1/2 ERROR -> ETHERNET ROM CHECKSUM - 23. The checksum
bytes are derived from the firmware's own algorithm (documented in
LANCE_REPORT.md); they are not read off a real PROM.
* the interrupt on INT1, matching the ISR 0x3FA93400 and INTENB bit 1.
* bus-master DMA, translating the LANCE's byte address to the TMS34010's
bit-addressed main RAM (byte B -> word B>>1), cross-checked against the
firmware's own CSR1 = 0xFF60 = RAM word 0x7FB0.
* VT1200_LANCELOG to trace the register/ROM accesses.
Observed with the firmware's own diagnostics menu (driven by the real LK401
model; new menu_keys.lua): the M -- ETHERNET ROM TEST now PASSES. The
C -- LANCE TEST runs the chip for real -- the firmware's init block
(mode 0x004C = LOOP|INTL|DTCR, PADR 08:00:2b:16:4b:5a) is read via the new DMA,
INIT completes (CSR0 = 0x6FE3, IDON), the chip transmits and receives -- but its
internal-loopback receive still reports a CRC error under MAME (RCV descriptor
1 = 0x4B00, message count 0). Cause: MAME's receive() runs the FCS check when
MODE_LOOP && MODE_DTCR (am79c90.cpp:263) even though DTCR means no FCS was
appended. No MAME change was made; quoted and reported in LANCE_REPORT.md.
The OS (skip gate) reads the ROM MAC, programs CSR1/2/3, INITs, sees IDON,
STRTs and polls CSR0; the Terminal Manager now shows Ethernet Address
08-00-2B-16-4B-5A (was 00-00-00-00-00-00) and LAT_08002B164B5A.
Baselines unchanged: no-env samples=198 (3 PCs), VT1200_IO_PATH_STUB samples=53
(75 PCs). ROM md5 unchanged. No MAME tree changes.
9.8 KiB
LANCE Ethernet — milestone 1 report
Date: 2026-10-16. Plan: docs/superpowers/plans/2026-10-16-lance-ethernet.md.
Firmware vt1200_v22.bin md5 b46a3e442ccb86134d2356bd70d22fd2 (unmodified).
MAME tree unmodified (see section 6). Configuration: bare run unless stated.
Milestone 1 target was the register interface and the address ROM. This report covers those, plus three things the firmware forced beyond them: the address ROM turned out to be eight bytes with a checksum, the interface is only half the Ethernet test (it also needs bus-master DMA), and running the firmware's own diagnostics menu was the only way to see the tests at all.
1. What was modelled
| piece | where | how |
|---|---|---|
| LANCE register file | bit 0x5900000 = RAP, 0x5800000 = RDP |
MAME am79c90 (AM79C90), offset 0 of each window; every other offset still falls through to the board model |
| bus-master DMA | dma_in/dma_out |
LANCE byte address B → main-RAM word B >> 1 |
| Ethernet address ROM | bit 0x6800000 (byte 0xD00000) |
eight bytes, read as words with the byte in the low lane |
| interrupt | intr_out → INT1 (line 0), .invert() |
matches the ISR 0x3FA93400 and INTENB bit 1 (0x3FA8C340) |
| logging | VT1200_LANCELOG=<path> |
every RAP/RDP/ROM access with PC and time |
The register map is confirmed by the firmware itself. io5800_5900_init
(L3FDDCBE0) and lance_stop (L3FDDCA90) produce exactly the LANCE protocol,
and the srl 0x3 on a bit address before loading CSR1/CSR2 proves the chip is
byte addressed (plan section 1.1).
The DMA unit, cross-checked
The plan's own cross-check holds. The firmware programs the init block at
CSR1 = 0xFF60; a RAM dump taken while its LANCE test runs shows the init block
at byte 0xFF60 (main-RAM word 0x7FB0 = bit 0x7fb00). So
dma_in(o) = m_mainram[o >> 1] is right, not o >> 2 or o >> 3.
2. The address ROM is eight bytes, with a checksum
The plan modelled six bytes (the MAC) and flagged the guide's "format and checksum" caveat as an open risk. That risk is real, and it is now resolved.
0x3FDD74B0 (called by the ROM test) is the reader:
3fdd7520: move a9,a11 ; sll 0x4,a11 ; add a13,a11 ; dest = local[i]
3fdd7560: move a9,a7 ; sll 0x4,a7
3fdd7590: add a5,a7 ; a7 = 0x6800000 + i*16
3fdd75a0: move *a7,a7,0 ; andi 0xff,a7 ; byte i (low lane)
3fdd7600: cmpi 0x7,a9 ; jrle L3FDD7520 ; i = 0..7 -> EIGHT bytes
Then a folded ones-complement checksum over bytes 0..5, compared to bytes 6..7:
3fdd7630: sub a10,a10 ; sum = 0, i = 0
L3FDD7650:
3fdd7650: sll 0x1,a10 ; (>=0x10000 ? -=0xffff) ; fold(sum<<1)
3fdd7710: move *a11,a11,0 ; sll 0x8,a11 ; add a10,a11
3fdd7780: move *a7,a10,0 ; add a10,a11 ; move a11,a10 ; fold
3fdd7820: addk 2,a9 ; cmpi 0x5,a9 ; jrle L3FDD7650 ; pairs (0,1),(2,3),(4,5)
3fdd7860: cmpi 0xffff,a10 ; (== ? =0)
3fdd78b0: expected = (rom[6]<<8)|rom[7]
3fdd7920: cmp a10,a9 ; jreq pass ... else a8 = 1
For the MAC 08 00 2b 16 4b 5a this yields 0xC186, so the ROM is:
08 00 2b 16 4b 5a c1 86
A six-byte ROM returns expected = 0 and fails
LEVEL1/2 ERROR -> ETHERNET ROM CHECKSUM - 23 (string at bit 0x3FDCA688).
With the eight-byte ROM the test passes (section 4).
Caveat, stated plainly. Bytes 6–7 are derived from the firmware's own
algorithm; they are not read off a physical PROM. A real PROM carries the same
value because it must validate, so this is a self-consistent ROM, not a
fabricated verdict — the firmware still does the read, the sum and the compare.
But the test passing is a consistency check of this model, not independent
evidence that a real VT1200 PROM holds c1 86.
3. The OS programs the chip (skip gate)
VT1200_SKIP_LEVEL1=1, VT1200_LANCELOG. Excerpt (PC, access):
3F9D2880 RAP=0 lance_stop
3F9D28D0 RDP=0004 CSR0 STOP
3F9D2A20 read 08 00 2B 16 4B 5A OS copies the ROM MAC into its own buffer
3F9D4380 RAP=0 / RDP=0004 lance_stop
3F9D4420 RAP=1 / RDP=0F80 CSR1 = init block (byte 0xFF80)
3F9D44E0 RAP=2 / RDP=0000 CSR2
3F9D45E0 RAP=3 / RDP=0000 CSR3
3F9D4680 RAP=0 / RDP=0001 CSR0 = INIT
3F9D4720 RAP=0 / RDP=0181 read CSR0: IDON|INTR|INIT -> INIT completed
3F9D4900 RDP=0042 CSR0 = STRT|INEA
...
So the OS completes INIT (IDON set), starts the chip and runs its interrupt
path. What it does with the interface is therefore: read the MAC ROM →
program CSRs 1/2/3 → INIT → wait for IDON → STRT → poll CSR0 in its ISR. With
no network peer it prints Could not determine IP address after 5 retries / TCP/IP is now down. (a missing address, which the plan allows) and the
Terminal Manager now shows the real hardware address:
Ethernet Address 08-00-2B-16-4B-5A
LAT Host Name LAT_08002B164B5A
(Previously 00-00-00-00-00-00 / LAT_000000000000.)
4. The firmware's own Ethernet tests
The level-0 diagnostics menu (VERSION 2.08 - SELECTED TEST TYPE:) is reached in
a bare run and offers C - LANCE TEST and M - ETHERNET ROM TEST. It is
driven by the real LK401 model, so the tests run in the firmware's own context —
no jumping into routines. harness/mame/menu_keys.lua presses the keycaps via
the keyboard I/O ports:
M=/home/arti/code/k-space/mame0289/vt1200
MENU_KEYS=M,Z MENU_AT=300 MENU_LAST=2600 VT1200_DUMP_FRAMES=1200 \
VT1200_DUMP_DIR=. $M vt1200 -rompath harness/mame/roms -video none \
-sound none -nothrottle -debug -debugger none \
-autoboot_script harness/mame/menu_keys.lua -autoboot_delay 0
| test | before | after |
|---|---|---|
| M — Ethernet ROM test | LEVEL1/2 ERROR -> ETHERNET ROM CHECKSUM - 23 |
passes; menu returns, no error banner |
| C — LANCE test | LEVEL1/2 ERROR -> LANCE - 24/34, ERROR RESULT 20000032, CSR REG VALUE 00000000, RCV DESCR 1 00008000 |
still fails, but far deeper: ERROR RESULT 00000050, CSR REG VALUE 00006FE3, RCV DESCR 1 00004B00, XMIT DESCR 1 00000301 |
The ROM test now reads all eight bytes and validates the checksum.
The LANCE test's register/INIT stage passes; the loopback does not
With DMA bound, the C - LANCE TEST runs the chip for real. The init block the
firmware builds (RAM byte 0xFF60, from a VT1200_DUMP_RAM dump) is:
mode = 0x004C MODE_LOOP | MODE_INTL | MODE_DTCR
PADR = 08:00:2b:16:4b:5a <- the ROM MAC, copied in
LADRF = 0
RX ring = 0x00FEE0 (len 8) TX ring = 0x00FF20 (len 8)
The chip reads that block through the (new) DMA, completes INIT
(CSR0 = 0x6FE3, IDON set), transmits (XMIT DESCR 1 = 0x0301, OWN cleared,
no transmit errors) and receives — but the receive descriptor is
RCV DESCR 1 = 0x4B00 (ERR | CRC | STP | ENP) with RCV DESCR 3 (MCNT) = 0,
so the test reports a failure. The failure is the loopback CRC.
5. The remaining failure: MAME's FCS check under MODE_DTCR
The firmware runs internal loopback with CRC generation disabled
(MODE_DTCR, mode bit 3). MAME's receive() then does the FCS check:
// src/devices/machine/am79c90.cpp:263
if (!(m_mode & MODE_LOOP) || (m_mode & MODE_DTCR))
{
u32 const crc = util::crc32_creator::simple(buf, length);
if (~crc != FCS_RESIDUE) { ... m_rx_md[1] |= RMD1_ERR | RMD1_CRC; }
}
Because DTCR means no FCS was appended (transmit(), same file: append_fcs = !(m_mode & MODE_DTCR)), the receiver's CRC can never match and the descriptor
always comes back ERR|CRC with message count 0. On the real terminal this
self-test passes, so the real chip must not fail the receive CRC in internal
loopback when it generated none — i.e. the check should be skipped whenever the
frame carries no FCS, not only when MODE_LOOP && !MODE_DTCR.
No MAME change was made. Per the project conventions this is reported for a
second reader, with the quote above, rather than edited. It is the next blocker
for the C - LANCE TEST; the byte↔bit DMA itself is done and verified.
Also open (from the plan)
AM79C90vsAM7990was not settled by evidence. Both were tried and behave identically on this test, soAM79C90is kept (it is the part in DEC's ownkn01, the template the plan cites).get_buf_lengthis the only behavioural difference that matters, and the firmware's descriptor here does not distinguish them. A board photograph or a datasheet would settle it.- The level-0 "LANCE not stopped" check (
0x3F201320, error 33) is behind DUART1 input-port bit 2 = high; the harness holds IP2 low (the value the blit-status test at0x3F204AA0needs), so that path is not taken and the check neither runs nor fails. A reset chip readsCSR0 = 0x0004(STOP set), which is what the check tests, so it would pass if taken.
6. Reproduction and scope
cd /home/arti/code/k-space/vt1200
JOBS=8 harness/mame/build.sh
# register file + ROM + DMA, live:
M=/home/arti/code/k-space/mame0289/vt1200
VT1200_SKIP_LEVEL1=1 VT1200_LANCELOG=lance.log $M vt1200 \
-rompath harness/mame/roms -video none -sound none -nothrottle \
-debug -debugger none -autoboot_script harness/mame/progress.lua
# the firmware's own ROM test (passes):
MENU_KEYS=M,Z MENU_AT=300 VT1200_DUMP_FRAMES=1200 VT1200_DUMP_DIR=. \
$M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \
-debug -debugger none -autoboot_script harness/mame/menu_keys.lua
Regression baselines are unchanged by this work (the LANCE is only touched by
lance_stop before the OS phase): progress.lua at 200 frames is still
samples=198 (3 distinct PCs) with no env and samples=53 with
VT1200_IO_PATH_STUB=1; the skip-gate Terminal Manager is unchanged except for
the now-correct MAC. harness/mame/build.sh test passes. Nothing in
vt1200_board.* or the ROM was touched.