; MaxcMemory.mu -- Microcode to support the Maxc2 memory interface

;	Last modified April 12, 1978  7:22 PM

; New emulator instructions for Maxc memory references.

; All take the following arguments:
; ac0	pointer to 2-word Maxc address vector.  The top 16 bits
;	are in the first word and the bottom 4 bits left-justified
;	in the second word.
; ac1	pointer to data vector.  For single-word operations, this
;	is 3 words, with the top 32 bits in the first two words and
;	the bottom 8 bits (including the 4 tag bits) in the third.
;	For block operations, this is a vector of either 3-word
;	or 2-word blocks, depending on whether all 40 bits or only
;	the top 32 bits of each Maxc word are being transferred.
; ac3	(block operations only) Maxc word count.

; All return an error code in ac0 upon completion:
; 0	normal
; 1	parity error detected on data bus during read or RMW
;	(if RMW, the write was nevertheless completed)
; 2	timed out (>80 microseconds to complete operation)
; 4	interface busy when started (should never happen)
; 10	unimplemented operation
; It is conceivable that more than one of the above errors could
; occur at the same time.

; Single-word operations do not affect any ac except ac0.
; Block operations update both the address vector and ac1
; to contain the Maxc and Alto addresses of the first word not
; transferred (this is true whether or not the transfer completes
; successfully).  Normally, ac3 is set to zero, but if the transfer
; terminates abnormally, ac3 contains the number of words that
; remain to transfer.  Multi-word operations are interruptable;
; if interrupted, the updated state is stored in ac1, ac3, and
; the address vector as usual, but ac0 is not clobbered and the
; pc is backed up so the instruction will be started over.

; Single-word operations
;66000	MFETCH	fetches one word from Maxc and puts in data vector.
;66001	MSTORE	stores one word from data vector to Maxc.
;66002	MRMW	"or"s the data vector with the Maxc word and puts
;		the result both into the data vector and into Maxc.

; Block operations
;66003	MBLKS	repeatedly stores a single word from data vector
;		into the specified number of successive Maxc words.
;66004	MFBLK32	fetches a block of words from Maxc, putting only
;		the top 32 bits in the data vector.
;66005	MSBLK32	stores a block of words to Maxc, getting only the
;		top 32 bits from the data vector (bottom 8 get
;		garbage).
;66006	MFBLK40	fetches a block of words from Maxc, putting all
;		40 bits in the data vector.
;66007	MSBLK40	stores a block of words to Maxc, getting all 40
;		bits from the data vector.

;Memory Refresh Task,
;Mouse Handler,
;EIA Handler,
;Interval Timer,
;Calender Clock, and
;part of the cursor.
; **** Modified for Maxc2 support ****

!17,20,TX0,TX6,TX3,TX2,TX8,TX5,TX1,TX7,TX4,,,,,,,;
!1,2,DOTIMER,NOTIMER;
!1,2,NOTIMERINT,TIMERINT;
!1,2,DOCUR,NOCUR;
!1,2,SHOWC,WAITC;
!1,2,SPCHK,NOSPCHK;

!1,2,NOCLK,CLOCK;
!1,1,DTODD;
!1,1,MRTLAST;
!1,2,CNOTLAST,CLAST;

$CLOCKTEMP$R11;
$R37	$R37;
$CURX	$R20;
$CURDATA $R21;
$MTEMP	$R25;
$YPOS	$R27;

MRT:	SINK← MOUSE, BUS;	MOUSE DATA IS ANDED WITH 17B
MRTA:	L← T← -2, :TX0;		DISPATCH ON MOUSE CHANGE

;****	These next few lines are added Maxc support, from here....
$MCount	$R14;
!1,2,DecCnt,NoDecCnt;

TX0:	L← MCount-1, BUS=0;	Special timer for Maxc memory insts
	:DecCnt;		[DecCnt, NoDecCnt]
DecCnt:	MCount←L;
;****	...to here.

NoDecCnt: L← T← R37 AND NOT T;	CHECK FOR INTERVAL TIMER/EIA
	SH=0, L←T← 77+T+1;	
	:DOTIMER, R37← L, ALUCY;	
NOTIMER:L← CURX, :NOCLK;
NOCLK:	T← REFMSK, SH=0;
	MAR← R37 AND T, :DOCUR;
NOCUR:	CURDATA← L, TASK;
MRTLAST:CURDATA← L, :MRT;	END OF MAIN LOOP


DOTIMER:MAR←EIALOC;		INTERVAL TIMER/EIA INTERFACE
DTODD:	L←2 AND T;
	SH=0, L←T←BIAS.T;
	CURDATA←L, :SPCHK;	CURDATA←CURRENT TIME WITHOUT CONTROL BITS

SPCHK:	SINK←MD, BUS=0, TASK;	CHECK FOR EIA LINE SPACING
SPIA:	:NOTIMERINT, CLOCKTEMP←L;

NOSPCHK:L←MD;			CHECK FOR TIME=NOW
	MAR←TRAPDISP-1;		CONTAINS TIME AT WHICH INTERRUPT SHOULD HAPPEN
	MTEMP←L;		IF INTERRUPT IS CAUSED, LINE STATE WILL BE STORED
	L← MD-T;
	SH=0, TASK, L←MTEMP, :SPIA;

TIMERINT: MAR← ITQUAN;		STORE THE THING IN CLOCKTEMP AT ITQUAN
	L← CURDATA;
	R37← L;
	T←NWW;			AND CAUSE AN INTERRUPT ON THE CHANNELS 
	MD←CLOCKTEMP;		SPECIFIED BY ITQUAN+1
	L←MD OR T, TASK;
	NWW←L;

NOTIMERINT: SINK←CURDATA, BUS=0, :NOTIMER;

CLOCK:	MAR← CLOCKLOC;		R37 OVERFLOWED. UPDATE CLOCK
	NOP;
	L← MD+1;
	MAR← CLOCKLOC;
	MTEMP← L, TASK;
	MD← MTEMP, :NOTIMER;

DOCUR:	L← T← YPOS;		CHECK FOR VISIBLE CURSOR ON THIS SCAN
	SH < 0, L← 20-T-1;
	SH<0, L← 2+T, :SHOWC;

WAITC:	YPOS← L, L← 0, TASK, :MRTLAST;
SHOWC:	MAR← CLOCKLOC+T+1, :CNOTLAST;

CNOTLAST: T← CURX, :CURF;
CLAST:	T← 0;
CURF:	YPOS← L, L← T;
	CURX← L;
	L← MD, TASK;
	CURDATA← L, :MRT;


;AFTER THIS DISPATCH, T WILL CONTAIN XCHANGE, L WILL CONTAIN YCHANGE-1

TX1:	L← T← ONE +T, :M00;		Y=0, X=1
TX2:	L← T← ALLONES, :M00;		Y=0, X=-1
TX3:	L← T← 0, :M00;			Y=1, X= 0
TX4:	L← T← ONE AND T, :M00;		Y=1, X=1
TX5:	L← T← ALLONES XOR T, :M00;	Y=1, X=-1
TX6:	T← 0, :M00;			Y= -1, X=0
TX7:	T← ONE, :M00;			Y= -1, X=1
TX8:	T← ALLONES, :M00;		Y= -1, X= -1

M00:	MAR← MOUSELOC;			START THE FETCH OF THE COORDINATES
	MTEMP← L;			YCHANGE -1
	L← MD+ T;			X+ XCHANGE
	T← MD;				Y
	MAR← MOUSELOC;			NOW RESTORE THE UPDATED COORDINATES
	T← MTEMP+ T+1;			Y+ (YCHANGE-1) + 1
	MTEMP← L, L← T;
	MD← MTEMP;
	MAR← MOUSELOC+1;
	MTEMP← L, TASK;
	MD← MTEMP, :MRTA;

;** The following declarations are for Maxc memory support in the
;	Emulator task.
;** Originally written by Charles P. Thacker, late of CSL.
;	Later updated by Ed McCreight.  Now maintained by Ed Taft.

$MBUSY?	$L16015, 0, 0;	Emulator NDF1=16: branch if memory interface busy

; Registers used for communication between Emulator and Memory tasks:
$AD0	$R17;		Address bits 0-15
$AD1	$R5;		Address bits 16-19 (same as SAD)
$MD1	$R10;		Data bits 0-15 (same as XH)
$MD2	$R15;		Data bits 16-31
$MD3	$R16;		Data bits 32-35
$MErr	$R7;		Error code (same as XREG)

; Constants:
$MFC	$10000;		SIO bit to start fetch
$MSC	$40000;		SIO bit to start store

!1, 2, ~Busy, Busy;

; Main instruction dispatch.
; Note that the order is important:  various branches later in
; the code parallel this table or "know" that fetches are even
; and stores are odd.

!7, 10, FetchFromMaxc, StoreToMaxc, MaxcRMW, MaxcBlockStore,
	MaxcFetchBlock32, MaxcStoreBlock32, MaxcFetchBlock40,
	MaxcStoreBlock40;



; The Maxc Memory Operation instructon (6600x) dispatches here.
; After initial setup, it does a further dispatch on the low 3 bits of DISP.

MaxcMemOp:
	MAR← AC0;			Fetch high address bits
	MBUSY?;				Test for interface already busy
	L← MD, :~Busy;	[~Busy, Busy]
~Busy:	MAR← AC0+1;			Fetch low address bits
	AD0← L, L← 0;
	MErr← L;			Initialize error code to zero
	L← MD;
	SINK← DISP, SINK← M7, BUS, TASK; Dispatch on memory operation
	AD1← L, :FetchFromMaxc;

; Here if memory interface already busy.  Return error.
Busy:	T←4, :DoneT;


; All instructions return to Done when finished successfully,
; or to DoneT with an error code in T.
Done:	L← MErr, TASK, :Done1;		Instruction duplicated elsewhere
DoneT:	L← MErr OR T, TASK;
Done1:	AC0← L;

Done2:	SWMODE;
	:START;

!7, 10, RetF, RetS, RetRMW, RetBS, RetF32, RetS32, RetF40, RetS40;
!1, 2, NotF32, FinF32;
!1, 2, NotF40, FinF40;

; MFETCH, MFBLK32, MFBLK40 start here
FetchFromMaxc:
	SINK← MFC, STARTF, :WaitTilDone;
MaxcFetchBlock32:
	SINK← MFC, STARTF, :WaitTilDone;
MaxcFetchBlock40:
	SINK← MFC, STARTF, :WaitTilDone;

; WaitTilDone returns here during MFETCH, MRMW, MFBLK32, MFBLK40
RetF:	MAR← AC1, :RetF1;
RetRMW:	MAR← AC1, :RetF1;
RetF32:	MAR← AC1, :RetF1;
RetF40:	MAR← AC1, :RetF1;

RetF1:	TASK;
	MD← MD1;

	MAR← AC1+1;
	T← DISP, T← M7;
	L← 4 XOR T;			Test for opcode 4 = MFBLK32
	T← AC1+1, SH=0;
	MD← MD2, :NotF32;		[NotF32, FinF32]

NotF32:	MAR← 0+T+1;			Put third word of data in memory
	T← DISP, T← M7;
	L← 6 XOR T;			Test for opcode 6 = MFBLK40
	SH=0, TASK;
	MD← MD3, :NotF40;		[NotF40, FinF40]

NotF40:	L← MErr, TASK, :Done1;		MFETCH, MRMW cases end here

!1, 2, NotS32, DoS32;

; MRMW starts like a store and finishes like a fetch
MaxcRMW:
	MAR← AC1;
	T← MFC;				RMW started by F and S together
	L← MSC OR T, :Store2;


; MSTORE, MBLKS, MSBLK32, MSBLK40 start here
StoreToMaxc:
	MAR← AC1, :Store1;
MaxcBlockStore:
	MAR← AC1, :Store1;
MaxcStoreBlock32:
	MAR← AC1, :Store1;
MaxcStoreBlock40:
	MAR← AC1, :Store1;

Store1:	L← MSC;
Store2: MD3← L;				Save bits for starting reference
	L← MD, TASK;
	MD1← L;

	MAR← AC1+1;
	T← DISP, T← M7;
	L← 5 XOR T;			Test for opcode 5 = MSBLK32
	T← AC1+1, SH=0;
	L← MD, :NotS32;			[NotS32, DoS32]

NotS32:	MD2← L, MAR← 0+T+1;
	SINK← MD3, STARTF;		Awaken memory task
	L← MD, TASK;
	MD3← L, :WaitTilDone;

DoS32:	SINK← MD3, STARTF;		Awaken memory task
	MD2← L, :WaitTilDone;

; WaitTilDone returns here during MSTORE
RetS:	L← MErr, TASK, :Done1;

!1, 2, NoCarry, Carry;
!1, 2, NotDoneB, DoneB;
!1, 2, CarryND,  CarryD;
!1, 2, ErrB, NoErrB;
!1, 2, MayInt, NoInt;
!1, 2, DoInt, DisInt;
!1, 1, ContB;

; Block transfer operations come here after each word transferred.
; First update the Alto address (AC1) by the appropriate amount
FinF32:	T← 2, :FinBlock;
RetS32:	T← 2, :FinBlock;
FinF40:	T← 3, :FinBlock;
RetS40:	T← 3, :FinBlock;
FinBlock:
	L← AC1+T, TASK;
	AC1← L;

; Now update the Maxc address (AD0, AD1) by 1 word and decrement
; and test the word count (AC3).  These two operations are
; interleaved to save time at the expense of space and to let us
; slip a TASK in (no further opportunities for a long time).
RetBS:	T← 7777;			Low part of address is top 4 bits
	L← T← AD1+T+1;
	L← AC3-1, ALUCY;
	AC3← L, L← T, SH=0, TASK, :NoCarry; [NoCarry, Carry]
NoCarry: AD1← L, :NotDoneB;		[NotDoneB, DoneB]

Carry:	AD1← L, :CarryND;		[CarryND, CarryD]

CarryND: L← AD0+1, TASK;
	AD0← L, :NotDoneB;

CarryD:	L← AD0+1, TASK;
	AD0← L, :DoneB;

; Test the other two terminating conditions:  an error occurred
; or an interrupt is pending.
NotDoneB:
	SINK← MErr, BUS=0;
	L← NWW, BUS=0, :ErrB;		[ErrB, NoErrB]
NoErrB:	SH<0, :MayInt;			[MayInt, NoInt] NWW>0 is interrupt
MayInt:	L← PC-1, :DoInt;		[DoInt, DisInt]

; No reason to terminate.  Repeat the main dispatch to transfer
; the next word.
NoInt:	SINK← DISP, SINK← M7, BUS, TASK, :ContB; [ContB, ContB]
DisInt:	SINK← DISP, SINK← M7, BUS, TASK;
ContB:	:FetchFromMaxc;

!1, 1, DoneB1;

; Terminate transfer.
; If normal completion or error, store error code in AC0.
; If interrupt, back up PC and do not clobber AC0.
; In either case, store the updated address vector into memory.
ErrB:	MAR← T← AC0, :DoneB1;		[DoneB1, DoneB1]
DoneB:	MAR← T← AC0;
DoneB1:	L← MErr;
	AC0← L, L← T, :EndB;

DoInt:	MAR← T← AC0;
	PC← L, L← T;
EndB:	MD1← L, TASK;			Save former AC0 across TASK
	MD← AD0;

	MAR← MD1+1;
	TASK;
	MD← AD1, :Done2;


; Subroutine called by everyone after initiating a reference.
; It waits for the reference to complete.  If it times out,
; the interface is reset and a timeout error is indicated in
; MErr.  Control returns to one of the RetX tags depending
; on the DISP opcode.

!1, 2, MNotBusy, MBusy;
!1, 2, WaitLoop, TimedOut;
!1, 2, TimeF, TimeS;

WaitTilDone:
	L← 3, TASK;
	MCount← L;			Start timer which MRT counts down

WaitLoop:
	MBUSY?;
	:MNotBusy;			[MNotBusy, MBusy]

MBusy:	SINK← MCount, BUS=0, TASK;
	:WaitLoop;			[WaitLoop, TimedOut]

; If the operation times out, we must reset the interface.  If the
; operation was a read, the memory task is now stuck in the middle
; of the read code rather than in its reset state, so we must wake
; it up by initiating a fake reference.  (RMW counts as a fetch in
; this case, since we assume it's not possible to time out during
; the store part of an RMW).
TimedOut:
	SINK← DISP, BUSODD;
	T← 2, :TimeF;			[TimeF, TimeS]
TimeF:	SINK← MFC, STARTF;		Awaken task so it resets itself
TimeS:	SINK← 20000, STARTF;		Reset interface hardware
	L← MErr OR T;
	MErr← L;

; The TASK in the next instruction is crucial because the
; timing is such that MBUSY? may report "not busy" before the
; memory task has had a chance to run.
MNotBusy:
	SINK← DISP, SINK← M7, BUS, TASK;
	:RetF;
;	[RetF, RetS, RetRMW, RetBS, RetF32, RetS32, RetF40, RetS40]

;	The Maxc memory interface task

$MAR1	$L20013, 0, 120000;		Branch if F=1
$MAR2	$L20014, 0, 120000;		Branch if S=1, start fetch if F=1

; T←  causes MDRx to be read, otherwise it is loaded.
$MDR1	$L20010, 70010, 120100;		Branch if parity error on bus
$MDR2	$L20011, 70011, 120100;
$MDR3	$L20012, 70012, 120100;		Start store if MDR3← 

!1, 2, MStore, MFetch;
!1, 1, MStor1;
!1, 2, MRead, MRMW;
!1, 2, NoFPEr, FPEr;
!1, 2, NoRPEr, RPEr;


RunMI:	MAR1← AD0;
	MAR2← AD1, :MStore;		[MStore, MFetch]

MStore:	MDR1← MD1, :MStor1;		[MStor1]
MStor1:	MDR2← MD2;
	MDR3← MD3;
	TASK;
	:RunMI;		CPT's comment: This could be speeded up. We
;			load all interface registers rapidly so that
;			short (test) cycles look good on the scope.


MFetch:	TASK, :MRead;			[MRead, MRMW]

MRead:	NOP;

; Task wakes up again when memory operation has completed.
	L← T← MDR1;
	MD1← L, L← 0+1, :NoFPEr;	[NoFPEr, FPEr]

FPEr:	MErr← L;

NoFPEr:	L← T← MDR2;
	T← MDR3;
	MD2← L, L← T, TASK;
	MD3← L, :RunMI;


MRMW:	NOP;

; Task wakes up again when memory operation has completed.
	T← MDR1;
	L← MD1 OR T, :NoRPEr;		[NoRPEr, RPEr]

RPEr:	MD1← L, L← 0+1;
	MErr← L, :NoRPX;

NoRPEr:	MD1← L;
NoRPX:	MDR1← MD1;
	T← MDR2;
	L← MD2 OR T;
	MD2← L;
	MDR2← MD2;
	T← MDR3;
	L← MD3 OR T;
	MD3← L, TASK;
	MDR3← MD3, :RunMI;