Created
February 23, 2026 07:06
-
-
Save damieng/f276c7f03a49c2b030200e94f386f2ab to your computer and use it in GitHub Desktop.
Full 64KB banked memory test for the Spectrum +3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| DEVICE ZXSPECTRUM128 | |
| ORG $4800 ; Bank 5 (display bank) | |
| ; --------------------------------------------------------- | |
| ; All-RAM mode 0 ($1FFD=01): Banks 0,1,2,3 at $0000/$4000/$8000/$C000. | |
| ; | |
| ; Strategy: | |
| ; • Bootstrap runs in Bank 5. Prints title/banks to display. | |
| ; ULA always reads Bank 5, so display is visible throughout the test. | |
| ; • Copies this code to Bank 1 (via $7FFD paging), then enables All-RAM. | |
| ; • Test runs in All-RAM. Every 256 addresses, briefly exits All-RAM, | |
| ; writes current address to Bank 5 display, then re-enters All-RAM. | |
| ; Registers (HL, DE) are preserved in the CPU across the bank switch; | |
| ; only stack must not straddle a switch (all PUSHes happen after exit, | |
| ; all POPs happen before re-entry). | |
| ; • Bank 0 ($0000-$3FFF): fully tested. | |
| ; • Bank 1 ($4000-$7FFF): skip first 6912 bytes ($4000-$5AFF, screen+code), | |
| ; test $5B00-$7FFF (above code and stack). | |
| ; • Bank 2 ($8000-$BFFF): fully tested. | |
| ; • Bank 3 ($C000-$FFFF): fully tested. | |
| ; • Each address: all 256 values written, read back and compared. | |
| ; • On PASS/FAIL: exit All-RAM (Bank 5 back at $4000), write result. | |
| ; --------------------------------------------------------- | |
| BOOTSTRAP: | |
| DI | |
| LD SP, $57FF ; Bank 5, below attributes; same SP used in All-RAM | |
| ; Copy ROM charset before All-RAM removes ROM access | |
| LD HL, $3D00 | |
| LD DE, FONT_BUF | |
| LD BC, 768 | |
| LDIR | |
| ; Clear first third of screen pixels ($4000-$47FF) | |
| LD HL, $4000 | |
| LD (HL), 0 | |
| LD DE, $4001 | |
| LD BC, $07FF | |
| LDIR | |
| ; Set all attributes: white ink on black paper | |
| LD HL, $5800 | |
| LD (HL), $07 | |
| LD DE, $5801 | |
| LD BC, $02FF | |
| LDIR | |
| ; Print to Bank 5 display — stays visible during All-RAM (ULA reads Bank 5) | |
| LD DE, $4000 | |
| LD HL, STR_TITLE | |
| CALL PRINT_STRING | |
| LD DE, $4020 | |
| LD HL, STR_BANKS | |
| CALL PRINT_STRING | |
| ; Page Bank 1 to $C000, copy our code there | |
| ; In All-RAM mode 0, Bank 1 sits at $4000-$7FFF. | |
| ; Our code at $4800 will be at $C800 while Bank 1 is paged to $C000. | |
| LD A, 1 | |
| LD BC, $7FFD | |
| OUT (C), A | |
| LD HL, $4800 | |
| LD DE, $C800 | |
| LD BC, CODE_SIZE | |
| LDIR | |
| ; Restore default paging ($7FFD=0) | |
| XOR A | |
| LD BC, $7FFD | |
| OUT (C), A | |
| ; Enable All-RAM mode 0 — execution continues from Bank 1 at $4800 | |
| LD A, %00000001 | |
| LD BC, $1FFD | |
| OUT (C), A | |
| ; --- Bank 0: $0000-$3FFF --- | |
| LD A, 1 ; blue border | |
| OUT ($FE), A | |
| LD HL, $0000 | |
| LD DE, $4000 ; 16384 bytes | |
| CALL RUN_TEST_BLOCK | |
| ; --- Bank 1: $5B00-$7FFF (skip $4000-$5AFF: screen+code+stack) --- | |
| LD A, 3 ; yellow border | |
| OUT ($FE), A | |
| LD HL, $5B00 | |
| LD DE, $2500 ; $8000 - $5B00 = 9472 bytes | |
| CALL RUN_TEST_BLOCK | |
| ; --- Bank 2: $8000-$BFFF --- | |
| LD A, 5 ; cyan border | |
| OUT ($FE), A | |
| LD HL, $8000 | |
| LD DE, $4000 | |
| CALL RUN_TEST_BLOCK | |
| ; --- Bank 3: $C000-$FFFF --- | |
| LD A, 6 ; magenta border | |
| OUT ($FE), A | |
| LD HL, $C000 | |
| LD DE, $4000 | |
| CALL RUN_TEST_BLOCK | |
| ; SUCCESS — exit All-RAM, Bank 5 back at $4000 (display writable again) | |
| XOR A | |
| LD BC, $1FFD | |
| OUT (C), A | |
| LD A, 4 ; green border | |
| OUT ($FE), A | |
| LD DE, $4080 | |
| LD HL, STR_PASS | |
| CALL PRINT_STRING | |
| ; ~2 second delay so user can see the result | |
| LD B, 8 | |
| DELAY_OUTER: | |
| LD HL, 0 | |
| DELAY_INNER: | |
| DEC HL | |
| LD A, H | |
| OR L | |
| JR NZ, DELAY_INNER | |
| DJNZ DELAY_OUTER | |
| ; Return to BASIC | |
| EI | |
| JP $0000 | |
| ; --------------------------------------------------------- | |
| ; BLOCK TESTER — HL = start address, DE = byte count | |
| ; For each address, writes every value 0-255 and reads back. | |
| ; Every 256 addresses (L=0), briefly exits All-RAM to update | |
| ; the Bank 5 display with the current address, then re-enters. | |
| ; Stack discipline: NO push/pop straddles a bank switch. | |
| ; All pushes after exit OUT, all pops before re-entry OUT. | |
| ; --------------------------------------------------------- | |
| RUN_TEST_BLOCK: | |
| BLOCK_LOOP: | |
| LD A, L | |
| OR A | |
| JR NZ, NO_DISP | |
| ; --- display update: exit All-RAM, write address, re-enter --- | |
| ; HL = current address, DE = remaining count — both live in registers, | |
| ; safe across the bank switch with no push needed yet. | |
| XOR A | |
| LD BC, $1FFD | |
| OUT (C), A ; exit All-RAM — Bank 5 now at $4000 | |
| ; NOW on Bank 5 stack — safe to push | |
| PUSH DE ; save remaining count | |
| PUSH HL ; save test address (DISP_HEX will clobber HL) | |
| CALL DISP_HEX ; show current address on line 4 (HL passed in) | |
| POP HL ; restore test address | |
| POP DE ; restore remaining count | |
| ; Re-enter All-RAM — no push/pop between here and the OUT | |
| LD A, %00000001 | |
| LD BC, $1FFD | |
| OUT (C), A ; Bank 1 back at $4000, same code, same address | |
| NO_DISP: | |
| ; Test every value 0-255 at address HL | |
| LD C, 0 | |
| VALUE_LOOP: | |
| LD (HL), C ; write value | |
| LD A, (HL) ; read back | |
| CP C ; compare | |
| JP NZ, FAIL ; HL = failing address, C = failing value | |
| INC C | |
| JR NZ, VALUE_LOOP ; loop until C wraps 255->0 (all 256 values done) | |
| INC HL | |
| DEC DE | |
| LD A, D | |
| OR E | |
| JR NZ, BLOCK_LOOP | |
| RET | |
| ; --------------------------------------------------------- | |
| ; DISP_HEX — show current address HL on line 4 | |
| ; Called from Bank 5 (after exit All-RAM), so stack is Bank 5. | |
| ; HL is clobbered; caller saves/restores it. | |
| ; --------------------------------------------------------- | |
| DISP_HEX: | |
| LD DE, $4080 ; line 4, col 0 | |
| CALL PRINT_ADDR ; prints HL as 4 hex digits; DE advances to col 4 | |
| LD HL, STR_TESTING | |
| JP PRINT_STRING ; tail-call; trailing spaces clear old content | |
| ; --------------------------------------------------------- | |
| ; PRINT_ADDR — print HL as 4 hex digits at DE | |
| ; --------------------------------------------------------- | |
| PRINT_ADDR: | |
| LD A, H | |
| PUSH HL | |
| CALL PRINT_BYTE | |
| POP HL | |
| LD A, L | |
| JP PRINT_BYTE ; tail-call | |
| ; --------------------------------------------------------- | |
| ; FAILURE HANDLER | |
| ; HL = failing address, preserved in registers across the OUT | |
| ; Exit All-RAM before displaying so Bank 5 display is writable | |
| ; --------------------------------------------------------- | |
| FAIL: | |
| LD A, 2 ; red border | |
| OUT ($FE), A | |
| XOR A ; HL unaffected by XOR A / LD BC / OUT | |
| LD BC, $1FFD | |
| OUT (C), A ; exit All-RAM — Bank 5 at $4000, HL still = fail addr | |
| LD DE, $4080 | |
| CALL PRINT_ADDR | |
| LD HL, STR_FAIL | |
| CALL PRINT_STRING | |
| HALT | |
| ; --------------------------------------------------------- | |
| ; PRINT_BYTE — print A as two hex digits at DE | |
| ; --------------------------------------------------------- | |
| PRINT_BYTE: | |
| PUSH AF | |
| RRCA : RRCA : RRCA : RRCA | |
| CALL PRINT_NIBBLE | |
| POP AF | |
| ; fall through | |
| PRINT_NIBBLE: | |
| AND $0F | |
| CP 10 | |
| JR C, NIBBLE_DIG | |
| ADD A, 'A' - 10 | |
| JR PRINT_CHAR | |
| NIBBLE_DIG: | |
| ADD A, '0' | |
| ; fall through | |
| PRINT_CHAR: | |
| SUB ' ' | |
| LD L, A | |
| LD H, 0 | |
| ADD HL, HL | |
| ADD HL, HL | |
| ADD HL, HL ; HL = (char - 32) * 8, 16-bit | |
| PUSH DE | |
| LD BC, FONT_BUF | |
| ADD HL, BC | |
| REPT 8 | |
| LD A, (HL) | |
| LD (DE), A | |
| INC HL | |
| INC D ; next scanline | |
| ENDR | |
| POP DE | |
| INC E ; next column | |
| LD D, $40 ; reset to top scanline | |
| RET | |
| PRINT_STRING: | |
| LD A, (HL) | |
| OR A | |
| RET Z | |
| PUSH HL | |
| CALL PRINT_CHAR | |
| POP HL | |
| INC HL | |
| JR PRINT_STRING | |
| ; --------------------------------------------------------- | |
| ; STRING DATA | |
| ; --------------------------------------------------------- | |
| STR_TITLE: DB "DamienG 64kb RAM test", 0 | |
| STR_BANKS: DB "BANKS 0/1/2/3", 0 | |
| STR_TESTING: DB " Testing ", 0 | |
| STR_PASS: DB "PASS ", 0 | |
| STR_FAIL: DB " FAIL ", 0 | |
| ; --------------------------------------------------------- | |
| ; FONT BUFFER — 96 chars (ASCII 32-127), 8 bytes each | |
| ; --------------------------------------------------------- | |
| ALIGN 8 | |
| FONT_BUF: | |
| DEFS 768, 0 | |
| CODE_END: | |
| CODE_SIZE EQU CODE_END - $4800 | |
| SAVETAP "64kbramtest.tap", CODE, "RAMtest", BOOTSTRAP, CODE_SIZE |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment