diff --git a/reference/mc68681-counter-period.md b/reference/mc68681-counter-period.md new file mode 100644 index 0000000..bf91a40 --- /dev/null +++ b/reference/mc68681-counter-period.md @@ -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 ` 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).