; 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