INSERT[DisplayDefs];*Defs file common to DisplayInit and Display
:IF[LFKeyBoard];
TITLE[LFDisplay];
:ELSEIF[LFMonitor];
TITLE[CSLFDisplay];
:ELSE;
TITLE[CSLDisplay];
:ENDIF;
%
Ed Fiala 26 May 1983: Increase cycles after Output from 4 to 5 at vCursm.
Ed Fiala 29 June 1982: Fix black background bug with display off.
Ed Fiala 20 May 1982: Add FourWakeups switch.
Ed Fiala 18 May 1982: cosmetic edits; save cycles at @vTVB1; improve average
main loop time by 2 cycles for CSLF terminal, 4 cycles for LF terminal;
saved 6 cycles on worst case for LF terminal; eliminated vBadCnt and
replaced vMouseDx and vMouseDy by a single register vMouseDxy; move
IOStrobe to 3rd mi at @vVB0; improve LF keyboard keystroke posting code.
Jim Sandman March 11, 1982 change KTable
Ed Fiala 22 February 1982

This source assembles a driver for any of the three monitor/keyboard
configurations with appropriate setting of LFMonitor and LFKeyboard switches.

During vertical blanking, the vCnt register is used to count the appropriate
number of times through the blanked scanlines (a) at end-of-field;
(b) during vertical sync; and (c) before the beginning of the next field.
During these blanked lines, all work necessary to clean up after the last
visible field and to setup for the next visible field will take place.

Cleanup for the last field consists of posting accumulated keyboard characters
(LF keyboard only), mouse position changes, and mouse buttons. These updates
could have been completed at end-of-message, but that would have worsened
DisplayTask timing requirements significantly, so the information is buffered
until vertical blanking and then posted.

Setup for the next field includes loading even or odd scanline cursor memory
words as appropriate for the next field, posting the vertical interrupt,
resyncing IAR, setting up the cursor control and the DCB for the next field.

On each scanline, a memory reference that sets IAddr[6:7] = 0 must be made
prior to testing the backchannel with IOAtten. IAddr[6:7] are loaded from
the low two bits of H2 on Input or Output or from the low two bits of SrcDest
on all other references. During vertical blanking, Input[--,0] or
Output[--,x] where x = vCReg or vCursor0 are usually used; during vertical
scan, Output[--,vBuf0] or IOFetchN[--,vfBuf0] are used. However, it is
possible to use a PFetch/StoreN[--,RMaddr] where RMaddr (i.e., the SrcDest
field of the mi) has two low zeroes.

Timing considerations are as follows:

(1) LF monitors require scanline service every 28.8 microseconds, CSL monitors
every 37.8 microseconds. Estimated average service time is 68 cycles
(6.8 microseconds) for LF monitors or 58 cycles (5.8 microseconds) for
CSL monitors, so the display task will consume 22 percent of all cycles for
LF and 15 percent for CSL monitors.

(2) To count cycles across tasking in the presence of memory references:
a. Charge all storage transport to the task initiating a reference, so a
PFetch1, 2, or 4 or a PStore1, 2, or 4 are charged 1, 2, or 4 cycles for
transport, as appropriate, and Input and Output are charged 1 cycle each;
this may result in a double charge if the transport occurs while another task
is in MC1/2 wait.
b. When waking up, assume MC1 is busy for 3 cycles, so a reference in
either of the first two mi counts as 5 cycles. This may be pessimistic; the
predecessor is usually the emulator; MC1 will be idle after prototypical
PFetch1/NextInst/NIRet and busy for 10 cycles after PStore1/NextInst/NIRet.
c. References initiated just prior to tasking are charged for MC1/MC2 wait
incurred by the successor task (normally the emulator); for simplicity, again
assume that the successor task will do non-memory mi for 4 cycles and then be
held in MC1/MC2 wait as long as necessary. I have empirically determined that
the average number of non-memory cycles in the successor task is 4.1 by
timing programs with the OnlyTwoWakeups and FourWakeups switches in different
states.

(3) Although DisplayTask is of high priority, each time it returns, another
task will run even if lower priority; but since DisplayTask reasserts its
wakeup request, it will run next barring higher priority requests. As
suggested in the timing model in (2) above, tasking before mi otherwise
suspended in memory wait improves emulator throughput about 2.0 percent since
the next task to run completes a few mi, on the average, before suspension in
MC1/MC2 wait, but, with too many returns, DisplayTask may not finish scanline
service within its 28.8 or 37.8 microsecond window. A service failure might
garbage a backchannel message and always displaces the screen image downward
two scanlines (two rather than one because of interleaving) from the point of
failure onward. To allow tasking more frequently, lower priority tasks must
not run too long without tasking; at present, the emulator has no known
sequences longer than 45 cycles without tasking.

A tiny amount of screen jumping is perhaps acceptable, but erroneous
backchannel messages are a bigger problem. For the CSL keyboard, bad messages
are detected and discarded, and the status of the four keyboard words and
mouse buttons is rebroadcast every five (?) scanlines, or 15.4 times/second,
so occasional missed messages produce no errors except almost imperceptible
non-movements in the mouse position.

However, errors are not detected for the LF keyboard, so a data-late will
distort any message in progress. Also, keyboard "strokes" are transmitted
by the keyboard microcomputer, so if one of these messages is missed,
confusion can be magnified. This problem is so bad that only very rare
data lates can be tolerated with an LF keyboard.

(4) The current code has no bad timing cases during vertical blanking (it
was necessary to load cursor memory words in two scanlines each rather than
in one scanline to make the timing for that case less critical than the
visible field.). The worst case occurs on the first scanline of the visible
field (which requires an extra Output) when an end-of-message event occurs on
the backchannel and the cursor is shown. Assembly switches allow tasking either 2, 3 or 4 times per scanline; the display seems solid at 4 tasks per
scanline, and since the emulator runs 2.0 percent faster, it is desirable
to leave the switches set for 4 wakeups unless there are an objectionable
number of data lates. The discussion here assumes 4 tasks/scanline. When
showing the cursor, only 3 tasks occur, so that the worst case is transformed
to occur on the first scanline, not showing the cursor, and servicing an
end-of-message event on the backchannel.

The first two scanlines for a zero-width DCB are the same timing as regular
scanlines; after that less critical. For these reasons, timing for the
regular visible scanline is the only case examined. The sequence is
as follows:
1st Wakeup
Output9 cycles1st scanline of field only
-------
IO163+18-4 cyclesEvery scanline
x3+20 cycles worst for LF keyboard
vM1/vM03+33 cycles worst for CSL keyboard
-------
2nd Wakeup
IO163+18-4 cyclesEvery scanline
-------
3rd Wakeup
IO163+18-4 cyclesEvery scanline
-------
4th Wakeup
IO163+18-4 cyclesEvery scanline

This happens every 28.8 microseconds and must happen within about 28.1
microseconds because the final IOFetch16 must finish in MC2 before the
next scanline. The average CPU requirement is about 68 cycles out of 288
cycles (40 mHz processor crystal) or 22 percent of all cycles.

Consequently, bad cases are as follows:
on 8 of the 404+44 scanlines, showing the cursor adds 9 cycles, but
only 3 wakeups occur in this case;
on the first visible scanline, an extra Output adds 9 cycles;
on message boundaries, up to 5 extra cycles may be needed (LF keyboard).
Hence, the typical case is that 4 wakeups and 68 cycles are needed in 281
cycles; if the only higher priority activity is the refresh timer
(26 cycles every 2560), then 281-68-26 implies that 4 consecutive lower
priority executions should not exceed 187 cycles (= 47 cycles between tasks)
in the absence of page faults, which is comfortable.

However, the bad timing cases all occur when a page, write protect, or MOB
fault occurs during a scanline. In this case, the fault handler will
preempt as many as 79 cycles (emulator was running) or 101 cycles (io task
was running)--these are the worst cases; see Fault.Mc. After preemption,
the fault handler continues in the emulator with an 11 cycle task and a
4 cycle task still in Fault.Mc. If the emulator was running, then the 79
cycles is not additive to other execution time because the emulator
would otherwise have continued to its tasking point, so preemption adds no
more than 77 cycles. If any io task was running, however, the fault
handler will resume it, so time available to the display task is 101 cycles
less than without the fault. Half of the faults take 8 cycles
less than this time. 3/4 of the faults will happen before or during the
first three display wakeups, when the emulator will have short sequences in
Fault.Mc after the long preemption. However, in the worst case, the display
task will be running on its final wakeup (or some other io task after the
display task has finished its 3rd wakeup) when the fault handler intervenes,
and in this case the effect of the fault will be to add 101 cycles to the
worst case. In this case, there is still no data late unless the four
preceding non-display wakeups averaged more than 21 cycles.

A truly worst case view would also allow for the unusual 9+5 cycles needed
by the display task, and in this case only 18 cycles between tasks is
permissible. However, the frequency of bad cases should be low, so data
lates should be infrequent.

If FourWakeups is 0, the LF monitor main loop will wakeup 3 times per
scanline (even when it shows the cursor). If OnlyTwoWakeups is 1, the LF
monitor main loop will wakeup only twice/scanline. For the reasons discussed
above, it may be necessary (particularly with LF keyboards) to have only 3
wakeups/scanline, but avoid if possible.
%
Set[OnlyTwoWakeups,0];
Set[FourWakeups,1];

Loca[vEvFet,DisplayPage,40];
*Dispatch table for CSL monitor only
Loca[vOdFet,DisplayPage,60];
*Dispatch table for CSL monitor only
Loca[KeyTable,DisplayPage,40];
*40b mi for LF keyboard only

Macro[KBCheck,DblGoTo[vM1,vM0,IOAtten’]];

SetTask[DisplayTask];

OnPage[DisplayPage];

***Eliminate this mi by moving vEndOfFieldLoc.
*vDBuf0/1 ← MouseX/Y
vFDone:
T ← LdF[vCR,15,1], At[vEndOfFieldLoc];

*End-of-field comes to vFDon1.
vFDon1:
PFetch2[vCSB,vDBuf0,MouseX];
*Complement even/odd field bit and turn on blanking
vCR ← (vCR) xor (OddFld&Blank);
*Set up to load cursor memory--mask Field bit.
vCursorY ← (Zero) xnor T;
*Excess 200b counts are kept in left and right halves of vMouseDxy.
:IF[LFKeyboard]; ******************************
*Store mouse buttons in vButtons[11:13d] into 177707; power supply normal,
*VS, and video are in vButtons[8:10], but not currently reported.
T ← (vButtons) and (4C);
T ← (LdF[vButtons,13,2]) or T;
vDBuf3 ← (Zero) xnor T;
*X is in right-half, Y in left-half.
T ← 200C;
T ← (LdF[vMouseDxy,10,10]) - T;
vDBuf0 ← (vDBuf0) + T;*Update MouseX
T ← 200C;
T ← (LdF[vMouseDxy,0,10]) - T;
vDBuf1 ← (vDBuf1) + T;*Update MouseY
:ELSE; ****************************************
*Mouse buttons already in position.
T ← vButtons;
vDBuf3 ← T;
*X is in left-half, Y in right-half.
T ← 200C;
T ← (LdF[vMouseDxy,0,10]) - T;
vDBuf0 ← (vDBuf0) + T;
T ← 200C;
T ← (LdF[vMouseDxy,10,10]) - T;
vDBuf1 ← (vDBuf1) + T;
:ENDIF; ***************************************
*Post mouse x,y by adding mouse dx/dy to MouseX/Y in the CSB; store buttons
*in vDBuf3; vDBuf2 smashes the word between MouseY and Buttons, but that’s ok.
PStore4[vCSB,vDBuf0,MouseX];
*Reset register to 0+200b (excess 200b); this avoids overflows because the
*maximum mouse movement in one field time is less than +/- 200b.
vMouseDxy ← 100000C;
*43 or 44 (+2 if LF) cycles to vM1
vMouseDxy ← (vMouseDxy) or (200C), Call[vSF0];

*Fetch CursorX/Y into vDBuf2/3; setup cursor position
@vVB0:
PFetch2[vCSB,vDBuf2,CursorX];
*Setup for vertical sync below.
vCR ← (vCR) or (VS);
**8 cycle abort here.
T ← vDBuf3, Skip[R Even];*Y coord.
*Y odd => invert earlier decision on which scanline of the cursor goes first.
vCursorControl ← (vCursorControl) xor (4000C);
*vCursorY init to Ycoord - (1 if B,2 if A); counts down by 2/scanline
*until negative; shows cursor for 8 scan-lines; finally, sets vCursorY
*to large pos. number to disable until end-of-field.
vCursorY ← (vCursorY) + T;
vDBuf2 ← (vDBuf2) xnor (0C);
*Increment nibble number
vDBuf2 ← (vDBuf2) + (4C);
T ← (vDBuf2) and not (176000C);*Save low ten bits
:IF[LFKeyboard]; ******************************
*Add X to initial Y
vCursorControl ← (vCursorControl) + T;
vBitmapHi ← HiA[KeyTable];*Setup for vPostKey
*37 or 40 cycles to vM1.
vBitmapHi ← (vBitmapHi) or (LoA[KeyTable]), Call[vKBIn];

*A user typing 70 words/min would average 420 chars/min = 840 keystrokes/min
*= 14 keystrokes/sec and this code is sampling vKeyBuffer at the field rate,
*77 times/second; for this algorithm to fall behind and drop characters,
*the user will have to produce 3 keystrokes in 2 field times.
*~13 cycles to vM1 if no keystroke posted, else 62 to 65 cycles to vM1 with
*12 cycles free during the dead time of the PStore1.
@vVB1:
LU ← LHMask[vKeyBuffer], Call[vPostKey];
:ELSE; ****************************************
*Increment nibble number
*Add X to initial Y; 33 or 36 cycles to vM1
vCursorControl ← (vCursorControl) + T, Call[vKBIn];
:ENDIF; ***************************************

*Repeat VSStart or +1 times for blank lines at end of field
@vVB2:
Input[vDBuf0,0,vCnt], GoTo[vKBIOS,R>=0];

vCnt ← 10C;
Output[vCR,vCReg];*Start vertical sync
*Load half of the cursor memory shown on this field in 16d scanlines.
*Every two scanlines four nibbles are loaded.
vVS0:
T ← CursorBitmap;
T ← (LdF[vSLC,1,4]) + T, Task;
PFetch1[vCSB,vDBuf0];*Fetch word to load (CSB+11b+vSLC.1..4)
vCnt ← (vCnt) - 1, Call[vLdCur];*~30 cycles to vM1

@vVS1:
Output[vDBuf0,vCursorMem0];
vSLC ← (vSLC) + (4C), Call[vLdCur];*~36 cycles to vM1

@vVS2:
Output[vDBuf0,vCursorMem0,vCnt], GoTo[.+3,R<0];
vSLC ← LHMask[vSLC];
vSLC ← (vSLC) + (10000C), GoTo[vVS0];

vCnt ← vSyncLength;
vSLC ← HiA[Sub[MaxScanLines,2]], Call[.+1];
*Continue VSync; repeats vSyncLength+1 scanlines
@vVS3:
Input[vDBuf0,0,vCnt], Skip[R<0];
vCnt ← (vCnt) - 1, IOStrobe, KBCheck;*7 cycles to vM1 loop
vDBuf1 ← Add[40000,vStart]C;
Output[vDBuf1,vBufStart];*Start←vBufSt, ForceIARLoad’←0
vCR ← (vCR) and not (VS);
*The RM register vDBuf1 is a "don’t care" in the next mi
Output[vDBuf1,vLdIAR];*IAR←Start (resynchronize)
*End VSync
vCnt ← vBlankLength, Call[vKBOut];*32 cycles to vM1

*NOTE: vBitmap/vBitmapHi coincident with vDBuf2/3;
@vTVB0:
PFetch2[vCSB,vDBuf2,DCBptr];*DCBptr & int. mask.
vDBuf1 ← Add[140000,vStart]C, Call[.+1];*ReSync IAR
*Repeat vBlankLength+1 blank scanlines at top of field
@vTVB1:
Input[vDBuf0,0,vCnt], Skip[R<0];
vCnt ← (vCnt) - 1, IOStrobe, KBCheck;*7 cycles to vM1
vCR ← (vCR) and not (Blank), Call[vChkMsg];*10 cycles to vM1
*Continue on same scanline after tasking.
Output[vDBuf1,vBufStart];*Start←vBufSt, ForceIARLoad’←1
*Field interrupt
T ← vDBuf3, LoadPageExternal[NotifyInterruptPage];
vDBuf0 ← T, IOStrobe, CallExternal[NotifyInterruptLoc];

@vTVB2:
T ← vCSBHi;
vBitmapHi ← T;
LU ← vBitmap, Call[vDCB0];*DCBptr fetched at @vTVB0+3
*Here on same scanline after tasking
LU ← (vDBuf0) and (DCBBackground);
T ← RHMask[vDBuf0], Skip[ALU=0];
vCR ← (vCR) or (BlackBackground), DblGoTo[vZeroWidthDCB,.+2,ALU=0];
vCR ← (vCR) and not (BlackBackground), GoTo[vZeroWidthDCB,ALU=0];
PFetch2[vBitmap,vBitMap,DCBBitmapPtr];
vSLC ← (vSLC) or (LoA[Sub[MaxScanLines,2]]);
vBitMapHi ← T ← (vBitMapHi) and (77C);
vBitMapHi ← (LSh[vBitMapHi,10]) + T + 1, GoTo[vWidNZ];

*Set CEnable bit; set cursor word number to largest line, so that it will
*count to 0/1 when cursor is first shown.
vDCB0:
vCursorControl ← (vCursorControl) or (172000C), Skip[ALU=0];
**Here use SrcDest=vDBuf0=0 to setup IOAtten for vChkMsg.
PFetch1[vBitmap,vDBuf0,DCBFlags], GoTo[vChkMsg];
Input[vCnt,0];
vDBuf0 ← 0C, KBCheck;*14 cycles to vM1

*vCReg=0 sets IAddr[6:7] to 0 for IOAtten test
vSF0:
Output[vCR,vCReg,vCursorY], GoTo[.+3,R Even];*Test field bit
vCursorControl ← T ← 0C;*Even scan lines next
vCnt ← vVSStart, GoTo[.+3];
vCursorControl ← T ← 4000C;*Odd scan lines next
vCnt ← Add[vVSStart!,1]C;
vSLC ← T, IOStrobe, KBCheck;

vLdCur:
Output[vSLC,vCursor0,vDBuf2];
vDBuf0 ← LCy[vDBuf0,4];
Output[vDBuf0,vCursorMem0];
vSLC ← (vSLC) + (4C);
Output[vSLC,vCursor0,vDBuf2];
vDBuf0 ← LCy[vDBuf0,4], GoTo[vKBO1];

vKBOut:
Output[vCR,vCReg];*Avoid Output-Output-PStore4 problem by
Nop;*waiting here for 5 mi
vKBO1:
IOStrobe, GoTo[vChkMsg];

vKBIn:
Input[vDBuf0,0];*Set up IAddr.6..7 = 0 to make IOAtten reference
*channel 0 for vChkMsg
vKBIOS:
vCnt ← (vCnt) - 1, IOStrobe, KBCheck;

vKBInx:
Input[vDBuf0,0];*Same as vKBIn without IOStrobe
vKBCnt:
vCnt ← (vCnt) - 1, KBCheck;

*For zero-width DCBs send two scanlines of zeroes (to fill the two ping-pong
*buffers), then do only cursor and message checking until the next DCB.
vZeroWidthDCB:
IOStrobe;
vBitmap ← ZeroDataBuffer, Task;
*19 cycles to vM1
vSLC ← (vSLC) or (LoA[Sub[MaxScanLines,4]]);

*31 cycles to vM1 (LF monitor); 47 cycles to task (CSL monitor)
Output[vCR,vCReg];
:IF[LFMonitor]; *******************************
IOFetch16[vBitMap,vfBuf0,0], Call[vZMsg];*Fill 1st buffer
*45 cycles to task in worst case (showing cursor)
IOFetch16[vBitMap,vfBuf0,0], Call[vZCur];

IOFetch16[vBitMap,vfBuf0,0], Call[vZMsg];*Fill 2nd buffer
IOFetch16[vBitMap,vfBuf0,0], Call[vZCur];
:ELSE; ****************************************
vCnt ← 0C;
IOFetch4[vBitMap,vfBuf0,0], Call[vZMsg];*Fill 1st buffer
*45 cycles to task in worst case (showing cursor)
Output[vCnt,vBuf0], Call[vZCur];

IOFetch4[vBitMap,vfBuf0,0], Call[vZMsg];*Fill 2nd buffer
Output[vCnt,vBuf0], Call[vZCur];
:ENDIF; ***************************************
*Loop checking for messages & cursor. Since only 16x16 of the hardware’s
*32x32 cursor is used, it is shown 8 scanlines/field and then disabled on
*the following scanline.
**Should figure a way to avoid checking both vCursorY and vSLC here**
vCursorY ← (vCursorY) - (2C), Skip[R<0];
Input[vDBuf0,0,vSLC], DblGoTo[vCursn,vCursm,R<0];
vCursorControl ← (vCursorControl) + (10000C);
LU ← (vCursorControl) + (10000C), Skip[ALU>=0];
*Last was final cursor line--disable cursor now.
vCursorControl ← (vCursorControl) and not (102000C);
Output[vCursorControl,vCursor0,vCnt], Skip[ALU>=0];
*Set vCursorY to count to a large positive number after one more iteration.
vCursorY ← 100000C;
vSLC, Skip[R<0];*Skip on last scanline of field
vCursm:
vSLC ← (vSLC) - 1, IOStrobe, GoTo[vChkMsg];
vCursn:
IOStrobe, Call[vChkMsg];
T ← LdF[vCR,15,1], GoTo[vFDon1];

vZMsg:
IOFetch16[vBitMap,vfBuf0,0], KBCheck;

vZCur:
IOStrobe;
:UNLESS[LFMonitor]; ***************************
Output[vCnt,vBuf0];
:ENDIF; ***************************************
vCursorY ← (vCursorY) - (2C), Skip[R<0];
vZCur0:
IOFetch16[vBitMap,vfBuf0,0], Return;
vCursorControl ← (vCursorControl) + (10000C);
LU ← (vCursorControl) + (10000C), Skip[ALU>=0];
*Last was final cursor line--disable cursor now.
vCursorControl ← (vCursorControl) and not (102000C);
Output[vCursorControl,vCursor0,vCnt], Skip[ALU>=0];
*Set vCursorY to count to a large positive number after one more iteration.
vCursorY ← 100000C;
GoTo[vZCur0];

:IF[LFMonitor]; *******************************
vWidNZ1:
IOStrobe, Disp[.+1];
*Odd field--offset by WordCount
T ← vCnt ← WordsPerLine, Return, DispTable[2];
*Even field--no offset
T ← vCnt ← 0C, Return;

vWidNZ:
Dispatch[vCR,15,1], Call[vWidNZ1];

%Begin visible field. Worst case execution time for a scanline (showing
the cursor and longest backchannel end-of-message case) is about 86 cycles;
normal time is about 66 cycles.
**Code here relies on another task running after the T ← vCnt ← X, Return;
**above to avoid the bypass kludge.
%
Output[vCR,vCReg], GoTo[vLFSL0];

vChkCall:
T ← vCnt ← (vCnt) + (20C), KBCheck;

vLastL:
T ← vCnt ← (vCnt) + (20C), Return;

:IF[OnlyTwoWakeups]; ****************************
*Not tasking here costs ~1.5 percent in machine utilization, so avoid if
*possible.
vLFSL1:
UseCTask, Call[vLastL];
:ELSE; ******************************************
vLFSL1:
Call[vLastL];
:ENDIF; *****************************************

*3rd wakeup/scanline (2nd if OnlyTwoWakeups=1)
IOFetch16[vBitmap,vfBuf0], Call[vLFSL2];
:IF[FourWakeups]; *******************************

*4th wakeup/scanline
IOFetch16[vBitmap,vfBuf0], Call[vLFSL3];

*1st wakeup/scanline
**Note: every cycle here degrades emulator performance about 0.5 percent.
vLFSL0:
IOFetch16[vBitmap,vfBuf0], Call[vChkCall];

*2nd wakeup/scanline
IOFetch16[vBitmap,vfBuf0], GoTo[vLFSL1];

vLFSL3:
vSLC ← (vSLC) - 1, IOStrobe, Skip[R<0];
vNextSL:
T ← vCnt ← (vCnt) + (Add[WordsPerLine!,20]C), Return;
Call[vNextSL];
T ← LdF[vCR,15,1], GoTo[vFDon1];

vLFSL2:
vCursorY ← (vCursorY) - (2C), Skip[R<0];
T ← vCnt ← (vCnt) + (20C), Return;
*Only three wakeups when showing the cursor to avoid slow timing.
vCursorControl ← (vCursorControl) + (10000C), Call[vShowC];

*1st wakeup/scanline
**Note: every cycle here degrades emulator performance about 0.5 percent.
IOFetch16[vBitmap,vfBuf0], Call[vChkCall];

*2nd wakeup/scanline
IOFetch16[vBitmap,vfBuf0], GoTo[vLFSL1];

vShowC:
LU ← (vCursorControl) + (10000C), Skip[ALU>=0];
vCursorControl ← (vCursorControl) and not (102000C);
Output[vCursorControl,vCursor0,vCnt], Skip[ALU>=0];
vCursorY ← 100000C;*Next scanline, disable cursor
T ← vCnt ← (vCnt) + (20C);
vSLC ← (vSLC) - 1, IOStrobe, GoTo[.+3,R>=0];
*Last scanline of DCB; change TPC to initiate DCB setup.
IOFetch16[vBitmap,vfBuf0], Call[vNextSL];
T ← LdF[vCR,15,1], GoTo[vFDon1];
IOFetch16[vBitmap,vfBuf0], GoTo[vNextSL];

:ELSE; ******************************************

*1st wakeup/scanline
**Note: every cycle here degrades emulator performance about 0.5 percent.
vLFSL0:
IOFetch16[vBitmap,vfBuf0], Call[vChkCall];

*2nd wakeup/scanline
IOFetch16[vBitmap,vfBuf0], GoTo[vLFSL1];

vLFSL2:
vCursorY ← (vCursorY) - (2C), Skip[R<0];
T ← vCnt ← (vCnt) + (20C), GoTo[vNoCurs];
vCursorControl ← (vCursorControl) + (10000C);
LU ← (vCursorControl) + (10000C), Skip[ALU>=0];
vCursorControl ← (vCursorControl) and not (102000C);
Output[vCursorControl,vCursor0,vCnt], Skip[ALU>=0];
vCursorY ← 100000C;*Next scanline, disable cursor
T ← vCnt ← (vCnt) + (20C);
vNoCurs:
vSLC ← (vSLC) - 1, IOStrobe, GoTo[vLast16,R>=0];
*Last scanline of DCB; change TPC to initiate DCB setup.
IOFetch16[vBitmap,vfBuf0], Call[vLastL];
T ← LdF[vCR,15,1], GoTo[vFDon1];
vLast16:
IOFetch16[vBitmap,vfBuf0];
**This mi requires another task to run before the display task runs again.
T ← vCnt ← (vCnt) + (Add[WordsPerLine!,20]C), Return;
:ENDIF; *****************************************

:ELSE; ****************************************

%NOTES:
Since each bitmap starts on an even word, interleaved scanlines will be
spaced by 4*n words, so the first word of each scanline has the same
alignment within a quadword.
%
vWidNZ:
Dispatch[vCR,15,1], Call[vWNZ2];

*Begin visible field with quadwords beginning each scanline
Output[vCR,vCReg], GoTo[vWNE1];

vWNZ2:
IOStrobe, Disp[.+1];
*Odd field--offset by WordsPerLine.
vBitmap ← (vBitmap) + (WordsPerLine), DispTable[2];
*Even field--no offset.
vWNZ0:
Dispatch[vBitmap,16,1], Skip[Carry’];
vBitmapHi ← (vBitmapHi) + (400C) + 1, GoTo[.-1];
T ← 44C, Disp[.+1];
PFetch2[vBitmap,vDBuf0], Return, DispTable[2];
T ← vCnt ← 0C, Call[vNoMsg];*vCnt is returned in T

*Begin visible field with non-quadaligned word pair beginning each scanline
**Require another task to intervene between the previous mi and DisplayTask.
Output[vCR,vCReg], GoTo[vWNO1];


v16a444:
IOFetch16[vBitmap,vfBuf0,14], KBCheck;

v16a4:
T ← vCnt ← 24C;
IOFetch16[vBitmap,vfBuf0,4], KBCheck;

vIO4Ck:
IOFetch4[vBitmap,vfBuf0,4], KBCheck;

v4a4164:
IOFetch4[vBitmap,vfBuf0];
T ← 34C;
IOFetch4[vBitmap,vfBuf0], Return;

*This page has code for fields which begin with a non-quadaligned word pair.
*Scanlines begin at vWNO1 or at v4+2; at these places, vBitmap+T must point
*at the first word and vCnt must contain the same value as T.
v444416:
Call[vIO4Ck];
IOFetch4[vBitmap,vfBuf0,10];
IOFetch4[vBitmap,vfBuf0,14];
IOFetch4[vBitmap,vfBuf0], GoTo[v16a];

v444:
T ← 30C, Call[v4a4164];
v4:
T ← 40C;
IOFetch4[vBitmap,vfBuf0], Call[vCurs];
PFetch2[vBitmap,vDBuf0], GoTo[vWNOLp];

vCurs:
vCursorY ← (vCursorY) - (2C), GoTo[vCursb,R>=0];
vCursorControl ← (vCursorControl) + (10000C);
LU ← (vCursorControl) + (10000C), Skip[ALU>=0];
vCursorControl ← (vCursorControl) and not (102000C);
Output[vCursorControl,vCursor0,vCnt], Skip[ALU>=0];
vCursorY ← 100000C;*Next scanline, disable cursor
vSLC ← (vSLC) - 1, IOStrobe, DblGoTo[vCursc,vCursd,R<0];
vCursb:
vSLC ← (vSLC) - 1, IOStrobe, GoTo[vCursd,R>=0];
*Last scanline of DCB; change TPC to initiate DCB setup.
vCursc:
Call[vCurse];
T ← LdF[vCR,15,1], GoTo[vFDon1];
vCursd:
vCnt ← T ← Add[WordsPerLine!,44]C;
vCurse:
vCursorControl ← vCursorControl, Return;*Interlock

vWNOLp:
T ← (vCnt) + (2C);
vBitmap ← (vBitmap) + T;
*NOTE: supplying some base register here is needed to avoid waiting for
*the PFetch2 to finish (because BR defaults to vDBuf0).
Output[vDBuf0,vBuf0,vCnt], Skip[Carry’];
vBitmapHi ← (vBitmapHi) + (400C) + 1;
Output[vDBuf1,vBuf0,vCnt];
T ← 67C;
*Can do IOFetch16’s so long as addr[8:15] .le. 360b (HW manual indicates a
*hex-aligned restriction, but it is wrong). If addr[10:17b] .ls. 340b,
*when normal IOFetch4-16-16 works
LU ← (LdF[vBitmap,10,6]) - T - 1, Call[v36d];
v16a:
T ← 24C;
IOFetch16[vBitmap,vfBuf0], Call[vCurs];
*Next scanline continues here with vCnt in T
vWNO1:
PFetch2[vBitmap,vDBuf0], GoTo[vWNOLp];

*23 cycles from wakeup to here
v36d:
Dispatch[vBitmap,13,4], Skip[Carry];
*35 cycles from wakeup to vM1
IOFetch4[vBitmap,vfBuf0,0], GoTo[v16a4];*2-4-16-16
T ← vCnt ← 20C, Disp[.+1];
*30 cycles from wakeup to vM1
IOFetch16[vBitmap,vfBuf0,0], Call[vChkMsg], At[vOdFet,0];*2-16-16-4
IOFetch16[vBitmap,vfBuf0], GoTo[v4];
*36 cycles from wakeup to vM1 (65 cycles from wakeup to task)
IOFetch4[vBitmap,vfBuf0,0], Call[v16a4], At[vOdFet,2];*2-4-16-4-4-4-4
IOFetch4[vBitmap,vfBuf0], GoTo[v444];
*36 cycles from wakeup to vM1
IOFetch4[vBitmap,vfBuf0,0], Call[v16a4], At[vOdFet,4];*2-4-16-4-4-4-4
IOFetch4[vBitmap,vfBuf0], GoTo[v444];
*36 cycles from wakeup to vM1 (65 cycles from wakeup to task)
IOFetch4[vBitmap,vfBuf0,0], GoTo[v16a4], At[vOdFet,6];*2-4-16-16
*30 cycles from wakeup to vM1
IOFetch16[vBitmap,vfBuf0,0], Call[vChkMsg], At[vOdFet,10];*2-16-16-4
IOFetch16[vBitmap,vfBuf0], GoTo[v4];
*33 cycles from wakeup to vM1
IOFetch4[vBitmap,vfBuf0,0], GoTo[v444416], At[vOdFet,12];*2-4-4-4-4-4-16
*33 cycles from wakeup to vM1
IOFetch4[vBitmap,vfBuf0,0], GoTo[v444416], At[vOdFet,14];*2-4-4-4-4-4-16
*36 cycles from wakeup to vM1 (65 cycles from wakeup to task)
IOFetch4[vBitmap,vfBuf0,0], GoTo[v16a4], At[vOdFet,16];*2-4-16-16

*This page has code for fields which have quadaligned scanlines. Scanlines
*may begin at vWNE1 or at vWNEgo+2; at these places the vBitmap base register
*must point at the first word of the scanline, vCnt is "don’t care", and
*the last two words for the next scanline at vBitmap+44b must have been
*PFetch’ed into vDBuf0/1.

vCur2:
Output[vDBuf0,vBuf0,vCnt];
vCursorY ← (vCursorY) - (2C), GoTo[vCur2b,R>=0];
vCursorControl ← (vCursorControl) + (10000C);
LU ← (vCursorControl) + (10000C), Skip[ALU>=0];
vCursorControl ← (vCursorControl) and not (102000C);
Output[vCursorControl,vCursor0,vCnt], Skip[ALU>=0];
vCursorY ← 100000C;*Next scanline, disable cursor
vSLC ← (vSLC) - 1, IOStrobe, DblGoTo[vCur2d,vCur2c,R>=0];
vCur2b:
vSLC ← (vSLC) - 1, IOStrobe, GoTo[vCur2d,R>=0];
*Last scanline of DCB; change TPC to initiate DCB setup.
vCur2c:
Output[vDBuf1,vBuf0,vCnt], Call[.+2];
T ← LdF[vCR,15,1], GoTo[vFDon1];
vDBuf1 ← vDBuf1, Return;
vCur2d:
vBitmap ← (vBitmap) + (LShift[WordsPerLine!,1]C);
Output[vDBuf1,vBuf0,vCnt], Skip[Carry’];
vBitmapHi ← (vBitmapHi) + (400C) + 1;
*Fetch the end two words for next time unless last line
T ← 44C;
PFetch2[vBitmap,vDBuf0], Return;

v4442:
T ← 30C, Call[v4a4164];
v42:
T ← 40C;
IOFetch4[vBitmap,vfBuf0], Call[vCur2];
vWNE1:
T ← 67C, GoTo[vWNEgo];

v4416442:
IOFetch4[vBitmap,vfBuf0,4];
IOFetch4[vBitmap,vfBuf0,10];
T ← vCnt ← 34C, Call[v16a444];
IOFetch4[vBitmap,vfBuf0], GoTo[v42];

*Next scanline continues here with 67b in T
*Can do IOFetch16’s so long as address[8:15] .le. 360b
*(HW manual indicates a hex-aligned restriction, but it is wrong)
vWNEgo:
LU ← (LdF[vBitmap,10,6]) - T - 1, Call[vWNELp];
*Return here for the normal IOFetch4-16-16-2 case
IOFetch16[vBitmap,vfBuf0], Call[vCur2];
T ← 67C, GoTo[vWNEgo];

vWNELp:
Dispatch[vBitmap,13,4], Skip[Carry];
IOFetch4[vBitmap,vfBuf0,0], GoTo[v16a4];
T ← vCnt ← 20C, Disp[.+1];

*This dispatch table is entered when vBitmap points into the last 40b words
*of a 400b-word group. The code uses two IOFetch16’s if possible, where the
*constraint is that an IOFetch16 can’t cross the 400b word boundary.
IOFetch16[vBitmap,vfBuf0,0], Call[vChkMsg], At[vEvFet,0];*16-16-4-2
IOFetch16[vBitmap,vfBuf0], GoTo[v42];

IOFetch4[vBitmap,vfBuf0,0], Call[v16a4], At[vEvFet,2];*4-16-4-4-4-4-2
IOFetch4[vBitmap,vfBuf0], GoTo[v4442];

IOFetch4[vBitmap,vfBuf0,0], Call[v16a4], At[vEvFet,4];*4-16-4-4-4-4-2
IOFetch4[vBitmap,vfBuf0], GoTo[v4442];

IOFetch4[vBitmap,vfBuf0,0], GoTo[v16a4], At[vEvFet,6];*4-16-16-2
IOFetch16[vBitmap,vfBuf0,0], Call[vChkMsg], At[vEvFet,10];*16-16-4-2
IOFetch16[vBitmap,vfBuf0], GoTo[v42];
IOFetch4[vBitmap,vfBuf0,0], GoTo[v4416442], At[vEvFet,12];*4-4-4-16-4-4-2
IOFetch4[vBitmap,vfBuf0,0], GoTo[v4416442], At[vEvFet,14];*4-4-4-16-4-4-2
IOFetch4[vBitmap,vfBuf0,0], GoTo[v16a4], At[vEvFet,16];*4-16-16-2
:ENDIF; ***************************************
vChkMsg:
DblGoTo[vM1,vM0,IOAtten’];

:IF[LFKeyBoard]; ******************************
%The keyboard microcomputer delivers messages to the UTVFC over the
backchannel one bit at-a-time, with a format as follows:
0 SSSBBB YYYY XXXX 1
-or-KKKKKKKK 1 SSSBBB YYYY XXXX 1
where the right-most bit is the first to arrive. XXXX and YYYY are two’s
complement mouse delta-X and delta-Y, respectively; SSSBBB are mouse buttons
(in what order?) and status bits (video, VS, and power supply normal); a 0
after this terminates the message; a 1 is followed by 8 bits of key data,
which encode key strokes (i.e., transitions of keyboard characters up or
down); the vPostKey subroutine uses KeyTable (next page) to translate KKKKKKKK
into bits to be set/cleared in the storage table at vCSB+10.

The mouse resolves 200 pixels/inch. 35 inches/sec is the maximum speed
required for tracking the mouse. We update the mouse position once every
field, ~77 times a second. Therefore tracking the mouse at maximum velocity
requires a field large enough hold 35*200/77 = 91d counts. Adding 1 bit for
sign mandates an 8 bit field for x and another 8 bit field for y.

vMsgStatus remains 75b until a backchannel 1 (IOAtten) begins a message.
Bit 0 indicates a message in progress; vMsgStatus[10:12b] is "state" and
vMsgStatus[13:17b] is "count". Carry-out of count increments state:
state size
0 -- idle, terminated by a 1 bit
1
3 Build 1st 3 bits of delta X.
2
4 Collect 4th bit of X and post; build 1st 3 bits of delta Y.
3
7 Collect 4th bit of Y and post; build all 6 bits of BS.
4
1 Post buttons and status; 0 in 1st bit terminates message.
8 if 1 in 1st bit, continue and collect 1st 7 bits of key data.
5
1 Collect 8th bit of K and post; reset to state 0.

Must preserve T or reload it from vCnt for main loop.

Timing is 4 cycles when idle, 7 for the 1st message bit, 10 for
non-transition message bits, 15 or 16 on mouse delta X or delta Y, 15 or 16
for key data, and 15 or 14 for buttons and status. Results are preserved in
RM until the next vertical blanking period, then posted.
%
vM1:
LU ← LdF[vMsgStatus,13,5], Skip[R<0];*0’s (IOAtten’) here
vMsgRet:
vMsg ← RSh[vMsg,1], Return;*Idle
Dispatch[vMsgStatus,10,3], Skip[ALU=0];*Dispatch on state bits
vMsgStatus ← (vMsgStatus) + 1, GoTo[.-2];*Count .ne. 0
vMsg ← RSh[vMsg,1], Disp[.+1];
*Reach the dispatch table after 7 cycles.
*States 0-1 never get here; states 6-7 impossible.
*State 2 .eq. X; set count to 4-1; clear high bits of vMsg for vKY later.
vKX:
T ← vMsg ← RSh[vMsg,14], GoTo[vKeyX], DispTable[4,17,2];
*State 3 .eq. Y; set count to 7-1
vKY:
T ← vMsg ← RSh[vMsg,4], GoTo[vKeyY];
*State 4 .eq. Buttons; change to state 0 (no char follows) or 5, count 10b-1
T ← vMsg ← RSh[vMsg,7], GoTo[vKeyBI];
*State 5: post key data; reset to state 0.
vKB:
T ← vMsg ← RSh[vMsg,10];
vKeyBuffer ← (LSh[vKeyBuffer,10]) or T;
vMsgStatus ← 75C;
vMSU:
T ← vCnt, Return;

vKeyY:
vMsgStatus ← (vMsgStatus) or (32C), Skip;
vKeyX:
vMsgStatus ← (vMsgStatus) or (35C);
vMouseDxy ← (vMouseDxy) + T, GoTo[vMSU];

*State 4; set count to 10b-1 if key data follows.
vKeyBS:
vMsgStatus ← (vMsgStatus) or (31C), Skip;
vKeyBI:
vMsgStatus ← 75C;*Directly here if no key data follows
vButtons ← T, GoTo[vMSU];

vKCount:
vMsgStatus ← (vMsgStatus) + 1, Return;

*Dispatch on state bits and high bit of count.
vM0:
LU ← Dispatch[vMsgStatus,10,4], Skip[R<0];*1’s (IOAtten) here
vMsgStatus ← (vMsgStatus) or (100000C), GoTo[vMsgRet];
vMsg ← RSh[vMsg,1], Disp[.+1];*Dispatch on state bits
*Timing to the dispatch table is 6 cycles.
vMsg ← (vMsg) or (100000C), GoTo[vKCount], DispTable[10,17,3];
vMouseDxy ← (vMouseDxy) - (10C), GoTo[vKX];
vMsg ← (vMsg) or (100000C), GoTo[vKCount];
vMouseDxy ← (vMouseDxy) + (174000C), GoTo[vKY];
vMsg ← (vMsg) or (100000C), GoTo[vKCount];
T ← vMsg ← RSh[vMsg,7], GoTo[vKeyBS];
vMsg ← (vMsg) or (100000C), GoTo[vKCount];
vMsg ← (vMsg) or (100000C), GoTo[vKB];

%Have 0 to 2 characters represented in vKeyBuffer; determine the bit and
word position in the vCSB+10 bit table by lookup in IM KeyTable;
set/clear that bit according to key down/up. Have to manipulate the
vKeyBuffer register in such a way that if vChkMsg shifts in another event,
nothing is lost; also, don’t want to process events twice. Algorithm is:
If LH of vKeyBuffer is non-zero, post the event, zero the LH, and done; else
left shift vKeyBuffer 10b and conditionally post. Both of these tests must
be made on one scanline.
%
vPostKey:
T ← LdF[vKeyBuffer,1,5], GoTo[vPKGo,ALU#0];*If no data,
vKeyBuffer ← LSh[vKeyBuffer,10];*shift to other event
T ← LdF[vKeyBuffer,1,5], Skip[ALU=0];
vBitmapHi ← (vBitmapHi) + T, GoTo[vPKGo1];
Input[vDBuf0,0], GoTo[vKBIOS];
vPKGo:
vBitmapHi ← (vBitmapHi) + T;
vPKGo1:
T ← LdF[vKeyBuffer,6,1];*Set H2 to high/low word
*Now set vBitmapHi[0:7] to 0 for later while reading IM.
APCTask&APC ← vBitmapHi, vBitmapHi ← T, NoRegILockOK;
ReadCS;*Get the word
LU ← LdF[vKeyBuffer,7,1], DispTable[1,1,0];*Even loc after ReadCS
*low or high byte
T ← 10C, GoTo[vPKeyL,ALU=0];*vCSB+10 = 1st KB word
*Low byte of IM word.
T ← (LdF[CSData,15,3]) + T;*Get word number
PFetch1[vCSB,vDBuf0];*Fetch KB word
vBitmap ← T;*Bypass kludge to remember address
Dispatch[CSData,11,2];*Dispatch on top two bits of bit number
*Dispatch on bottom two bits of bit number
Dispatch[CSData,13,2], Disp[.+1];
vPKeyB:
vDBuf1 ← T ← 100000C, Disp[vPKey0], DispTable[4];
vDBuf1 ← T ← 4000C, Disp[vPKey0];
vDBuf1 ← T ← 200C, Disp[vPKey0];
vDBuf1 ← T ← 10C, Disp[vPKey0];

*Shift possible 2nd character into bits 0:7 and test for key up/down
vPKey0:
vKeyBuffer ← RHMask[vKeyBuffer], DblGoTo[vPKUp,vPKDwn,R<0], DispTable[4];
T ← RSh[vDBuf1,1], GoTo[vPKey0];
T ← RSh[vDBuf1,2], GoTo[vPKey0];
T ← RSh[vDBuf1,3], GoTo[vPKey0];

vPKUp:
vDBuf0 ← (vDBuf0) or T, IOStrobe, Skip;*Key up--set bit
vPKDwn:
vDBuf0 ← (vDBuf0) and not T, IOStrobe;*Key down--clear bit
*Use SrcDest=0 to setup IOAtten here.
PStore1[vBitmap,vDBuf0,0], GoTo[vKBIOS];*Store word

*High byte of IM word.
vPKeyL:
T ← (LdF[CSData,5,3]) + T;
PFetch1[vCSB,vDBuf0];
vBitmap ← T;
Dispatch[CSData,1,2];
Dispatch[CSData,3,2], Disp[vPKeyB];

*KeyBoard Translation Table from Level III to Alto
*Entry in the table is bitnumber*8+wordnumber

Set[ByteLoc,KeyTable];

Macro[Byte,IMData[LH[LShift[#1,10],#2] RH[LShift[#3,10],#4] At[ByteLoc]]
Set[ByteLoc,ADD[ByteLoc,1]]];

KTable:
Byte[177,177,177,177];
*00
Byte[177,177,177,177];
Byte[177,177,177,177];
Byte[177,177,177,177];

*04:
Byte[005,115,155,105];
* D1, T10(\), T9, T8.
Byte[075,173,065,042];
* T7, R6(BW), T6, L12(FL4).
Byte[163,114,055,045];
* L9(FL3), L6(LF), T5, T4.
Byte[035,015,025,177];
* T3, T2, T1(esc), .

*10:
Byte[164,177,172,144];
* R4, , R1, R2.
Byte[004,125,177,024];
* R5, R3(FR5), , L10.
Byte[034,044,054,177];
* L7, L4, L1.
Byte[177,064,177,177];
* , A9, , .

*14:
Byte[154,074,171,150];
* R7, R10, R11, R8.
Byte[014,153,113,161];
* R9(FR4), R12(swat), A7(space), L11(CTL).
Byte[160,124,134,162];
* L8, L5, L2, L3(DEL).
Byte[104,165,177,177];
* A8, A11, , .

*20:
Byte[177,175,143,140];
* , A12, A6(shift-R), /.
Byte[122,131,073,063];
* ., (comma), m, n.
Byte[072,070,052,101];
* b, v, c, x.
Byte[102,177,135,177];
* z, , 47, .

*24:
Byte[142,152,141,132];
* A4(Return), 46(←), (quote), :.
Byte[121,110,062,043];
* l, k, j, h.
Byte[023,032,050,041];
* g, f, d, s.
Byte[051,177,103,112];
* a, , A3(lock), A5(shift-L).

*30:
Byte[145,151,123,130];
* A10, 45(]) , 42([), p.
Byte[111,071,060,033];
* o, i, u, y.
Byte[013,003,030,021];
* t, r, e, w.
Byte[031,022,177,174];
* q, A1(tab), , D2.

*34:
Byte[170,133,120,100];
* A2(bs), =, -, 0
Byte[061,053,040,020];
* 9, 8, 7, 6.
Byte[000,010,001,011];
* 5, 4, 3, 2.
Byte[002,012,177,177];
* 1, 48, , .
:ELSE; ****************************************

%CSL KEYBOARD vChkMsg collects and posts incoming messages from keyboard
microcomputer. Form of message is 1xxxxtttmmmmmmmmmmmmmmmm1, where
t = type bit, m = message bit, x = unused, and 1’s represent the leading
and trailing flags. IOAtten encodes the bits, delivered one/scanline.
At message start, vMsgStatus>=0 and the leading 1 has just been shifted out
of vMsg; in this case vMsg is saved in vMsgStatus with a sign bit of 1.
At message end, vMsgStatus<0 and the trailing 1 has just been shifted into
the low bit of vMsg.
Timing here to return = 4 cycles usually; 13 on message start; 30 on KB0 to
KB3 (includes 10 cycles MC1 wait by next task to run); 25 on Mouse XY end;
and 19 on KSMS end.
%
vM0:
vMsg ← LSh[vMsg,1], DblGoTo[vMsgA,vNoMsg,R<0];
vM1:
vMsg ← (LSh[vMsg,1]) + 1, DblGoTo[vMsgA,vNoMsg,R<0];

vNoMsg:
T ← vCnt, Return;
vMsgA:
T ← LSh[vMsgStatus,7], GoTo[vMsgEnd,R<0];
*Just collected 1st word of message; save it in vMsgStatus with sign of 1
vMsgBg:
T ← (vMsg) or (100000C);
vMsgStatus ← T;
vMsg ← 400C, GoTo[vNoMsg];

vMsgEnd:
*End-of-message--rebuild message body
T ← vMsg ← (RSh[vMsg,1]) or T, Skip[R Odd];
vMsgStatus ← 0C, GoTo[vEOMs];*Bad end flag--reject message
*Dispatch on type bits, setup for next message.
vMsgStatus ← Dispatch[vMsgStatus,4,3];
vKeyBuffer ← T, Disp[.+1];
*Timing from VM0/VM1 to here = 13 cycles
*Reject type 0 (possibly noise)
vMsg ← 0C, GoTo[vNoMsg], DispTable[10];
PStore1[vCSB,vKeyBuffer,10], GoTo[vEOMs];*KB0
PStore1[vCSB,vKeyBuffer,11], GoTo[vEOMs];*KB1
PStore1[vCSB,vKeyBuffer,12], GoTo[vEOMs];*KB2
PStore1[vCSB,vKeyBuffer,13], GoTo[vEOMs];*KB3
*Stores KSMS in 177033; junk in 177030-177032 would be ok.
vButtons ← T, GoTo[vEOMs];*KSMS (keyset/mouse switches)
vKeyBuffer ← 200C, GoTo[vMXY];*Delta X and Y for mouse
*Depressing the boot button causes an endless stream of IOAtten’ which
*manifests as an endless stream of 1’s here; for a message in which both
*the body and type are all 1’s (to eliminate noise) boot the machine.
*On keyboard boot, KB0 is 177776b for BS or 177577b for 0; KB0 is passed
*through the activity of Initial to the microcode being booted in BootTask’s
*T register, which is not smashed.
vMsg ← (vMsg) + 1;*Boot button
Skip[ALU=0];
vEOMs:
vMsg ← 0C, GoTo[vNoMsg];
PFetch1[vCSB,vMsg,10];*Fetch KB0 as parameter for Boot
vKeyBuffer ← LoA[BootSV];
vKeyBuffer ← (vKeyBuffer) or (HiA[BootSV,BootTask]);
APCTask&APC ← vKeyBuffer;
LU ← MNBR ← vMsg, Return;*Continue in BootTask at SuckADuck

*Delta X and delta Y are sent excess 128d; the value in vMouseDxy is
*initialized to 128d and the mouse cannot move more than about +/- 91d
*in one field time, so overflow of the 8 bit fields is impossible.
vMXY:
T ← (vKeyBuffer) or (100000C);
T ← (vMsg) - T;
vMouseDxy ← (vMouseDxy) + T, GoTo[vEOMs];

SetTask[BootTask];
*Only the T-registers of unused tasks (Possibly MNBR or SALUF also) will
*survive across the boot, so we tuck away KB0 so that microcode being booted
*can decide what kind of boot to do.
SuckADuck:
T ← MNBR, At[BootSV];
Boot, GoTo[.];
:ENDIF; ***************************************

:IF[LFKeyBoard];
END[LFDisplay];
:ELSEIF[LFMonitor];
END[CSLFDisplay];
:ELSE;
END[CSLDisplay];
:ENDIF;