; MEMTST3.ASM
; ------------
;
; A ROM-able Memory-Test for the Altair 8800
; by Frank McCoy, Copyright 2007
; Version 3.4
;
; This memory-test was designed with four
; objectives in mind:
;
; 1.  It must be *thorough*:
;     A. Check for pattern sensitivity
;     B. Check with more than one type of pattern
;     C. Check for speed-sensitive memory
;     D. Check for data retention
;     E. Check for adjacent pattern interference
;     F. Check for addressing errors
;     G. Check for address interference
;
; 2.  It must be *fast*:
;     A. Do all of its test in less than
;        1 second per 4K board
;
; 3.  It must be easy to use:
;     A. Interface to MITS serial-I/O boards
;     B. Give prompts where needed
;     C. Input the range-to-be-tested in Hex
;     D. Output the address of errors in Hex
;     E. Tell what the test and error bytes were
;     F. Tell which bits were in error
;
; 4.  It must be *completely* ROM-able:
;     A. Must use *no* RAM except that under test
;     B. All variables to be held in CPU registers
;     C. Use no stack in RAM for subroutines
;        This leaves the stack-pointer available for other use
;     E. It must be 1024 bytes long or less,
;        to fit in a single 2708 ROM chip or four 1702 chips
;
; 5,  It must support various hardware configurations:
;     A. It must use only basic 8080 registers and instructions
;     B. Support MITS single SIO board, 2SIO, and PT 3P+S boards
;     C. Support 3 standard monitors plus basic TTY.
; The above is done using the sense-switches
; All subroutine calls in this program are unconditional
; jumps not calls.  Prior to jumping to the desired subroutine,
; the calling routine must point the stack-pointer at a location
; in ROM containing the desired return-address (or addresses)
;
; This is done so that there is no need for a stack-area
; in working RAM memory.  This also leaves the stack-pointer
; Available as a working register when not used for returns.
;
; Every test is repeated with the complement of that test,
; For a total of ten tests (0 through 9) where every bit
; in every byte is tested ten times; five times in each
; state of one or zero.
;
; The first (and most exhaustive) set of tests (0-5) use three
; different pseudo-random shift-register patterns and then
; the complement of each pattern, for a total of six passes
;
; The second set (6,7) use alternating ones and zeroes in a
; "checkerboard" pattern and (again) its inverse.
;
; The third (and last) set (8,9) use all ones and then
; all zeroes; leaving memory cleared.
;
; The COLDST routine turns off the Cassette recorder
; (necessary on my original machine)
; Initializes both 2SIO boards, and sends a
; Clear-Screen message to the terminal selected.
;
;--------------------------------
; MITS Altair or Imsai 8800 I/O ports used:
; If your system has one of these board's I/O addressed at a
; non-standard location, then just change the appropriate
; definition of the three below; and reassemble the program.
;
SIOBRD	EQU	0	; MITS Single-SIO board usually at port-0
SIO2BRD	EQU	16	; MITS Dual-SIO board usually at port-16
PTPLUSS	EQU	0	; Processor-Technology 3P+S board at port-0
;
;
; The bits input from the SENSE switches are defined as follows:
;    00000000B
;        ||||
;        ||++----Bit-0 & Bit-1 form a two-bit number
;        ||      0 = MITS SIO  board at Port-0  (Default)
;        ||      1 = MITS 2SIO board at Port-16
;        ||      2 = MITS 2SIO board at Port-18
;        ||      3 = Processor-Technology 3P+S board at Port-0
;        ||
;        ++----- Bit-3 & Bit-4 form a two-bit number
;                0 = No screen commands; Use CRLF (Default)
;                    Basic TTY, or any that doesn't match below
;                Types 1-3 are "glass teletypes that support
;                Clear-Screen & Back-Space-over-character control
;                1 = Lear Siegler AMD-3A or compatible
;                    (ADM-22, ADM-52, Soroc, TVI-925)
;                2 = VT-100 or compatible monitor
;                    (VT-52, ANSI)
;                3 = South West Technical Products CT-1024 TVT
;
; Thus default (00H) would assume a TTY
; with a single MITS SIO board at port-0 (As defined below)
; A sense-switch setting of 09H (00001001B) would
; set the test for a VT-100 OR ANSI compatible monitor
; with a 2SIO board at port-16
;
SENSE	EQU	0FFH	; Sense-Switches at 0xFF on Altair 8800
;
SIOTP	EQU	01H	; Serial-I/O type increment (0,1,2,3)
SIOMSK	EQU	SIOTP*3	; Bit-mask for serial-I/0 board type
PLUSTRM	EQU	SIOMSK	; Processor Technology 3P+S = Last of 3
TRMTP	EQU	04H	; Terminal-type increment   (0,4,8,C)
TRMMSK	EQU	TRMTP*3	; Bit-mask for terminal-type
;
;
; From the above, we get the following:
;
SIOS	EQU	SIOBRD	; MITS Single SIO board Status
SIOD	EQU	SIOS+1	; MITS Single SIO board Data
;
SIOS0	EQU	SIO2BRD	; MITS 2SIO Board Lower Status
SIOD0	EQU	SIOS0+1	; MITS 2SIO Board Lower Data
SIOS1	EQU	SIOS0+2	; MITS 2SIO Board Upper Status
SIOD1	EQU	SIOS1+1	; MITS 2SIO Board Upper Data
;
PTSS	EQU	PTPLUSS	; Processor-Technology 3P+S Serial Status
PTSD	EQU	PTSS+1	; Processor-Technology 3P+S Serial Data
; I/0 byte equates:
;
CLEAR	EQU	03H	; Clear 2SIO boards
INTCI	EQU	11H	; Sets clock and interupts on 2SIO boards
;
;--------------------------------
; ASCII Control-Characters used
;
BEL	EQU	07H	; Control-G Bell character
BS	EQU	08H	; Control-H Backspace
LF	EQU	0AH	; Control-J Line-Feed
CR	EQU	0DH	; Control-M Carriage-Return
DC2	EQU	12H	; Control-R Back-Cursor  For SWTP TVT
DC3	EQU	13H	; Control-S Home-up      For SWTP TVT
SYN	EQU	16H	; Control-V Clear-Screen For SWTP TVT
SUB	EQU	1AH	; Control-Z Clear-Screen For ADM-3A
ESC	EQU	1BH	; Control-[ Escape character
RS	EQU	1EH	; Control-^ Home-up      For ADM-3A
;
;
;--------------------------------
;       Main operating system
;--------------------------------
;
; User-defined monitor routine
; Or just point at COLDST if you don't have one
;
;MONITOR:	EQU	0E126H	; My monitor
MONITOR:	EQU	0FF00H	; Altair Boot ROM
;
	ORG	0F000H	; Probably a ROM
;	ORG	100H	; CP/M Program
;
; Cold-Start ("Boot").
;
COLDST:	IN	SENSE	; Check which I/O board type
	ANI	TRMMSK	; Only check board bits
	JZ	CLRST	; If MITS single SIO board then skip
	SUI	PLUSTRM	; Or if Processor-Technology 3P+S
	JZ	CLRST	; Then don't bother to setup 2SIO
	MVI	A,CLEAR	; 03 hex is clear for 2SIO ports
	OUT	SIOS0	; Clear lower 2SIO board Status
	OUT	SIOS1	; Clear upper 2SIO board Status
	MVI	A,INTCI	; 11 hex sets the clock and interrupts
	OUT	SIOS0	; Setup lower 2SIO board Status
	OUT	SIOS1	; Setup upper 2SIO board Status
ESCCHK:	CPI	ESC	; Got here from error-routine?
	JZ	MONITOR	; Done if escape
CLRST:	MVI	L,0	; Set don't print "OK" message (L=0)
;
START:	MOV	E,L	; "MEMORY OK" message flag to E
	IN	SENSE	; Get which terminal-type
	LXI	SP,INTS	; If zero, init TTY (CRLF)
	ANI	TRMMSK	; Test upper bits
STRTLP:	POP	H	; Get message pointer
	JZ	BEGIN	; Print right message
	SUI	TRMTP	; Is it next terminal type?
	JMP	STRTLP	; Check until found
;
INTS:	DW	CRLF	; 00H = CRLF for TTY
	DW	INTIT	; 04H = Clear-Screen for ADM-3A
	DW	INTVT	; 08H = Clear-Screen for VT-100
	DW	INTTV	; 0CH = Clear-Screen for TVT
;
BEGIN:	LXI	SP,STCK	; Setup POP stack of return-addresses
	JMP	MSGOUT	; Print Clear-screen
;
BEGAN:	POP	H	; Get "MEMORY OK" message
	INR	E	; Check if ran test already
	RNZ		; If not then skip "OK" message
	JMP	MSGOUT	; OTOH, if good run, then print it
;
NXTLUP:	MOV	B,D	; Put DE into BC
	MOV	C,E	; (Save previous Address in BC)
LOOP:	POP	H	; Main program loop
	JMP	MSGOUT	; Print message and return
;
STCK:	DW	BEGAN	; And check for OK message
	DW	OKM	; If good, then "MEMORY OK" message
	DW	LOOP	; And start main program
	DW	PRMT1	; Point to "START"
	DW	LOOP	; Get next message pointer
	DW	ADRMS	; Point to "ING ADDRESS? "
	DW	INHEX	; Get start-address in DE
	DW	NXTLUP	; Move start-address to BC and continue
	DW	PRMT2	; Point to "  END"
	DW	LOOP	; Get next message pointer
	DW	ADRMS	; Point to "ING ADDRESS? "
	DW	INHEX	; Get end-address in DE
	DW	LOOP	; Get next message pointer
	DW	PASMS	; Point to "PASS:"
	DW	GOTADS	; Start now in BC, end in DE
;
GOTADS:	XCHG		; Swap DE and BC registers
	MOV	D,B	; So start-address and end-address
	MOV	E,C	; are in right place for testing
	MOV	B,H	; to match random-pattern routines
	MOV	C,L	;
	XRA	A	; Doing pass-0
;
;  DE register contains Starting-Address
;  BC register contains Ending-address
;  A  register contains Pass-Number
;
PRTPSN:	MVI	L,-1	; If got pass-ten, is good test
	CPI	10	; Pass-10 is finished
	JNC	START	; Done if all tests ran
	LXI	SP,PTPS	; Set up to print Pass-Number
	LXI	H,BSMS	; Point to BS offset table
BYTPNT:	ADD	A	; Pass-number offset * 2
	ADD	L	; Add to BS table
	JNC	NINCPS	; If no carry,
	INR	H	; Then don't increment high-byte
NINCPS:	MOV	L,A	; But always save new low-byte
	MOV	A,M	; Check byte to be printed
	ANA	A	; If null
	RZ		; Don't print null in BS table
	CPI	BS	; See if bbackspace
	JNZ	OUTCH	; Print '0'-'A' if not
	IN	SENSE	; Get terminal type
	ANI	TRMMSK	; Check if TTY
	RZ		; Don't print BS if TTY
	JMP	OUTCH	; Print Backspace if glass terminal
;
PTPS:	DW	PRTPS	; Return after printing Backspace
	DW	SETLUP	; And then start test
;
;
;  The SETLUP routine loads the appropriate starting data-pattern
;  into either the HL register (for random patterns 0-5) or
;  into the DE register (for fixed-data patterns 7-9)
;  The starting address in DE will be put into the stack-pointer
;  If fixed-data pattern, the starting-address in DE
;  will be moved to HL, while DE will contain pattern
;  instead of HL
;
;  On Entry:
;  DE register contains Starting-Address
;  BC register contains Ending-address
;  A  register contains Pass-Number
;
SETLUP:	MOV	H,D	; Copy start-address into HL
	MOV	L,E	;
	SPHL		; And save start in stack-pointer
	SUI	'0'	; Strip off ASCII offset
	MOV	E,A	; Put in E for *3 & save pass-count
	ADD	E	; Make offset two   bytes
	ADD	E	; Make offset three bytes
	LXI	H,RBYTS	; Point at initial random-pattern
	ADD	L	; Add offset into patterns
	JNC	NOINC	; Skip if no carry
	INR	H	; Carry into upper byte
NOINC:	MOV	L,A	; Now got address of pattern in HL
	MOV	A,M	; Get low-byte of pattern
	INX	H	; Point high-byte
	MOV	H,M	; Then get high-byte
	MOV	L,A	; Now HL contains pattern
	MOV	A,E	; Get pass-number back for R/F test
	XCHG		; Move pattern into DE
	LXI	H,0	; Clear HL to
	DAD	SP	; Get start-address back in HL
	CPI	6	; See if random passes done
	JNC	LOOP3	; If so, then go store fixed data
	XCHG		; Else swap DE and HL for random
;
;
; LOOP1 Stores pseudo-random patterns throughout the memory
; test-area.  By using a psuedo-random shift-register pattern,
; most pattern-sensitive and addressing errors will be caught.
; as no two words in memory have the same two bytes in them
; The numbers/patterns are stored using the 8080 "PUSH H"
; op-code and recovered with "POP H" to get the pattern back.
; These two instructions store and recover two adjacent bytes
; in the fastest possible manner with the 8080 CPU.
; This helps check for timing sensitive problems.
;
; On entry:
; Stack-Pointer contains the current storage-address
; HL contains the current working data-pattern
; DE Contains the start-address
; BC contains the end-address
;
; Since HL gets used in calculating when to stop storing,
; It's recovered from memory with POP H to calculate next
; random data-pattern word.
;
; The pseudo-random pattern shifts the whole word one bit left;
; then Exclusive-ORs in what used to be uppermost bit (Bit 15),
; Next uppermost bit (Bit-14) and next-to lowermost bit, (Bit-1)
; These are combined and placed in new Bit-0.
; This is about the simplest 16-bit CRC pattern to calculate
; that doesn't repeat within the 8080 address-range.
;
; X^16 + X^15 + X^2
;
; Almost all other 16-bit CRC calculations use *four* bits + 1
;
; For example: CCITT CRC = X^16 + x^12 + X^5 + 1
;
; This is much simpler, faster, shorter; and since we're dealing
; with parallel media, not serial, just as good for our needs.
; A graphic example is given below:
;
; Shift word left          <-<-<-<-<-<-<-<-
;                          0000000000000000<-+  Shift in new bit-0
; Logical XOR of old bits: ||            |   |
; 15,14,1 = new bit-0      ++------------+---+
;
; Thus 0000000000000001 => 0000000000000011 => 0000000000000110
;   => 0000000000001100 => 0000000000011001 => 0000000000110011
; This is symmetric (and chosen to be).
; Thus 1111111111111110 => 1111111111111100 => 1111111111111001
;   => 1111111111110011 => 1111111111100110 => 1111111111001100
;
; The CRC repeats with a period of 32767 counts;
; Thus it will never repeat in the 8080 address-space;
; Needing 65534 bytes to repeat; and the test program
; takes 1024 bytes in ROM that cannot be tested while running.
; The "random" starting patterns are chosen to match this CRC.
; The patterns FFFF and OOOO repeat forever;
; and should not be used with this CRC generator
;
; The pattern-set starting with 0001H will never match any
; of the patterns in the FFFEH set, and vice-versa.
;
LOOP1:	INX	SP	; Pre-increment stack-pointer two bytes
	INX	SP	;
	PUSH	H	; Save two-byte data pattern in RAM
	POP	H	; Now restore stack-pointer
	LXI	H,0	; Clear HL to
	DAD	SP	; To get current working address in HL
	MOV	A,C	; Then compare with end-address
	SUB	L	;
	MOV	A,B	;
	SBB	H	;
	JC	DONE1	; If past end, then go to next test
	DCX	SP	; Point back at bytes just stored
	DCX	SP	;
	POP	H	; Put them in HL for manipulation
;
; Following computation does CRC shift left for random pattern
;
	DAD	H	; Shift left 1 bit
	RAR		; Shift in carry to Bit-15
	XRA	H	; And XOR with Bit-15 (what was Bit-14)
	RLC		; So get Bit-15 + Bit 14
	RLC		; And shift it left-around byte-only
	RLC		; to bit-2 position (what was bit-1)
	XRA	L	; XOR old Bits-15, 14 and Bit-1
	RRC		; Move XORed bits down to Bit-0
	RRC		; (Which is now cleared by double-add)
	ANI	1	; And off and remove other bits
	XRA	L	; XOR with cleared bit-0 and byte
	MOV	L,A	; Store new bit-0 (with byte)
	JMP	LOOP1	; And continue storing.
;
; DONE1 checks to see which random data-pattern we're on
; and then sets up to do next data-pattern.
;
; On entry:
; DE contains start-address of test
; BC contains end-address of test
;
; On exit to LOOP2:
; Stack-pointer now has Start-Address again
; DE Unchanged
; BC Unchanged
; 
DONE1:	MOV	L,E	; Copy original start-address
	MOV	H,D	; Into HL
	SPHL		; And back into stack-pointer for save
	LXI	H,RBYTS	; Point at random pass-byte table
DNLP1:	LDAX	D	; Get first byte of pattern
	CMP	M	; Does low byte compare with pass-byte?
	INX	H	; Bump to upper byte for comparison
	JNZ	CKLP1	; No, skip upper compare
	INX	D	; Point second byte of memory
	LDAX	D	; Get it
	CMP	M	; Does high byte compare with pass-byte?
	JNZ	ERRORD	; Error if low byte matches but not high
	DCX	D	; Restore pointer
CKLP1:	INX	H	; Bump to pass-number
	MOV	A,M	; Get pass-number
	INX	H	; Bump to next byte for comparison
	JZ	SETLP2	; If match, then got pass-data
	CPI	6	; Check if at end of random patterns
	JC	DNLP1	; Continue until match or end
	MVI	A,001H	; Else is error, and likely first byte
	JMP	ERRORE	; So report it that way	
;
SETLP2:	LXI	H,0	; Set up to
	DAD	SP	; Restore start-pointer to DE
	XCHG		;
;
;
; LOOP2 continues through rest of memory to see if it matches
;
; Stack-Pointer contains the current retrieval-address
; HL contains the current working data-pattern
; DE Contains the start-address
; BC contains the end-address
;
; Only HL, A and the stack-pointer are changed in this routime
;
; Following computation does CRC shift left for random pattern
;
LOOP2:	POP	H	; Get back known-good data pattern
	DAD	H	; Shift left 1 bit
	RAR		; Shift in carry to Bit-15
	XRA	H	; And XOR with Bit-15 (what was Bit-14)
	RLC		; So get Bit-15 + Bit 14
	RLC		; And shift it left-around byte-only
	RLC		; to bit-2 position (what was bit-1)
	XRA	L	; XOR old Bits-15, 14 and Bit-1
	RRC		; Move XORed bits down to Bit-0
	RRC		; (Which is now cleared by double-add)
	ANI	1	; And off and remove other bits
	XRA	L	; XOR with cleared bit-0 and byte
	MOV	L,A	; Store new bit-0 (with byte)
;
; OK, got next word; so test for errors
;
	DCX	SP	; Want byte in A
	POP	PSW	; Get next byte from memory
	CMP	L	; Is low-byte right?
	JNZ	ERRORX	; If error, fix pointers
	DCX	SP	; Only want one byte
	POP	PSW	; Get next byte from memory
	CMP	H	; Is high-byte right?
	JNZ	ERRORY	; If error, fix pointers
;
	LXI	H,0	; Clear HL to
	DAD	SP	; To get current working address in HL
	MOV	A,C	; Then compare with end-address
	SUB	L	;
	MOV	A,B	;
	SBB	H	;
	JC	DONE2	; If past end, then go to next test
	DCX	SP	; Back up to last known good word
	DCX	SP	; 
	JMP	LOOP2	; And continue checking
;
;
; DONE2 checks to see which pass or test we're on
; by comparing first two bytes in test-area with
; entries in pattern-table to find a match.
; If found, it exits to PRTPSN with next pass-number
;
; On entry:
; DE contains start-address of test
; BC contains end-address of test
;
; On exit to PRTPSN:
; Stack-pointer now has Start-Address copied from DE
; DE Unchanged
; BC Unchanged
; A  Contains desired next pass number
;
DONE2:	MOV	L,E	; Get original start-address
	MOV	H,D	; Into HL
	SPHL		; And back into stack-pointer for save
	LXI	H,RBYTS	; Point at random pass-byte table
;
DNLP2:	LDAX	D	; Get first byte of pattern
	CMP	M	; Does low byte compare with pass-byte?
	INX	H	; Bump to upper byte for comparison
	JNZ	CKLP2	; No, skip upper compare
	INX	D	; Point second byte of memory
	LDAX	D	; Get it
	CMP	M	; Does high byte compare with pass-byte?
	JNZ	ERROR	; Error if low byte matches but not high
	DCX	D	; Restore pointer
CKLP2:	INX	H	; Bump to pass-number
	MOV	A,M	; Get pass-number
	INX	H	; Bump to next byte for comparison
	JZ	PRTPSN	; If match, then got pass-number
	ANA	A	; Check if at end yet
	JNZ	DNLP2	; Continue until match or end
;
; All the tests were good; but no pattern matched.
;
; If we get past the end of the table (0 byte as pass)
; Then is BIG error; as first two bytes should have
; matched at least one pattern.  (Or at least the FIRST
; byte should have matched anyway.)  This is especially
; true, since there's already been a check earlier
; for skipping past the end of the random-pattern table
; when doing random-pattern tests.  THEN, it just means
; there was probably an error in byte-0; so it's reported
; that way.  We should never get to this error; but with
; really flaky memory it's possible; so the test for
; that kind of program error is made.
;
	LXI	H,BADPG	; Program-Error
	JMP	ERROUT	; Print error-message and exit
;
; Random pattern byte-table (Contents of first stored byte)
;
RBYTS:	DW	00001H	; 0001H then pass-0
	DB	1	; So go do Pass-1
	DW	0FFFEH	; Then do inverse of 0001
	DB	2	; On pass-2
	DW	0209AH	; 1/3
	DB	3	; Repeat for two more random patterns
	DW	0DF65H	; Inv 
	DB	4	; Picked Offset by prime divisors
	DW	0BA2BH	; 3/5
	DB	5	; Into pattern-space
	DW	045D4H	; Inv
	DB	6	; After Pass-6, shift to fixed patterns
;
; Fixed pattern byte-table: Pattern stored througout memory
;
FBYTS:	DW	0AA55H	; First fixed "checkerboard" pattern
	DB	7	;
	DW	055AAH	; Then inverse checkerboard
	DB	8	;
	DW	0FFFFH	; Then all ones
	DB	9	;
	DW	00000H	; Finally, clear and test with zeroes
	DB	10	;
	DW	00001H	; If we get *here* is BIG error, since
	DB	0	; Pattern at start should match something
;
;
; LOOP3 stores contents of DE throughout memory; bytes alternating
;
; On entry:
; Stack-pointer holds saved start-address
; DE contains fixed data-pattern
; HL contains current storage address
; BC register points at end.
;
LOOP3:	MOV	M,E	; Store Low-byte
	INX	H	; Point next
	MOV	M,D	; Store High-byte
	INX	H	; Point next
	MOV	A,C	; Check for past end
	SUB	L	;
	MOV	A,B	;
	SBB	H	; Compare if HL now >= end
	JNC	LOOP3	; If not, then continue storing
;
	LXI	H,0	; Clear HL to
	DAD	SP	; Get back start-pointer in HL
;
; LOOP4	checks the contents of DE against memory; bytes alternating
;
; On entry:
; Stack-pointer holds saved start-address
; HL points at start
; DE contains pattern to be checked
; BC register points at end.
;
LOOP4:	MOV	A,E	; Low byte in A for testing
	MOV	E,D	; And swap D&E registers
	MOV	D,A	;
	CMP	M	; Check if this byte matches
	JNZ	ERROR	; If not, then report error
	INX	H	; Point at next byte
	MOV	A,C	; Check for past end
	SUB	L	;
	MOV	A,B	;
	SBB	H	; Compare if HL now >= end
	JNC	LOOP4	; If not, then continue comparing
;
; This section finds out which of last four passes we're in
; And sets up for next one ... Or exits, if done.
;
	LXI	H,0	; Set up to
	DAD	SP	; Recover start-pointer from stack
	XCHG		; And into DE for compatibility
	LXI	H,FBYTS	; Point at start of fixed-data bytes
	JMP	DNLP2	; Continue until match or end
;
;
; The ERROR routine prints out that there has been an error at
; the address in the H&L registers; and gets that byte.
; It then reports the test-byte in the accumulator.
; Next, the error-byte pointed-to by HL
; Finally, it prints out the bits differing in the two bytes.
;
; Various other "ERROR?" entry-points just restore or change
; The various registers to match that condition.
;
ERRORX:	MOV	B,L	; Test-byte was in L
	JMP	ERRORZ	;
;
ERRORY:	MOV	B,H	; Test-byte was in H
ERRORZ:	MOV	C,A	; Error-byte was in A
	DCX	SP	; Back up pointer to actual byte
	LXI	H,0	; Set up to
	DAD	SP	; Move stack-pointer to HL for error
	JMP	ERRORC	; Got error-byte; don't need to get it
;
ERRORD: MOV	C,A	; Error-byte in A
	MOV	B,M	; Test-byte in ROM memory
	JMP	ERRORR	; So got it
;
ERRORE:	XCHG		; If DE points at error instead
ERROR:	MOV	C,M	; Get and save error-byte quickly
	MOV	B,A	; Save test-byte and error-byte in BC
ERRORC:	XCHG		; Save error-address in DE
ERRORR:	LXI	SP,ERST	; Point at error-handler return-stack
ERLP0:	POP	H	; Get message-pointer in HL for MSGOUT
	JMP	MSGOUT	; Print it and return
;
ERLP1:	MOV	D,E	; Move low-byte to high-byte for printing
ERLP2:	MOV	A,D	; Get current byte into A for shift.
	RRC		; Shift to print upper nibble
	RRC		;
	RRC		;
	RRC		;
	JMP	PRTNIB	; And then print it
;
ERLP3:	MOV	A,D	; Get current byte
	JMP	PRTNIB	; And then print it
;
ERLP4:	MOV	D,B	; Put expected and recieved bytes
	MOV	E,C	; Into DE for printing also
	JMP	ERLP2	; and print first set.
;
ERST:	DW	ERMS1	; "    ERROR BYTE AT: "
	DW	ERLP2	; Print upper nibble
	DW	ERLP3	; Print second nibble
	DW	ERLP1	; Print third nibble
	DW	ERLP3	; Print low nibble
	DW	ERLP0	; Print message next
	DW	ERMS2	; "    TEST"
	DW	ERLP0	; Print message next
	DW	ERMS4	; " BYTE WAS: "
	DW	ERLP4	; Print high nibble of test-byte
	DW	ERLP3	; Print low nibble of test byte
	DW	ERLP0	; Print message next
	DW	ERMS3	; "   ERROR"
	DW	ERLP0	; Print message next
	DW	ERMS4	; " BYTE WAS: "
	DW	ERLP1	; Print high nibble of error-byte
	DW	ERLP3	; Print low nibble of error byte
	DW	ERLP0	; Print message next
	DW	ERMS5	; "BITS IN ERROR ARE: "
	DW	PRTBIT	; Done ... go print bits in error
;
;
; The PRTBIT routine prints the bits-in-error, where
; The expected Data is in register B; and
; The recieved Data is in register C.	
;
PRTBIT:	MOV	A,B	; Get expected data
	XRA	C	; And compare with recieved data
	MOV	C,A	; Save compared data
	MVI	B,80H	; Put rotating bit-mask in B
	MVI	E,8	; Current bit-count
	MVI	D,-1	; NO bits printed yet
;
; In the BITLUP routine:
; B contains rotating bit-mask to check each bit.
; C contains XOR of expected and recieved data.
; D contains flag of whether we've previously printed a bit
; E contains bit-number of mask, plus 1
; 
BITLUP:	DCR	E	; Decrement bit-character
	MOV	A,B	; Get current mask
	ANA	C	; Check if error in that bit
	JZ	BITOK	; Skip if not in error
	LXI	SP,BTST	; Want return to routine after print
	INR	D	; Skip ", " if none printed yet
	RZ		; Skip over comma-space stuff
	LXI	H,COMSP	; Point at comma-space msg
	JMP	MSGOUT	; and print ", "
;
GOTSPC:	MOV	A,E	; Get bit-number in error
	JMP	PRTNIB	; and print that.
;
BTST:	DW	GOTSPC	; And back to print bit-number
	DW	BITOK	; Return to bit-loop
;
BITOK:	MOV	A,B	; Get bit-mask
	RRC		; Move it one bit to the right
	MOV	B,A	; Save new mask
	JNC	BITLUP	; Until all bits tested
;
	LXI	H,CRLF	; Point CRLF message
ERROUT:	LXI	SP,BDND ; Done
	JMP	MSGOUT	; Print message, wait for input,
;
BDND:	DW	INCH	; Wait for operator input
	DW	ESCCHK	; And start over.
;
;
; The INHEX routine gets up to four characters of hex address
; A carriage-return terminates input.
; The routine accepts either uppercase or lowercase
; 'A'-'F' or 'a'-'f'
; Hitting escape exits to Monitor (YOU define yours.)
; No check is made for overflow; so extra input pushes out old.
; Illegal, non-hex characters send the program back to START
;
; On entry the stack-pointer should point to a word in
; ROM memory to be POP-ed back onto the program-counter
;
; HL is used in routine to save the stack-pointer
; BC is unchanged
; DE returns hex address input by user
; A returns with CR in accumulator and zero status
;
INHEX:	LXI	D,0	; Clear number at start.
	LXI	H,0	; Clear HL to
	DAD	SP	; Save stack-pointer
HEXLUP:	LXI	SP,HXST	; Point return into INHEX routine
;
; The INCH or input-character routine gets a character
; from the appropriate terminal.
; The character is then echoed to the same port
;
; On entry the stack-pointer should point to a word in
; ROM memory to be POP-ed back onto the program-counter
;
; BC, DE, and HL are unchanged.
; A returns with input character high-bit masked off
;
INCH:	IN	SENSE	; See which I/O board is being used
	ANI	SIOMSK	; Look at only I/O board bits
	JZ	INP0	; If zero, then single SIO board
	DCR	A	; If one,  then lower 2SIO board
	JZ	INP16	;
	DCR	A	; If two,  then upper 2SIO board
	JZ	INP18	; Else, must be       3P+S board
;
INPPS:	IN	PTSS	; Get the status 3P+S board
	ANI	40H	; See if ready for output
	JZ	INPPS	; Wait until character input
	MOV	A,M	; OK, now get the character
	IN	PTSD	; Get character from operator
	OUT	PTSD	; and echo to terminal
	ANI	07FH	; Ignore upper bit.
	RET		; Now have character in A
;
INP16:	IN	SIOS0	; Get the status of lower 2SIO board
	RRC		; See if character ready yet
	JNC	INP16	; Wait until character input
	IN	SIOD0	; Get character from operator
	OUT	SIOD0	; and echo to terminal
	ANI	07FH	; Ignore upper bit.
	RET		; Now have character in A
;
INP18:	IN	SIOS1	; Get the status of upper 2SIO board
	RRC		; See if character ready yet
	JNC	INP18	; Wait until character input
  	IN	SIOD1	; Get character from operator
	OUT	SIOD1	; and echo to terminal
	ANI	07FH	; Ignore upper bit.
	RET		; Now have character in A
;
INP0:	IN	SIOS	; Get status of single SIO board
	RRC		; Check low-order bit
	JC	INP0	; Wait until character input
	IN	SIOD	; Get character from operator
	OUT	SIOD	; and echo to terminal
	ANI	07FH	; Ignore upper bit.
	RET		; Now have character in A
;
HXST:	DW	GETCHR
;
GETCHR:	SPHL		; Restore stack-pointer
	CPI	CR	; End of input?
	RZ		; Yes, done
	CPI	'0'	; See if less than '0'
	JC	ESCCHK	; No ... Is error
	CPI	'9'+1	; See if number
	JC	NUMIN	; Number, if '0' - '9'
	CPI	'a'	; See if lowercase
	JC	CHKUPR	; If not, then check uppercase
	SUI	'a'-'A'	; If lowercase, conver to upper
CHKUPR:	CPI	'A'	; See if less than 'A'
	JC	ESCCHK	; Exit if input error
	CPI	'F'+1	; See if in 'A' to 'F'
	JNC	ESCCHK	; Exit if input error
	SUI	'A'-'0'-10	; Is 'A' to 'F'
NUMIN:	SUI	'0'	; Is '0' to '9'; so justify
;
GOTVAL:	XCHG		; Swap value into HL for shift-left
	DAD	H	; Shift left one
	DAD	H	; Shift left two
	DAD	H	; Shift left three
	DAD	H	; Shift left four
	ADD	L	; Put in new nibble
	MOV	L,A	; New result back into HL
	XCHG		; Then back into DE
	JMP	HEXLUP	; Go get next character
;
;
; The PRTNIB Routine prints the hex value of the
; lower nibble in the A register.
;
; On entry the stack-pointer should point to a word in
; ROM memory to be POP-ed back onto the program-counter
; 
; On exit, HL will point to memory location containing
; ASCII representation of nibble.
; A register will contain that same ASCII character.
; ('0' for 0, '1' for 1 ... 'F' for 15)
; BC and DE registers unchanged.
;
PRTNIB:	ANI	0FH	; Ignore upper bits
	LXI	H,HEXMS	; Point at "0123456789ABCDEF"
	JMP	BYTPNT	; And print character for 0-F
;
;
PRTPS	INX	H	; Point at Pass-Number after BS
;
;
; The OUTCH or out-character routine outputs one byte
; of data pointed-to by the HL register pair
;
; On entry the stack-pointer should point to a word in
; ROM memory to be POP-ed back onto the program-counter
; 
; On exit, the A register will contain the printed character.
;
;
OUTCH:	IN	SENSE	; See which I/O board is being used
	ANI	SIOMSK	; Look at only I/O board bits
	JZ	OUT0	; If zero, then single SIO board
	DCR	A	; If one,  then lower 2SIO board
	JZ	OUT16	;
	DCR	A	; If two,  then upper 2SIO board
	JZ	OUT18	; Else, must be       3P+S board
;
OUTPS:	IN	PTSS	; Get the status 3P+S board
	RLC		; See if ready for output
	JNC	OUTPS	; Wait until last charater out
	MOV	A,M	; OK, now get the character
	OUT	PTSD	; and print it
	RET		; Done
;
OUT16:	IN	SIOS0	; Get the status lower 2SIO board
	ANI	02H	; See if ready for output
	JZ	OUT16	; Wait until last charater out
	MOV	A,M	; OK, now get the character
	OUT	SIOD0	; and print it
	RET		; Done
;
OUT18:	IN	SIOS1	; Get the status of upper 2SIO board
	ANI	02H	; See if ready for output
	JZ	OUT18	; Wait until last charater out
	MOV	A,M	; OK, now get the character
	OUT	SIOD1	; and print it
	RET		; Done
;
OUT0:	IN	SIOS	; Get status of port-0
	RLC		; Check high-order bit
	JC	OUT0	; Wait until last charater out
	MOV	A,M	; OK, now get the character
	OUT	SIOD	; and print it
	RET		; Done
;
;
; The MSGOUT routine prints what is pointed-to by the
; HL register-pair, until a zero-byte is found.
;
; On entry the stack-pointer should point to a word in
; ROM memory to be POP-ed back onto the program-counter
;
; HL points to zero-byte at end of message on exit
; BC, and DE are unchanged.
; A is returned as zero and zero status
;
MSGOUT:	MOV	A,M	; Get next byte from memory
	ANA	A	; Test if end of message
	RZ		; Done if zero-byte
	IN	SENSE	; See which I/O board is being used
	ANI	SIOMSK	; Look at only I/O board bits
	JZ	MSG0	; If zero, then single SIO board
	DCR	A	; If one,  then lower 2SIO board
	JZ	MSG16	;
	DCR	A	; If two,  then upper 2SIO board
	JZ	MSG18	; Else, must be       3P+S board
;
MSGPS:	IN	PTSS	; Get the status 3P+S board
	RLC		; See if ready for output
	JNC	MSGPS	; Wait until last charater out
	MOV	A,M	; OK, now get the character
	OUT	PTSD	; and print it
	INX	H	; Point to next character
	JMP	MSGOUT	; And continue until done
;
MSG16:	IN	SIOS0	; Get the status of lower 2SIO board
	ANI	02H	; See if ready for output
	JZ	MSG16	; Wait until last charater out
	MOV	A,M	; OK, now get the character
	OUT	SIOD0	; and print it
	INX	H	; Point to next character
	JMP	MSGOUT	; And continue until done
;
MSG18:	IN	SIOS1	; Get the status of upper 2SIO board
	ANI	02H	; See if ready for output
	JZ	MSG18	; Wait until last charater out
	MOV	A,M	; OK, now get the character
	OUT	SIOD1	; and print it
	INX	H	; Point to next character
	JMP	MSGOUT	; And continue until done
;
MSG0:	IN	SIOS	; Get status of port-0
	RLC		; Check high-order bit
	JC	MSG0	; Wait until last charater out
	MOV	A,M	; OK, now get the character
	OUT	SIOD	; and print it
	INX	H	; Point to next character
	JMP	MSGOUT	; And continue until done
;
;
; Messages to be printed.  Always end with zero-byte
;
; Clear-Screen VT-100
INTIT:	DB	RS,SUB,0
;
; Clear-Screen VT-100
INTVT:	DB	ESC,"[J",0
;
; Clear-Screen SWTP TVT
INTTV:	DB	DC3,SYN,"    ",DC2,DC2,DC2,DC2,0
;
PRMT1:	DB	"START",0
;
PRMT2:	DB	LF	;
	DB	"  END",0
;
ADRMS:	DB	"ING ADDRESS? ",0
;
OKM:	DB	BEL,"MEMORY OK"
CRLF:	DB	CR,LF,0
;
ERMS1:	DB	BEL,CR,LF,"    ERROR BYTE AT: ",0
;
ERMS2:	DB	CR,LF,"    TEST",0
;
ERMS3:	DB	CR,LF,"   ERROR",0
;
ERMS4:	DB	" BYTE WAS: ",0
;
ERMS5	DB	CR,LF,"BITS IN ERROR ARE: ",0
;
COMSP:	DB	", ",0
;
PASMS:	DB	LF,"PASS:",0
;
BADPG:	DB	BEL,CR,LF,"BYTE /= ANY PATN"
	DB	CR,LF
;
BSMS	DB	0	; Start of backspace table
;
HEXMS:	DB	'0'	; Re-use as Hex-byte table also
	DB	BS
	DB	'1'
	DB	BS
	DB	'2'
	DB	BS
	DB	'3'
	DB	BS
	DB	'4'
	DB	BS
	DB	'5'
	DB	BS
	DB	'6'
	DB	BS
	DB	'7'
	DB	BS
	DB	'8'
	DB	BS
	DB	'9'
	DB	BS
	DB	'A'
	DB	BS
	DB	'B'
	DB	BS
	DB	'C'
	DB	BS
	DB	'D'
	DB	BS
	DB	'E'
	DB	BS
	DB	'F'
;
	END
