{File name: LispDisplay.mc
Last Edit: 30-Jul-83 16:44:26
Fixed bank 0 core smash bug Purcell 30-Jul-83 16:44:20
Modified for variable display rate by don 1-Jul-83 14:54:22
Description: Dandelion Display Microcode: "Do not attempt to adjust your set. We control the vertical, we control the horizontal."
Author: R. Garner (based on previous version by R. Crane, Pitts Jarvis)
Created: April 15 1980,
Edited: R. Garner, May 19, 1980 6:20 PM}
{Edited: Purcell, July 15, 1982 8:57 PM}{adapted for current use}
{NOTE: There is a good chance this code can be written without register "Line"!!
The number of refreshes per scan line required is
(128 Refreshes/2 mSec)(28.8 uSec/Line) = 1.8 Refreshes/Line
This code assumes a total of 897 (381’x) Lines per Frame:
26Top Border
808Field
28Bottom border
4Blanking
27Vert Sync (14 even, 13 odd)
4End Vert Sync
}
SetTask[1];
StartAddress[DisplayStart];
{——————————————————————————————————————————————————————
Initilization
——————————————————————————————————————————————————————}
{Note: u7FF, u1FFF, and u3FFF should be initialized by the Emulator task}
DisplayStart:dX ← LShift1 0FF, SE←1, CANCELBR[$, 0F],c1;
dX ← LShift1 dX, SE←1,c2;
r9u3FF ← dX,c3;
dX ← 14’d,c1;
uBBTime ← dX,c2;{initialize for standard display rate}
c3;
rhdY ← IOPageHigh, Refresh,c1;
dX ← IOPage,c2;
dX ← dX LRot8,c3;
dX ← dX or DCBLoc,c1;
uDCBLoc ← dX,c2;
dX ← dX + cursorMapLoc,c3;
uInitCurMapAddr ← dX,c1;
Line ← PicLenShifted,c2;
dX ← Line LRot8,c3;
uInitCurBufAddr ← dX, Refresh,c1;
Line ← LRot1 Line,c2;
Line ← LRot1 Line, rhLine ← 0,c3;
rBuInitPicLength ← Line {328’x}, ClrDPRq,c1;
Noop,c2;
uClockBits ← 0, GOTO[EndVSync],c3;
{——————————————————————————————————————————————————————
End Vertical Sync
——————————————————————————————————————————————————————}
{1st line of "End Vertical Sync" (4 clicks): Init cursor copy loop count; fetch & load border register; fetch wakeup mask.}
EndVSync:dX ← 7, L5 ← L5.evs2, Refresh,c1, at[VSyncTime,10,HibernateRet];
uCopyCount ← dX,c2;
dY ← uDCBLoc,c3;
MAR ← [rhdY, dY + cursorYLoc],c1;
DCtl ← EndVSync, CANCELBR[$, 0],c2;
dX ← MD{cursorY},c3;
MAR ← [rhdY, dY + WakeupLoc],c1;
uDoCursor ← dX, CANCELBR[$, 0],c2;
dX ← MD{WakeupMask}, CALL[Sleep],c3;
{2nd line of "End Vertical Sync" (4 clicks): Fetch exit length value and invert and blank bits. If the display is turned off, the microcode executes as usual except there is border pattern everywhere instead of picture. Format of control bits:
0: 1=> off,
4: 1=> Invert,
5: 1=> disconnect (presently ignored),
6-15: scan line wake count}
MAR ← [rhdY, dY + BorderLoc], L5 ← L5.tb1,c1, at[L5.evs2,10,SleepRet];
uDWakeup ← dX, CANCELBR[$, 0],c2;
dX ← MD{BorderPattern},c3;
MAR ← [rhdY, dY + DBitsLoc],c1;
DBorder ← dX, dY ← ~1, CANCELBR[$, 0],c2;
dX ← MD and dY{ScanLineWakeup and ~1}, XHDisp,c3;
uExitLength ← dX, Refresh, DISP2[NormOn],c1;
NormOn:dX ← Pic, GOTO[DisplayNorm],c2, at[0,4,NormOn];
NormOff:dX ← NoPic, GOTO[DisplayNorm],c2, at[1,4,NormOn];
InvOn:dX ← InvPic, GOTO[DisplayInv],c2, at[2,4,NormOn];
InvOff:dX ← NoInvPic, GOTO[DisplayInv],c2, at[3,4,NormOn];
DisplayNorm:dY ← NoPic, CALL[Sleep],c3;
DisplayInv:dY ← NoInvPic, CALL[Sleep],c3;
{——————————————————————————————————————————————————————
Top Border
——————————————————————————————————————————————————————}
{Border work involves initializing cursor related registers and copying the lines of the visible area within which the cursor appears into the Cursor Buffer. The following compuations are made:
The Line number on which the visible region stops:
uPicLength ← rBuInitPicLength or oddField
The Line number on which a wakeup is caused.:
uExitLength ← MIN[(ScanLineWakeup AND ~1) + 2 OR oddField, uPicLength]
The Line number on which the cursor begins (If CursorY is greater than uPicLength, then no cursor will be displayed):
uDoCursor ← CursorY + ((CursorY xor oddField) and 1)
The Line number on which the cursor stops:
uCursorStop ← uDoCursor + 14’d
The word address of the first word of the cursor bit map in the DCB:
uCurMapAddr ← uInitCurMapAddr + ((CursorY xor oddField) and 1)
The word address of the first location of cursor in the Visible Region:
uCurVisAddr ← uDoCursor*wordsPerLine + (CursorX RShift4)
The word address of the first location of the cursor in the Cursor Buffer:
uCurBufAddr ← rBuInitPicLength*wordsPerLine + (CursorX RShift4)
The Fifo entries for a cursor line where the cursor is in the middle:
uCursorFifo0 ← [(CursorX RShift4) - 1,,0]
uCursorFifo1 ← [(CursorX RShift4) + 1,,(rBuInitPicLength-uDoCursor)]
uCursorFifo2 ← [63,,0]
The Fifo entries for a cursor line where the cursor is at an edge:
LeftEdge = (CursorX RShift4)=0,
RightEdge = (CursorX RShift4)=62 or 63
uCursorFifo0’ ← IF LeftEdge THEN uCursorFifo1 ELSE [1,,0]
uCursorFifo1’ ← IF RightEdge THEN uCursorFifo0 ELSE [62,,0]
uCursorFifo2’ ← IF RightEdge THEN [63,,(rBuInitPicLength-uDoCursor)] ELSE uCursorFifo2
The Fifo entry for a line without a cursor is [63,,0].
The mask & shift required to OR the cursor into the visible region are also computed.}
{1st line of "Top Border" (6 clicks): Set uExitLength to min of ScanLineWakeup & InitPicLength.}
TopBorder:uStartPic ← dX, Refresh, L5 ← L5.tb2,c1, at[L5.tb1,10,SleepRet];
uNoPic ← dY,c2;
dY ← Line and 1,c3;
dX ← rBuInitPicLength or dY{oddField},c1;
dY ← uExitLength or dY{oddField},c2;
dY ← dY + 2,c3;
dY ← dY and r9u3FF,c1;
[] ← dX - dY, CarryBr,c2;
uPicLength ← dX, BRANCH[BadExitLen, ExitLenOK],c3;
BadExitLen:uExitLength ← dX, GOTO[PicLen] ,c1;
ExitLenOK:uExitLength ← dY ,c1;
PicLen:dY ← uDoCursor {cursorY},c2;
dX ← dY xor uClockBits {cursorY xor oddField},c3;
dX ← dX and 1 {(cursorY xor oddField) and 1},c1;
dY ← dX + dY {((cursorY xor oddField) and 1)+cursorY}, DCtl ← uNoPic,c2;
uDoCursor ← dY, CALL[Sleep],c3;
{2nd line of "Top Border" (7 clicks): Compute uDoCursor, uCurMapAddr, init uCurVisAddr, uCursorStop, rAuCursorMask, and the cursorShift in rhdX.}
TopB2:dX ← uInitCurMapAddr or dX, Refresh, L5 ← L5.DCycle6,c1, at[L5.tb2,10,SleepRet];
uCurMapAddr ← dX,c2;
dX ← rBuInitPicLength,c3;
dX ← dX - dY {rBuInitPicLength - uDoCursor},c1;
uCursorDiff ← dX,c2;
dX ← dY + 0E {uDoCursor +14’d},c3;
uCursorStop ← dX, CALL[DCycle6],c1;
{1 click of dX←dY LRot1; dX←dX LRot1; dX←dX LRot4}
uCurVisAddr ← dX {uDoCursor,,000000},c2, at[L5.DCycle6,10,DCycleRet];
dY ← uDCBLoc,c3;
MAR ← [rhdY, dY + cursorXLoc],c1;
dY ← ~0F, CANCELBR[$, 0],c2;
rhdX ← dX ← MD{cursorX} and dY, XDisp,c3;
dY ← dX LRot12 {cursorX RShift4}, DISP4[DMask],c1;
{1 cycle of "dX←mask",c2;}
DMaskRet:rAuCursorMask ← dX, CALL[Sleep],c3;
{3rd line of "Top Border" (6 clicks): Compute uCurVisAddr, uCurBufAddr, and preliminary uCursorFifo2. Set uDoCursor to off screen if CursorX is off screen.}
TopB3:dX ← ~3F, L5 ← L5.DCycleA,c1, at[L5.DCycle6,10,SleepRet];
[] ← dY and dX {cursorX xor ~3F}, ZeroBr,c2;
dX ← uCurVisAddr {uDoCursor,,000000}, BRANCH[$, CursorOn],c3;
uDoCursor ← ~dY xor dY, GOTO[CurVisAddr],c1;
CursorOn:Noop,c1;
CurVisAddr:uCurVisAddr ← dX or dY {uDoCursor,,cursorX},c2;
dX ← uInitCurBufAddr,c3;
CurBufAddr:
{30-Jul-83 16:43:25 BUG FIX: neg cursorX caused out of range store}
dY ← dY and 03F,c1;
Noop,c2;
Noop,c3;
uCurBufAddr ← dX or dY {rBuInitPicLength,,cursorX}, Refresh, CALL[DCycleA],c1;
{1 click of dX←LRot1 dY; dX←LRot1 dX; dX←dX LRot8}
{dX = cursorX,,0000000000}
dY ← r9u3FF,c2, at[L5.DCycleA,10,DCycleRet];
uCursorFifo2 ← ~dY {111111,,0000000000}, CALL[Sleep],c3;
{4th line of "Top Border" (3 clicks cursor in middle, 4 clicks cursor on edge): Compute uCursorFifo0, uCursorFifo1, and uCursorFifo2.}
dX ← dX - dY - 1, Xbus ← uStartPic, XDisp, L5 ← L5.tb5,c1, at[L5.DCycleA,10,SleepRet];
uCursorFifo0 ← dX {dX=cursorX-1,,0000000000}, BRANCH[SkipCopy, $, 0B],c2;
dX ← dX+dY+1 {dX=cursorX,,0000000000}, ZeroBr,c3;
dX ← dX+dY+1 {dX=cursorX+1,,0000000000}, ZeroBr, Refresh, BRANCH[$, CurAtLeft],c1;
[] ← dX+dY+1 {dY=cursorX+2,,0000000000}, NZeroBr, BRANCH[$, CurAt3F0],c2;
dX ← dX or uCursorDiff {cursorX+1,,PicLength - uDoCursor}, BRANCH[CurAt3E0, Sleep],c3;
{Cursor is on left or right edge.}
CurAtLeft:dX ← dX or uCursorDiff {[1,PicLength - uDoCursor]}, CANCELBR[$],c2;
dY ← LShift1 ~dY {dX=[62,0]}, GOTO[CurEdgeFin],c3;
CurAt3F0:dX ← uCursorDiff {[0,PicLength - uDoCursor]}, CANCELBR[$],c3;
uCursorFifo2 ← ~dY xor dX {[63,PicLength - uDoCursor]}, GOTO[CurAtR],c1;
CurAt3E0:uCursorFifo2 ← dX {[63,PicLength - uDoCursor]},c1;
CurAtR:dX ← dY+1 {dX=[1,0]},c2;
dY ← uCursorFifo0 {cursorX-1,,0000000000},c3;
CurEdgeFin:uCursorFifo0 ← dX,c1;
dX ← dY,c2;
CALL[Sleep],c3;
{5th-12th lines of "Top Border" (9 clicks/line): Copy 8 lines from the visible region into the cursor buffer while ORing the cursor bit map into the lines.}
CopyInit:uCursorFifo1 ← dX, Refresh,c1, at[L5.tb5,10,SleepRet];
CopyCursor:dY ← uCurMapAddr,c2;
Line ← uCurVisAddr,c3;
MAR ← [rhdY, dY], dY ← dY + 2, L5 ← L5.DCycle,c1;
uCurMapAddr ← dY, [] ← rhdX, XDisp, CANCELBR[$, 2],c2;
dY ← MD{cursor word}, DISP4[DCycle],c3;
{1 click of dX←LRot1 dY; dX←LRot1 dX; dX←dX LRotn}
MAR ← [rhLine, Line+0],c1, at[L5.DCycle,10,DCycleRet];
dY ← dX and rAuCursorMask,c2;
dY ←MD or dY {visible or Left cursor},c3;
MAR ← [rhLine, Line + 1],c1;
dX ← dX and ~rAuCursorMask, CANCELBR[$, 2],c2;
dX ←MD or dX {visible or Right cursor},c3;
Line ← Line + 80,c1;
uCurVisAddr ← Line,c2;
Line ← uCurBufAddr,c3;
MAR ← [rhLine, Line+0],c1;
MDR ← dY, dY ← uClockBits,c2;
dY ←dY + 2,c3;
MAR ← [rhLine, Line + 1],c1;
MDR ← dX, WriteOK, CANCELBR[$, 2]c2;
Line ← Line + 80 ,c3;
uClockBits ← dY, Refresh,c1;
dX ← uCopyCount-1, NibCarryBr, SuppressTimingWarning,c2;
uCurBufAddr ← Line, ClrDPRq, BRANCH[CopyDone, $] ,c3;
uCopyCount ← dX, Refresh, GOTO[CopyCursor],c1;
{13th Line of "Top Border" (3+Field clicks): Update the clock, reset the line number, and preload the CtlFifo (It should be loaded 1 line in advance). The clock registers must be updated in the same click.}
CopyDone:Noop,c1;
CDone:Line ← RShift1 dY {uClockBits/2},c2;
dY ← dY and 1 {zero line count, preserve oddField},c3;
dX ← uClockLow,c1;
dX ← dX+Line {uClockLow+uClockBits/2}, CarryBr,c2;
Line ← uClockHigh, BRANCH[$, UpClkHigh],c3;
uClockLow ← dX, GOTO[CUC],c1;
UpClkHigh:uClockLow ← dX, Line ← Line + 1,c1;
CUC:uClockHigh ← Line, L5 ← L5.FieldStart,c2;
uClockBits ← dY, CALL[Field],c3;
{If the display is off, do not write into low bank.}
SkipCopy:rhdX ← dX ← OffTime, CALL[Snore],c3;
CopySkipped:dY ← uClockBits, GOTO[CDone],c1, at[OffTime,10,HibernateRet];
{——————————————————————————————————————————————————————
Visible Field
——————————————————————————————————————————————————————}
{Line without cursor is 2 clicks, cursor line is 4 clicks, interrupt line is 2 additional clicks.}
FieldStart:DCtl ← uStartPic, L5 ← L5.Field, BRANCH[FieldC, FieldExit],c1;
Field:dX ← ~r9u3FF{normal fifo entry}, Refresh, BRANCH[FieldC, FieldExit],c1;
FieldC:[] ← dY xor uDoCursor, ZeroBr,c2;
CursorDone:uClockBits ← dY, BRANCH[$, Cursor],c3;
DCtlFifo ← dX + dY, Refresh,c1;
dY ← dY + 2, ClrDPRq, pRet5,c2;
[] ← dY xor uExitLength, ZeroBr, BRANCH[Field, FieldStart],c3;
Cursor:[] ← dY xor uCursorStop, ZeroBr,c1;
dX ← dY + 2, BRANCH[$, CursorLast],c2;
uDoCursor ← dX, GOTO[Curse],c3;
CursorLast:uDoCursor ← ~dY xor dY,c3;
Curse:dX ← uCursorFifo0,c1;
DCtlFifo ← dX + dY,c2;
dX ← uCursorFifo1,c3;
DCtlFifo ← dX + dY,c1;
dX ← uCursorFifo2, GOTO[CursorDone],c2;
FieldExit:dX ← uPicLength, CANCELBR[$],c2;
[] ← dY xor dX, ZeroBr,c3;
uExitLength ← dX, BRANCH[$, FieldDone],c1;
Noop,c2;
Noop,c3;
dX ← uWP,c1;
dX ← dX or uDWakeup, MesaIntRq,c2;
uWP ← dX, GOTO[Field],c3;
FieldDone:dY ← dY + 2,c2;
uClockBits ← dY, GOTO[BotBorder],c3;
{——————————————————————————————————————————————————————
Bottom border
——————————————————————————————————————————————————————}
BotBorder:dY ← uNoPic, Refresh,c1;{alu ops in c1 and c2 swapped}
dX ← uBBTime, ClrDPRq,c2;
rhdX ← BotBordTime,c3;
c1;
CALL[Hibernate],c2;
{——————————————————————————————————————————————————————
Start Vertical Retrace
——————————————————————————————————————————————————————}
StartBlk:dX ← rhdX ← BlankTime, Refresh,c1, at[BotBordTime,10,HibernateRet];
dY ← Blank, CALL[Hibernate],c2;
{——————————————————————————————————————————————————————
Vertical Sync
——————————————————————————————————————————————————————}
StartVSync:uClockBits ← Line ← Line xor dY{1}, Refresh,,c1, at[BlankTime,10,HibernateRet];
Line ← Line and 1,c2;
dX ← Line LRot4,c3;
dY ← dX or StartVSync,c1;
dX ← VSyncTime + Line, rhdX ← VSyncTime, CALL[Hibernate],c2;
{——————————————————————————————————————————————————————
Hibernate, Sleep
——————————————————————————————————————————————————————}
Hibernate:DCtl ← dY LRot0,c3;
Snore:dX ← dX - 1, NegBr, Refresh, L5 ← L5.Hibernate,c1, at[L5.Hibernate,10,SleepRet];
Xbus ← rhdX, XDisp, BRANCH[Winter, Spring],c2;
Winter:CANCELBR[Sleep, 0F],c3;
Spring:dY ← 1, RET[HibernateRet],c3;
Sleep:Line ← uClockBits, Refresh,c1;
Line ← Line + 2, pRet5,c2;
uClockBits ← Line, ClrDPRq, RET[SleepRet],c3;
{——————————————————————————————————————————————————————
DCycle
——————————————————————————————————————————————————————}
DCycle:dX ← dY, Xbus ← 0, XDisp, GOTO[NoRot],c*, at[0,10,DCycle];
dX ← LRot1 dY, Xbus ← 0, XDisp, GOTO[NoRot],c*, at[0F,10,DCycle];
dX ← LRot1 dY, Xbus ← 0, XDisp, GOTO[DRot1],c*, at[0E,10,DCycle];
dX ← RRot1 dY, Xbus ← 1, XDisp, GOTO[NoRot],c*, at[0D,10,DCycle];
dX ← dY, Xbus ← 1, XDisp, GOTO[NoRot],c*, at[0C,10,DCycle];
dX ← LRot1 dY, Xbus ← 1, XDisp, GOTO[NoRot],c*, at[0B,10,DCycle];
DCycle6:dX ← LRot1 dY, Xbus ← 1, XDisp, GOTO[DRot1],c*, at[0A,10,DCycle];
dX ← RRot1 dY, Xbus ← 2, XDisp, GOTO[NoRot],c*, at[9,10,DCycle];
dX ← dY, Xbus ← 2, XDisp, GOTO[NoRot],c*, at[8,10,DCycle];
dX ← LRot1 dY, Xbus ← 2, XDisp, GOTO[NoRot],c*, at[7,10,DCycle];
DCycleA:dX ← LRot1 dY, Xbus ← 2, XDisp, GOTO[DRot1],c*, at[6,10,DCycle];
dX ← RRot1 dY, Xbus ← 3, XDisp, GOTO[NoRot],c*, at[5,10,DCycle];
dX ← dY, Xbus ← 3, XDisp, GOTO[NoRot],c*, at[4,10,DCycle];
dX ← LRot1 dY, Xbus ← 3, XDisp, GOTO[NoRot],c*, at[3,10,DCycle];
dX ← LRot1 dY, Xbus ← 3, XDisp, GOTO[DRot1],c*, at[2,10,DCycle];
dX ← RRot1 dY, Xbus ← 0, XDisp, GOTO[NoRot],c*, at[1,10,DCycle];
NoRot:pRet5, DISP2[DLRot],c*;
DRot1:dX ← LRot1 dX, pRet5, DISP2[DLRot],c*;
DLRot:RET[DCycleRet],c*, at[0,4,DLRot];
dX ← dX LRot4, RET[DCycleRet],c*, at[1,4,DLRot];
dX ← dX LRot8, RET[DCycleRet],c*, at[2,4,DLRot];
dX ← dX LRot12, RET[DCycleRet],c*, at[3,4,DLRot];
{——————————————————————————————————————————————————————
DMask
——————————————————————————————————————————————————————}
DMask: dX ← 1, GOTO[DMaskRet], c2, at[0F,10,DMask];
dX ← 3, GOTO[DMaskRet], c2, at[0E,10,DMask];
dX ← 7, GOTO[DMaskRet], c2, at[0D,10,DMask];
dX ← 0F, GOTO[DMaskRet], c2, at[0C,10,DMask];
dX ← 1F, GOTO[DMaskRet], c2, at[0B,10,DMask];
dX ← 3F, GOTO[DMaskRet], c2, at[0A,10,DMask];
dX ← 7F, GOTO[DMaskRet], c2, at[9,10,DMask];
dX ← 0FF, GOTO[DMaskRet], c2, at[8,10,DMask];
dX ← LShift1 0FF, SE←1, GOTO[DMaskRet] {dX ← 1FF}, c2, at[7,10,DMask];
dX ← RShift1 u7FF, GOTO[DMaskRet] {dX ← 3FF}, c2, at[6,10,DMask];
dX ← u7FF, GOTO[DMaskRet] {dX ← 7FF}, c2, at[5,10,DMask];
dX ← RShift1 u1FFF, GOTO[DMaskRet] {dX ← FFF}, c2, at[4,10,DMask];
dX ← u1FFF, GOTO[DMaskRet] {dX ← 1FFF}, c2, at[3,10,DMask];
dX ← u3FFF, GOTO[DMaskRet] {dX ← 3FFF}, c2, at[2,10,DMask];
dX ← RShift1 (~dX xor dX), GOTO[DMaskRet] {dX ← 7FFF}, c2, at[1,10,DMask];
dX ← ~dX xor dX, GOTO[DMaskRet] {dX ← FFFF}, c2, at[0,10,DMask];