commit 102c08569ab034c874b8c01ca08f5d020a85c15f Author: Joseph Ferano Date: Thu Apr 17 00:45:41 2025 +0800 First commit, still porting Odin code diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..824bace --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "rusted-nes" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..253f91b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rusted-nes" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/basic.asm b/basic.asm new file mode 100644 index 0000000..059a8d8 --- /dev/null +++ b/basic.asm @@ -0,0 +1,15 @@ +; basic.s - Simple 6502 assembly example for cc65 + +.segment "CODE" +.proc main + ; Initialize some registers + ldx #0 ; X = 0 + ldy #10 ; Y = 10 + +loop: + inx ; Increment X + dey ; Decrement Y + bne loop ; Branch if Y not equal to 0 + + rts ; Return from subroutine +.endproc diff --git a/basic.bin b/basic.bin new file mode 100644 index 0000000..acfa1c0 Binary files /dev/null and b/basic.bin differ diff --git a/full.asm b/full.asm new file mode 100644 index 0000000..65f8982 --- /dev/null +++ b/full.asm @@ -0,0 +1,839 @@ +; 6502 Instruction Testing File +; Contains all official and unofficial instructions with all valid addressing modes + + .org $8000 + +; ADC - Add With Carry +adc_tests: + ADC #$10 ; Immediate + ADC $10 ; Zero Page + ADC $10,X ; Zero Page,X + ADC $1000 ; Absolute + ADC $1000,X ; Absolute,X + ADC $1000,Y ; Absolute,Y + ADC ($10,X) ; Indirect,X + ADC ($10),Y ; Indirect,Y + +; ALR (unofficial) - AND + LSR +alr_tests: + ALR #$10 ; Immediate + +; AND - Logical AND +and_tests: + AND #$10 ; Immediate + AND $10 ; Zero Page + AND $10,X ; Zero Page,X + AND $1000 ; Absolute + AND $1000,X ; Absolute,X + AND $1000,Y ; Absolute,Y + AND ($10,X) ; Indirect,X + AND ($10),Y ; Indirect,Y + +; ANC (unofficial) - AND + set C as bit 7 +anc_tests: + ANC #$10 ; Immediate + +; ARR (unofficial) - AND + ROR +arr_tests: + ARR #$10 ; Immediate + +; ASL - Arithmetic Shift Left +asl_tests: + ASL A ; Accumulator + ASL $10 ; Zero Page + ASL $10,X ; Zero Page,X + ASL $1000 ; Absolute + ASL $1000,X ; Absolute,X + +; AXS (unofficial) - Store A&X into memory +;; axs_tests: + ;; AXS #$10 ; Immediate + ;; AXS $10 ; Zero Page + ;; AXS $10,Y ; Zero Page,Y + ;; AXS $1000 ; Absolute + +; Branch Instructions +branch_tests: + BCC label1 ; Relative + BCS label1 ; Relative + BEQ label1 ; Relative + BMI label1 ; Relative + BNE label1 ; Relative + BPL label1 ; Relative + BVC label1 ; Relative + BVS label1 ; Relative +label1: + +; BIT - Bit Test +bit_tests: + BIT $10 ; Zero Page + BIT $1000 ; Absolute + +; BRK - Force Interrupt +brk_tests: + BRK ; Implicit + +; CLC/CLD/CLI/CLV - Clear flags +clear_tests: + CLC ; Implicit + CLD ; Implicit + CLI ; Implicit + CLV ; Implicit + +; CMP - Compare +cmp_tests: + CMP #$10 ; Immediate + CMP $10 ; Zero Page + CMP $10,X ; Zero Page,X + CMP $1000 ; Absolute + CMP $1000,X ; Absolute,X + CMP $1000,Y ; Absolute,Y + CMP ($10,X) ; Indirect,X + CMP ($10),Y ; Indirect,Y + +; CPX - Compare X Register +cpx_tests: + CPX #$10 ; Immediate + CPX $10 ; Zero Page + CPX $1000 ; Absolute + +; CPY - Compare Y Register +cpy_tests: + CPY #$10 ; Immediate + CPY $10 ; Zero Page + CPY $1000 ; Absolute + +; DCP (unofficial) - DEC + CMP +dcp_tests: + DCP $10 ; Zero Page + DCP $10,X ; Zero Page,X + DCP $1000 ; Absolute + DCP $1000,X ; Absolute,X + DCP $1000,Y ; Absolute,Y + DCP ($10,X) ; Indirect,X + DCP ($10),Y ; Indirect,Y + +; DEC - Decrement Memory +dec_tests: + DEC $10 ; Zero Page + DEC $10,X ; Zero Page,X + DEC $1000 ; Absolute + DEC $1000,X ; Absolute,X + +; DEX/DEY - Decrement Register +dex_dey_tests: + DEX ; Implicit + DEY ; Implicit + +; EOR - Exclusive OR +eor_tests: + EOR #$10 ; Immediate + EOR $10 ; Zero Page + EOR $10,X ; Zero Page,X + EOR $1000 ; Absolute + EOR $1000,X ; Absolute,X + EOR $1000,Y ; Absolute,Y + EOR ($10,X) ; Indirect,X + EOR ($10),Y ; Indirect,Y + +; ISC/ISB (unofficial) - INC + SBC +isc_tests: + ISC $10 ; Zero Page + ISC $10,X ; Zero Page,X + ISC $1000 ; Absolute + ISC $1000,X ; Absolute,X + ISC $1000,Y ; Absolute,Y + ISC ($10,X) ; Indirect,X + ISC ($10),Y ; Indirect,Y + +; INC - Increment Memory +inc_tests: + INC $10 ; Zero Page + INC $10,X ; Zero Page,X + INC $1000 ; Absolute + INC $1000,X ; Absolute,X + +; INX/INY - Increment Register +inx_iny_tests: + INX ; Implicit + INY ; Implicit + +; JMP - Jump +jmp_tests: + JMP $1000 ; Absolute + JMP ($1000) ; Absolute Indirect + +; JSR - Jump to Subroutine +jsr_tests: + JSR $1000 ; Absolute + +; KIL (unofficial) - Halt the CPU +;; kil_tests: +;; KIL ; Implicit + +; LAX (unofficial) - LDA + LDX +lax_tests: + LAX #$10 ; Immediate + LAX $10 ; Zero Page + LAX $10,Y ; Zero Page,Y + LAX $1000 ; Absolute + LAX $1000,Y ; Absolute,Y + LAX ($10,X) ; Indirect,X + LAX ($10),Y ; Indirect,Y + +; LAS (unofficial) - LDA/TSX with stack pointer +las_tests: + LAS $1000,Y ; Absolute,Y + +; LDA - Load Accumulator +lda_tests: + LDA #$10 ; Immediate + LDA $10 ; Zero Page + LDA $10,X ; Zero Page,X + LDA $1000 ; Absolute + LDA $1000,X ; Absolute,X + LDA $1000,Y ; Absolute,Y + LDA ($10,X) ; Indirect,X + LDA ($10),Y ; Indirect,Y + +; LDX - Load X Register +ldx_tests: + LDX #$10 ; Immediate + LDX $10 ; Zero Page + LDX $10,Y ; Zero Page,Y + LDX $1000 ; Absolute + LDX $1000,Y ; Absolute,Y + +; LDY - Load Y Register +ldy_tests: + LDY #$10 ; Immediate + LDY $10 ; Zero Page + LDY $10,X ; Zero Page,X + LDY $1000 + + + LDY $1000 ; Absolute + LDY $1000,X ; Absolute,X + +; LSR - Logical Shift Right +lsr_tests: + LSR A ; Accumulator + LSR $10 ; Zero Page + LSR $10,X ; Zero Page,X + LSR $1000 ; Absolute + LSR $1000,X ; Absolute,X + +; NOP - No Operation +nop_tests: + NOP ; Implicit + NOP #$10 ; Immediate (unofficial) + NOP $10 ; Zero Page (unofficial) + NOP $10,X ; Zero Page,X (unofficial) + NOP $1000 ; Absolute (unofficial) + NOP $1000,X ; Absolute,X (unofficial) + +; ORA - Logical Inclusive OR +ora_tests: + ORA #$10 ; Immediate + ORA $10 ; Zero Page + ORA $10,X ; Zero Page,X + ORA $1000 ; Absolute + ORA $1000,X ; Absolute,X + ORA $1000,Y ; Absolute,Y + ORA ($10,X) ; Indirect,X + ORA ($10),Y ; Indirect,Y + +; Stack Operations +stack_tests: + PHA ; Push Accumulator - Implicit + PHP ; Push Processor Status - Implicit + PLA ; Pull Accumulator - Implicit + PLP ; Pull Processor Status - Implicit + +; RLA (unofficial) - ROL + AND +rla_tests: + RLA $10 ; Zero Page + RLA $10,X ; Zero Page,X + RLA $1000 ; Absolute + RLA $1000,X ; Absolute,X + RLA $1000,Y ; Absolute,Y + RLA ($10,X) ; Indirect,X + RLA ($10),Y ; Indirect,Y + +; ROL - Rotate Left +rol_tests: + ROL A ; Accumulator + ROL $10 ; Zero Page + ROL $10,X ; Zero Page,X + ROL $1000 ; Absolute + ROL $1000,X ; Absolute,X + +; ROR - Rotate Right +ror_tests: + ROR A ; Accumulator + ROR $10 ; Zero Page + ROR $10,X ; Zero Page,X + ROR $1000 ; Absolute + ROR $1000,X ; Absolute,X + +; RRA (unofficial) - ROR + ADC +rra_tests: + RRA $10 ; Zero Page + RRA $10,X ; Zero Page,X + RRA $1000 ; Absolute + RRA $1000,X ; Absolute,X + RRA $1000,Y ; Absolute,Y + RRA ($10,X) ; Indirect,X + RRA ($10),Y ; Indirect,Y + +; RTI - Return from Interrupt +rti_tests: + RTI ; Implicit + +; RTS - Return from Subroutine +rts_tests: + RTS ; Implicit + +; SAX (unofficial) - Store A&X +sax_tests: + SAX $10 ; Zero Page + SAX $10,Y ; Zero Page,Y + SAX $1000 ; Absolute + SAX ($10,X) ; Indirect,X + +; SBC - Subtract with Carry +sbc_tests: + SBC #$10 ; Immediate + SBC $10 ; Zero Page + SBC $10,X ; Zero Page,X + SBC $1000 ; Absolute + SBC $1000,X ; Absolute,X + SBC $1000,Y ; Absolute,Y + SBC ($10,X) ; Indirect,X + SBC ($10),Y ; Indirect,Y + +; SEC/SED/SEI - Set Flags +set_tests: + SEC ; Implicit + + + + SEC ; Implicit + SED ; Implicit + SEI ; Implicit + +; SHX (unofficial) - Store X&H +shx_tests: + SHX $1000,Y ; Absolute,Y + +; SHY (unofficial) - Store Y&H +shy_tests: + SHY $1000,X ; Absolute,X + +; SLO (unofficial) - ASL + ORA +slo_tests: + SLO $10 ; Zero Page + SLO $10,X ; Zero Page,X + SLO $1000 ; Absolute + SLO $1000,X ; Absolute,X + SLO $1000,Y ; Absolute,Y + SLO ($10,X) ; Indirect,X + SLO ($10),Y ; Indirect,Y + +; SRE (unofficial) - LSR + EOR +sre_tests: + SRE $10 ; Zero Page + SRE $10,X ; Zero Page,X + SRE $1000 ; Absolute + SRE $1000,X ; Absolute,X + SRE $1000,Y ; Absolute,Y + SRE ($10,X) ; Indirect,X + SRE ($10),Y ; Indirect,Y + +; STA - Store Accumulator +sta_tests: + STA $10 ; Zero Page + STA $10,X ; Zero Page,X + STA $1000 ; Absolute + STA $1000,X ; Absolute,X + STA $1000,Y ; Absolute,Y + STA ($10,X) ; Indirect,X + STA ($10),Y ; Indirect,Y + +; STX - Store X Register +stx_tests: + STX $10 ; Zero Page + STX $10,Y ; Zero Page,Y + STX $1000 ; Absolute + +; STY - Store Y Register +sty_tests: + STY $10 ; Zero Page + STY $10,X ; Zero Page,X + STY $1000 ; Absolute + +; TAS (unofficial) - Store A&X into SP and A&X&H into memory +tas_tests: + TAS $1000,Y ; Absolute,Y + +; Register Transfers +transfer_tests: + TAX ; Implicit + TAY ; Implicit + TSX ; Implicit + TXA ; Implicit + TXS ; Implicit + TYA ; Implicit + +; XAA (unofficial) - Complex behavior with A,X,# (unstable) +;; xaa_tests: +;; XAA #$10 ; Immediate + +; Additional unused opcodes/variants for complete coverage +additional_tests: + ; Some assemblers might not recognize all unofficial opcodes by name + ; So here's a section with .byte directives for any missed opcodes + + ; Additional NOPs (different variants) + .byte $1A ; NOP implicit (unofficial) + .byte $3A ; NOP implicit (unofficial) + .byte $5A ; NOP implicit (unofficial) + .byte $7A ; NOP implicit (unofficial) + .byte $DA ; NOP implicit (unofficial) + .byte $FA ; NOP implicit (unofficial) + + ; JAM/KIL/HLT variants (these halt the CPU) + .byte $02 ; KIL/JAM (unofficial) + .byte $12 ; KIL/JAM (unofficial) + .byte $22 ; KIL/JAM (unofficial) + .byte $32 ; KIL/JAM (unofficial) + .byte $42 ; KIL/JAM (unofficial) + .byte $52 ; KIL/JAM (unofficial) + .byte $62 ; KIL/JAM (unofficial) + .byte $72 ; KIL/JAM (unofficial) + .byte $92 ; KIL/JAM (unofficial) + .byte $B2 ; KIL/JAM ( + + + .byte $B2 ; KIL/JAM (unofficial) + .byte $D2 ; KIL/JAM (unofficial) + .byte $F2 ; KIL/JAM (unofficial) + + ; LAX variants + .byte $AB ; LAX immediate (unofficial) - might not be recognized as LAX #$ + + ; SAX variants + .byte $87 ; SAX zero page (unofficial) + .byte $97 ; SAX zero page,Y (unofficial) + .byte $8F ; SAX absolute (unofficial) + .byte $83 ; SAX (indirect,X) (unofficial) + + ; ANE/XAA (highly unstable) + .byte $8B ; XAA/ANE immediate (unofficial) + + ; SHY variants + .byte $9C ; SHY absolute,X (unofficial) + + ; SHX variants + .byte $9E ; SHX absolute,Y (unofficial) + + ; TAS/SHS variants + .byte $9B ; TAS/SHS absolute,Y (unofficial) + + ; LAS/LAR variants + .byte $BB ; LAS/LAR absolute,Y (unofficial) + + ; Additional addressing mode testing for all instructions + + ; Test boundary conditions + LDA $FF ; Zero page boundary + LDA $FF,X ; Zero page wrap-around + LDA $FFFF ; Absolute address boundary + LDA $FFFF,X ; Absolute,X wrap-around + LDA $FFFF,Y ; Absolute,Y wrap-around + ;; JMP ($FFFF) ; Indirect JMP boundary + + ; Test with specific values that might be edge cases + LDA #$00 ; Immediate with 0 + LDA #$FF ; Immediate with max value + LDA #$80 ; Immediate with sign bit + + ; Exhaustive branch testing + label_forward: + BEQ label_forward_target + BNE label_forward_target + BCS label_forward_target + BCC label_forward_target + BMI label_forward_target + BPL label_forward_target + BVS label_forward_target + BVC label_forward_target + label_forward_target: + + ; Backward branches + BEQ label_backward + BNE label_backward + BCS label_backward + BCC label_backward + BMI label_backward + BPL label_backward + BVS label_backward + BVC label_backward + label_backward: + + ; Exercise all unofficial instructions with all addressing modes + ; Some of these combinations might not exist on real hardware + + ; DCP (DEC + CMP) all modes + DCP $20 ; Zero Page + DCP $20,X ; Zero Page,X + DCP $2000 ; Absolute + DCP $2000,X ; Absolute,X + DCP $2000,Y ; Absolute,Y + DCP ($20,X) ; Indirect,X + DCP ($20),Y ; Indirect,Y + + ; ISC/ISB (INC + SBC) all modes + ISC $20 ; Zero Page + ISC $20,X ; Zero Page,X + ISC $2000 ; Absolute + ISC $2000,X ; Absolute,X + ISC $2000,Y ; Absolute,Y + ISC ($20,X) ; Indirect,X + ISC ($20),Y ; Indirect,Y + + ; SLO (ASL + ORA) all modes + SLO $20 ; Zero Page + SLO $20,X ; Zero Page,X + SLO $2000 ; Absolute + + + SLO $2000 ; Absolute + SLO $2000,X ; Absolute,X + SLO $2000,Y ; Absolute,Y + SLO ($20,X) ; Indirect,X + SLO ($20),Y ; Indirect,Y + + ; RLA (ROL + AND) all modes + RLA $20 ; Zero Page + RLA $20,X ; Zero Page,X + RLA $2000 ; Absolute + RLA $2000,X ; Absolute,X + RLA $2000,Y ; Absolute,Y + RLA ($20,X) ; Indirect,X + RLA ($20),Y ; Indirect,Y + + ; SRE (LSR + EOR) all modes + SRE $20 ; Zero Page + SRE $20,X ; Zero Page,X + SRE $2000 ; Absolute + SRE $2000,X ; Absolute,X + SRE $2000,Y ; Absolute,Y + SRE ($20,X) ; Indirect,X + SRE ($20),Y ; Indirect,Y + + ; RRA (ROR + ADC) all modes + RRA $20 ; Zero Page + RRA $20,X ; Zero Page,X + RRA $2000 ; Absolute + RRA $2000,X ; Absolute,X + RRA $2000,Y ; Absolute,Y + RRA ($20,X) ; Indirect,X + RRA ($20),Y ; Indirect,Y + + ; LAX (LDA + LDX) all documented modes + LAX $20 ; Zero Page + LAX $20,Y ; Zero Page,Y + LAX $2000 ; Absolute + LAX $2000,Y ; Absolute,Y + LAX ($20,X) ; Indirect,X + LAX ($20),Y ; Indirect,Y + LAX #$20 ; Immediate (unstable) + + ; SAX (A&X store) all modes + SAX $20 ; Zero Page + SAX $20,Y ; Zero Page,Y + SAX $2000 ; Absolute + SAX ($20,X) ; Indirect,X + + ; Data regions to test loading and storing + .byte $AA, $55, $00, $FF, $01, $80, $7F, $81 + + ; Test multiple data byte formats +data_section: + .byte $01, $02, $03, $04 + .word $1234, $5678 + .dword $12345678 ; Some assemblers support this + .byte %01010101 ; Binary format + .byte 'A', 'B', 'C' ; Character format + + ; Test self-modifying code scenarios + LDA #$EA ; Load NOP opcode + STA modify_target ; Modify the instruction below +modify_target: + BRK ; This will be modified to NOP + + ; Additional instruction combinations to ensure all opcodes are covered + + ; NOP variants (unofficial) + .byte $04 ; NOP zero page + .byte $44 ; NOP zero page + .byte $64 ; NOP zero page + .byte $0C ; NOP absolute + .byte $1C ; NOP absolute,X + .byte $3C ; NOP absolute,X + .byte $5C ; NOP absolute,X + .byte $7C ; NOP absolute,X + .byte $DC ; NOP absolute,X + .byte $FC ; NOP absolute,X + .byte $14 ; NOP zero page,X + .byte $34 ; NOP zero page,X + .byte $54 ; NOP zero page,X + .byte $74 ; NOP zero page,X + .byte $D4 ; NOP zero page,X + .byte $F4 ; NOP zero page,X + + ; ANC variants + .byte $0B ; ANC immediate + .byte $2B ; ANC immediate (another variant) + + ; ALR/ASR variant + .byte $4B ; ALR immediate + + ; ARR variant + .byte $6B ; ARR immediate + + ; AXS/SBX variant + .byte $CB ; AXS immediate + + ; Test decimal mode instructions + SED ; Set decimal mode + ADC #$09 ; Add in decimal mode + SBC #$05 ; Subtract in decimal mode + CLD ; Clear decimal mode + + ; Complex addressing modes with concrete values + LDA $1234,X ; Absolute,X + LDA $1234,Y ; Absolute,Y + LDA ($12,X) ; Indirect,X + LDA ($34),Y ; Indirect,Y + JMP ($5678) ; Indirect + + ; Specific tests for page boundary crossing + ; Set X and Y to values that will cause page crosses + LDX #$FF + LDY #$FF + + ; These will cross page boundaries with the above X/Y values + LDA $1001,X ; Cross to $1100 + LDA $1001,Y ; Cross to $1100 + LDA ($01),Y ; Indirect cross + + ; Specific tests for zero page wrap-around + LDX #$FF + LDA $01,X ; Should wrap to $0000 + + ; Complete set of stack operations + LDX #$FF + TXS ; Set stack pointer to $FF + PHA ; Push A + PHP ; Push processor status + PLA ; Pull A + PLP ; Pull processor status + + ; Test every possible branch + LDA #$80 ; Set N flag + BPL no_branch1 ; Should not branch + LDA #$00 ; Clear N flag + BMI no_branch2 ; Should not branch +no_branch1: +no_branch2: + + LDA #$00 + BEQ branch1 ; Should branch + LDA #$01 ; This should be skipped +branch1: + LDA #$01 + BNE branch2 ; Should branch + LDA #$02 ; This should be skipped +branch2: + + CLC ; Clear carry + BCS no_branch3 ; Should not branch + SEC ; Set carry + BCC no_branch4 ; Should not branch +no_branch3: +no_branch4: + + CLV ; Clear overflow + BVS no_branch5 ; Should not branch + ; Need to set overflow - more complex + ; For test purposes we'll use a .byte directive + .byte $B8 ; CLV + .byte $70, $FE ; BVS -2 (back to the CLV) +no_branch5: + + ; Exhaustive testing of BIT instruction + LDA #$C0 ; Set bits 7 and 6 + STA $30 ; Store in zero page + BIT $30 ; Test BIT with N and V set + + LDA #$00 ; Clear all bits + STA $31 ; Store in zero page + BIT $31 ; Test BIT with all flags cleared + + ; Test all compare instructions + LDA #$50 + CMP #$30 ; Compare with smaller (carry set) + CMP #$50 ; Compare with equal (carry set, zero set) + CMP #$70 ; Compare with larger (carry clear) + + LDX #$40 + CPX #$20 ; Compare X with smaller + CPX #$40 ; Compare X with equal + CPX #$60 ; Compare X with larger + + + LDY #$60 + CPY #$40 ; Compare Y with smaller + CPY #$60 ; Compare Y with equal + CPY #$80 ; Compare Y with larger + + ; Test shifts and rotates with various values + LDA #$01 ; Test shift left with 1 + ASL A ; Should become 2 + + LDA #$80 ; Test shift left with sign bit + ASL A ; Should set carry, clear result + + LDA #$01 ; Test rotate left with 1 + CLC ; Clear carry + ROL A ; Should become 2 + + LDA #$80 ; Test rotate left with sign bit + CLC ; Clear carry + ROL A ; Should set carry, clear result + + LDA #$80 ; Test shift right with sign bit + LSR A ; Should become 0x40, clear carry + + LDA #$01 ; Test shift right with 1 + LSR A ; Should become 0, set carry + + LDA #$01 ; Test rotate right with 1 + CLC ; Clear carry + ROR A ; Should become 0 + + LDA #$01 ; Test rotate right with 1 + SEC ; Set carry + ROR A ; Should become 0x80 + carry + + ; Exhaustive increment/decrement tests + LDA #$FF + STA $40 ; Store FF in memory + INC $40 ; Should wrap to 0 + + LDA #$01 + STA $41 ; Store 01 in memory + DEC $41 ; Should become 0 + + LDX #$FF ; X = FF + INX ; Should wrap to 0 + + LDX #$01 ; X = 01 + DEX ; Should become 0 + + LDY #$FF ; Y = FF + INY ; Should wrap to 0 + + LDY #$01 ; Y = 01 + DEY ; Should become 0 + + ; Test conditional behavior after operations + LDA #$00 + BEQ is_zero1 ; Should branch + BRK ; Should be skipped +is_zero1: + + LDA #$40 + BNE not_zero1 ; Should branch + BRK ; Should be skipped +not_zero1: + + LDA #$80 + BMI is_negative1 ; Should branch + BRK ; Should be skipped +is_negative1: + + LDA #$7F + BPL is_positive1 ; Should branch + BRK ; Should be skipped +is_positive1: + + ; Test unofficial instructions with various values + + ; SLO (ASL memory, then ORA with A) + LDA #$01 + STA $50 + LDA #$02 + SLO $50 ; $50 becomes $02, A becomes $02|$02 = $02 + + ; RLA (ROL memory, then AND with A) + LDA #$03 + STA $51 + LDA #$01 + SEC ; Set carry + RLA $51 ; $51 becomes $07, A becomes $01&$07 = $01 + + ; SRE (LSR memory, then EOR with A) + LDA #$01 + STA $52 + LDA #$03 + SRE $52 ; $52 becomes $00 with carry, A becomes $03^$00 = $03 + + ; RRA (ROR memory, then ADC with A) + LDA #$80 + STA $53 + LDA #$01 + CLC ; Clear carry + RRA $53 ; $53 becomes $40, A becomes $01+$40 = $41 + + ; Test memory wrapping behavior + LDX #$FF + LDA $00,X ; Zero page wrap from $FF to $00 + + ; Test processor flags after arithmetical operations + CLC + LDA #$7F + ADC #$01 ; Should set overflow and negative (0x7F + 0x01 = 0x80) + + SEC + LDA #$80 + SBC #$01 ; Should set overflow and clear negative (0x80 - 0x01 = 0x7F) + + ; More page boundary crossings + LDX #$FF + LDA $1000,X ; Read from $10FF + + LDY #$FF + LDA $1000,Y ; Read from $10FF + + LDY #$FF + LDA ($80),Y ; If $80/$81 contains $1000, reads from $10FF + + ; Comprehensive unofficial opcodes testing + ; ANC (AND immediate then copy bit 7 to carry) + LDA #$80 + ANC #$FF ; Should set carry + + LDA #$7F + ANC #$FF ; Should clear carry + + ; ALR/ASR (AND immediate then LSR) + LDA #$FF + ALR #$55 ; A becomes $55, then $2A with carry set + + ; ARR (AND immediate then ROR) + LDA #$FF + SEC + ARR #$55 ; A becomes $55 diff --git a/full.bin b/full.bin new file mode 100644 index 0000000..56a5651 Binary files /dev/null and b/full.bin differ diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ada4815 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod sim6502; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c6247fe --- /dev/null +++ b/src/main.rs @@ -0,0 +1,19 @@ +use rusted_nes::sim6502::*; +use std::fs; +use std::fs::File; +use std::io::Read; + +fn main() { + let mut f = File::open("basic.bin").expect("File not found"); + let metadata = fs::metadata("basic.bin").expect("Could not load metadata"); + let bytes = metadata.len(); + let mut buffer = vec![0; bytes as usize]; + let bytes_read = f.read(&mut buffer).expect("Problem reading file into buffer"); + + println!("Read {} bytes", bytes_read); + + let instructions = decode_instructions(&buffer, bytes_read as i32); + for inst in instructions { + println!("{:?}", inst); + } +} diff --git a/src/sim6502.rs b/src/sim6502.rs new file mode 100644 index 0000000..303c2ed --- /dev/null +++ b/src/sim6502.rs @@ -0,0 +1,356 @@ +#![allow(dead_code)] + +use InstructionName::*; +use AddressingShort::*; +use Register::*; +use Flag::*; + +pub enum Addressing { + Implicit, + Accumulator, + Immediate, + ZeroPage, + ZeroPageX, + ZeroPageY, + Absolute, + AbsoluteX, + AbsoluteY, + IndirectX, + IndirectY, + AbsoluteIndirect, + Relative, +} + +#[derive(Debug, PartialEq)] +pub enum InstructionName { + ADC, AHX, ALR, AND, ANC, ARR, ASL, AXS, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRK, + BVC, BVS, CLC, CLD, CLI, CLV, CMP, CPX, CPY, DCP, DEC, DEX, DEY, EOR, ISC, INC, + INX, INY, JMP, JSR, KIL, LAX, LAS, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA, + PLP, RLA, ROL, ROR, RRA, RTI, RTS, SAX, SBC, SEC, SED, SEI, SHX, SHY, SLO, SRE, + STA, STX, STY, TAS, TAX, TAY, TSX, TXA, TXS, TYA, XAA, +} + +#[derive(Debug)] +pub enum AddressingShort { + Imp, Acc, Imm, ZPg, ZPX, + ZPY, Abs, AbX, AbY, InX, + InY, AbI, Rel, +} + +#[derive(Debug)] +pub struct InstructionDefinition { + name: InstructionName, + addressing: AddressingShort, +} + +#[derive(Copy, Clone)] +pub enum Register { + A, + X, + Y, + S, +} + +#[derive(Copy, Clone)] +pub enum Flag { + Carry, + Zero, + Interrupt, + Decimal, + Overflow, + Negative, +} + +macro_rules! impl_indexing { + ($array_type:ty, $enum_name:ident) => { + impl std::ops::Index<$enum_name> for $array_type { + type Output = <$array_type as std::ops::Index>::Output; + + fn index(&self, flag: $enum_name) -> &Self::Output { + &self[flag as usize] + } + } + + impl std::ops::IndexMut<$enum_name> for $array_type { + fn index_mut(&mut self, flag: $enum_name) -> &mut Self::Output { + &mut self[flag as usize] + } + } + }; +} + +impl_indexing!([bool; 6], Flag); +impl_indexing!([u8; 4], Register); + +#[derive(Debug)] +pub enum Operand { + None, +} + +pub struct Cpu { + interrupt_request: bool, + registers: [u8; 4], + memory: Vec, + stack: Vec, + status_flags: [bool; 6], + program_counter: i16, +} + +#[derive(Debug)] +pub struct Instruction { + opcode: u8, + name: InstructionName, + src_opr: Operand, + dst_opr: Operand, + bytes_read: i32, + raw_data: Vec, +} + +impl Cpu { + pub fn store(&mut self, _operand: &Operand, _value: u8) {} + pub fn load(&mut self, _operand: &Operand) -> u8 { 0 } + pub fn load_u16(&mut self, _operand: &Operand) -> u16 { 0 } + + pub fn check_z_n_flags(&mut self, result: u8) -> u8 { + self.status_flags[Zero] = result == 0; + self.status_flags[Negative] = result & 0x80 > 0; + result + } + + pub fn check_overflow_flag(&mut self, value: u8) { + self.status_flags[Overflow] = value & 0x40 > 0 + } + + pub fn get_flags_as_byte(&self) -> u8 { + let mut byte_: u8 = 0; + for (i,t) in self.status_flags.iter().enumerate() { + byte_ |= (0x1 << i as u8) & if *t { 0x1 } else { 0x0 } + } + return byte_ + } + + pub fn set_flags_from_byte(&mut self, byte_: u8) { + for (i,_) in (0..8).enumerate() { + let idx = i as u8; + self.status_flags[i] = (byte_ & (idx + 1)) > 0; + } + } + + + pub fn rotate_shift(&mut self, inst: &Instruction, head_bit: u8, tail_bit: u8) { + let val = self.load(&inst.src_opr); + let bit = val & head_bit; + let mut val = if head_bit > tail_bit { val << 1 } else { val >> 1 }; + val |= if self.status_flags[Carry] { tail_bit } else { 0x0 }; + let flags_result = self.check_z_n_flags(val); + self.store(&inst.dst_opr, flags_result); + self.status_flags[Carry] = bit > 0; + } + + pub fn logic_shift(&mut self, inst: &Instruction, head_bit: u8, tail_bit: u8) { + let mut val = self.load(&inst.src_opr); + let bit = val & head_bit; + val = if head_bit > tail_bit { val << 1 } else { val >> 1 }; + let flags_result = self.check_z_n_flags(val); + self.store(&inst.dst_opr, flags_result); + self.status_flags[Carry] = bit > 0x0; + } + + pub fn branch(mut self, offset: i8) { + self.program_counter += 2 + offset as i16; + } +} + +macro_rules! instruction_defs { + ($({$name:ident, $addr:ident}),* $(,)?) => { + [ + $( + InstructionDefinition { + name: $name, + addressing: $addr + }, + )* + ] + }; +} + +const INSTRUCTION_DEFS: [InstructionDefinition; 256] = instruction_defs![ +// x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF +/*0x*/{BRK,Imp},{ORA,InX},{KIL,Imp},{SLO,InX},{NOP,ZPg},{ORA,ZPg},{ASL,ZPg},{SLO,ZPg},{PHP,Imp},{ORA,Imm},{ASL,Acc},{ANC,Imm},{NOP,Abs},{ORA,Abs},{ASL,Abs},{SLO,Abs}, +/*1x*/{BPL,Rel},{ORA,InY},{KIL,Imp},{SLO,InY},{NOP,ZPX},{ORA,ZPX},{ASL,ZPX},{SLO,ZPX},{CLC,Imp},{ORA,AbY},{NOP,Imp},{SLO,AbY},{NOP,AbX},{ORA,AbX},{ASL,AbX},{SLO,AbX}, +/*2x*/{JSR,Abs},{AND,InX},{KIL,Imp},{RLA,InX},{BIT,ZPg},{AND,ZPg},{ROL,ZPg},{RLA,ZPg},{PLP,Imp},{AND,Imm},{ROL,Acc},{ANC,Imm},{BIT,Abs},{AND,Abs},{ROL,Abs},{RLA,Abs}, +/*3x*/{BMI,Rel},{AND,InY},{KIL,Imp},{RLA,InY},{NOP,ZPX},{AND,ZPX},{ROL,ZPX},{RLA,ZPX},{SEC,Imp},{AND,AbY},{NOP,Imp},{RLA,AbY},{NOP,AbX},{AND,AbX},{ROL,AbX},{RLA,AbX}, +/*4x*/{RTI,Imp},{EOR,InX},{KIL,Imp},{SRE,InX},{NOP,ZPg},{EOR,ZPg},{LSR,ZPg},{SRE,ZPg},{PHA,Imp},{EOR,Imm},{LSR,Acc},{ALR,Imm},{JMP,Abs},{EOR,Abs},{LSR,Abs},{SRE,Abs}, +/*5x*/{BVC,Rel},{EOR,InY},{KIL,Imp},{SRE,InY},{NOP,ZPX},{EOR,ZPX},{LSR,ZPX},{SRE,ZPX},{CLI,Imp},{EOR,AbY},{NOP,Imp},{SRE,AbY},{NOP,AbX},{EOR,AbX},{LSR,AbX},{SRE,AbX}, +/*6x*/{RTS,Imp},{ADC,InX},{KIL,Imp},{RRA,InX},{NOP,ZPg},{ADC,ZPg},{ROR,ZPg},{RRA,ZPg},{PLA,Imp},{ADC,Imm},{ROR,Acc},{ARR,Imm},{JMP,AbI},{ADC,Abs},{ROR,Abs},{RRA,Abs}, +/*7x*/{BVS,Rel},{ADC,InY},{KIL,Imp},{RRA,InY},{NOP,ZPX},{ADC,ZPX},{ROR,ZPX},{RRA,ZPX},{SEI,Imp},{ADC,AbY},{NOP,Imp},{RRA,AbY},{NOP,AbX},{ADC,AbX},{ROR,AbX},{RRA,AbX}, +/*8x*/{NOP,Imm},{STA,InX},{NOP,Imm},{SAX,InX},{STY,ZPg},{STA,ZPg},{STX,ZPg},{SAX,ZPg},{DEY,Imp},{NOP,Imm},{TXA,Imp},{XAA,Imm},{STY,Abs},{STA,Abs},{STX,Abs},{SAX,Abs}, +/*9x*/{BCC,Rel},{STA,InY},{KIL,Imp},{AHX,InY},{STY,ZPX},{STA,ZPX},{STX,ZPY},{SAX,ZPY},{TYA,Imp},{STA,AbY},{TXS,Imp},{TAS,AbY},{SHY,AbX},{STA,AbX},{SHX,AbY},{AHX,AbY}, +/*Ax*/{LDY,Imm},{LDA,InX},{LDX,Imm},{LAX,InX},{LDY,ZPg},{LDA,ZPg},{LDX,ZPg},{LAX,ZPg},{TAY,Imp},{LDA,Imm},{TAX,Imp},{LAX,Imm},{LDY,Abs},{LDA,Abs},{LDX,Abs},{LAX,Abs}, +/*Bx*/{BCS,Rel},{LDA,InY},{KIL,Imp},{LAX,InY},{LDY,ZPX},{LDA,ZPX},{LDX,ZPY},{LAX,ZPY},{CLV,Imp},{LDA,AbY},{TSX,Imp},{LAS,AbY},{LDY,AbX},{LDA,AbX},{LDX,AbY},{LAX,AbY}, +/*Cx*/{CPY,Imm},{CMP,InX},{NOP,Imm},{DCP,InX},{CPY,ZPg},{CMP,ZPg},{DEC,ZPg},{DCP,ZPg},{INY,Imp},{CMP,Imm},{DEX,Imp},{AXS,Imm},{CPY,Abs},{CMP,Abs},{DEC,Abs},{DCP,Abs}, +/*Dx*/{BNE,Rel},{CMP,InY},{KIL,Imp},{DCP,InY},{NOP,ZPX},{CMP,ZPX},{DEC,ZPX},{DCP,ZPX},{CLD,Imp},{CMP,AbY},{NOP,Imp},{DCP,AbY},{NOP,AbX},{CMP,AbX},{DEC,AbX},{DCP,AbX}, +/*Ex*/{CPX,Imm},{SBC,InX},{NOP,Imm},{ISC,InX},{CPX,ZPg},{SBC,ZPg},{INC,ZPg},{ISC,ZPg},{INX,Imp},{SBC,Imm},{NOP,Imp},{SBC,Imm},{CPX,Abs},{SBC,Abs},{INC,Abs},{ISC,Abs}, +/*Fx*/{BEQ,Rel},{SBC,InY},{KIL,Imp},{ISC,InY},{NOP,ZPX},{SBC,ZPX},{INC,ZPX},{ISC,ZPX},{SED,Imp},{SBC,AbY},{NOP,Imp},{ISC,AbY},{NOP,AbX},{SBC,AbX},{INC,AbX},{ISC,AbX}, +]; + +fn execute(cpu: &mut Cpu, inst: &Instruction) { + match inst.name { + // Access + LDA | STA | LDX | STX | LDY | STY => { + let val = cpu.load(&inst.src_opr); + cpu.store(&inst.dst_opr, val); + }, + // Transfer + TAX | TAY | TXA | TYA => { + let val = cpu.load(&inst.src_opr); + let flags_result = cpu.check_z_n_flags(val); + cpu.store(&inst.dst_opr, flags_result); + } + // Arithmetic + ADC => { + let memory = cpu.load(&inst.src_opr); + let result = cpu.registers[A] as u16 + memory as u16; + let carry_val = if cpu.status_flags[Carry] { 1 } else { 0 }; + cpu.registers[A] = result as u8 + carry_val; + cpu.status_flags[Carry] = result > 0xFF; + let reg_a = cpu.registers[A]; + let res = result as u8; + cpu.status_flags[Overflow] = (res ^ reg_a) & (res ^ memory) & 0x80 > 0; + cpu.check_z_n_flags(result as u8); + }, + // Arithmetic + INC | INX | INY => { + let val = cpu.load(&inst.src_opr) + 1; + let flags_result = cpu.check_z_n_flags(val); + cpu.store(&inst.dst_opr, flags_result); + }, + DEC | DEX | DEY => { + let val = cpu.load(&inst.src_opr) - 1; + let flags_result = cpu.check_z_n_flags(val); + cpu.store(&inst.dst_opr, flags_result); + }, + // Compare + SBC | CMP | CPX | CPY => { + let reg = match inst.name { + SBC | CMP => A, + CPX => X, + _ => Y + }; + let memory = cpu.load(&inst.src_opr); + let reg_val = cpu.registers[reg] as u16; + let result = reg_val - (memory as u16); + if inst.name == SBC { + let carry_val = if cpu.status_flags[Carry] { 1 } else { 0 }; + cpu.registers[reg] = result as u8 - carry_val; + } + + cpu.status_flags[Carry] = result < 0; + + let reg_a = cpu.registers[A]; + let res = result as u8; + cpu.status_flags[Overflow] = (res ^ reg_a) & (res ^ !memory) & 0x80 > 0; + cpu.check_z_n_flags(res); + }, + // Shift + ASL => cpu.logic_shift(inst, 0x80, 0x01), + LSR => cpu.logic_shift(inst, 0x01, 0x08), + ROL => cpu.rotate_shift(inst, 0x80, 0x01), + ROR => cpu.rotate_shift(inst, 0x01, 0x08), + // Bitwise + AND => { + cpu.registers[A] &= cpu.load(&inst.src_opr); + cpu.check_z_n_flags(cpu.registers[A]); + }, + BIT => { + let mem_val = cpu.load(&inst.src_opr); + let result = cpu.registers[A] & mem_val; + cpu.check_z_n_flags(result); + cpu.check_overflow_flag(result); + }, + ORA => { + cpu.registers[A] |= cpu.load(&inst.src_opr); + cpu.check_z_n_flags(cpu.registers[A]); + }, + EOR => { + cpu.registers[A] != cpu.load(&inst.src_opr); + cpu.check_z_n_flags(cpu.registers[A]); + }, + // Branching + BCC => if !cpu.status_flags[Carry] { cpu.branch(cpu.load(&inst.src_opr) as i8) }, + BCS => if cpu.status_flags[Carry] { cpu.branch(cpu.load(&inst.src_opr) as i8) }, + BNE => if !cpu.status_flags[Zero] { cpu.branch(cpu.load(&inst.src_opr) as i8) }, + BEQ => if cpu.status_flags[Zero] { cpu.branch(cpu.load(&inst.src_opr) as i8) }, + BPL => if !cpu.status_flags[Negative] { cpu.branch(cpu.load(&inst.src_opr) as i8) }, + BMI => if cpu.status_flags[Negative] { cpu.branch(cpu.load(&inst.src_opr) as i8) }, + BVC => if !cpu.status_flags[Overflow] { cpu.branch(cpu.load(&inst.src_opr) as i8) }, + BVS => if cpu.status_flags[Overflow] { cpu.branch(cpu.load(&inst.src_opr) as i8) }, + // Jump + JMP => cpu.program_counter = cpu.load_u16(&inst.src_opr) as i16, + JSR => { + // TODO: This is likely wrong, you can't pass in a u8 just like that + cpu.stack.push((cpu.program_counter + 2) as u8); + cpu.program_counter = cpu.load_u16(&inst.src_opr) as i16; + }, + RTS => { + let pc = cpu.stack.pop().unwrap(); + cpu.program_counter = (pc + 1) as i16; + }, + BRK => { + cpu.stack.push((cpu.program_counter + 2) as u8); + cpu.stack.push(cpu.get_flags_as_byte()); + let pc_constant = 0xFFFE as u16; + cpu.program_counter = pc_constant as i16; + cpu.status_flags[Interrupt] = true; + }, + RTI => { + let flags_byte = cpu.stack.pop().unwrap(); + let pc = cpu.stack.pop().unwrap(); + cpu.set_flags_from_byte(flags_byte); + // TODO: This is likely wrong, you can't pass in a u8 just like that + cpu.program_counter = pc as i16; + cpu.status_flags[Interrupt] = false; + } + // Stack + PHA | PHP | PLA | PLP | TSX | TXS => {}, + // Something about PusH/PulL accumulator or PS + + // Flags + SEC => cpu.status_flags[Carry] = true, + SED => cpu.status_flags[Decimal] = true, + SEI => cpu.status_flags[Interrupt] = true, + + CLC => cpu.status_flags[Carry] = false, + CLD => cpu.status_flags[Decimal] = false, + CLI => cpu.status_flags[Interrupt] = false, + CLV => cpu.status_flags[Overflow] = false, + + // We can figure this out later + NOP | KIL => {}, + + // Unofficial/Illegal + AHX | ALR | ANC | ARR | AXS | DCP | ISC | LAX | LAS + | RLA | RRA | SAX | SHX | SHY | SLO | SRE | TAS | XAA => {}, + + } + +} + +pub fn decode_instructions(data: &[u8], bytes_to_read: i32) -> Vec<&InstructionDefinition>{ + let mut idx: usize = 0; + let mut instructions: Vec<&InstructionDefinition> = + Vec::with_capacity((bytes_to_read / 2) as usize); + while (idx as i32) < bytes_to_read { + let mut processed = 0; + let inst = &INSTRUCTION_DEFS[data[idx] as usize]; + instructions.push(inst); + processed += match inst.addressing { + Imp | Acc => 1, + Imm | ZPg | Rel | AbI + | ZPX | InX | InY => 2, + ZPY | AbX | AbY | Abs => 3, + }; + idx += processed + } + instructions +}