{db
{
file: <CoPilot>DLion>MBusB0.mc
Created by E. Fiala 19-Nov-86 17:48:28 by splitting BlockB0.mc.
Edited:

BJackson 22-Oct-87  3:29:43 use "map.rd" instead of "30".
  Trow	13-Oct-87 15:22:50 Remove all code for Daybreak.  Reverse targets 1 and 2 of XwdDisp.
  Fiala  9-Jul-86 14:10:12 Added WriteMBus and ReadMBus opcodes to CedarB0.mc thereby
	exceeding the maximum size handled by Mass; split off this file.
  Fiala 30-Jul-86 10:25:30 Removed the boolean return value for WriteMBus & ReadMBus;
	instead, trap on MBus timeout.  Coded WritePCBus and ReadPCBus variations.
  Fiala  4-Aug-86 17:08:43 Fixed bug in WritePCBus.
  Fiala 22-Aug-86 11:34:34 Bummed the Read/Write MBus/PCBus opcodes down close to the
  	minimums possible.  Three Noops were required in the main loop to make the code
	work for unknown reasons.  Added the byteInterval parameter to the four opcodes
	to permit separation of RGB arrays into individual color separations during
	scanning.
  Fiala 22-Aug-86 16:56:28 Removed apparently extraneous FloatNop at WPCA and increased
  	the number of Noops after the read or write of the 2nd data byte by 1 cycle;
	did MDR ← T or FloatResult rather than loading T first for ReadPCBus and ReadMBus.
  Fiala	10-Oct-86 15:07:33 Added 3 Noops in the ack wait code for both cases of the 4
  	MBus/PCBus opcodes.
  Fiala 19-Nov-86 17:54:31 Cosmetic edits; split away from BlockB0.mc. 

Copyright (C) 1986 by Xerox Corporation.  All rights reserved.

Formerly, all of this code except the MBus and PCBus opcodes was in CedarB0.mc; later it
was put into BlockB0.mc with the long block move and Checksum opcodes; finally, it was
split into a separate file.  Opcodes defined here are as follows:
	WriteMBus
	ReadMBus
	WritePCBus
	ReadPCBus

All of these are performance critical.  The *MBus opcodes work in conjunction with
procedures in XBus.mesa and XBusImpl.mesa; a diagnostic for these is in XBusImpl.mesa.

Notes:
1) For Read/Write M/PC Bus opcodes the L2 register can be used (0 = normal, 1 = last
iteration, 2 = MBaddr bank cross) to permit the "TOS ← TOS - 1, ZeroBr" and
"PC ← PC + T, CarryBr" to be overlapped with the dead cycles following the read/write
before the Ack is sampled.  Then the final mi for the iteration does
"L2Disp, FloatNop, BRANCH[$, WMA, 0B]" for WriteMBus and WritePCBus and the MAR ← at the
beginning of the next iteration does a DISP2 to unscramble the result.  This moves two
mi from the beginning of the main loop into the dead cycles before ackwait, and might
permit removing the three noops in the ackwait dead time.

2) Read M/PC Bus has a Noop immediately before the MAR ← at the end of the main loop which
might permit TOS ← TOS - 1 to be done earlier.  Also, the final ← FloatResult may count as
a FloatNop for the following iteration, in which case one FloatNop can be omitted at the
beginning of the iteration on a read.
}

SetTask[0];

{*************************************************************************
The BX and X buses (both 16 bits wide) are connected by a bidirectional driver
enabled by the condition byte and nibble' with the direction selected by fY.0;
i.e., the bidirectional driver is enabled (cycles) iff fS[0..3] = 0C, 0E, or 0F
(unused forms of immediate Byte constant).  The device is fY[0..3]..SUAddr[4..7].
The FloatNop, FloatULS, FloatUMS, FloatUMP, etc. functions cause fS[0..3] and fY[1..3]
to get proper values.  The driver direction is BX ← X, unless ← FloatResult accompanies
the function; (← FloatResult causes fY[0..0] = 1).  In addition to selecting the
BX bus device, fS[2..3], as in a regular instruction, determine whether SUaddr comes
from 0..stackP or rA..fZ; FloatUMS and FloatULS use 0..stackP for SUaddr; the fZ
field can be used (only) for a LRotn function with these, and rA is available.
FloatNop, FloatUMP, and FloatULP use fZ in the current instruction or
Ybus ← value, AltUaddr in the previous instruction to specify SUaddr[4..7]; if
AltUaddr was used in the preceding instruction, rA and fZ are unconstrained in the
current instruction.

In addition to operating the bus, each FloatXX function has a side effect on the BX bus
2 cycles later; a FloatNop causes the BX bus to become free 2 cycles later, while the
FloatUMS/ULS and FloatUMP/ULP cause the bus to be driven by high/low results 2 cycles
later.  A FloatNop is needed 2 BX cycles before each operation which drives the BX bus
from the Dandelion; I determined empirically that omitting the FloatNop resulted in
occasional bit pickups, so apparently pullups need time to charge the bus or something
like that.  When reading the MBus, the strategy is to use "FloatNop" in the 1st instruction,
"FloatUMP, Xbus ← FloatResult" in the second, and finally "T ← FloatResult" in the
last.

The four MBus/PCBus opcodes share a common implementation except that memory remapping
code differs for VM reads and writes.  In addition, each PCBus operation transfers only a
byte, so two BX bus sequences are needed to transfer the whole word.  The following
pseudo-program shows the implementation:

a Read/Write M/PC Bus: PROC[controlData: WORD, VMaddr, MBaddr: LONG POINTER,
   byteInterval, wordCount: CARDINAL] ~ {
	WriteM[3, HighHalf[MBaddr]];
	WriteM[2, controlData];
	GOTO[Enter];
LP:	IF (wordCount ← wordCount - 1) = 0 THEN RETURN[];
	IF (LowHalf[MBaddr] ← LowHalf[MBaddr] + byteInterval) carries THEN {
	  HighHalf[MBaddr] ← HighHalf[MBaddr] + 1;
	  WriteM[3, HighHalf[MBaddr]];
	  };
	IF (VMaddr ← VMaddr + 1) crosses page boundary THEN {
Enter:	  Remap[VMaddr];  --Different for VM reads and writes
	  };
	IF interrupt pending & enabled THEN service it;
	outData: WORD ← VMaddr↑;
	WriteL[3, LowHalf[MBaddr]];
	WriteL[2, outData];  --or outData lcy 8 for PCBus opcodes
	FOR I: CARDINAL IN [0..12) DO
	  IF BITAND[ReadM[2], 4] # 0 THEN {  --Multibus Ack received
	  XXX
	  };
	  ENDLOOP;
	OpcodeTrap[parameter = 1]; --MBus Timeout
	};

XXX is specific to the opcode as follows:
aWriteMBus:	XXX = GOTO[LP];

aReadMBus:	XXX = VMaddr↑ ← ReadL[2]; GOTO[LP];

aWritePCBus:	XXX = 
	    IF (LowHalf[MBaddr] ← LowHalf[MBaddr] + byteInterval) carries THEN {
	      HighHalf[MBaddr] ← HighHalf[MBaddr] + 1;
	      WriteM[3, HighHalf[MBaddr]];
	      };
	    WriteL[3, LowHalf[MBaddr]];
	    WriteL[2, outData];  --Output the odd byte
	    FOR I: CARDINAL IN [0..12) DO
	      IF BITAND[ReadM[2], 4] # 0 THEN GOTO[LP]  --MultiBus Ack received
	      ENDLOOP;
	    OpcodeTrap[parameter = 1]; --MBus Timeout


aReadPCBus:	XXX =
    	    evenByte: WORD ← BITSHIFT[ReadL[2], 8];
	    IF (LowHalf[MBaddr] + 1) carries THEN WriteM[3, HighHalf[MBaddr] + 1];
	    WriteL[3, LowHalf[MBaddr] + 1];
	    WriteL[2, garbage];
	    FOR I: CARDINAL IN [0..12) DO
	      IF BITAND[ReadM[2], 4] # 0 THEN {  --Ack received
	        VMaddr↑ ← BITOR[evenByte, BITAND[ReadL[2], 0FF]];
		GOTO[LP];
	        };
	      ENDLOOP;
	    OpcodeTrap[parameter = 1]; --MBus Timeout

All four opcodes are minimal stack, so the stack U registers can be directly
referenced rather than using STK and stackP.
  uStack2/	controlData
  uStack3/	LowHalf[VM address] ("from" on write, "to" on read)
  uStack4/	HighHalf[VM address] ("from" on write, "to" on read)
  uStack5/	LowHalf[MBus address] ("to" on write, "from" on read)
  uStack6/	HighHalf[MBus address] ("to" on write, "from" on read)
  uStack7/	byteInterval
  TOS/		wordCount
During the execution of the block transfer, registers are used as follows:
  L3/			0 (WriteMBus), 1 (ReadMBus), 2 (WritePCBus), or 3 (ReadPCBus)
  L2/			0 (normal), 1 (done), or 2 (MBaddr bank cross)
  PC/			LowHalf[MBus address]
  uStack6/		HighHalf[MBus address]
  TT	/		LowHalf[VM address]
  rhTT, uStack4/	HighHalf[VM address]
  Rx, rhRx, Q/		RM address
  T/			Word from/to VM
  rhT/			temporary, even byte on PCBus operations
  TOS/			wordCount
  L/			2
  G/			temporary
  uStack8/		byteInterval
  stackP/		usually contains 3 during the main loop for BX bus operations
uStack3, 5, and 8 contain opcode state at the beginning of the opcode but are not updated
during the loop; they must be reconstructed prior to page or write protect faults,
interrupts, or timeouts.

The opcode began with the following sequence in bank 1 (see CedarMisc.mc & CedarB1.mc):
	Xbus ← ibHigh, XDisp,					c1, opcode[364'b];
	Rx ← ib, XDisp, push, DISP4[MiscHigh],			c2;
	STK ← TOS, pop, DISP4[Misc4n],				c3, at[4, 10, MiscHigh];

	Bank ← MSBank0, L3 ← n,					c1;
	Rx ← Rx + 0FF + 1 {trap table displacement},		c2;
	uNcTrapOffset ← Rx, pop {stackP=5}, GOTOABS[B0MBus],	c3;
where n = 0 for WriteMBus, 1 for ReadMBus, 2 for WritePCBus, and 3 for ReadPCBus.

WriteMBus timing
	= 10 clicks + 7 clicks/word + 2 clicks/ackwait + 3 clicks/page cross
	= 2.5 microseconds/word.
ReadMBus timing
	= 10 clicks + 8 clicks/word + 2 clicks/ackwait + 3 clicks/page cross
	= 3.3 microseconds/word.
WritePCBus timing
	= 10 clicks + 13 clicks/word + 2 clicks/ackwait + 3 clicks/page cross
	= 4.5 microseconds/word.
ReadPCBus timing
	= 10 clicks + 15 clicks/word + 2 clicks/ackwait + 3 clicks/page cross
	= 5.7 microseconds/word.
Ack wait should be 0 in all normal cases.
*************************************************************************}
@MBus:	[] ← TOS, rhTT ← uStack4 {HighHalf[remote address]}, ZeroBr,	c1, at[B0MBus];
	UPCsave ← PC, pop {stackP=5}, BRANCH[$, MBusZeroWC],	c2;
	ULsave ← L, pop {stackP=4},				c3;

	UGsave ← G, {PC ← rhPC,} L2 ← 0,			c1;
	L ← uStack6, pop {stackP=3},				c2;
	FloatNop {Free BX in 2 BX cycles},			c3;
	
	FloatNop {Free BX in 2 BX cycles},			c1;
{WriteM[stackP=3, Rx=HighHalf[remote address]}
	FloatUMS, Xbus {BXL[stackP]} ← L LRot0,			c2;
	L ← uStack2 {controlData}, pop {stackP=2},		c3;

{WriteM[stackP=2, Rx=controlData; written only once for the whole block}
	FloatUMS, Xbus {BXL[stackP]} ← L LRot0,			c1;
	TT ← uStack3 {LowHalf[VM address]}, push {stackP=3},	c2;
	PC ← uStack5 {LowHalf[remote address]},			c3;

{uStack8, the only U register which can be referenced during FloatNop,
holds the byteInterval during the loop; it will be overwritten by the
wordCount on a page fault, timeout, or interrupt.
}
	T ← uStack7,						c1;
	uStack8 ← T, FloatNop,					c2;
	FloatNop, L3Disp,					c3;

	Map ← Q ← [rhTT, TT + 0], BRANCH[WMPC, RMPC],		c1;
WMPC:	FloatULS, Xbus ← PC LRot0, GOTO[WMPCGo],		c2, at[2, 4];
RMPC:	FloatULS, Xbus ← PC LRot0, GOTO[RMPCGo], 		c2, at[3, 4, WMPC];

MBusZeroWC: {wordCount = 0 at entry}
	stackP ← 0, GOTO[RWMPCDone2],				c3;

{Update state and restore registers prior to exit for interrupt, fault, or
timeout.  Must not smash T or Q here (may hold fault parameters).
}
MBRestRegsA:
	uStack5 ← PC {Update LowHalf[MBaddr]},			c3;

MBRestRegs:
	uStack8 ← TOS {Update wordCount},			c1;
	stackP ← 7,						c2;
	TT ← TT and ~0FF,					c3;

	Rx ← Q and 0FF,						c1;
	TT ← TT or Rx,						c2;
	uStack3 ← TT {Update LowHalf[VMaddr]},			c3;

	L ← ULsave,						c1;
	G ← UGsave, L0Disp,					c2;
	PC ← UPCsave, RET[MBRestRegsRet],			c3;	


{HighHalf[VM address] and HighHalf[remote address] were already correct at the
time of the fault; arrive here with the kind of fault in T and virtual address
in Q from MBRestRegsA.
}
	Noop,							c1, at[L0.MBFaultFix, 10, MBRestRegsRet];
	GOTO[NcRWFault] {In CedarB0.mc},			c2;


{*********************************************************************
Come here on page crossings.  TT has the old page number in the left-half
and 0FF in the right-half.
*********************************************************************}
RWMPCPgCr:
	TT ← TT + 1, CarryBr,					c1;
	Q ← rhTT, BRANCH[RWMPCPgCr1, $],			c2;
	  Q ← Q + 1,						c3;
	  rhTT ← Q LRot0,					c1;
	  uStack4 ← Q,						c2;
RWMPCPgCr1:
	Noop,							c3;
	Map ← Q ← [rhTT, TT + 0], L3Disp,			c1;
	BRANCH[WMPCPgCr2, RMPCPgCr2],				c2;


WMPCPgCr2:
WMPCGo:	Rx ← rhRx ← MD, XRefBr {Referenced?},			c3, at[2, 4];

WMPCRR1:
	MAR ← [rhRx, Q + 0], MesaIntBr, BRANCH[$, WMPCRR2],	c1;
{Referenced = FALSE; if page not vacant, set Referenced = TRUE}
	CANCELBR[$, 3],						c2;
	Xbus ← Rx LRot0, XwdDisp {WP & Dirty dispatch},		c3;

	Map ← [rhTT, TT + 0], DISP2[WMPCMW],			c1, at[2, 4];
WMPCMW:	MDR ← Rx or 10 {Set Referenced}, GOTO[WMPCRR],		c2, at[0, 4]; {~WP, ~Dirty}
{db}	MDR ← Rx or 10 {Set Referenced}, GOTO[WMPCRR],		c2, at[2, 4, WMPCMW]; {~WP, Dirty}
{db}	MDR ← Rx or 10 {Set Referenced}, GOTO[WMPCRR],		c2, at[1, 4, WMPCMW]; {WP, ~Dirty}
	T ← qPageFault, L0 ← L0.MBFaultFix, GOTO[MBRestRegsA],	c2, at[3, 4, WMPCMW]; {WP, Dirty => Vacant}

WMPCRR:	Xbus ← 1, XDisp, GOTO[WMPCRR1],				c3;

WMPCRR2: {L ← 2 init is for entry at WMPCGo}
	L ← 2, L3Disp, DISP2[RWMPC4],				c2;

RMPCPgCr2:
RMPCGo:	Rx ← rhRx ← MD, XwdDisp {WP & Dirty bits},		c3, at[3, 4, WMPCPgCr2];

RMPCRR1:
	MAR ← [rhRx, Q + 0], MesaIntBr, DISP2[RMPCRR],		c1;
{Not Dirty => set both Referenced and Dirty = TRUE.}
RMPCRR:	CANCELBR[$, 3], {~WP, ~Dirty}				c2, at[0, 4];
	Noop,							c3;

	Map ← [rhTT, TT + 0],					c1;
{bj}	MDR ← Rx or map.rd {Set Referenced & Dirty},		c2;
	Xbus ← 1, XDisp, GOTO[RMPCRR1],				c3;

{L ← 2 init is for entry at RMPCGo}
{db}	L ← 2, L3Disp, DISP2[RWMPC4],				c2, at[2, 4, RMPCRR];
{db}	T ← qWriteProtect, L0 ← L0.MBFaultFix, CANCELBR[MBRestRegsA, 3],	c2, at[1, 4, RMPCRR];
{WP & Dirty = vacant}
	T ← qPageFault, L0 ← L0.MBFaultFix, CANCELBR[MBRestRegsA, 3],	c2, at[3, 4, RMPCRR];


{*********************************************************************
Interrupt requests for all opcodes come here.
*********************************************************************}
RWMPCInt:
	[] ← uWP, ZeroBr {Wakeups pending?},			c1;
	[] ← uWDC, NZeroBr {Wakeups disabled?}, L0 ← L0.MBIntExit, BRANCH[$, RWMPCNoInt1],	c2;
	uStack5 ← PC, ClrIntErr, BRANCH[MBRestRegs, RWMPCNoInt2],	c3;

{MBRestRegs returns here.}
	Bank ← MSBank1,						c1, at[L0.MBIntExit, 10, MBRestRegsRet];
	Noop,							c2;
	Noop, GOTOABS[B1IntContinue],				c3;

RWMPCNoInt1: {No wakeups}
	ClrIntErr, CANCELBR[RWMPCNoInt2],			c3;

RWMPCNoInt2:
	Noop,							c1;
	L3Disp,							c2;
	BRANCH[RWM6, RWPC6, 1],					c3;


{*********************************************************************
Ack timed out => trap with parameter 1.  First update progress and restore registers.
*********************************************************************}
RWPCTO:	uStack5 ← PC, CALL[MBRestRegs],				c3;
RWMPCTO:
	uStack5 ← PC, CALL[MBRestRegs],				c3;

	TT ← 1, GOTO[NcTrapC2] {In CedarB0.mc},			c1, at[L0.AckTO, 10, MBRestRegsRet];


{*********************************************************************
Come here after moving last word.
*********************************************************************}
RWMPCDone: {word count has run out without errors}
	PC ← UPCsave, pop {stackP=2}, CANCELBR[$],		c3;

	Xbus ← uPCCross, XRefBr, pop {stackP=1},		c1;
	PC ← PC + 1, pop {stackP=0}, BRANCH[RWMPCDone1, $],	c2;
	MesaIntRq, GOTO[RWMPCDone2],				c3;
RWMPCDone1:
	Noop,							c3;

RWMPCDone2:
	Bank ← MSBank1,						c1;
	G ← UGsave, IBDisp,					c2;
	L ← ULsave, DISPNI[Bank1OpTable],			c3;



{**********************************************************************
Main loop code for the four opcodes.
Update TOS, <rhTT/uStack4..TT>, and <uStack6..PC> to reflect completion
of the preceding iteration; then check for interrupts.  Note that any
page fault happens after the state has been updated. stackP = 3 here.
**********************************************************************}
RMPCG:	FloatNop, T ← uStack8,					c3;

WPCE:	TOS ← TOS - 1, ZeroBr, GOTO[RWMPC0],			c1;
WMA:	TOS ← TOS - 1, ZeroBr,					c1;
RWMPC0:	PC ← PC + T, CarryBr, BRANCH[$, RWMPCDone],		c2;
RWMPC1:	FloatNop, BRANCH[RWMPC2, $],				c3;

	  T ← uStack6,						c1;
	  T ← T + 1,						c2;
	  uStack6 ← T,						c3;

	  {WriteM[stackP=3, T=HighHalf[remote address]}
	  FloatUMS, Xbus ← T LRot0,				c1;
	  FloatNop, GOTO[RWMPC1],				c2;

RWMPC2:	MAR ← Q ← [rhRx, Q + 1], MesaIntBr,			c1;
{WriteL[stackP=3, PC=LowHalf[MBus address]]}
	FloatULS, Xbus {BXL[stackP=3]} ← PC LRot0, L3Disp, DISP2[RWMPC4],	c2;

{At this point, registers have been updated to reflect completion of the
previous word, the new MBus address has been sent to the BX bus, and the
word for the current iteration is fetched here into T (irrelevant on
MBus and PCBus reads).
}
RWMPC4:	T ← MD, pop, BRANCH[RWM6, RWPC6, 1],			c3, at[0, 4];
	T ← MD, pop, CANCELBR[RWMPCInt, 3], {Int., no pg. cr.}	c3, at[1, 4, RWMPC4];
	TT ← TT or 0FF, CANCELBR[RWMPCPgCr, 3], {No int., pg. cr.}	c3, at[2, 4, RWMPC4];
{Service the page cross first when both an interrupt and page cross occur simultaneously.}
	TT ← TT or 0FF, CANCELBR[RWMPCPgCr, 3],{Int & pg.cr.}	c3, at[3, 4, RWMPC4];

{WriteL[stackP=2, T=VMaddr↑]}
RWM6:	FloatULS, Xbus {BXL[stackP=2]} ← T LRot0, push {stackP=3}, GOTO[RWMPC7],	c1, at[1, 4];
RWPC6:	FloatULS, Xbus {BXL[stackP=2]} ← T LRot8, push {stackP=3}, 	c1, at[3, 4, RWM6];

{On the Multibus1, the ack from the previous operation is cleared 4 x 250 nsec
of an unsynchronized clock after the FloatULS above has been executed, and the
new ack is normally raised roughly 300 nsec after that, but worst case wait is
10.0 microseconds.  So the old ack will be cleared 5.39 to 7.19 cycles after
and the new ack usually raised 10 cycles after the FloatULS.  Of the 6 Noop's
in the code below, 3 appear unnecessary, but they provide added safety against
reading the old ack while improving the chance of getting the new ack on the
first probe.  On WriteMBus, the 8th mi after FloatUMP (or the 19th mi after the
FloatULS) will change LowHalf[MBaddr] for the next write operation.
}
RWMPC7:	G ← 0B {12d loop counter for timeout}, L0 ← L0.AckTO,	c2;
	Noop,							c3;

	Noop,							c1;
	Noop,							c2;
	Noop,							c3;

	Noop,							c1;
	Noop,							c2;
	FloatNop,						c3;

	FloatNop,						c1;
RWMPC8:	Ybus ← L {2}, AltUaddr {FloatUMS}, BRANCH[$, RWMPCTO],	c2;
	FloatUMP, Xbus ← FloatResult, L3Disp,			c3;

	rhT ← FloatResult, DISP2[WM9],				c1;

{The XDisp branches here are equivalent to [] ← rhT and 4, NZeroBr.
}
WM9:	Xbus ← rhT, XDisp {Ack?  Test bit 13d},			c2, at[0, 4];
	FloatNop, T ← uStack8, BRANCH[$, WMA, 0B],		c3;
	G ← G - 1, ZeroBr {Timeout?}, GOTO[RWMPC8],		c1;

RM9:	Xbus ← rhT, XDisp {Ack?  Test bit 13d},			c2, at[1, 4, WM9];
	FloatNop, T ← 0, BRANCH[$, RMA, 0B],			c3;
	G ← G - 1, ZeroBr {Timeout?}, GOTO[RWMPC8],		c1;

WPC9:	Xbus ← rhT, XDisp {Ack?  Test bit 13d},			c2, at[2, 4, WM9];
	FloatNop, BRANCH[$, WPCA, 0B],				c3;
	G ← G - 1, ZeroBr {Timeout?}, GOTO[RWMPC8],		c1;

RPC9:	Xbus ← rhT, XDisp {Ack?  Test bit 13d},			c2, at[3, 4, WM9];
	FloatNop, BRANCH[$, RPCA, 0B],				c3;
	G ← G - 1, ZeroBr {Timeout?}, GOTO[RWMPC8],		c1;


{VMaddr↑ ← ReadL[2]}
RMA:	Noop,							c1;
RMPCF:	Ybus ← L {2}, AltUaddr,					c2;
	FloatULP, Xbus ← FloatResult,				c3;

	MAR ← [rhRx, Q + 0],					c1;
	MDR ← FloatResult or T, CANCELBR[RMPCG, 1],		c2;


{Come here from the common code after receiving the Ack for the even byte}
RPCA:	Ybus ← L {2}, AltUaddr,					c1;
	FloatULP, Xbus ← FloatResult,				c2;
	rhT ← FloatResult,					c3;

{Jump here from common code on WritePCBus after writing first byte
and from ReadPCBus after reading the first byte.  I think the
rhT ← FloatResult in the previous mi counts as a FloatNop, so only
one more is required here (???).
}
WPCA:	FloatNop, G ← uStack8,					c1;
	PC ← PC + G, CarryBr,					c2;

	FloatULS, Xbus {BXL[stackP=3]} ← PC LRot0, pop, BRANCH[RWPCB, $],	c3;
 	  G ← uStack6, push,					c1;
	  G ← G + 1,						c2;
	  uStack6 ← G,						c3;

{WriteM[stackP=3, HighHalf[remote address]]}
	  FloatUMS, Xbus {BXL[stackP=3]} ← G LRot0, pop,	c1;
	  FloatNop,						c2;
	  FloatNop,						c3;
{WriteL[stackP=2, odd data byte (garbage if PCBus read)]}
RWPCB:	FloatULS, Xbus {BXL[stackP=2]} ← T LRot0, push {stackP=3},	c1;
	G ← 0B {12d loop counter for timeout}, L0 ← L0.AckTO,	c2;
	T ← rhT {even byte if ReadPCBus},			c3;

	T ← T LRot8,						c1;
	Noop,							c2;
	Noop,							c3;

	Noop,							c1;
	Noop,							c2;
	FloatNop,						c3;

	FloatNop,						c1;
RWPCC:	Ybus ← L {2}, AltUaddr {FloatUMS}, BRANCH[$, RWPCTO],	c2;
	FloatUMP, Xbus ← FloatResult, L3Disp,			c3;

	rhT ← FloatResult, BRANCH[WPCD, RPCD]			c1;
WPCD:	[] ← rhT, XDisp {Ack? Test bit 13d},			c2, at[2, 4];
	FloatNop, T ← uStack8, BRANCH[$, WPCE, 0B],		c3;
	G ← G - 1, ZeroBr {Timeout?}, GOTO[RWPCC],		c1;

RPCD:	[] ← rhT, XDisp {Ack? Test bit 13d},			c2, at[3, 4, WPCD];
	FloatNop, BRANCH[$, RPCE, 0B],				c3;
	G ← G - 1, ZeroBr {Timeout?}, GOTO[RWPCC],		c1;

{Multibus Ack received for odd byte => read odd byte, combine with even byte
and write the resulting word at to↑.}
RPCE:	GOTO[RMPCF],						c1;
db}