*----------------------------------------------------------- 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];