; 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