reference: DUART datasheet verification of the two mc68681 edits (claim 1 refuted)

Download the SCN2681 (Philips 1995), SCC2681, SCN68681 and MC68HC681 UM
datasheets into reference/duart-datasheet/ with pdftotext extractions, and
audit each claimed justification against them:

  Claim 1 (TxRDY asserted in tra_complete, not tra_callback): REFUTED.
    The quoted p.7 sentence is real, but SCN2681 p.22 says explicitly
    'TxRDY sets at the end of the start bit time' -- which is exactly the
    removed upstream behaviour.  Upstream was right.

  Claim 2 (overrun latched on the start bit, not completion): CONFIRMED.
    'the overrun error status bit (SR[4]) will be set upon receipt of the
    start bit of the new (overrunning) character' (p.8, also SRA[4] p.14).
    Caveats: p.8 also says errors are 'strobed ... at the received
    character boundary' (internal tension); and a valid start bit is
    validated ~half a bit after the falling edge, not at it.

  Claim 3 (3-deep FIFO, 4th held, overrun on 5th; MAME faithful): CONFIRMED.
    'capacity of three characters' + 4th held in the shift register (p.8);
    MAME's SIZE+1 guard overruns exactly on the 5th.

Also snapshot the worker's uncommitted MAME diff and upstream 0.289 code.
This commit is contained in:
SDD committed 2026-10-16 12:13:07 +03:00
1 parent 9902722165
commit 031d30da67
14 files changed
+28372

No files matched your search

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,121 @@
diff --git a/src/devices/machine/mc68681.cpp b/src/devices/machine/mc68681.cpp
index 70d3cfd0bb30..077cc0a0d38e 100644
--- a/src/devices/machine/mc68681.cpp
+++ b/src/devices/machine/mc68681.cpp
@@ -1326,6 +1326,7 @@ duart_channel::duart_channel(const machine_config &mconfig, const char *tag, dev
, SR(0)
, rx_enabled(0)
, rx_fifo_num(0)
+ , m_rx_line(true)
, m_tx_data_in_buffer(false)
, m_tx_break(false)
, m_bits_transmitted(255)
@@ -1357,6 +1358,26 @@ void duart_channel::device_start()
save_item(NAME(m_tx_break));
save_item(NAME(m_bits_transmitted));
save_item(NAME(m_tx_enabled));
+ save_item(NAME(m_rx_line));
+}
+
+// VT1200 NOTE (local change, not upstream): the datasheet latches the overrun
+// error "upon receipt of the start bit of the new (overrunning) character",
+// i.e. while the FIFO is full and the previous character is still held in the
+// receive shift register. MAME's rx_fifo_push() latches it only when that
+// character is fully received, a whole character late. Watching the RxD line
+// here reproduces the earlier latch time. Revert: delete this override, delete
+// the m_rx_line member, and restore the device_serial_interface::rx_w calls in
+// mc68681.h's rx_a_w/rx_b_w/rx_c_w/rx_d_w.
+void duart_channel::rx_w(int state)
+{
+ if (state == 0 && m_rx_line && (rx_fifo_num == MC68681_RX_FIFO_SIZE + 1))
+ {
+ SR |= STATUS_OVERRUN_ERROR;
+ update_interrupts();
+ }
+ m_rx_line = (state != 0);
+ device_serial_interface::rx_w(state);
}
void duart_channel::device_reset()
@@ -1451,6 +1472,18 @@ void duart_channel::tra_complete()
else
{
SR |= STATUS_TRANSMITTER_EMPTY;
+ // VT1200 NOTE (local change, not upstream): also assert TxRDY here. The
+ // VT1200 level-1 host-port test runs an interrupt-driven loopback: the
+ // DUART ISR writes the pattern byte on TxRDYA and, per transmit, reads
+ // SRA and expects the RX FIFO to have filled in step (FFULL before the
+ // 5th byte, OVR after it). Asserting TxRDY mid-character (see the
+ // removed block in tra_callback below) made the ISR one byte ahead of
+ // the receiver, so the snapshots were FFULL/OVR one index too late and
+ // the test failed. Asserting TxRDY when the shift register empties
+ // (the THR->TSR transfer point) makes the ISR pace one byte per
+ // character. Revert: delete the STATUS_TRANSMITTER_READY term below
+ // and restore the removed block in tra_callback.
+ SR |= STATUS_TRANSMITTER_READY;
update_interrupts();
}
}
@@ -1485,12 +1518,21 @@ void duart_channel::tra_callback()
rx_w(transmit_register_get_data_bit());
}
- // TxRDY is not set until the end of start bit time
- if (++m_bits_transmitted > 1 && !m_tx_data_in_buffer && m_tx_enabled)
- {
- SR |= STATUS_TRANSMITTER_READY;
- update_interrupts();
- }
+ // VT1200 NOTE (local change, not upstream): the upstream code asserted
+ // TxRDY here ("not until the end of start bit time") when no byte was
+ // buffered. The datasheet instead asserts TxRDY at the THR->TSR transfer
+ // ("Data is transferred from the holding register to transmit shift
+ // register when it is idle or has completed transmission of the previous
+ // character. The TxRDY conditions are then asserted again"), which is the
+ // character boundary for a buffered byte -- not mid-character. Asserting
+ // on the start bit made the VT1200 loopback test's ISR run one byte ahead
+ // of the receiver. Revert: remove the increment below and restore
+ // if (m_bits_transmitted++ > 0 && !m_tx_data_in_buffer && m_tx_enabled)
+ // {
+ // SR |= STATUS_TRANSMITTER_READY;
+ // update_interrupts();
+ // }
+ m_bits_transmitted++;
}
void duart_channel::update_interrupts()
diff --git a/src/devices/machine/mc68681.h b/src/devices/machine/mc68681.h
index d5476e822c2f..c1e09db44d94 100644
--- a/src/devices/machine/mc68681.h
+++ b/src/devices/machine/mc68681.h
@@ -31,6 +31,11 @@ public:
void write_chan_reg(int reg, uint8_t data);
void update_interrupts();
+ // VT1200 NOTE (local change, not upstream): saw the RxD line so the overrun
+ // error can be latched on the start bit of the overrunning character (the
+ // datasheet's timing), rather than at its completion in rx_fifo_push().
+ void rx_w(int state);
+
void rx_fifo_push(uint8_t data, uint8_t errors);
uint8_t read_rx_fifo();
@@ -67,6 +72,7 @@ private:
int rx_fifo_read_ptr;
int rx_fifo_write_ptr;
int rx_fifo_num;
+ bool m_rx_line; // VT1200 NOTE (local): previous RxD line state
int m_ch;
@@ -108,8 +114,8 @@ public:
virtual uint8_t read(offs_t offset);
virtual void write(offs_t offset, uint8_t data);
- void rx_a_w(int state) { m_chanA->device_serial_interface::rx_w((uint8_t)state); }
- void rx_b_w(int state) { m_chanB->device_serial_interface::rx_w((uint8_t)state); }
+ void rx_a_w(int state) { m_chanA->rx_w((uint8_t)state); }
+ void rx_b_w(int state) { m_chanB->rx_w((uint8_t)state); }
auto irq_cb() { return write_irq.bind(); }
auto a_tx_cb() { return write_a_tx.bind(); }
@@ -0,0 +1,52 @@
// license:BSD-3-Clause
// copyright-holders:Mariusz Wojcieszek, R. Belmont
#ifndef MAME_MACHINE_MC68681_H
#define MAME_MACHINE_MC68681_H
#pragma once
#include "diserial.h"
#define MC68681_RX_FIFO_SIZE 3
// forward declaration
class duart_base_device;
// duart_channel class
class duart_channel : public device_t, public device_serial_interface
{
public:
duart_channel(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
/* State */
int tx_baud_rate, rx_baud_rate;
/* Receiver */
uint8_t rx_enabled;
uint16_t rx_fifo[MC68681_RX_FIFO_SIZE + 1];
int rx_fifo_read_ptr;
int rx_fifo_write_ptr;
int rx_fifo_num;
int m_ch;
/* Transmitter */
uint8_t m_tx_data;
bool m_tx_data_in_buffer;
bool m_tx_break;
uint8_t m_bits_transmitted;
bool m_tx_enabled;
/* Rx/Tx clocking */
void rx_a_w(int state) { m_chanA->device_serial_interface::rx_w((uint8_t)state); }
void rx_b_w(int state) { m_chanB->device_serial_interface::rx_w((uint8_t)state); }
auto irq_cb() { return write_irq.bind(); }
auto a_tx_cb() { return write_a_tx.bind(); }
auto b_tx_cb() { return write_b_tx.bind(); }
auto inport_cb() { return read_inport.bind(); } // deprecated: use ipX_w() instead
auto outport_cb() { return write_outport.bind(); }
// new-style push handlers for input port bits
@@ -0,0 +1,117 @@
{
uint8_t errors = 0;
if (is_receive_framing_error())
errors |= STATUS_FRAMING_ERROR;
if (is_receive_parity_error())
errors |= STATUS_PARITY_ERROR;
rx_fifo_push(get_received_char(), errors);
}
}
void duart_channel::rx_fifo_push(uint8_t data, uint8_t errors)
{
if (rx_fifo_num == (MC68681_RX_FIFO_SIZE + 1))
{
logerror("68681: FIFO overflow\n");
SR |= STATUS_OVERRUN_ERROR;
// In case of overrun the FIFO tail entry is overwritten
// Back rx_fifo_write_ptr up by one position
if (rx_fifo_write_ptr)
rx_fifo_write_ptr--;
else
rx_fifo_write_ptr = MC68681_RX_FIFO_SIZE;
}
rx_fifo[rx_fifo_write_ptr++] = data | (errors << 8);
if (rx_fifo_write_ptr > MC68681_RX_FIFO_SIZE)
rx_fifo_write_ptr = 0;
if (rx_fifo_num <= MC68681_RX_FIFO_SIZE)
{
rx_fifo_num++;
}
if (rx_fifo_num == 1)
{
SR |= STATUS_RECEIVER_READY;
if (!(MR1 & MODE_BLOCK_ERROR))
SR &= ~(STATUS_RECEIVED_BREAK | STATUS_FRAMING_ERROR | STATUS_PARITY_ERROR);
SR |= errors;
}
if (rx_fifo_num == MC68681_RX_FIFO_SIZE)
SR |= STATUS_FIFO_FULL;
update_interrupts();
}
void duart_channel::tra_complete()
{
// The transmit shift register has just emptied. If another character is
// waiting in the THR, transfer it to the shift register and assert TxRDY:
// per the datasheet the THR->shift transfer is where the TxRDY conditions
// are re-asserted, providing "one full character time of buffering". If the
// THR is empty the transmitter is now idle, so set TxEMT (TxRDY is already
// set from the previous transfer).
//
// Asserting TxRDY here, at the transfer -- rather than in the idle branch --
// both prevents the transmitter from deadlocking when TxRDY would otherwise
// fail to re-assert on a CPU-write / final-bit-time race, and preserves the
// one-character buffering window that firmware polling or pipelining on TxRDY
// relies on.
if (m_tx_data_in_buffer)
{
transmit_register_setup(m_tx_data);
m_bits_transmitted = 0;
m_tx_data_in_buffer = false;
SR |= STATUS_TRANSMITTER_READY;
update_interrupts();
}
else
{
SR |= STATUS_TRANSMITTER_EMPTY;
update_interrupts();
}
}
void duart_channel::tra_callback()
{
// don't actually send in loopback mode
if ((MR2 & 0xc0) != 0x80)
{
int bit = transmit_register_get_data_bit();
//printf("%s ch %d transmit %d\n", tag(), m_ch, bit);
if (m_ch == 0)
{
m_uart->write_a_tx(bit);
}
else if (m_ch == 1)
{
m_uart->write_b_tx(bit);
}
else if (m_ch == 2)
{
m_uart->write_c_tx(bit);
}
else if (m_ch == 3)
{
m_uart->write_d_tx(bit);
}
}
else
{
// loop back transmitted bit
rx_w(transmit_register_get_data_bit());
}
// TxRDY is not set until the end of start bit time
if (++m_bits_transmitted > 1 && !m_tx_data_in_buffer && m_tx_enabled)
{
SR |= STATUS_TRANSMITTER_READY;
update_interrupts();
}
}
void duart_channel::update_interrupts()
{
// Handle the TxEMT and TxRDY bits based on mode
switch (MR2 & 0xc0) // what mode are we in?
{
+209
View File
@@ -0,0 +1,209 @@
# DUART datasheet verification — SCN2681 / MC68681
Subject: the two "datasheet-backed" MAME `mc68681.cpp/.h` edits (commit
`9902722165fc`) that make the VT1200 level-1 HOST PORT self-test progress, plus the
incidental FIFO-depth claim.
**This verification is done against the datasheet, not against the code.** The MAME
tree (`/home/arti/code/k-space/mame0289`) was reverted and re-modified by another
worker several times during this audit; a snapshot of the modified diff and of upstream
0.289 is kept under `reference/duart-datasheet/`.
## Sources used
| File | What it is |
|---|---|
| `reference/duart-datasheet/SCN2681-transputer.pdf` (+ `.txt`) | **Primary.** Philips Semiconductors **SCN2681** DUART product specification, dated **1995 May 1**. The part actually fitted to the VT1200 is the Signetics/Philips SCN2681. |
| `reference/duart-datasheet/SCC2681-nxp.pdf` (+ `.txt`) | Philips **SCC2681** datasheet, 2004 Apr 06 (same family, later rev) — corroboration. |
| `reference/duart-datasheet/SCN68681-xdevs.pdf` (+ `.txt`) | Signetics **SCN68681** datasheet — corroboration. |
| `reference/duart-datasheet/MC68681UM-nxp.pdf` (+ `.txt`) | Motorola **MC68HC681 User's Manual** (MC68681 / MC2681 family) — the Motorola-flavoured text, for the cross-family comparison. |
| `reference/duart-datasheet/MC2681-motorola-retroweb.pdf`, `SCN2681-signetics-tcd.pdf` | Additional copies (scanned / image-only; kept for provenance). |
| `reference/duart-datasheet/mc68681-VT1200-worker-diff.patch` | Snapshot of the worker's uncommitted MAME diff. |
| `reference/duart-datasheet/mc68681-upstream-0.289-transmit.txt` | Pristine upstream (MAME 0.289 clone commit `f34f02505e32`) transmit/RX code. |
Page numbers below are the **printed page numbers** in the Philips SCN2681 document
(each page footer reads `1995 May 1 <n>`).
## Summary
| # | Claim | Verdict |
|---|---|---|
| 1 | TxRDY must be asserted in `tra_complete()` (at the THR→TSR transfer / character boundary), because "Data is transferred … when it is idle or has completed transmission of the previous character. The TxRDY conditions are then asserted again", so upstream's `tra_callback()` assertion ("not until the end of start bit time") was wrong. | **REFUTED** |
| 2 | The overrun error is latched on the **start bit** of the overrunning character, not when that character completes; MAME's `rx_fifo_push()` latch is a whole character late. | **CONFIRMED** (one caveat) |
| 3 | MAME's receive FIFO is faithful: 3-deep, a 4th character held, overrun on the 5th. | **CONFIRMED** |
---
## Claim 1 — TxRDY assertion point — **REFUTED**
**The cited sentence is real.** SCN2681, printed page **7**, section **"TRANSMITTER
OPERATION"** (also page 3-2 of the MC68HC681 User's Manual):
> "The SCN2681 indicates to the CPU that it is ready to accept a character by setting
> the TxRDY bit in the status register. … When a character is loaded into the Transmit
> Holding Register (THR), the above conditions are negated. **Data is transferred from
> the holding register to transmit shift register when it is idle or has completed
> transmission of the previous character. The TxRDY conditions are then asserted
> again** which means one full character time of buffering is provided."
**But that sentence does not establish *when*, and the datasheet does establish it —
in the opposite direction.** SCN2681, printed page **22**, section
**"The CTS, RTS, CTS Enable Tx signals" → "Transmitter Disable Note"**:
> "**TxRDY sets at the end of the "start bit" time. It is during the start bit that the
> data in the transmit holding register is transferred to the transmit shift
> register.**"
The same sentence appears verbatim in the 2004 Philips SCC2681 (p. 22) and the
Signetics SCN68681. It is the datasheet's only statement of TxRDY timing **relative to
the start bit**, and it agrees with the code the worker removed.
**Upstream was right.** The removed block was:
```c++
// TxRDY is not set until the end of start bit time
if (++m_bits_transmitted > 1 && !m_tx_data_in_buffer && m_tx_enabled)
{
SR |= STATUS_TRANSMITTER_READY;
update_interrupts();
}
```
`m_bits_transmitted` is reset to 0 when a character is loaded into the transmit shift
register (`write_TX` idle case, and `tra_complete` transfer case). `tra_callback()` is
called once per transmit-clock edge and the first call emits the **start bit**
(`device_serial_interface::transmit_register_get_data_bit()` returns the frame MSB
first). So the condition `++m_bits_transmitted > 1` first fires on the **second** edge —
i.e. at the **end of the start-bit interval**. Upstream's comment is a near-verbatim
restatement of the datasheet sentence on page 22, and upstream implemented it
correctly.
**What the citation actually describes** is the THR→TSR transfer — the same event
upstream's `tra_complete()` **buffered** branch already handled ("Data is transferred …
when … it has completed transmission of the previous character"). It says nothing about
deferring the assertion to full character completion, which is what the worker's change
does (it removes the page-22 assertion and relies on `tra_complete()` alone). Asserting
TxRDY at `tra_complete()` moves it **~1 character earlier** than the datasheet's
"end of the start bit time".
**Cross-family note (as requested):** the Motorola **MC68HC681 User's Manual** does
*not* contain the "end of the start bit time" sentence — it only says (§4.3.9.6,
SRA[2]) "Transmitter ready is set when the character is transferred to the transmit
shift register." On this point the Motorola text is the weaker/less specific of the
two. The VT1200 part is the **SCN2681 (Philips/Signetics)**, so page 22 governs, and it
refutes the claim.
**Assessment:** the quoted sentence exists and is accurately transcribed, but it does
not justify the change; the datasheet's explicit TxRDY-timing sentence supports the
behaviour that was removed.
---
## Claim 2 — Overrun latch point — **CONFIRMED** (with one caveat)
**The cited wording is real and explicit.** SCN2681, printed page **8**, section
**"Receiver FIFO"**:
> "The RHR consists of a First-In-First-Out (FIFO) stack with a capacity of three
> characters. … If the FIFO is full when a new character is received, that character is
> held in the receive shift register until a FIFO position is available. If an
> additional character is received while this state exits[sic], the contents of the FIFO
> are not affected; the character previously in the shift register is lost and the
> **overrun error status bit (SR[4] will be set-upon receipt of the start bit of the new
> (overrunning) character)**."
Corroborated by the SRA[4] definition, SCN2681 printed page **14**:
> "SRA[4] – Channel A Overrun Error — This bit, when set indicates that one or more
> characters in the received data stream have been lost. **It is set upon receipt of a
> new character when the FIFO is full and a character is already in the receive shift
> register waiting for an empty FIFO position.**"
and by the MC68HC681 User's Manual (§3.2, p. 3-3/3-4):
> "… the character previously in the shift register is lost and the **overrun-error
> status bit will be set upon receipt of the start bit of the new overrunning
> character**."
So the datasheet **does** say "start bit", not "when the character has been received".
MAME's original `rx_fifo_push()` latches `STATUS_OVERRUN_ERROR` only when the overrunning
character has been **fully received** (it is called from `rcv_complete()`), i.e. ~one
character late. The worker's direction of correction is datasheet-backed.
**Caveat 1 (datasheet is internally in tension).** SCN2681 page 8 also says:
> "The parity error, framing error, overrun error and received break state (if any) are
> **strobed into the SR at the received character boundary**, before the RxRDY status
> bit is set."
Taken alone this reads like completion timing. It is a general statement about all four
error/break flags; the overrun-specific sentence ("upon receipt of the start bit") is the
more specific and repeated one (it appears in all four datasheets), and SRA[4] repeats
"upon receipt of a new character". So the start-bit reading is the better-supported one —
but the datasheet is not perfectly self-consistent on this point, and a reader could be
forgiven for the completion reading.
**Caveat 2 (which "start bit"?).** The datasheet defines a start bit as *received* only
after it is validated: "the state of the RxD pin is sampled each 16X clock for 7 1/2
clocks … If RxD is still Low, a valid start bit is assumed" (p. 8). "Upon receipt of the
start bit" therefore means ~½ bit time **after** the falling edge, not at the falling
edge itself. The worker's `duart_channel::rx_w()` latches on the falling edge
(`state == 0`), i.e. about half a bit **earlier** than the datasheet. For the overrun
condition this is inconsequential, but it is not a literal implementation of the text.
**Assessment:** confirmed. The datasheet explicitly latches overrun at the start bit of
the overrunning character; MAME's completion-time latch was late. (Minor caveats above.)
---
## Claim 3 — FIFO depth — **CONFIRMED**
**Datasheet.** SCN2681, printed page **8**, section **"Receiver FIFO"**:
> "The RHR consists of a First-In-First-Out (FIFO) stack with a **capacity of three
> characters**."
and, immediately after (same page):
> "If the FIFO is full when a new character is received, **that character is held in the
> receive shift register** until a FIFO position is available. If an additional
> character is received while this state exits[sic] … the **overrun** error status bit
> (SR[4]) will be set …"
So: three characters in the FIFO, a fourth held in the receive shift register, and
overrun on the **fifth** — exactly "3-deep, 4th held, overrun on 5th". The
MC68HC681 User's Manual agrees ("consists of three receive holding registers. … data
flowing from the receiver to the CPU is quadruply buffered", p. 3-3).
**MAME.** `MC68681_RX_FIFO_SIZE == 3`, FIFO array `rx_fifo[MC68681_RX_FIFO_SIZE + 1]`
(4 slots). `rx_fifo_num` is incremented only while `<= 3` (so it reaches 4 on the 4th
character) and `rx_fifo_push()` sets `STATUS_OVERRUN_ERROR` when it is called with
`rx_fifo_num == MC68681_RX_FIFO_SIZE + 1 == 4` — i.e. when the **5th** character is
pushed. `STATUS_FIFO_FULL` is set when `rx_fifo_num == 3`. This matches the datasheet's
threshold: 3 in the FIFO, 4th accepted, overrun on the 5th.
**Assessment:** confirmed. The `+1` array slot and the `SIZE+1` overrun guard model the
datasheet's "FIFO of three + one held in the shift register" correctly. This part of the
worker's reasoning is not the source of the test failure.
---
## Incidental findings
* **Local loopback clock.** SCN2681, printed page **12** (MR2A[7:6] = 10, local loopback
mode): "2. **The transmit clock is used for the receiver.**" The report's §5.4 note
(MAME clocks the loopback receiver from the independent receive clock) is directionally
correct against the datasheet. Not one of the three claims under review.
* **"Receipt of a start bit" vs falling edge** applies to Claim 2 (above).
* **The datasheet's own inconsistency** ("strobed … at the received character boundary"
vs "set upon receipt of the start bit") is the only place any of these three points
fails to be unambiguous; on Claim 1 there is no ambiguity — page 22 is explicit.
## Bottom line
Only **one** of the three cited justifications holds up as written. Claim 2 and Claim 3
are accurately quoted and correctly applied. **Claim 1 is the misleading one**: its
quotation is genuine, but the datasheet contains a more specific sentence — "TxRDY sets
at the end of the 'start bit' time" (SCN2681 p. 22) — that says the opposite of what the
edit assumes, and which the removed upstream code implemented. Removing the
`tra_callback()` assertion is **not** datasheet-backed.