diff --git a/examples/asm/functional_test/6502_functional_test.bin b/examples/asm/functional_test/6502_functional_test.bin index 8a20227..24ce1ce 100644 Binary files a/examples/asm/functional_test/6502_functional_test.bin and b/examples/asm/functional_test/6502_functional_test.bin differ diff --git a/examples/asm/functional_test/6502_functional_test.ca65 b/examples/asm/functional_test/6502_functional_test.ca65 new file mode 100644 index 0000000..255f765 --- /dev/null +++ b/examples/asm/functional_test/6502_functional_test.ca65 @@ -0,0 +1,6125 @@ +; +; 6 5 0 2 F U N C T I O N A L T E S T +; +; Copyright (C) 2012-2020 Klaus Dormann +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, either version 3 of the License, or +; (at your option) any later version. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . + + +; This program is designed to test all opcodes of a 6502 emulator using all +; addressing modes with focus on propper setting of the processor status +; register bits. +; +; version 05-jan-2020 +; contact info at http://2m5.de or email K@2m5.de +; +; assembled with CA65, linked with LD65 (cc65.github.io): +; ca65 -l 6502_functional_test.lst 6502_functional_test.ca65 +; ld65 6502_functional_test.o -o 6502_functional_test.bin \ +; -m 6502_functional_test.map -C example.cfg +; example linker config (example.cfg): +; MEMORY { +; RAM: start = $0000, size=$8000, type = rw, fill = yes, \ +; fillval = $FF, file = %O; +; ROM: start = $8000, size=$7FFA, type = ro, fill = yes, \ +; fillval = $FF, file = %O; +; ROM_VECTORS: start = $FFFA, size=6, type = ro, fill = yes, \ +; fillval = $FF, file = %O; +; } +; SEGMENTS { +; ZEROPAGE: load=RAM, type=rw; +; DATA: load=RAM, type=rw, offset=$0200; +; CODE: load=RAM, type=rw, offset=$0400; +; VECTORS: load=ROM_VECTORS, type=ro; +; } +; +; No IO - should be run from a monitor with access to registers. +; To run load intel hex image with a load command, than alter PC to 400 hex +; (code_segment) and enter a go command. +; Loop on program counter determines error or successful completion of test. +; Check listing for relevant traps (jump/branch *). +; Please note that in early tests some instructions will have to be used before +; they are actually tested! +; +; RESET, NMI or IRQ should not occur and will be trapped if vectors are enabled. +; Tests documented behavior of the original NMOS 6502 only! No unofficial +; opcodes. Additional opcodes of newer versions of the CPU (65C02, 65816) will +; not be tested. Decimal ops will only be tested with valid BCD operands and +; N V Z flags will be ignored. +; +; Debugging hints: +; Most of the code is written sequentially. if you hit a trap, check the +; immediately preceeding code for the instruction to be tested. Results are +; tested first, flags are checked second by pushing them onto the stack and +; pulling them to the accumulator after the result was checked. The "real" +; flags are no longer valid for the tested instruction at this time! +; If the tested instruction was indexed, the relevant index (X or Y) must +; also be checked. Opposed to the flags, X and Y registers are still valid. +; +; versions: +; 28-jul-2012 1st version distributed for testing +; 29-jul-2012 fixed references to location 0, now #0 +; added license - GPLv3 +; 30-jul-2012 added configuration options +; 01-aug-2012 added trap macro to allow user to change error handling +; 01-dec-2012 fixed trap in branch field must be a branch +; 02-mar-2013 fixed PLA flags not tested +; 19-jul-2013 allowed ROM vectors to be loaded when load_data_direct = 0 +; added test sequence check to detect if tests jump their fence +; 23-jul-2013 added RAM integrity check option +; 16-aug-2013 added error report to standard output option +; 13-dec-2014 added binary/decimal opcode table switch test +; 14-dec-2014 improved relative address test +; 23-aug-2015 added option to disable self modifying tests +; 24-aug-2015 all self modifying immediate opcodes now execute in data RAM +; added small branch offset pretest +; 21-oct-2015 added option to disable decimal mode ADC & SBC tests +; 04-dec-2017 fixed BRK only tested with interrupts enabled +; added option to skip the remainder of a failing test +; in report.i65 +; 05-jan-2020 fixed shifts not testing zero result and flag when last 1-bit +; is shifted out + +; C O N F I G U R A T I O N + +;ROM_vectors writable (0=no, 1=yes) +;if ROM vectors can not be used interrupts will not be trapped +;as a consequence BRK can not be tested but will be emulated to test RTI +ROM_vectors = 0 + +;load_data_direct (0=move from code segment, 1=load directly) +;loading directly is preferred but may not be supported by your platform +;0 produces only consecutive object code, 1 is not suitable for a binary image +load_data_direct = 1 + +;I_flag behavior (0=force enabled, 1=force disabled, 2=prohibit change, 3=allow +;change) 2 requires extra code and is not recommended. SEI & CLI can only be +;tested if you allow changing the interrupt status (I_flag = 3) +I_flag = 1 + +;configure memory - try to stay away from memory used by the system +;zero_page memory start address, $52 (82) consecutive Bytes required +; add 2 if I_flag = 2 +zero_page = $a + +;data_segment memory start address, $7B (123) consecutive Bytes required +; check that this matches the linker configuration file +data_segment = $200 + .if (data_segment & $ff) <> 0 + .error "low byte of data_segment MUST be $00 !!" + .endif + +;code_segment memory start address, 13.1kB of consecutive space required +; add 2.5 kB if I_flag = 2 +; check that this matches the linker configuration file +code_segment = $400 + +;self modifying code may be disabled to allow running in ROM +;0=part of the code is self modifying and must reside in RAM +;1=tests disabled: branch range +disable_selfmod = 1 + +;report errors through I/O channel (0=use standard self trap loops, 1=include +;report.i65 as I/O channel, add 3.5 kB) +report = 0 + +;RAM integrity test option. Checks for undesired RAM writes. +;set lowest non RAM or RAM mirror address page (-1=disable, 0=64k, $40=16k) +;leave disabled if a monitor, OS or background interrupt is allowed to alter RAM +ram_top = -1 + +;disable test decimal mode ADC & SBC, 0=enable, 1=disable, +;2=disable including decimal flag in processor status +disable_decimal = 0 + +;macros for error & success traps to allow user modification +;example: +; .macro trap +; jsr my_error_handler +; .endmacro +; .macro trap_eq +; bne :+ +; trap ;failed equal (zero) +;: +; .endmacro +; +; my_error_handler should pop the calling address from the stack and report it. +; putting larger portions of code (more than 3 bytes) inside the trap macro +; may lead to branch range problems for some tests. + .if report = 0 + .macro trap + jmp * ;failed anyway + .endmacro + .macro trap_eq + beq * ;failed equal (zero) + .endmacro + .macro trap_ne + bne * ;failed not equal (non zero) + .endmacro + .macro trap_cs + bcs * ;failed carry set + .endmacro + .macro trap_cc + bcc * ;failed carry clear + .endmacro + .macro trap_mi + bmi * ;failed minus (bit 7 set) + .endmacro + .macro trap_pl + bpl * ;failed plus (bit 7 clear) + .endmacro + .macro trap_vs + bvs * ;failed overflow set + .endmacro + .macro trap_vc + bvc * ;failed overflow clear + .endmacro +; please observe that during the test the stack gets invalidated +; therefore a RTS inside the success macro is not possible + .macro success + jmp * ;test passed, no errors + .endmacro + .endif + .if report = 1 + .macro trap + jsr report_error + .endmacro + .macro trap_eq + bne :+ + trap ;failed equal (zero) +: + .endmacro + .macro trap_ne + beq :+ + trap ;failed not equal (non zero) +: + .endmacro + .macro trap_cs + bcc :+ + trap ;failed carry set +: + .endmacro + .macro trap_cc + bcs :+ + trap ;failed carry clear +: + .endmacro + .macro trap_mi + bpl :+ + trap ;failed minus (bit 7 set) +: + .endmacro + .macro trap_pl + bmi :+ + trap ;failed plus (bit 7 clear) +: + .endmacro + .macro trap_vs + bvc :+ + trap ;failed overflow set +: + .endmacro + .macro trap_vc + bvs :+ + trap ;failed overflow clear +: + .endmacro +; please observe that during the test the stack gets invalidated +; therefore a RTS inside the success macro is not possible + .macro success + jsr report_success + .endmacro + .endif + + .define equ = + +carry equ %00000001 ;flag bits in status +zero equ %00000010 +intdis equ %00000100 +decmode equ %00001000 +break equ %00010000 +reserv equ %00100000 +overfl equ %01000000 +minus equ %10000000 + +fc equ carry +fz equ zero +fzc equ carry+zero +fv equ overfl +fvz equ overfl+zero +fn equ minus +fnc equ minus+carry +fnz equ minus+zero +fnzc equ minus+zero+carry +fnv equ minus+overfl + +fao equ break+reserv ;bits always on after PHP, BRK +fai equ fao+intdis ;+ forced interrupt disable +faod equ fao+decmode ;+ ignore decimal +faid equ fai+decmode ;+ ignore decimal +m8 equ $ff ;8 bit mask +m8i equ $ff&~intdis ;8 bit mask - interrupt disable + +;macros to allow masking of status bits. +;masking test of decimal bit +;masking of interrupt enable/disable on load and compare +;masking of always on bits after PHP or BRK (unused & break) on compare + .if disable_decimal < 2 + .if I_flag = 0 + .macro load_flag p1 + lda #p1&m8i ;force enable interrupts (mask I) + .endmacro + .macro cmp_flag p1 + cmp #(p1|fao)&m8i ;I_flag is always enabled + always on bits + .endmacro + .macro eor_flag p1 + eor #(p1&m8i|fao) ;mask I, invert expected flags + always on bits + .endmacro + .endif + .if I_flag = 1 + .macro load_flag p1 + lda #p1|intdis ;force disable interrupts + .endmacro + .macro cmp_flag p1 + cmp #(p1|fai)&m8 ;I_flag is always disabled + always on bits + .endmacro + .macro eor_flag p1 + eor #(p1|fai) ;invert expected flags + always on bits + I + .endmacro + .endif + .if I_flag = 2 + .macro load_flag p1 + lda #p1 + ora flag_I_on ;restore I-flag + and flag_I_off + .endmacro + .macro cmp_flag p1 + eor flag_I_on ;I_flag is never changed + cmp #(p1|fao)&m8i ;expected flags + always on bits, mask I + .endmacro + .macro eor_flag p1 + eor flag_I_on ;I_flag is never changed + eor #(p1&m8i|fao) ;mask I, invert expected flags + always on bits + .endmacro + .endif + .if I_flag = 3 + .macro load_flag p1 + lda #p1 ;allow test to change I-flag (no mask) + .endmacro + .macro cmp_flag p1 + cmp #(p1|fao)&m8 ;expected flags + always on bits + .endmacro + .macro eor_flag p1 + eor #p1|fao ;invert expected flags + always on bits + .endmacro + .endif + .else + .if I_flag = 0 + .macro load_flag p1 + lda #p1&m8i ;force enable interrupts (mask I) + .endmacro + .macro cmp_flag p1 + ora #decmode ;ignore decimal mode bit + cmp #(p1|faod)&m8i ;I_flag is always enabled + always on bits + .endmacro + .macro eor_flag p1 + ora #decmode ;ignore decimal mode bit + eor #(p1&m8i|faod) ;mask I, invert expected flags + always on bits + .endmacro + .endif + .if I_flag = 1 + .macro load_flag p1 + lda #p1|intdis ;force disable interrupts + .endmacro + .macro cmp_flag p1 + ora #decmode ;ignore decimal mode bit + cmp #(p1|faid)&m8 ;I_flag is always disabled + always on bits + .endmacro + .macro eor_flag p1 + ora #decmode ;ignore decimal mode bit + eor #(p1|faid) ;invert expected flags + always on bits + I + .endmacro + .endif + .if I_flag = 2 + .macro load_flag p1 + lda #p1 + ora flag_I_on ;restore I-flag + and flag_I_off + .endmacro + .macro cmp_flag p1 + eor flag_I_on ;I_flag is never changed + ora #decmode ;ignore decimal mode bit + cmp #(p1|faod)&m8i ;expected flags + always on bits, mask I + .endmacro + .macro eor_flag p1 + eor flag_I_on ;I_flag is never changed + ora #decmode ;ignore decimal mode bit + eor #(p1&m8i|faod) ;mask I, invert expected flags + always on bits + .endmacro + .endif + .if I_flag = 3 + .macro load_flag p1 + lda #p1 ;allow test to change I-flag (no mask) + .endmacro + .macro cmp_flag p1 + ora #decmode ;ignore decimal mode bit + cmp #(p1|faod)&m8 ;expected flags + always on bits + .endmacro + .macro eor_flag p1 + ora #decmode ;ignore decimal mode bit + eor #p1|faod ;invert expected flags + always on bits + .endmacro + .endif + .endif + +;macros to set (register|memory|zeropage) & status + .macro set_stat p1 ;setting flags in the processor status register + load_flag p1 + pha ;use stack to load status + plp + .endmacro + + .macro set_a p1,p2 ;precharging accu & status + load_flag p2 + pha ;use stack to load status + lda #p1 ;precharge accu + plp + .endmacro + + .macro set_x p1,p2 ;precharging index & status + load_flag p2 + pha ;use stack to load status + ldx #p1 ;precharge index x + plp + .endmacro + + .macro set_y p1,p2 ;precharging index & status + load_flag p2 + pha ;use stack to load status + ldy #p1 ;precharge index y + plp + .endmacro + + .macro set_ax p1,p2 ;precharging indexed accu & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,x ;precharge accu + plp + .endmacro + + .macro set_ay p1,p2 ;precharging indexed accu & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,y ;precharge accu + plp + .endmacro + + .macro set_z p1,p2 ;precharging indexed zp & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,x ;load to zeropage + sta zpt + plp + .endmacro + + .macro set_zx p1,p2 ;precharging zp,x & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,x ;load to indexed zeropage + sta zpt,x + plp + .endmacro + + .macro set_abs p1,p2 ;precharging indexed memory & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,x ;load to memory + sta abst + plp + .endmacro + + .macro set_absx p1,p2 ;precharging abs,x & immediate status + load_flag p2 + pha ;use stack to load status + lda p1,x ;load to indexed memory + sta abst,x + plp + .endmacro + +;macros to test (register|memory|zeropage) & status & (mask) + .macro tst_stat p1 ;testing flags in the processor status register + php ;save status + pla ;use stack to retrieve status + pha + cmp_flag p1 + trap_ne + plp ;restore status + .endmacro + + .macro tst_a p1,p2 ;testing result in accu & flags + php ;save flags + cmp #p1 ;test result + trap_ne + pla ;load status + pha + cmp_flag p2 + trap_ne + plp ;restore status + .endmacro + + .macro tst_x p1,p2 ;testing result in x index & flags + php ;save flags + cpx #p1 ;test result + trap_ne + pla ;load status + pha + cmp_flag p2 + trap_ne + plp ;restore status + .endmacro + + .macro tst_y p1,p2 ;testing result in y index & flags + php ;save flags + cpy #p1 ;test result + trap_ne + pla ;load status + pha + cmp_flag p2 + trap_ne + plp ;restore status + .endmacro + + .macro tst_ax p1,p2,p3 ;indexed testing result in accu & flags + php ;save flags + cmp p1,x ;test result + trap_ne + pla ;load status + eor_flag p3 + cmp p2,x ;test flags + trap_ne ; + .endmacro + + .macro tst_ay p1,p2,p3 ;indexed testing result in accu & flags + php ;save flags + cmp p1,y ;test result + trap_ne ; + pla ;load status + eor_flag p3 + cmp p2,y ;test flags + trap_ne + .endmacro + + .macro tst_z p1,p2,p3 ;indexed testing result in zp & flags + php ;save flags + lda zpt + cmp p1,x ;test result + trap_ne + pla ;load status + eor_flag p3 + cmp p2,x ;test flags + trap_ne + .endmacro + + .macro tst_zx p1,p2,p3 ;testing result in zp,x & flags + php ;save flags + lda zpt,x + cmp p1,x ;test result + trap_ne + pla ;load status + eor_flag p3 + cmp p2,x ;test flags + trap_ne + .endmacro + + .macro tst_abs p1,p2,p3 ;indexed testing result in memory & flags + php ;save flags + lda abst + cmp p1,x ;test result + trap_ne + pla ;load status + eor_flag p3 + cmp p2,x ;test flags + trap_ne + .endmacro + + .macro tst_absx p1,p2,p3 ;testing result in abs,x & flags + php ;save flags + lda abst,x + cmp p1,x ;test result + trap_ne + pla ;load status + eor_flag p3 + cmp p2,x ;test flags + trap_ne + .endmacro + +; RAM integrity test +; verifies that none of the previous tests has altered RAM outside of the +; designated write areas. +; uses zpt word as indirect pointer, zpt+2 word as checksum + .if ram_top > -1 +check_ram macro + cld + lda #0 + sta zpt ;set low byte of indirect pointer + sta zpt+3 ;checksum high byte + .if disable_selfmod = 0 + sta range_adr ;reset self modifying code + .endif + clc + ldx #zp_bss-zero_page ;zeropage - write test area +ccs3: adc zero_page,x + bcc ccs2 + inc zpt+3 ;carry to high byte + clc +ccs2: inx + bne ccs3 + ldx #hi(abs1) ;set high byte of indirect pointer + stx zpt+1 + ldy #lo(abs1) ;data after write & execute test area +ccs5: adc (zpt),y + bcc ccs4 + inc zpt+3 ;carry to high byte + clc +ccs4: iny + bne ccs5 + inx ;advance RAM high address + stx zpt+1 + cpx #ram_top + bne ccs5 + sta zpt+2 ;checksum low is + cmp ram_chksm ;checksum low expected + trap_ne ;checksum mismatch + lda zpt+3 ;checksum high is + cmp ram_chksm+1 ;checksum high expected + trap_ne ;checksum mismatch + .endmacro + .else + .macro check_ram + ;RAM check disabled - RAM size not set + .endmacro + .endif + + .macro next_test ;make sure, tests don't jump the fence + lda test_case ;previous test + cmp #test_num + trap_ne ;test is out of sequence +test_num .set test_num + 1 + lda #test_num ;*** next tests' number + sta test_case + ;check_ram ;uncomment to find altered RAM after each test + .endmacro + + .ZEROPAGE + .res zero_page, 0 + .org zero_page + +;break test interrupt save +irq_a: .res 1,0 ;a register +irq_x: .res 1,0 ;x register + .if I_flag = 2 +;masking for I bit in status +flag_I_on: .res 1,0 ;or mask to load flags +flag_I_off: .res 1,0 ;and mask to load flags + .endif +zpt: ;6 bytes store/modify test area +;add/subtract operand generation and result/flag prediction +adfc: .res 1,0 ;carry flag before op +ad1: .res 1,0 ;operand 1 - accumulator +ad2: .res 1,0 ;operand 2 - memory / immediate +adrl: .res 1,0 ;expected result bits 0-7 +adrh: .res 1,0 ;expected result bit 8 (carry) +adrf: .res 1,0 ;expected flags NV0000ZC (only binary mode) +sb2: .res 1,0 ;operand 2 complemented for subtract +zp_bss: +zps: .byte $80,1 ;additional shift pattern to test zero result & flag +zp1: .byte $c3,$82,$41,0 ;test patterns for LDx BIT ROL ROR ASL LSR +zp7f: .byte $7f ;test pattern for compare +;logical zeropage operands +zpOR: .byte 0,$1f,$71,$80 ;test pattern for OR +zpAN: .byte $0f,$ff,$7f,$80 ;test pattern for AND +zpEO: .byte $ff,$0f,$8f,$8f ;test pattern for EOR +;indirect addressing pointers +ind1: .word abs1 ;indirect pointer to pattern in absolute memory + .word abs1+1 + .word abs1+2 + .word abs1+3 + .word abs7f +inw1: .word abs1-$f8 ;indirect pointer for wrap-test pattern +indt: .word abst ;indirect pointer to store area in absolute memory + .word abst+1 + .word abst+2 + .word abst+3 +inwt: .word abst-$f8 ;indirect pointer for wrap-test store +indAN: .word absAN ;indirect pointer to AND pattern in absolute memory + .word absAN+1 + .word absAN+2 + .word absAN+3 +indEO: .word absEO ;indirect pointer to EOR pattern in absolute memory + .word absEO+1 + .word absEO+2 + .word absEO+3 +indOR: .word absOR ;indirect pointer to OR pattern in absolute memory + .word absOR+1 + .word absOR+2 + .word absOR+3 +;add/subtract indirect pointers +adi2: .word ada2 ;indirect pointer to operand 2 in absolute memory +sbi2: .word sba2 ;indirect pointer to complemented operand 2 (SBC) +adiy2: .word ada2-$ff ;with offset for indirect indexed +sbiy2: .word sba2-$ff +zp_bss_end: + + .DATA + .org data_segment + +test_case: .res 1,0 ;current test number +ram_chksm: .res 2,0 ;checksum for RAM integrity test +;add/subtract operand copy - abs tests write area +abst: ;6 bytes store/modify test area +ada2: .res 1,0 ;operand 2 +sba2: .res 1,0 ;operand 2 complemented for subtract + .res 4,0 ;fill remaining bytes +data_bss: + .if load_data_direct = 1 +ex_andi:and #0 ;execute immediate opcodes + rts +ex_eori:eor #0 ;execute immediate opcodes + rts +ex_orai:ora #0 ;execute immediate opcodes + rts +ex_adci:adc #0 ;execute immediate opcodes + rts +ex_sbci:sbc #0 ;execute immediate opcodes + rts + .else +ex_andi:.res 3 +ex_eori:.res 3 +ex_orai:.res 3 +ex_adci:.res 3 +ex_sbci:.res 3 + .endif +;zps .byte $80,1 ;additional shift patterns test zero result & flag +abs1: .byte $c3,$82,$41,0 ;test patterns for LDx BIT ROL ROR ASL LSR +abs7f: .byte $7f ;test pattern for compare +;loads +fLDx: .byte fn,fn,0,fz ;expected flags for load +;shifts +rASL: ;expected result ASL & ROL -carry +rROL: .byte 0,2,$86,$04,$82,0 +rROLc: .byte 1,3,$87,$05,$83,1 ;expected result ROL +carry +rLSR: ;expected result LSR & ROR -carry +rROR: .byte $40,0,$61,$41,$20,0 +rRORc: .byte $c0,$80,$e1,$c1,$a0,$80 ;expected result ROR +carry +fASL: ;expected flags for shifts +fROL: .byte fzc,0,fnc,fc,fn,fz ;no carry in +fROLc: .byte fc,0,fnc,fc,fn,0 ;carry in +fLSR: +fROR: .byte 0,fzc,fc,0,fc,fz ;no carry in +fRORc: .byte fn,fnc,fnc,fn,fnc,fn ;carry in +;increments (decrements) +rINC: .byte $7f,$80,$ff,0,1 ;expected result for INC/DEC +fINC: .byte 0,fn,fn,fz,0 ;expected flags for INC/DEC +;logical memory operand +absOR: .byte 0,$1f,$71,$80 ;test pattern for OR +absAN: .byte $0f,$ff,$7f,$80 ;test pattern for AND +absEO: .byte $ff,$0f,$8f,$8f ;test pattern for EOR +;logical accu operand +absORa: .byte 0,$f1,$1f,0 ;test pattern for OR +absANa: .byte $f0,$ff,$ff,$ff ;test pattern for AND +absEOa: .byte $ff,$f0,$f0,$0f ;test pattern for EOR +;logical results +absrlo: .byte 0,$ff,$7f,$80 +absflo: .byte fz,fn,0,fn +data_bss_end: + + + .CODE + .org code_segment + .P02 ; disable 65SC02, 65C02 and 65816 instructions +start: cld + ldx #$ff + txs + lda #0 ;*** test 0 = initialize + sta test_case +test_num .set 0 + +;stop interrupts before initializing BSS + .if I_flag = 1 + sei + .endif + +;initialize I/O for report channel + .if report = 1 + jsr report_init + .endif + +;pretest small branch offset + ldx #5 + jmp psb_test +psb_bwok: + ldy #5 + bne psb_forw + trap ;branch should be taken + dey ;forward landing zone + dey + dey + dey + dey +psb_forw: + dey + dey + dey + dey + dey + beq psb_fwok + trap ;forward offset + + dex ;backward landing zone + dex + dex + dex + dex +psb_back: + dex + dex + dex + dex + dex + beq psb_bwok + trap ;backward offset +psb_test: + bne psb_back + trap ;branch should be taken +psb_fwok: + +;initialize BSS segment + .if load_data_direct <> 1 + ldx #zp_end-zp_init-1 +ld_zp: lda zp_init,x + sta zp_bss,x + dex + bpl ld_zp + ldx #data_end-data_init-1 +ld_data:lda data_init,x + sta data_bss,x + dex + bpl ld_data + .if ROM_vectors = 1 + ldx #5 +ld_vect:lda vec_init,x + sta vec_bss,x + dex + bpl ld_vect + .endif + .endif + +;retain status of interrupt flag + .if I_flag = 2 + php + pla + and #4 ;isolate flag + sta flag_I_on ;or mask + eor #lo(~4) ;reverse + sta flag_I_off ;and mask + .endif + +;generate checksum for RAM integrity test + .if ram_top > -1 + lda #0 + sta zpt ;set low byte of indirect pointer + sta ram_chksm+1 ;checksum high byte + .if disable_selfmod = 0 + sta range_adr ;reset self modifying code + .endif + clc + ldx #zp_bss-zero_page ;zeropage - write test area +gcs3: adc zero_page,x + bcc gcs2 + inc ram_chksm+1 ;carry to high byte + clc +gcs2: inx + bne gcs3 + ldx #hi(abs1) ;set high byte of indirect pointer + stx zpt+1 + ldy #lo(abs1) ;data after write & execute test area +gcs5: adc (zpt),y + bcc gcs4 + inc ram_chksm+1 ;carry to high byte + clc +gcs4: iny + bne gcs5 + inx ;advance RAM high address + stx zpt+1 + cpx #ram_top + bne gcs5 + sta ram_chksm ;checksum complete + .endif + next_test + + .if disable_selfmod = 0 +;testing relative addressing with BEQ + ldy #$fe ;testing maximum range, not -1/-2 (invalid/self adr) +range_loop: + dey ;next relative address + tya + tax ;precharge count to end of loop + bpl range_fw ;calculate relative address + clc ;avoid branch self or to relative address of branch + adc #2 + nop ;offset landing zone - tolerate +/-5 offset to branch + nop + nop + nop + nop +range_fw: + nop + nop + nop + nop + nop + eor #$7f ;complement except sign + sta range_adr ;load into test target + lda #0 ;should set zero flag in status register + jmp range_op + + dex ; offset landing zone - backward branch too far + dex + dex + dex + dex + ;relative address target field with branch under test in the middle + dex ;-128 - max backward + dex + dex + dex + dex + dex + dex + dex + dex ;-120 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-110 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-100 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-90 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-80 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-70 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-60 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-50 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-40 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-30 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-20 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;-10 + dex + dex + dex + dex + dex + dex + dex ;-3 +range_op: ;test target with zero flag=0, z=1 if previous dex +range_adr = *+1 ;modifiable relative address + beq *+64 ;+64 if called without modification + dex ;+0 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+10 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+20 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+30 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+40 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+50 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+60 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+70 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+80 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+90 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+100 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+110 + dex + dex + dex + dex + dex + dex + dex + dex + dex + dex ;+120 + dex + dex + dex + dex + dex + dex + nop ;offset landing zone - forward branch too far + nop + nop + nop + nop + beq range_ok ;+127 - max forward + trap ; bad range + nop ;offset landing zone - tolerate +/-5 offset to branch + nop + nop + nop + nop +range_ok: + nop + nop + nop + nop + nop + cpy #0 + beq range_end + jmp range_loop +range_end: ;range test successful + .endif + next_test + +;partial test BNE & CMP, CPX, CPY immediate + cpy #1 ;testing BNE true + bne test_bne + trap +test_bne: + lda #0 + cmp #0 ;test compare immediate + trap_ne + trap_cc + trap_mi + cmp #1 + trap_eq + trap_cs + trap_pl + tax + cpx #0 ;test compare x immediate + trap_ne + trap_cc + trap_mi + cpx #1 + trap_eq + trap_cs + trap_pl + tay + cpy #0 ;test compare y immediate + trap_ne + trap_cc + trap_mi + cpy #1 + trap_eq + trap_cs + trap_pl + next_test +;testing stack operations PHA PHP PLA PLP + + ldx #$ff ;initialize stack + txs + lda #$55 + pha + lda #$aa + pha + cmp $1fe ;on stack ? + trap_ne + tsx + txa ;overwrite accu + cmp #$fd ;sp decremented? + trap_ne + pla + cmp #$aa ;successful retreived from stack? + trap_ne + pla + cmp #$55 + trap_ne + cmp $1ff ;remains on stack? + trap_ne + tsx + cpx #$ff ;sp incremented? + trap_ne + next_test + +;testing branch decisions BPL BMI BVC BVS BCC BCS BNE BEQ + set_stat $ff ;all on + bpl nbr1 ;branches should not be taken + bvc nbr2 + bcc nbr3 + bne nbr4 + bmi br1 ;branches should be taken + trap +br1: bvs br2 + trap +br2: bcs br3 + trap +br3: beq br4 + trap +nbr1: + trap ;previous bpl taken +nbr2: + trap ;previous bvc taken +nbr3: + trap ;previous bcc taken +nbr4: + trap ;previous bne taken +br4: php + tsx + cpx #$fe ;sp after php? + trap_ne + pla + cmp_flag $ff ;returned all flags on? + trap_ne + tsx + cpx #$ff ;sp after php? + trap_ne + set_stat 0 ;all off + bmi nbr11 ;branches should not be taken + bvs nbr12 + bcs nbr13 + beq nbr14 + bpl br11 ;branches should be taken + trap +br11: bvc br12 + trap +br12: bcc br13 + trap +br13: bne br14 + trap +nbr11: + trap ;previous bmi taken +nbr12: + trap ;previous bvs taken +nbr13: + trap ;previous bcs taken +nbr14: + trap ;previous beq taken +br14: php + pla + cmp_flag 0 ;flags off except break (pushed by sw) + reserved? + trap_ne + ;crosscheck flags + set_stat zero + bne brzs1 + beq brzs2 +brzs1: + trap ;branch zero/non zero +brzs2: bcs brzs3 + bcc brzs4 +brzs3: + trap ;branch carry/no carry +brzs4: bmi brzs5 + bpl brzs6 +brzs5: + trap ;branch minus/plus +brzs6: bvs brzs7 + bvc brzs8 +brzs7: + trap ;branch overflow/no overflow +brzs8: + set_stat carry + beq brcs1 + bne brcs2 +brcs1: + trap ;branch zero/non zero +brcs2: bcc brcs3 + bcs brcs4 +brcs3: + trap ;branch carry/no carry +brcs4: bmi brcs5 + bpl brcs6 +brcs5: + trap ;branch minus/plus +brcs6: bvs brcs7 + bvc brcs8 +brcs7: + trap ;branch overflow/no overflow + +brcs8: + set_stat minus + beq brmi1 + bne brmi2 +brmi1: + trap ;branch zero/non zero +brmi2: bcs brmi3 + bcc brmi4 +brmi3: + trap ;branch carry/no carry +brmi4: bpl brmi5 + bmi brmi6 +brmi5: + trap ;branch minus/plus +brmi6: bvs brmi7 + bvc brmi8 +brmi7: + trap ;branch overflow/no overflow +brmi8: + set_stat overfl + beq brvs1 + bne brvs2 +brvs1: + trap ;branch zero/non zero +brvs2: bcs brvs3 + bcc brvs4 +brvs3: + trap ;branch carry/no carry +brvs4: bmi brvs5 + bpl brvs6 +brvs5: + trap ;branch minus/plus +brvs6: bvc brvs7 + bvs brvs8 +brvs7: + trap ;branch overflow/no overflow +brvs8: + set_stat $ff-zero + beq brzc1 + bne brzc2 +brzc1: + trap ;branch zero/non zero +brzc2: bcc brzc3 + bcs brzc4 +brzc3: + trap ;branch carry/no carry +brzc4: bpl brzc5 + bmi brzc6 +brzc5: + trap ;branch minus/plus +brzc6: bvc brzc7 + bvs brzc8 +brzc7: + trap ;branch overflow/no overflow +brzc8: + set_stat $ff-carry + bne brcc1 + beq brcc2 +brcc1: + trap ;branch zero/non zero +brcc2: bcs brcc3 + bcc brcc4 +brcc3: + trap ;branch carry/no carry +brcc4: bpl brcc5 + bmi brcc6 +brcc5: + trap ;branch minus/plus +brcc6: bvc brcc7 + bvs brcc8 +brcc7: + trap ;branch overflow/no overflow +brcc8: + set_stat $ff-minus + bne brpl1 + beq brpl2 +brpl1: + trap ;branch zero/non zero +brpl2: bcc brpl3 + bcs brpl4 +brpl3: + trap ;branch carry/no carry +brpl4: bmi brpl5 + bpl brpl6 +brpl5: + trap ;branch minus/plus +brpl6: bvc brpl7 + bvs brpl8 +brpl7: + trap ;branch overflow/no overflow +brpl8: + set_stat $ff-overfl + bne brvc1 + beq brvc2 +brvc1: + trap ;branch zero/non zero +brvc2: bcc brvc3 + bcs brvc4 +brvc3: + trap ;branch carry/no carry +brvc4: bpl brvc5 + bmi brvc6 +brvc5: + trap ;branch minus/plus +brvc6: bvs brvc7 + bvc brvc8 +brvc7: + trap ;branch overflow/no overflow +brvc8: + next_test + +; test PHA does not alter flags or accumulator but PLA does + ldx #$55 ;x & y protected + ldy #$aa + set_a 1,$ff ;push + pha + tst_a 1,$ff + set_a 0,0 + pha + tst_a 0,0 + set_a $ff,$ff + pha + tst_a $ff,$ff + set_a 1,0 + pha + tst_a 1,0 + set_a 0,$ff + pha + tst_a 0,$ff + set_a $ff,0 + pha + tst_a $ff,0 + set_a 0,$ff ;pull + pla + tst_a $ff,$ff-zero + set_a $ff,0 + pla + tst_a 0,zero + set_a $fe,$ff + pla + tst_a 1,$ff-zero-minus + set_a 0,0 + pla + tst_a $ff,minus + set_a $ff,$ff + pla + tst_a 0,$ff-minus + set_a $fe,0 + pla + tst_a 1,0 + cpx #$55 ;x & y unchanged? + trap_ne + cpy #$aa + trap_ne + next_test + +; partial pretest EOR # + set_a $3c,0 + eor #$c3 + tst_a $ff,fn + set_a $c3,0 + eor #$c3 + tst_a 0,fz + next_test + +; PC modifying instructions except branches (NOP, JMP, JSR, RTS, BRK, RTI) +; testing NOP + ldx #$24 + ldy #$42 + set_a $18,0 + nop + tst_a $18,0 + cpx #$24 + trap_ne + cpy #$42 + trap_ne + ldx #$db + ldy #$bd + set_a $e7,$ff + nop + tst_a $e7,$ff + cpx #$db + trap_ne + cpy #$bd + trap_ne + next_test + +; jump absolute + set_stat $0 + lda #'F' + ldx #'A' + ldy #'R' ;N=0, V=0, Z=0, C=0 + jmp test_far + nop + nop + trap_ne ;runover protection + inx + inx +far_ret: + trap_eq ;returned flags OK? + trap_pl + trap_cc + trap_vc + cmp #('F'^$aa) ;returned registers OK? + trap_ne + cpx #('A'+1) + trap_ne + cpy #('R'-3) + trap_ne + dex + iny + iny + iny + eor #$aa ;N=0, V=1, Z=0, C=1 + jmp test_near + nop + nop + trap_ne ;runover protection + inx + inx +test_near: + trap_eq ;passed flags OK? + trap_mi + trap_cc + trap_vc + cmp #'F' ;passed registers OK? + trap_ne + cpx #'A' + trap_ne + cpy #'R' + trap_ne + next_test + +; jump indirect + set_stat 0 + lda #'I' + ldx #'N' + ldy #'D' ;N=0, V=0, Z=0, C=0 + jmp (ptr_tst_ind) + nop + trap_ne ;runover protection + dey + dey +ind_ret: + php ;either SP or Y count will fail, if we do not hit + dey + dey + dey + plp + trap_eq ;returned flags OK? + trap_pl + trap_cc + trap_vc + cmp #('I'^$aa) ;returned registers OK? + trap_ne + cpx #('N'+1) + trap_ne + cpy #('D'-6) + trap_ne + tsx ;SP check + cpx #$ff + trap_ne + next_test + +; jump subroutine & return from subroutine + set_stat 0 + lda #'J' + ldx #'S' + ldy #'R' ;N=0, V=0, Z=0, C=0 + jsr test_jsr +jsr_ret = *-1 ;last address of jsr = return address + php ;either SP or Y count will fail, if we do not hit + dey + dey + dey + plp + trap_eq ;returned flags OK? + trap_pl + trap_cc + trap_vc + cmp #('J'^$aa) ;returned registers OK? + trap_ne + cpx #('S'+1) + trap_ne + cpy #('R'-6) + trap_ne + tsx ;sp? + cpx #$ff + trap_ne + next_test + +; break & return from interrupt + .if ROM_vectors = 1 + load_flag 0 ;with interrupts enabled if allowed! + pha + lda #'B' + ldx #'R' + ldy #'K' + plp ;N=0, V=0, Z=0, C=0 + brk + .else + lda #>brk_ret0 ;emulated break + pha + lda #brk_ret1 ;emulated break + pha + lda #bin_rti_ret ;emulated interrupt for rti + pha + lda #dec_rti_ret ;emulated interrupt for rti + pha + lda #jsr_ret + trap_ne + lda $1fe + cmp #brk_ret0 + trap_ne + lda $1fe + cmp #brk_ret1 + trap_ne + lda $1fe + cmp # 1 +zp_init: +zps_: .byte $80,1 ;additional shift pattern to test zero result & flag +zp1_: .byte $c3,$82,$41,0 ;test patterns for LDx BIT ROL ROR ASL LSR +zp7f_: .byte $7f ;test pattern for compare +;logical zeropage operands +zpOR_: .byte 0,$1f,$71,$80 ;test pattern for OR +zpAN_: .byte $0f,$ff,$7f,$80 ;test pattern for AND +zpEO_: .byte $ff,$0f,$8f,$8f ;test pattern for EOR +;indirect addressing pointers +ind1_: .word abs1 ;indirect pointer to pattern in absolute memory + .word abs1+1 + .word abs1+2 + .word abs1+3 + .word abs7f +inw1_: .word abs1-$f8 ;indirect pointer for wrap-test pattern +indt_: .word abst ;indirect pointer to store area in absolute memory + .word abst+1 + .word abst+2 + .word abst+3 +inwt_: .word abst-$f8 ;indirect pointer for wrap-test store +indAN_: .word absAN ;indirect pointer to AND pattern in absolute memory + .word absAN+1 + .word absAN+2 + .word absAN+3 +indEO_: .word absEO ;indirect pointer to EOR pattern in absolute memory + .word absEO+1 + .word absEO+2 + .word absEO+3 +indOR_: .word absOR ;indirect pointer to OR pattern in absolute memory + .word absOR+1 + .word absOR+2 + .word absOR+3 +;add/subtract indirect pointers +adi2_: .word ada2 ;indirect pointer to operand 2 in absolute memory +sbi2_: .word sba2 ;indirect pointer to complemented operand 2 (SBC) +adiy2_: .word ada2-$ff ;with offset for indirect indexed +sbiy2_: .word sba2-$ff +zp_end: + .if (zp_end - zp_init) <> (zp_bss_end - zp_bss) + ;force assembler error if size is different + .error "mismatch between bss and zeropage data" + .endif +data_init: +ex_and_:and #0 ;execute immediate opcodes + rts +ex_eor_:eor #0 ;execute immediate opcodes + rts +ex_ora_:ora #0 ;execute immediate opcodes + rts +ex_adc_:adc #0 ;execute immediate opcodes + rts +ex_sbc_:sbc #0 ;execute immediate opcodes + rts +;zps: .byte $80,1 ;additional shift patterns test zero result & flag +abs1_: .byte $c3,$82,$41,0 ;test patterns for LDx BIT ROL ROR ASL LSR +abs7f_: .byte $7f ;test pattern for compare +;loads +fLDx_: .byte fn,fn,0,fz ;expected flags for load +;shifts +rASL_: ;expected result ASL & ROL -carry +rROL_: .byte 0,2,$86,$04,$82,0 +rROLc_: .byte 1,3,$87,$05,$83,1 ;expected result ROL +carry +rLSR_: ;expected result LSR & ROR -carry +rROR_: .byte $40,0,$61,$41,$20,0 +rRORc_: .byte $c0,$80,$e1,$c1,$a0,$80 ;expected result ROR +carry +fASL_: ;expected flags for shifts +fROL_: .byte fzc,0,fnc,fc,fn,fz ;no carry in +fROLc_: .byte fc,0,fnc,fc,fn,0 ;carry in +fLSR_: +fROR_: .byte 0,fzc,fc,0,fc,fz ;no carry in +fRORc_: .byte fn,fnc,fnc,fn,fnc,fn ;carry in +;increments (decrements) +rINC_: .byte $7f,$80,$ff,0,1 ;expected result for INC/DEC +fINC_: .byte 0,fn,fn,fz,0 ;expected flags for INC/DEC +;logical memory operand +absOR_: .byte 0,$1f,$71,$80 ;test pattern for OR +absAN_: .byte $0f,$ff,$7f,$80 ;test pattern for AND +absEO_: .byte $ff,$0f,$8f,$8f ;test pattern for EOR +;logical accu operand +absORa_:.byte 0,$f1,$1f,0 ;test pattern for OR +absANa_:.byte $f0,$ff,$ff,$ff ;test pattern for AND +absEOa_:.byte $ff,$f0,$f0,$0f ;test pattern for EOR +;logical results +absrlo_:.byte 0,$ff,$7f,$80 +absflo_:.byte fz,fn,0,fn +data_end + .if (data_end - data_init) <> (data_bss_end - data_bss) + ;force assembler error if size is different + .error "mismatch between bss and data" + .endif + +vec_init + .word nmi_trap + .word res_trap + .word irq_trap +vec_bss equ $fffa + .endif ;end of RAM init data + + .if (load_data_direct = 1) & (ROM_vectors = 1) + .segment "VECTORS" + .org $fffa ;vectors + .word nmi_trap + .word res_trap + .word irq_trap + .endif diff --git a/examples/asm/functional_test/Makefile b/examples/asm/functional_test/Makefile new file mode 100644 index 0000000..ff35eea --- /dev/null +++ b/examples/asm/functional_test/Makefile @@ -0,0 +1,3 @@ +build: + ca65 6502_functional_test.ca65 + ld65 -C ../linker.cfg -o 6502_functional_test.bin 6502_functional_test.o diff --git a/examples/asm/functional_test/README.md b/examples/asm/functional_test/README.md index 8b457be..907825d 100644 --- a/examples/asm/functional_test/README.md +++ b/examples/asm/functional_test/README.md @@ -17,6 +17,14 @@ make build This will create a `6502_functional_test.bin` file in the `build` directory, which the emulator will load. +## Running + +Then, from the root of the repository, run: + +```bash +cargo run --release --example functional +``` + ## Credits Taken from