:TITLE[Timer]; %Ed Fiala 17 January 1983: Clear the left four bits of Refr in Timer init for Henning. Ed Fiala 3 November 1982: Add code for Timer in slot 16b to call NotifyInterrupt, and initialize TimerInts register. Ed Fiala 13 September 1982: Parameterize timer constants as f(RefreshPeriod). Tom Rich 9 September 1982: WithTor => WithEOM. Ed Fiala 17 May 1982: moved ethernet output timers; automate TimerTable init. Ed Fiala 6 May 1982: Cosmetic edits; removed prTime, NWW, RSImage, and RS232 init to Initialize.Mc. Ed Fiala 16 March 1982: Replaced 'Return' in timer dispatch by 'SetFault, GoTo[.]'; added With3MB, With10MB, WithTOR conditionals; added TimerGoLoc; change Refr init to preserve refresh sequence from Initial; count ClockLo+1 rather than +4. Ed Fiala 24 August 1981: improve Refresh timer some more. Ed Fiala 5 May 1981: Add prTime init. Ed Fiala 29 April 1981: Remove manual init for mi at 0 and 1 and move the system initialization to Initialize.Mc; bum two mi out of Refresh timer code; improve initialization; add init for NWW; WithMidas conditional. Ed Fiala 3 March 1981: put in parameters for varying refresh period and clock rate; set values for 8 msec refresh period, improving performance about 3.9 percent. NOTES: 1) Save 1 mi if Midas resumes at MidasHaltLoc-1 rather than MidasHaltLoc+1. 2) Be wary of timer overflow during DisplayInit.Mc's execution. Occurrences of LoadTimer and AddToTimer must be separated by 7 mi, and branch conditions are illegal with LoadToTimer and AddToTimer. Here the 7 mi requirement is fulfilled with 3 mi before LoadTimer/AddToTimer and 4 mi after. OTHER MODULES MUST GUARANTEE 3 MI FROM WAKEUP TO LOADTIMER/ADDTOTIMER AND 4 MI AFTER LOADTIMER/ADDTOTIMER BEFORE TASKING TO BE CONSISTENT. In Load/AddToTimer, 0:3 are new state, 4:11 new data, and 12:15 are slot number. When a timer wakeup is deferred too long, wakeup and AddToTimer don't work properly. If a timer is able to occupy the wakeup slot, then, no matter how long tasking is deferred, wakeup will happen as soon as tasking occurs. However, if some other timer occupies the slot, then 8192 cycles after it expired, the timer's data field will become positive and cease requesting a wakeup--8192 cycles after that it will go negative again, etc. So any deferral of 8193 to 16384 cycles will delay wakeup at least 16384 cycles. Even if an expiring timer immediately occupies the wakeup slot, AddToTimer may leave the data field with an 8192 cycle wait for next time, if tasking is defferred too long. Slot 17b is used for refresh and realtime clock update. Since timer periods are denominated in processor cycles, microcode can control both the number of cycles between timer wakeups and the constant added to the clock each wakeup. Presently, 1 is added to ClockLo every 2560 cycles. Software uses the READR opcode (see MesaESC.Mc) to obtain the value in vCrystal and adjust automatically to both variable processor clock speed, and the addend to the realtime clock. Refresh must cycle fast enough to maintain storage. 16k RAMs are configured as 128 rows x 128 columns; one Dolphin refresh reference refreshes four rows, so 32 references are required per cycle; 64k RAMs are configured as 256 x 256 and would require 64 references per cycle. Both 16k and 64k RAMs specify a 2 ms cycle at maximum operating temperature (80 deg C). The primary leakage phenomenon necessitating refresh is exponential in temperature, about exp(-44*(300/T)), so required refresh rate goes up by a factor of 2 for every increase of 5 degrees C. Refresh at a 2 ms period is undesirable because ~24 of every 640 cycles (~3.75 percent of all cycles) would be consumed by the refresh timer (Only 16 cycles are used, but the Refresh reference leaves MC1 busy for an additional 10 cycles, and the next task to run is expected to reference memory after 3 cycles, so 16+7 = 23 cycles). Therefor, refresh period has been increased to 8 ms, accepting less clock precision and a longer storage refresh period to make the emulator run faster. This surrenders about 10 degrees in temperature margin and possibly requires some marginal RAMs to be replaced, but the refresh specification is believed very conservative and we do not intend to operate anywhere near the maximum operating temperature. Parameters for other periods are given in the table below. Initialization must be particularly careful about refresh. Initial uses a refresh timer 1.5 times slower than the one here during its storage test to detect marginal RAMs (12 ms refresh period vs. 8 ms here at present). After zeroing storage, Initial changes to a 2560-cycle refresh timer, identical to emulators'. After putting Pilot1 and its overlays in storage, Initial exits with LoadRAM. During LoadRAM, inline refresh is done quickly, and the state variable used for refresh is carefully initialized to the same value as the timed refresh used previously to avoid unrefreshed intervals. Initialize.Mc sets Refr to the value in xfTemp1 immediately before calling Timer init, so no significant refresh hickup occurs before or after Initial's LoadRAM. Subsequent Pilot LoadRAM calls always allow normal refresh to avoid discontinuities in the refresh cycle. However, HRam init in DisplayInit.Mc goes about 7500 cycles without tasking, so some RAMs go 8.75 ms unrefreshed. The following refresh periods are for a 100 ns cycle and 16k-bit RAMs (values larger than 16 ms require a double timer); with 64k or 256k RAMs, respectively, refresh period doubles or quadruples: Refresh period RTimer value Timer period 2 ms 50257b 640 cycles 4 ms 50517b 1280 cycles 8 ms 51217b 2560 cycles 16 ms 52417b 5120 cycles % Set[RefConstant,Add[50017,LShift[RShift[RefreshPeriod,6],4]]]; SetTask[TTask]; RTMP _ 100000C, At[TimerInitLoc]; ClrTimerS: *NOTE: Must have 7 mi between successive LoadTimer/AddToTimer operations and *may not have branch condition in same mi with LoadTimer/AddToTimer. LoadTimer[RTMP]; *Disable all Timers RTimer _ HiA[RefConstant]; *Set timer period RTimer _ (RTimer) or (LoA[RefConstant]); RTMP _ (RTMP) + 1, ResetMemErrs; *Clear pending memory errors TimerInts _ 0C; *Clear notify bits Nop; LU _ (RTMP) and (17C); *there are 16d timers Refr _ (Refr) and not (170000C), GoTo[ClrTimerS,ALU#0]; LoadTimer[RTimer]; LoadPage[TimerPage]; LU _ Timer, GoToP[SetUpRef]; *Eat any (leftover) timer trying to go off OnPage[TimerPage]; SetUpRef: Call[TimerRet], At[TimerGoLoc]; %T _ Timer here appears to read the value last loaded into or added to the refresh timer??? Thus trying to read the amount of timer overrun by looking at the data field doesn't work. % TimerWakeup: *Timer wakeups come here T _ Dispatch[Timer,14,4]; *Since Refresh is the most intrusive timer, use this mi to do something *useful for it and not harmful to other timer wakeups. Refr _ (Refr) and not (1000C), Disp[.+1]; %NOTE: TimerTable is correct here only for those Timers used by Pilot1; it makes no provision for timers used by Pilot2 or by later overlays. Pilot2 timers generally require no change in Mesa2Occupied because the TimerTable location involved will harmlessly (in fact, usefully) be initialized by the code here; also, Mesa1Occupied requires no change because unused timer table entries are already available for overwriting. Assemble TimerTable automatically here with respect to the 3mb and 10mb Ethernet controllers--an output timer is needed for each task, and, by convention, the timer used for this is in the slot equal to the task number. NOTE: The Jasmine scanner uses slots 13-16 and the audio board uses slot 2; these timers are in the Pilot2 assembly. % Set[UsedTimers,0]; *Bit n set if timer slot n has been used. IFE[With3MB,1, Set[UsedTimers,Or[UsedTimers,LShift[1,xoTask],LShift[1,xoTask2]]]]; IFE[With10MB,1,Set[UsedTimers,Or[UsedTimers,LShift[1,enxTask], LShift[1,enxTask2],LShift[1,enxTask3]]]]; Macro[FillInTimer,(IFE[And[UsedTimers,LShift[1,#1]],0,(SetFault, GoTo[.], At[TimerTable,Xor[#1,17]])])]; :IF[WithEOM]; **************************************** Timers: Refresh[Refr], DblGoTo[ClkUpdate,EOMRefresh,ALU#0], At[TimerTable,00]; *slot 17 :ELSE; *********************************************** Timers: Refresh[Refr], GoTo[ClkUpdate], At[TimerTable,00]; *slot 17 :ENDIF; ********************************************** T _ TimerInts, LoadPage[NotifyInterruptPage], GoTo[UserTimer], At[TimerTable,01]; *slot 16 :IF[With3MB]; **************************************** *Was slots 10 and 4 on 17 May 1982. APCTask&APC _ xoNotify, GoTo[TimerRet], At[TimerTable,Xor[xoTask,17]]; *Note that enxNotify3 and xoNotify2 are the same register, so could use the *same timer slot for these two. APCTask&APC _ xoNotify2, GoTo[TimerRet], At[TimerTable,Xor[xoTask2,17]]; :ENDIF; ********************************************** :IF[With10MB]; *************************************** *Was slots 7, 6, and 5 on 17 May 1982. APCTask&APC _ enxNotify, GoTo[TimerRet], At[TimerTable,Xor[enxTask,17]]; APCTask&APC _ enxNotify2, GoTo[TimerRet], At[TimerTable,Xor[enxTask2,17]]; APCTask&APC _ enxNotify3, GoTo[TimerRet], At[TimerTable,Xor[enxTask3,17]]; :ENDIF; ********************************************** FillInTimer[15]; FillInTimer[14]; FillInTimer[13]; FillInTimer[12]; FillInTimer[11]; FillInTimer[10]; FillInTimer[07]; FillInTimer[06]; FillInTimer[05]; FillInTimer[04]; FillInTimer[03]; FillInTimer[02]; FillInTimer[01]; FillInTimer[00]; :IF[WithEOM]; **************************************** EOMRefresh: IOStrobe; :ENDIF; ********************************************** ClkUpdate: AddToTimer[RTimer]; ClockLo _ (ClockLo) + 1; :IF[WithMidas]; ************************************** T _ (FFault) and (10000C), Skip[Carry']; ClockHi _ (ClockHi) + 1; T _ (Printer) and T; Refr _ (Refr) + (20C), Skip[ALU#0]; Return; *Midas resumes program at MidasHaltLoc+1 MHalt: GoTo[.], SetFault, At[MidasHaltLoc]; *Midas halt request :ELSE; *********************************************** ClockHi _ (ClockHi) + 1, UseCOutAsCIn; Refr _ (Refr) + (20C); :ENDIF; ********************************************** TimerRet: Return, At[MidasHaltLoc,1]; *NotifyInterrupt smashes registers 340 and 341 which were coincident with *xoNotify2, enxNotify2, and enxNotify3 on 3 November 1982. Because 340 *is TimerInts, that register is actually preserved. UserTimer: GoToP[NotifyInterrupt]; :END[Timer]; (1795)\f5 329f0 18f5 7729f0 7f5 1180f0 12f5 6f0 12f5 6f0 12f5 6f0 12f5 6f0 12f5 6f0 12f5 6f0 12f5 6f0 12f5 6f0 12f5 6f0 12f5 6f0 12f5 6f0 12f5 6f0 12f5 6f0 12f5 10f0 7f5