; 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