Files
vt1200/reference/lance-loopback-fcs.md
T
SDD 7612fcbda9 reference: LANCE loopback-FCS datasheet evidence (MAME condition is right)
Add the Am7990 Technical Manual and Am79C90 datasheet to
reference/lance-datasheet/ and a report, reference/lance-loopback-fcs.md,
that answers whether MAME's am79c90 receive() FCS condition
(!(MODE_LOOP) || MODE_DTCR) is inverted.

Verdict: MAME is right. The CRC generator and checker share one circuit,
so with LOOP=1 the receiver can only check the FCS when DTCR=1 -- exactly
what MAME does. The proposed inversion would be wrong in both loopback
cases. No MAME change made.
2026-10-16 16:46:47 +03:00

311 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LANCE loopback FCS: is MAME's `MODE_DTCR` term inverted?
Date: 2026-10-16. Investigation only. ROM `vt1200_v22.bin` md5
`b46a3e442ccb86134d2356bd70d22fd2` (untouched). Nothing in the MAME tree or in
`harness/mame/` was changed. Datasheets downloaded to
`reference/lance-datasheet/` (originals + `pdftotext` extractions).
## Verdict (short)
**MAME is right. The condition is NOT inverted.**
`src/devices/machine/am79c90.cpp:263`
```c
if (!(m_mode & MODE_LOOP) || (m_mode & MODE_DTCR))
```
is a byte-for-byte implementation of what both the Am7990 and the Am79C90
datasheets say. The reason it "reads" as inverted is that the CRC generator and
the CRC checker are **one shared circuit**, so the receiver can only check the CRC
when the transmitter is **not** using the generator — i.e. exactly when `DTCR = 1`.
The proposed "fix" (`(m_mode & MODE_DTCR)` → `!(m_mode & MODE_DTCR)`) would be
wrong in **both** loopback cases.
The `LANCE_REPORT.md` §5 diagnosis is a misreading of the hardware. See
[Consequence for the VT1200](#consequence-for-the-vt1200) below.
---
## The two parts, and whether they differ
| part | document used | loopback-CRC rule |
|---|---|---|
| **Am7990** LANCE | Am7990 Technical Manual, Aug 1986 | "no CRC check ... since the CRC logic is shared"; with DTCR=1 the receiver checks |
| **Am79C90** C-LANCE | Am79C90 datasheet | identical wording |
They do **not** differ on this question. They differ in one other place only:
the C-LANCE adds the `ADD_FCS` bit (TMD1 bit 13) that can override `DTCR=1` on a
per-packet basis ("The ADD_FCS bit (bit 13, TMD1) can be used to override a
DTCR=1 setting on a per packet basis."). MAME already gates that on
`type() == AM79C90` (`transmit()`). The unsettled Am7990-vs-Am79C90 question does
**not** affect the loopback-FCS verdict. (Neither part is settled for the VT1200;
that is out of scope here.)
---
## The decisive quotes
### Am7990 Technical Manual (1986), Mode register, printed p. 1-24 (PDF p. 31)
> **03 DTCR** DISABLE TRANSMIT CRC. When DTCR = 0, the transmitter generates and
> appends a CRC to the transmitted packet. When DTCR = 1, the CRC logic is
> allocated to the receiver and no CRC is generated or sent with the transmitted
> packet.
>
> During loopback, DTCR = O will cause a CRC to be generated on the transmitted
> packet, but **no CRC check is done by the receiver since the CRC logic is shared
> and cannot generate and check CRC at the same time.** The generated CRC is
> written into memory with the data and can be checked by the host software.
>
> **If DTCR = 1 during loopback, the host software must append a CRC value to the
> transmit data. The receiver checks the CRC on the received data and reports any
> errors.** Since the CRC generator is used to generate the hash filter, the
> multicast addressing cannot be used when DTCR = 1.
> **02 LOOP** LOOPBACK allows the LANCE to operate in full duplex mode for test
> purposes. The packet size is 8-32 bytes. The received packet can be up to 36
> bytes (32 + 4 bytes CRC) **when DTCR=O**. During loopback, the runt packet filter
> is disabled because the packet is smaller than the minimum size Ethernet packet
> (64 bytes).
### Am7990 Technical Manual (1986), §1.8.3 "CYCLIC REDUNDANCY CHECK (CRC)",
printed p. 1-12 (PDF p. 19)
> The LANCE requirements for the CRC logic are:
>
> 1. **TRANSMISSION - Mode bit 2 (LOOP) = 0, Mode bit 3 (DTCR) = 0.** The LANCE
> calculates the CRC from the first bit following the Start bit to the last bit
> of the data field. The CRC value inverted is appended to the transmission in
> one unbroken bit stream.
> 2. **RECEPTION - Mode bit 2 (LOOP) = 0.** The LANCE performs a check on the input
> bit stream from the first bit following the Start bit to the last bit in the
> frame. The LANCE continually samples the state of the CRC check on framed byte
> boundaries and, when the incoming bit stream stops, the last sample determines
> the state of the CRC error. Framing error (FRAM) is not reported if there is
> no CRC error.
> 3. **LOOPBACK - Mode bit 2 (LOOP) = 1, Mode bit 3 (DTRC) = 0.** The LANCE generates
> and appends the CRC value to the outgoing bit stream as in transmission but
> **does not perform the CRC check of the incoming bit stream.**
> 4. **LOOPBACK - Mode bit 2 (LOOP) = 1, Mode bit 3 (DTRC) = 1.** The LANCE
> **performs the CRC check on the incoming bit stream as in reception** but does
> not generate or append the CRC value to the outgoing bit stream during
> transmission.
(Note the OCR/typo `DTRC` for `DTCR` in bullets 3–4; the bit is DTCR.)
And, from the LOOPBACK constraints (§1.8.3, item 4):
> CRC may be either generated and appended to the output serial bit stream or may
> be checked on the input serial bit stream **but not both in the same
> transaction.**
### Am7990 Technical Manual (1986), §4.3.3 "CRC GENERATOR/CHECKER LOGIC TEST",
printed p. 4-4 (PDF p. 51)
> During the loopback test (internal or external), the CRC logic may be tested.
> Since the LANCE operates in half-duplex mode, the CRC generator or checker must
> be tested separately as follows. The Transmit CRC Generator is tested by
> programming DTCR=O in the Mode Register. This enables the transmit CRC
> generation and **disables the Receive CRC Checker.** ...
>
> The Receive CRC Checker is tested by setting the DTCR bit in the Mode Register.
> The user then calculates the CRC for the test packet **and appends it to the end
> of the test packet for transmission** (test packet max size= 32 + 4 bytes CRC).
> The Receive CRC Checker checks the last four bytes of the packet for CRC error
> and updates the receive descriptors accordingly.
### Am79C90 C-LANCE datasheet, Mode register bit 03 DTCR, printed p. 24 (PDF p. 24)
> **03 DTCR** DISABLE TRANSMIT CRC. When DTCR = 0, the transmitter will generate
> and append a CRC to the transmitted packet. When DTCR = 1, the CRC logic is
> allocated to the receiver and no CRC is generated and sent with the transmitted
> packet. The ADD_FCS bit (bit 13, TMD1) can be used to override a DTCR=1 setting
> on a per packet basis.
>
> During loopback, DTCR = 0 will cause a CRC to be generated on the transmitted
> packet, but **no CRC check will be done by the receiver since the CRC logic is
> shared and cannot generate and check CRC at the same time.** The generated CRC
> will be written into memory with the data and can be checked by the host
> software.
>
> **If DTCR = 1 during loopback, the host software must append a CRC value to the
> transmit data. The receiver will check the CRC on the received data and report
> any errors.**
### Am79C90 C-LANCE datasheet, "Cyclic Redundancy Check (CRC)", printed p. 35
> 2. **RECEPTION – MODE <02> LOOP = 0.** The C-LANCE performs a check on the input
> bit stream from the first bit following the SFD to the last bit in the frame. ...
> 3. **LOOPBACK – MODE <02> LOOP =1, MODE <03> DTRC = 0.** The C-LANCE generates and
> appends the CRC value to the outgoing bit stream as in Transmission but **does
> not perform the CRC check of the incoming bit stream.**
> 4. **LOOPBACK – MODE <02> LOOP = 1 MODE <03> DTRC = 1.** C-LANCE **performs the CRC
> check on the incoming bit stream as in Reception**, but does not generate or
> append the CRC value to the outgoing bit stream during transmission.
> CRC may not be used for both transmission and reception simultaneously.
---
## Question 2 — does the receiver's CRC check apply to a frame that carries no FCS?
**The check is tied to the MODE bits, not to whether the frame's bytes happen to
look like they end in an FCS.** The hardware has one shared CRC circuit, so the
rule is purely `(LOOP, DTCR)`:
| LOOP | DTCR | chip checks RX FCS? | which datasheet bullet |
|---|---|---|---|
| 0 | 0 | **yes** | 2 (RECEPTION, `LOOP=0`) |
| 0 | 1 | **yes** | 2 (RECEPTION, `LOOP=0`; DTCR only affects the transmitter) |
| 1 | 0 | **no** | 3 (LOOPBACK, generator owns the shared circuit) |
| 1 | 1 | **yes** | 4 (LOOPBACK, checker owns the shared circuit) |
In the one loopback case where the chip checks (`LOOP=1, DTCR=1`) the datasheet
**requires the host to put the FCS there** ("the host software must append a CRC
value to the transmit data"). So the chip is not "checking a frame with no FCS"
by design — it is checking a frame the host was required to FCS-terminate. If the
host breaks that contract, the chip reports a CRC error, correctly.
MAME's condition reproduces the table exactly:
| LOOP | DTCR | MAME `!(LOOP) \|\| DTCR` | chip (per datasheet) |
|---|---|---|---|
| 0 | 0 | check | check |
| 0 | 1 | check | check |
| 1 | 0 | skip | skip |
| 1 | 1 | check | check |
MAME's FCS arithmetic was also checked independently. `FCS_RESIDUE = 0xdebb20e3`
(`am79c90.h:50`) and `crc32_creator::simple()` is zlib's `crc32()` (init
`0xffffffff`, final complement). Over a frame `M || FCS` with a correct
little-endian FCS, zlib returns `0x2144df1c`, and `~0x2144df1c == 0xdebb20e3`.
So `~crc != FCS_RESIDUE` **passes a correctly-terminated frame** and fails one
that is not. There is nothing wrong with the arithmetic either.
---
## Question 3 — upstream MAME history
The shallow 0.289 checkout has no file history, so the history was read from the
upstream GitHub repository (`mamedev/mame`).
* Condition **introduced** in `3349e3e990ab` (2018-11-22, *"am79c90: fixes
(nw)"*): `!(LOOP) || !(INTL) || DTCR`.
* **Changed** in `67f9c842e9f2` (2018-11-25, *"am79c90: many fixes (nw)"*), whose
message says **"corrected internal loopback behaviour"**, to the current
`!(LOOP) || DTCR`. The diff drops the `!(INTL)` term:
```diff
- if (!(m_mode & MODE_LOOP) || !(m_mode & MODE_INTL) || (m_mode & MODE_DTCR))
+ if (!(m_mode & MODE_LOOP) || (m_mode & MODE_DTCR))
```
Dropping `!(INTL)` makes external loopback follow the same rule as internal
loopback — which is what the datasheet bullets 3/4 describe (they are written
for `LOOP`, not `INTL`). So the change moved MAME **toward** the datasheet, not
away from it.
The commit message does not quote a datasheet, but the resulting term matches
both datasheets exactly, so no further history is needed to accept it.
---
## Question 4 — verdict
**MAME is right; the check is not inverted.** No change is warranted.
The change proposed in `LANCE_REPORT.md` §5 —
```c
// PROPOSED, DO NOT APPLY
if (!(m_mode & MODE_LOOP) || !(m_mode & MODE_DTCR))
```
— would be wrong in both loopback cases: it would **skip** the check for
`LOOP=1, DTCR=1` (datasheet bullet 4: the chip checks) and **perform** it for
`LOOP=1, DTCR=0` (datasheet bullet 3: the chip does not check).
**Revert note (if anyone applies that change anyway):** it contradicts the four
`MODE`-bit CRC requirements quoted above from the Am7990 Technical Manual
p. 1-12 and the Am79C90 datasheet p. 35, and it would make MAME's receiver
diverge from the part in both loopback modes. Revert to
`!(m_mode & MODE_LOOP) || (m_mode & MODE_DTCR)` and keep the citation.
---
## Question 6 — when is the check skipped in normal (non-loopback) receive?
**Never, on this chip.** Datasheet bullet 2 is `RECEPTION - Mode bit 2 (LOOP) = 0`
with **no DTCR term**: outside loopback the receiver always runs the FCS check,
and `DTCR` speaks only about the *transmitter*. The FCS's presence in a normal
frame is the remote sender's business; the local `DTCR` bit says nothing about it.
So `!(m_mode & MODE_LOOP)` (check whenever `LOOP=0`) is the correct half of the
condition, and it must not be tightened to `!(LOOP) && !(DTCR)` or similar.
---
## Consequence for the VT1200
`harness/mame/LANCE_REPORT.md` §4.1 records the firmware's own `C - LANCE TEST`
init block as:
```
mode = 0x004C = MODE_LOOP | MODE_INTL | MODE_DTCR
```
Per the datasheet, `LOOP=1, DTCR=1` is **bullet 4**: the receiver **does** check
the FCS, and the host is **required** to append it. MAME therefore behaves like
the part when it reports `RCV DESCR 1 = 0x4B00` (`ERR|CRC|STP|ENP`). The §5
inference — "the real chip must not fail the receive CRC ... i.e. the check should
be skipped whenever the frame carries no FCS" — does **not** follow from the
hardware; it assumes the frame carries no FCS, which is exactly the contract the
datasheet forbids when `DTCR=1`.
So the open question is really: **does the firmware's test frame carry a
4-byte FCS or not?**
* If it does **not**, the real LANCE flags a CRC error too, the `C - LANCE TEST`
fails on real hardware, and MAME is faithful (the failure is the test's, and a
level-1/self-test failure is non-fatal, per `LEVEL1_CONTINUE_REPORT.md`).
* If it **does**, then MAME's FCS arithmetic (verified above) should pass it, and
any mismatch is a **different** MAME bug — not this condition — worth chasing
separately.
`LANCE_REPORT.md` §5 assumed the first bullet ("DTCR means no FCS was appended")
without checking whether the *firmware* appends one; the datasheet says the
firmware is obliged to. That is the gap, and it is the thing to observe on the
real machine.
### What the real machine would have to show to settle it
A boot does **not** settle it: a level-1/self-test failure is reported and the
boot continues (`LEVEL1_CONTINUE_REPORT.md`, and `README.md` "How far the OS
gets"), so "the terminal boots" proves nothing about the LANCE test.
To demonstrate it, read the **LANCE test's own result**, not the boot outcome:
1. **Run the terminal's on-board diagnostics and read the specific LANCE result**
(the `C - LANCE TEST` / "Ethernet port (LANCE) test", error 24). A displayed
**pass** means the loopback frame the chip received was FCS-terminated (the
host appended it, as the datasheet requires) — which is consistent with the
datasheet and with MAME being right, and would mean MAME's failure is a
different bug. A displayed **CRC error** means MAME is not only right about the
rule, it is faithful about this very test.
2. **Stronger and independent of the firmware's verdict:** capture what the chip
actually receives. Dump the transmit buffer the test builds (or watch the RX
descriptor / bus on a real machine) and check for **4 trailing FCS bytes**. If
present, the chip checks a terminated frame; if absent, the chip flags CRC.
Note: the task's premise "if it passes ... then the real chip does not reject a
loopback frame that carries no FCS" is backwards for this part. The datasheet says
the chip **does** check when `DTCR=1`; a real-hardware *pass* would therefore
prove the frame **carries** an FCS, not that the check is disabled.
---
## Assets
Downloaded originals and extractions in `reference/lance-datasheet/` — see that
directory's `README.md` for provenance and SHA-256 checksums.