; MaxcImp.mu -- Microcode for driving Maxc2 Alto Imp interface

;	Last modified April 13, 1978  7:45 PM

; Derived from Alto-1822 microcode by Larry Stewart.
; The software interface is the same, but the implementation is more
; time-efficient due to the use of a number of additional S-registers.
; This microcode runs only on Alto-I.

; Data structures:
; Imp Command Block (ICB)
; word	0	control word -- command
;	1	unused
;	2	input pointer -- next word to be used
;	3	input buffer end -- first word not in buffer
;	4	output pointer -- next word to be used
;	5	output buffer end -- first word not in buffer
;	6	control post location
;	7	control interrupt channels
;	10	input post location
;	11	input interrupt channels
;	12	output post location
;	13	output interrupt channels

; Microcode status codes:
$ISDone	$777;	Normal completion.
$ISOvf	$1377;	Buffer overflow (input only)
$ISIBLZ	$1777;	Block length zero (input only)


; Imp Task-specific functions:

$IREAD	$L 000000, 070017, 000100;	F1=17	← Input data, branch if end
$IWRITE	$L 020016, 000000, 120000;	F1=16	Output data ←
$IOCLR	$L 016015, 000000, 000000;	F1=15	Clear output wakeup
$IPOSTF	$L 016014, 000000, 000100;	F1=14	← Status

$ISWAKC	$L 024014, 000000, 000000;	F2=14	Clear SIO-generated wakeup
$IBRNCH	$L 024013, 000000, 000000;	F2=13	4-way branch on wakeup
$IIENBL	$L 024012, 000000, 000000;	F2=12	Start read (turn on RFNIB)
$ISETCS	$L 026011, 000000, 120000;	F2=11	Set control ←
$IPTMOD	$L 024010, 000000, 000000;	F2=10	2-way branch on throwaway mode


; S-registers dedicated to this task

$ICBPtr	$R70;		Imp Command Block pointer
$IPtr	$R71;		Input Pointer -- last word stored
$IEPtr	$R72;		Input end pointer -- first word beyond end of buffer
$OPtr	$R73;		Output Pointer -- last word fetched
$OEPtr	$R74;		Output end pointer -- first word beyond end of buffer

%14, 14, 0, IIStart, IControl, IOData, IIData;	00xx, 01xx, 10xx, 11xx
!1, 2, IPost, IReset;
!1, 2, IIEnab, IIZero;
%4, 4, 0, IIAcpt, IIDisc;			x0xx, x1xx
%5, 5, 0, IIMore, IIFull, IILast, IIFLst;	x0x0, x0x1, x1x0, x1x1
%4, 5, 1, IIDMor, IIDLst;			x0x1, x1x1
!1, 2, IOMain, IOInit;
!1, 2, IOMore, IOEnd;
!1, 2, IONLst, IOLast;

$12	$12;

; *** Imp Task starting address and main loop ***

ImpTask:
	L← 0, TASK, :IOFin;		Indicate no output in progress

; Task always blocks here and wakes up when there is something to do.
ImpLoop:
	T← ICBPtr, IBRNCH;		Branch on wakeup condition
	:IIStart;			[IIStart, IControl, IOData, IIData]


; *** Common Post routine ***
; Expects offset of post location in T and post code in M.
IPost:	MAR← ICBPtr+T;			Reference post location
	T← NWW;
	MD← M, IPOSTF;			Bus AND hardware status with post code
	L← MD OR T, TASK;		OR interrupt bits into NWW
	NWW← L, :ImpLoop;


; *** Imp Control/Status command ***
; Establishes command block pointer, issues command given in command word,
; and posts control status.

IControl:
	MAR← L← AC1;			Fetch control word
	ICBPtr← L, ISWAKC;		Save control block ptr, clear wakeup
	ISETCS← MD, L← MD-1;		Issue control function; Master Reset?
	T← 6, SH=0;			T← control post offset
IPDone:	L← ISDone, :IPost;		[IPost, IReset] L← done status

; Here if Master Reset was issued.
IReset:	L← 0;				Note no output in progress
IODone:	OPtr← L, :IPDone;

; *** Input initialization ***
; Reads pointers from control block and enables Imp input.
IIStart:
	MAR← 2+T;			Reference ICB word 2
	ISWAKC;				Clear SIO-generated wakeup
	T← MD-1;			T← input pointer -1
	L← MD;				T← end pointer
	IEPtr← L;			IEPtr← end pointer
	L← IEPtr-T-1;			Test for zero-length buffer
	L← T, T← 10, SH=0;		T← input post offset
	IPtr← L, :IIEnab;		[IIEnab, IIZero] IPtr← input ptr -1
IIEnab:	TASK, :IIDMor;

; Here if input buffer length is zero.
IIZero:	L← ISIBLZ, :IPost;		Post buffer length zero error

; *** Input main loop ***
; Note that the instruction at IIAcpt has two branches:  a NEXT[9] branch
; for SH=0 (buffer full) and a NEXT[7] branch for IREAD (last word).
IIData:	MAR← T← IPtr+1;			Start data store
	L← IEPtr-T, IPTMOD;		Test buffer full, test discard mode
	L← T, T← IREAD, SH=0, :IIAcpt;	[IIAcpt, IIDisc] Read data word
IIAcpt:	IPtr← L, L← T, :IIMore;		[IIMore, IIFull, IILast, IIFLst]
IIMore:	MD← M, TASK;			Store data word
IIDMor:	IIENBL, :ImpLoop;		Enable input of next word

; Here after reading last word.
IILast:	MD← M;				Store data word
	T← ICBPtr;
	MAR← 2+T;			Reference ICB word 2
	L← IPtr+1, TASK;		Store pointer to last word used +1
	MD← M;

; Note that our wakeup is still asserted because the IREAD that reads the
; last word doesn't clear it.  It is necessary to issue another IREAD
; in this case.
IILst1:	SINK← IREAD;			Clear the wakeup (can't branch)
	T← 10, :IPDone;			T← input post offset, post done

; Here when we are in discard mode.
; There is a NEXT[7] branch for IREAD (last word), which we take, and
; a NEXT[9] branch for SH=0 (buffer full), which we squash.
IIDisc:	TASK, :IIDMor;			[IIDMor, IIDLst]
IIDLst:	:IILst1;

; Here when input buffer overflows.
IIFLst:	SINK← IREAD;			Also last word, clear wakeup
IIFull:	T← 10;				T← input post offset
	L← ISOvf, :IPost;		Post input overflow status


; *** Output main loop ***
; OPtr contains zero when no output is in progress.
; That is how we decide whether we are initializing or in the main loop.
IOData:	MAR← T← OPtr+1, BUS=0;		Start data fetch, see if first
	L← OEPtr-T, :IOMain;		[IOMain, IOInit]
IOMain:	L← M-1, IOCLR, SH=0;		Last word already sent?
	L← 0, SH=0, :IOMore;		[IOMore, IOEnd] Is this last word?
IOMore:	IWRITE← MD, L← T, :IONLst;	[IONLst, IOLast] Send the data word
IOLast:	ISETCS← 2;			Set last word flop if appropriate
IONLst:	TASK;
IOFin:	OPtr← L, :ImpLoop;		Update output pointer

; Here when woken up after last word sent.
IOEnd:	T← 12, :IODone;			T← Output post offset;
;					Zero OPtr and post normal done

; Here when woken up with no output in progress.
IOInit:	T← ICBPtr;			Reference ICB word 4
	MAR← 4+T;
	NOP;
	L← MD-1;			L← output pointer -1
	T← MD;				T← end pointer
	OPtr← L, L← T, TASK;
	OEPtr← L, :IOData;		Now send first word