;  FILE ' TriconBody.mu ' -- by Roger Bates
; Body of Trident microcode -- requires definitions in TriConMc.mu
; or equivalent.
; Copyright Xerox Corporation 1979, 1981

; Last modified June 26, 1981  3:29 PM by Taft
;
; MICROCODE FOR TRIDENT DISK CONTROLLER  
; THIS MICROCODE IS WRITTEN TO OPERATE ON BOTH ALTO I AND ALTO II's.
;
;*modified to abort command chain if write-data-late occurs so as
; to avoid chaining into garbage commands.
; Generally tightened things up and made the microcode more robust.
;
;*broken into two parts (global predefs and everything else)
; to facilitate combining Trident microcode with other stuff.
;
;*modified to speed up read task main loop for Alto-IIs.
;
;*modified so un-needed words are not written into MESA's parity locations.
; The new discard locations are 177034 and 177035 (output to alto keyboard).
;
;*modified to allow safe switching of drive select between chained
; disk commands.  This mod is achieved by first checking to see if a new
; drive is specified; if so:
; Isue a TAG instruction to select the new drive.
; set the "Last Cylinder" value to -1 so a seek will allways occur
; wait for the next sector pulse to occur (on the newly selected drive)
; so we know that the Tag instruction was the last one issued, and that
; it has infact been executed
;
;*modified for running on both AltoI and AltoII's.
;
;*modified for testing of abnormal conditions in sector loop with abort and
; status in 644.  this version doesn't update 640 untill a command is complete
; so that the read task can get its dcb pointer from 640 when needed
;
;
;*Location #03 starts the microcode for sending control and data
; out to the disk controller;
;*Location #17 starts the microcode for receiving data back from
; the disk controller;
;*Location #21 starts a Pollynomial divide routine used for
; disk error recoveries;

;  TRIDENT DISK MICROCODE - TASK 03 - SERVICE OF OUTPUT FIFO
;  TRIDENT DISK MICROCODE - TASK 17 - SERVICE OF INPUT FIFO
;
;  PROCESSES A KBLK (IN PAGE 0) WHICH LOOKS LIKE:
;	640	POINTER TO DCB
;	641	LAST DRIVE SELECTED
;	642	LAST TRACK SELECTED
;	643	STATUS AT SECTOR MARK
;	644	STATUS AT COMMAND ABORT
;
;  PROCESSES A DCB WHICH LOOKS LIKE:
;	DCB	TRACK ADDRESS
;	DCB+1	HEAD(LEFT BYTE),  SECTOR(RIGHT BYTE)
;	DCB+2	DRIVE SELECT
;	DCB+3	POINTER TO NEXT DCB
;	DCB+4	ID (#122645)
; [
;	DCB+5	FIRST COMMAND
;	DCB+6	WORD COUNT
;	DCB+7	MEMORY ADDRESS FOR DATA TRANSFER
;	DCB+8	ECC0 - TO BE FILLED IN BY READ TASK
;	DCB+9	ECC1 - TO BE FILLED IN BY READ TASK
;	DCB+10	STATUS AT END OF TRANSFER
; ] repeat
;	DCB+?	Command word of zero to terminate
;	DCB+?+1	Mask of channel(s) to interrupt on
;
;
;	**SPECIAL FUNCTION 2 DEFINITIONS**
;
;    "FOO← STATUS"	READS THE STATUS		F2 = 10
;    "MD← KDTA"		READS A DATA WORD		F2 = 06,BUS = 2
;    "KTAG← FOO"	WRITES A TAG INSTRUCTION	F2 = 12
;    "KDTA← FOO"	WRITES A DATA WORD		F2 = 13
;    "WAIT"		IS IDENTICAL TO 'BLOCK'		F2 = 14 OR 15
;    "RESET"		RESET INPUT FIFO & CLR ERRORS	F2 = 16
;    "EMPTY"		WAITS FOR OUTPUT FIFO TO EMPTY	F2 = 17
;
$STATUS	$L66010, 66010, 000100;		DF2=10 (RHS)
$nSTATUS$L24010, 00000, 000100;		NF2=10 = STATUS but doesn't set BS --
;					this is for Bus ANDing
$KDTA	$L26013, 14002, 124100;		DF2=13 (LHS); BS=2  (RHS)
$KTAG	$L26012, 00000, 124000;		DF2=12 (LHS) REQUIRES BUS DEF
$WAIT	$L24014, 00000, 000000;		NF2=14
$RESET	$L24016, 00000, 000000;		NF2=16
$EMPTY	$L24017, 00000, 000000;		NF2=17

; Wakeup control:
; WAIT and EMPTY must be issued at least 2 instructions before a TASK
; in order for them to "take".
; KDTA← and ←KDTA must be issued at least 2 instructions before a TASK
; in order to affect the priority computation done by that TASK,
; but wakeups are not generated unless the FIFO has at least 4 words of
; data (read task) or at least 4 words of free space (write task).
; This permits the read/write loops to be tight, but requires some care
; at the end of a transfer.

;	**HANDY CONSTANTS**

$400	$400;
$4000	$4000;
$10000	$10000;
$177700	$177700;
$177777	$177777;
$612	$612;
$BITBUCKET $177034;	This is the address for READING the alto keyboard

;	**R REGISTERS**

$SHREG	$R14;	MUST BE A REAL 'R' REGISTER
$COMM	$R14;	SET SAME AS SHREG; USED IN BUS=0
$MTEMP	$R25;
$WCBPTR	$R71;
$LAST1	$R72;
$FROM	$R73;	
$RCBPTR	$R74;	
$FROM/R	$R75;
$LAST1/R $R76;
$AC0	$R3;
$AC1	$R2;
$NWW	$R4;
$NUM	$R2;	Note sharing with AC1; must be a real 'R' register
$REF	$R76;	Note sharing with LAST1/R

; Note: on an Alto-I it is unsafe to test BUS=0 when reading an S-register
; in the first instruction after a task switch.  Testing BUS=0 in other than
; the first instruction is OK.




;	**PREDEFINITIONS FOR TEST CONDITIONS**

!1,2,DCB+4,DONE;
!1,2,REPST,DCB+2A;	
!1,2,ID-ERR,DCB+2;
!1,2,NEWDR,DCBA;
!1,2,DCB,LOC3A;
!1,2,CHKWDL,NOWDL;
!1,2,NOWDL2,WABORT;
!1,2,SENDTRK,DCB+1L;
!1,2,CKSEC,NOWAIT;
!1,2,SCOM,SECTOR;
!1,2,WAITSEC,SECOK;
!1,2,PASTSEC,SCOMM;
!1,2,NOEMPTY,SEMPTY;
!1,2,DECODE,SNDSTAT;
!1,2,NOINT,SNDINT;
!1,2,R/WCOM?,DCB+3;
!1,2,R/WCOM, NO-R/W;
!1,2,WRITE,READ;
!1,1,NORW+1;
!1,1,WRITE+1;
!1,2,W-EVEN,W-ODD;
!1,2,CONT/W,DONE/W;
!1,2,CHECK,DCB+5;
!1,2,END/CK,DCB+5+1;
!1,2,SEND/CK,WDS/DONE;
!1,1,DCB+5+2;
!1,2,DCB+3A,WRT640;

!1,2,CK-CNT,ABORT;	*&*
!1,2,SNDERR,SEC-ERR;	*&*

;	**WRITE TASK**
;	**THIS CODE IS EXECUTED EACH SECTOR TIME**
LOC3: 	T← 2, :LOC3B;		SEE IF THERE IS A COMMAND
LOC3A:	T← 2;
LOC3B:	MAR← 642-T;	
	NOP;
	L← MD;
	T← MD, SH=0;		TEST FOR NO COMMAND ADDRESS
	WCBPTR← L, L← T, :DCB+4; [DCB+4, DONE]

; No DCB present.  Normally, just update status in KBLK.Status.
; But if KBLK.drive has its sign bit set, also select that drive.
DONE:	SH<0;			Test for new drive sel
	L← 7777 AND T, WAIT, :REPST; [REPST, DCB+2A]

REPST:	MAR← 642+1;		UPDATE THE STATUS AT 643B
	L← STATUS, TASK, :W640X;


;	**PROCESS A COMMAND**
; First, test for valid ID (seal), and check for new drive selection.
DCB+4:	T← 4;
	MAR← WCBPTR+T;		GET CHECK WORD
	MTEMP← L;		Save current drive select
	T← 122645;
	L← MD-T;
	T← 2, SH=0;		TEST FOR VALID ID
	MAR← WCBPTR+T, :ID-ERR;	[ID-ERR, DCB+2] GET DRIVE SELECT
DCB+2:	LAST1← L;		LAST1=0 signals first block of sector
	FROM← L;		FROM=0 signal previous write (=> store status)
	T← MTEMP;		TEST FOR DRIVE SEL EQ CURRENT DRIVE
	L← MD - T, T← MD;
	L← T, SH=0, TASK;
DCB+2A:	COMM← L, :NEWDR;	[NEWDR, DCBA] COMM← new drive number

; Select new drive.
; Can get here even if there is no DCB (see above).
NEWDR:	MAR← 642-1;		UPDATE KBLK+1
	T← 77400, WAIT;		Send drive select TAG and wait for next sector
	MD← COMM, L← COMM OR T;	This will make sure that everything is stable
	MAR← 642;		CLOBBER CYLINDER TO FORCE A SEEK
	KTAG← M;
	SINK← WCBPTR, BUS=0, TASK;  TEST FOR NO COMMAND IN PROGRESS
	MD← 177777, :DCB;	[DCB, LOC3A]

; WRITE TASK (cont'd)

; Fetch cylinder select and check current hardware status
DCB:	MAR← WCBPTR, :DCBB;	GET CYLINDER SELECT
DCBA: 	MAR← WCBPTR;
DCBB:	L← 400, nSTATUS;	Bus AND -- check status for write data-late
	T← 5, SH=0;
	L← MD, :CHKWDL;		[CHKWDL, NOWDL]

; Write data-late present in status.
; Inspect command and abort unless it is a diskReset.
; This is because a previous command may have dumped garbage into
; this command's disk address (by the label chaining trick).
CHKWDL:	MAR← WCBPTR+T;		Fetch header command
	COMM← L;
	T← 10;			Test reset command bit
	L← MD AND T;
	L← COMM, SH=0;

; Seek to new cylinder if different from current
NOWDL:	MAR← 642, :NOWDL2;	[NOWDL2, WABORT]
NOWDL2:	COMM← L;
	T← COMM;
	L← MD-T;
	T← 7777.T, SH=0;	TEST FOR SAME CYLINDER ADDRESS
	T← 10000 OR T, :SENDTRK; [SNDTRK, DCB+1L]

SENDTRK: L← 40000 OR T, WAIT;	WAIT UNTIL POSITIONING DONE
	MAR← 642;		UPDATE KBLK+2
	KTAG← M, TASK;
	MD← COMM;

; Now at correct cylinder.
; Select head
DCB+1L:	MAR← WCBPTR+1;		GET HEAD SELECT
	T← 177400, EMPTY;  	LEFT BYTE ONLY
	L← MD AND T, T← MD;	L← Left byte only, T← whole word
	MAR← WCBPTR+1;		Store back word with OffTrack bit stripped off
	SHREG← L LCY 8;
	L← 37777 AND T, TASK;
	MD← M;

; Wait here until write FIFO is empty before sending head select tag.
; (I think this is so that the new head select won't interfere
; with an ongoing read).

	T← 30000;
	L← SHREG OR T, TASK;
	KTAG← M, :DCB+5;	Send Head Select Tag instruction

; WRITE TASK (cont'd)

; Come here at the beginning of each "block" in the disk command:
DCB+5:	T← 5;
DCB+5+1: MAR← L← WCBPTR+T, :DCB+5+2;  [DCB+5+2, DCB+5+2] Fetch command word
DCB+5+2: WCBPTR← L;
	T← 300;
	L← MD AND T, T← MD;
	L← 7777 AND T, SH=0;	TEST FOR READ/WRITE COMMAND
	COMM← L, :CKSEC;	[CKSEC, NOWAIT]; COMM← command

; Command is read or write, may have to wait for correct sector
CKSEC:	L← LAST1;	
	L← 100, SH=0;		TEST FOR FIRST BLOCK
NOWAIT:	LAST1← L, :SCOM;	[SCOM, SECTOR]

; First block of a read/write command.  Search for correct sector
SECTOR:	T← 4;
	MAR← WCBPTR-T;		Fetch sector number from DCB
	L← LAST1-1, BUS=0;	TEST FOR SECTOR LOOP HUNG
	LAST1← L, :CK-CNT;	[CK-CNT, ABORT]
CK-CNT:	T← 17;
	L← MD AND T;
	T← 2000 OR T;		Test sector = next sector and NotReady = 0
	T← STATUS.T;
	L← M-T, WAIT;		TURN OFF WAKE UP TILL SECTOR
	T← 10000, SH=0;		TEST FOR NEXT SECTOR OK
PASTSEC: T← 10 OR T, :WAITSEC;	[WAITSEC, SECOK]

WAITSEC: TASK;			WAIT FOR NEXT SECTOR
	:SECTOR;

; Next instruction forces the controller to wait until the next sector
; pulse before executing any more commands from the FIFO.
SECOK:	KTAG← 100000;		A NON-KTAG INS IS REQUIRED NEXT!
	L← 4+T, T← 4;		L← 10014; T← 4;
	KTAG← M;		ENABLE HEAD AND RESET DISK ERR'S
	MAR← WCBPTR-T;
	KTAG← M;		3 TIMES SO HEAD IS ENABLED
	KTAG← M;		FOR 5 US.

; Here we test the sector number all over again, though we just checked it
; about 10 microinstructions ago, with no intervening TASKs.
; It really makes a difference, though!  If you don't do this you get
; occasional header errors, sector overflows, etc.  Puzzlement!
	T ←  17;
	L← MD AND T;
	T← 2000 OR T;		Test sector = next sector and NotReady = 0
	T← STATUS.T;
	L← M-T;
	SH=0;			TEST FOR NEXT SECTOR OK
	L← T← COMM, :PASTSEC;	[PASTSEC, SCOMM]

; This code is to handle errors in the sector loop, and
; other errors that cause the command chain to be aborted.
ID-ERR:	T← 20, :SNDERR;		***#20 = INVALID ID
WABORT:	T← 400, :SNDERR;	***#400 = write data-late
ABORT:	L← T← 2000, nSTATUS;	***#2000 = SEEK INCOMPLETE
	SH=0;			TEST FOR SEEK OK
	:SNDERR;		[SNDERR, SEC-ERR]
SEC-ERR: T← M+1;		***#1 = INVALID SECTOR
SNDERR:	L← 2+T, T← 2;		***#2 = ONE OF THE ABOVE
	MAR← 642+T;		Store abort code in 644
	LAST1← L, WAIT;
	L← KTAG← 0;		Reset all tags
	MD← LAST1;
WRT640:	MAR← 642-T;
	TASK;	
W640X:	MD← M, :LOC3;

; WRITE TASK (cont'd)

; Now ready to issue the new command.
SCOM:	L← T← COMM;
SCOMM:	L← 10000 OR T, SH=0;	Test for end of commands (COMM = 0)
	MAR← T← WCBPTR+1, :NOEMPTY; [NOEMPTY, SEMPTY] Fetch count or int mask

; End of commands in this DCB
SEMPTY:	KTAG← 0, L← T;
	EMPTY;			Wait til write FIFO empty before advancing
	WCBPTR← L;
	L← MD, TASK, :LINK;	Get interrupt bits

; Issue new command
NOEMPTY: KTAG← M, L← T;
	WCBPTR← L;
	L← MD + 1;		GET WORD COUNT+1
LINK: 	LAST1← L;		Save word count +1 or interrupt bits

; Note the following code gets executed even if we have reached the end
; of commands in this DCB, because we want to store status and generate an
; interrupt if the previous command was a write.  The extra KDTA← is harmless.
	MAR← WCBPTR+1;		Fetch data address
	KDTA← LAST1;		SEND WORD COUNT+1
	L← FROM;
	L← MD-1, SH=0, TASK;	TEST FOR PREVIOUS WRITE
	FROM← L, :DECODE;  	[DECODE, SNDSTAT]  FROM ← ADDRESS-1

; Either the previous command was a write or the current command is
; the first one in the DCB.  Write the status into the word before
; the start of the current block, which is the status word of the previous
; block if there was one and the DCB seal word otherwise.
SNDSTAT: L← 177740, nSTATUS;	GET JUST THE STATUS BITS
	T← 2;
	MAR← WCBPTR-T;		UPDATE STATUS SUCH THAT GOOD STATUS = 1
	L← M+1, nSTATUS;	Bus AND status again to filter out glitches
	SINK← COMM, BUS=0;
	MD← M, :NOINT;		[NOINT, SNDINT]

; The previous command was a write and was the last one in the DCB.
; Initiate interrupts as specified in the word after the zero command word.
SNDINT:	T← LAST1;		Interrupt bits fetched above
	L← NWW OR T, TASK;
	NWW← L, :DCB+3;

NOINT:	T← FROM-1, :DECOD1;
DECODE:	T← FROM-1;
DECOD1:	L← LAST1+T, TASK;
	LAST1← L;		LAST1← address of last word of data +1

; Now look at the command and decide what to do.
	T← COMM, BUS=0;		TEST FOR ALL ZERO COMMAND WORD
	L← 300 AND T, :R/WCOM?;	[R/WCOM?, DCB+3] Test write+read bits

; Nonzero command, what is it?
R/WCOM?: L← 200 AND T, SH=0;	Test write bit
	L← 4000 AND T, SH=0, :R/WCOM;  [R/WCOM, NO-R/W] Test check bit

; Non read-write command, assume it is a diskReset or diskRestore.
; For a disk rezero operation to work, the control tag line must be removed
; while leaving the rezero bit on the tag bus ie KTAG← 2.
; The RESET command will terminate the read task, possibly while it is
; in the middle of a command; it won't wake up again until a new read
; command comes along.  Therefore, we zero RCBPTR, FROM/R, and LAST1/R,
; which the read task must check upon resumption from every TASK.
NO-R/W:	RCBPTR← L;		[NORW+1, NORW+1] Resynchronize read task
NORW+1:	KTAG← 2;
	FROM/R← L;
	LAST1/R← L, RESET, :DONE/W;  RESET defines the input FIFO as empty

; WRITE TASK (cont'd)

; Read or write command, which?
R/WCOM:	T← FROM, SH=0, :WRITE;	[WRITE, READ]

; *** Write command ***
;	*** odd start address test

WRITE:	KDTA← ONE, L← ONE AND T;  [WRITE+1, WRITE+1] SEND START BIT TO DISK
WRITE+1: SH=0; 			TEST FOR EVEN WORD ADDRESS
	MAR← L← T← FROM+1, :W-EVEN; [W-EVEN, W-ODD]

W-ODD:	FROM← L, TASK, :W-ODD2;
;	***end of test

LOOP/W:	MAR← T← FROM+1;
W-EVEN:	L← LAST1-T;
	L← ONE+T, SH<0;		TEST FOR LAST WORDS
	FROM← L, :CONT/W;	[CONT/W, DONE/W]

CONT/W:	KDTA← MD, TASK;
W-ODD2:	KDTA← MD, :LOOP/W;

;	*** odd end address test IS NOT NEEDED - 
;	CONTROLLER WILL IGNORE UNEXPECTED DATA WORDS.
;	SO ONE EXTRA WORD MAY BE SENT, BUT IS OK.

DONE/W:	L← 0;			Signal that previous command was a write
	FROM← L, :DCB+5;


; *** Read command ***
; Test on check bit pending.  If not a check command, we are done with it.
READ:	:CHECK;			[CHECK, DCB+5]

; Check command.  Pour the words to be checked into the write FIFO.
; Note that the first two words of a block are always checked,
; followed by additional words until a zero word or the end of the block.
CHECK:	MAR← L← FROM+1;
	FROM← L;
	KDTA← MD;

	MAR← L← FROM+1;
	FROM← L, TASK;
	KDTA← MD;

LOOP/CK: MAR← L← T← FROM+1;
	FROM← L;
	L← LAST1-T;
	T← 5, SH<0;		TEST FOR LAST WORD
	L← MD, BUS=0, :END/CK;	[END/CK, DCB+5+1] TEST FOR ALL ZEROS DATA WORD
END/CK:	TASK, :SEND/CK;		[SEND/CK, WDS/DONE]

SEND/CK: KDTA← M, :LOOP/CK;

WDS/DONE: :DCB+5;		INSTRUCTION AFTER A TASK

; Chain to next DCB, but be careful not to get confused if the software
; has yanked the current DCB out from under us.
DCB+3:	T←2;
	MAR←642-T;		FIND THE START OF THIS DCB AGAIN
	NOP;
	L← MD;
	MAR← M+T+1, SH=0;	GET THE POINTER TO THE NEXT COMMAND
	:DCB+3A;		[DCB+3A, WRT640]
DCB+3A:	L←MD, :WRT640;		NOW PUT THIS POINTER IN 640

;	**READ TASK**

; Note that the read task is highest-priority, so there is no point
; in TASKing except in those places where our wakeup has been removed
; and we desire to wait until the next wakeup.

; The write task can cause us to abort and resynchronize upon resumption
; after any TASK.  This may be deduced from the fact that any one of
; RCBPTR, FROM/R, or LAST1/R is zero.  (Note that FROM/R = LAST1/R = 0
; is sufficient to break out of the main loops.)

;	**PREDEFINITIONS FOR TEST CONDITIONS**

!1,2,STRTBLK,DISCARD;
!1,2,CHUCK,INITRD;
!1,2,STRTRD,SKIP;
!1,2,ALTO?,R-ODDS;
!1,2,ALTOII,ALTOI;
!1,2,CONT/I,DONE/I;
!1,2,LOOP/II,DONE/II;
!1,2,CONT/II,BRK/II;
!1,2,R-DONE1,R-BRK;
!1,2,R-ODDE,R-EXIT;
!1,2,ECCERR,NOERR;
!1,2,R-EXIT2,LOC17A;
!1,2,NORINT,RINT;

LOC17:	T← 2, :LOC17B;
LOC17A:	T← 2;
LOC17B:	MAR← 642-T;
	T← 5;
	L← MD+T, BUS=0;		TEST FOR NO DCB POINTER
BLK?:	RCBPTR← L, :STRTBLK;	[STRTBLK, DISCARD]

STRTBLK: MAR← L← RCBPTR+1;
	RCBPTR← L;
	T← MD;			FIRST GET COUNT

	MAR← L← RCBPTR+1;	NOW GET THE MEMORY POINTER
	NOP;
	L← MD-1;
	FROM/R← L;		SAVE ADDRESS-1

	MAR← RCBPTR-1;
	L← FROM/R+T;
	LAST1/R← L;		LAST1/R = LAST ADDRESS
	T← 4000;
	L← MD AND T;
	L← RCBPTR+1, SH=0;	TEST FOR NO COMPAIR COMMAND
	RCBPTR← L, :CHUCK;	[CHUCK, INITRD]

CHUCK:	T← 2;	
	L← FROM/R+T;		IF COMPAIR THEN DISCARD THE
	MAR← BITBUCKET;		FIRST 2 WORDS IN THE FIFO 
	FROM/R← L;		Also increment FROM/R by 2
	MD← KDTA, TASK;
	MD← KDTA;

INITRD:	T← FROM/R;
	L← LAST1/R-T-1;
	SH<0;			TEST FOR LAST WORDS
	L← ONE AND T, :STRTRD;	[STRTRD, SKIP]

SKIP:	T← 177700, nSTATUS, :R-EXIT;

; READ TASK (cont'd)

;	*** odd start address test
STRTRD: MAR← T← FROM/R+1, SH=0;	TEST FOR EVEN WORD ADDRESS-1
	L← FROM/R, :ALTO?; 	[ALTO?, R-ODDS]

R-ODDS:	MD← KDTA, L← T;

;	*** Test for ALTO I or ALTO II
ALTO?:	MAR← 612+1;
	FROM/R← L;
	SINK← MD, BUS=0, TASK;	Test for an ALTO I
	:ALTOII;		[ALTOII, ALTOI]

;	***Now the Double-Word transfer loop for ALTO I
ALTOI:	MAR← T← FROM/R+1;
	L← LAST1/R-T-1;
	L← ONE+T, SH<0;		TEST FOR LAST WORDS
	FROM/R← L, :CONT/I;	[CONT/I, DONE/I]

CONT/I:	MD← KDTA, TASK;
	MD← KDTA, :ALTOI;

;	***Now the Double-Word transfer loop for ALTO II
ALTOII:	T← FROM/R+1;		Must do end test once before first iteration
	L← LAST1/R-T-1, T← LAST1/R;
	L← -2+T, SH<0;		LAST1/R← last word -2 for end test
	LAST1/R← L, :LOOP/II;	[LOOP/II, DONE/II]

LOOP/II: MAR← T← FROM/R+1, BUS=0;  Note this works ok on Alto-II!
	L← LAST1/R-T-1, :CONT/II; [CONT/II, BRK/II]
CONT/II: MD← KDTA;
	MD← KDTA;
	MTEMP← L, L← 0+T+1, SH<0, TASK; Load MTEMP to zero the bus
	FROM/R← L, :LOOP/II;	[LOOP/II, DONE/II]

;	*** odd end address test
DONE/II: T← LAST1/R+1, BUS=0, :R-DONE;  T←  Last address-1
DONE/I:	T← LAST1/R-1, BUS=0;	T←  Last address-1
R-DONE:	L← ONE AND T, :R-DONE1;	[R-DONE1, R-BRK]
R-DONE1: MTEMP← L, MAR← 0+T+1, SH=0; TEST FOR EVEN WORD ADDRESS
	T← 177700, nSTATUS, :R-ODDE; [R-ODDE, R-EXIT]

R-ODDE:	MD← KDTA;

;	***end of test
; Read the status twice to filter out possible glitch in status bits
; (particularly NotReady) caused by leading edge of Sector pulse.
R-EXIT:	L← STATUS AND T, TASK;
BRK/II:	FROM/R← L;

; If we get out of the read loop with RCBPTR=0, it can only happen
; if a RESET command cleared it (see write task, above).  In this
; case, the header for the next transfer is sliding into the FIFO,
; so we might as well go read it.
R-BRK:	L← RCBPTR;		Can't test with BUS=0 on Alto-I
	SH=0;

; READ TASK (cont'd)

;~~SINCE THIS MIGHT BE THE END OF READING, 
;   THE READ-TASK WAKE-UP MAY NOT BE ACTIVE,
;   SO THERE MUST NOT BE A 'TASK' UNTIL ALL PROCESSING IS DONE.
;
;~~THIS IS THE HIGHEST PRIORITY TASK, SO IT WOULDN'T HELP ANY WAY.

; What's left in the FIFO now are:
; (1) 2 words of raw checksum, which we discard;
; (2) 2 words of computed ECC, which are both zero if no error has occurred.

	MAR← BITBUCKET, :R-EXIT2; [R-EXIT2, LOC17A]
R-EXIT2: NOP;
	MD← KDTA;		READ IN 2 EXTRA WORDS
	MD← KDTA;

	MAR← L← RCBPTR + 1;  	NOW ENTER THE FIRST ECC WORD
	RCBPTR← L;
	MD← T← KDTA;		SAVE ECC0 IN T

	MAR← L← RCBPTR + 1;	NOW ENTER THE SECOND ECC WORD
	RCBPTR← L;
	MD← KDTA, L← KDTA OR T;	MD← ECC1, L← ECC1 % ECC0
	T← FROM/R+1;		Recover status word, set low bit (done status)
	MAR← L← RCBPTR+1, SH=0;	TEST FOR ZERO ECC CODE
	RCBPTR← L, L← T, :ECCERR; [ECCERR, NOERR]
ECCERR:	L← 10 OR T;		Nonzero ECC, set the ECC error bit
NOERR:	MD← M;			Store status in block

	MAR← L← RCBPTR+1;	NOW GET THE NEXT COMM
	RCBPTR← L;
	T← 100;
	L← MD AND T, BUS=0;	Test for zero command word
	MAR← RCBPTR+1, :NORINT;	[NORINT, RINT] Fetch interrupt mask (maybe)

; The previous command was the last one.  Initiate interrupts as specified
; in the word after the zero command word.
RINT:	T← NWW;
	L← MD OR T, SH=0, TASK, :LOC16X; Know L=0 here--force branch to LOC17

NORINT:	L← NWW, SH=0, TASK;	SEE IF THE NEXT COMM IS NOT A READ
LOC16X:	NWW← L, :LOC16;		[LOC16, LOC17]

; If we get here, it is presumed that another READ command
; awaits.  But if a RESET was executed when the read wakeup
; was absent (after TASK two instructions back), RCBPTR will now
; be 0, irrespective of waiting commands.
LOC16:	L← RCBPTR;		Can't test with BUS=0 on Alto-I
	SH=0, :BLK?;		Go check and discard if RCBPTR = 0

; Here if we are awakened with no DCB.  Just discard 4 words
; to make our wakeup go away.
DISCARD: MAR← BITBUCKET;
	L← 0;
	MD← KDTA;
	MD← KDTA;
	MAR← BITBUCKET;
	RCBPTR← L;
	MD← KDTA, TASK;
	MD← KDTA, :LOC17;

;************NOW START THE ECC MICROCODE ROUTINE************
; JUMPRAM(21)
;	AC0!0 = NUMBER
;	AC0!1 = REFERANCE
;	RETURNS	AC0 = NUMBER OF SHIFTS ( > 4000B => error)
; Note that this microcode shares registers with the Trident read task.
; Therefore call this only when the disk is quiet.

;	**PREDEFINITIONS FOR TEST CONDITIONS**

!1,2, ECCCON, ECCXIT;
!1,2, DOXOR, NOXOR;
!1,2, BADECC, ECCLP;


LOC21:	MAR← AC0+1;  		GET REFERENCE NUMBER
	T← 3777;		11 bits worth
	L← MD AND T;

	MAR← AC0;  		GET NUMBER
	REF← L;
	L← MD;
	NUM← L, L← 0, BUS=0, TASK, :SETAC0; Set AC0 to zero and start loop

ECCLP:	T← NUM-1;		See if NUM=REF
	L← REF-T-1;
	T← NUM+T+1, SH=0;	T← NUM LSH 1
	L← 4000 AND T, :ECCCON;	[ECCCON, ECCXIT] Exit if NUM=REF
ECCCON:	L← T, T← 4000, SH=0;	See if end-around carry
	T← 5 OR T, :DOXOR;	[DOXOR, NOXOR] T← 4005
DOXOR:	L← M XOR T;		Yes, do XOR-feedback trick
NOXOR:	NUM← L;
	L← AC0-T;		Error if more than 4005 iterations
	L← AC0+1, SH<0, TASK;	Increment iteration count
SETAC0:	AC0← L, :BADECC;	[BADECC, ECCLP]

ECCXIT:	:EXITRAM;
BADECC:	:EXITRAM;