; 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