:TITLE[Timer];

%Edit by Ed Fiala 12 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.
Edit by Ed Fiala 24 August 1981: improve Refresh timer some more.
Edit by Ed Fiala 5 May 1981: Add prTime init.
Edit by 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.
Edit by 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) Should automatically assemble Ethernet timers rather than using ugly
conditionals.
3) Change ClockLo increment from 4 to 1 when Pilot has been changed to use
vCrystal properly.
4) 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. At the moment, 4 is added to ClockLo every 2560 cycles (because
historically 1 was added every 640 cycles). Eventually software will use the
READR opcode (see MesaX.Mc) to obtain the value in the vCrystal register 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 17 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 17+7 = 24
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 manufacturer’s
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
640 cycle timer with the Refr register as a state variable for a period of
2 ms. After putting Pilot1 and its overlays in storage, Initial exits with
LoadRAM. During LoadRAM, inline refresh is done slightly faster, but xfTemp1
is used as the Refresh base register, so an unrefreshed period of almost 4 ms
might be experienced by some RAMs, if xfTemp1 and Refr are maximally out of
sync--however, this is still less than the 8 ms refresh period used by Pilot.
Pilot immediately starts a 2560 cycle refresh timer with Refr as the state
variable; Initialize.Mc sets Refr to the value in xfTemp1 immediately before
calling this code, so there is no significant refresh hickup after Initial’s
LoadRAM. Subsequent LoadRAM calls during Pilot init always allow normal
refresh to avoid discontinuities in the refresh cycle. However, skipping
the CSLKeybaord and CSLFKeyboard overlays goes about ? cycles without
tasking and HRam init in DisplayInit.Mc then goes about 7500 more cycles
without tasking.

The following refresh periods are for a 100 ns cycle (values larger than
16 ms require a double timer):
Refresh period RTimer value
Timer period
2 ms50257b 640 cycles
4 ms50517b1280 cycles
8 ms51217b2560 cycles
16 ms52417b5120 cycles
%


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[51217];*Set up 256 us clock period
RTimer ← (RTimer) or (LoA[51217]);*and 8 ms refresh period
*prTime and NWW are emulator register in the RM 320-377 area.
NWW ← T ← 0C;
RSImage ← RS232 ← T;
RTMP ← (RTMP) + 1, ResetMemErrs;*Clear pending memory errors
LU ← (RTMP) and (17C);*there are 16d timers
prTime ← 3C, 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];
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 should be handled as follows: (1) Remove the IMReserve in Mesa1Occupied
for the TimerTable location to be overwritten; no change in Mesa2Occupied is
required because the TimerTable location involved will harmlessly (in fact,
usefully) be initialized by the code here. (2) Generally adjust IMReserves
in both Mesa1Occupied and Mesa2Occupied so that there will be enough free
locations on TimerPage for the new code in Pilot2.
%

:IF[WithTOR]; ****************************************
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; **********************************************
SetFault, GoTo[.], At[TimerTable,01];*slot 16 (Jasmine overwrites)
SetFault, GoTo[.], At[TimerTable,02];*slot 15 (Jasmine overwrites)
SetFault, GoTo[.], At[TimerTable,03];*slot 14 (Jasmine overwrites)
SetFault, GoTo[.], At[TimerTable,04];*slot 13 (Jasmine overwrites)
SetFault, GoTo[.], At[TimerTable,05];*slot 12
SetFault, GoTo[.], At[TimerTable,06];*slot 11
:IF[With3MB]; ****************************************
*slot 10 (3mb Ethernet slot = xoTask)
APCTask&APC ← xoNotify, GoTo[TimerRet], At[TimerTable,07];
*slot 06 (2nd 3mb Ethernet slot = xoTask2)
APCTask&APC ← xoNotify2, GoTo[TimerRet], At[TimerTable,11];
:ELSE; ***********************************************
SetFault, GoTo[.], At[TimerTable,07];*slot 10
SetFault, GoTo[.], At[TimerTable,11];*slot 06
:ENDIF; **********************************************
:IF[With10MB]; ***************************************
*slots 04, 05, and 07 for 10 mb Ethernet, enxTask, enxTask2, and enxTask3.
APCTask&APC ← enxNotify, GoTo[TimerRet], At[TimerTable,13];
APCTask&APC ← enxNotify2, GoTo[TimerRet], At[TimerTable,12];
APCTask&APC ← enxNotify3, GoTo[TimerRet], At[TimerTable,10];
:ELSE; ***********************************************
SetFault, GoTo[.], At[TimerTable,13];*slot 04
SetFault, GoTo[.], At[TimerTable,12];*slot 05
SetFault, GoTo[.], At[TimerTable,10];*slot 07
:ENDIF; **********************************************
SetFault, GoTo[.], At[TimerTable,14];*slot 03
SetFault, GoTo[.], At[TimerTable,15];*slot 02 (Audio overwrites)
SetFault, GoTo[.], At[TimerTable,16];*slot 01
SetFault, GoTo[.], At[TimerTable,17];*slot 00
EOMRefresh:
IOStrobe;
ClkUpdate:
AddToTimer[RTimer];
ClockLo ← (ClockLo) + (4C);*4C for 256 us clock period
: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
GoTo[.], SetFault, At[MidasHaltLoc];*Midas halt request
:ELSE; ***********************************************
ClockHi ← (ClockHi) + 1, UseCOutAsCIn;
Refr ← (Refr) + (20C);
:ENDIF; **********************************************
TimerRet:
Return, At[MidasHaltLoc,1];

:IF[With3MB]; ****************************************
IFE[xoTask,10,,ER[Edit.for.3MB.Ethernet.task.change.needed]];
IFE[xoTask2,6,,ER[Edit.for.3MB.Ethernet.task.change.needed]];
:ENDIF; **********************************************
:IF[With10MB]; ***************************************
IFE[enxTask,4,,ER[Edit.for.10MB.Ethernet.task.change.needed]];
IFE[enxTask2,5,,ER[Edit.for.10MB.Ethernet.task.change.needed]];
IFE[enxTask3,7,,ER[Edit.for.10MB.Ethernet.task.change.needed]];
:ENDIF; **********************************************

:END[Timer];