Files
vt1200/reference/mc68681-counter-period.md

13 KiB
Raw Permalink Blame History

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.

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:

  • 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:

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