First commit, still porting Odin code

This commit is contained in:
Joseph Ferano 2025-04-17 00:45:41 +08:00
commit 102c08569a
10 changed files with 1244 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View File

@ -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"

6
Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "rusted-nes"
version = "0.1.0"
edition = "2024"
[dependencies]

15
basic.asm Normal file
View File

@ -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

BIN
basic.bin Normal file

Binary file not shown.

839
full.asm Normal file
View File

@ -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

BIN
full.bin Normal file

Binary file not shown.

1
src/lib.rs Normal file
View File

@ -0,0 +1 @@
pub mod sim6502;

19
src/main.rs Normal file
View File

@ -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);
}
}

356
src/sim6502.rs Normal file
View File

@ -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<usize>>::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<u8>,
stack: Vec<u8>,
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<u8>,
}
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
}