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;(2048)\f5