mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
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.
This commit is contained in:
1 parent
5ed4048720
commit
b6da987aae
3 files changed
+202
-9
No files matched your search
@@ -0,0 +1,150 @@
|
||||
# `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` | `0x0300` ✔ |
|
||||
| 1 | `0x4C` | `LOOP|INTL|DTCR` — firmware appends a **correct** FCS; receiver checks | 1 | `0x0300` | `0x0300` ✔ |
|
||||
| 2 | `0x4C` | `LOOP|INTL|DTCR` — firmware appends **nothing**; receiver checks | 0 | **`0x6B00`** (CRC **and** FRAM) | `0x4B00` ✘ |
|
||||
| 3 | `0x54` | `LOOP|INTL|COLL` — forced collision | 0 | `TMD3 & 0x400` (RTRY) | not reached |
|
||||
|
||||
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
|
||||
|
||||
```sh
|
||||
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).
|
||||
+11
-6
@@ -68,6 +68,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 |
|
||||
| `LANCE_LOOPBACK_REPORT.md` | `C — LANCE TEST`: the TX buffer **does** carry a correct FCS in the passing `DTCR=1` case and deliberately carries **none** in the failing one; the residual mismatch is MAME never setting `RMD1_FRAM` |
|
||||
| `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 |
|
||||
@@ -225,10 +226,12 @@ LAT_08002B164B5A` (it used to show `00-00-00-00-00-00`). The
|
||||
(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.
|
||||
fails, but **not** because our DMA loses the FCS. The test's failing case is a
|
||||
*negative* test that deliberately sends a frame with **no** FCS and expects a CRC
|
||||
error; the frame that carries an FCS (the `DTCR=1` passing case) is delivered
|
||||
intact and passes. The one bit left is `RMD1_FRAM` — the test expects
|
||||
`RMD1 = 0x6B00`, MAME reports `0x4B00`, and MAME never sets `FRAM`. See
|
||||
`LANCE_LOOPBACK_REPORT.md`; the `M - ETHERNET ROM TEST` passes.
|
||||
|
||||
### Getting past level 1
|
||||
|
||||
@@ -259,7 +262,7 @@ 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 (20; down from 33)
|
||||
## Environment knobs (21; 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
|
||||
@@ -267,7 +270,9 @@ default. The one exception is that the **tablet is on by default**; `VT1200_MOUS
|
||||
is the switch that restores the faithful mouse (see the divergences above).
|
||||
|
||||
Instrumentation: `VT1200_DUARTLOG`, `VT1200_LANCELOG` (every LANCE RAP/RDP and
|
||||
address-ROM access with the PC), `VT1200_BOARD_LOG`, `VT1200_BOARD_MAP`,
|
||||
address-ROM access with the PC), `VT1200_LANCE_DMA_LOG` (every LANCE bus-master
|
||||
DMA word access: byte address, value, byte lanes, PC — how the transmit buffer and
|
||||
the descriptor write-back are read directly), `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`,
|
||||
|
||||
+41
-3
@@ -96,6 +96,10 @@
|
||||
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_LANCE_DMA_LOG=<path> every LANCE bus-master DMA word access (byte
|
||||
address, value, byte lanes, PC), so the
|
||||
transmit buffer and the descriptor write-back
|
||||
are observed directly rather than inferred.
|
||||
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;
|
||||
@@ -755,6 +759,14 @@ private:
|
||||
FILE *m_lancelog = nullptr;
|
||||
void log_lance(char kind, const char *what, u16 value);
|
||||
|
||||
// VT1200_LANCE_DMA_LOG=<path>: every bus-master DMA word access (the LANCE's
|
||||
// own byte address, the value and, for writes, the byte lanes). This is how
|
||||
// the transmit buffer and the descriptor write-back are observed directly,
|
||||
// rather than inferred: the chip's byte address B is main-RAM word B>>1, and
|
||||
// the even/odd byte lane is dma_in/dma_out's own (am79c90.cpp dma_in/dma_out).
|
||||
FILE *m_lance_dmalog = nullptr;
|
||||
void log_lance_dma(char kind, u32 addr, u16 value, u16 mask);
|
||||
|
||||
// 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.
|
||||
@@ -945,6 +957,15 @@ void vt1200_state::machine_start()
|
||||
fprintf(m_lancelog, "# kind port val pc cyc t -- every LANCE RAP/RDP/ROM access\n");
|
||||
}
|
||||
|
||||
if (const char *p = getenv("VT1200_LANCE_DMA_LOG"))
|
||||
{
|
||||
m_lance_dmalog = fopen(p, "w");
|
||||
if (m_lance_dmalog)
|
||||
fprintf(m_lance_dmalog,
|
||||
"# kind byteaddr value mask pc cyc t -- every LANCE bus-master DMA word access\n"
|
||||
"# 'r' = chip read (dma_in), 'w' = chip write (dma_out)\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;
|
||||
@@ -1408,6 +1429,16 @@ void vt1200_state::log_lance(char kind, const char *what, u16 value)
|
||||
fflush(m_lancelog);
|
||||
}
|
||||
|
||||
void vt1200_state::log_lance_dma(char kind, u32 addr, u16 value, u16 mask)
|
||||
{
|
||||
if (!m_lance_dmalog)
|
||||
return;
|
||||
fprintf(m_lance_dmalog, "%c %06X %04X %04X pc=%08X cyc=%llu t=%.6f\n",
|
||||
kind, addr, value, mask, (unsigned)m_maincpu->state_int(TMS34010_PC),
|
||||
(unsigned long long)m_maincpu->total_cycles(), machine().time().as_double());
|
||||
fflush(m_lance_dmalog);
|
||||
}
|
||||
|
||||
// Copy one active scanline out of the video RAM framebuffer.
|
||||
//
|
||||
// The TMS34010 screen-refresh address is reconstructed by the device into
|
||||
@@ -1705,12 +1736,19 @@ void vt1200_state::vt1200(machine_config &config)
|
||||
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;
|
||||
u16 v;
|
||||
if (w < 0x00200000) // 4 MiB of main RAM = 0x200000 words
|
||||
return m_mainram[w];
|
||||
log_lance('d', "DMA-RANGE", u16(w >> 16));
|
||||
return 0x0000;
|
||||
v = m_mainram[w];
|
||||
else
|
||||
{
|
||||
log_lance('d', "DMA-RANGE", u16(w >> 16));
|
||||
v = 0x0000;
|
||||
}
|
||||
log_lance_dma('r', u32(o), v, 0xffff);
|
||||
return v;
|
||||
});
|
||||
m_lance->dma_out().set([this](offs_t o, u16 data, u16 mem_mask) {
|
||||
log_lance_dma('w', u32(o), data, mem_mask);
|
||||
const offs_t w = o >> 1;
|
||||
if (w < 0x00200000)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user