harness: LANCE Ethernet -- register file, 8-byte address ROM, INT1 and byte<->bit DMA

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.
This commit is contained in:
SDD committed 2026-10-16 16:39:21 +03:00
1 parent 7bf195e615
commit 634246ecb1
4 files changed
+529 -6

No files matched your search

+228
View File
@@ -0,0 +1,228 @@
# 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:
```sh
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:
```c++
// 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)
* `AM79C90` vs `AM7990` was not settled by evidence. Both were tried and behave
identically on this test, so `AM79C90` is kept (it is the part in DEC's own
`kn01`, the template the plan cites). `get_buf_length` is 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 at `0x3F204AA0` needs), so that path is not taken and the
check neither runs nor fails. A reset chip reads `CSR0 = 0x0004` (STOP set),
which is what the check tests, so it would pass if taken.
---
## 6. Reproduction and scope
```sh
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.
+24 -3
View File
@@ -61,6 +61,7 @@ Reports in this directory:
|---|---|
| `GATE_CLEANUP_REPORT.md` | promoting the working config to the default; retiring the settled env gates |
| `TABLET_REPORT.md` | the VSXXX-AB tablet: the self-test device-code ID, the 5-byte absolute report, and absolute host-pointer input |
| `LANCE_REPORT.md` | the LANCE Ethernet model: the register file, the 8-byte address ROM and its checksum, the byte↔bit DMA, and the remaining loopback/CRC blocker |
| `RENDER_REPORT.md` | the active-pixel scanline renderer, and the geometry it derived |
| `DUART_REPORT.md` | the DUARTs: wiring, the ISR-driven transmit, the datasheet-verified model edits |
| `BRINGUP_LOG.md` | the whole bring-up narrative, **including withdrawn claims** — read it sceptically |
@@ -116,6 +117,13 @@ to be a six-variable incantation; it is now the default, and a bare
`INPUT_REPORT.md` and `TABLET_REPORT.md`.
* A mono palette (pens 0/1 = black/white) and an active-pixel scanline renderer
that reads VRAM. See `RENDER_REPORT.md`.
* The **LANCE Ethernet controller** — MAME's `am79c90` (AM79C90) at the
firmware's two register windows (**bit `0x5900000` = RAP, `0x5800000` = RDP**),
with its bus-master DMA bound (LANCE byte `B` → main-RAM word `B >> 1`) and its
interrupt on **INT1**. The **Ethernet address ROM** at bit `0x6800000` is
modelled as the eight bytes the firmware actually reads:
`08 00 2b 16 4b 5a c1 86` (MAC + a checksum it validates). See
`LANCE_REPORT.md`.
A bare run gets through POST and then **fails level 0's I/O data path test**, so it
lands in the interactive diagnostics menu (`VERSION 2.08 - SELECTED TEST TYPE:`)
@@ -202,7 +210,19 @@ the VSXXX-AA mouse back (device code `0010`, 3-byte relative reports). The mouse
is the *faithful* part — the real terminal almost certainly has a mouse — so the
tablet default is a **deliberate divergence** (below).
Not modelled: **LANCE Ethernet**. It does not block the X11 bring-up.
The OS now also programs the **LANCE**: it reads the Ethernet address ROM,
programs CSR1/2/3 with its init block, issues `INIT`, sees `IDON`, issues `STRT`
and polls CSR0 in its interrupt path. The Terminal Manager therefore shows the
real hardware address — `Ethernet Address 08-00-2B-16-4B-5A`, `LAT Host Name
LAT_08002B164B5A` (it used to show `00-00-00-00-00-00`). The
`Could not determine IP address` banner is the *missing address* the plan allows
(no network peer), not a LANCE failure. See `LANCE_REPORT.md`.
Not fully working: the firmware's `C - LANCE TEST` (its internal loopback) still
fails at a **CRC** check — the firmware runs loopback with `MODE_DTCR` and MAME's
`receive()` then always mismatches the FCS. That is a MAME-device question,
quoted in `LANCE_REPORT.md` section 5 and **not** edited; the `M - ETHERNET ROM
TEST` passes.
### Getting past level 1
@@ -233,14 +253,15 @@ Terminal Manager usable. It is a **value-aware inverse switch**: the default
lives in the code, not in a wrapper script setting `VT1200_TABLET`, so
`VT1200_MOUSE=1` (or `VT1200_MOUSE=0` for the tablet) always flips it either way.
## Environment knobs (19; down from 33)
## Environment knobs (20; down from 33)
Almost all are opt-in and off by default — there is no longer a "working
incantation" to remember, because everything the firmware needs to run is now the
default. The one exception is that the **tablet is on by default**; `VT1200_MOUSE`
is the switch that restores the faithful mouse (see the divergences above).
Instrumentation: `VT1200_DUARTLOG`, `VT1200_BOARD_LOG`, `VT1200_BOARD_MAP`,
Instrumentation: `VT1200_DUARTLOG`, `VT1200_LANCELOG` (every LANCE RAP/RDP and
address-ROM access with the PC), `VT1200_BOARD_LOG`, `VT1200_BOARD_MAP`,
`VT1200_IRQLOG`, `VT1200_LOG_UNMAPPED`, `VT1200_DPYLOG` (+`_FRAMES`),
`VT1200_DUMP_FRAME` (+`_FRAMES`), `VT1200_DUMP_RAM` (+`_FRAMES`),
`VT1200_DUMP_VRAM` (+`_FRAMES`), `VT1200_DUMP_DIR`, `VT1200_NO_RENDER`,
+61
View File
@@ -0,0 +1,61 @@
-- menu_keys.lua -- drive the level-0 diagnostics menu with the modelled LK401
-- keyboard, to run a selected test through the firmware's own test map.
--
-- MENU_KEYS=C,Z MENU_AT=300 MENU_DUMP=200,800 \
-- <mame> vt1200 ... -video none -nothrottle -debug -debugger none \
-- -autoboot_script harness/mame/menu_keys.lua
--
-- MENU_KEYS is a comma list of single characters (LK401 keycaps). Each is
-- pressed for MENU_HOLD frames (default 6), MENU_GAP frames apart (default 30).
local machine = manager.machine
local ports = machine.ioport.ports
-- LK401 port index by character (VT1200_LK401_KEY_LIST order).
local KEY = {
["C"] = ":lk401:K40", ["Z"] = ":lk401:K38", ["M"] = ":lk401:K44",
["O"] = ":lk401:K21", ["Q"] = ":lk401:K13", ["X"] = ":lk401:K39",
}
local keys = {}
for k in string.gmatch(os.getenv("MENU_KEYS") or "C,Z", "([^,]+)") do
keys[#keys + 1] = k
end
local at = tonumber(os.getenv("MENU_AT") or "300")
local hold = tonumber(os.getenv("MENU_HOLD") or "6")
local gap = tonumber(os.getenv("MENU_GAP") or "30")
local last = tonumber(os.getenv("MENU_LAST") or "1500")
-- schedule: key i pressed at at + i*gap
local frame = 0
local function set_key(k, v)
local tag = KEY[k]
if not tag then print("MENU unknown key " .. k); return end
local port = ports[tag]
local f
for _, field in pairs(port.fields) do f = field end
if f then f:set_value(v) end
end
emu.register_frame_done(function()
frame = frame + 1
for i, k in ipairs(keys) do
local t = at + (i - 1) * gap
if frame == t then
set_key(k, 1)
print(string.format("MENU frame=%d press %s", frame, k))
elseif frame == t + hold then
set_key(k, 0)
end
end
if frame >= last then
machine:exit()
end
end, "vt1200-menu-keys")
local dbg = machine.debugger
if dbg then
dbg.visible_cpu = machine.devices[':maincpu']
dbg.execution_state = 'run'
end
+216 -3
View File
@@ -44,9 +44,42 @@
is NOT a keyboard model) so the count-only level-0 test can pass; the
default stays faithful and still fails.
What is NOT modelled: LANCE Ethernet and the board system logic beyond the
register model. The firmware therefore still stops at the level-1 HOST
PORT test and wedges in the system-board LED loop; that is expected.
What is NOT modelled: the board system logic beyond the register model. The
LANCE register file, its address ROM, its interrupt and its bus-master DMA
ARE modelled (see below); what is not yet satisfying is the chip's own
internal loopback (a MAME FCS question, reported in LANCE_REPORT.md).
---------------------------------------------------------------------
LANCE Ethernet (milestone 1 of the LANCE plan):
* AM79C90 (MAME's am79c90) at the firmware's two windows: bit 0x5900000
is RAP (register-address port) and 0x5800000 is RDP (register-data
port). Every other offset in those two windows still falls through to
the board model -- the logic fronting the LANCE is otherwise undecoded.
* The Ethernet address ROM at bit 0x6800000 (physical byte 0xD00000):
EIGHT bytes -- 08:00:2b:16:4b:5a (DEC OUI + this unit's serial) followed
by a two-byte checksum. It is a physical device, not firmware data --
the MAC does not appear in the ROM image. The firmware reads eight
bytes and validates a folded ones-complement checksum over the first
six (0x3FDD7520 / 0x3FDD78B0); six bytes alone fail error 23.
* Interrupt INT1, which is where the firmware installs the LANCE ISR
0x3FA93400 (the DUARTs sit on INT2 in the OS phase).
* Bus-master DMA: bound, translating the LANCE's byte address to the
TMS34010's bit-addressed main RAM (byte B -> RAM word B>>1). The
firmware's init block at CSR1 = 0xFF60 lands at main-RAM word 0x7FB0,
which is where the firmware's own RAM dump has it.
* The chip's own internal loopback still reports a CRC error under MAME
(RCV descriptor 1 = 0x4B00, message count 0) because the firmware runs
loopback with MODE_DTCR; see README.md -- reported, not papered over,
and no MAME change was made.
The Ethernet-related tests this affects: the level-1 Ethernet address ROM
test (error 23) now PASSES; the LANCE test's register/INIT stage works (the
chip reads the init block, completes INIT, sees IDON, STRTs and moves
descriptors) but its loopback still reports a CRC failure under MAME. The
level-0 "LANCE not stopped" check (error 33 -- a reset chip reads
CSR0 = 0x0004, STOP set) is behind DUART1 IP bit 2, which this harness holds
low, so that path is not taken.
---------------------------------------------------------------------
Instrumentation knobs (all opt-in; none changes execution):
@@ -60,6 +93,9 @@
VT1200_DUARTLOG=<path> every DUART access (not de-duplicated),
so a write that happens once per iteration
cannot hide.
VT1200_LANCELOG=<path> every LANCE RAP/RDP access (and the address
ROM reads) with the PC, so the OS's
programming of the chip is visible.
VT1200_IRQLOG=<path> DUART1 IRQ assert: ISR/SRA at the moment of
the interrupt.
VT1200_LOG_UNMAPPED=<path> catch-all over the whole address space;
@@ -153,6 +189,7 @@
#include "cpu/tms34010/tms34010.h"
#include "diserial.h"
#include "emupal.h"
#include "machine/am79c90.h"
#include "machine/mc68681.h"
#include "screen.h"
@@ -605,6 +642,7 @@ public:
, m_lk401(*this, "lk401")
, m_mouse(*this, "mouse")
, m_tablet(*this, "tablet")
, m_lance(*this, "lance")
, m_vram(*this, "vram")
{
}
@@ -682,6 +720,41 @@ private:
void duart2_a_tx_w(int state);
void duart2_b_tx_w(int state);
// --- LANCE (Am7990/Am79C90) Ethernet controller --------------------------
// The firmware's Ethernet interface is the LANCE register file: bit
// 0x5900000 = RAP (register-address port), bit 0x5800000 = RDP
// (register-data port). Both are byte-addressed devices, which is why the
// firmware does srl 0x3 on a bit address before loading CSR1/CSR2 (see
// docs/superpowers/plans/2026-10-16-lance-ethernet.md section 1.1).
//
// The chip is MAME's am79c90_device (AM79C90 C-LANCE). Its CSR0 STOP
// semantics make the level-0 "LANCE not stopped" test pass by construction
// (a reset chip reads 0x0004), and its CSR1 read/write path satisfies the
// level-1 register test.
//
// The bus-master DMA callbacks are bound in vt1200(): LANCE byte B maps to
// main-RAM word B>>1. See the note there.
required_device<am79c90_device> m_lance;
// Ethernet address ROM: bit 0x6800000 (physical byte 0xD00000). The
// firmware reads EIGHT bytes, one per 16-bit step:
// movi 0x6800000,a11 / ... / movb *a7(0x0),*a11(0x1b0) / addk 16,a7
// (0x3FDD7520-0x3FDD7620), then validates a checksum over the first six.
// The TMS34010's RBYTE() extracts the LOW byte of the word at the bit
// address (34010ops.h RFIELDMAC(0xff,9): RDMEM_WORD >> (bit&0xf)), so
// handler offset i (=(bitaddr-base)>>4) returns ROM byte i in the low byte.
u16 lance_rom_r(offs_t off);
u16 lance_rdp_r(offs_t off); // RDP = chip data port (regs_r offset 0)
void lance_rdp_w(offs_t off, u16 data);
u16 lance_rap_r(offs_t off); // RAP = chip register select (regs_r offset 1)
void lance_rap_w(offs_t off, u16 data);
// VT1200_LANCELOG=<path>: every LANCE register access with the PC, so the
// OS's programming of the chip (RAP writes, CSR0 INIT/STRT/STOP) is visible.
FILE *m_lancelog = nullptr;
void log_lance(char kind, const char *what, u16 value);
// VT1200_IO_PATH_STUB=1: opt-in stand-in for the unknown test fixture on
// the keyboard port, so level-0's count-only I/O data path test can pass.
// NOT a keyboard model. See iopath_stub_arm() and the README.
@@ -865,6 +938,13 @@ void vt1200_state::machine_start()
fprintf(m_duartlog, "# win kind reg val pc -- every DUART access (not de-duplicated)\n");
}
if (const char *p = getenv("VT1200_LANCELOG"))
{
m_lancelog = fopen(p, "w");
if (m_lancelog)
fprintf(m_lancelog, "# kind port val pc cyc t -- every LANCE RAP/RDP/ROM access\n");
}
// With the real DUART in place, board-level signals that used to be faked
// by the window model come from the DUART's input port (read register 0x0D:
// IP0-IP6, bit 7 always set). The monitor-selection switch is IP bits 4-6;
@@ -902,6 +982,7 @@ void vt1200_state::machine_start()
logerror("vt1200: DUART2 devices: LK401 keyboard (ch A) + %s (ch B)%s\n",
m_tablet_selected ? "VSXXX-AB tablet (default)" : "VSXXX-AA mouse",
m_tablet_selected ? " [divergence: VT1200_MOUSE=1 forces the mouse]" : " [VT1200_MOUSE]");
logerror("vt1200: lance=on (AM79C90 @RAP 0x5900000 / RDP 0x5800000, IRQ->INT1), rom=08:00:2b:16:4b:5a+c186, dma=bound (byte>>1 -> main RAM)\n");
}
void vt1200_state::machine_reset()
@@ -1000,6 +1081,27 @@ void vt1200_state::program_map(address_map &map)
// distinct from the write-only IMR (the board write-back latch aliased them,
// producing a phantom interrupt that wedged the OS at the X11 weave).
map(VT1200_WIN2, VT1200_WIN2 + 0xff).rw(FUNC(vt1200_state::duart2_r), FUNC(vt1200_state::duart2_w));
// LANCE Ethernet controller. Registered AFTER the board-window model so
// this wins the register slice (map precedence is last-wins); every OTHER
// offset in the two windows still falls through to the board model, because
// the board logic fronting the LANCE is otherwise undecoded (plan risk 1).
//
// bit 0x5900000 (WIN1) = RAP, the register-address port
// bit 0x5800000 (WIN0) = RDP, the register-data port
//
// Each window entry covers one 16-bit word (0x10 map units); a word access
// at the window base is handler offset 0. The firmware accesses only offset
// 0 of each window.
map(VT1200_WIN1, VT1200_WIN1 + 0x000f).rw(FUNC(vt1200_state::lance_rap_r), FUNC(vt1200_state::lance_rap_w));
map(VT1200_WIN0, VT1200_WIN0 + 0x000f).rw(FUNC(vt1200_state::lance_rdp_r), FUNC(vt1200_state::lance_rdp_w));
// The Ethernet address ROM at bit 0x6800000 (physical byte 0xD00000). It is
// a separate device, not a region of the firmware image: the known MAC does
// not appear anywhere in vt1200_v22.bin. EIGHT bytes, one per 16-bit step
// (addk 16,a7), read with movb (low byte of the word -- see lance_rom_r):
// six MAC bytes then a two-byte checksum, so eight 16-bit words.
map(0x6800000, 0x6800000 + 0x007f).r(FUNC(vt1200_state::lance_rom_r));
}
// MAME may hand us either an entry-relative offset (0..0xffff) or the absolute
// address; BOARD_BASE is 0x10000-aligned, so ORing the low 16 bits is correct
@@ -1233,6 +1335,79 @@ void vt1200_state::log_duart(int win, char kind, u32 reg, u16 value)
fflush(m_duartlog);
}
// --- LANCE (Am7990/Am79C90) -------------------------------------------------
//
// The register protocol is the chip's own: a write to RAP selects one of CSR0-3,
// and RDP then reads/writes that register. The firmware's sequence (plan
// section 1.1) is the textbook "program IADR/CSR1-3, then CSR0 INIT":
//
// RAP=1; RDP=addr_low ; RAP=2; RDP=addr_high ; RAP=3; RDP=0 ; RAP=0; RDP=0x7f41
//
// MAME's regs_r/regs_w take a single offset: 0 selects RDP, non-zero selects RAP.
// So the RDP handlers call regs_r/regs_w(space, 0) and the RAP handlers call
// regs_r/regs_w(space, 1).
u16 vt1200_state::lance_rdp_r(offs_t)
{
const u16 v = m_lance->regs_r(m_maincpu->space(AS_PROGRAM), 0);
log_lance('R', "RDP", v);
return v;
}
void vt1200_state::lance_rdp_w(offs_t, u16 data)
{
log_lance('W', "RDP", data);
m_lance->regs_w(0, data);
}
u16 vt1200_state::lance_rap_r(offs_t)
{
const u16 v = m_lance->regs_r(m_maincpu->space(AS_PROGRAM), 1);
log_lance('R', "RAP", v);
return v;
}
void vt1200_state::lance_rap_w(offs_t, u16 data)
{
log_lance('W', "RAP", data);
m_lance->regs_w(1, data);
}
u16 vt1200_state::lance_rom_r(offs_t off)
{
// The board's Ethernet address ROM is EIGHT bytes, not six: the firmware's
// reader at 0x3FDD7520 loops a9 = 0..7 (cmpi 0x7 / jrle), then computes a
// folded ones-complement checksum over bytes 0..5 and compares it to
// (rom[6] << 8) | rom[7] at 0x3FDD78B0-0x3FDD7930. That is the "format and
// checksum" the service guide describes, and it is why a six-byte ROM fails
// LEVEL1/2 ERROR -> ETHERNET ROM CHECKSUM - 23.
//
// Bytes 0..5 are DEC's OUI 08:00:2b plus this unit's serial 16:4b:5a
// (NEXT_SESSION.md section 6). Bytes 6..7 are the checksum the firmware
// itself computes for those six bytes (derivation in LANCE_REPORT.md); a
// real PROM would carry the same value because it must validate. The
// TMS34010 byte load takes the LOW byte of the 16-bit word (RBYTE ->
// RFIELDMAC(0xff,9): RDMEM_WORD >> (bit & 0xf)), so byte i goes in the low
// byte of word i.
static const u8 rom[8] = {
0x08, 0x00, 0x2b, 0x16, 0x4b, 0x5a, // MAC (DEC OUI + unit serial)
0xc1, 0x86 // checksum over the MAC
};
const u16 v = (off < 8) ? u16(rom[off]) : 0x0000;
log_lance('R', "ROM", v);
return v;
}
void vt1200_state::log_lance(char kind, const char *what, u16 value)
{
if (!m_lancelog)
return;
fprintf(m_lancelog, "%c %-3s val=%04X pc=%08X cyc=%llu t=%.6f\n",
kind, what, value, (unsigned)m_maincpu->state_int(TMS34010_PC),
(unsigned long long)m_maincpu->total_cycles(), machine().time().as_double());
fflush(m_lancelog);
}
// Copy one active scanline out of the video RAM framebuffer.
//
// The TMS34010 screen-refresh address is reconstructed by the device into
@@ -1509,6 +1684,44 @@ void vt1200_state::vt1200(machine_config &config)
m_duart1->irq_cb().set(FUNC(vt1200_state::duart1_irq_w));
m_duart2->irq_cb().set(FUNC(vt1200_state::duart2_irq_w));
// LANCE Ethernet. AM79C90 (C-LANCE) with the 12.5 MHz reference clock the
// DEC kn01 uses; the clock only paces the chip's poll/interrupt timers, so
// any plausible value satisfies the register tests and INIT (plan risk 3).
//
// Interrupt -> INT1 (line 0). The LANCE ISR 0x3FA93400 is installed on the
// INT1 slot and 0x3FA8C340-470 then enables INT1 (INTENB bit 1 = 0x0002) --
// NOT INT2, where DUART1 sits in the OS phase (see the plan, section 1.2).
// /INTR is active-low, hence .invert(). During POST the chip is STOPped and
// INEA is clear, so it never asserts, and the same line's POST occupant
// (DUART1) is unaffected.
//
// Bus-master DMA, below: the LANCE addresses memory in BYTES, while the
// TMS34010 program space is bit-addressed (each 16-bit word spans 16
// address units), so LANCE byte B is main-RAM word B>>1. Cross-checked
// against the firmware's own init block address: it programs CSR1 = 0xFF60,
// which is bit 0x7fb00 = main-RAM word 0x7FB0, and the init block IS at
// byte 0xFF60 in the RAM dump (mode 0x004C, PADR 08:00:2b:16:4b:5a).
AM79C90(config, m_lance, XTAL(12'500'000));
m_lance->intr_out().set_inputline(m_maincpu, 0).invert();
m_lance->dma_in().set([this](offs_t o) -> u16 {
const offs_t w = o >> 1;
if (w < 0x00200000) // 4 MiB of main RAM = 0x200000 words
return m_mainram[w];
log_lance('d', "DMA-RANGE", u16(w >> 16));
return 0x0000;
});
m_lance->dma_out().set([this](offs_t o, u16 data, u16 mem_mask) {
const offs_t w = o >> 1;
if (w < 0x00200000)
{
COMBINE_DATA(&m_mainram[w]);
}
else
{
log_lance('d', "DMA-RANGE", u16(w >> 16));
}
});
// The real devices on DUART2: channel A = keyboard (4800 8N1), channel B =
// the pointing device (4800 8O1), exactly as the board wires them. Both
// talk to the SCN2681 through the genuine device_serial_interface bit lines: