diff --git a/harness/mame/LANCE_REPORT.md b/harness/mame/LANCE_REPORT.md new file mode 100644 index 0000000..ccbcf4f --- /dev/null +++ b/harness/mame/LANCE_REPORT.md @@ -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=` | 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. diff --git a/harness/mame/README.md b/harness/mame/README.md index 107b193..49797f3 100644 --- a/harness/mame/README.md +++ b/harness/mame/README.md @@ -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`, diff --git a/harness/mame/menu_keys.lua b/harness/mame/menu_keys.lua new file mode 100644 index 0000000..92dbff3 --- /dev/null +++ b/harness/mame/menu_keys.lua @@ -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 \ +-- 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 diff --git a/harness/mame/vt1200.cpp b/harness/mame/vt1200.cpp index c4d0e43..f3e2394 100644 --- a/harness/mame/vt1200.cpp +++ b/harness/mame/vt1200.cpp @@ -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= every DUART access (not de-duplicated), so a write that happens once per iteration cannot hide. + VT1200_LANCELOG= 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= DUART1 IRQ assert: ISR/SRA at the moment of the interrupt. VT1200_LOG_UNMAPPED= 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 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=: 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: