//MX.BCPL interface to Maxc2

get "mx.d"


//GetRegData and PutRegData accept a register index as follows:
//	0	X	8 bits
//	1	AC	4 bits
//	2	Y	9 bits
//	3	P	36 bits
//	4	Q	36 bits
//	5	F	36 bits
//	6	NPC	12 bits
//	7	MAR	20 bits
//	8	MDR	40 bits (MDRL is the right-most bits)
//	9	KMAR	20 bits
//	10	KMDR	40 bits (KMDRL is the right-most bits)
//	11	ARM	36 bits
//	12	IMA	12 bits
//	13	KUNIT	3 bits
//	14	EREG	36 bits
//	15	BPC	20 bits
//	16	P1	36 bits (P1 and P are written concurrently)
//The duplicate registers for X and Y and the bus and alu branch
//conditions are invisible.

//GetMemData and PutMemData accept a memory index as follows:
//	0	IM[0,35] 36 bits		4,096 words (for diag only)
//	1	IM[36,71] 36 bits	4,096 words (for diag only)
//	2	IM	72 bits		4,096 words
//	3	SM	36 bits		512 words
//	4	DM	36 bits		512 words
//	5	MP	18 bits		1,024 words
//	6	MAIN	40 bits		1,048,576 words
//	7	RM	36 bits		32 words
//	8	LM	36 bits		32 words
//	9	STK	12 bits		12 words
//	10	DM1	36 bits		512 words
//	11	DM2	36 bits		512 words
//	12	LDR	80 bits		100 words
//	13	KSTAT	36 bits		8 words (read-only--bad result if int-in-progress)

//These routines accept a pointer called DataVec to a vector
//of data packed left-justified for each microprocessor word.
//The trailing bits of the last Alto word used to hold the
//value for a microprocessor register are returned zero-filled
//by Get, but may contain garbage on Put.

//GetMemData and PutMemData also use a pointer AddrVec to a
//double precision memory address which is unpacked into two
//Alto words called MADDRH and MADDRL during execution of the routines.

//The interface to Maxc2 works with 36 bits at-a-time.
//When sending data to the microprocessor, BR0 receives
//bits 0-15, BR1 16-31, and BR2 32-35 in bits 8-11 of the Alto word.
//When reading data from the microprocessor, B0, B1, and B2
//are used in the same way.  The data must be complemented on
//both input and output.

//The Get and Put routines above freely smash EREG, Q, IMA, NPC, and
//F in the course of reading or writing the register explicitly
//addressed.  MGetRegData, MPutRegData, MGetMemData, and MPutMemData
//are identical to the above but restore incidentally clobbered
//registers afterwards.

//XctMic is used to execute microinstructions through the maintenance
//interface.  It does this by loading the 64-bit microinstructions
//into PIR0, PIR1, PIR2, and PIR3 of the interface, and then
//loading CR with control bits for executing the microinstruction.

//All of these operations are carried out through "memory
//references" to ADREG (selecting one of the maintenance
//interface registers mentioned above) and then to either
//INREG or OUTREG.

//An assortment of suboutines move data to and from the maintenance
//interface's data register before and after microinstructions.
//DataVec's are universally used to hold values for registers and memories.

//The 80-bit microinstructions executed through the maintenance
//interface are assembled by Micro using a declaration file
//called DLANG2 nearly identical to LANG2 for ordinary microinstructions.
//Differences are:  fields in microinstructions are in different
//locations; branch address is missing; and values for the control register
//CR must be assembled as well.  LOADER.MC contains microinstructions needed
//by procedures in MX.  The order of these instructions
//is declared by the manifest constants in MX.D, so it should
//not be changed.

//Read into standard statics all registers clobbered incidentally while
//reading or writing other registers.
let ReadAllRegs() be
[	XctR36(RDEREG,EREG);
	for I = 0 to 11 do	//Save the stack
	[ XctR12(RDSTK,STK+I); XctMic(POPSTK)
	]
	XctR12(RDNPC,lv NPC)
	XctR12(RNIMA,lv IMA); IMA = (IMA-20B)&177760B  //NPC←IMA+1, then B←NPC
	XctR8(RDX,lv X); XctR9(RDY,lv Y); XctR36(RDSMF,SMFORF)
	XctR36(RDQ,Q); XctR36(RDF,F); RestoreSNI(); MPDSwitchPhase()
]


//Restore registers clobbered during STK changes
//Route the values to NPC to STK by a CALL
and RestoreSNI() be
[	for I = 11 to 0 by -1 do
	[ XctL12(LDNPC,STK+I); XctMic(CALL) ]
	RestoreMRegs(2)
]


and RestoreAfterLoad() be
[	RestoreMRegs(2); RestoreMRegs(4)
	RestoreMRegs(7); ReadAllRegs()
]


//Note that TMP and TMP1 must point at even word boundaries
//Arg asmtbl is pointer to function for doing the memory operation
and SetupMAIN(asmtbl) be
[	let errs = (asmtbl(TMP,TMP1) & 7)
	if errs eq 0 then return
	@ADREG = RESR; @OUTREG = 125B; @ADREG = 0	//Reset error latches
	if (errs & 4) ne 0 do
	[ StartIO(20000B)
	  WssCSS("Zapped memory interface ")
	]
	if (errs & 2) ne 0 then WssCSS("NXM ")
	if (errs & 1) ne 0 then WssCSS("Memory bus PE ")
]


//Move tag bits from pos 36-39 to pos 0-3
and RFix40(DataVec,MemVec) be
[	let T1,T2,T3 = MemVec!0,MemVec!1,(MemVec!2) & 177400B
	DataVec!0 = (T1 rshift 4)+(T3 lshift 4)
	DataVec!1 = (T2 rshift 4)+(T1 lshift 12)
	DataVec!2 = ((T3 rshift 4)+(T2 lshift 12))&177400B
]


//Move tag bits from pos 0-3 (from text input) to pos 36-39 (for memory)
and WFix40(MemVec,DataVec) be
[	let T1,T2,T3 = DataVec!0,DataVec!1,(DataVec!2) & 177400B
	MemVec!0 = (T1 lshift 4)+(T2 rshift 12)
	MemVec!1 = (T2 lshift 4)+(T3 rshift 12)
	MemVec!2 = ((T3 lshift 4)+(T1 rshift 4)) & 177400B
]

//Get register data
//Always read from the hardware.  Also update standard statics to shorten
//the state saving required for memory read/write.
and GetRegData(RegX,DataVec) be
[	switchon RegX into [
case 0:	XctR8(RDX,DataVec); X = DataVec!0; endcase
case 1:	XctR4(RDAC,DataVec); endcase
case 2:	XctR9(RDY,DataVec); Y = DataVec!0; endcase
case 3:	XctR36(RDP,DataVec); endcase
case 4:	XctR36(RDQ,DataVec); MoveBlock(Q,DataVec,3); endcase
case 5:	XctR36(RDF,DataVec); MoveBlock(F,DataVec,3); endcase
case 6:	XctR12(RDNPC,DataVec); NPC = DataVec!0; endcase
case 7:	XctR20(RDMAR,DataVec); endcase
case 8:	XctR36(RDMDR,DataVec); XctMic(RDMDRL); goto GetB40
case 9:	XctR20(RDKMAR,DataVec); endcase
case 10: XctR36(RDKMDR,DataVec); XctMic(RDKMDRL); goto GetB40
case 11: XctR36(RDARM,DataVec); endcase
case 12: XctR12(RNIMA,lv IMA); IMA = (IMA-20B)&177760B
	 DataVec!0 = IMA; XctL12(LDNPC,lv NPC); endcase
case 13: XctR4(RDKUN,DataVec); DataVec!0 = DataVec!0 lshift 1; endcase
case 14: XctR36(RDEREG,DataVec); MoveBlock(EREG,DataVec,3); endcase
case 15: XctR20(RDBPC,DataVec); endcase
case 16: XctR36(RDP,TMP); XctR36(RDPP1,DataVec); XctL36(LDP,TMP); endcase
default: CallSwat(); return
	]
	@ADREG = 0; return

GetB40:	@ADREG = B2; DataVec!2 = DataVec!2 + (not (@INREG lshift 4) & 7400B)
	RFix40(DataVec,DataVec); @ADREG= 0
]


and MGetRegData(RegX,DataVec) be
[	GetRegData(RegX,DataVec); XctL36(LDEREG,EREG); @ADREG = 0
]


//Change register data
and PutRegData(RegX,DataVec) be
[	switchon RegX into [
case 0:	XctL8(LDX,DataVec); X = DataVec!0; return
case 1:	XctL4(LDAC,DataVec); return
case 2:	XctL9(LDY,DataVec); Y = DataVec!0; return
case 16:
case 3:	XctL36(LDP,DataVec); return
case 4:	XctL36(LDQ,DataVec); MoveBlock(Q,DataVec,3); return
//Note:  cannot read/load SMFORF directly because it is slow.
//Hence, following uses Q as intermediary between SMFORF and E-bus,
//setting F to the new value, and finally restoring Q and SMFORF.
case 5:	XctL36(LDQ,DataVec); XctMic(LDSMF)
	XctMic(SETFNQ); XctMic(LDSMF); XctL36(CLRFLQ,SMFORF)
	XctMic(LDSMF); XctL36(LDQ,Q); MoveBlock(F,DataVec,3); return
case 6:	XctL12(LDNPC,DataVec); NPC = DataVec!0; return
case 7:	GetRegData(8,TMP); XctL20(LDMAR,DataVec); PutRegData(8,TMP); return
case 8:	WFix40(TMP1,DataVec); XctL36(LDMDR,TMP1); XctL8(LDMDRL,TMP1+2); return
case 9:	GetRegData(10,TMP); XctL20(LDKMAR,DataVec); PutRegData(10,TMP); return
case 10: WFix40(TMP1,DataVec); XctL36(LDKMDR,TMP1); XctL8(LDKMDRL,TMP1+2); return
case 11: XctL36(LDARM,DataVec); return
case 12: XctL12(LDNPC,DataVec); XctL12(BRNIMA,lv NPC); IMA = DataVec!0; return
case 13: TMP!0 = DataVec!0 rshift 1; XctL4(LDKUN,TMP); return
case 14: XctL36(LDEREG,DataVec); MoveBlock(EREG,DataVec,3); return
case 15: XctL20(LDBPC,DataVec); return
default: CallSwat(); return
	]
]


and MPutRegData(RegX,DataVec) be
[	PutRegData(RegX,DataVec); XctL36(LDEREG,EREG); MPDSwitchPhase()
]

//Get memory data
and GetMemData(MemX,AddrVec,DataVec) = valof
[	switchon ConvertAV(AddrVec,MemX) into
	[
case 0:	XctMic(RDIMH); XctR36(RDEREG,DataVec); endcase
case 1:	XctMic(RDIML); XctR36(RDEREG,DataVec); endcase
case 2:	XctMic(RDIMH); XctR36(RDEREG,DataVec)
	XctMic(RDIML); XctR36(RDEREG,TMP); SetIMRd(DataVec); endcase
case 3:	XctR36(RDSM,DataVec); endcase
case 4:	XctR36(RDDM,DataVec); endcase
case 5:	XctR18(RDMAP,DataVec); endcase
case 6:	SetupMAIN(table [ FETCH; RTN ] ); RFix40(DataVec,TMP1); endcase
case 7:	XctR36(RDRM,DataVec); endcase	//Very clever here--see LOADER.MC
case 8:	XctR36(RDLM,DataVec); endcase
case 9:	DataVec!0 = STK!MADDRL; endcase
case 10: XctR36(RDDM1,DataVec); endcase
case 11: XctR36(RDDM2,DataVec); endcase
case 12: MoveBlock(DataVec,LADDR,5); endcase
case 13: GetRegData(11,TMP)	//Return -1 if int in progress
	if TMP!0 < 0 do
	[ MoveBlock(DataVec,table [ -1; -1; 170000B ] , 3); endcase ]
	GetRegData(13,TMP)	//Preserve KUNIT
	MADDRL = MADDRL lshift 13; PutRegData(13,lv MADDRL)
	XctR36(RDKSTAT,DataVec); PutRegData(13,TMP); endcase
default: resultis false
	]
	resultis true
]


and MGetMemData(MemX,AddrVec,DataVec) = valof
[	test GetMemData(MemX,AddrVec,DataVec)
	ifso [ RestoreMRegs(MemX); resultis true ]
	ifnot resultis false
]


//Change memory data (analogous to GetMemData)
and PutMemData(MemX,AddrVec,DataVec) = valof
[	switchon ConvertAV(AddrVec,MemX) into
	[
//Have to load EREG first, then IM so that parity gets computed.
//Otherwise, could go direct from BR to IM
case 0:	XctL36(LDEREG,DataVec); XctMic(LDIMH); endcase
case 1:	XctL36(LDEREG,DataVec); XctMic(LDIML); endcase
case 2:	SetIMLd(DataVec)
	XctL36(LDEREG,DataVec); XctMic(LDIMH)
	XctL36(LDEREG,TMP); XctMic(LDIML); endcase
case 3:	XctL36(LDQ,DataVec); XctMic(LDSM)
	if MADDRL eq 377B then MoveBlock(SMFORF,DataVec,3); endcase
case 4:	XctL36(LDQ,DataVec); XctMic(LDDM); endcase
case 5:	XctL18(LDQ,DataVec); XctMic(LDMAP); endcase
case 6:	WFix40(TMP1,DataVec); SetupMAIN(table [ STORE; RTN ] ); endcase
case 7:	XctL36(LDQ,DataVec); XctMic(LDRM); endcase
case 8:	XctL36(LDQ,DataVec); XctMic(LDLM); endcase
case 9:	STK!MADDRL = (DataVec!0) & 177760B
	RestoreSNI(); ReadAllRegs(); endcase
case 10: XctL36(LDQ,DataVec); XctMic(LDDM1); endcase
case 11: XctL36(LDQ,DataVec); XctMic(LDDM2); endcase
case 12: MoveBlock(LADDR,DataVec,5); endcase
case 13: DisplayError("Read-only register")
default: resultis false
	]
	resultis true
]


and MPutMemData(MemX,AddrVec,DataVec) = valof
[	test PutMemData(MemX,AddrVec,DataVec)
	ifso [ RestoreMRegs(MemX); MPDSwitchPhase(); resultis true ]
	ifnot resultis false
]

and BreakIML(AddrVec,BPBit) be
[	MGetMemData(2,AddrVec,INSTST)
	INSTST!3 = ((INSTST!3) & not 100000B)%BPBit
	MPutMemData(2,AddrVec,INSTST)
]


//Insert break point at address (error if no address given)
and InsertBreak(Addr; numargs Zot) be
[	let Avec = vec 1; Avec!0 = 0; Avec!1 = Addr
	test Zot eq 0
	ifso [ WssCSS("No address typed"); Blink() ]
	ifnot
	[ BreakIML(Avec,100000B)
	  WssCSS("Inserted break at")
	  Wos(CmdCommentStream,Addr)
	]
]


//Remove break point at address (or at IMA if no address given)
and RemoveBreak(Addr; numargs Zot) be
[	let Avec = vec 1; Avec!0 = 0; Avec!1 = Addr
	if Zot eq 0 then Avec!1 = IMA rshift 4
	BreakIML(Avec,0)
	WssCSS("Removed break at")
	Wos(CmdCommentStream,Avec!1)
]


//If Zot is 0 then return (step or start at current address)
//else load IMA with address, NPC with address+1, and clear interrupts.
and SetupNPCIMA(Addr,Zot) be
[	if Zot ne 0 do
	[ IMA = Addr lshift 4; NPC = IMA+20B
	  RestoreMRegs(2); XctMic(INTRETN)
	]
	MADDRL = IMA rshift 4
]


//Single step microprocessor at address (continue at IMA if no address given)
and SingleStepM(Addr; numargs Zot) be
[	SetupNPCIMA(Addr,Zot)
	WssCSS(Zot ne 0 ? "Step at","Next step from")
	Wos(CmdCommentStream,MADDRL)
	ResetMaxc(SINSTP); ReadAllRegs()
	ShowIMSym(CmdCommentStream,", halt at ",IMA rshift 4)
]


and ShowIMSym(Stream,Str,Addr) be
[	Wss(Stream,Str)
	let AVec = vec 1; AVec!0 = 0; AVec!1 = Addr
	SearchBlocks(Stream,2,AVec)
]


and ResetMaxc(Inst) be
[	@ADREG = RESR; @OUTREG = 125B	//Reset memory error latches
	let Zot = @ERRINF		//Reset Alto FER latch
	XctMic(WKWRES)			//Unhang KRMW and RMW
	let ARMvec = vec 2; GetRegData(11,ARMvec)
	ARMvec!0 = (ARMvec!0 & 16B) lshift 3
	PutRegData(11,ARMvec); XctL36(LDEREG,EREG); XctMic(Inst); @ADREG = 0
]

//Start at address.
and StartM(Addr; numargs Zot) = valof
[	SetupNPCIMA(Addr,Zot)
	ResetMaxc(MGO)
	WssCSS(Zot eq 0 ? "Continue at","Go at")
	Wos(CmdCommentStream,MADDRL)
	QUITact = CreateAction("Abort",lv HaltMaxc,0,0,$C-100B)
	QuitF = AddToEveryTimeList(HaltWait,0)
	resultis HaltWaitMenu
]

and HaltWait(Nix) be
[	@ADREG = RUN; let Z = @INREG
	if (Z & 200B) eq 0 do
	[ Z = @INREG
	  if (Z & 200B) ne 0 do
	  [ WssCSS("RG "); UpdateDisplay(); return ]
	  WssCSS(", Halt ")
	  if (Z & 100B) eq 0 then WssCSS("Breakpoint ")
	  if (Z & 160B) ne 0 then ErrorProtect(lv ReportPE,Z)
	  MaxcStopped(true)
	]
]
 

and HaltMaxc(Nix) be
[	XctMic(MSTOP); Resets(CmdCommentStream)
	WssCSS("Halted by mouse"); MaxcStopped()
]


and MaxcStopped(ShowF) be
[	if QuitF ge 0 do
	[ ReadAllRegs(); RemoveFromEveryTimeList(QuitF) ]
	if ShowF then ShowIMSym(CmdCommentStream," at ",IMA rshift 4)
	QuitCmdOverlay()
]


and HaltWaitMenu(S,Nix) be WsMarkA(QUITact)


//Wait for X seconds
and Wait(X) be
[	for I = 0 to X do
	[ for J = 0 to 37777B do [ ]
	]
	return
]


and ProcOnTest() = valof
[	@ADREG = PPSTAT; let X = @INREG & 3
	resultis X eq 3 ? true,false
]


and PrintIMA(Stream,X,DVec,AVec) be
[	ShowIMSym(Stream,"",DVec!0 rshift 4)
	for N = 0 to 9 by 3 do
	[ CharInputRoutine(GetField(N,3,DVec)+$0 ) ]
	ClearInText()
]


and PrintDM(Stream,X,DVec,AVec) be
[	ShowIMSym(Stream,"",GetField(1,11,DVec))
	ShowIMSym(Stream,",",GetField(12,12,DVec))
	ShowIMSym(Stream,",",GetField(24,12,DVec))
	if DVec!0 ge 0 do [ Wss(Stream," [immediate]") ]
]


and InitHardware() be
[	@ERRENB = 16B	//Enable FER interrupt only
	ReadAllRegs()
]


and FinishHardware() be StartIO(#100000)