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

;	Last modified September 23, 1978  3:54 PM

; 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 words at LINTAB-4 through LINTAB-2 must be set up as required by
; the Interval Timer Task, if the hardware Interval Timer is being used.
; See comments above the IntTimerTask code.

; 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 CommProc 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
$LINE*4	$R77;	Current line number in B10:13 (for addressing control regs).
;		*** Must never use LINE*4 in instruction with MAR←.
$EIADAT	$R74;	EIA data, temporary
$EIATMP	$R75;	Temporary

; Standard R-registers

$NWW	$R4;
$MTEMP	$R25;

; CommProc task-specific functions

; F1=16:  BUS[11] ← 1 iff input request;  BUS[12:15] ← line number.
; *** Must define this as a non-data F1 due to botch in Mu that
;     causes data F1's to set the BS field as well.
$COMLINE $L 016016, 000000, 000100;

; F1=14:  if input request, BUS[0:7] and BUS[8:15] ← input data (same value
;	in both bytes);  if output request, output data ← BUS[0:7].
$COMDATA $L 020014, 070014, 120100;

; 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, Discard, 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, CPIn, CPOut;
!1, 2, SameState, LIMCon0;
!3, 4, LCRet0, LCRet1, LCRet2,
	LCRetX;		Extra return reserved for external caller

$LCRetX# $3;		Constant corresponding to LCRetX offset

; *** CommProc Task ***
; Reset location of the task.
; Upon wakeup, determine line number and direction of request.

CommProcTask:
	T← 17-1, COMLINE;		T← line-1
	L← T← 17+T+1, COMLINE;		L← T← 2*line
	L← M+T;				L← 4*line
	LINE*4← L;			Save for addressing control regs
	L← 20, COMLINE;			Test in/out bit
	T← LINE*4+T+1, SH=0;		T← 6*line +1
	L← T← LINTAB+T+1, :CPIn;	[CPIn, CPOut] L← T← LINTAB + 6*line +2

; Here if input request.
; Read the data byte and store it in EIADAT.
CPIn:	LTEPTR← L, MAR← T;		Reference word 2 of entry, save ptr
	T← 377;				Data mask
	L← COMDATA AND T;		Get the data byte
	EIADAT← L, :CPCom;

; Here if output request.
CPOut:	MAR← L← 2+T;			Reference word 4 of entry, save ptr
	LTEPTR← L;

; 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.
CPCom:	L← MD;				LCB
	SINK← MD, BUS, TASK;		Dispatch on line state
	LCB← L, :Discard;		[Discard, ...]

; Here if in discard state.
; If this is an input wakeup, just discard the received character.
; If an output wakeup, turn off Send Enable.
Discard: L← 20, COMLINE;		Test in/out bit (1 = input)
	T← 100, SH=0;			Send Enable bit
	L← 0, :SameState;		[SameState, LIMCon0] LIMCon0 return 0
LCRet0:	BLOCK, :SameS1;


; *** Task 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
	BLOCK;				Clear wakeup
	TASK;
	MD← M, :CommProcTask;

; Here when done processing and don't want to change state.

SameState:
	BLOCK;
SameS1:	TASK;
	:CommProcTask;

; 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, :Discard;		[Discard, ...]

; *** EIA subroutines ***

; Subroutine to post LCB completion.
; T has return index, L has post code
; Posts the supplied code and current hardware status and 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, EPIn, EPOut;
!1, 2, EPErr, ~EPErr;

EIAPost:
	MTEMP← L, L← T;			MTEMP← post code, L← return index
	SINK← LCB, BUS=0;		Test for no LCB present
	EIATMP← L, :OkLCB1;		[OkLCB1, NoLCB1] EIATMP← return index
OkLCB1:	T← 177000;			Construct status register address
	T← 300 OR T;			T← 177300
	T← LINE*4+T+1;			T← 177301 + 4*line
	L← 20, COMLINE;			Test in/out bit
	MAR← 2+T, T← 2;			MAR← 177303 + 4*line (status for line)
	L← LCB+T, SH=0;			LCB← pointer to LCB word 2
	LCB← L, :EPIn;			[EPIn, EPOut] Select error mask:
EPOut:	T← 10, :EPCom1;			Output: Overrun(T)
EPIn:	T← 7;				Input: Overrun(R), Parity, Framing
EPCom1:	L← MD AND T, T← MD;		L← errors, T← entire status word
	L← T, T← 100000, SH=0;		L← status
	MAR← LCB+1, :EPErr;		[EPErr, ~EPErr] Reference LCB word 3
~EPErr:	T← MTEMP OR T, :EPCom2;		No errors, set to post supplied code
EPErr:	T← 1 OR T;			Errors, set to post code 1
EPCom2:	MD← M, L← T, TASK;		Word 3 ← hardware status
	MD← M;				Word 2 ← post code

; Cause interrupt as specified in LCB's mask, then chain to next LCB
	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 LCY 8, L← T;		MTEMP← data byte, L← return index
	SINK← M, BUS, TASK;		Dispatch on return index
	COMDATA← MTEMP, :ESRet0;	[ESRet0, ...] BUS[0:7] ← data byte

; 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



; Subroutines to set or reset bits in LIM control register.
; L has return index, T has mask of bits to set or reset (B[4:15] only).
; Clobbers EIATMP.

; LCRetX predefinition appears earlier.
!1, 2, LIMC0, LIMC1;

; Call here to set masked bit(s) to one:
LIMCon1: T← 100000 OR T;		Remember setting bits to one

; Call here to set masked bit(s) to zero:
LIMCon0: MTEMP← L, L← T;		MTEMP← return index, L← mask
	T← 177000;			Construct control register address
	T← LINE*4 OR T;			= 177300 + 4*line
	MAR← T← 300 OR T;		Fetch current value
	T← M, L← T, SH<0;		T← mask, L← address, test 0/1 call
	EIATMP← L, :LIMC0;		[LIMC0, LIMC1] EIATMP← address
LIMC0:	L← MD AND NOT T, :LIMCx;	Setting masked bit(s) to zero
LIMC1:	L← MD OR T;			Setting masked bit(s) to one
LIMCx:	MAR← EIATMP;			Store updated control word
	SINK← MTEMP, BUS, TASK;		Dispatch on return index
	MD← M, :LCRet0;			[LCRet0, ...]

; 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← 200;				Turn Receive Enable off
	L← 1, :LIMCon0;			LIMCon0 return index 1
LCRet1:	T← 200;				Now turn Receive Enable back on
	L← 2, :LIMCon1;			LIMCon1 return index 2
LCRet2:	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.  Begin transmission of a new frame.
OkLCB4:	L← TSYN1#, :SndSYN;		Send SYN and enter new state

; Here if awoken from Idle state with no LCB.
NoLCB4:	:Discard;			Reset Send Enable, 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.
; When the next wakeup occurs, if another LCB hasn't been set up yet,
; the code at TIdle will reset Send Enable to turn off the transmitter.
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