# 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).