INSERT[DisplayDefs];*Defs file common to DisplayInit and Display
:IF[LFKeyBoard];
TITLE[LFDisplay];
:ELSEIF[LFMonitor];
TITLE[CSLFDisplay];
:ELSE;
TITLE[CSLDisplay];
:ENDIF;
*Ed Fiala 29 June 1982

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

Timing considerations are as follows:

(1) LF monitors require scanline service every 28.8 microseconds, CSL monitors
every 37.8 microseconds. A crude estimate of average service time is the MC2
time for the IOFetch references: 4*(16+2) cycles (7.2 microseconds) for the
four IOFetch16’s of LF monitors or 4+6+6+6+18+18 cycles (5.8 microseconds) for
the PFetch2, two Outputs, one IOFetch4, and two IOFetch16’s of CSL monitors.
This suggests that the display task will consume 25 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 3 cycles and then be
held in MC1/MC2 wait as long as necessary.

(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
indicated in the timing model in (2) above, tasking before mi that would
otherwise be suspended in memory wait improves machine utilization since
another task may complete a few mi 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 or displace the screen image downward one scanline from
the point of failure onward. An attempt is made to task several times per
scanline and to limit worst case time between returns to 50 cycles to satisfy
service requirements of other tasks; however, common cases for the CSL monitor
presently require 63 cycles.

(4) The worst case for a scanline occurs when an end-of-message event occurs
on the backchannel and the cursor is being shown. It has been empirically
determined that the current arrangement, in which DisplayTask tasks twice, is
almost safe (screen glitches and missed messages rarely occur), but tasking a
third time loses.

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 has two low zeroes.
%

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

*If OnlyTwoWakeups is false, the LF monitor main loop will wakeup
*thrice/scanline; if 1, it will wakeup only twice/scanline.
Set[OnlyTwoWakeups,0];

SetTask[DisplayTask];

OnPage[DisplayPage];

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

*End-of-field comes to vFDon1.
*Set up to load cursor memory--mask Field bit.
vFDon1:
PFetch4[vCSB,vDBuf0,MouseX];
*Complement even/odd field bit and turn on blanking
vCR ← (vCR) xor (OddFld&Blank);
vCursorY ← (Zero) xnor T;
T ← vMouseDx;
vMouseDx ← 0C;
vDBuf0 ← (vDBuf0) + T;
T ← vMouseDy;
vDBuf1 ← (vDBuf1) + T;
*Post mouse x,y by adding vMouseDx/y to MouseX/Y in the CSB; CursorX/Y store
*is a no-op.
PStore4[vCSB,vDBuf0,MouseX];
vMouseDy ← 0C, Call[vSF0];*40 cycles to vM1

*Here with CursorX/Y in vDBuf2/3; setup cursor position
@vVB0:
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; 17 cycles to vM1
:IF[LFKeyboard]; ******************************
Macro[KBCheck,GoTo[vChkMsg]];
vDBuf2 ← (vDBuf2) + (4C);
*Mouse buttons; vDBuf0 stored into 177033. Here the mouse buttons are in
*vButtons[11:13d]; power supply normal, VS, and video are in vButtons[8:10]
*but not used.
T ← (vButtons) and (4C);
T ← (LdF[vButtons,13,2]) or T;
vDBuf0 ← (Zero) xnor T;
*Here IOAtten is selected by two low bits of the RM address of vDBuf0.
PStore1[vKeyboard,vDBuf0,3], Call[vKBCnt];
T ← (vDBuf2) and not (176000C);*save low ten bits
*Add X to initial Y; 30 or 33 cycles to task
vCursorControl ← (vCursorControl) + T, IOStrobe;
*Setup for vertical sync below; 19 cycles to task.
vCR ← (vCR) or (VS), Call[vPKSetup];

*A user typing 70 words/min would average 420 chars/min = 840 keystrokes/sec
*= 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 more than 2 keystrokes/field, which seems
*unlikely.
@vVB1:
Input[vDBuf0,0], Call[vKBCnt];*7 cycles to vM1
LU ← LHMask[vKeyBuffer], Call[vPostKey];*54 cycles to task

*@vVB2:
Input[vDBuf0,0], Call[vKBCnt];* 7 cycles to vM1
*
LU ← LHMask[vKeyBuffer], Call[vPostKey];*54 cycles to task
:ELSE; ****************************************
Macro[KBCheck,DblGoTo[vM1,vM0,IOAtten’]];
vDBuf2 ← (vDBuf2) + (4C), Call[vKBInx];
T ← (vDBuf2) and not (176000C);*save low ten bits
vCursorControl ← (vCursorControl) + T, IOStrobe, Task;*Add X to initial Y
vCR ← (vCR) or (VS);*14 or 17 cycles to task
:ENDIF; ***************************************

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

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];*28 cycles to vM1

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

*Set CEnable bit; set cursor word number to largest line, so that it will
*count to 0/1 when cursor is first shown.
@vTVB1:
T ← vCSBHi;
vBitmapHi ← T;
LU ← vBitmap;*DCBptr fetched at vSF2
vCursorControl ← (vCursorControl) or (172000C), Skip[ALU#0];
vDBuf0 ← 0C, Skip;*no bitmap
PFetch1[vBitmap,vDBuf0,DCBFlags];
Input[vCnt,0], Call[vChkMsg];*20 cycles to vM1
*Here on same scanline after tasking
LU ← (vDBuf0) and (DCBBackground);
LU ← 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];

*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:
vBitmap ← ZeroDataBuffer;
IOStrobe, Task;
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, KBCheck;
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[vSL0];

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

vLFSL1:
T ← vCnt ← (vCnt) + (20C), Call[vChkMsg];

*2nd wakeup/scanline
:IF[OnlyTwoWakeups]; ****************************
IOFetch16[vBitmap,vfBuf0];
*Not tasking here costs 1 to 3% in machine utilization, so avoid unless
*absolutely necessary.
UseCTask, Call[vLastL];
:ELSE; ******************************************
IOFetch16[vBitmap,vfBuf0], Call[vLastL];
:ENDIF; *****************************************

*3rd wakeup/scanline
IOFetch16[vBitmap,vfBuf0], Call[vLFSL2];

*1st wakeup/scanline
**Note: every cycle here degrades emulator performance about 0.5 percent.
vSL0:
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;
: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:
T ← 100000C, 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 core table at vKeyboard+4.

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 0 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.

At call, 100000b must be in T.

Timing (including vChkMsg) is 8 cycles when idle, 11 for the 1st message bit,
14 or 15 for non-transition message bits, 18 or 22 for mouse delta X or Y,
18 or 19 for key data, and 18 or 20 for buttons and status. Results are
preserved in RM until the next vertical blanking period, then posted.
%
vM1:
LU ← LdF[vMsgStatus,13,5], GoTo[.+3,R<0];*0’s (IOAtten’) here
vMBld0:
vMsg ← RSh[vMsg,1];*Idle
vMSU:
T ← vCnt, Return;
Dispatch[vMsgStatus,10,3], Skip[ALU=0];*Dispatch on state bits
vMsgStatus ← (vMsgStatus) + 1, GoTo[vMBld0];*Count .ne. 0
vMsg ← RSh[vMsg,1], Disp[vMX2];

vM0:
LU ← LdF[vMsgStatus,13,5], GoTo[.+3,R<0];*1’s (IOAtten) here
vMsgStatus ← 75C;
vMsgStatus ← (vMsgStatus) or (100000C), GoTo[vMSU];
Dispatch[vMsgStatus,10,3], GoTo[.+3,ALU=0];*Dispatch on state bits
vMsgStatus ← (vMsgStatus) + 1;*Count .ne. 0
***This is the mi which requires 100000b in T at entry--perhaps reconsider
***this requirement.
vMsg ← (RSh[vMsg,1]) or T, GoTo[vMSU];
vMsg ← (RSh[vMsg,1]) or T, Disp[.+1];
*States 0-1 never get here; states 6-7 impossible.
*State 2 .eq. X; set count to 4-1
vMX2:
vMsgStatus ← (vMsgStatus) or (35C), GoTo[vKeyX], DispTable[4,17,2];
*State 3 .eq. Y; set count to 7-1
vMsgStatus ← (vMsgStatus) or (32C), GoTo[vKeyY];
*State 4 .eq. Buttons; change to state 0 (no char follows) or 5, count 10b-1
T ← vMsg ← RSh[vMsg,7], DblGoTo[vKeyBS,vKeyBI,R<0];
*State 5: post key data; reset to state 0.
T ← vMsg ← RSh[vMsg,10];
vKeyBuffer ← (LSh[vKeyBuffer,10]) or T;
vMsgStatus ← 0C, GoTo[vMSU];

vKeyX:
T ← vMsg ← RSh[vMsg,14], Skip[R>=0];
T ← (vMsg) or not (17C);*neg., extend sign to seven bits
vMouseDx ← (vMouseDx) + T, GoTo[vMSU];

vKeyY:
T ← vMsg ← RSh[vMsg,14], Skip[R>=0];
T ← (vMsg) or not (17C);*neg., extend sign to seven bits
vMouseDy ← (vMouseDy) + T, GoTo[vMSU];

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

%Have 0 to 2 characters represented in vKeyBuffer; determine the bit and
word position in the vKeyboard 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
between the 1st and 2nd calls to vPostKey, nothing is lost; also, don’t want
to process events twice. Algorithm is:
1) If LH of vKeyBuffer is non-zero, post the event, zero the LH, and done.
Else shift vKeyBuffer left 8 bits.
2) If LH of vKeyBuffer is non-zero, post the event, zero the LH, and done.
Else done.
%
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];
IOStrobe;
Return;
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 ← 4C, GoTo[vPKeyL,ALU=0];*vKeyboard + 4 = 1st KB word
*Low byte of IM word.
T ← (LdF[CSData,15,3]) + T;*Get word number
PFetch1[vKeyboard,vDBuf1];*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:
vDBuf0 ← T ← 100000C, Disp[vPKey0], DispTable[4];
vDBuf0 ← T ← 4000C, Disp[vPKey0];
vDBuf0 ← T ← 200C, Disp[vPKey0];
vDBuf0 ← 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[vDBuf0,1], GoTo[vPKey0];
T ← RSh[vDBuf0,2], GoTo[vPKey0];
T ← RSh[vDBuf0,3], GoTo[vPKey0];

vPKUp:
vDBuf1 ← (vDBuf1) or T, IOStrobe, Skip;*Key up--set bit
vPKDwn:
vDBuf1 ← (vDBuf1) and not T, IOStrobe;*Key down--clear bit
PStore1[vBitmap,vDBuf1,0];*Store word
*Setup for next iteration during the 15 cycle MC1 busy of the PStore1;
*this is almost free because the next task is unlikely to compute more than
*10 cycles without making a memory reference.
vPKSetup:
vBitmapHi ← HiA[KeyTable];
vBitmapHi ← (vBitmapHi) or (LoA[KeyTable]), Return;

*High byte of IM word.
vPKeyL:
T ← (LdF[CSData,5,3]) + T;
PFetch1[vKeyboard,vDBuf1];
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,150,105];
* D1, T10(\), T9, T8.
Byte[075,171,065,163];
* T7, R6(BW), T6, L12(FL4).
Byte[172,160,055,045];
* L9(FL3), L6(LF), T5, T4.
Byte[035,012,025,177];
* T3, T2, T1(esc), .

*10:
Byte[164,177,173,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,161,155];
* R7, R10, R11, R8.
Byte[014,153,113,042];
* R9(FR4), R12(swat), A7(space), L11(CTL).
Byte[114,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,015,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;
27 on KB0 to KB3 and on KSMS (includes 8 cycles MC1 wait by next task to run;
or 27 on Mouse XY 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, GoTo[.+3,R Odd];
vMsgStatus ← 0C;
vBadCnt ← (vBadCnt) + (400C), GoTo[vEOMs];*Count bad end flags
*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); count for debugging
vBadCnt ← (vBadCnt) + 1, GoTo[vEOMs], DispTable[10];
PStore1[vKeyboard,vKeyBuffer,4], GoTo[vEOMs];*KB0
PStore1[vKeyboard,vKeyBuffer,5], GoTo[vEOMs];*KB1
PStore1[vKeyboard,vKeyBuffer,6], GoTo[vEOMs];*KB2
PStore1[vKeyboard,vKeyBuffer,7], GoTo[vEOMs];*KB3
*Stores KSMS in 177033; junk in 177030-177032 would be ok.
PStore1[vKeyboard,vKeyBuffer,3], GoTo[vEOMs];*KSMS (keyset/mouse switches)
T ← 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[vKeyboard,vMsg,4];*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

vMXY:
T ← (RHMask[vMsg]) - T;*delta Y (sent as excess 128d)
vMouseDy ← (vMouseDy) + T;
T ← 200C;
T ← (RSh[vMsg,10]) - T;*delta X (sent as excess 128d)
vMouseDx ← (vMouseDx) + 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;