; CommProc2.mu -- Microcode for controlling the Alto Communication Processor.
;		This file contains the character-at-a-time and uninterpreted
;		block software interfaces and the interval timer handler.

;	Last modified September 23, 1978  3:54 PM


; **** Character-at-a-time Receiver ****

; LCB format:
; word	0 to 3	standard.  ***Next LCB link must point to itself***
;	4	begin pointer (first word of buffer)
;	5	end pointer (begin+length -- first word beyond end)
;	6	read pointer (next word to read)
;	7	write pointer (next word to write)
; Ring buffer conventions:
;	Empty buffer is represented by write pointer = read pointer.
;	Full buffer is represented by write pointer +1 = read pointer
;	(modulo wraparound).
; These conventions are the ones used in the Bcpl Ring Buffer package
; (word version).

!1, 2, ~RWrap, RWrap;
!1, 2, ~RBOvf, RBOvf;

RCAAT:	T← LCB;
	MAR← 6+T;			Reference LCB word 6
	NOP;
	L← MD;				L← read ptr (word 6)
	T← MD;				T← write ptr (word 7)
	EIATMP← L, MAR← T;		Reference word pointed to by write ptr
	L← 1+T;				L← write ptr +1
	MD← EIADAT, TASK;		Store data byte in ring buffer
	EIADAT← L;			Save write ptr across TASK

	T← LCB;
	MAR← 5+T;			Reference LCB word 5
	L← 7+T;				Compute LCB+7
	MTEMP← L;
	T← EIADAT;			The saved write ptr +1
	L← MD-T;			Compare with end ptr (word 5)
	L← MD, SH=0;			L← begin ptr (word 4)
	T← EIATMP, :~RWrap;		[~RWrap, RWrap] T← read ptr
RWrap:	EIADAT← L;			Wrap write ptr around to beginning
~RWrap:	L← EIADAT-T;			Compare new write ptr with read ptr
	MAR← MTEMP, SH=0;		Reference LCB word 7
	TASK, :~RBOvf;			[~RBOvf, RBOvf]
~RBOvf:	MD← EIADAT, :ESRet2;		Store write ptr, go generate interrupt

; Here if ring buffer is full.  Post an overflow error.
RBOvf:	NOP;				TASK pending

	L← T← 2, :EIAPost;		Post code = 2, return index = 2
EPRet2:	:SameState;

; **** Character-at-a-time Transmitter ****

; LCB format:
; word	0 to 3	standard
;	4	begin pointer (first word of buffer)
;	5	end pointer (begin+length -- first word beyond end)
;	6	read pointer (next word to read)
;	7	write pointer (next word to write)
; Ring buffer conventions:
;	Empty buffer is represented by write pointer = read pointer.
;	Full buffer is represented by write pointer +1 = read pointer
;	(modulo wraparound).
; These conventions are the ones used in the Bcpl Ring Buffer package
; (word version).

!1, 2, ~TRBE, TRBE;
!1, 2, ~TWrap, TWrap;

TCAAT:	T← LCB;
	MAR← L← 6+T;			Reference LCB word 6
	EIATMP← L;
	T← MD;				T← read ptr (word 6)
	L← MD-T;			Compare with write ptr (word 7)
	L← T, SH=0, TASK;
	EIADAT← L, :~TRBE;		[~TRBE, TRBE] EIADAT← read ptr

; Transmit ring buffer not empty.  Update read pointer.
~TRBE:	MAR← EIATMP-1;			Reference LCB word 5
	T← EIADAT+1;			The saved read ptr +1
	L← MD-T;			Compare with end ptr (word 5)
	L← MD, SH=0;			L← begin ptr (word 4)
	MAR← EIATMP, :~TWrap;		[~TWrap, TWrap] Reference LCB word 6
~TWrap:	L← T;				Not wraparound, use old read ptr +1
TWrap:	TASK;				If at end, use begin ptr
	MD← M;				Update read ptr

; Transmit the data byte
	MAR← EIADAT;			Fetch word pointed to by old read ptr
	T← 2;				EIASend return index 2
	L← MD, :EIASend;		L← data byte, go send it

; Now generate interrupt if desired by software.
ESRet2:	MAR← LCB+1;			Reference LCB word 1
	T← NWW;				Current wakeups waiting
	L← MD OR T, TASK;		Merge in word 1 (interrupt mask)
	NWW← L, :SameState;		Stay in same state

; If ring buffer is empty, turn off Send Enable.
TRBE:	:Discard;

; **** Uninterpreted Block Transmitter ****

; 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	control command to be issued at end of block
; Bytes are fetched two per word, left byte first.

!1, 2, OkLCB5, NoLCB5;
!1, 2, TUMore, TUDone;
!1, 2, URight, ULeft;
!1, 2, TCCom, ~TCCom;


; "Idle" state.
; When awoken, see if there is an LCB, and if so start sending data.

TUIdl:	SINK← LCB, BUS=0;		Is there an LCB?
	:OkLCB5;			[OkLCB5, NoLCB5]

; Here when there is no LCB.
NoLCB5:	:Discard;			Reset Send Enable, stay in Idle state

; There is an LCB.  Begin transmission of a new frame.
OkLCB5:	L← TUBlk#, :ChangeStateGo;	Enter new state

; ChangeStateGo immediately returns control to TUBlk to transmit first byte.

; "Transmitting uninterpreted block" state.
; Transmit next data byte if there is one.
TUBlk:	T← 4;
	MAR← L← LCB+T;			Reference LCB word 4
	L← M+1;				L← LCB+5
	MTEMP← L;
	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, :TUMore;		[TUMore, TUDone] EIATMP← address

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

; Left byte is next.
ULeft:	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;			Swap to right
	L← MTEMP, :UData1;		L← data byte, go send

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

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

; Now send data byte.  L = data byte.
UData1:	T← 3, :EIASend;			EIASend return index 3
ESRet3:	:SameState;			Remain in same state

; Here when entire block has been transmitted.
; Issue control command specified in LCB, if any.
TUDone:	MAR← MTEMP+1;			Reference LCB word 6
	NOP;
	L← MD, BUS=0, TASK;		L← control command
	EIATMP← L, :TCCom;		[TCCom, ~TCCom] Bypass if zero

TCCom:	T← 177000;			Construct control register address
	T← LINE*4 OR T;			= 177300 + 4*line
	MAR← 300 OR T;
	TASK;
	MD← EIATMP;			Issue command

; Post completion and chain to next block, if any.
~TCCom:	L← 0;				L← post code 0
	T← 3, :EIAPost;			T← return index 3
EPRet3:	L← TUIdl#, :ChangeStateGo;	Enter idle state immediately

; *** Interval Timer Task ***

; Data structure (shared with CommProcTask):
; Words relative to LINTAB (recall that LINTAB is even)
;	-4	interrupt bit mask, used when timer expires
;	-3	time interval (units of 625 microseconds)
;	-2	time counter (maintained by microcode)

!1, 2, ITCont, ITDone;

IntTimerTask:
	T← LINTAB;
	MAR← L← -2+T;			Fetch current counter value
	MTEMP← L;			MTEMP← LINTAB-2
	L← MD-1, BUS=0;			Decrement and test count
	:ITCont;			[ITCont, ITDone]

; Timer has not expired, continue counting.
ITCont:	MAR← MTEMP;			Store decremented count in memory
	TASK;
ITLast:	MD← M, :IntTimerTask;

; Here when timer expires.  Issue interrupt and reset count.
ITDone:	MAR← -4+T;
	T← NWW;
	L← MD OR T;			Merge word -4 (interrupt bit mask)
	T← MD-1;			Time interval -1
	MAR← MTEMP;			Store into word -2
	NWW← L, L← T, TASK, :ITLast;	Cause interrupt, finish store of count