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