;$PAGINATE
;$title(Arnold 5 test)
;$subtitle(RAM wave pattern test)
;$copyright(Copyright (c) 1989, 1990, Amstrad plc.)
;$pagewidth=131
;
;        PUBLIC  RAMTest         ;called from TESTPACK and indirectly out of
;                                ; command table in MESSAGES
;
;        EXTERN  ScreenSetMode
;        EXTERN  SetCursorPos
;        EXTERN  PrintStringHL
;        EXTERN  PrintAHex
;        EXTERN  KeyboardReadNoDelay
;        EXTERN  OfferRetest
;
;
;        EXTERN  ?TempSPStore            ;(TESTVARS)
;        EXTERN  ?RAMTestStack           ;(TESTVARS)
;        EXTERN  ?NumberofRAMBlocks      ;(TESTVARS)
;        EXTERN  .RecognizableString     ;(MESSAGES)
;        EXTERN  .LenRecogString         ;(MESSAGES)
;        EXTERN  .TestingBlockMess       ;(MESSAGES)
;
;
;RAMLessCALL     %MACRO  TargetRoutine
;                %GENSYM ReturnLabel
;                ld      sp,ReturnLabel
;                jp      TargetRoutine
;ReturnLabel     defw    ReturnLabel + 2
;                %ENDM
;
;        DEFSEG  TestCode, CLASS=CODE
;
;        SEG     TestCode

;=======
RAMTest:
;=======
;
; This routine is called very early in the morning (before the menu is
; processed). Control passes to here with a "RAMLessCall" so SP is pointing
; to the return address. We want to go on and use RAMLessCalls to the test
; routine so we have to preserve the final return address in some way. What
; I will do is store the entry SP value in IY.
;
; This test is performed in 8 sections. Each testing a 16K block. The order of
; testing is as follows:
;
;    Find out if there is 64K (4 blocks) or 128K (8 blocks) of 16K
;    move the test routine to RAM at 04000h
;    control is passed to RAM based routine
;     lower ROM is disabled
;     TEST 00000h..03FFFh RAM block 0
;     lower ROM is enabled
;     control is passed back to lower test routine
;    TEST 04000h..07FFFh RAM block 1 switched in (power on state)
;    TEST 08000h..0BFFFh RAM block 2
;    top ROM is disabled
;    TEST 0C000h..0FFFFh RAM block 3
;    check to see if there looks like being an extra 64K
;    if not, end test here
;    FOR i=4 TO 7
;     switch RAM block i to 04000h..07FFFh
;     TEST 04000h..07FFFh
;    NEXT i
;
        ld      (QUESTMARK_TempSPStore),sp       ;save stack position in QUESTMARK_RAMStack

;
; The following code now tries to determine if the machine really has 128K
; of memory fitted. It does this by writing a known string at 04000h which
; will go into block 1. It then attempts to switch RAM blocks so that block 4
; appears at 04000h. If the recognizable text has disappeared then the RAM
; switch must have worked and there is probably 128K of memory so we then
; procede to switch 4, 5, 6 and 7 in turn to 04000h and perform the RAM test
; on each. If this first RAM switch does not make the text disapper then we
; try to switch block 7 to appear at 4000h. If this doesn't work then we can
; be fairly certain that there really only is 64K and go home happy. If, on
; the other hand, this second RAM switch appears to work then we have
; identified a block switching failure.
;
        ld      sp,QUESTMARK_RAMTestStack        ;use local stack in this test

        ld      hl,.RecognizableString
        ld      de,04000h               ;start of RAM block 1 at 04000h
        ld      bc,.LenRecogString
        ldir                            ;put some known text there

        ld      bc,07FC4h               ;7Fxx=ULA Cx=switch RAM 4=block 4
        out     (c),c                   ;try to make block 4 appear at 04000h

        ld      hl,04000h               ;area to check
        ld      bc,.LenRecogString      ;length to check
        ld      de,.RecognizableString
_RAMCheckString4:
        ld      a,(de)                  ;get char of known string
        inc     de
        cpi                             ;check a=(hl) and HL+, BC-
        jr      nz,_RAMString4Wrong     ;something else has been found
        ld      a,b
        or      c                       ;is BC=0 yet QUESTMARK_
        jr      nz,_RAMCheckString4
;
; here when string was found so block 1 did not disappear after the 7F<-C4
; this implies that either there is only 64K of RAM (so the RAM test should
; end) or that there is summat wrong with the RAM switching register. To
; be doubly sure we will just do the same thing on RAM block 7. If this
; doesn't appear either then we have to assume there really only is 64K. If
; however 7 does appear then we have found a fault in RAM Bank switching
; logic.
;
        ld      bc,07FC0h               ;code=0 means block 1 to 04000h
        out     (c),c                   ;back to switch on state (I hope).

        ld      hl,.RecognizableString
        ld      de,04000h               ;start of RAM block 1 at 04000h
        ld      bc,.LenRecogString
        ldir                            ;put some known text there

        ld      bc,07FC7h               ;7Fxx=ULA Cx=switch RAM, 7=block 7
        out     (c),c                   ;try to make block 7 appear at 04000h

        ld      hl,04000h               ;area to check
        ld      bc,.LenRecogString      ;length to check
        ld      de,.RecognizableString
_RAMCheckString7:
        ld      a,(de)                  ;get char of known string
        inc     de
        cpi                             ;check a=(hl) and HL+, BC-
        jr      nz,_RAMString7Wrong     ;something else has been found
        ld      a,b
        or      c                       ;is BC=0 yet QUESTMARK_
        jr      nz,_RAMCheckString7
;
; The recognizable string remained visible after the attempted switch of
; block 7 (as well as the earlier block 4) so we must assume that there
; really only is 64K and end the RAMTest with a PASS.
;
        ld      a,3                     ;we could possibly report the value
        jp      _RAMFoundAmount

_RAMString7Wrong:
;
; Here when switching block 4 did not obscure the test string in block 1
; but switching block 7 did obscure it. The RAM switching logic is not
; working properly
;
        ld      a,3                     ;error code = RAM switch failure
        scf
        jp      _RAMEndTest

_RAMString4Wrong:
;
; Here when switching block 4 caused the string in block 1 to disappear. This
; suggests that there is 128K and the RAM switching logic is working so we
; procede to test blocks 4, 5, 6 and 7.
;
        ld      a,7

_RAMFoundAmount:
        ld      (QUESTMARK_NumberOfRAMBlocks),a

        ld      bc,07FC0h
        out     (c),c                   ;put "normal" RAM back

        ld      de,00a02h               ;(10,6)
        call    SetCursorPos

        ld      hl,.TestingBlockMess
        ld      b,1
        call    PrintStringHL

        ld      a,(QUESTMARK_NumberOfRAMBlocks)
        call    PrintAHex

        ld      de,00a1eh
        call    SetCursorPos            ;move to number position

        ld      a,1
        call    PrintAHex

        ld      hl,04000h               ;test block 1 at 04000h
        call    _RAMTestBlock
        ld      c,1                     ;block id in case it failed
        jp      c,_RAMTestFailure

        call    _RAMTestForEscape

        ld      de,00a1eh
        call    SetCursorPos            ;move to number position

        ld      a,2
        call    PrintAHex

        ld      hl,8000h
        ld      de,4000h
        ld      bc,3FFFh
        ldir                            ;copy RAM based variables

        ld      sp,5000h                ;somewhere safe

        ld      hl,08000h               ;test block 2 at 08000h
        call    _RAMTestBlock
        ld      c,2                     ;block id in case it failed
        jp      c,_RAMTestFailure

        ld      sp,QUESTMARK_RAMTestStack        ;reinitialise stack used during test

        ld      hl,4000h
        ld      de,8000h
        ld      bc,3FFFh
        ldir                            ;put RAM variables back

        call    _RAMTestForEscape

        ld      de,00A1Eh
        call    SetCursorPos

        ld      a,0
        call    PrintAHex

        ld      hl,_RAMTestBlock        ;start of routines to be moved
        ld      de,04000h               ;somewhere in "safe" RAM
        ld      bc,_RAMTestZeroLen
        ldir                            ;move block to the RAM

        call    04002h                  ;Once the code has been moved to RAM
                                        ;I know that the _RAMTestZero entry
                                        ;point must be 04002h

        ld      c,0                     ;block id in case it failed
        jp      c,_RAMTestFailure

        call    _RAMTestForEscape


        ld      hl,0C000h
;        RAMLessCALL _RAMTestBlock       ;test block 3 at 0C000h
        call    _RAMTestBlock           ;test block 3 at 0C000h
        ld      c,3                     ;block id in case it failed
        jp      c,_RAMTestFailure


        exx
        ld      c,08Ah                  ;reset C' to correct MODE etc
        exx

        call    _RAMTestForEscape
;
; Now we check to see if our earlier test told us there was 64K or 128K. If its
; 4 (0..3) blocks then we end here else we test the 2nd 64K
;
        ld      a,(QUESTMARK_NumberOfRAMBlocks)
        cp      3
        jp      z,_RAMEndTest

        ld      a,1
        call    ScreenSetMode

        ld      de,00a02h               ;(10,6)
        call    SetCursorPos

        ld      hl,.TestingBlockMess
        ld      b,1
        call    PrintStringHL

        ld      a,(QUESTMARK_NumberOfRAMBlocks)
        call    PrintAHex


        ld      de,00a1eh
        call    SetCursorPos            ;move to number position

        ld      a,4
        call    PrintAHex

        ld      bc,07FC4h
        out     (c),c                   ;switch block 4 to 04000h
        ld      hl,04000h
        call    _RAMTestBlock           ;now test the block that appeared there
        ld      c,4                     ;block id in case it failed
        jp      c,_RAMTestFailure

        call    _RAMTestForEscape

        ld      de,00a1eh
        call    SetCursorPos            ;move to number position

        ld      a,5
        call    PrintAHex

        ld      bc,07FC5h
        out     (c),c                   ;switch block 5 to 04000h
        ld      hl,04000h
        call    _RAMTestBlock           ;now test the block that appeared there
        ld      c,5                     ;block id in case it failed
        jp      c,_RAMTestFailure

        call    _RAMTestForEscape

        ld      de,00a1eh
        call    SetCursorPos            ;move to number position

        ld      a,6
        call    PrintAHex

        ld      bc,07FC6h
        out     (c),c                   ;switch block 6 to 04000h
        ld      hl,04000h
        call    _RAMTestBlock           ;now test the block that appeared there
        ld      c,6                     ;block id in case it failed
        jp      c,_RAMTestFailure

        call    _RAMTestForEscape

        ld      de,00a1eh
        call    SetCursorPos            ;move to number position

        ld      a,7
        call    PrintAHex

        ld      bc,07FC7h
        out     (c),c                   ;switch block 7 to 04000h
        ld      hl,04000h
        call    _RAMTestBlock           ;now test the block that appeared there
        ld      c,7                     ;block id in case it failed
        jp      c,_RAMTestFailure

        ld      bc,07FC0h
        out     (c),c                   ;put RAM block 1 back at 04000h

        xor     a                       ;clear Carry cos all 128K works
        ld      a,128                   ;may want to report this amount QUESTMARK_
        jr      _RAMEndTest

_RAMEndTest:
;
        ld      d,h
        ld      e,l                     ;in case of error we cannot rely on HL

        exx
        ld      bc,7F8Ah                ;its "normal" value
        exx

        ld      bc,7FC0h
        out     (c),c                   ;put RAM paging back to default

        ld      sp,(QUESTMARK_TempSPStore)       ;set SP back to its entry value
        jr      c,_RAMSkipReTest        ;an error has been flagged so drop out
        push    af
        call    OfferRetest
        pop     bc
        jp      c,RAMTest
        push    bc
        pop     af
_RAMSkipReTest:
        ret

_RAMTestFailure:
;
; If, after testing a 16K block, a RET C is made then control passes here.
; At this point HL=the physical RAM address (0..FFFF), B:4..7 identifies
; which bit failed (1..8), B:0..3 identifies the actual area of failure
; (fill, wave up, wave down, etc). C holds the number of the block that
; failed (0..7)
;
        ld      a,h
        and     03Fh                    ;making HL lie in range 0..16K
        ld      h,a

        ld      a,2                     ;RAM error
        scf
        jr      _RAMEndTest

;=================
_RAMTestForEscape:
;=================
;
        call    KeyboardReadNoDelay
        cp      0FFh
        ret     z
        xor     a                       ;flag no error
        jp      _RAMEndTest

;VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
;
; The following block of code is moved into RAM at 04000h after the block 1
; there has been OKd. This code then disables the lower ROM (from which it is
; called by the above code), tests the RAM at 00000..03FFFh, then re-enables
; the lower ROM and RETs
;
; Included in this code which is moved is the actual "_RAMTestBlock" routine.
; It is called by the code while resident in RAM (with this ROM hidden) so
; must be there. To save us having to do any tricky offset calculations, the
; entry for _RAMTestBlock and _RAMTestZero are arranged to be at fixed
; locations.
;
; You may well be asking yourselves why the entry point for _RAMTestBlock
; is just a jump over _RAMTestZero to the rest of its code. Well this is to
; make it easy to work out the CALL address out of _RAMTestZero that is made
; to _RAMTestBlock when this code is resident in RAM at 04000h.
;
; I can tell you for nothing that while RAM based, _RAMTestBlock EQU 04000h
; and (because it's a JR), _RAMTestZero EQU 040002h
;
_RAMTestBlock:
        jr      _RAMRestOfRAMTestBlock  ;see above comment

_RAMTestZero:
;
; After the move to RAM this code is at the fixed location 04002h. It is
; entered from RAMTest by a fixed "RAMLessCALL 04002h"
;
        ld      bc,07F8Dh               ;I/O addr of ULA ROM/MODE register
        out     (c),c                   ;switch off both ROMs

;
; following should really be 00000h
;
        ld      hl,00000h               ;block to test
        call    04000h                  ;test the block - I can hard code the
                                        ;04000h here because, as explained
                                        ;above, while RAM based:
                                        ;  _RAMTestBlock  EQU  04000h
;
; Carry flag holds success or failure
;
        ld      bc,7F89h
        out     (c),c                   ;switch lower ROM on again

        ret                             ;back into the lower ROM

_RAMRestOfRAMTestBlock:
;
; This is the main test routine that does a 16K block at a time. It is entered
; with HL=<start address> and the test is performed from there to HL+16K.
;
; On exit C is set if an error was detected otherwise it returns NC.
;
; To report the error we need to know the offset (returned in HL), whether it
; was on Fill, Wave Up Checking Test Pattern, Wave Up Checking Inverse,
; Wave Down Checking Inverse or Wave Down Checking TP (B=1, 2, 3, 4 or 5 resp.).
;
; The Block number is not returned by this routine. It is up to the caller to
; know which block he was testing at the time and pass that back in C (say).
; This routine will return an indication of which bit failed in the upper
; nybble of B (B:4, B:5 and B:6 to be precise).
;
; Because this routine is run in both the ROM and is moved to RAM it must
; be re-locatable.
;
; The algorithm for this RAM test is as follows:
;
; FOR test_pat = 0 TO 128 SHIFT 1
;  inverse = test_pat XOR 0FFh
;  Store HL in IX
;  FOR HL=HL TO HL+16384
;   (HL) <- test_pat
;   CP (HL),test_pat
;   IF <> GOTO RAM_FILL_FAIL                    (B=1)
;  NEXT HL
;  Get back copy of stored HL from IX
;  Make binary inverse of test_pat
;  FOR HL=HL TO HL+16384
;   CP (HL),test_pat
;   IF <> GOTO RAM_WAVE_UP_TP_FAIL              (B=2)
;   (HL) <- inverse
;   CP (HL),inverse
;   IF <> GOTO RAM_WAVE_UP_INVERSE_FAIL         (B=3)
;  NEXT HL
;  Get back copy of stored HL
;  FOR HL=HL+16384 TO HL STEP -1
;   CP (HL),inverse
;   IF <> GOTO RAM_WAVE_DOWN_INVERSE_FAIL       (B=4)
;   (HL) <- test_pat
;   CP (HL),test_pat
;   IF <> GOTO RAM_WAVE_DOWN_TP_FAIL            (B=5)
;  NEXT HL
; NEXT test_pat
;
; IX will hold the copy of HL so that we can keep going back to the start
; of the block.
;
; BC will hold the 16K counts.
;
; D will hold the test pattern and E will hold its binary inverse. The outer
; loop will shift this value left until it drops off
;
        ld      d,1                     ;start at 1 (will then shift 2,4,8,...)
_RAMBlockPatternLoop:
        ld      a,d                     ;must use A for BIN ops.
        xor     0FFh                    ;flip the bits to make "inverse"
        ld      e,a                     ;and store it safe

;        ToIXl   l
;        ToIXh   h                       ;store a copy of HL (block start) in IX
; store HL in BC'
        ld      a,l
        exx
        ld      c,a
        exx
        ld      a,h
        exx
        ld      b,a
        exx

        ld      bc,16384                ;gonna fill 16Ks worth with D
_RAMBlockFillLoop:
        ld      a,d                     ;need pattern in A to do a CP
        ld      (hl),d                  ;store "test_pat"
        cp      (hl)                    ;did it store OK QUESTMARK_
        jr      nz,_RAMBlockFillFail    ;Nope so croak
        inc     hl                      ;step location on
        dec     bc                      ;count down
        ld      a,b
        or      c
        jr      nz,_RAMBlockFillLoop    ;loop until we've done the full 16K

;        FromIXl l
;        FromIXh h                       ;get back block start addr
; get HL from BC'
        exx
        ld      a,c
        exx
        ld      l,a
        exx
        ld      a,b
        exx
        ld      h,a
        jr      _RAMSkipTheJump

_RAMHalfwayBack:
        jr      _RAMBlockPatternLoop    ;doing a long relative jump

_RAMSkipTheJump:
        ld      bc,16384                ;going to wave up 16Ks worth
_RAMBlockWaveUpLoop:
        ld      a,d
        cp      (hl)                    ;check is test_pat still stored
        jr      nz,_RAMBlockUpTPFail    ;test_pat failed on Wave Up
        ld      a,e                     ;now gonna be doing a CP with inverse
        ld      (hl),e                  ;store inverse
        cp      (hl)                    ;did that store OK QUESTMARK_
        jr      nz,_RAMBlockUpInvFail   ;inverse did not store OK on Wave Up
        inc     hl                      ;step location on
        dec     bc
        ld      a,b
        or      c
        jr      nz,_RAMBlockWaveUpLoop

;        FromIXl l
;        FromIXh h                       ;get back block start addr
; get HL from BC'
        exx
        ld      a,c
        exx
        ld      l,a
        exx
        ld      a,b
        exx
        ld      h,a

        ld      bc,16384                ;now gonna wave down 16Ks worth
        add     hl,bc                   ;set HL to end of block to test
        dec     hl
_RAMBlockWaveDownLoop:
        ld      a,e                     ;initially expect to find inverse
        cp      (hl)                    ;is inverse still stored QUESTMARK_
        jr      nz,_RAMBlockDownInvFail
        ld      a,d                     ;now we'll be looking for test_pat
        ld      (hl),d                  ;store test_pat on way down
        cp      (hl)                    ;but did it store QUESTMARK_
        jr      nz,_RAMBlockDownTPFail
        dec     hl                      ;step location back
        dec     bc
        ld      a,b
        or      c
        jr      nz,_RAMBlockWaveDownLoop

        inc     hl                      ;back from (start-1) to start

        ld      a,d
        sla     a                       ;move left across the byte
        ld      d,a
        jr      nc,_RAMHalfWayBack      ;haven't dropped into the carry yet
;        jr      nc,_RAMBlockPatternLoop ;haven't dropped into the carry yet

        xor     a                       ;clear carry cos RAM is OK
        ret

_RAMBlockFillFail:
        ld      b,1                     ;error code
        jr      _RAMBlockFailCommon

_RAMBlockUpTPFail:
        ld      b,2                     ;error code
        jr      _RAMBlockFailCommon

_RAMBlockUpInvFail:
        ld      b,3                     ;error code
        jr      _RAMBlockFailCommon

_RAMBlockDownInvFail:
        ld      b,4                     ;error code
        jr      _RAMBlockFailCommon

_RAMBlockDownTPFail:
        ld      b,5                     ;error code
        jr      _RAMBlockFailCommon


_RAMBlockFailCommon:
;
; B = 1..5 to identify type of error.
;
        ld      c,a                     ;A is what we expected to find->C
        ld      a,(hl)                  ;A now has what's actually there

        xor     c                       ;if you XOR two values together, the
                                        ;one bits left are the bits where the
                                        ;bytes were different.
;
; if Z is set after the XOR then A and C were the same (i.e. (HL) has settled
; down to be what it should) so we exit at this point with B:4..B:6 = 0 meaning
; we can't identify the failing bit
;
        jr      z,_RAMBlockFailNoBit

;
; if NZ then at least one bit is different so we just keep shifting A and
; incrementing C until we see carry go high. (This only identifies the bottom
; bit if there are multiple failing bits).
;
        ld      c,1                     ;assume bit 1 failed
_RAMBlockFailScanBitLoop:
        srl     a
        jr      c,_RAMBlockFailBitFound ;the bit has been found and C idents
        inc     c
        jr      _RAMBlockFailScanBitLoop

_RAMBlockFailBitFound:
;
; now going to add the BIT ID that is in C into the top nybble of B. The
; lower nybble of B holds 1..5 to identify the type of failure.
;
        ld      a,c                     ;C=1..8
        dec     a                       ;make 0..7
        sla     a
        sla     a
        sla     a
        sla     a                       ;move to upper nybble
        or      b                       ;add in the error code
        ld      b,a                     ;put result back in B

_RAMBlockFailNoBit:
        scf                             ;C indicates a failure
        ret

_RAMTestZeroLen .equ     $ - _RAMTestBlock
;
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
