;;; Apple IIe Auxiliary memory audit routines ;;; Copyright © 2017 Zellyn Hunter !zone auxmem { ;; Bitmask for whether ranges of Cxxx memory look like ROM or ;; something else. 1 means it looks like ROM, 0 means it ;; doesn't. How do we check whether a range looks like ROM? ;; Check values at four different addresses, carefully chosen ;; to have consistent values in different ROM versions. The ;; check data is at .cxtestdata .C_12 = %0010 ; Is C100-C2FF ROM or something else? .C_47 = %0100 ; Is C400-C7FF ROM or something else? .C_3 = %1000 ; Is C300-C3FF ROM or something else? .C_8f = %0001 ; Is C800-CFFE ROM or something else? .C_0 = %0000 .C_skip = $80 ; Skip ROM checks. .C_1348 = .C_12 | .C_3 | .C_47 | .C_8f ; Everything is ROM .C_38 = .C_3 | .C_8f ; C300-C3FF and C800-CFFE are ROM .checkdata = tmp1 .ismain = tmp3 .region = tmp4 .actual = tmp1 .desired = tmp2 ;;; Auxmem tests. First, we try the language card test again, but in ;;; auxmem. (If the language card test failed the first time, we skip ;;; this part.) ;;; ;;; Then, we try data-driven tests. ;;; ;;; On success, carry will be clear; on failure, set. AUXMEMTESTS lda #0 sta AUXRESULT ;; If we have 64k or less, skip this test. lda MEMORY cmp #65 bcs + +print !text "64K OR LESS:SKIPPING AUXMEM TEST",$8D +printed sec rts + +print !text "TESTING AUX MEM",$8D +printed ;; If we failed the Language Card test already, there's no ;; point in trying the auxmem version of it. lda LCRESULT bne .auxlc +print !text "LC FAILED BEFORE:SKIPPING AUXMEM LC",$8D +printed jmp .datadriventests .auxlc ;; Run langcard tests in auxmem lda LCRESULT sta LCRESULT1 lda #0 sta LCRESULT ;; Store distinct values in RAM areas overwritten by the ;; language card test, to see if they stay safe. The language ;; card tests should leave non-auxmem memory and language card ;; bank alone. These $44 bytes will act as canaries. lda $C08B ; Read and write bank 1 lda $C08B lda #$44 sta $D17B ; $D17B is $53 in Apple II/plus/e/enhanced sta $FE1F ; FE1F is $60 in Apple II/plus/e/enhanced lda $C083 ; Read and write bank 2 lda $C083 lda #$44 sta $D17B jsr zptoaux sta SET_ALTZP jsr LANGCARDTESTS_NO_CHECK sta RESET_ALTZP jsr zpfromaux lda LCRESULT bne + +prerr $0008 ;; E0008: We tried to run the langcard tests again with auxmem (ALTZP active), and they failed, so we're quitting the auxmem test. !text "QUITTING AUXMEM TEST DUE TO LC FAIL",$8D +prerred sec rts ;; Check that the stuff we stashed in main RAM was unaffected. + lda $C088 ; Read bank 1 lda $D17B cmp #$44 beq + pha +print !text "WANT BANK1 $D17B" +printed beq .lcerr + lda $C080 ; Read bank 2 lda $D17B cmp #$44 beq + pha +print !text "WANT BANK2 $D17B" +printed beq .lcerr + lda $FE1F cmp #$44 beq .datadriventests ; all three canaries were OK. Jump to main data-driven tests. pha +print !text "WANT RAM $FE1F" +printed .lcerr +print !text "=$44;GOT $" +printed pla jsr PRBYTE +prerr $0009 ;; E0009: We wrote $44 to main RAM in the three test locations used by the LC test. They should have been unaffected by the LC test while it was using auxmem, but at least one of them was modified. !text "" +prerred sec rts .success lda #' ' sta $427 +print !text "AUXMEM TESTS SUCCEEDED",$8D +printed lda #1 sta AUXRESULT clc rts ;;; Main data-driven test. PCL,PCH holds the address of the next ;;; data-driven test routine. We expect the various softswitches ;;; to be reset each time we loop at .ddloop. .datadriventests lda #<.auxtests sta PCL lda #>.auxtests sta PCH ;;; Main data-drive-test loop. .ddloop ldy #0 ; data-driven tests are null-terminated. lda (PCL),Y beq .success lda #3 sta SET_ALTZP sta SET_RAMWRT sta SET_RAMRD .initloop ; Loop twice: initialize aux to $3 and main to $1. ;; Store current value of A (first $3, then $1) into all ;; locations in [.memorylocs:.memorylocs+.memorylen]. (This ;; will store A in $00 several times, but that's ok. ldy #.memorylen - ldx .memorylocs,y stx + +1 ldx .memorylocs+1,y stx + +2 + sta $ffff ;; this address gets replaced dey dey bpl - sta RESET_ALTZP sta RESET_RAMWRT sta RESET_RAMRD sec sbc #2 bcs .initloop jmp (PCL) ; Jump to test routine. ;; Test routine will JSR back to here, so the check data address is on the stack. ;; .checkdata (tmp1/tmp2) is the pointer to the current checkdata byte ;; .ismain (tmp3) is the main/aux loop counter. ;; .region (tmp4) is the zp/main/text/hires loop counter .check ;; Increment all the test memory locations, so we can see what we were reading and writing. inc $ff inc $100 inc $200 inc $3ff inc $427 inc $7ff inc $800 inc $1fff inc $2000 inc $3fff inc $4000 inc $5fff inc $bfff ;; pull address off of stack: it points just below check data for this test. pla sta .checkdata pla sta .checkdata+1 ;; First checkdata byte is for Cxxx tests. jsr NEXTCHECK ; grab the next byte of check data in A, use it to set flags. bmi + jsr .checkCxxx ;; Save checkdata address in XY. Reset all softswitches. Then restore checkdata. + ldx .checkdata ldy .checkdata+1 jsr RESETALL stx .checkdata sty .checkdata+1 ;; Do the next part twice. lda #1 sta .ismain jsr NEXTCHECK .checkloop ; Loop twice here: once for main, once for aux. lda #4 sta .region ldx #$fe ldy #0 .memlp inx inx lda .memorylocs,x sta SRC lda .memorylocs+1,x sta SRC+1 ora SRC beq .memlpinc ;; Perform the actual memory check. lda (SRC),y cmp (.checkdata),y bne .checkerr beq .memlp .memlpinc jsr NEXTCHECK dec .region ; loop four times: zero, main, text, hires bne .memlp dec .ismain bmi .checkdone ldx .checkdata ldy .checkdata+1 lda .ismain sta SET_ALTZP sta SET_RAMRD sta SET_RAMWRT stx .checkdata sty .checkdata+1 sta .ismain jmp .checkloop .checkdone ;; Jump PCL,PCH to next test, and loop. ldx .checkdata ldy .checkdata+1 jsr RESETALL stx PCL sty PCH jmp .ddloop .checkerr ;; X = index of memory location ;; A = actual ;; Y = 0 ;; desired = (.checkdata),y pha lda .ismain clc ror ror ora (.checkdata),y tay pla ;; Now: ;; X = index of memory location ;; A = actual ;; Y = desired | (high bit set if main, unset=aux) jsr RESETALL sta .actual sty .desired lda .memorylocs,x sta SRC lda .memorylocs+1,x sta SRC+1 +print !text "GOT $" +printed lda .actual jsr PRBYTE +print !text " AT $" +printed ldx SRC ldy SRC+1 jsr PRNTYX lda .desired bpl + eor #$80 sta .desired +print !text " OF MAIN MEM (WANT $" +printed beq ++ + +print !text " OF AUX MEM (WANT $" +printed ++ lda .desired jsr PRBYTE lda #')' jsr COUT lda #$8D jsr COUT jsr .printtest +prerr $000A ;; E000A: This is a data-driven test of main and auxiliary memory softswitch operation. We initialize $FF, $100, $200, $3FF, $427, $7FF, $800, $1FFF, $2000, $3FFF, $4000, $5FFF, and $BFFF in main RAM to value 1, and in auxiliary RAM to value 3. Then, we perform a testdata-driven sequence of instructions. Finally we (try to) increment all test locations. Then we test the expected values of the test locations in main and auxiliary memory. For more information on the operation of the auxiliary memory soft-switches, see Understanding the Apple IIe, by James Fielding Sather, Pg 5-22 to 5-28. !text "FOLLOWED BY INC OF TEST LOCATIONS. SEE" +prerred sec rts ;;; Check that the expected ROM areas are visible. .checkCxxx .gotCxxx = tmp0 .wantCxxx = SCRATCH pha jsr .genCxxxFingerprint pla cmp .gotCxxx beq .checkCxxxDone lda .gotCxxx ;; Reset, but copy .checkdata over. ldx .checkdata ldy .checkdata+1 jsr RESETALL stx .checkdata sty .checkdata+1 sta .gotCxxx ldy #0 lda (.checkdata),y sta .wantCxxx jsr .printtest +print !text "WANT:",$8D +printed lda .wantCxxx jsr .printCxxxBits +print !text "GOT:",$8D +printed lda .gotCxxx jsr .printCxxxBits +prerr $000B ;; E000B: This is a the Cxxx-ROM check part of the auxiliary memory data-driven test (see E000A for a description of the other part). After a full reset, we perform a testdata-driven sequence of instructions. Finally we check which parts of Cxxx ROM seem to be visible. We check C100-C2FF, C300-C3FF, C400-C7FF (which should be the same as C100-C2FF), and C800-CFFE. For more details, see Understanding the Apple IIe, by James Fielding Sather, Pg 5-28. !text "CXXX ROM TEST FAILED" +prerred ;; Don't continue with auxmem check: return from parent JSR. pla pla sec rts .checkCxxxDone rts .genCxxxFingerprint .dataptr = SCRATCH2 .want = SCRATCH3 .loopctr = SCRATCH lda #0 sta .gotCxxx lda #0 sta .dataptr lda #4 sta .loopctr -- clc ror .gotCxxx ldx #4 ; four check bytes per region lda #$8 ; start out with positive match bit ora .gotCxxx sta .gotCxxx - ldy .dataptr lda .cxtestdata,y iny sta SRC lda .cxtestdata,y iny sta SRC+1 lda .cxtestdata,y iny sta .want sty .dataptr ldy #0 lda (SRC),y cmp .want beq + lda #($ff-$8) ; mismatch: clear current bit and .gotCxxx sta .gotCxxx + dex bne - dec .loopctr bne -- rts .printCxxxBits tax +print !text "- C100-C2FF: " +printed txa and #.C_12 jsr .printCxxxBit +print !text "- C300-C3FF: " +printed txa and #.C_3 jsr .printCxxxBit +print !text "- C400-C7FF: " +printed txa and #.C_47 jsr .printCxxxBit +print !text "- C800-CFFE: " +printed txa and #.C_8f jsr .printCxxxBit rts .printCxxxBit bne + +print !text "?",$8D +printed rts + +print !text "ROM",$8D +printed rts ;;; Print out the sequence of instructions at PCL,PCH, until we hit a JSR. .printtest +print !text "AFTER SEQUENCE",$8D +printed jmp PRINTTEST ;;; Copy zero page to aux mem. Assumes zp pointing at main mem, and leaves it that way. zptoaux ldx #0 - sta RESET_ALTZP lda 0,x sta SET_ALTZP sta 0,x inx bne - sta RESET_ALTZP rts ;;; Copy zero page from aux mem. Assumes zp pointing at main mem, and leaves it that way. zpfromaux ldx #0 - sta SET_ALTZP lda 0,x sta RESET_ALTZP sta 0,x inx bne - rts ;;; These are the main auxmem tests. Their format is: ;;; ;;; lda TEST_NUMBER ;;; ; (do whatever you want in normal assembly) ;;; jsr .check ;;; !byte ROM_CHECK_WANT, ZP_WANT, MAIN_WANT, TEXT_WANT, HIRES_WANT, ZP_AUX_WANT, MAIN_AUX_WANT, TEXT_AUX_WANT, HIRES_AUX_WANT ;;; ;;; ;;; The tests work like so: ;;; ;;; 1) The harness code stores $1 into all the test addresses in ;;; normal memory, and $3 into all the test locations in aux ;;; memory. ;;; ;;; 2) The test-specific piece of custom assembly code does whatever ;;; it wants to do to softswitches. ;;; ;;; 3) The harness code increments each of the test addresses ;;; once. Some will be in normal memory, some in aux memory, ;;; depending on what the custom code did. ;;; ;;; 4) If the ROM_CHECK_WANT byte is not .C_skip, then the harness ;;; calculates which portions of Cxxx memory are reading as ROMs, ;;; and validates that using the routine .checkCxxx ;;; ;;; 5) The harness code runs through the test addresses, one region at ;;; a time, checking that the values are what the test expects. ;;; ;;; The regions and their test addresses (stored at .memorylocs), ;;; which correspond to the segments that can be independently ;;; pointed at main memory or aux memory, are: ;;; ;;; - zero page: $ff, $100 ;;; - main memory: $200, $3ff, $800, $1fff, $4000, $5fff, $bfff ;;; - text: $427, $7ff ;;; - hires: $2000, $3fff ;;; ;;; The harness checks that the zero page main memory test ;;; addresses hold ZP_WANT, and that the zero page main memory test ;;; addresses hold ZP_AUX_WANT. Similarly for the other three ;;; regions. .auxtests ;; Our four basic tests -------------------------------------- ;; Test 1: everything reset. lda #1 jsr .check !byte .C_skip, 2, 2, 2, 2, 3, 3, 3, 3 ;; Test 2: write to AUX but read from Main RAM, everything else normal. lda #2 sta SET_RAMWRT jsr .check !byte .C_skip, 2, 1, 1, 1, 3, 2, 2, 2 ;; Test 3: write to main but read AUX, everything else normal. lda #3 sta SET_RAMRD jsr .check !byte .C_skip, 2, 4, 4, 4, 3, 3, 3, 3 ;; Test 4: write to AUX, read from AUX, everything else normal. lda #4 sta SET_RAMRD sta SET_RAMWRT jsr .check !byte .C_skip, 2, 1, 1, 1, 3, 4, 4, 4 ;; Our four basic tests, but with 80STORE ON ----------------- ;; (400-7ff is pointing at main mem) ;; Test 5: everything reset. lda #5 sta SET_80STORE jsr .check !byte .C_skip, 2, 2, 2, 2, 3, 3, 3, 3 ;; Test 6: write to aux lda #6 sta SET_RAMWRT sta SET_80STORE jsr .check !byte .C_skip, 2, 1, 2, 1, 3, 2, 3, 2 ;; Test 7: read from aux lda #7 sta SET_RAMRD sta SET_80STORE jsr .check !byte .C_skip, 2, 4, 2, 4, 3, 3, 3, 3 ;; Test 8: read and write aux lda #8 sta SET_RAMRD sta SET_RAMWRT sta SET_80STORE jsr .check !byte .C_skip, 2, 1, 2, 1, 3, 4, 3, 4 ;; Our four basic tests, but with 80STORE and PAGE2 ON ------- ;; (400-7ff is pointing at aux mem) ;; Test 9: everything reset. lda #9 sta SET_80STORE sta SET_PAGE2 jsr .check !byte .C_skip, 2, 2, 1, 2, 3, 3, 4, 3 ;; Test A: write to aux lda #$a sta SET_RAMWRT sta SET_80STORE sta SET_PAGE2 jsr .check !byte .C_skip, 2, 1, 1, 1, 3, 2, 4, 2 ;; Test B: read from aux lda #$b sta SET_RAMRD sta SET_80STORE sta SET_PAGE2 jsr .check !byte .C_skip, 2, 4, 1, 4, 3, 3, 4, 3 ;; Test C: read and write aux lda #$c sta SET_RAMRD sta SET_RAMWRT sta SET_80STORE sta SET_PAGE2 jsr .check !byte .C_skip, 2, 1, 1, 1, 3, 4, 4, 4 ;; Our four basic tests, but with 80STORE and HIRES ON ------- ;; (400-7ff and 2000-3fff are pointing at main mem) ;; Test D: everything reset. lda #$d sta SET_80STORE sta SET_HIRES jsr .check !byte .C_skip, 2, 2, 2, 2, 3, 3, 3, 3 ;; Test E: write to aux lda #$e sta SET_RAMWRT sta SET_80STORE sta SET_HIRES jsr .check !byte .C_skip, 2, 1, 2, 2, 3, 2, 3, 3 ;; Test F: read from aux lda #$f sta SET_RAMRD sta SET_80STORE sta SET_HIRES jsr .check !byte .C_skip, 2, 4, 2, 2, 3, 3, 3, 3 ;; Test 10: read and write aux lda #$10 sta SET_RAMRD sta SET_RAMWRT sta SET_80STORE sta SET_HIRES jsr .check !byte .C_skip, 2, 1, 2, 2, 3, 4, 3, 3 ;; Our four basic tests, but with 80STORE, HIRES, PAGE2 ON --- ;; (400-7ff and 2000-3fff are pointing at aux mem) ;; Test 11: everything reset. lda #$11 sta SET_80STORE sta SET_HIRES sta SET_PAGE2 jsr .check !byte .C_skip, 2, 2, 1, 1, 3, 3, 4, 4 ;; Test 12: write to aux lda #$12 sta SET_RAMWRT sta SET_80STORE sta SET_HIRES sta SET_PAGE2 jsr .check !byte .C_skip, 2, 1, 1, 1, 3, 2, 4, 4 ;; Test 13: read from aux lda #$13 sta SET_RAMRD sta SET_80STORE sta SET_HIRES sta SET_PAGE2 jsr .check !byte .C_skip, 2, 4, 1, 1, 3, 3, 4, 4 ;; Test 14: read and write aux lda #$14 sta SET_RAMRD sta SET_RAMWRT sta SET_80STORE sta SET_HIRES sta SET_PAGE2 jsr .check !byte .C_skip, 2, 1, 1, 1, 3, 4, 4, 4 ;; Test 15: Cxxx test with everything reset. lda #$15 jsr .check !byte .C_3, 2, 2, 2, 2, 3, 3, 3, 3 ;; Test 16: Cxxx test with SLOTC3ROM set lda #$16 sta SET_SLOTC3ROM jsr .check !byte .C_0, 2, 2, 2, 2, 3, 3, 3, 3 ;; Test 17: Cxxx test with INTCXROM set. lda #$17 sta SET_INTCXROM jsr .check !byte .C_1348, 2, 2, 2, 2, 3, 3, 3, 3 ;; Test 18: Cxxx test with SLOTC3ROM and INTCXROM set lda #$18 sta SET_SLOTC3ROM sta SET_INTCXROM jsr .check !byte .C_1348, 2, 2, 2, 2, 3, 3, 3, 3 ;; Test 19: Cxxx test with "INTC8ROM" set lda #$19 lda $C300 jsr .check !byte .C_38, 2, 2, 2, 2, 3, 3, 3, 3 ;; Test 1A: Cxxx test showing inability to reset "INTC8ROM" with softswitches. lda #$1A lda $C300 sta SET_SLOTC3ROM jsr .check !byte .C_8f, 2, 2, 2, 2, 3, 3, 3, 3 ;; Test 1B: Cxxx test showing ability to reset "INTC8ROM" with CFFF reference. lda #$1B lda $C300 sta SET_SLOTC3ROM lda RESET_INTC8ROM jsr .check !byte .C_0, 2, 2, 2, 2, 3, 3, 3, 3 ;; Test 1C: Cxxx test showing inability to reset "INTC8ROM" with CFFF reference. lda #$1B sta SET_SLOTC3ROM sta SET_INTCXROM lda RESET_INTC8ROM jsr .check !byte .C_1348, 2, 2, 2, 2, 3, 3, 3, 3 ;; Test 1D: Cxxx test showing that "INTC8ROM" isn't set if SLOTC3ROM isn't reset. lda #$1D sta SET_INTCXROM sta SET_SLOTC3ROM lda $C300 sta RESET_INTCXROM jsr .check !byte .C_0, 2, 2, 2, 2, 3, 3, 3, 3 !byte 0 ; end of tests .memorylocs ;; zero page locations !word $ff, $100, 0 ;; main memory locations !word $200, $3ff, $800, $1fff, $4000, $5fff, $bfff, 0 ;; text locations !word $427, $7ff, 0 ;; hires locations !word $2000, $3fff, 0 ;; end .memorylen = * - .memorylocs - 2 !word 0 ;;; Bytes to check to see whether ranges of memory contain ROM or not. ;;; If I recall correctly, these were chosen to be bytes that remain ;;; the same in different ROM versions. .cxtestdata ;; C800-Cffe !byte $00, $c8, $4c ; CB00: 4C !byte $21, $ca, $8d ; CA21: 8D !byte $43, $cc, $f0 ; CC43: F0 !byte $b5, $ce, $7b ; CEB5: 7B ;; C100-C2ff !byte $4d, $c1, $a5 ; C14D: A5 !byte $6c, $c1, $2a ; C16C: 2A !byte $b5, $c2, $ad ; C2B5: AD !byte $ff, $c2, $00 ; C2FF: 00 ;; C400-C7ff !byte $36, $c4, $8d ; C436: 8D !byte $48, $c5, $18 ; C548: 18 !byte $80, $c6, $8b ; C680: 8B !byte $6e, $c7, $cb ; C76E: CB ;; C300-C3ff !byte $00, $c3, $2c ; C300: 2C !byte $0a, $c3, $0c ; C30A: 0C !byte $2b, $c3, $04 ; C32B: 04 !byte $e2, $c3, $ed ; C3E2: ED } ;auxmem