: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 valueTimer period
2 ms50257b 640 cycles
4 ms50517b1280 cycles
8 ms51217b2560 cycles
16 ms52417b5120 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];