mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
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.
311 lines
14 KiB
Markdown
311 lines
14 KiB
Markdown
# 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.
|