*----------------------------------------------------------- Title[DisplayMain.mc.....November 23, 1982 3:42 PM...Taft]; *** Pilot-only version *** * Main part of Dorado display microcode -- included with emulators, * but not included with Bootstrap/Initial microcode. *----------------------------------------------------------- *----------------------------------------------------------- * Terminal Word Task (TWT) and Display Word Task (DWT) microcode. * TWT runs as either DWT or AWT, depending on hardware configuration. * If TWT runs as AWT, then DWT may run independently on behalf of * a display other than the 7-wire terminal, using the same word task microcode. *----------------------------------------------------------- % This microcode is structured as if it used subtasks, because it may be shared by non-Alto terminal microcode using DWT. However, when running in Alto terminal emulation mode as AWT or DWT, it NEVER expects to get a B channel command or to run as the B subtask. Note that this microcode can run on behalf of the DispM terminal controller or of either A or B channel of the DispY controller. DispY and DispM use independent but parallel RBase, MemBase, and TIOA values, and the A and B channels are differentiated by SubTask. Thus, for example, references to ACount (below) apply equally well to BCount or TCount in the appropriate circumstances. This code uses subtasks, and must ALWAYS Branch to the same place when blocking, must never test Branch conditions across blocks, and must use task-specific RBase, RStk, and MemBase values correctly. Note that the controller is designed to prohibit wakeups while DWT is initiated (either running or preempted) and for at least n instructions after any BLOCK, where n is approximately 5. DWT microcode wakes up in response to flags set by the DHT microcode. DWT does not need to be specially awakened for initialization. DWT will initialize itself EXCEPT that AAddress, BAddress, ACount, and BCount must have been initialized explicitly by DHT, because you cannot guarantee which subtask will run first, and DWTInit is executed only once. Subtask.0 defines a block of 16 subtask specific registers, of which 5 per subtask are used by DWT: AAddress, ACount, ANextCount, ANextAddrLo, and ANextAddrHi are the subtask-specific registers for Channel A. There is a similar set for channel B. % Set[XTask, IP[DWT]]; *----------------------------------------------------------- TWTInitPC: * Terminal word task initialization: return appropriate task number. *----------------------------------------------------------- Subroutine; KnowRBase[THTRegion]; T_ AWT, DisplayConfig, Branch[.+2, R<0]; T_ DWT; CoReturn; TopLevel; * Terminal word task starts up here as either DWT or AWT. * Select TIOA that addresses the appropriate hardware. * All other initialization and main loop code is the same as DWT. MemBase_ TChannelBR; RBase_ RBase[THTRegion]; RBase_ RBase[TWTRegion], DisplayConfig, Branch[UseDWTFlag, R>=0]; T_ AWTFlag, Branch[SetDWTFlag]; *----------------------------------------------------------- DWTInitPC: * Display word task initialization, for non-standard displays. *----------------------------------------------------------- Subroutine; KnowRBase[TWTRegion]; T_ DWT, CoReturn; TopLevel; * Display word task starts up here iff it is not being used for * driving the 7-wire terminal. RBase_ RBase[AChannelRegion]; MemBase_ AChannelBR; UseDWTFlag: T_ DWTFlag; *same for both subtasks SetDWTFlag: TIOA_ T; T_ 20C, Branch[DWTCheck]; *same for both subtasks *----------------------------------------------------------- * Display word task main loop -- shared by terminal word task. * ACount >=0 here iff data remains to be output for the current scan line. *----------------------------------------------------------- DWTStart: ACount_ (ACount)-T, Branch[DWTCheck, R<0]; * Fetch the next munch. Output_ 20 signals to the display controller * that a new munch is on its way. AAddress_ (IOFetch_ AAddress)+(Output_ T), Block, Branch[DWTStart]; * AAddress will be even if we just exhausted a scan line. * AAddress will be odd if we have been awakened to start a new scan line. * In either case, isolate flag in AAddress[15] for use in adjusting * the WCB flags (below). DWTCheck: AAddress_ (AAddress) AND (1C), Branch[DWTAdjustWCBFlags, R even]; * Starting new scan line. Copy parameters left by DHT. DWTRefill: BrHi_ ANextAddrHi, Branch[DWTSpuriousWakeup, R<0]; BrLo_ ANextAddrLo; ACount_ ANextCount; * (# Munches to Fetch)-1 in [8..11] * Now adjust WCB flags, as follows: * If we just exhausted a scan line, AAddress = 0 now; execute Output_ 0 * to clear the CurWCB flag, and set AAddress to -1 for the next wakeup. * If we are starting a new scan line, AAddress = 1 now; execute Output_ 1 * to set the CurWCB flag and clear the NextWCB flag, and set AAddress to 0 * for the first IOFetch. DWTAdjustWCBFlags: AAddress_ (AAddress)-1, Output_ AAddress, Block, Branch[DWTStart]; * A bug in the hardware causes occasional spurious wakeups; just ignore them. * Note that we do nothing to dismiss the spurious wakeups -- the ones caused by * the known hardware bug are transient. DWTSpuriousWakeup: ACount_ T-T-1, Block, Branch[DWTStart]; *----------------------------------------------------------- * 7-wire Terminal Horizontal Task (THT) microcode. * This microcode runs as DHT or AHT and does everything * for the display except IOFetches. *----------------------------------------------------------- *----------------------------------------------------------- DHTInitPC: * Dummy Display Horizontal Task initialization. * The label DHTInitPC is redefined if real DHT microcode is included. *----------------------------------------------------------- Subroutine; T_ DHT, CoReturn; TopLevel; * DHT will be awakened once; just ignore the wakeup. Block, Branch[.]; *----------------------------------------------------------- THTInitPC: * Terminal Horizontal Task initialization: return appropriate task number. * Note: must awaken the appropriate task here, since the task number is * a variable. *----------------------------------------------------------- Subroutine; KnowRBase[THTRegion]; T_ AHT, DisplayConfig, Branch[THTInitAHT, R<0]; T_ DHT; Wakeup[DHT], CoReturn; TopLevel; * DHT starts here T_ Statics, Branch[THTInit1]; * Use DispY TIOA Subroutine; THTInitAHT: Wakeup[AHT], CoReturn; TopLevel; * AHT starts here T_ TStatics, Branch[THTInit1]; * Use DispM TIOA THTInit1: TIOA_ T; RBase_ RBase[TWTRegion]; T_ AllShutUp, Call[OutputGetsT]; * Disable WakeUps, Reset DDC TCount_ T-T-1, MemBase_ IOBR; TAddress_ T-T-1, TIOA[TNLCB]; * Set initial TWT WCB flag TSetNextWCB_ ANextWCBFlag; * Initialize both channels to maximum left margins, so they never turn on. Call[SendMaxMarg]; * Reset various NLCB registers T_ AScan, Call[OutputGetsT]; T_ BScan, Call[OutputGetsT]; * Initialize B because polarity is ORed with A! T_ Modes, Call[OutputGetsT]; * THT initialization (cont'd) * Skip over HRam and MiniMixer initialization if DispM present. RBase_ RBase[THTRegion]; PD_ NOT (DisplayConfig); Branch[NoInitRams, ALU>=0]; * Initialize HRam and MiniMixer. Call[InitHRam]; Call[JLoadMiniMixer]; Nop; * Placement * Initialize vertical waveform and left margin constants according to * the appropriate hardware configuration and display mode, chosen from: * {Alto, LF (full screen)} X {DispY, DispM}. * These are selected by, respectively, * (DisplayConfig[14:15] = {0, 1}) X (DisplayConfig[0] = {0, 1}) NoInitRams: T_ LCY[DisplayConfig, DisplayConfig, 1], Branch[.+3, R odd]; * Initialize vertical waveforms for an Alto monitor: VBlankToVSyncCnt_ VBlankToVSyncCntAlto; VSyncToVBlankCnt_ VSyncToVBlankCntAlto, Branch[.+3]; * Initialize vertical waveforms for an LF monitor: VBlankToVSyncCnt_ VBlankToVSyncCntLF; VSyncToVBlankCnt_ VSyncToVBlankCntLF; * Now compute the IM address of the margin constants. BootState_ A0, TaskingOff, Call[AfterMarginConsts]; * MargConst[LMarg, CursorX] assembles IM word with the LMarg NLCB constant in the * left half and the CursorX NLCB constant in the right half. M[MargConst, Set[T1@, Or[ALMarg!, And[Sub[0, #1], NLCBDataMask!]]] Set[T2@, Or[CursorX!, And[Sub[0, #2], NLCBDataMask!]]] Data[(Byt0[RShift[T1@, 10]] Byt1[And[T1@, 377]] Byt2[RShift[T2@, 10]] Byt3[And[T2@, 377]])]]; DispTable[4]; * Indexed by DisplayConfig[14:15],,DisplayConfig[0] MargConst[Dec[0,0,8,4], Dec[0,3,7,3]]; * Alto monitor, DispY board MargConst[Dec[0,0,2,2], Dec[0,3,4,2]]; * Alto monitor, DispM board * These constants are for a picture that fills the LF monitor. MargConst[Dec[0,3,4,7], Dec[0,6,3,6]]; * LF monitor, full width, DispY board MargConst[Dec[0,2,8,6], Dec[0,6,0,6]]; * LF monitor, full width, DispM board * T = table index, Link = table base. Read the table entry from IM. * Miscellaneous other initialization is interspersed with this. AfterMarginConsts: TTemp0_ T OR (Link), Call[LinkGetsTTemp0]; TVCWShadowReg_ A0, TIOA[TStatics], ReadIM[0]; T_ Link, Call[LinkGetsTTemp0]; T_ LCY[T, T, 10], ReadIM[1]; T_ T XOR (Link); LMargConst_ T, Call[LinkGetsTTemp0]; TerminalLo_ A0, ReadIM[2]; T_ Link, Call[LinkGetsTTemp0]; T_ LCY[T, T, 10], ReadIM[3]; CursorXConst_ T XOR (Link); TerminalHi_ T_ A0, TaskingOn, Call[OutputGetsT]; * Statics_ 0 to enable wakeups BootTimer_ T_ A0, Block, Branch[THTNewField]; *----------------------------------------------------------- LinkGetsTTemp0: *----------------------------------------------------------- Subroutine; Link_ TTemp0, Return; TopLevel; *----------------------------------------------------------- * Start of new field *----------------------------------------------------------- KnowRBase[THTRegion]; THTNewField: TFieldAreaReg_ VBlankToVSyncCnt; TIOA[TNLCB], Call[SendMaxMarg]; *Initialize cursor to zero T_ CursorLo, Call[OutputGetsT]; T_ CursorHi, Call[OutputGetsT]; TVCWShadowReg_ T_ (TVCWShadowReg) OR (VBlank); * VSync=0, VBlank=1 T_ BootTimer, Output_ T; * T for boot check below %*----------------------------------------------------------- Booting obeys the following protocol: 1. Ignore button pushes that are very short (less than 8 ms). 2. Wait 1.5 seconds after a single push to see if the the user is performing a multiple-push boot. 3. If the button is down more than 2.5 seconds, ignore the button push. The baseboard also sees the boot button, and will boot the machine itself if a multiple-push boot occurs. The ReadTerminal subroutine, called once every scan line (38 us), watches the boot button, and reports results in these registers: TerminalHi < 0 iff boot button is presently down; BootTimer = duration of most recent button push (units of 38 us); 0 => no boot has occurred; 177777B => push was > 2.5 seconds long. Note: ReadTerminal filters out too-short pushes (< 8 ms). Note: BootTimer is valid only if the boot button is now up (TerminalHi >= 0). This logic runs once per field (60 times/second) and decides what to do. The BootState register reflects the current state, as follows: BootState=0 if nothing is happening; otherwise: BootState[0:14] = number of fields remaining until action should be taken (if boot button stays up); BootState[15] = action: 1 = boot, 0 = ignore. %*----------------------------------------------------------- BootCheck: * T=BootTimer; TerminalHi[0]=1 if boot button now down PD_ T, TerminalHi, Branch[EndBootCheck, R<0]; PD_ BootState, Branch[NewBoot, ALU#0]; * Boot button is now up and has not been down recently. BootState_ (BootState)-(2C), Branch[.+2, ALU#0]; BootState_ A0, Branch[EndBootCheck]; * Nothing is happening * Timing button-up interval. Timer expired? Branch[.+2, ALU<0]; Branch[EndBootCheck]; * Timer has expired. Reset state and take appropriate action. BootState, Branch[Boot, R odd]; BootState_ A0, Branch[EndBootCheck]; * Boot button is now up but has been down recently. Start button-up timer, * and set action = ignore if multiple pushes are in progress or the last * push was too long. NewBoot: BootState_ MinimumWait, Branch[.+3, ALU#0]; * Branch if multiple push PD_ (BootTimer)+1; * Action_ boot iff BootTimer # -1 BootState_ (BootState)+1, XorSavedCarry; BootTimer_ A0, Branch[EndBootCheck]; *----------------------------------------------------------- * Vertical retrace activity *----------------------------------------------------------- KnowRBase[THTRegion]; EndBootCheck: T_ DCSB.cursorXCoord; TTemp0_ (Fetch_ T)+1; * Fetch cursorX; TTemp0_ cursorY TTemp1_ MD, T_ APointer, Call[OutputGetsT]; * reset NLCB[ReaderPtr] to zero !! VBToVSLoop: T_ BPointer; Output_ T, Call[ReadTerminal]; * kills wakeup T_ CursorXConst, Block, Branch[VBToVSLoop, ALU#0]; * Tests TFieldAreaReg TFieldAreaReg_ VSyncToVSyncCnt; * Complement field type and set VSync=1 (VBlank=1 already) TVCWShadowReg_ (TVCWShadowReg) XOR (Or[VSync!, 1]C); T_ T-(TTemp1); * Add cursor position to base constant TTemp0_ (Fetch_ TTemp0)+1, Output_ T; * Send CursorX to NLCB; fetch cursorY T_ (TVCWShadowReg) AND (1C); * isolate fieldtype TCursorYReg_ T-MD; * calculate CursorY count/index Fetch_ TTemp0; * Fetch vertical field interrupt mask RBase_ RBase[WP]; WP_ (WP) OR MD, Reschedule; RBase_ RBase[THTRegion]; VSToVSLoop: Output_ TVCWShadowReg; * Also kills wakeup TSLCReg_ T-T-1, Call[ReadTerminal]; T_ (TVCWShadowReg) AND (1C), * isolate FieldType Block, Branch[VSToVSLoop, ALU#0]; * Tests TFieldAreaReg TFieldAreaReg_ T+(VSyncToVBlankCnt); * add FieldType into VBlank count TVCWShadowReg_ (TVCWShadowReg) AND (Not[VSync!]C); * VSync_ 0 VSToVBLoop: Output_ TVCWShadowReg; Call[ReadTerminal]; Block, Branch[VSToVBLoop, ALU#0]; * Tests TFieldAreaReg T_ TVCWShadowReg_ (TVCWShadowReg) AND (Not[Or[VSync!, VBlank!]]C), Call[OutputGetsT]; * VSync_ 0, VBlank_ 0 T_ (TReg400C) OR (LowByte[VisibleLineCnt]); TFieldAreaReg_ T, RBase_ RBase[TWTRegion]; TReaderPtrReg_ A0; * init to 0, needed every new field *----------------------------------------------------------- * Visible portion of field. *----------------------------------------------------------- KnowRBase[TWTRegion]; T_ DCSB.width; T_ (Fetch_ T)+1; * Fetch width TNWrdsMinus1_ MD, T_ (Fetch_ T)+1; * Fetch height TSLCReg_ MD, T_ (Fetch_ T)+1; * Fetch bitMap TNextAddrLo_ MD, Fetch_ T, T_ DCSB.flags; TNextAddrHi_ MD, Fetch_ T; * Fetch flags TScratch_ MD; * Send scan control word to NLCB T_ Or[FullRes!, Size1!, Polarity!]C, TScratch, Branch[.+2, R<0]; * Branch if background=black T_ T AND (Not[Polarity!]C); T_ T OR (AScan), Call[OutputGetsT]; * Send left margin control to NLCB T_ LMargConst, Call[OutputGetsT]; * Send width to NLCB T_ LSH[TNWrdsMinus1, 4]; * Convert words to pixels T_ T+(Sub[WidthOffset!, 1]C); * WidthOffset is positive const T_ T XOR (AWidthDataMask), * Negate and insert register select Call[OutputGetsT]; * Adjust width and height counts -- both want to be (remaining count)-1 TNWrdsMinus1_ (TNWrdsMinus1)-1; TSLCReg_ T_ (TSLCReg)-1, RBase_ RBase[THTRegion]; * Compute initial bit map offset based on even vs odd field. PD_ T, TVCWShadowReg, RBase_ RBase[TWTRegion], Branch[.+2, R odd]; T_ A0, * Even field DblBranch[NoScanLine, DoScanLine, ALU<0]; T_ (TNWrdsMinus1)+1, * Odd field DblBranch[NoScanLine, DoScanLine, ALU<0]; *----------------------------------------------------------- * THT per-scan-line activity *----------------------------------------------------------- KnowRBase[TWTRegion]; * T = 2*NWrds (except on first scan line). * Advance address to next scan line for this field. DoScanLine: TNextAddrLo_ (TNextAddrLo)+T; TNextAddrHi_ A_ TNextAddrHi, XorSavedCarry, Call[JamPtrAndNextCnt]; * Enable TWT to run!! TIOA[AHTFlag]; T_ A0, Output_ TSetNextWCB, Branch[DoCursor]; * Here if off the bottom of the bit map -- do not start up TWT. NoScanLine: Call[SendMaxMarg]; * returns T=0 * Cursor processing. T must have zero on entry here. DoCursor: T_ T+1, RBase_ RBase[THTRegion]; T_ TCursorYReg_ (TCursorYReg)+T+1, TIOA[TNLCB], Branch[NoCursor, R<0]; PD_ T-(22C); * T is >= 2 T_ (TCursorBaseMinus2)+T, Branch[BeyondCursor, ALU>=0]; Fetch_ T; * T was in [2..21], now points to data TTemp0_ MD, Branch[SendCursor]; NoCursor: T_ CursorLo, Branch[CheckSLC]; BeyondCursor: TCursorYReg_ 100000C; * maximum negative value TTemp0_ A0; * Cursor data _ 0 SendCursor: T_ (TTemp0) AND (377C); * Have cursor data in TTemp0 now T_ T OR (CursorLo), Call[OutputGetsT]; T_ RSH[TTemp0, 10]; T_ T OR (CursorHi); CheckSLC: Output_ T, Call[ReadTerminal]; * Returns with ALU = TFieldAreaReg * Notice: Code MUST have done at least one Output to NLCB before reaching here RBase_ RBase[TWTRegion], Branch[EndOfField, ALU=0]; TSLCReg_ (TSLCReg)-1; T_ ((TNWrdsMinus1)+1) LSH 1, Block, DblBranch[NoScanLine, DoScanLine, ALU<0]; EndOfField: RBase_ RBase[THTRegion], Block, Branch[THTNewField]; % Note: when the bit map is exhausted, TSLCReg will continue to decrement on every line and the Branch to NoScanLine will be taken. On subsequent scan lines only cursor processing will be performed, until FieldArea is exhausted. Note: TSLCReg maintains the remaining number of scan lines minus one. % *----------------------------------------------------------- SendMaxMarg: * Send maximum margins to NLCB -- for blank scan lines. * Entry: TIOA[NLCB] or TIOA[TNLCB] * Exit: T = 0 *----------------------------------------------------------- Subroutine; DontKnowRBase; T_ AMaxMarg; Output_ T; T_ BMaxMarg; * Does nothing if talking to DispM T_ A0, Output_ T, Return; *----------------------------------------------------------- JamPtrAndNextCnt: * First, calculates the number of munches to send to the hardware, * loading ANextCount. The hardware reader pointer is updated and sent * to the hardware. Finally, the shadow reader pointer is updated for the * next entry into this subroutine. Runs on behalf of AChannelRegion, * BChannelRegion, or TChannelRegion, depending on RBase. * Sets AReaderPtrReg, ANextCount. Destroys T. * Invariant for entry: AReaderPtrReg contains munch pointer into the fifo * for the Next scan line, and nothing else!! *----------------------------------------------------------- Subroutine; KnowRBase[AChannelRegion]; * or BChannelRegion, or TChannelRegion * first calculate next munch count for transfer. Calculation is * NEW Word Pointer - 1 + NWRDS. * Result in [8..11] is number of munches to transfer -1. T_ (ANextAddrLo) AND (17C); * get NEW word pointer ANextCount_ T+(ANWrdsMinus1); * set by DCB processing * merge WORD offset into fifo pointer and send to hardware T_ (AReaderPtrReg)+T; * word offset into RP T_ T OR (APointer); T_ T OR (AChannelReg); * Select channel for NLCB command T_ ANextCount, Output_ T; * send reader pointer * now calculate update for reader pointer register (shadows hardware RP) AReaderPtrReg_ (AReaderPtrReg)+T, Carry20; AReaderPtrReg_ (AReaderPtrReg) AND (360C), Return; *----------------------------------------------------------- JLoadMiniMixer: * Loads the mininmixer with the identity for Alto emulation. * This is done ONCE in the beginning of the world. Users must be friendly enough * to reload MiniMixer as they exit from user code to Alto emulation code. * Entry: RBase[THTRegion]. * Exit: TIOA[MiniMixer] * Clobbers T, TTemp0, TTemp1 *----------------------------------------------------------- Subroutine; KnowRBase[THTRegion]; * MiniMixer inputs are: * 0 Cursor * 1 AItem.0 * 2 AItem.1 * 3 AItem.2 OR Polarity * 4 AItem.3 * 5 BItem.0 * 6 AOn * 7 BOn * Desired output function is: * IF ((Cursor OR AItem.0) XOR Polarity) = 1 THEN black ELSE white * where black = 0, white = 17B. * Note that all AItem.i are zero when AOn = 0 or i >= item size. * Therefore, for Alto emulation we don't need to look at AOn at all; and * AItem.2 OR Polarity = Polarity. TTemp1_ A0, TIOA[MiniMixer]; * address in 0..7, start with 0 JamMMLoop: T_ (TTemp1) LSH 1; TTemp0_ (TTemp1) OR T; * TTemp0.0_ AItem.0 OR Cursor T_ LSH[TTemp1, 3]; * T.0_ Polarity PD_ (TTemp0) XOR T; T_ TTemp1, Branch[.+2, ALU<0]; * Branch if black T_ T OR (17C); * White = 17B TTemp1_ (TTemp1)+(400C); Output_ T, Branch[JamMMLoop, ALU#0]; Return; TopLevel; *----------------------------------------------------------- SetDisplayFieldRate: * Emulator operation to adjust the vertical field rate, by changing VBlankToVSyncCnt. * Enter: Stack = new VBlankToVSyncCnt value * Exit: Stack = old value; exits by IFUJump[0] *----------------------------------------------------------- TopLevel; Set[XTask, IP[EMU]]; T_ Stack, RBase_ RBase[VBlankToVSyncCnt]; VBlankToVSyncCnt_ T, Q_ VBlankToVSyncCnt; Stack_ Q, IFUJump[0];