On Aug 14 2004, 11:11, Vintage Computer Festival wrote:
On Sat, 14 Aug 2004, Pete Turnbull wrote:
> registers), though it would on a 6502. If anyone wants a copy I
can
give you the
Z80 code, some notes from the project writeup, and the
CACM references.
Please!
OK, it's below. First I ought to explain a couple of things about the
code and the system it was written for. This was for a very basic Z80
system consisting of some EPROM, RAM, timers, and minimal I/O, which
included a 2-line x 16-char LCD display. It was used for second-year
project work.
The way the LCD display was interfaced precluded the use of normal
hanshaking or reading, so the routines that write to it incorporate a
delay long enough to ensure that the display has completed the update
before another write can occur.
Secondly, the whole system was designed to report any error (and refuse
to continue if it was serious), and to do this, the startup routines
perform a number of tests, each of which uses no part of the hardware
that's not already been passed "good". Well, that's the ideal;
it's
not so simple in practice. It begins with a ROM CRC check (and it's a
proper CRC, not a simple checksum, so it will catch all single-bit
errors, most 2-bit errors, and many multiple-bit errors). It should
really do a CPU test first, but that would need the ROM to hold the
test routines. The justification here is that it uses nothing except
the processor and the ROM itself, and the assumption is that any
serious fault in the processor will cause that to fail. If so, it's
easy to take out the ROM, CRC check it on an EPROM programmer, and tell
whether the ROM really is at fault -- if not, it must be the CPU. The
chances of a CPU failing are quite small; the chances that a failing
CPU will still execute instructions are a fraction of that; the chances
that a CPU with faulty instructions will still execute the CRC test are
a fraction of that; the chances that a faulty CPU that still passes the
CRC has faults in common instructions used elsewhere are ... etc.
So at the point that the RAM test executes, we "know" the CPU is OK and
we know the ROM is OK, but we don't want to use the RAM to hold data
during the RAM test. So no variables other than in registers, and *no
stack* hence no subroutines. Hence the rather odd way of executing
pseudo-subroutines to display things (on the LCD which we assume is
working; it's just been "exhaustively tested" by the visual test of
seeing a "ROM CRC ERROR" or "ROM CRC PASSED" message, followed by
"RAM
test" on it :-)).
Lastly, this test does everything one bit at a time, not one byte at a
time. That's because the system used byte-wide RAM, in which in theory
any bit could interact with any other. You could safely parallelise
this to do a byte at a time if you used a separate RAM chip for each
bit (because you could assume they wouldn't interact except through a
faulty data bus or power glitches) and that would speed it up more than
8 times.
Here's the code:
# Filename: RAMtest.s
#
# Author: Pete Turnbull
# Address: Department of Computer Science, University of York,
# Heslington, YORK YO10 5DD.
# Email: pnt1(a)york.ac.uk
#
# Created: 26-Nov-1995
# Last update: 18-Nov-1996
# Description: An efficient RAM test
# Version: 0.4 now uses 20-bit error-counter
# 0.3 modified to work as standalone test for CTS
19-Oct-1996
# 0.2 code converted from ZASM to as80 for MCP, March
1996
# 0.1 original module for inclusion in CTS 1995, coded in
ZASM
#
# **************************************************************************
# *
# * An implementation of Suk and Reddy's Test B RAM testing procedure.
# * Implemented from information in an article "Functional Testing of
# * Semiconductor Random Access Memories", M.S.Abadir and H.K.Reghbati,
# * Computing Surveys, Vol.15 No.3 September 1983, ACM.
# * The original NTA article is "Efficient Algorithms for testing
# * Semiconductor Random Access Memories", Nair, Thatte and Abraham,
IEEE
# * Transactions on Computing, Vol.C-27, No.6, June 1978.
# * A similar fault model was used by D.S.Suk and S.M.Reddy, to develop
even
# * better tests, including their Test B, described in "A march test
for
# * functional faults in semiconductor random access memories", IEEE
# * Transactions on Computing, Vol.C-30, No.12, Dec 1981.
# * This is an O(n) procedure, much more efficient for large memories
than
# * GALPAT. It also finds coupling faults invisible to GALPAT tests.
# *
# * GALPAT complexity 4n^2 + 2n, finds all stuck-at, and some coupling
faults
# * GALPAT-II 4n^2 + 4n, finds all stuck-at, and all coupling
faults
# * T&A 8n.lg(n), finds all stuck-at, and all coupling
faults
# * NTA 30n, finds all stuck-at, and all coupling
faults
# * S&R-B 16n, finds all stuck-at, and all coupling
faults
# * assuming no decoder multiple-access
faults,
# * and all decoder faults if no coupling
faults.
# *
# * The proviso about coupling/decoder faults really just means that
the two
# * types of fault are seen as equivalent (indistinguishable) by the
test,
# * not that they are not detected.
# *
# * There are even faster tests than S&R-B but they don't necessarily
find
# * all errors (some errors may mask others).
# * With our Z80 running at 3.6864MHz, this test takes about 6 seconds.
# * From the formulae above, an "equivalent" GALPAT would take over an
hour.
# *
# * Byte operations would be acceptable for 1-bit wide memories, merely
# * being an application of parallel testing. However, our RAM is
8-bit, so
# * we use multiple operations to access each bit in a byte. This is
not
# * quite according to the test specification, as the lower bits in
each
# * byte are read/written up to 21 more times than they should be.
# * However, if the RAM is OK, correct values are written every time,
and
# * consideration of the fault model shows that this will only affect
soft
# * errors, which, by definition, are likely to be corrected by other
# * operations in the test procedure and would not be detected by most
test
# * algorithms.
# * If we just used byte operations, we might miss some coupling
faults.
# *
# * Errors are reported on the LCD, using in-line code (no subroutine
stack),
# * in the form "aaaa:xx (yy) -pp" where "aaaa" is the address with
the
error,
# * "xx" is the found data, "yy" is the expected data, and
"pp" is the
section
# * number (1A, 1B, 1C, 2, 3 or 4). DE holds an error count,
supplemented by
# * H to use 20 bits so that multiples of 8*8192 errors (eg every bit
in every
# * byte of 8K) can be recorded.
# *
# * The test always runs to completion, but then displays the error
count and
# * HALTs if it's non-zero. If it is zero, this version prints the
message
# * "RAM OK " and halts.
# *
# **************************************************************************
#
#
# ***** Memory addresses *****
#
rom = 0 # start of 8KB EPROM area
romtop = 0x1FFF # last byte in ROM
ram = 0x4000 # start of 8KB RAM area
ramsize = 8192 # size of RAM
ramtop = ram+ramsize-1 # last address in RAM
#
#
# ***** I/O addresses *****
#
LCDins = 0x80 # LCD base address, instruction
register
LCDdat = LCDins+1 # LCD data register
#
#
# ***** assorted constants *****
#
LCinit = 0x3C # LCD "Funct.Set": 8 bit data, 2 lines
LCDoff = 0x08 # set off: OR this with D/C/B for ON
Don = 4 # sets Display ON
LCDon = LCDoff + Don # set on: OR this with C/B for cursor
Con = 2 # sets Cursor ON
Bon = 1 # sets Blink ON (else cursor is solid)
LCDclr = 0x01 # clears LCD display/RAM, sets address
0
LCDhom = 0x02 # moves cursor to position 1, line 1
setCG = 0x40 # sets "write to char gen RAM". OR
with addr.
setDD = 0x80 # sets "write to data display". OR
with addr.
line2 = 0x40 # address of second line
setL2 = setDD + line2 # sets "write" to write to line 2
#
dly120 = 32 # loop constant for just under 120
microsecs
space = ' ' # ASCII space character
#
#
# **************************************************************************
# *
# * Here is the actual code...
# * Note there's no point in pushing registers or calling subroutines,
# * as we're scribbling all over all the RAM including the stack area.
# * Before we start, reset LCD and tell the world what we're going to
do.
# *
# **************************************************************************
#
.org rom
init: ld a, LCinit # tell it about data format etc
out (LCDins), a
ld b, dly120
waitI0: djnz waitI0 # let the LCD sort itself out
ld a, LCDclr # clear display and RAM, home cursor
out (LCDins), a
ld bc, 4900/7 # that takes about 4.9ms
waitLC: dec bc # loop takes 26 T-cycles, about 7
microsecs
ld a, b
or c
jr nz, waitLC # wait for init to complete
ld a, LCDon + Con # turn on the display
out (LCDins), a
ld b, dly120
waitI1: djnz waitI1 # let the LCD get it done
ld hl, SRBmsg
jp mssgHL # and return from there to following
code
SRBmsg: "RAM test " # this message fills the first line
.byte 0 # terminator
#
#
# ***** Step 0. Initialise, and set all RAM to 0's (we hope).
# 8Kbytes-worth of LDIR takes 21 T-cycles x 8192, about 47
milliseconds
#
SRB0: ld de, 0 # assume we're going to pass - no
errors
ld h, d # (error counter is 24-bit)
exx # save that thought!
ld hl, ram
ld (hl), 0 # set first location to zero
ld bc, ramsize - 1 # make 1FFF copies...
ld de, ram + 1 # ...starting here
ldir # block copy, fastest way to set all
the rest
#
# ***** Step 1. 3 pairs of read/write operations for each *bit* (not
byte).
# This is a marching pattern, but tests that each bit can be 1 or
0.
# For sake of speed, loops are done with JP, faster than JR if
jump
# is made. However, JR is faster if the jump is not taken, so is
used for
# the jump-on-failure -- which hopefully is rarely taken!
# "nxtbit" loop takes 110 T-cycles, about 30 microseconds.
# "nxtbyt" loop takes 8 nxtbits + 60 T-cycles = 940 T-cycles,
about 255us
# 8Kbytes-worth takes 255us x 8192 = 2.09 seconds.
#
ld bc, ramtop + 1 # where to stop
ld d, 1 # mask for current bit in current byte
ld hl, ram-ramtop-1
nxtbyt: add hl, bc # start position
ld e, 0 # what the whole of the current byte
should be
nxtbit: ld a, (hl) # ** Read: Ci(=0)
and d # select current bit
jr nz, fail1a # if not still 0, it's duff
OK1a: ld a, e # what it should be, so far
or d # ** Write: Ci <-- 1
ld (hl), a # put it away
ld a, (hl) # ** Read: Ci(=1)
and d
jr z, fail1b
OK1b: ld a, e # current bit is still zero in E
ld (hl), a # ** Write: Ci <-- 0
ld a, (hl) # ** Read: Ci(=0)
and d # select current bit again
jr nz, fail1c # if not still 0, it's duff
OK1c: ld a, e # what it should be, so far
or d # ** Write: Ci <-- 1
ld e, a # update the copy
ld (hl), a # put it to bed
rlc d # next bit - NB 8-bit rotate
jp nc, nxtbit
inc hl
ccf # it was set by the last RLC D
sbc hl, bc # see if we've run out of RAM
jp nz, nxtbyt
#
# ***** Step 2. Check each bit Ci(=1), then toggle it twice.
# "nxtbi2" loop takes 62 T-cycles, about 17 microseconds.
# "nxtby2" loop takes 8 nxtbits + 46 T-cycles = 542 T-cycles,
about 147us
# 8Kbytes-worth takes 147us x 8192 = 1.2 seconds.
#
ld hl, ram-ramtop-1
nxtby2: add hl, bc # E is still FF, this section doesn't
change it
nxtbi2: ld a, (hl) # ** Read: Ci(=1)
and d
jr z, fail2
OK2: ld a, e # what it should be (0FFH)
xor d # make current bit zero
ld (hl), a # ** Write Ci <-- 0
ld a, e # restore the '1'
ld (hl), a # ** Write Ci <-- 1
rlc d
jp nc, nxtbi2
inc hl
ccf # it was set
sbc hl, bc # see if we've run out of RAM
jp nz, nxtby2
#
# ***** Step 3. Check each bit Ci(=1), then toggle it three times.
# "nxtbi3" loop takes 77 T-cycles, about 21 microseconds.
# "nxtby3" loop takes 8 nxtbits + 46 T-cycles = 662 T-cycles,
about 180us
# 8Kbytes-worth takes 180us x 8192 = 1.5 seconds.
#
ld hl, ram-ramtop-1
nxtby3: add hl, bc
ld e, 0xFF # it gets changed to 0 as we go round
nxtbi3: ld a, (hl) # ** Read: Ci(=1)
and d
jr z, fail3
OK3: ld a, e # what the byte should be
xor d # make current bit zero
ld (hl), a # ** Write Ci <-- 0
ld a, e # restore the '1'
ld (hl), a # ** Write Ci <-- 1
xor d # make current bit zero
ld e, a # copy it
ld (hl), a # ** Write Ci <-- 0
rlc d
jp nc, nxtbi3
inc hl
ccf # it was set
sbc hl, bc # see if we've run out of RAM
jp nz, nxtby3
#
# ***** Step 4. Check each bit Ci(=0), then toggle it twice.
# "nxtbi4" loop takes 62 T-cycles, about 17 microseconds.
# "nxtby4" loop takes 8 nxtbits + 46 T-cycles = 542 T-cycles,
about 147us
# 8Kbytes-worth takes 147us x 8192 = 1.2 seconds.
#
ld hl, ram-ramtop-1
nxtby4: add hl, bc # E is now zero, after finishing Step 3
nxtbi4: ld a, (hl) # ** Read: Ci(=0)
and d
jr nz, fail4
OK4: ld a, e # what it should be (00H)
xor d # make current bit a '1'
ld (hl), a # ** Write Ci <-- 1
ld a, e # restore the '0'
ld (hl), a # ** Write Ci <-- 0
rlc d
jp nc, nxtbi4
inc hl
ccf # it was set
sbc hl, bc # see if we've run out of RAM
jp nz, nxtby4
jp SRBend # testing completed
#
#
# **************************************************************************
# *
# * The following code uses IX to store the return address. It uses
BC,
# * but restores BC = ramtop + 1 on exit. It prints a message to give
# * the failed address, data as read, expected data, and section where
# * the error was detected.
# *
# **************************************************************************
#
fail1a: ld ix, OK1a # here if Read: Ci(=0) failed in Step
1a
ld a, 0x1A # section code
jr fail
fail1b: ld ix, OK1b # here if Read: Ci(=1) failed in Step
1b
ld a, 0x1B
jr fail
fail1c: ld ix, OK1c # here if Read: Ci(=0) failed in Step
1c
ld a, 0x1C
jr fail
fail2: ld ix, OK2 # here if Read: Ci(=0) failed in Step 2
ld a, 2
jr fail
fail3: ld ix, OK3 # here if Read: Ci(=1) failed in Step 3
ld a, 3
jr fail
fail4: ld ix, OK4 # here if Read: Ci(=0) failed in Step 4
ld a, 4
#
# ***** This is the part that actually does the printing
#
fail: ex af, af # save section code for the moment
ld a, setL2
out (LCDins), a # address LCD second line
ld b, dly120
fwait1: djnz fwait1 # give it time to do its stuff
ld c, h # upper byte of failing address
ld iy, f.L
jr f.hex # print it
f.L: ld c, l # lower byte of failing address
ld iy, f.coln
jr f.hex
f.coln: ld a, ':' # print colon
out (LCDdat), a
ld b, dly120
fwait2: djnz fwait2
ld c, (hl) # get the duff data
ld iy, f.spc
jr f.hex
f.spc: ld a, space # print space
out (LCDdat), a
ld b, dly120
fwait3: djnz fwait3
ld a, '(' # print left bracket
out (LCDdat), a
ld b, dly120
fwait4: djnz fwait4
ld c, e # expected data
ld iy, f.brk
jr f.hex
f.brk: ld a, ')' # print right bracket
out (LCDdat), a
ld b, dly120
fwait5: djnz fwait5
ld a, space # print space
out (LCDdat), a
ld b, dly120
fwait6: djnz fwait6
ld a, '-' # print a dash
out (LCDdat), a
ld b, dly120
fwait7: djnz fwait7
ex af, af # where we saved the section number
ld c, a
ld iy, f.fin
jr f.hex
f.fin: exx # remember H,DE' had zero to signify
"pass"
inc e # so update error count
jr nz, f.exx
inc d # update middle byte too, if necessary
jr nz, f.exx
inc h # update top byte if necessary
f.exx: exx
ld bc, ramtop + 1 # caller expects this to be here
jp (ix) # back to "caller" (or wherever!)
#
#
# **************************************************************************
# *
# * This pseudo-subroutine is "called" to print a pair of hex digits.
# *
# * Entry: value in C, return address in IY.
# * Exit: A and B mangled.
# *
# **************************************************************************
#
f.hex: ld a, c # get value to print
rrca # move upper nibble to lower
rrca
rrca
rrca
and 0x0F # remove any garbage
cp 0x0A # do we need a letter?
jr c, AOK.1
add a, 'A' - 0x3A # yes, adjust it
AOK.1: add a, '0' # make it ASCII
out (LCDdat), a # print it
ld b, dly120
Await1: djnz Await1 # give LCD time to do his stuff
f.hex1: ld a, c # get lower nibble this time
and 0x0F
cp 0x0A
jr c, AOK.2
add a, 'A' - 0x3A
AOK.2: add a, '0'
out (LCDdat), a
ld b, dly120
Await2: djnz Await2
jp (iy) # return to wherever caller specified
#
# **************************************************************************
# *
# * This pseudo-subroutine prints a message (if the as-yet-untested LCD
is
# * working) -- for use by the test routines that have no stack.
# *
# * Place the required message immediately after the call; this code
returns
# * to the address immediately beyond the message.
# *
# * Entry: HL points to the message, zero-terminated
# * Exit: A, B mangled, HL updated
# *
# **************************************************************************
#
mssgHL: ld a, (hl) # get the character
or a # is it the terminator?
inc hl # doesn't affect the flags...
jr nz, do.mhl # ...so this depends on the character
jp (hl) # to the code following the message
do.mhl: out (LCDdat), a # send character to LCD
ld b, dly120
waitm: djnz waitm # wait for our slow LCD
jr mssgHL # repeat until cooked
#
#
# **************************************************************************
# *
# * Arrive here on completion of RAM testing
# *
# **************************************************************************
#
SRBend: ld a, setDD + 4 # to replace "test" on LCD with count
out (LCDins), a
ld b, dly120
fwait8: djnz fwait8 # give LCD time to do his stuff
exx # alternate reg set had DE = error
count
ld a, h # ...see if it passed
or d
or e
jp nz, SRBerr # skip if errors
ld hl, SRB.OK # tell the user all is well
jp mssgHL
SRB.OK: "OK "
.byte 0 # marks end of message
halt # we're all done here
#
# ***** do this if RAM failed test
#
SRBerr: ld c, h # error count, MSByte
ld iy, SRBE.M
jr f.hex1 # only show 5 digits, to fit 16-char
display
SRBE.M: ld c, d # error count, middle byte
ld iy, SRBE.L
jr f.hex
SRBE.L: ld c, e # error count, LSByte
ld iy, SRB.em
jr f.hex
SRB.em: ld hl, errors # message " errors"
jp mssgHL
errors: " errors" # with 20-bit count, just fits 16-char
display
.byte 0 # marks end of message
SRBhlt: halt # unwise to carry on with duff RAM
--
Pete Peter Turnbull
Network Manager
University of York