13 KiB
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:
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:
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:
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: }
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):
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:
0does not mean 65536 — the C/T is a down-counter that passes through terminal count0000and keeps counting down (SCN2681 p. 16; SCN68681 p. 9).0is below the minimum legal preload (SCN2681/SCC2681/SCN68681:0002; MC68HC681:0001). MAME's clamp to1yields 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:
// 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 theIMR = 0x08(counter-ready only) write that creates the storm never happens. - The OS does leave
CTUR/CTLRunprogrammed on the DUARTs it configures: the OS bring-up log showsW 06200004=0060 ACRand no write to06200006/06200007(harness/mame/OS_BRINGUP_REPORT.md§8.3). And during the OS, the DUART1 ISR reads back0x0009(R 06400005 pc=3FB43900 val=0009, §2) — bit 3 set, i.e. the counter fallback is running and settingISR[3]on DUART1 as well. - It still does not storm because the OS never enables (or services) the counter-ready
interrupt: it programs
IMR = 0x33on DUART2 (no bit 3;OS_BRINGUP_REPORT.md§4.1) and its dispatcher masks the ISR with0x66(bits 1,2,5,6 — not bit 3). MAME'supdate_interrupts()raises the IRQ only for masked bits, soISR[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, md5b46a3e442ccb86134d2356bd70d22fd2(unchanged; nothing in the MAME tree orharness/mame/was modified by this task). - MAME source read-only:
/home/arti/code/k-space/mame0289/src/devices/machine/mc68681.cpplines 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).