; AltoEIA1.mu -- Microcode for controlling the Alto EIA interface.
;		This file contains the main dispatch, subroutines,
;		and BiSync software interface.

;	Last modified September 23, 1978  4:14 PM

; This microcode runs as part of the Memory Refresh Task.
; It replaces the Interval Timer microcode.

; The Alto must be an Alto II, and it must be modified to make MRT
; be a RAM-related task.

; Data structures:
; LINTAB is an 8-entry table (one per line).  Each entry is 6 words,
; and its base is at LINTAB + 6*line, which must be even.
; The layout of an entry is:
; word	0	unused
;	1	current hardware status, posted by microcode on every wakeup
;	2	pointer to input Line Command Block (LCB)
;	3	current input state
;	4	pointer to output LCB
;	5	current output state

; The word at LINTAB-1 must contain a pointer to a 256-word table
; that assists in CRC computation.  See comments after UpdateCRC
; for information on its construction.

; The line state is a small integer representing the state of the microcode
; with respect to a given line and direction.  It is used to dispatch to
; the appropriate microcode after initial, common processing is completed.
; The microcode updates this word to change states.

; LCBs must be aligned on even-word boundaries.
; The first four words of the LCB have a standard interpretation:
; word	0	link to next LCB (0 if none)
;	1	interrupt bit mask
;	2	microcode status is posted here upon completion:
;		 bit 0 = 0 before completion, set to 1 upon completion
;			(must be initialized to zero by software).
;		 bits 1-15 are error status:
;			0	no error
;			1	fatal error status
;			>1	protocol-dependent error
;	3	hardware status is posted here upon completion or error


; Registers dedicated to EIA microcode.
; They may be R- or S-registers.

$LINTAB	$R71;	Address of base of line table
$LTEPTR	$R72;	Address of LINTAB entry for current line
$LCB	$R73;	Address of LCB for current line
$EIASTS	$R77;	Hardware status for current line -- never used with MAR←
$EIADAT	$R74;	EIA data, temporary
$EIATMP	$R75;	Temporary

; Standard R-registers

$NWW	$R4;
$MTEMP	$R25;

; State number assignments.
; Must be parallel to EIAStates predefinition.
; States 0-7 are reserved as initial states.
; Note:  the Alto constant memory has small integers only up to 31.
; Larger state numbers will have to be manufactured on the fly.

; Throwaway state -- all wakeups are ignored
$Reset#	$0;

; States for BiSync receiver
$RIdle#	$1;	Idle, waiting for first DLE -- Initial state.
$RDLE1#	$10;	Received first DLE, waiting for STX
$RData#	$11;	Receiving data, in transparent mode
$RDLE2#	$12;	Received DLE during data, waiting for next byte
$RETX1#	$13;	Received ETX, waiting for first CRC byte
$RETX2#	$14;	Received first CRC byte, waiting for second

; States for BiSync transmitter
$TIdle#	$2;	Idle, waiting for something to send -- Initial state.
$TSYN1#	$15;	Sent first SYN
$TSYN2#	$16;	Sent second SYN
$TSYN3#	$17;	Sent third SYN, waiting to send DLE
$TDLE1#	$20;	Sent DLE, waiting to send STX
$TData#	$21;	Transmitting data, in transparent mode
$TDLE2#	$22;	Sent DLE, waiting to send second one (DLE doubling)
$TDLE3#	$23;	Sent DLE, waiting to send ETX
$TETX1#	$24;	Sent ETX, waiting to send first CRC byte
$TETX2#	$25;	Sent first CRC byte, waiting to send second
$TEnd#	$26;	Sent second CRC byte, ready to post completion

; State for character-at-a-time receiver
$RCAAT#	$3;

; State for character-at-a-time transmitter
$TCAAT#	$4;

; States for uninterpreted block mode transmitter
$TUIdl#	$5;	Idle, waiting for something to send -- Initial state
$TUBlk#	$27;	Transmitting data block

!37,40, SameState, RIdle, TIdle, RCAAT, TCAAT, TUIdl, , ,
	RDLE1, RData, RDLE2, RETX1, RETX2, TSYN1, TSYN2, TSYN3,
	TDLE1, TData, TDLE2, TDLE3, TETX1, TETX2, TEnd, TUBlk;


; Random constants
$1	$1;
$170000	$170000;
$177700	$177700;
$177701	$177701;


; Predefinitions
; !1, 2, EIAInt, EIAExit;		Must appear previously with MRT code

!1, 2, EIAIn, ~EIAIn;
!1, 1, EIAIn1;
!1, 2, EIAOut, EIADismiss;
!1, 2, EIAEr, ~EIAEr;
!1, 1, EIADsp;
!1, 2, GotLCB, NoLCB;
!1, 1, ChangeState;
!1, 2, EIAEx1, EIADs1;

; *** EIA entry code ***

; To get control here, the relevant changes in AltoIIMRT16K.mu are:
; Old:
;	!1, 2, DOTIMER, NOTIMER;
;		...
;		L← REFIIMSK AND T, :DOTIMER;	[DOTIMER, NOTIMER]
; 	NOTIMER: R37← L;
; 	NOTIMERINT: T← 2;

; New:
;	!1, 2, CheckEIA, NoEIA;
;	!1, 2, EIAInt, EIAExit;
;		...
;		L← REFIIMSK AND T, :CheckEIA;	[CheckEIA, NoEIA]
; 	NoEIA: R37← L;
; 	EIAExit: T← 2;

; Enter here from MRT code if EIA is turned on (R37[15] = 1).
; L has word to be stored back in R37.
; Read EIA status to see whether any line is requesting service.

CheckEIA:
	T← 2;
	MAR← T← 177701+T+1;		Reference 177704 = EIA status
	R37← L, L← T;			Update R37, L← 177704
	EIATMP← L;			EIATMP← 177704 for later use
	T← 1;
	L← MD AND T, T← MD;		Test bit 15 of status, T← whole word
	L← 7000 AND T, SH=0;		L← line number lshift 9
	MTEMP← L LCY 8, L← T, :EIAInt;	[EIAInt, EIAExit] MTEMP← 2*line

; Found a line requesting service.
; Compute pointer to line table entry and store current hardware status.
; Have L = status, MTEMP = 2*line, EIATMP = 177704.
EIAInt:	EIASTS← L;			Save entire status word
	T← MTEMP;			T← 2*line
	T← MTEMP+T+1;			T← 4*line +1
	T← MTEMP+T+1;			T← 6*line +2
	L← LINTAB+T, TASK;		L← LINTAB + 6*line +2
	LTEPTR← L;			= pointer to word 2 of entry

	MAR← LTEPTR-1;			Store status in word 1 of entry
	T← 200;
	MD← T← EIASTS, L← EIASTS AND T;	Test Receive Data Available flag
	L← 100 AND T, T← 100, SH=0;	Test Transmit Buffer Empty flag
	L← EIASTS AND NOT T, T← EIASTS, SH=0, :EIAIn; [EIAIn, ~EIAIn] Reset it

; Here if Receive Data Available flag is set.
; Read the data byte and store it in EIADAT.
EIAIn:	MAR← EIATMP-1, :EIAIn1;		[EIAIn1] Reference 177703 = input data
EIAIn1:	L← 177577 AND T;		Reset Receive Data Available flag
	EIASTS← L;
	T← 377;				Data mask
	L← MD AND T, TASK;		Get the data byte
	EIADAT← L;

	MAR← LTEPTR;			Reference word 2 of entry
	T← 16, :EIACom;			Mask of receiver error bits
;					= Parity, Overflow, Framing error

; Here if Receive Data Available flag is not set.
; Branch pending on Transmit Buffer Empty.
; If Transmit Buffer Empty is not set, just dismiss the interrupt.
~EIAIn:	EIASTS← L, :EIAOut;		[EIAOut, EIADismiss] Update status

; Here if Transmit Buffer Empty flag is set.
EIAOut:	T← 2;
	L← LTEPTR+T, TASK;		Advance LTEPTR to word 4 of entry
	LTEPTR← L;

	MAR← LTEPTR;			Reference word 2 of entry
	T←20, :EIACom;			Mask of transmit error bits
;					= Fill Char Sent (treat as data-late)

; EIA entry code (cont'd)

; T has mask of status bits to be considered fatal errors.
; Now get the LCB pointer for this line and direction.
; If a fatal error has occurred, post an error in the LCB and set
; the line's state to Idle.  Otherwise, dispatch on the line's state.
; Fetch has been started for words 2 and 3 (input) or 4 and 5 (output).
EIACom:	L← EIASTS AND T;		Test status bits
	SH=0;
	L← T← MD, BUS=0, :EIAEr;	[EIAEr, ~EIAEr] Get LCB, test for zero

; Note that we normally dispatch without testing to see whether there is
; an LCB.  The assumption is that if there is no LCB, the microcode will
; have left the line in the Idle state or will otherwise be prepared
; to handle this condition.
~EIAEr:	SINK← MD, BUS, TASK;		[EIADsp] Dispatch on line state
EIADsp:	LCB← L, :SameState;		[SameState, ...]

; Here if a fatal error status bit is set.
; If there is an LCB, post error status in it before dispatching.
EIAEr:	L← MD, :GotLCB;			[GotLCB, NoLCB] L← state
GotLCB:	MAR← 2+T;			Reference LCB word 2
	NOP;
	MD← 1;				Word 2 ← fatal error status code
	MD← EIASTS;			Word 3 ← hardware status
NoLCB:	SINK← M, L← T, BUS, TASK, :EIADsp; Now branch on state


; *** EIA exit code ***
; Branch to ChangeState with new state number in L, or
; to SameState to leave the state unchanged.

ChangeState:
	MAR← LTEPTR+1;			Store into state word
	T← EIASTS;
	MD← M, :EIADismiss;

; Here when done processing and don't want to change state.
; Can get here also during the initial dispatch if the line
; is in the "throwaway" state.
; If we just did input processing, test to see whether an output
; request is also pending.  If so, do not dismiss the EIA service request.
; This will cause the line to be serviced again during the next run of MRT.
; The idea is not to run for too long per MRT period, since if we overrun the
; MRT period the clock loses time, the cursor jiggles, etc.

SameState:
	T← EIASTS;

; If we get here from the branch at ~EIAIn, we know that T contains
; an EIASTS with the Transmit Buffer Empty bit zero.
EIADismiss:
	L← 100 AND T;			Test Transmit Buffer Empty
	MAR← 177700;			Store into control word
	T← 170000 OR T, SH=0;		Make Interrupt Acknowledge command
	L← 20000 XOR T, TASK, :EIAEx1;	[EIAEx1, EIADs1] 150xxx + line lsh 9
EIADs1:	MD← M, :EIAExit;		Exit back to MRT

; Output request pending, just go away and let the memory reference die.
EIAEx1:	:EIAExit;

; Branch to ChangeStateGo with new state number in L, to establish
; a new state and immediately dispatch to the microcode for that state.
ChangeStateGo:
	MAR← LTEPTR+1;			Store into line state word
	SINK← M, BUS, TASK;		Dispatch on new state
	MD← M, :SameState;

; *** EIA subroutines ***

; Subroutine to post LCB completion.
; T has return index, L has post code
; Unless an error has already been posted, posts the supplied code
; and current hardware status.  In any event, marks LCB completed.
; Returns having set up new LCB, if any.
; Does nothing if there is no LCB.
; Clobbers EIATMP.

!3, 4, EPRet0, EPRet1, EPRet2, EPRet3;
!1, 2, OkLCB1, NoLCB1;
!1, 2, OldEr, ~OldEr;

EIAPost:
	MTEMP← L, L← T;			MTEMP← post code, L← return index
	T← 1;
	MAR← T← LCB+T+1, BUS=0;		Reference LCB word 2, test for no LCB
	EIATMP← L, L← T, :OkLCB1;	[OkLCB1, NoLCB1] EIATMP← return index
OkLCB1:	LCB← L;				LCB← pointer to LCB word 2
	T← 100000;			Prepare to set completion bit
	L← MD OR T, BUS=0;		Test existing post code
	MAR← LCB, :OldEr;		[OldEr, ~OldEr] Reference LCB word 2

; There isn't already an error posted.
; Post the supplied code and current hardware status.
~OldEr:	L← MTEMP OR T;			Merge completion bit into code
	MD← M, TASK;			Word 2 ← post code
	MD← EIASTS, :NxtLCB;		Word 3 ← hardware status

; There is already an error posted.
; Just set the completion bit and do not store hardware status.
OldEr:	TASK;
	MD← M;				Word 2 ← old post code with completion

; Cause interrupt as specified in LCB's mask, then chain to next LCB
NxtLCB:	MAR← LCB-1;			Reference LCB word 1
	T← NWW;				Current wakeups waiting
	L← MD OR T;			Merge in word 1 (mask)
	T← MD;				T← word 0 (new LCB)
	MAR← LTEPTR;			Reference LCB pointer in LINTAB entry
	NWW← L, L← T;			Update wakeups
	MD← M;				Store new LCB pointer in memory
EPExit:	SINK← EIATMP, BUS, TASK;	Dispatch on return index
	LCB← L, :EPRet0;		[EPRet0, ...]

; Here if there was no LCB.
NoLCB1:	L← 0, :EPExit;			Exit having done nothing



; Subroutine to send data byte on current line.
; T has return index, L has data byte.

!3, 4, ESRet0, ESRet1, ESRet2, ESRet3;

EIASend:
	MTEMP← L, L← T;			MTEMP← data byte, L← return index
	T← EIASTS;
	T← 7000 . T;			T← line lshift 9
	MAR← 177701+1;			177702 = data output register
	T← MTEMP OR T;			Merge line and data byte
	SINK← M, L← T, BUS, TASK;	Dispatch on return index
	MD← M, :ESRet0;			[ESRet0, ...] Complete store

; Subroutine to test data byte in EIADAT against one or more constants.
; The control structure is somewhat tricky, and is best explained
; by the following example:

;		L← 0, BUS, :BTInit;
;	BTne0:	T← first constant, :BTest;
;	BTne1:	T← second constant, :BTest;
;	BTne2:	T← third constant, :BTest;
;	BTne3:	;	Get here if EIADAT matched none of the constants

;	BTeq1:	;	Get here if EIADAT matched the first constant
;	BTeq2:	;	Get here if EIADAT matched the second constant
;	BTeq3:	;	Get here if EIADAT matched the third constant

; The flow of control is as follows.  The first instruction established
; an initial dispatch index (0 in this case), and BTInit returns to
; the 'BTneX' label corresponding to this index.  Then, for successively
; increasing values of X, BTest compares the constant passed to it against
; EIADAT and returns to 'BTneX' if they don't match and 'BTeqX' if they do.
; A 'TASK' is done shortly before the return to either label.
; EIATMP is clobbered.

; The 'BTneX' and 'BTeqX' labels are given in the following predefinitions,
; which must be parallel and in strictly sequential values of X.
; Note that there need not be a 'BTeqX' label for values of X that
; are initial dispatch indices (except BTeq0, which is a dummy).

!17, 20, BTne0, BTne1, BTne2, BTne3, BTne4, BTne5, BTne6, BTne7,
	BTne10, BTne11, BTne12;
!17, 20, BTeq0, BTeq1, BTeq2,      , BTeq4,      , BTeq6, BTeq7,
	BTeq10,       , BTeq12;

!1, 2, BTInit, BTeq;

BTest:	L← EIADAT-T;			Test data byte equal to argument
	L← EIATMP+1, SH=0;		Increment index and dispatch on it
BTeq0:	SINK← M, BUS, TASK, :BTInit;	[BTInit, BTeq]

BTInit:	EIATMP← L, :BTne0;		[BTne0, ...] Not equal
BTeq:	EIATMP← L, :BTeq0;		[BTeq0, ...] Equal



; Subroutine to issue "Reset" command to current line.
; L has return index, T has command bit (D = 400, RRT = 200, RR = 100)

!1, 2, EResR0;	(Only one caller at present, but there used to be more)

EIAReset:
	MTEMP← L, L← T;			MTEMP← return index, L← command bit
	T← EIASTS;			Current status word
	T← 7000 . T;			T← line lshift 9
	T← 100000 OR T;			Construct Reset command
	T← 30000 OR T;			= 130000 + line lshift 9
	MAR← 177700;			Reference control word
	L← M OR T;			Merge in supplied command bit
	SINK← MTEMP, BUS, TASK;		Dispatch on return index
	MD← M, :EResR0;			[EResR0, ...] Issue command

; Subroutine to update CRC in LCB given new data byte in EIADAT.
; L has return index.
; Also returns updated CRC in EIADAT.
; Does nothing harmful if there is no LCB.
; Clobbers EIATMP.
; Altorithm is:
;	CRC = (CRC rshift 8) xor CRCTab!((CRC xor data) & #377)

!7, 10, UCRet0, UCRet1, UCRet2, UCRet3, UCRet4, UCRet5, UCRet6;
!1, 2, OkLCB2, NoLCB2;

UpdateCRC:
	T← 6;
	MAR← LCB+T, BUS=0;		Reference LCB word 6
	EIATMP← L, :OkLCB2;		[OkLCB2, NoLCB2] EIATMP← return index
OkLCB2:	T← EIADAT;			The data byte
	L← MD XOR T, T← MD;		L← CRC table index, T← old CRC
	MAR← LINTAB-1;			LINTAB-1 points to CRC table
	T← M, L← T;			T← index, L← old CRC
	MTEMP← L LCY 8;			MTEMP← old CRC lcy 8
	T← 377 . T;			Zero high byte of table index
	L← MD+T;			L← desired address in CRC table
	MAR← M;				Fetch word from CRC table
	T← 377;				Compute old CRC rshift 8
	T← MTEMP . T;
	L← MD XOR T, TASK;		Compute new CRC
	EIADAT← L;			Store here temporarily

	T← 6;
	MAR← LCB+T;			Reference LCB word 6
	SINK← EIATMP, BUS, TASK;	Dispatch on return index
	MD← EIADAT, :UCRet0;		[UCRet0, ...] Store updated CRC

; Here if there is no LCB.  Return having done nothing.
NoLCB2:	SINK← EIATMP, BUS, TASK;
	:UCRet0;			[UCRet0, ...]

; Note:
; LINTAB-1 must contain a pointer to a 256-word table CRCTab,
; constructed as follows:

; for i = 0 to 255 do
;    [
;    let crc = 0
;    let val = i
;    for power = 0 to 7 do
;       test (val & 1) eq 0
;          ifso
;             val = val rshift 1
;          ifnot
;             [
;             crc = crc xor (#120001 rshift (7 - power))
;             val = (val rshift 1) xor #120001
;             ]
;    CRCTab!i = crc
;    ]

; **** BiSync Receiver finite state machine ****

; This FSM runs whether or not there is an LCB describing a buffer
; to be read into.  Code that references the LCB tests carefully for
; its nonexistence.  Thus, we stay in sync with incoming data even
; if the software fails to provide an LCB.

; BiSync character constants
$SYN	$26;
$DLE	$20;
$STX	$2;
$ETX	$203;

; LCB format:
; word	0 to 3	standard
;	4	bit 0:  0 if left byte is next, 1 if right
;		bits 1-15:  remaining byte count
;	5	address of word containing next byte
;	6	partial CRC (must be zeroed initially by software)

!1, 2, ~RDDLE, RDDLE;
!1, 2, OkLCB3, NoLCB3;
!1, 2, ~RDOvf, RDOvf;
!1, 2, RRight, RLeft;
!1, 2, CRCEr, RPost;


; "Idle" state.
; If new byte is:
;	SYN	ignore, stay in idle state
;	DLE	ignore, expect STX next
;	other	drop sync, restart receiver, stay in Idle state

RIdle:	L← 0, BUS, :BTInit;		Supply initial dispatch to BTest
BTne0:	T← SYN, :BTest;			Is it a SYN?
BTne1:	T← DLE, :BTest;			No, is it a DLE?
BTne2:	:EPRet1;			No, restart receiver

; SYN -- ignore, stay in idle state.
BTeq1:	:SameState;

; DLE
BTeq2:	L← RDLE1#, :ChangeState;	Change state, wait for next data byte


; "Received first DLE" state.
; If new byte is:
;	STX	enter Receive Data state
;	other	post line control error in LCB and restart receiver

RDLE1:	L← 3, BUS, :BTInit;		Supply initial dispatch to BTest
BTne3:	T← STX, :BTest;			Is it an STX?
BTne4:	L← 3, :RPost;			No, post protocol error

; STX -- enter Receive Data State
BTeq4:	L← RData#, :ChangeState;	Change state, wait for next data byte

; BiSync Receiver FSM (cont'd)

; "Receive Data" state -- main transfer loop.
; Unless byte is DLE, store it in the buffer and update pointer and count.
RData:	T← EIADAT;			Received data byte
	L← DLE-T;			Is it a DLE?
	T← 4, SH=0;
RData0:	MAR← L← LCB+T, BUS=0, :~RDDLE;	[~RDDLE, RDDLE] Reference LCB word 4
~RDDLE:	MTEMP← L, :OkLCB3;		[OkLCB3, NoLCB3] MTEMP← LCB+4
OkLCB3:	T← 77777;
	L← MD AND T, T← MD;		T← LCB word 4 (count), test count=0
	L← MD, SH=0;			L← LCB word 5 (address)
	EIATMP← L, :~RDOvf;		[~RDOvf, RDOvf] EIATMP← address

; Count wasn't zero, so buffer didn't overflow
~RDOvf:	L← 77777+T;			Decrement count and flip bit 0
	MAR← MTEMP;			Reference LCB word 4
	T← EIATMP+1, SH<0;		T← address+1, which byte?
	MD← M, L← T, TASK, :RRight;	[RRight, RLeft] Word 4 ← updated count

; Left byte is next.
RLeft:	MD← EIATMP;			Word 5 ← address (unchanged)

	L← EIADAT;			The received data byte
	MAR← EIATMP;			Reference data word in buffer
	MTEMP← L LCY 8;			Swap data into left byte
	TASK, :RData1;			Go store in buffer

; Right byte is next.
RRight:	MD← M;				Word 5 ← address+1

	MAR← EIATMP;			Reference data word in buffer
	T← EIADAT;			The received data byte
	L← MD OR T;			Merge with existing left byte
	MAR← EIATMP;			Store the word back in buffer
	MTEMP← L, TASK;
RData1:	MD← MTEMP;			Complete the store

; Update CRC and do not change state.
NoLCB3:	L← 0, :UpdateCRC;		UpdateCRC return index 0
UCRet0:	:SameState;

; Here if buffer overflowed.
; Assume that the reason for buffer overflow is that the receiver has
; lost character sync, causing it to miss the end-of-packet (DLE ETX).
; Post LCB completion with overflow error, then restart receiver.
RDOvf:	L← 4, :RPost;

; Here if DLE received.
RDDLE:	L← RDLE2#, :ChangeState;	[ChangeState] wait for next data byte

; BiSync Receiver FSM (cont'd)

; "DLE during data" state.
; If byte after DLE is:
;	DLE	put one DLE in the buffer and treat it as data
;	SYN	ignore
;	ETX	include in CRC and await ending CRC bytes
RDLE2:	L← 5, BUS, :BTInit;		Supply initial dispatch to BTest
BTne5:	T← DLE, :BTest;			Is it a DLE?
BTne6:	T← SYN, :BTest;			No, is it a SYN?
BTne7:	T← ETX, :BTest;			No, is it an ETX?

; Not any of the legal successors to DLE.  Post LCB completion with error.
BTne10:	L← 5, :RPost;

; DLE DLE -- treat as single DLE data byte
BTeq6:	MAR← LTEPTR+1;			Change state back to "Receive Data"
	TASK;
	MD← RData#;

	T←4, :RData0;			Go process DLE as normal data byte

; DLE SYN -- ignore
BTeq7:	L← RData#, :ChangeState;

; DLE ETX -- include ETX in CRC and await CRC bytes
BTeq10:	L← 1, :UpdateCRC;		UpdateCRC return index 1
UCRet1:	L← RETX1#, :ChangeState;


; First "Received ETX" state.
; Data byte is first CRC byte -- include in CRC and await second.
RETX1:	L← 2, :UpdateCRC;		UpdateCRC return index 2
UCRet2:	L← RETX2#, :ChangeState;


; Second "Received ETX" state.
; Data byte is second CRC byte -- include in CRC.
; Then check computed CRC and post LCB completion appropriately.
RETX2:	L← 3, :UpdateCRC;		UpdateCRC return index 3
UCRet3:	L← EIADAT, BUS=0;		Test computed CRC for zero
	:CRCEr;				[CRCEr, RPost]

; CRC error, post error code.
CRCEr:	L← 6, :RPost;

; Here to post LCB completion.  L has post code.
RPost:	T← 1, :EIAPost;			EIAPost return index 1

; Restart receiver searching for SYN.
EPRet1:	T← 100;				RR (Restart Receiver)
	L← 0, :EIAReset;		EIAReset return index 0
EResR0:	L← RIdle#, :ChangeState;	Revert to idle state

; **** BiSync Transmitter finite state machine ****

; LCB format:
; word	0 to 3	standard
;	4	bit 0:  0 if left byte is next, 1 if right
;		bits 1-15:  remaining byte count
;	5	address of word containing next byte
;	6	partial CRC (must be zeroed initially by software)

!1, 2, OkLCB4, NoLCB4;
!1, 2, ~TDone, TDone;
!1, 2, TRight, TLeft;
!1, 2, TCRC1, TCRC2;


; "Idle" state and states for sending initial SYN SYN SYN DLE STX.
; When awoken, start sending a bunch of SYNs (3 of them).
; If we are awoken from the Idle state with no LCB, just ignore the wakeup
; and stay in the same state.

TIdle:	T← LCB, BUS=0;			Is there an LCB?
	:OkLCB4;			[OkLCB4, NoLCB4]

; There is an LCB.  Zero the microcode status, which may have had an
; error posted due to Fill Character Transmitted hardware status.
OkLCB4:	MAR← 2+T;			Reference LCB word 2
	L← TSYN1#;
	MD← 0, :SndSYN;			Send SYN and enter new state

; Here if awoken from Idle state with no LCB.
NoLCB4:	:SameState;			Just stay in Idle state


; Send some more SYNs
TSYN1:	L← TSYN2#, :SndSYN;		Send second SYN
TSYN2:	L← TSYN3#, :SndSYN;		Send third SYN


; Finished sending a bunch of SYNs.  Now send DLE STX.
TSYN3:	L← TDLE1#, :SndDLE;		Send DLE and enter new state


TDLE1:	T← STX;				Send STX and enter new state
	L← TData#, :SndChg;

; BiSync Transmitter FSM (cont'd)

; "Transmit Data" state -- main transfer loop.
TData:	T← 4;
	MAR← L← LCB+T;			Reference LCB word 4
	MTEMP← L;			MTEMP← LCB+4
	T← 77777;
	L← MD AND T, T← MD;		T← LCB word 4 (count), test count=0
	L← MD, SH=0;			L← LCB word 5 (address)
	EIATMP← L, :~TDone;		[~TDone, TDone] EIATMP← address

; Count wasn't zero, send another data byte.
~TDone:	L← 77777+T;			Decrement count and flip bit 0
	MAR← MTEMP;			Reference LCB word 4
	T← EIATMP+1, SH<0;		T← address+1, which byte?
	MD← M, L← T, TASK, :TRight;	[TRight, TLeft] Word 4 ← updated count

; Left byte is next.
TLeft:	MD← EIATMP;			Word 5 ← address (unchanged)

	MAR← EIATMP;			Reference data word in buffer
	T← 177400;			Extract left byte
	L← MD AND T;
	MTEMP← L LCY 8, T← 0+1;		Swap to right, T← 1 (EIASend index)
	L← MTEMP, :TData1;		L← data byte, go send

; Right byte is next.
TRight:	MD← M;				Word 5 ← address+1

	MAR← EIATMP;			Reference data word in buffer
	T← 377;				Extract right byte
	L← MD AND T;
	T← 1;				EIASend return index 1

; Now send data byte.  L = data byte, T = 1.
TData1:	EIADAT← L, :EIASend;		EIADAT← data byte, go send it

; Test whether it is a DLE.
ESRet1:	L← 11, BUS, :BTInit;		Supply initial dispatch to BTest
BTne11:	T← DLE, :BTest;			Is it a DLE?

; Not a DLE.  Update CRC and do not change state.
BTne12:	L← 4, :UpdateCRC;		UpdateCRC return index 4
UCRet4:	:SameState;

; Transmitted a DLE.  Update CRC with this one, then send another
; DLE but do not include that one in the CRC.
BTeq12:	L← 5, :UpdateCRC;		UpdateCRC return index 5
UCRet5:	L← TDLE2#, :ChangeState;

; Send second DLE and change state back to "Transmit Data".
TDLE2:	L← TData#, :SndDLE;

; BiSync Transmitter FSM (cont'd)

; Here when all data has been transmitted.
; Now send DLE.
TDone:	L← TDLE3#, :SndDLE;


; Now send ETX and include it in the CRC.
TDLE3:	L← ETX;				Set up ETX byte for UpdateCRC
	EIADAT← L;
	L← 6, :UpdateCRC;		UpdateCRC return index 6
UCRet6:	T← ETX;				Send ETX and change state
	L← TETX1#, :SndChg;


; Send the CRC, low-order byte first, then high.
TETX1:	L← 377, :TCRC;			Mask for low byte
TETX2:	L← 177400, :TCRC;		Mask for high byte

; Common code for the TETX1 and TETX2 states
TCRC:	T← 6;				Reference LCB word 6 = CRC
	MAR← LCB+T;
	T← M, SH<0;			Which half?
	L← T← MD . T, :TCRC1;		[TCRC1, TCRC2] Mask the byte

; Here to send the low-order byte.
TCRC1:	L← TETX2#, :SndChg;		Send byte, change state

; Here to send the high-order byte.
TCRC2:	MTEMP← L LCY 8;			Swap into right byte
	T← MTEMP;
	L← TEnd#, :SndChg;		Send byte, change state


; Here when the last CRC byte has been sent.
; Post LCB completion, send pad character, and revert to idle state.
; Alas, there is no way to turn the transmitter off!
; The software must initialize the Transmitter Fill Register so that
; the USRT transmits all ones when no data is supplied.
; The microcode at TIdle must be prepared to ignore wakeups that 
; occur when there is no new LCB set up.
TEnd:	L← T← 0, :EIAPost;		Return index 0, post code 0
EPRet0:	T← 377;				Send all-ones pad character
	L← TIdle#, :SndChg;		Change to Idle state



; Here to send DLE and change to state given in L.
SndDLE:	T← DLE, :SndChg;

; Here to send SYN and change to state given in L.
SndSYN:	T← SYN, :SndChg;

; Here to send data byte in T and change to state given in L.
SndChg:	EIATMP← L, L← T;		Save new state
	T← 0, :EIASend;			EIASend return index 0
ESRet0:	L← EIATMP, :ChangeState;	Restore state, change to it