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