*----------------------------------------------------------------------------
Title[PilotDual3Ether.Mc...March 31, 1988 7:26:35 pm PST...Willie-Sue];
* Ethernet microcode for Pilot, handles "multicast" on input (Swinehart)
*----------------------------------------------------------------------------

*----------------------------------------------------------------------------
* Data structures
*----------------------------------------------------------------------------

* This microcode supports a "multicast" input regimen. The client can specify from
* 0 to 255 host numbers, using a bit vector. Packets directed to any of the specified
* hosts will be accepted by the microcode. It is up to the client to sort out the results.
* The feature is enabled by setting the ICSB.host field to -1. Then the ICSB.hosts vector
* will be used to decide which packets to accept. Note that the client can disable the
* acceptance of broadcast packets. Setting ICSB.host to a valid host returns to the
* conventional single-host mode. The ICSB.hosts vector should have the proper value
* before the ICSB.host field is set to -1.
* ICSBD3: LONG POINTER TO InputControllerStatusBlock = LOOPHOLE[177700B];
* InputControllerStatusBlock: TYPE = MACHINE DEPENDENT RECORD [
MC[ICSBD3.next, 177700];  * next: POINTER TO IOControlBlock,
MC[ICSBD3.interruptMask, 177701]; * interruptMask: WORD,
MC[ICSBD3.host, 177702];  * host: CARDINAL, -- if >=0, conventional one-host style
MC[ICSBD3.missed, 177703]; * missed: CARDINAL,
    * skipOverOCSB: ARRAY [4..30B) OF CARDINAL,
MC[ICSBD3.hosts, 177730];  * hosts: PACKED ARRAY [0..256) OF BOOL,
    * ... ] -- Other stuff microcode doesn't use
* OCSBD3: LONG POINTER TO OutputControllerStatusBlock = LOOPHOLE[177710B];
* OutputControllerStatusBlock: TYPE = MACHINE DEPENDENT RECORD [
MC[OCSBD3.next, 177710];  * next: POINTER TO IOControlBlock,
MC[OCSBD3.interruptMask, 177711]; * interruptMask: WORD,
MC[OCSBD3.minPacketSpacing, 177712]; * minPacketSpacing: CARDINAL, -- 32us units
    * ... ] -- Other stuff microcode doesn't use

* IOControlBlock: TYPE = MACHINE DEPENDENT RECORD [
MC[IOCBD3.next, 0];  * next: POINTER TO IOControlBlock,
MC[IOCBD3.completion, 1];  * completion: CompletionStatus,
MC[IOCBD3.wordsUsed, 2];  * wordsUsed: CARDINAL,
MC[IOCBD3.load, 3];  * load: CARDINAL,
MC[IOCBD3.length, 4];  * length: CARDINAL,
MC[IOCBD3.buffer, 5];  * buffer: LONG POINTER]

* CompletionStatus: TYPE = MACHINE DEPENDENT RECORD [
* microcode: {busy, done, bufferOverflow, loadOverflow, (377B)},
* hardware: [0..377B]]
* Hardware status for input is status word received through receive fifo;
* for output is status word read from EControl.

* Microcode completion status codes:
MC[CS.done, 400];
MC[CS.bufferOverflow, 1000];
MC[CS.loadOverflow, 1400];
BR[D3EIBR, 16]; * Dual 3 MB Ethernet Input base register
BR[D3EOBR, 17]; * Dual 3 MB Ethernet Output base register
*----------------------------------------------------------------------------
* TASK assignments
*----------------------------------------------------------------------------
TaskN[D3EIT,12];
TaskN[D3EOT,10];
*----------------------------------------------------------------------------
* TIOA assignments
*----------------------------------------------------------------------------
Device[D3EData, 25];  * 10 Megabit Ethernet data input/output
Device[D3EControl, 26]; * NS Ethernet control reg [Tx,Rx,Test]
*----------------------------------------------------------------------------
* Register assignments
*----------------------------------------------------------------------------
Set[!D3EIRegs, !FInRegion];
Set[!D3EORegs, !LTRegion];
SetRMRegion[D3EIRegs];
RVN[D3EICB]; * Pointer to IOCB being worked on
RVN[D3EIPtr]; * Input main loop pointer/count
RVN[D3EITemp1]; * Input temporaries
RVN[D3EITemp2];
SetRMRegion[D3EORegs]; * Used by output task -- first 3 must parallel EIRegs
RVN[D3EOCB]; * Pointer to IOCB being worked on
RVN[D3EOPtr]; * Output main loop pointer/count
RVN[D3EOTemp1]; * Output temporary
RVN[D3EOTemp2]; * Output temporary
RVN[D3EOTime]; * Time at end of last packet successfully transmitted
RVN[D3RNum]; * State for random number generator
RVN[D3RConst]; * Constant (13849) for random number generator
RVRel[D3ExCB, And[IP[D3EICB], 17]];
RVRel[D3ExPtr, And[IP[D3EIPtr], 17]];
RVRel[D3ExTemp1, And[IP[D3EITemp1], 17]];
*----------------------------------------------------------------------------
* Emulator Task -- ResetEther instruction.
* Branched to from Mesa emulator to reset Ethernet hardware and tasks.
*----------------------------------------------------------------------------
Set[XTask, IP[EMU]];  * Emulator mode
TopLevel;
DontKnowRBase;

D3ResetEther:
 T← D3EControl;
 TIOA← T;
 TaskingOff;   * Smash the hardware off
 T← TurnOffRx, Call[OutputGetsT];
 T← TurnOffTx, Call[OutputGetsT];
 Call[D3EITInitPC];  * Reset the tasks
 LdTPC← T, Wakeup[D3EIT];
 Call[D3EOTInitPC];
 LdTPC← T, Wakeup[D3EOT];
 T← A0, TaskingOn;
 TIOA← T, IFUJump[0];
*----------------------------------------------------------------------------
* Initialization code for Input Task
*----------------------------------------------------------------------------
Set[XTask, IP[D3EIT]];

Subroutine;
D3EITInitPC: T← D3EIT, CoReturn;
TopLevel;

 T← D3EControl;
 TIOA← T, Block, Branch[D3EIIdle]; * full TIOA for initialization
*----------------------------------------------------------------------------
* Initialization code for Output Task
*----------------------------------------------------------------------------
Set[XTask, IP[D3EOT]];

Subroutine;
D3EOTInitPC:
T← D3EOT, CoReturn;
TopLevel;
 RBase← RBase[D3EORegs];
 T← 33000C;   * Init random number generator
 D3RConst← T+(31C);  * Constant (33031B = 13849)
 T← D3EControl;
 TIOA← T, Block, Branch[D3EOIdle]; * full TIOA for initialization
*----------------------------------------------------------------------------
* Input Task
*----------------------------------------------------------------------------

Set[XTask, IP[D3EIT]];  * Non-emulator mode

* Idle state of the Ethernet input task.
* Wake up here when first word of a new packet arrives.
D3EIIdle: MemBase← IOBR;
 T← ICSBD3.next;
 Fetch← T, T← ICSBD3.host;
 PD← MD, RBase← RBase[D3EIRegs]; * Is there an IOCB?
 D3EICB← MD, TIOA[D3EData],
  Branch[D3EINoCB, ALU=0]; * Branch if no IOCB
 Fetch← T, D3EITemp2← T← Input, * Fetch ICSBD3.host, save first word of packet
  Branch[D3EIPLZ, IOAtten]; * Branch if zero-length packet

* Address filtering.
 PD← NOT MD, TIOA[D3EControl]; * (Done upside-down for placement)
 T← A0, Branch[D3MultiTest, ALU>=0]; * Use multicast method if ICSBD3.host is negative

* Traditional method: test for single host, broadcast, or promiscuous.
 T← RCY[T, D3EITemp2, 10], Call[D3EAdrCk]; * T← destination host, test for zero
 PD← T-MD, Call[D3EAdrCk];  * Test for destination host = me
 PD← MD, Call[D3EAdrCk];  * Test for me = 0 (promiscuous)

* Packet not accepted by filter.
* Tell hardware to ignore the rest of this packet.
D3EIIgn: T← WaitForBOP;   * Wakeup at start of next packet
D3EILast: Output← T, Block, Branch[D3EIIdle];

Subroutine;
D3EAdrCk: Branch[D3Accept, ALU=0];
 Return;
TopLevel;

* Multicast method: use destination as an index into the ICSB.hosts array.
D3MultiTest:
 T← RSH[D3EITemp2, 14];  * T ← Word offset
 T← T+(ICSBD3.hosts);
 Fetch← T, D3EITemp1← D3EITemp2;
 T← MD, D3EIPtr← ShC;
 D3EITemp1← T, ShC← D3EITemp1; * Shift count ← low 4 bits of destination host
     * (D3EITemp1[4:7]); R/T select bits get garbage
 PD← ShiftNoMask[D3EITemp1]; * Left-justify selected bit (from D3EITemp1 or T)
 ShC← D3EIPtr, Branch[D3EIIgn, ALU>=0]; * Reject if selected bit is zero
 Nop;

* Packet accepted by filter. Set up buffer pointer and count.
D3Accept: D3EIPtr← A0, Call[D3EIOCBSetup];
 T← D3EITemp2, TIOA[D3EData], Block; * Recover first data word, await next

* Input main loop.
* Each iteration stores the word previously input from the interface
* and inputs the next word. This facilitates end-of-packet handling.
* EIPtr has negative of number of words remaining in the buffer.
* IOAtten branches if the data word being read is the end-of-packet status.
 D3EIPtr← (Store← D3EIPtr)+1, DBuf← T, Branch[D3EIEnd, IOAtten];
 T← Input, Block, Branch[.-1, ALU#0];

* Get here when buffer is exactly full (EIPtr=0).
* We know that the word in T is not EOP status since we would have taken
* the IOAtten branch if it were. If IOAtten is now true, then the word in T
* is the CRC (which we must discard) and the next input is the status word.
* If IOAtten is false, the buffer has overflowed.
 D3EIPtr← D3EITemp1;   * Set packet length = buffer length
 T← Input, Branch[D3EIEnd1, IOAtten]; * Branch if end-of-packet
 T← CS.bufferOverflow, Branch[D3EIPost];


* Normal end-of-packet exit from main loop.
* One extra word (the CRC) has been stored in the buffer and must not be
* included in the count. The next input is the status word.
D3EIEnd: T← (D3EITemp1)-1;   * Initial buffer length
 D3EIPtr← (D3EIPtr)+T;  * Actual packet length
 T← Input;   * Read status word
D3EIEnd1: T← T OR (CS.done);  * Done completion status

* Post input completion status now in T.
D3EIPost: D3EITemp1← ICSBD3.next;
 Call[D3EPost];
 Block, Branch[D3EIIdle];  * Await next packet

* Here if packet length zero, i.e., first word input was end-of-packet status.
* This should happen only if receiver-detected collision reporting is turned
* on, since runt packets are ordinarily filtered out by the phase decoder.
D3EIPLZ: D3EIPtr← A0, Branch[D3EIEnd1]; * Length← 0, post done status

* Discard packet because IOCB queue is empty.
D3EINoCB: T← T+1, TIOA[D3EControl];  * Increment ICSBD3.missed
 Fetch← T;
 D3EITemp1← MD+1;
 Store← T, DBuf← D3EITemp1, Branch[D3EIIgn]; * Ignore rest of packet
*----------------------------------------------------------------------------
* Output Task
*----------------------------------------------------------------------------

Set[XTask, IP[D3EOT]];

* Idle state of the Ethernet output task. TIOA=EControl.
* Wake up here at end of previous packet or when poked by software.
D3EOIdle: MemBase← IOBR;
 T← OCSBD3.next;   * Fetch OCSB.next
 T← (Fetch← T)+(Sub[OCSBD3.minPacketSpacing!, OCSBD3.next!]C);
 RBase← RBase[D3EORegs];
 Fetch← T, D3EOCB← PD← MD;  * Fetch OCSBD3.minPacketSpacing
 T← (D3EOCB)+(IOCBD3.load),
  Branch[D3EONoCB, ALU=0]; * Go shut off transmitter if no IOCB

* Test and update load
 Fetch← T, D3EOTemp2← MD;  * Fetch IOCB.load
 D3EOTemp1← MD+MD+1;  * Load lsh 1 +1
 Store← T, DBuf← D3EOTemp1, D3EOTemp1← MD, * Store new load, save current
  Call[D3Random];  * T← random number
 T← RSH[T, 10];   * Use leftmost 8 bits

* Test for load overflow (old load has sign bit set).
* Then mask countdown with old load and test for nonzero result.
* Note that the countdown clock ticks every 16 us, but we want to count in
* units of 32 us, so we double the count before using it.
 T← ((D3EOTemp1) AND T) LSH 1, Branch[D3ELodOv, R<0];
 PD← T-T, Branch[D3EWaitC, ALU#0]; * Carry← 1; branch if wait required

* On first transmission attempt, enforce minimum inter-packet spacing.
* Note: RTClock counts 32-usec intervals. D3EOTemp2 = OCSB.minPacketSpacing
 T← D3EOTime, RBase← RBase[RTClock]; * Time last transmission ended
 T← (RTClock)-T, RBase← RBase[D3EORegs]; * Inter-packet spacing
 T← ((D3EOTemp2)-T-1) LSH 1; * Compare with min permitted
D3EWaitC: D3EOTemp1← T-1, Branch[D3EOGo, Carry']; * Start now if exceeded min

* Must wait before retransmitting. EOTemp1 has wait time -1 in 16-usec units.
* CountDown turns off data wakeups and requests a wakeup at next
* tick of countdown clock.
D3EWait: T← CountDown;
 D3EOTemp1← (D3EOTemp1)-1, Output← T, Block, Branch[.-1, R>=0];

* Now time to transmit packet. Set up buffer pointer and count.
D3EOGo: D3EOPtr← T-T-1, Call[D3EIOCBSetup];
 D3EOPtr← (Fetch← D3EOPtr)+1; * Pre-fetch the first word
 TIOA[D3EData], Branch[D3EOEnd, ALU=0];

* Output main loop.
* Data transfer is pipelined: each iteration outputs a data word
* fetched in the previous iteration and starts the fetch of the data for
* the next iteration. This way, cache misses generally occur while our
* task is blocked, and lower-priority tasks are permitted to run while
* the cache is being loaded.
* EOPtr is the negative of the number of words remaining to be fetched.
* IOAtten branches if a collision, data late, or Fifo PE has aborted output.
 D3EOPtr← (Fetch← D3EOPtr)+1, T← MD, Branch[D3EOAbrt, IOAtten];
 Output← T, Block, Branch[.-1, ALU#0];

* Next-to-last word has been output and last word has been fetched
D3EOEnd: T← D3EOTemp1, Output← MD;  * Output last word
 D3EOPtr← T, TIOA[D3EControl]; * Set words used = buffer length
 T← SendEOP;   * Declare end of packet
 Output← T, Block;

* Wake up here only when packet completely sent or collision has occurred.
 D3EOTemp1← Input, Branch[D3EOAbr1, IOAtten]; * Branch if aborted, get status
 RBase← RBase[RTClock];  * Remember current time
 T← RTClock, RBase← RBase[D3EORegs];
 D3EOTime← T;
D3EODone: T← (D3EOTemp1) AND (377C); * Just the status bits
 T← T OR (CS.done);  * Done completion status

* Post ending status and reset interface for next packet.
D3EOPost: D3EOTemp1← OCSBD3.next;
 Call[D3EPost];   * Post status in T
 T← TurnOffTx, Branch[D3EORest]; * Turn transmitter off and on again

* Here if collision or other error occurred.
* If a collision, just try again. If not, post output done.
D3EOAbrt: T← (D3EOTemp1)-1;   * Initial buffer length
 D3EOPtr← (D3EOPtr)+T, TIOA[D3EControl]; * Words actually sent
 D3EOTemp1← Input;   * Read status

* If not a collision, post output done with whatever status bits we got.
D3EOAbr1: PD← (D3EOTemp1) AND (TxCollision); * Collision?
 T← TurnOffTx, Branch[D3EODone, ALU=0]; * Branch if not
D3EORest: Output← T;   * Turn off to reset collision bit
 T← TurnOnTx, Branch[D3EOLast]; * Turn on and try again

* Load overflow, post error status
D3ELodOv: T← CS.loadOverflow, Branch[D3EOPost]; * Load overflow post code

* Shut off hardware because IOCB queue is empty.
D3EONoCB: T← TurnOffTx, Branch[D3EOLast];
D3EOLast: Output← T, Block, Branch[D3EOIdle];
*----------------------------------------------------------------------------
* Task-independent Subroutines
*----------------------------------------------------------------------------

*----------------------------------------------------------------------------
D3EIOCBSetup: * Set up IOCB pointers and counts
* Enter: ExCB = IOCBD3 being worked on
* ExPtr = 0 for input, -1 for output
* MemBase = IOBR
* Exit: ExPtr = negative word count
* MemBase = ExBR
* ExBR = base register properly set up for indexing by ExPtr
* Points the base register beyond the end of the buffer -2^16, and
* addresses the buffer with a negative index in ExPtr.
* Clobbers ExTemp1, T
*----------------------------------------------------------------------------
Subroutine;

 T← (D3ExCB)+(IOCBD3.length);
 T← (Fetch← T)+1;  * Fetch IOCBD3.length
 T← (Fetch← T)+1, D3ExTemp1← MD; * Fetch low buffer pointer
 Fetch← T, T← MD,  * Fetch high buffer pointer
  D3ExPtr, Branch[.+2, R<0]; * Which base register?
 T← T+(D3ExTemp1), MemBase← D3EIBR, Branch[.+2]; * Low pointer + length
 T← T+(D3ExTemp1), MemBase← D3EOBR; * Low pointer + length
 T← MD, BRLo← T;
 T← T-1, XorSavedCarry;  * Adjust high for negative indexing
 T← D3ExTemp1, BRHi← T;
 D3ExPtr← (0S)-T, Return;  * ExPtr← negative count
*----------------------------------------------------------------------------
D3EPost:  * Post command completion.
* Enter: T = completion status word
* ExPtr = actual packet length
* ExCB = IOCB being worked on
* ExTemp1 = ICSB or OCSB pointer (IOBR relative)
* Exit: MemBase = IOBR
* Clobbers T, ExCB, ExTemp1, RBase
*----------------------------------------------------------------------------
Subroutine;

 MemBase← IOBR;
 D3ExCB← (Fetch← D3ExCB)+1;  * Fetch IOCBD3.next
 T← (Store← D3ExCB)+1, DBuf← T; * Store IOCB.completion
 Store← T, DBuf← D3ExPtr, T← MD; * Store IOCB.wordsUsed
 D3ExTemp1← (Store← D3ExTemp1)+1, DBuf← T; * Store xCSB.next
 Fetch← D3ExTemp1;   * Fetch xCSBD3.interruptMask
 RBase← RBase[NWW];  * Initiate interrupts
 NWW← (NWW) OR MD, Reschedule, Return;

*----------------------------------------------------------------------------
D3Random:  * Generate random number and return it in T.
* Must have RBase[D3RNum] (=D3EORegs).
* Uses algorithm:
*  R← (2^11 + 2^2 + 2^0)* R + 13849
*----------------------------------------------------------------------------
Subroutine;
KnowRBase[D3RNum];

 T← LSH[D3RNum, 11];  * T← 2^9 * R
 T← T+(D3RNum);   * (2^9 + 2^0)* R
 T← LSH[T, 2];   * (2^11 + 2^2)* R
 T← T+(D3RNum);   * (2^11 + 2^2 + 2^0)* R
 T← D3RNum← T+(D3RConst), Return; * +13849

TopLevel;