From 102c08569ab034c874b8c01ca08f5d020a85c15f Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Thu, 17 Apr 2025 00:45:41 +0800 Subject: [PATCH] First commit, still porting Odin code --- .gitignore | 1 + Cargo.lock | 7 + Cargo.toml | 6 + basic.asm | 15 + basic.bin | Bin 0 -> 9 bytes full.asm | 839 +++++++++++++++++++++++++++++++++++++++++++++++++ full.bin | Bin 0 -> 1004 bytes src/lib.rs | 1 + src/main.rs | 19 ++ src/sim6502.rs | 356 +++++++++++++++++++++ 10 files changed, 1244 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 basic.asm create mode 100644 basic.bin create mode 100644 full.asm create mode 100644 full.bin create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/sim6502.rs 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 0000000000000000000000000000000000000000..acfa1c0b2fa4111364a70ebb1e59c04055b4551c GIT binary patch literal 9 QcmZ3)uz>4D$Av!$02Rjs=>Px# literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..56a5651583b7ec2277d38c8cd5806c7a0b6c3408 GIT binary patch literal 1004 zcmZ9Ke{|Jz9LAsTy{pzX^|GQ-^rjg3(Va#kbXSN%YqN#W2-%88XoTIGYaOI<5S7kt zNj^>5N@_CAiE8=L5{l5A=+{9b6w%jr|2pS+o^#%RynmhNDd7$t;BN7#cu3sJy^P>Y zPUC#e5toPy#VKTw$^=q6ay(tc$)dVTU0Pd8B)O#{NqM0%_2q1#jP2L2ZstS2;1lt! z_)5Ib7Fzj%pF|TM@UhsyHa3f$Vux7AR@U<+pNj9qrWsdpIfYy!ZWcF)Tt=~nLmUvk zI3jj&klp;rFXE`!yYXr*rb%jb)E zq7yynDte2aB1|`iF`AKLtQaGPQb3#+m?>TnFNulEP4H%C+EC$ z;zc(;{Po|V^i$6pl7HKM&n;j6*>AN~vu9O4HE;ghHH-X}{!-s>^!*0kcXGaqwRLGp zt4pa)js(`0h4S?OdV_7V&9a@c9kO+{)%MsSJ0SCAM`XL~pmnky)>ZB3E$b-@TQ|$J zf!1F(ST;!3#|GFi8*L+HV`XDxL#@EZ+Z3B5tB_5VjkC$N*jCvx*;?5`TVb_UWwULT ztkRYg%J*Uw)4WhPDLcKqsJtjUy)bvkP`7{Pmg!2m?Dn)?7vJ>AH~)0czV80de@{JY z#DtFymvr9OVS8hCdP8>lg7o_AiRDE#e!_3`Xz-}_YW$S}=d>H^0`r|Ba%u}X&v`8& z7s{v$=TvKTS$K8XQP&iY^o=;hv-)Payyi%AUUOMZwAEUpt;LDrrf5@fQ_OiOE|%iF zjD9ZG&f(4rkWuQqoKokIpvxd%~}exmc?2Dgyc= K3Z0)bq3}Nu+CUlr literal 0 HcmV?d00001 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 +}