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