*-----------------------------------------------------------
Title[TriDisk.mc...April 13, 1982 3:31 PM...Taft];
* Emulates an Alto Trident disk on a Dorado’s Trident disk(s).
* This code is intended to coexist with AltoDiabloDisk.mc --
* on a Dorado with multiple drives, drive 0 is used to emulate
* an Alto Diablo disk and the other drives emulate Alto Trident disks.
*-----------------------------------------------------------

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

KBLK format:
VM 640:pointer to disk command block (KCB)
VM 641:number of currently-selected drive
VM 642:cylinder number of most recently started disk command
VM 643:status at last sector pulse

KCB format:
KCB+0:cylinder number
KCB+1:head ,, sector numbers
KCB+2:drive number
KCB+3:pointer to next KCB
KCB+4:command seal (overwritten by initial status)
KCB+5:header command
KCB+6:header word count
KCB+7:pointer to header block
KCB+10:header ECC0
KCB+11:header ECC1
KCB+12:header ending status
KCB+13 to 20: label command block (similar to KCB+5 to 12)
KCB+21 to 27: data command block (similar to KCB+5 to 12)
KCB+30:unused
KCB+31:completion interrupt bit mask*** not implemented

Disk command format:
0-3:unused
4:check data
5:unused
6:strobe late*** no way to access this at present
7:strobe early*** no way to access this at present
8:write
9:read
10:unused
11:reset head address
12:device check reset
13:head select
14:re-zero
15:advance head address

Disk status format:
0:seek incomplete
1:illegal head number
2:device check
3:not selected
4:not on-line
5:not ready
6:sector overflow
7:write data late
8:read data late
9:compare error
10:read-only
11:cylinder offset
12:ECC error -- reported only in KCB
12-15:sector number -- reported only in KBLK

Note: at present this microcode may be used to emulate either the Alto
TFS 9-sector (1024-word data block) format or the Alto Juniper 16-sector
(512-word data block) format. HOWEVER, due to deficiencies in the Dorado
sector size selection mechanism, a drive to be used for 9-sector emulation
must be jumpered for 117 sectors (Dorado standard), whereas a drive to be
used for 16-sector emulation must be jumpered for 16 sectors.
%

*-----------------------------------------------------------
* SIO instruction
* Diablo disk does not use SIO, but Trident disk does.
* DiskSIO is first defined in ADiabloDisk.mc, and is redefined here.
* SIO[40] enables Trident emulation; SIO[20] disables it.
* Stack = SIO argument.
* Current state of Trident emulation is reflected in TridentFlag:
*
B0:1 = Trident emulation enabled, 0 = disabled
*
B1:0 = force drive selection at next Trident command
*
B12-15:current subsector count
*-----------------------------------------------------------

Set[XTask, IP[EMU]];
TopLevel;
DontKnowRBase;

DiskSIO:
PD← (Stack) AND (60C);
PD← (Stack) AND (40C), Branch[ESIO, ALU=0];
T← A0, RBase← RBase[DiskRegs], Branch[.+2, ALU=0];
T← (TridentFlag) OR (100000C);* Enable Trident emulation

* Give the disk task a wakeup, in case the currently-selected drive
* is not on-line and hence not giving any sector wakeups.
* ASSUMPTION: code doing a disk SIO will NOT be trying to read/write
* the disk (Diablo or Trident) at the same time, and hence the microcode
* will not be confused by the extra wakeup.
TridentFlag← T, TaskingOff;
Wakeup[DSK];
TaskingOn, Branch[ESIO];


*-----------------------------------------------------------
* End of idle loop for Diablo emulation.
* If Trident emulation is enabled, check for newly-issued Trident commands,
* and switch to Trident emulation if any are found.
* EndAltoLoop is first defined in DiskSubrs.mc, and is redefined here.
*-----------------------------------------------------------

Set[XTask, IP[DSK]];
KnowRBase[DiskRegs];

EndAltoLoop:
T← (K400)+(240C);* VM 640 = KCB pointer
T← (Fetch← T)+1, TridentFlag, Branch[.+2, R<0];
KStatus← A0, Block, Branch[AltoLoop]; * Trident emulation disabled

* Trident emulation is enabled. A Trident command is pending if either
* VM 640 (KCB pointer) is nonzero or VM 641 (drive number) is negative.
Fetch← T, KPtr← PD← MD;* VM 641 = drive number
KTemp0← MD, Branch[StartTrident, ALU#0];
KTemp0, Branch[StartTrident1, R<0];
KStatus← A0, Block, Branch[AltoLoop]; * No Trident command

* Switch to Trident emulation mode.
StartTrident:
Nop;
StartTrident1:
TridentFlag← (TridentFlag) AND (Not[40000]C); * Force drive select
T← T-T-1, MemBase← DiskBR,* BRHi← MDSHi-1 for Trident transfers
Call[SetDiskBRHi];
MemBase← IOBR, Branch[TriIdleLoop];

*-----------------------------------------------------------
* Idle loop for Trident emulation: awakened once per sector.
* Stores status and checks for newly-issued commands.
*-----------------------------------------------------------

TriIdleLoop:
T← clearSeekTagTW, Call[DoMuffOutput]; * clear any spurious SeekTagTW
Call[UpdateSector];

T← KSelect, Call[SendDriveTag];* Turn on select for current drive
T← (K400)+(240C);* VM 640 = KCB pointer
T← (Fetch← T)+1, TridentFlag,* Switch to Diablo if Trident disabled
Branch[SwitchToDiablo, R>=0];
Fetch← T, KPtr← PD← MD;* VM 641 = drive number
KAddr← MD, Branch[TriNewCmmd, ALU#0]; * Branch if have command
KAddr← (KAddr) AND (77777C),* Isolate drive number
Branch[TriDriveSelect, R<0]; * Branch if new drive select

* No Trident commands. Store Trident status and check for Diablo command.
T← muffsStatus;
Call[Read20Muffs];* Read drive status
KTemp1← T AND (Not[17]C);* Report drive status in [0:11]
T← LDF[Sector, 4, 0];* Report sector number in [12:15]
KTemp1← (KTemp1) OR T, FreezeBC; * ALU branch cond ← unmasked Sector
T← (KSelect) AND (Not[tagSelectDrive!]C),
Branch[.+2, ALU<0];* Don’t deselect if sector unsynchronized
Call[SendDriveTag];* Turn off drive select
T← (K400)+(121C);* VM 521 = Diablo KCB pointer
T← (Fetch← T)+(Sub[643, 521]C);
PD← MD;
Store← T, DBuf← KTemp1, Branch[.+2, ALU#0]; * Store status in VM 643
Block, Branch[TriIdleLoop];

*-----------------------------------------------------------
* Trident is idle and there is a new Diablo command. Switch to Diablo mode.
*-----------------------------------------------------------
Nop;
SwitchToDiablo:
T← A0, MemBase← DiskBR;* BRHi← MDSHi for Diablo transfers
Call[SetDiskBRHi];
Call[InitRamDiablo];* Reset format Ram, select drive 0
KStatus← A0, MemBase← IOBR, Block, * This will block until Index
Branch[AltoLoop];* Begin executing Diablo commands

*-----------------------------------------------------------
* Select new Trident drive, forced by software storing 100000B + drive into
* VM 641, without issuing a command.
* Also get here when a Trident command selects a new drive.
* T = 641, KAddr = new drive number, KPtr = 0 or KCB pointer.
*-----------------------------------------------------------
TriDriveSelect:
Store← T, DBuf← KAddr, Call[InitRamTrident];
T← TridentFlag← (TridentFlag) OR (40000C); * Reset force flag
KTemp0← T AND (77C);* Extract subsector count
T← KAddr, Call[SetDriveAndSubSector]; * Select drive and subsector count
Block, Branch[TriIdleLoop];* Block til index, then look again

*-----------------------------------------------------------
* Have the first Trident command of a new chain.
* Set up the format Ram according to this command, and force a drive select
* if the sector format has changed.
*-----------------------------------------------------------
TriNewCmmd:
Nop;
KAddr← T-T-1, Call[InitRamTrident]; * Pretend current drive is -1
PD← (TridentFlag)+(TridentFlag); * Test TridentFlag[1]
T← (KPtr)+(4C), Branch[DoTCmmd1, ALU>=0]; * Branch if format changed

*-----------------------------------------------------------
* Have a Trident command to execute. KPtr = KCB pointer.
* Check for correct seal and new drive select.
*-----------------------------------------------------------
DoTCmmd:
T← (K400)+(241C);* VM 641 = current drive number
Fetch← T, T← KPtr;
KAddr← MD, T← T+(4C);* KCB+4 = command seal
DoTCmmd1:
T← (Fetch← T)-(2C);* KCB+2 = drive number
Fetch← T, KCmmd← 122400C, T← MD; * Correct seal = 122645B
KCmmd← (KCmmd) OR (245C);
KCmmd← (KCmmd) XOR T;
PD← (KAddr) XOR MD, KAddr← MD, * Compare new drive with current
Branch[TCmmdBadSeal, ALU#0]; * Branch if invalid seal
T← (KPtr)+(5C), Branch[.+2, ALU=0];
T← (K400)+(241C), Branch[TriDriveSelect]; * New drive, go select it

*-----------------------------------------------------------
* Run down the command list in the KCB and construct the single-word
* command wanted by the controller. T = KPtr+5, KCmmd = 0.
* This code depens on the KCB containing at most 3 command blocks.
*-----------------------------------------------------------
TConstCmmd:
T← (Fetch← T)+(6C);* Fetch command for this block
KCmmd← LSH[KCmmd, 2];
KTemp0← PD← MD;
PD← (KTemp0) AND (12C), Branch[TConstCmmdEnd, ALU=0];
PD← (KTemp0) AND (200C), Branch[TResetRestore, ALU#0];
PD← (KTemp0) AND (4000C), Branch[.+2, ALU=0];
KCmmd← (KCmmd) OR (commandWrite), Branch[TConstCmmd];
Branch[.+2, ALU=0];
KCmmd← (KCmmd) OR (commandCompare), Branch[TConstCmmd];
KCmmd← (KCmmd) OR (commandRead), Branch[TConstCmmd];

* End of commands. Left-shift KCmmd so that the first command is non-null.
* (Careful: a do-nothing command will leave KCmmd entirely zero.)
* Compare requested cylinder with current one.
* These two operations are interwoven to save space and time.
TConstCmmdEnd:
T← (K400)+(242C);* VM 642 = current cylinder
KTemp1← Fetch← T;
KTemp3← (Fetch← KPtr)+1, T← MD;* KPTR+0 = new cylinder; ALU#0
PD← (KCmmd) AND (300C), Branch[.+3, ALU=0]; * Branch if do-nothing
PD← T XOR MD, Branch[.+3, ALU#0];
KCmmd← LSH[KCmmd, 2], Branch[.-2]; * Shift command into position
PD← T XOR MD;
Fetch← KTemp3, T← MD, Branch[NoTSeek, ALU=0]; * KBLK+1 = head,,sector

*-----------------------------------------------------------
* Must do a seek. T = new cylinder number, KTemp1 = 642B.
*-----------------------------------------------------------
Store← KTemp1, DBuf← T,* Update current cylinder in VM 642
Call[SeekAndWaitForReady];

*-----------------------------------------------------------
* Now select the head and wait for the right sector.
* MD = head ,, sector; KTemp3 = KPtr+1.
*-----------------------------------------------------------
NoTSeek:
T← MD, KStatus← A0;
T← RSH[T, 10], KAddr← MD;
T← T OR (tagHead), Call[SendTag]; * Send the head tag command
T← (KAddr) AND (37777C);* Remove offset head positioning

PD← KCmmd;* do-nothing command?
KAddr← (KAddr) AND (377C), Branch[TCmmdSeekOnly, ALU=0];

* Store head,,sector (with offset head positioning stripped off)
* back into KPtr+1, so that header checking will work properly
* when offset head positioning is being done.
Store← KTemp3, DBuf← T;

TWaitSector:
SCall[WaitForSector];* Returns with TIOA[DiskControl]
Branch[TBadSector];* +1 return: failed to find sector

*-----------------------------------------------------------
* Issue the command to the controller.
* Then check to see whether we issued it in time, and if not revoke it
* and wait for this sector to come around again.
*-----------------------------------------------------------
Output← KCmmd, Call[UpdateSector]; * Returns with T = Sector
PD← (KAddr) XOR T;* Are we still at the same sector?
T← A0, TIOA[DiskControl], Branch[TCmmdInTime, ALU=0];

Output← T;* Not in time. This clears Active
Output← T, Branch[TWaitSector];* This reloads command register

*-----------------------------------------------------------
* Now do the data transfers.
*-----------------------------------------------------------
TCmmdInTime:
KStatus← A0, Block, Call[UpdateSector]; * Block til start of sector
KCmmd← RCY[KCmmd, KCmmd, 6];* Header command to [14:15]
TCmmdSeekOnly:
T← (KPtr)+(4C);* KCB+4 = seal = first block -1
TResetFinish:
KTemp3← T;
KTemp0← muffReadError;

* Here KTemp3 = pointer to status word for previous block;
* KStatus = software status conditions for previous block (just ECC error);
* KTemp0 = muffReadError or muffWriteError as appropriate.
* Each iteration of TCmmdLoop executes the command in KCmmd[14:15]
* and left-cycles KCmmd 2 bits.
TCmmdLoop:
Call[Read1Muff];* Check for hardware errors
T← muffsStatus, KTemp0, Branch[TCmmdNoErr, R even];

* There was a hardware error in the previous block. Read entire status word.
* Bits 0-11 of the hardware status are identical to the emulated Alto Trident
* status. Bits 12 and 13 are IOBParityErr and FifoParityErr, which aren’t
* defined on the Alto; we map these to ReadDataErr so that the software will
* at least notice that there was an error. (These will subsequently cause the
* command chain to be aborted.) Bits 14 and 15 are ReadError and WriteError,
* which are irrelevant for our purposes here.
Call[Read20Muffs];
PD← (KTemp0) AND (14C);* IOB or Fifo PE?
T← (KTemp0) AND (Not[17]C), Branch[.+2, ALU=0];
T← T OR (100C);* Yes, also set ReadDataErr bit
KStatus← (KStatus) OR T;* Merge with software status bits
TCmmdNoErr:
T← (KStatus)+1;* Set done bit
KTemp3← (Store← KTemp3)+1, DBuf← T; * Store status word

* Now dispatch on the next command. Note: KTemp0 contains hardware status
* that must be preserved for examination at TCmmdEnd.
KTemp3← (KTemp3)+1;* Skip over command word
KTemp3← (Fetch← KTemp3)+1;* Fetch word count
KTemp3← (Fetch← KTemp3)+1, T← MD; * Fetch memory address
DskMAddr← (0S)-T, T← MD;* DskMAddr← -count
T← T-(DskMAddr), MemBase← DiskBR; * T← memory address + count
BDispatch← KCmmd;* Dispatch on KCmmd[14:15]
KStatus← A0, BRLo← T;* Set BR for negative indexing

TCmmdTable: DispTable[4, 7, 4],
Branch[TCmmdEnd];* 0 no more commands
T← 1C, Branch[TCmmdWrite];* 1 write; T← sync pattern to write
KTemp0← muffRdFifoTW, Branch[TCmmdCheck]; * 2 check
KTemp0← muffRdFifoTW, Branch[TCmmdRead]; * 3 read

*-----------------------------------------------------------
* Write command.
* Controller gives a WriteFifoTW when there is room for at least 4 words
* in the Fifo. Doing an Output that reduces the free space below 4 causes
* WriteFifoTW to be dropped at T0 of the 4th cycle after the Output,
* so a Block on the 5th cycle will take effect.
* I think it was originally intended that a 3-instruction, 2-word loop
* be possible:
Output; Output; Block;
* Unfortunately, this doesn’t work if the second Output causes the wakeup
* to be dropped, because it is dropped so late that we will go around
* the loop twice more, outputting 4 words when there is room for only 3.
* Thus the minimal loop is:
{Output; Output; Nop}; Block;
* where the instructions inside { } may be permuted in any way.
* An equivalent loop is:
Output; Block;
* and it takes less microcode.
* Due to control section bugs, we must not Block on a memory reference
* if the task wakeup might be dropped at T0 of that instruction.
*-----------------------------------------------------------
TCmmdWrite:
TIOA[DiskData];
DskMAddr← (Fetch← DskMAddr)+1, Output← T; * Output sync pattern

DskMAddr← (Fetch← DskMAddr)+1, Output← MD;
Block, Branch[.-1, ALU<0];

Output← MD;* Output last word

* Changing TIOA from DiskData to DiskControl disables WriteFifoTW.
* The wakeup is removed at T0 of the third instruction after the one that
* changes TIOA, so the earliest we can block is the fourth instruction.
* Hardware generates one more WriteFifoTW when it is done with this block.
TIOA[DiskControl];
KTemp0← muffWriteError;* Select appropriate status bit
KCmmd← LCY[KCmmd, KCmmd, 2];* Shift command for next block
KTemp3← (KTemp3)+1, MemBase← IOBR; * Skip over ECC words
KTemp3← (KTemp3)+1, Block, Branch[TCmmdLoop]; * Wait til write done

*-----------------------------------------------------------
* Read command.
* Controller gives a ReadFifoTW when there are at least 3 words in the Fifo
* (actually, 2 in the Fifo and 1 in OutReg). Doing an Input that reduces
* the count below 3 causes ReadFifoTW to be dropped at T0 of the 4th cycle
* after the Input, so a Block on the 5th cycle will take effect.
* Thus the minimal loop is:
Input; Block;
* Due to control section bugs, we must not Block on a memory reference
* if the task wakeup might be dropped at T0 of that instruction.
*-----------------------------------------------------------
TCmmdRead:
DskMAddr← (DskMAddr)-1, Block, Call[Read1Muff];
KTemp0, TIOA[DiskData], Branch[TReadBadTW, R even];
PD← DskMAddr, T← Input, Branch[.+2]; * Can’t do back-to-back Inputs

PD← (Store← DskMAddr)+1, DBuf← T, T← Input;
DskMAddr← (DskMAddr)+1, Block, Branch[.-1, ALU#0];

* A read block ends with 2 garbage words and 2 ECC words. When we fall out
* of the main loop, we have already read the first garbage word.
Call[ReadECC];* Returns with ECC test hanging
Branch[TReadCheckEnd];* Remainder same as check case

*-----------------------------------------------------------
* Check command.
* Controller gives a ReadFifoTW when there is at least 1 word in the Fifo
* (actually, OutReg full regardless of Fifo). Doing an Input that empties
* the Fifo causes ReadFifoTW to be dropped at T0 of the 2nd cycle
* after the Input, so a Block on the 3rd cycle will take effect.
* Thus the minimal loop is:
Input; Nop; Nop; Block;
* Due to control section bugs, we must not Block on a memory reference
* if the task wakeup might be dropped at T0 of that instruction.
*-----------------------------------------------------------
TCmmdCheck:
DskMAddr← (Fetch← DskMAddr)+1, Block, Call[Read1Muff];
KTemp0← A0, TIOA[DiskData], Branch[TCheckBadTW, R even];

* The first two words are treated differently from the rest:
* they are ALWAYS checked and are NEVER stored. Handle these in-line.
* MD = first word from memory; KTemp0 accumulates the OR of all differences.
T← Input;
T← T XOR MD;
KTemp0← (KTemp0) OR T;
DskMAddr← (Fetch← DskMAddr)+1, Block;

T← Input;
T← T XOR MD, DskMAddr,
Branch[TCheckLast, R>=0]; * Branch if only 2 words in block
KTemp0← (KTemp0) OR T;
DskMAddr← (Fetch← DskMAddr)+1, Block, Branch[TCheckLoop];

TCheckLast:
KTemp0← (KTemp0) OR T, Branch[TCheckDone];

* Main check loop -- 4 cycles per word.
* At the top of the loop, MD = the word fetched from memory during the
* previous cycle; KTemp0 accumulates the OR of all differences;
* Carry is set iff this is the last iteration.
TCheckLoop:
PD← MD, T← KTemp1← Input;
T← T XOR MD, Branch[TCheckStore, ALU=0]; * Don’t check if MD=0
KTemp0← (KTemp0) OR T, Branch[.+2, Carry];
DskMAddr← (Fetch← DskMAddr)+1, Block, * More to go, fetch next
DblBranch[TCheckLoop, TCheckError, ALU=0];
DskMAddr← (DskMAddr)+1,* Last word, check it and done
DblBranch[TCheckDone, TCheckError1, ALU=0];

* If a check error occurs, store the data that was actually read,
* and then just read the rest of the block.
TCheckError:
DskMAddr← (DskMAddr)-1, Branch[TCheckStore];
TCheckError1:
DskMAddr← (DskMAddr)-1, Branch[TCheckStore];

* As soon as we encounter a zero word from memory, stop checking and
* just store the disk data into memory.
TNoCheckLoop:
KTemp1← Input;
TCheckStore:
T← (DskMAddr)-1, Branch[TCheckStoreLast, R>=0];
DskMAddr← (Fetch← DskMAddr)+1;
Store← T, DBuf← KTemp1, Block, Branch[TNoCheckLoop];

TCheckStoreLast:
Store← T, DBuf← KTemp1;

* Check command (cont’d)

* Here when done checking. KTemp0 is nonzero if there were any differences.
TCheckDone:
PD← KTemp0, TIOA[DiskMuff];
T← clearCompareErr, Branch[.+2, ALU#0];

* No differences: clear CompareErr. Note that if a compare error occurred
* in an earlier block, ReadDataErr will be set, and this will NOT clear it.
Output← T, Branch[.+2];

* Check error occurred. Set Compare Error status bit here so that the
* error is properly reported in the status for this block.
* The error will be captured by the hardware (as ReadDataErr) at the
* beginning of the next block, and will be reported in the status for
* all subsequent blocks until the software issues a Reset.
KStatus← (KStatus) OR (100C);

* Now continue on and read the ECC.
TIOA[DiskData], Block;
Call[CheckECC];

* At this point, the ECC words are in T and KTemp0.
* This is the tail of both reading and checking.
* KTemp3 points to first ECC word for this block.
TReadCheckEnd:
PD← (KTemp0) OR T, MemBase← IOBR;
T← (Store← KTemp3)+1, DBuf← T, Branch[.+2, ALU=0]; * Store ECC0
KStatus← (KStatus) OR (10C);* ECC error
T← (Store← T)+1, DBuf← KTemp0; * Store ECC1
KCmmd← LCY[KCmmd, KCmmd, 2];* Shift command for next block
KTemp0← muffReadError;
KTemp3← T, MemBase← IOBR, Branch[TCmmdLoop];

* If a non-Fifo TW occurs at the beginning of reading or checking, most
* likely the data was so bad that the controller was unable to lock onto it
* before reaching the end of the sector. Report this as a SectorOverflow.
TReadBadTW:
Nop;
TCheckBadTW:
KCmmd← T← A0, TIOA[DiskControl]; * No more commands
KTemp0← A0, Output← T, Call[OutputGetsT]; * Reset controller
KStatus← 1000C, Branch[TReadCheckEnd]; * Post this status

*-----------------------------------------------------------
* Command has completed.
* KTemp0 has current hardware status (0 if no errors have occurred).
*-----------------------------------------------------------
TCmmdEnd:
PD← KTemp0, MemBase← IOBR;* Error occurred?
T← A0, TIOA[DiskControl], Branch[.+2, ALU=0];
Output← T, Call[OutputGetsT];* Yes, reset command register
T← clearSeekTagTW, Call[DoMuffOutput]; * Clear TW caused by TagDone
T← (K400)+(214C);* Fifo over/underflow, Fifo/IOB PE?
PD← (KTemp0) AND T;
T← (KPtr)+(3C), Branch[TCmmdCatastrophe, ALU#0];

Fetch← T, T← K400;* KCB+3 is pointer to next command
T← T+(240C), KPtr← MD;
Store← T, PD← DBuf← KPtr;* Store it in VM 640
Branch[TriIdleLoop, ALU=0];* Is there a command?
Branch[DoTCmmd];* Yes, go to it directly

*-----------------------------------------------------------
* Catastrophic error occurred.
* Reset command register in case we have gotten out of sync with controller.
* Then disable further KCB processing and post abort status in VM 644.
* Format of status word in 644 (as defined by Alto and by Triex):
*
B5seek timed out (not implemented; Triex thinks it’s B4!)
*
B6ECC compute error (not implemented: indistinguishable from
*
check error!)
*
B7data late: microcode may be out of sync with hardware
*
B9IOB parity error
*
B10Fifo parity error
*
B11invalid seal
*
B13read task hung (not implemented)
*
B14KCB aborted because of one of these errors
*
B15failed to find desired sector within 64 sector times
*-----------------------------------------------------------
TCmmdCatastrophe:
T← (K400)+(200C);
PD← (KTemp0) AND T;* Fifo over/underflow?
T← (KTemp0) AND (14C), Branch[.+2, ALU=0]; * Mask IOB and Fifo PE
T← T OR (40C);* Report over/underflow as data late
KStatus← LSH[T, 3];* Shift into position
TCmmdAbort:
KStatus← (KStatus) OR (2C);* Set abort bit
T← (K400)+(244C);* Store abort status in VM 644
Store← T, DBuf← KStatus, KStatus← A0;
T← (K400)+(240C);* Zero VM 640 to abort command chain
Store← T, DBuf← KStatus, Branch[TriIdleLoop];

TCmmdBadSeal:
KStatus← 20C, Branch[TCmmdAbort];
TBadSector:
KStatus← 1C, Branch[TCmmdAbort];

*-----------------------------------------------------------
* Reset/Restore command.
* This is the only place where ClearErrors is done to reset latched errors.
* KTemp0 has the command.
*-----------------------------------------------------------
TResetRestore:
Nop;
T← (KTemp0) OR (tagControl);
KTemp3← T, Call[SendTag];
T← clearErrors, Call[DoMuffOutput];
PD← LSH[KTemp3, 16];* Is this a restore?
KStatus← A0, Branch[TResetDone, ALU>=0];

* For Restore, must block til drive is ready and an Index pulse arrives.
T← clearAllTWs, Call[DoMuffOutput];
T← K400, TIOA[DiskControl], Call[OutputGetsT]; * BlockTilIndex
T← (K400)+(242C);* Forget saved cylinder address
Store← T, Sector← DBuf← -1C, Block;

* Now finish up by storing header status. Know KCmmd[14:15]=0 here.
TResetDone:
T← (KPtr)+(12C);* Address of header status
Branch[TResetFinish];

*-----------------------------------------------------------
InitRamTrident:
* Init format Ram for Trident emulation.
* Enter: KPtr = KCB pointer for some Trident command (0 if none)
*
TridentFlag[12:15] = current subsector count
*
Assumes Ram words 5-17 are already set up for Diablo emulation.
* Exit: Updates TridentFlag[12:15]
*
Clears TridentFlag[1] if sector size changed, to force drive select.
* Clobbers T, KTemp0, KTemp1, KTemp2, KTemp3, KStatus, TIOA
* Strategy: InitRam is called when switching between Diablo and Trident mode,
* when switching drives, and whenever a new chain of commands is started.
* If there is a command, and the command transfers a data block,
* then set up the format Ram and select a sector format according
* to the command. If there is no command or the command is a non-data
* command, then assume whatever sector format is currently selected.
* This strategy depends on the assumption that nobody sets up command
* chains consisting of a non-data command followed by data commands.
*-----------------------------------------------------------
Subroutine;

PD← KPtr;
KTemp3← Link, Branch[RamSetup, ALU=0];
TopLevel;
T← (KPtr)+(6C);
T← (Fetch← T)+(14C);* KCB+6 = header count
KTemp0← MD-1;
T← (Fetch← T)-(6C);* KCB+22 = data count
KTemp2← MD-1;
Fetch← T, PD← KTemp2;* KCB+14 = label count
KTemp1← MD-1, Branch[.+2, ALU>=0];
Branch[RamSetup];* No data count, skip subsector select

* Choose subsector count. 9-sector format assumes drive is jumpered for
* 117 sectors and chooses a subsector count of 14B. 16-sector format assumes
* drive is jumpered for 16 sectors and chooses a subsector count of 0.
T← (KTemp2) AND (1000C);* Data block > 512 words?
KStatus← TridentFlag, Branch[.+3, ALU=0];
T← 14C;* Yes, choose 14B
MaxSectors← 11C, Branch[.+2];* 9 sectors around
MaxSectors← 20C;* No, choose 0; 16 sectors around
T← (KStatus) XOR T;* See if different from current
T← T AND (17C);
TridentFlag← (TridentFlag) XOR T, Branch[.+2, ALU=0];
TridentFlag← (TridentFlag) AND (Not[40000]C); * Force drive select
KSelect← (KSelect) AND (Not[4000]C); * Sectors evenly divide the disk

* Load words 0-4 of format Ram; remaining words are same as for Diablo.
RamSetup:
T← A0, TIOA[DiskControl], Call[OutputGetsT]; * Clear Ram address
T← KTemp0, TIOA[DiskRam];
T← KTemp1, Output← T,* [0] header count -1
Call[OutputGetsT];* [1] label count -1
T← A0, Output← KTemp2,* [2] data count -1
Call[OutputGetsT];* [3] unused count
T← Or[tagRead!, tagHeadSelect!]C;
T← T OR (tagAltoLeader);* [4] control tag for read
Link← KTemp3, Branch[OutputGetsT]; * Output← T and Return

*-----------------------------------------------------------
SetDiskBRHi:
* Set BRHi for disk transfers
* Enter: T = amount to add to MDSHi to yield correct value
*
MemBase = DiskBR
* Clobbers T
*-----------------------------------------------------------
Subroutine;

RBase← RBase[EmuBRHiReg];
T← (EmuBRHiReg)+T, RBase← RBase[DiskRegs];
BRHi← T, Return;