RAM test: was Re: PET startup sequence??

From: Pete Turnbull <pete_at_dunnington.u-net.com>
Date: Mon Aug 16 17:51:06 2004

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_at_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
Received on Mon Aug 16 2004 - 17:51:06 BST

This archive was generated by hypermail 2.3.0 : Fri Oct 10 2014 - 23:36:34 BST