;==============================================================
; 8080 Memory Test Program by Martin Eberhard
; Runs stand-alone in an Altair, or under CP/M
;==============================================================
;Notes:
; Formatted to assemble with digital Research'S ASM.
;
; This program runs at 0000h in a standalone configuration, and
; at the standard 0100h under CP/M. When standalone, all memory
; can be tested, as RAMTEST moves itself out of the way to test
; memory starting at 0. When running under CP/M. it can test
; all memory up to the BIOS (including page 0 memory). If the
; CP/M user specifies an ending address that's above the start
; of the BIOS, RAMTEST will adjust the ending address downward
; to just below the BIOS.
;
; This program runs 2 different tests:
; * The "Pattern Test" fills memory with a sequence of bytes.
;   and then read it back to see if every byte is correct. The
;   sequence is 53 (a prime number) of bytes long, and is
;   constructed so as to try lots of combinations and to have
;   lots of bits flipping between adjacent addresses and
;   between successive writes. Each write is done 3 times,
;   inverting the data each time as quickly as possible. Each
;   pass through the test shifts the pattern one address later
;   in memory, so every pattern byte is tried in every memory
;   location. A dot is printed on the console after each pass.
;   If the user specifies more than one run, then each byte
;   of the pattern is incremented for subsewuent runs, so that
;   the pattern is different for every run until 256 runs.
;
; * The "Bit Walk Test" divides memory into "prime words,"
;   where a prime word is either 5, 7, 11, or 17 bytes long,
;   depending on the run number. All of memory is filled with
;   just one bit set in each prime word, and then the whole
;   range is checked, and a dot is printed on the console.
;   Then the bit is shifted one position over within the prime
;   words, and the test is repeated. All bit positions within
;   the prime word are checked this way. The entire test is
;   repeated again, but with all bits high except one. (The
;   number of dots will change with each run as the size of
;   the prime words change.)
;
; Whenever the data that is read from RAM does not match the
; expected value, a message is printed on the console that
; identifies the address, the expected data, and the actual
; data from RAM. At the end of the test, the total number
; of errors (capped at FFFFh) is printed on the console in hex.
;==============================================================
;VERS. 1.00 by M. Eberhard 14 April 2014
;  Created
;==============================================================

VERSION	equ	100h		;BCD-encoded version number
;==============================================================

FALSE	equ	0
TRUE	equ	not FALSE

CPMCOD	equ	TRUE		;TRUE uses CP/M for console I/O
				;FALSE uses the 88-2SIO for I/O

MAXLIN	equ	8		;Max input characters

;-------------------------------------------------------------
;Equates for the Altair 88-2SIO, the 8800b Turnkey Module,
;the MITS 88-UIO, and my own 88-2SIOJP
;
;(All of these are all built around the Motorola MC6850 ACIA)
;-------------------------------------------------------------
 if not CPMCOD
;ACIA registers

SIOBAS	equ	10h
S2CTLA	EQU	SIOBAS		;ACIA A control output port
S2STAA	EQU	SIOBAS		;ACIA A status input port
S2TXDA	EQU	SIOBAS+1	;ACIA A Tx data register
S2RXDA	EQU	SIOBAS+1	;ACIA A Rx data register

;ACIA ctR/stat values

S2RDF	EQU	00000001B	;Rx data register full
S2TBE	equ	00000010B	;Tx data register empty

S2RST	equ	00000011B	;Master reset
S22STP	equ	00010001B	;2 stop bits, /16
S21stP	equ	00010101B	;1 stop bit, /16


CDATA	equ	S2RXDA		;Console Data port
CSTAT	equ	S2STAA		;Console Status port
CRXRDY	equ	S2RDF		;Console Rx Ready
CTXRDY	equ	S2TBE		;Console Tx Ready
 endif ;not CPMCOD

;**************
; CP/M Equates
;**************
 if CPMCOD
;------------------------------------------
;BDOS Entry Points and low-memory locations
;------------------------------------------
WBOOT	equ	0000H		;Jump to BIOS warm boot
WBOOTA	equ	WBOOT+1		;Address of Warm Boot
IOBYTE	equ	WBOOT+3

;--------------------------------------------
;BIOS Entry Points, relative to the base
;address in WBOOT. BIOS calls are used
;(instead of BDOS calls) where speed matters.
;--------------------------------------------
CSTART	equ	00h	;Cold start
CONST	equ	06h	;Console Status
CONIN	equ	09h	;Console Input
CONOUT	equ	0Ch	;Console output
 endif ;CPMCOD

;-----
;ASCII
;-----
CTRLC	equ	03h		;ASCII control-C
BS	equ	08h		;ASCII backspace
LF	equ	0Ah		;ASCII linefeed
CR	equ	0DH		;ASCII carriage return
QUOTE	equ	27h		;ASCII single quote
DEL	equ	7Fh		;ASCII delete

;=================
;Beginning of Code
;=================
 if not CPMCOD
	ORG	0
 endif ;not CPMCOD
 if CPMCOD
	org	100h
 endif ;CPMCOD

;***********************
;One-time initialization
;***********************
	xra	a
	sta	PASCNT		;Make every run a little different

	mov	l,a
	mov	h,a		;hl=0

 if not CPMCOD
	pop	d		;get return address

	dad	sp		;get original SP into hl
	lxi	sp,STACK	;use local stack

	push	h		;remember incoming sp for exit
	push	d		;remember return address
 endif ;not CPMCOD

 if CPMCOD
	dad	sp		;get original SP into hl
	lxi	sp,STACK	;use local stack
	push	h		;just to match non CP/M code

	lhld	WBOOTA+1	;gets BIOS page and IOBYTE
	push	h
 endif ;CPMCOD

	call	CILPRT
 db '=====================',CR
 db 'RAMTest  Version '
 db VERSION/256+'0','.',((VERSION/16) and 0Fh)+'0'
 db (VERSION and 0Fh)+'0',CR
 db '    By M.Eberhard ',CR 
 db '=====================',CR+80h

;********************************************
;Main loop to request paramaters and test RAM
;********************************************
MAIN:	lxi	h,0
	shld	ERRCNT		;clear error counter
	lxi	sp,STACK-4	;repair stack pointer
;Get and validate test paramaters from the user

FRETRY:	call	GETPRM
	db	CR,' First page?',' '+80h
	mov	d,a

 if CPMCOD
	pop	h		;l=BIOS page
	push	h
	mov	a,d		;is the first page
	cmp	l		;..past the BIOS?
	jc	FAOK
	mov	d,a		;temp
	call	CILPRT
	db	'First page can',QUOTE,'t be above',' '+80h
	mov	a,l
	dcr	a
	call	PAHEX2
	jmp	FRETRY
FAOK:
 endif ;CPMCOD

	call	GETPRM
	db	CR,' Last page?',' '+80h
	mov	e,a

	cmp	d		;Is last smaller than first?
	jc	BADADR		;y: abort

 if CPMCOD
	lxi	h,WBOOTA+1	;BIOS page
	cmp	m		;Last address above BIOS?
	jc	NOTRIM
	mov	e,m		;y: trim it
	dcr	e
NOTRIM:
 endif ;CPMCOD

	inr	e

PRTRY:	call	GETPRM
	db	CR,' How many runs (1-9, 0 for nonstop)?',' '+80h
	cpi	10
	jnc	PRTRY

	dcr	a		;0 becomes FF
	jm	PASSN
	inr	a
PASSN:	sta	PASSES

	call	CILPRT
	db	'(^C aborts',')'+80h

;**********************************************************
;Loop through the entire test the requested number of times
; d = first page
; e = last page+1
; PASSN = remaining number of passes, FFh for continuous
;**********************************************************
PASLUP:	push	d		;test parameters
;--------------------------------------------------
;If start is before the end of this code then start
;after this code first, and plan for a 2nd block
;--------------------------------------------------
	lxi	b,0		;c: assume no overlap
				;b: convenient 0
	mvi	a,MOVPAG-1
	cmp	d		;d=requested start page
	jc	PASLP1

	inr	a		;MOVPAG
	mov	d,a		;start after TESTRAM
	inr	c		;remember overlap

PASLP1:	push	b		;remember c= 2pass

	mov	l,b		;0
	mov	h,d		;hl=start=address

;===================================================
;Test the portion of the specified range
;that's above RAMTEST, if any
; c = 1 iff some of the test range overlaps RAMTEST
; d = MOVPAG, the start page for this range
; e = end page+1
; hl = start address
;===================================================
	cmp	e		;Anything above RAMTEST?
	cc	PATEST		;y: Pattern Test followed
				;..by Bit Walk test

;=========================================
;If any of the specified range is below
;the end of RAMTEST, then relocate RAMTEST
;out of the way, and execute there
;=========================================
	pop	b		;c=2nd pass
	dcr	c
	jnz	PASDON

;-----------------------------------
;Relocate us away from address 0000h
;-----------------------------------
	call	RELOC
	pop	d		;d=first, e=last+1

;--------------------------------------------
;Test the portion of the range that's beneath
;RAMTEST's normal position in memory
;--------------------------------------------
	push	d

;Calculate the end page

	mvi	a,MOVPAG
	cmp	e		;all the way to the end?
	jnc	TEST3
	mov	e,a		
TEST3:

	mov	h,d		;starting address
	mvi	l,0

;=======================================
;Move RAMTEST out of the way, test RAM
;beneath RAMTEST, and move RAMTEST back.
;=======================================
	lxi	sp,STACK-6+MOVADR ;relocated stack

	jmp	MCODE+MOVADR	;go to relocated code
MCODE:

	call	PATEST+MOVADR	;Pattern test followed
				;..by Bit Walk test

	call	RELOC+MOVADR	;Put RAMTEST back

	jmp	OCODE		;back to un-relocated code
OCODE:

	lxi	sp,STACK-6	;3 items on the stack

;-------------------
;Repeat as requested
;-------------------
PASDON:	pop	d		;test parameters

	lxi	h,PASCNT
	inr	m

	inx	h		;PASSES
	mov	a,m
	ora	a
	jm	PASLUP		;Nonstop?

	dcr	m
	jnz	PASLUP

;-----------------------------
;Report total number of errors
;-----------------------------
DONE:	call	RPTERS

;-------------------------------------
;All done with the requested test. Ask
;if the user wants to do another test
;-------------------------------------
DRETRY:	call	CILPRT
	db	'Test agai','n'+80h
	call	ASKYN
	jnc	MAIN

;------------
;Exit RAMTEST
;------------
	jmp	EXIT

;***Subroutine*****************
;Pattern-Test a block of memory
;On Entry:
;  hl=starting address
;  e = last page+1
;Trashes psw,bc
;******************************
PATEST:

;-------------
;Announce test
;-------------
RP00:	call	CILPRT
	db	'Patter','n'+80h
RP01:	call	TANNCE		;announce parameters

RP02:	lxi	b,MTPAT		;Test pattern sequence

;Loop until all memory locations have seen each pattern byte

PTLOOP:	Push	b		;Pattern position
	push	h		;Start address

;--------------------------------------------
;Fill memory with pattern. Do a fast
;read/invert/write twice to stress the memory.
; bc = pattern table entry address
;  e = end page address
; hl = RAM address
;---------------------------------------------
RP03:	lda	PASCNT		;For varying the pattern
	mov	d,a

FIL1:	mov	a,c
	cpi	PATEND and 0FFh	;Pattern end?
RP04:	jnz	FIL2
RP05:	lxi	b,MTPAT		;y: restart pattern

FIL2:	ldax	b		;Get a pattern byte
	add	d		;d=PASCNT
	inx	b

;High-frequency memory byte test while
;we fill memory with the pattern

	mov	m,a		;Write pattern to memory

	mov	a,m		;Invert & write
	cma
	mov	m,a

	mov	a,m		;twice
	cma
	mov	m,a

	inx	h		;next address

	mov	a,h		;byte count
	cmp	e		;end?
RP06:	jnz	FIL1		;n: keep filling

	pop	h		;Start address
	pop	b		;Pattern position

;-----------------------------
;compare memory to the pattern
;-----------------------------
	push	b		;Pattern position
	push	h		;Start address

CML1:	mov	a,c
	cpi	PATEND and 0FFh	;Pattern end?
RP07:	jnz	CML2
RP08:	lxi	b,MTPAT		;y: restart pattern

CML2:	ldax	b		;Get pattern byte
	mov	d,a
RP09:	lda	PASCNT		;vary pattern with each pass
	add	d
	inx	b		;next pattern byte

	mov	d,m		;remember for error report
	cmp	d		;compare pattern to memory
RP10:	cnz	CMPERR		;report any mismatch

	inx	h		;next RAM location

	mov	a,h
	cmp	e		;end?
RP11:	jnz	CML1

;---------------------------------------------------
;Done with one pass. Print pacifier, test for abort,
;and do another pass, unless we are done.
;---------------------------------------------------
	pop	h		;Start address

RP12:	call	PACIFY		;pacifier, abort opportunity
	pop	b		;Pattern position

	inx	b		;move pattern once

	mov	a,c
	cpi	PATEND and 0FFh	;Pattern end?
RP13:	jnz	PTLOOP

;Fall into WBTEST for the Bit Walk Test

;***Subroutine*********
;Bit Walk Test
;On Entry:
;  hl=starting address
;  e = last page+1
;Trashes psw,bc
;**********************
WBTEST:

;-------------
;Announce test
;-------------
RW01:	call	CILPRT
	db	'Bit wal','k'+80h
RW02:	call	TANNCE		;Announce parameters

	mvi	c,0		;mostly low first

WBLUP0:
RW03:	lda	PASCNT		;get a prime number
	ani	3
	rlc			;This makes the sewuence
	adi	5		;..5,7,11,17
	mov	d,a

	mvi	a,1
RW04:	sta	BITPOS		;Bit position

;---------------------------------------------------------
;Write the pattern to memory
; b keeps track of which byte within the "word"
; c = 00 for the 1st full pass, FF for the 2nd
; d = which byte within the prime-byte "word" gets the bit
; e = ending page
; hl = memory address
; BITPOS = the bit within the chosen byte
;---------------------------------------------------------
WBLOOP:	push	h		;starting address

WBWLP1:
RW05:	lda	PASCNT		;get a prime number
	ani	3		;create 0,2,4,6 sewuence
	rlc			;This makes the sewuence
	adi	5		;..into 5,7,11,17
	mov	b,a

WBWLP2:	mov	a,b
	cmp	d
	
	mvi	a,0
RW06:	jnz	WBWLP3		;does this byte get the bit?

RW07:	lda	BITPOS		;y: flip the bit
WBWLP3:	xra	c
	mov	m,a

	inx	h
	mov	a,h
	cmp	e		;done with range?
RW08:	jz	WBWDON

	dcr	b
RW09:	jnz	WBWLP2
RW10:	jmp	WBWLP1	

WBWDON:	pop	h		;starting address

;---------------------------------------------------------
;See if the read-back matches what we wrote
; b keeps track of which byte within the "word"
; c = 00 for the 1st full pass, FF for the 2nd
; d = which byte within the prime-byte "word" gets the bit
; e = ending page
; hl = memory address
; BITPOS = the bit within the chosen byte
;---------------------------------------------------------
	push	h		;starting address

WBRLP1:
RW11:	lda	PASCNT		;get a prime number
	ani	3
	rlc			;This makes the sewuence
	adi	5		;..5,7,11,17
	mov	b,a

WBRLP2:	mov	a,b
	cmp	d
	
	mvi	a,0
RW12:	jnz	WBRLP3		;does this byte get the bit?

RW13:	lda	BITPOS		;y: flip the bit
WBRLP3:	xra	c
	cmp	m
RW14:	cnz	WBERR		;mismatch

	inx	h
	mov	a,h
	cmp	e		;done with range?
RW15:	jz	WBRDON

	dcr	b
RW16:	jnz	WBRLP2
RW17:	jmp	WBRLP1	

WBRDON:

;-----------------------------------------
;Print a pacifier and check for user abort
;-----------------------------------------
RW18:	call	PACIFY		;pacifier, abort opportunity

;----------------------------------------------
;Next pass through RAM.
;Move the bit over within the prime-byte "word"
;----------------------------------------------
RW19:	lxi	h,BITPOS
	mov	a,m
	rlc
	mov	m,a

	pop	h		;starting address

RW20:	jnc	WBLOOP		;in the same byte?

	dcr	d
RW21:	jnz	WBLOOP		;Done them all?

	mov	a,c		;invert and start over
	xra	a
	mov	c,a
RW22:	jnz	WBLUP0		;All done?

	ret

;--------------------------	
;Report a Bit Walk error
;On Entry:
;  hl = address
;   a = expected
;  (hl) = found
;--------------------------
WBERR:	push	d
	mov	d,m
RW23:	call	CMPERR
	pop	d
	ret

;-----------------------------
; Memory Test Pattern Sequence
; 53 bytes long. (Prime)
;-----------------------------
MTPAT:	db	000h,0FFh,055h,0AAh,033h,0CCH,0F0h,00Fh
	db	0C3h,03CH,066h,099h,078h,001h,0FEh,002h
	db	0FDH,004h,0FBh,008h,0F7h,010h,0EFh,020h
	db	0DFH,040h,0BFh,080h,07Fh,0A5h,05Ah,0E7h
	db	018h,063h,09Ch,027h,0D8h,0DBh,042h,0E3h
	db	016h,0F5h,00Ah,050h,0AFh,0DDh,022h,0FFh
	db	000h,0FFh,000h,0FFh,0B7h
PATEND:

;***Subroutine**************************
;Announce test parameters
;On Entry:
;  hl=starting address
;  e = last page+1
;  Type of test has already been printed
;***************************************
TANNCE:
R000:	call	ILPRNT
	db	' test:',' '+80h
R001:	call	PHLHEX
R002:	call	ILPRNT
	db	' through',' '+80h

	push	h
	mov	h,e	;l=0 already
	dcx	h
R003:	call	PHLHEX
	pop	h

R004:	call	ILPRNT
	db	CR+80h
	ret
		
;***Subroutine****************
;Report memory error, and give
;user an opportunity to abort
;On Entry:
;  a=expected data
;  d=found data
;  hl=address
;Trashes psw
;*****************************
CMPERR:	push	b
	mov	b,a		;expected data

	push	h
R005:	lhld	ERRCNT
	inx	h
	mov	a,h
	ora	a		;wrap?
R006:	jz	ERRFUL
R007:	shld	ERRCNT
ERRFUL:	pop	h


R008:	call	CILPRT		;CR LF space begins line
	db	' '+80h

R009:	call	PHLHEX

R010:	call	ILPRNT		;print colon space
	db	':',' '+80h

R011:	call	ILPRNT
	db	'Wrote',' '+80h

	mov	a,b		;expected data
R012:	call	PAHEX2

R013:	call	ILPRNT
	db	', read',' '+80h
	mov	a,d
R014:	call	PAHEX2		;memory data

	pop	b

R015:	call	CHKABT		;Abort?
	cpi	' '		;Pause?
	rnz

R016:	jmp	GETKBD		;wait for end of pause


;***Subroutine*********************************
;Print hl as 4 hex digits & accumulate checksum
;On Entry:
;  hl=2 bytes to print
;Trashes psw,c
;**********************************************
PHLHEX:	mov	a,h		;h first
R017:	call	PAHEX2		;returns with carry clear
	mov	a,l		;then l

;Fall into PAHEX2

;***Subroutine***************************
;Print a as 2 hex digits
;On Entry:
;  a=byte to print
;Trashes psw,c
;****************************************
PAHEX2:	push	psw		;save for low digit
	
	rrc			;move the high four down
	rrc
	rrc
	rrc
R018:	call	PAHEX1		;print high digit
	pop	psw		;this time the low four

;Fall into PAHEX1 to print low digit

;***Subroutine***************************
;Print low nibble of a as 1 hex digit
;On Exit:
;  psw,c trashed
;****************************************
PAHEX1:	ani	0Fh		;Four on the floor
	adi	'0'		;We work with ASCII here
	cpi	'9'+1		;0-9?
R019:	jc	PNIB1		;Yup: print & return

	adi	'A'-'9'-1	;make it a letter
PNIB1:

;Fall into PRINTA

;***Subroutine***********
;Print a on the console
;Returns printed chr in c
;************************
PRINTA:	mov	c,a


 if not CPMCOD
PAWAIT:	in	CSTAT
	ani	CTXRDY
R020:	jz	PAWAIT

	mov	a,c
	out	CDATA
	ret
 endif ;not CPMCOD


 if CPMCOD
	mvi	a,CONOUT

;Fall into GOBIOS

;***Subroutine*********************
;Go call a BIOS driver directly
;On Entry:
;  c=value for BIOS routine, if any
;  a = BIOS call address offset
;On Return:
;  psw as BIOS left it
;  all other regs preserved
;**********************************
GOBIOS:	push	h
	push	d
	push	b

	mov	d,a

	lxi	h,IOBYTE	;memory test value
	mov	b,m		;..remember

R020:	lda	STACK-3		;restore IOBYTE
				;..saved on stack
	mov	m,a
	mov	a,d

	push	b
RC20A:	call	DOBIOS
	pop	b

	lxi	h,IOBYTE	;memory test value
	mov	m,b

	pop	b
	pop	d
	pop	h
	ret

;---Local Subroutine---------------
;Go call a BIOS driver directly
;On Entry:
;  c=value for BIOS routine, if any
;  a = BIOS call address offset
;On Return:
;  all regs as BIOS left them
;(26 cycles + BIOS time)
;----------------------------------
DOBIOS:
RC20B:	lhld	WBOOTA		;get BIOS base address
	mov	l,a		;a has jump vector
	pchl			;'call' BIOS routine
 endif ;CPMCOD

;***Subroutine*****
;Ask Y/N
;On Exit:
;  Carry set if no
;Trashes psw,bc,de
;******************
ASKYN:

R021:	call	ILPRNT
	db	' (Y/N)?',' '+80h

R022:	call	GETLIN
	dcr	b		;just one chr?
R023:	jnz	ASKYN		;n: bogus

	ldax	d		;Get 1st chr
	ani	'a'-'A' xor 0FFh
	cpi	'Y'
	rz

	cpi	'N'
	stc
R024:	jnz	ASKYN

	ret

;***Subroutine*********************************
;Print a pacifier dot and chech for user abort.
;Abort if control-C.
;On Exit:
;  if a chr is waiting, then chr is in a
;  if no chr waiting, Z set, a=0
;**********************************************	
PACIFY:
R025:	call	ILPRNT		;print pacifier
	db	'.'+80h

;Fall into CHKABT

;***Subroutine*********************************
;Get keyboard status. if a chr is waiting, then
;return it in a with parity stripped. Abort if
;control-C.
;On Exit:
;  if a chr is waiting, then chr is in a
;  if no chr waiting, Z set, a=0
;**********************************************	
CHKABT:

 if not CPMCOD
	in	CSTAT
	ani	CRXRDY
	rz			;N: ret W/ Z set
 endif ;not CPMCOD

 if CPMCOD
	mvi	a,CONST		;anything on console?
RC25A:	call	GOBIOS
	ora	a		;Z means no chr waiting
	rz
 endif ;CPMCOD

;Fall into GETKBD

;***Subroutine**************************
;Wait for and get Console keyboard data
;On Exit:
;  a=keyboard character, parity stripped
;  Z clear
;***************************************
GETKBD:

 if not CPMCOD
	in	CSTAT
	ani	CRXRDY
R026:	jz	GETKBD

	in	CDATA		;get keyboard chr
 endif ;not CPMCOD

 if CPMCOD
	mvi	a,CONIN		;read the typed chr
R026:	call	GOBIOS
 endif ;CPMCOD

	ani	7Fh		;strip parity
	cpi	CTRLC		;abort character typed?
	rnz

;Fall into abort

;***Exit****
;User abort
;***********
R027:	call	CILPRT
	db	'^','C'+80h


R028:	lxi	h,0100h		;are we relocated?
	dcr	h

;Put code back in its original position if needed

R029:	cnz	RELOC

	jmp	DONE		;back to un-relocated code

;***Subroutine******************************
; Print CR LF then inline string at (sp)
; Calls to CILPRT are followed by the string
; the last string byte has its MSB set
;On Exit:
;  Carry clear
;Trashes psw,c
;*******************************************
CILPRT:
R030:	call	ILPRNT
	db	CR+80h

;Fall into ILPRNT

;***Subroutine**************************************
;Print In-line Message
;The call to ILPRNT is followed by a message string.
;Follow CR's with LF's
;The last message chr has its msb set.
;On Exit:
;  Carry clear
;Trashes psw,c
;***************************************************
ILPRNT:	xthl			;Save hl, get msg addr
R031:	call	HLPRNT
	xthl			;Restore hl,
				;..get return address
	ret

;***Subroutine***********************************
;Print Message at hl
;Follow all CR's with LF's
;On Entry:
;  hl points to the message
;  The last message chr has its msb set.
;On Exit:
;  Carry clear
;Trashes psw,c
;************************************************
HLPRNT:	mov	a,m		;get a character
	ani	7Fh		;strip end marker


HLPRT1:
R032:	call	PRINTA		;returns chr in c

	mvi	a,CR		;just print a CR?
	cmp	c		;c has printed chr
	mvi	a,LF
R033:	jz	HLPRT1		;y: follow with LF

	mov	a,m		;was that the last chr?
	ora	a		;msb was set if so (Clears carry)
	inx	h		;Next byte
R034:	jp	HLPRNT		;Do all bytes of msg

	ret

;***Subroutine*******
;Report error count
;********************
RPTERS:
R035:	call	CILPRT
	db	'Done',CR+80h

R036:	lhld	ERRCNT
R037:	call	PHLHEX
R038:	call	ILPRNT
	db	'h total error','s'+80h
	ret

;***Subroutine******************************
;Relocate code to the "ohter" location
;If we are running at 0 then move to MOVADR
; Otherwise, move back to 0
;On Exit:
;  Code has been moved
;  All table entries relocated
;  The relocate table has been relocated too
;Trashes psw,bc
;*******************************************
RELOC:	push	h
	push	d

;Relocate to the "other" location

	lxi	h,MOVADR	;destination

RL01:	lxi	d,0		;source (d gets relocated)

	mov	a,d		;This gets relocated
	mov	d,e		;make de 0 now

	ora	a		;Are we relocated?
RL02:	jz	RELOC0
	xchg			;y: swap source and destination
RELOC0:

;Announce move

RL03:	call	CILPRT
	db	'Relocating to',' '+80h

RL04:	call	PHLHEX

	lxi	b,MOVADR	;Byte count

;Copy the code to its new location,
;verifying each write

RELUP0:	ldax	d		;move the code
	mov	m,a

	cmp	m		;make sure it wrote!
RL05:	jnz	MWERR

	inx	h
	inx	d
	dcx	b
	mov	a,b
	ora	c
RL06:	jnz	RELUP0

	mov	a,h		;calculate difference
	sub	d

RL07:	mov	c,a		;difference

;Relocate the absolute address references in the code
;(RL07 is back one byte because of the 2-byte offset in the table.)

	mvi	a,RP00/256
	add	c
	mov	h,a		;dest page address
	mov	l,b		;0

RL08:	lxi	d,RELTBL
	mvi	b,RTLEN		;word count

RELUP1:	ldax	d		;address low byte from table
	cmp	l		;next page?
RL09:	jnc	RLP1

	inr	h
RLP1:	mov	l,a		;de=address

	mov	a,m		;relocate the code
	add	c
	mov	m,a

	cmp	m		;make sure it wrote!

RL10:	jnz	MWERR

	inx	d
	dcr	b
RL11:	jnz	RELUP1

	pop	d
	pop	h
	ret

;------------------------------------------
;Read-back error when moving code
;On Entry: Relocate message already printed
; hl=address
; a=expected
; (hl)=found
;------------------------------------------
MWERR:	mov	d,m		;found data
	mov	e,a		;expected data

RL12:	call	ILPRNT
	db	' failed at',' '+80h

	mov	a,e		;expected
RL13:	call	CMPERR

RL14:	call	RPTERS		;report total errors

MWERR1:
RL15:	call	CILPRT
	db	'Exi','t'+80h
RL16:	call	ASKYN
RL17:	jc	MWERR1		;Y is the only answer

;Fall into EXIT

;------------
;Exit RAMTEST
;------------
EXIT:
RL18:	lxi	sp,STACK-4	;1st item on the stack
	pop	d		;return address
	pop	h		;original sp

 if CPMCOD
	mov	d,e		;BIOS page
	mvi	e,CSTART	;cold-start entry point
 endif ;CPMCOD

	sphl			;repair stack
	xchg			;hl = return address

	pchl			;go there

;***Table************************************
;Relocation addresses
;(Low bytes only, must be in ascending order)
;********************************************
RELTBL:	db	(RP00+2) and 0FFh
	db	(RP01+2) and 0FFh,(RP02+2) and 0FFh,(RP03+2) and 0FFh
	db	(RP04+2) and 0FFh,(RP05+2) and 0FFh,(RP06+2) and 0FFh
	db	(RP07+2) and 0FFh,(RP08+2) and 0FFh,(RP09+2) and 0FFh
	db	(RP10+2) and 0FFh,(RP11+2) and 0FFh,(RP12+2) and 0FFh
	db	(RP13+2) and 0FFh


	db	(RW01+2) and 0FFh,(RW02+2) and 0FFh,(RW03+2) and 0FFh
	db	(RW04+2) and 0FFh,(RW05+2) and 0FFh,(RW06+2) and 0FFh
	db	(RW07+2) and 0FFh,(RW08+2) and 0FFh,(RW09+2) and 0FFh
	db	(RW10+2) and 0FFh,(RW11+2) and 0FFh,(RW12+2) and 0FFh
	db	(RW13+2) and 0FFh,(RW14+2) and 0FFh,(RW15+2) and 0FFh
	db	(RW16+2) and 0FFh,(RW17+2) and 0FFh,(RW18+2) and 0FFh
	db	(RW19+2) and 0FFh,(RW20+2) and 0FFh,(RW21+2) and 0FFh
	db	(RW22+2) and 0FFh,(RW23+2) and 0FFh

	db	(R000+2) and 0FFh
	db	(R001+2) and 0FFh,(R002+2) and 0FFh,(R003+2) and 0FFh
	db	(R004+2) and 0FFh,(R005+2) and 0FFh,(R006+2) and 0FFh
	db	(R007+2) and 0FFh,(R008+2) and 0FFh,(R009+2) and 0FFh
	db	(R010+2) and 0FFh,(R011+2) and 0FFh,(R012+2) and 0FFh
	db	(R013+2) and 0FFh,(R014+2) and 0FFh,(R015+2) and 0FFh
	db	(R016+2) and 0FFh,(R017+2) and 0FFh,(R018+2) and 0FFh
	db	(R019+2) and 0FFh,(R020+2) and 0FFh

 if CPMCOD
	db	(RC20A+2) and 0FFh,(RC20B+2) and 0FFh
 endif ;CPMCOD

	db	(R021+2) and 0FFh
	db	(R022+2) and 0FFh,(R023+2) and 0FFh,(R024+2) and 0FFh
	db	(R025+2) and 0FFh

 if CPMCOD
	db	(RC25A+2) and 0FFh
 endif ;CPMCOD

	db	(R026+2) and 0FFh,(R027+2) and 0FFh
	db	(R028+2) and 0FFh,(R029+2) and 0FFh,(R030+2) and 0FFh
	db	(R031+2) and 0FFh,(R032+2) and 0FFh,(R033+2) and 0FFh
	db	(R034+2) and 0FFh,(R035+2) and 0FFh,(R036+2) and 0FFh
	db	(R037+2) and 0FFh,(R038+2) and 0FFh

	db	(RL01+2) and 0FFh,(RL02+2) and 0FFh,(RL03+2) and 0FFh
	db	(RL04+2) and 0FFh,(RL05+2) and 0FFh,(RL06+2) and 0FFh
	db	(RL07+2) and 0FFh,(RL08+2) and 0FFh,(RL09+2) and 0FFh
	db	(RL10+2) and 0FFh,(RL11+2) and 0FFh,(RL12+2) and 0FFh
	db	(RL13+2) and 0FFh,(RL14+2) and 0FFh,(RL15+2) and 0FFh
	db	(RL16+2) and 0FFh,(RL17+2) and 0FFh,(RL18+2) and 0FFh

RTLEN	equ	$-RELTBL

;***Subroutine************************************
;Get hex value from user
;On entry:
;  top-of-stack points to inline message address
;On Exit:
;  a = 8-bit result
;Trashes c,hl
;*************************************************
GETPRM:	pop	h	;string address
	push	h	;for retries

	call	HLPRNT

	push	d
	call	GETLIN
	call	GETHEX
	pop	d

	jnc	GETPRM	;bogus input?

	xthl		;return address onto stack
	ret

;***Subroutine**************************************
;Read a command line from the keyboard, echoing and
;saving it in the input line buffer. CR input ends
;the sequence.
;On Exit:
;  Complete command line is in the input line buffer
;  de=address of the first chr on the line
;  b = chr count
;  Carry set if nothing but spaces found
;Trashes c
;***************************************************
GETLIN:	lxi	d,RAMBUF
	push	d		;save input line buffer'S
				;..start address

	mvi	b,0		;chr count

;Get & echo chrs, stashing them in the input line buffer
;at hl, until a CR is encountered or the line overflows

GLLOOP:	mvi	a,MAXLIN-1	;input buf full?
	cmp	b		;carry set if full

	cnc	LBCHR		;N:get another character
	jnc	GLLOOP		;carry means CR found

	pop	d		;de=line buffer address
	ret

;***Subroutine****************************************
;Get, echo, and store a Console character in the input
;line buffer. Handle deletes and backspaces. This
;assumes that the line buffer is all in one page. CR's
;are not echoed, but are replaced with 0.
;On Entry:
;  de=next free spot in the input line buffer
;   b=chr count
;On Exit (no deletes, no CR):
;  chr stored in buffer at original de
;  de=de+1
;  b=b+1
;  Carry clear
;On Exit (CR)
;  Carry set
;On Exit (BS or DEL)
;  de=de-1
;   b=b-1
;  Carry clear
;Trashes c
;*****************************************************
LBCHR:	call	GETKBD		;get a chr

	cpi	DEL		;delete?
	jz	LDELET
	cpi	BS		;backspace?
	jz	LDELET

	cpi	CR
	stc			;flag CR found
	rz

	stax	d		;enqueue
	inx	d		;Bump line buffer pointer
	inr	b
	call	PRINTA		;echo (trashes c)

	xra	a		;clear carry
	ret

;Backspace if possible

LDELET:	mov	a,b		;buffer size
	ora	a		;back up if we can
	rz			;done if not (carry clear)
	
	call	ILPRNT		;back up, clear Carry
	db	BS,' ',BS+80h	;erase old chr & back up	

	dcr	b		;back up
	dcx	d

	ret

;***Subroutine*************************************
;Get a 1- or 2-digit hex value
;On Entry:
;  b = byte count
;  de=address of next item in the input line buffer
;On Exit:
;  a=value of 1 or 2 digits found, defaults to 0
;  carry set if good value found
;  carry clear if no value found or bogus hex
;Trashed b,de
;**************************************************
GETHEX:	mov	a,b		;legal byte count?
	dcr	a		;1 or 2 only
	cpi	2
	rnc

	ldax	d
	call	HEXCON		;convert a to binary
	rnc			;nc means bogus hex

	dcr	b		;do we have a 2nd digit
	rz			;y: ret with carry set

	add	a		;shift high byte up
	add	a
	add	a
	add	a
	mov	b,a		;save 1st digit

	inx	d
	ldax	d		;2nd digit
	call	HEXCON
	rnc			;nc means bogus hex

	add	b		;combine with previous digit
	stc			;carry means success
	ret

;***Subroutine************************
;Convert ASCII hex digit to binary
;On Entry:
;  a=chr to convert
;On Exit:
;  a=binary, clear if bogus chr
;*************************************
HEXCON:	sui	'0'		;remove ASCII bias
	cpi	10
	rc			;if 0-9 then we're done

	ani	('a'-'A') xor 0FFh ;Lower case is ok
	sui	9+('A'-'9')	;should be 0-5 now
	cpi	6		;gap chr or too high?
	rnc			;error: return W/O carry

	sui	0F6h		;add 0Ah, set Carry
	ret

;***Error Routine*******************
;End address is before start address
;***********************************
BADADR:	call	CILPRT
	db	'Huh','?'+80h

	jmp	MAIN		;Try again

;=========
;Variables
;=========
PASCNT:	db	0	;Pass counter for randomizing
PASSES:	db	0	;number of passes to perform
			;(must follow PASCNT)
ERRCNT:	dw	0	;error count
BITPOS:	db	0	;Rotating bit position

;====================
;Stack
;(Gets relocated too)
;====================
	dw	0,0,0,0,0,0,0,0,0,0
	dw	0,0,0,0

 if CPMCOD
	dw	0,0,0,0,0,0,0,0	;CP/M needs more stack
 endif ;CPMCOD
STACK:

;======================================================
;Calculate the address of the next page after this code
;======================================================
MOVPAG	equ	($+255)/256
MOVADR	equ	MOVPAG*256
OFS	equ	MOVADR+2	;Offset in reloc table entries

;***Buffer**************
;RAM buffer 
;need not get reloccated
;***********************
RAMBUF:	ds	MAXLIN

	END
