mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
reference: DUART counter/timer period when CTUR/CTLR unprogrammed -- datasheet does not settle it
This commit is contained in:
1 parent
a6b6ae82ef
commit
7e65692b54
1 file changed
+312
@@ -0,0 +1,312 @@
|
||||
# MAME `mc68681` counter/timer period when `CTUR`/`CTLR` are never programmed
|
||||
|
||||
Task: the VT1200 stub level-1 run wedges in a DUART1 counter-ready interrupt storm
|
||||
because `ACR = 0x60` selects timer mode while `CTUR`/`CTLR` are never written.
|
||||
MAME substitutes `std::max(CTR.w.l, 1)` and re-arms every tick. Determine from the
|
||||
datasheet what the fitted part (Signetics/Philips **SCN2681**) actually does when the
|
||||
counter is unprogrammed.
|
||||
|
||||
**Verdict: the datasheet does NOT settle it.** Details below; the reasoning is as
|
||||
important as the quotes, and the conclusion is "stop, do not guess."
|
||||
|
||||
Sources (all local, in `reference/duart-datasheet/`): primary **SCN2681** Philips
|
||||
1995-05-01 product specification (`SCN2681-transputer.pdf`/`.txt`) — the part fitted;
|
||||
corroboration **SCC2681** Philips 2004-04-06 (`SCC2681-nxp`), **SCN68681** Signetics
|
||||
1998-09-04 (`SCN68681-xdevs`), **MC68HC681** Motorola User's Manual
|
||||
(`MC68681UM-nxp`). Page numbers are the printed page numbers (each page footer reads
|
||||
`1995 May 1 <n>` for the SCN2681, etc.).
|
||||
|
||||
---
|
||||
|
||||
## 1. What the datasheet says
|
||||
|
||||
### 1a. There is no "0 means 65536" convention — the counter counts *through* 0
|
||||
|
||||
SCN2681, printed page 16 (also SCN68681 p. 17, SCC2681 p. 19):
|
||||
|
||||
> "In the counter mode, the C/T counts down the number of pulses loaded into CTUR and
|
||||
> CTLR by the CPU. Counting begins upon receipt of a counter command. Upon reaching
|
||||
> terminal count (000016), the counter ready interrupt bit (ISR[3]) is set. The counter
|
||||
> continues counting past the terminal count until stopped by the CPU."
|
||||
|
||||
SCN68681, printed page 9, "Operation Control":
|
||||
|
||||
> "The counter operates as a down counter and sets its output bit in the ISR (Interrupt
|
||||
> Status Register) **each time it passes through 0**."
|
||||
|
||||
Motorola MC68HC681 User's Manual, §3.5.1 (printed page 3-6):
|
||||
|
||||
> "The value (preload value) stored in the concatenation of the C/T upper register
|
||||
> (CTUR) and the C/T lower register (CTLR) **can be from 000116 through FFFF16** …
|
||||
> When the counter counts from 000116 to 000016 (terminal count), the C/T-ready bit in
|
||||
> the interrupt status register (ISR[3]) is set. … **After 000016 the counter counts to
|
||||
> FFFF16, and continues counting down from there.**"
|
||||
|
||||
So 0000 is the **terminal count**, not 65536; the counter saturates/reloads downward, it
|
||||
does not wrap modulo 65536. **The "0 = 65536" hypothesis is refuted.**
|
||||
|
||||
### 1b. Zero is *below the minimum legal preload* — MAME's clamp to 1 is not derivable
|
||||
|
||||
SCN2681, printed page 16, "CTUR and CTLR – Counter/Timer Registers":
|
||||
|
||||
> "The CTUR and CTLR hold the eight MSBs and eight LSBs, respectively, of the value to
|
||||
> be used by the counter/timer in either the counter or timer modes of operation.
|
||||
> **The minimum value which may be loaded into the CTUR/CTLR registers is 000216.** Note
|
||||
> that these registers are write-only and cannot be read by the CPU."
|
||||
|
||||
Motorola gives the same idea with a different floor: "can be from **000116** through
|
||||
FFFF16" (MC68HC681 UM §3.5, p. 3-6). In timer mode the SCN2681 says the square-wave
|
||||
period is "twice the value (in clock periods) of the CTUR and CTLR" — so a preload of 2
|
||||
is the fastest legal half-period; a preload of 0 is not a legal state at all.
|
||||
|
||||
Consequence: MAME's `max(CTR.w.l, 1)` yields a half-period of **1** clock tick, which
|
||||
**no legal preload can produce** (Philips floor = 2, Motorola floor = 1 but never 0).
|
||||
It is a modelling choice, not a datasheet value.
|
||||
|
||||
### 1c. But the datasheet never says what an *unprogrammed* counter does
|
||||
|
||||
The datasheet defines the **legal** range and says nothing about 0. It also never
|
||||
defines the **power-up/reset** value of CTUR/CTLR — and the reset description is
|
||||
internally contradictory:
|
||||
|
||||
SCN2681, printed page 2, `RESET` pin description:
|
||||
|
||||
> "Reset: A High level clears internal registers (SRA, SRB, IMR, ISR, OPR, OPCR), puts
|
||||
> OP0–OP7 in the High state, **stops the counter/timer**, and puts Channels A and B in
|
||||
> the inactive state, with the TxDA and TxDB outputs in the mark (High) state."
|
||||
|
||||
Note that `CTUR`/`CTLR` are **not** in the list of registers reset clears.
|
||||
|
||||
SCN2681, printed page 16, "Counter/Timer" (the *same* datasheet):
|
||||
|
||||
> "**On power up and after reset, the timer/counter runs in timer mode and can only be
|
||||
> restarted. Because it cannot be shut off or stopped, and runs continuously in timer
|
||||
> mode**, it is recommended that at initialization, the output port (OP3) should be
|
||||
> masked off through the OPCR[3:2] = 00 until the T/C is programmed to the desired
|
||||
> operational state."
|
||||
|
||||
and, on the same page:
|
||||
|
||||
> "If new values have not been loaded, the previous count values are preserved and used
|
||||
> for the next count cycle."
|
||||
|
||||
So on the fitted part: after reset the C/T **is running** in timer mode (the counter
|
||||
section), yet the reset pin description says reset **stops** it. The datasheet does not
|
||||
state the reset preload value, and the counter section's "runs continuously" statement
|
||||
means the counter is armed with *whatever* value CTUR/CTLR holds — which at power-up is
|
||||
not defined.
|
||||
|
||||
### 1d. The family differs on this exact point (as asked)
|
||||
|
||||
SCC2681 (2004), printed page 19:
|
||||
|
||||
> "On power up and after reset the timer/counter **comes up stopped** and in the timer
|
||||
> mode. It will require a start counter command (a read at address 0xE) to start it.
|
||||
> Because it cannot be shut off or stopped once started, and runs continuously in timer
|
||||
> mode, it is recommended that at initialization, the output port (OP3) should be masked
|
||||
> off through the OPCR[3:2] = 00 until the T/C is programmed to the desired operational
|
||||
> state."
|
||||
|
||||
Motorola MC68HC681 UM, §3.5 (p. 3-6/3-7) is silent on "stopped at reset":
|
||||
|
||||
> "At power-up and after reset, the C/T operates in timer mode."
|
||||
> "In timer mode, the C/T runs continuously; the CPU cannot start or stop it."
|
||||
|
||||
So the **fitted SCN2681 says the counter runs after reset**; the later **SCC2681 says it
|
||||
comes up stopped** and needs a Start Counter command. This is a genuine family split
|
||||
and, by itself, prevents any single unambiguous answer for "unprogrammed".
|
||||
|
||||
---
|
||||
|
||||
## 2. MAME's exact behaviour
|
||||
|
||||
File: `/home/arti/code/k-space/mame0289/src/devices/machine/mc68681.cpp`.
|
||||
|
||||
Reset (`duart_base_device::device_reset`, lines 275–289) zeroes the preload and quotes
|
||||
the SCN2681 reset line:
|
||||
|
||||
```c++
|
||||
275: void duart_base_device::device_reset()
|
||||
276: {
|
||||
277: ACR = 0; /* Interrupt Vector Register */
|
||||
...
|
||||
282: CTR.d = 0; /* Counter/Timer Preset Value */
|
||||
283: // "reset clears internal registers (SRA, SRB, IMR, ISR, OPR, OPCR) puts OP0-7 in the high state, stops the counter/timer, and puts channels a/b in the inactive state"
|
||||
```
|
||||
|
||||
The timer callback reloads the preload every half period with the clamp:
|
||||
|
||||
```c++
|
||||
500: TIMER_CALLBACK_MEMBER(duart_base_device::duart_timer_callback)
|
||||
501: {
|
||||
502: if (ACR & 0x40)
|
||||
503: {
|
||||
504: // Timer mode
|
||||
505: half_period ^= 1;
|
||||
...
|
||||
536: if (!half_period)
|
||||
537: set_ISR_bits(INT_COUNTER_READY);
|
||||
538:
|
||||
539: int count = std::max(CTR.w.l, uint16_t(1)); // <-- clamp
|
||||
540: start_ct(count);
|
||||
541: }
|
||||
542: else
|
||||
543: {
|
||||
...
|
||||
548: // Counter mode
|
||||
549: set_ISR_bits(INT_COUNTER_READY);
|
||||
550: start_ct(0xffff);
|
||||
551: }
|
||||
552: }
|
||||
```
|
||||
|
||||
The same clamp is reached on the two other arming paths — the **Start-Counter command**
|
||||
(register `0x0e` read) and the **ACR write that enters timer mode**:
|
||||
|
||||
```c++
|
||||
735: case 0x0e: /* Start counter command */
|
||||
736: {
|
||||
737: if (ACR & 0x40)
|
||||
738: {
|
||||
739: // Reset the timer
|
||||
740: half_period = 0;
|
||||
741: }
|
||||
742:
|
||||
743: int count = std::max(CTR.w.l, uint16_t(1)); // <-- clamp
|
||||
744: start_ct(count);
|
||||
745: break;
|
||||
746: }
|
||||
```
|
||||
|
||||
```c++
|
||||
960: case 0x04: /* ACR */
|
||||
961: {
|
||||
962: uint8_t old_acr = ACR;
|
||||
963: ACR = data;
|
||||
...
|
||||
967: if ((old_acr ^ data) & 0x40)
|
||||
968: {
|
||||
969: if (data & 0x40)
|
||||
970: {
|
||||
971: // Entering timer mode
|
||||
972: uint16_t count = std::max(CTR.w.l, uint16_t(1)); // <-- clamp
|
||||
973: half_period = 0;
|
||||
974:
|
||||
975: start_ct(count);
|
||||
976: }
|
||||
```
|
||||
|
||||
and the arming primitive itself (lines 494–498):
|
||||
|
||||
```c++
|
||||
494: void duart_base_device::start_ct(int count)
|
||||
495: {
|
||||
496: uint32_t clock = get_ct_rate();
|
||||
497: duart_timer->adjust(attotime::from_ticks(count, clock), 0);
|
||||
498: }
|
||||
```
|
||||
|
||||
Reading `CTUR`/`CTLR` (lines 999–1005) only updates `CTR`, so with no write `CTR.w.l`
|
||||
stays 0, every arm uses count = 1, and the timer callback sets `ISR[3]` every second
|
||||
callback — hence the ~229-CPU-cycle storm reported in
|
||||
`harness/mame/LEVEL1_CONTINUE_REPORT.md`.
|
||||
|
||||
**Measured for the VT1200 run:** DUART1 register 4 (ACR) is written once, `0x60`, at PC
|
||||
`3FA71CB0`; registers 6/7 (CTUR/CTLR) are **never written**. Confirmed from the harness
|
||||
logs (`hp_board.log`/`board-hp.log` line 96: `W 06400004 pc=3FA71CB0 val=0060`; no
|
||||
`06400006`/`06400007` write anywhere in the run). The clamp is therefore exactly the
|
||||
line in play.
|
||||
|
||||
---
|
||||
|
||||
## 3. Verdict
|
||||
|
||||
**The datasheet does not settle it.**
|
||||
|
||||
What *is* settled:
|
||||
* `0` does **not** mean 65536 — the C/T is a down-counter that passes through terminal
|
||||
count `0000` and keeps counting down (SCN2681 p. 16; SCN68681 p. 9).
|
||||
* `0` is **below the minimum legal preload** (SCN2681/SCC2681/SCN68681: `0002`;
|
||||
MC68HC681: `0001`). MAME's clamp to `1` yields a period **no legal preload can
|
||||
produce**, so it is a model invention, not a datasheet value.
|
||||
|
||||
What is **not** settled:
|
||||
* The datasheet defines no behaviour for preload `0` (it is simply out of range).
|
||||
* It does not define the power-up/reset value of `CTUR`/`CTLR` — reset does not clear
|
||||
them (SCN2681 p. 2), and the reset state of the counter is self-contradictory in the
|
||||
SCN2681 itself ("stops the counter/timer" p. 2 vs "runs in timer mode and can only be
|
||||
restarted … cannot be shut off or stopped" p. 16).
|
||||
* The family disagrees: SCN2681 (fitted) runs after reset; SCC2681 comes up stopped and
|
||||
needs a Start Counter command.
|
||||
|
||||
So we cannot derive a trustworthy "correct" period from the datasheet. We can say only
|
||||
that MAME's specific value is not datasheet-derived; every alternative (2, 1, "stopped
|
||||
until Start") is likewise not settled by the fitted part's own contradicting text.
|
||||
|
||||
**No change is justified on datasheet grounds. Do not apply anything.** (The task called
|
||||
this outcome in advance: a "does not settle it" result means stop rather than guess.)
|
||||
|
||||
Note on scope: the harness report `LEVEL1_CONTINUE_REPORT.md` §"The wall, named" already
|
||||
described this as upstream `max(CTR,1)` behaviour and did not change MAME, which was
|
||||
correct under this finding.
|
||||
|
||||
---
|
||||
|
||||
## 4. Minimal change — stated but **NOT applied**
|
||||
|
||||
Because the verdict is "does not settle it", there is **no datasheet-backed edit** to
|
||||
propose, and none is made. For the record of what a *non*-datasheet-backed "make it
|
||||
slower" patch would look like (so the owner can see the shape and reject it), the
|
||||
candidate would be at three sites in `src/devices/machine/mc68681.cpp`:
|
||||
|
||||
```c++
|
||||
// lines 539, 743, 972 — candidate only, NOT applied:
|
||||
-int count = std::max(CTR.w.l, uint16_t(1));
|
||||
+int count = std::max(CTR.w.l, uint16_t(2)); // or 0xffff, etc.
|
||||
```
|
||||
|
||||
**Revert note for that candidate:** each of the three edits is a single-token change on
|
||||
lines 539, 743, and 972; reverting is restoring `uint16_t(1)` on each. **Do not apply
|
||||
this** — it is arbitrary: 2 matches the Philips floor but not the Motorola part, it still
|
||||
invents behaviour for an illegal preload, and the datasheet gives no basis for choosing
|
||||
it over "the counter stays stopped" (SCC2681) or "runs with an undefined preload"
|
||||
(SCN2681). Choosing any value here would be exactly the "model quietly invents
|
||||
behaviour" failure this check exists to catch.
|
||||
|
||||
---
|
||||
|
||||
## 5. Does the skip path hit the same fallback?
|
||||
|
||||
**Yes, the fallback is exercised — but it is harmless there, so this is not worth much
|
||||
as a separate bug.**
|
||||
|
||||
* The skip path (`VT1200_SKIP_LEVEL1=1`) never runs the level-1 counter test, so the
|
||||
`IMR = 0x08` (counter-ready only) write that creates the storm never happens.
|
||||
* The OS *does* leave `CTUR`/`CTLR` unprogrammed on the DUARTs it configures: the OS
|
||||
bring-up log shows `W 06200004=0060 ACR` and no write to `06200006`/`06200007`
|
||||
(`harness/mame/OS_BRINGUP_REPORT.md` §8.3). And during the OS, the DUART1 ISR reads
|
||||
back `0x0009` (`R 06400005 pc=3FB43900 val=0009`, §2) — bit 3 set, i.e. **the counter
|
||||
fallback is running and setting `ISR[3]` on DUART1 as well**.
|
||||
* It still does not storm because the OS never enables (or services) the counter-ready
|
||||
interrupt: it programs `IMR = 0x33` on DUART2 (no bit 3; `OS_BRINGUP_REPORT.md` §4.1)
|
||||
and its dispatcher masks the ISR with `0x66` (bits 1,2,5,6 — not bit 3). MAME's
|
||||
`update_interrupts()` raises the IRQ only for masked bits, so `ISR[3]` being set is
|
||||
invisible.
|
||||
|
||||
So: the skip path hits the *same model fallback* (unprogrammed preload in timer mode),
|
||||
but only the **aborted level-1 test** turns it into a storm by enabling solely the
|
||||
counter-ready interrupt (`IMR = 0x08`) on a dispatcher that handles bit 3. The storm is
|
||||
a level-1-path symptom; the skip/OS path is unaffected. That lowers the value of chasing
|
||||
this fallback — it is not what blocks the OS.
|
||||
|
||||
---
|
||||
|
||||
## Reproduction / provenance
|
||||
|
||||
* ROM `harness/mame/roms/vt1200/vt1200_v22.bin`, md5 `b46a3e442ccb86134d2356bd70d22fd2`
|
||||
(unchanged; nothing in the MAME tree or `harness/mame/` was modified by this task).
|
||||
* MAME source read-only: `/home/arti/code/k-space/mame0289/src/devices/machine/mc68681.cpp`
|
||||
lines 275–289, 494–498, 500–553, 735–746, 960–976, 999–1005.
|
||||
* Datasheet quotes from `reference/duart-datasheet/SCN2681-transputer.txt` (printed
|
||||
pages 2 and 16), `SCN68681-xdevs.txt` (page 9), `SCC2681-nxp.txt` (page 19),
|
||||
`MC68681UM-nxp.txt` (§3.5, pages 3-6/3-7).
|
||||
Reference in new issue
Block a user