Last active
May 30, 2023 07:04
-
-
Save luckasRanarison/79d0a602401b5d58a02bb03bc30bc2fb to your computer and use it in GitHub Desktop.
6502-rs (base 2A03)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use crate::opcodes::CPU_OPCODES; | |
use self::{AddressMode::*, AsmInstr::*, StatusFlag::*}; | |
#[rustfmt::skip] | |
#[derive(Debug, Clone, Copy)] | |
pub enum AsmInstr { | |
LDA, LDX, LDY, | |
STA, STX, STY, | |
TAX, TAY, TSX, TXA, TXS, TYA, | |
PHA, PHP, PLA, PLP, | |
DEC, DEX, DEY, | |
INC, INX, INY, | |
ADC, SBC, | |
AND, EOR, ORA, | |
ASL, LSR, | |
ROL, ROR, | |
CLC, CLD, CLI, CLV, | |
SEC, SED, SEI, | |
CMP, CPX, CPY, | |
BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS, | |
JMP, JSR, RTS, | |
BRK, RTI, | |
BIT, NOP | |
} | |
#[derive(Debug, Clone, Copy)] | |
pub enum AddressMode { | |
Implied(Register), | |
Immediate, | |
Absolute, | |
AbsoluteX, | |
AbsoluteY, | |
ZeroPage, | |
ZeroPageX, | |
ZeroPageY, | |
Indirect, | |
IndirectX, | |
IndirectY, | |
Relative, | |
} | |
#[rustfmt::skip] | |
#[derive(Debug, Clone, Copy)] | |
pub enum Register { A, X, Y, SP, SR, PC } | |
#[rustfmt::skip] | |
#[derive(Debug)] | |
enum StatusFlag { C, Z, I, D, B, __, V, N } | |
#[derive(Debug)] | |
enum Address { | |
MemoryAddress(u16), | |
Register(Register), | |
} | |
#[derive(Debug)] | |
pub struct Cpu { | |
register_a: u8, | |
register_x: u8, | |
register_y: u8, | |
stack_pointer: u8, | |
status_register: u8, | |
program_counter: u16, | |
memory_map: [u8; 0xFFFF], | |
} | |
impl Cpu { | |
pub fn new() -> Self { | |
Self { | |
register_a: 0x00, | |
register_x: 0x00, | |
register_y: 0x00, | |
stack_pointer: 0xFF, | |
program_counter: 0x8000, | |
status_register: 0b0011_0000, | |
memory_map: [0x00; 0xFFFF], | |
} | |
} | |
pub fn load_program(&mut self, program: Vec<u8>) { | |
let program_rom = &mut self.memory_map[0x8000..0x8000 + &program.len()]; | |
program_rom.copy_from_slice(&program); | |
} | |
pub fn run(&mut self) { | |
loop { | |
let opcode_index = self.read_u8(self.program_counter); | |
let opcode = CPU_OPCODES | |
.get(&opcode_index) | |
.unwrap_or_else(|| panic!("Invalid opcode {:x}", opcode_index)); | |
let adr_mode = opcode.adr_mode; | |
self.program_counter += 1; | |
let prev_counter = self.program_counter; | |
match opcode.asm { | |
LDA => self.lda(adr_mode), | |
LDX => self.ldx(adr_mode), | |
LDY => self.ldy(adr_mode), | |
STA => self.sta(adr_mode), | |
STX => self.stx(adr_mode), | |
STY => self.sty(adr_mode), | |
TAX => self.tax(), | |
TAY => self.tay(), | |
TSX => self.tsx(), | |
TXA => self.txa(), | |
TXS => self.txs(), | |
TYA => self.tya(), | |
PHA => self.pha(), | |
PHP => self.php(), | |
PLA => self.pla(), | |
PLP => self.plp(), | |
DEC => self.dec(adr_mode), | |
DEX => self.dex(adr_mode), | |
DEY => self.dey(adr_mode), | |
INC => self.inc(adr_mode), | |
INX => self.inx(adr_mode), | |
INY => self.iny(adr_mode), | |
ADC => self.adc(adr_mode), | |
SBC => self.sbc(adr_mode), | |
AND => self.and(adr_mode), | |
EOR => self.eor(adr_mode), | |
ORA => self.ora(adr_mode), | |
ASL => self.asl(adr_mode), | |
LSR => self.lsr(adr_mode), | |
ROL => self.rol(adr_mode), | |
ROR => self.ror(adr_mode), | |
CLC => self.clc(), | |
CLD => self.cld(), | |
CLI => self.cli(), | |
CLV => self.clv(), | |
SEC => self.sec(), | |
SED => self.sed(), | |
SEI => self.sei(), | |
CMP => self.cmp(adr_mode), | |
CPX => self.cpx(adr_mode), | |
CPY => self.cpy(adr_mode), | |
BCC => self.bcc(), | |
BCS => self.bcs(), | |
BEQ => self.beq(), | |
BMI => self.bmi(), | |
BNE => self.bne(), | |
BPL => self.bpl(), | |
BVC => self.bvc(), | |
BVS => self.bvs(), | |
JMP => self.jmp(adr_mode), | |
JSR => self.jsr(), | |
RTS => self.rts(), | |
RTI => self.rti(), | |
BIT => self.bit(adr_mode), | |
NOP => self.nop(), | |
BRK => { | |
self.brk(); | |
break; | |
} | |
}; | |
if prev_counter == self.program_counter { | |
let counter = self.program_counter.wrapping_add(opcode.len() as u16 - 1); | |
self.program_counter = counter; | |
} | |
} | |
} | |
fn read_u8(&self, address: u16) -> u8 { | |
self.memory_map[address as usize] | |
} | |
fn read_u16(&self, address: u16) -> u16 { | |
let right = self.read_u8(address) as u16; | |
let left = self.read_u8(address + 1) as u16; | |
(left << 8) | (right) | |
} | |
fn write_u8(&mut self, data: u8, address: u16) { | |
self.memory_map[address as usize] = data; | |
} | |
fn write_u16(&mut self, data: u16, address: u16) { | |
let left = (data >> 8) as u8; | |
let right = (data & 0xFF) as u8; | |
self.write_u8(right, address); | |
self.write_u8(left, address + 1); | |
} | |
fn get_register(&mut self, register: Register) -> u8 { | |
match register { | |
Register::A => self.register_a, | |
Register::X => self.register_x, | |
Register::Y => self.register_y, | |
Register::SP => self.stack_pointer, | |
Register::SR => self.status_register, | |
Register::PC => unimplemented!(), | |
} | |
} | |
fn get_register_mut(&mut self, register: Register) -> &mut u8 { | |
match register { | |
Register::A => &mut self.register_a, | |
Register::X => &mut self.register_x, | |
Register::Y => &mut self.register_y, | |
Register::SP => &mut self.stack_pointer, | |
Register::SR => &mut self.status_register, | |
Register::PC => unimplemented!(), | |
} | |
} | |
fn set_flag(&mut self, flag: StatusFlag) { | |
self.status_register |= 1 << flag as u8; | |
} | |
fn clear_flag(&mut self, flag: StatusFlag) { | |
self.status_register &= !(1 << flag as u8); | |
} | |
fn update_negative_flag(&mut self, result: u8) { | |
match (result >> 7 & 1) == 1 { | |
true => self.set_flag(N), | |
false => self.clear_flag(N), | |
} | |
} | |
fn update_zero_flag(&mut self, result: u8) { | |
match result == 0 { | |
true => self.set_flag(Z), | |
false => self.clear_flag(Z), | |
} | |
} | |
fn update_negative_zero_flag(&mut self, result: u8) { | |
self.update_negative_flag(result); | |
self.update_zero_flag(result); | |
} | |
fn update_carry_flag(&mut self, predicate: bool) { | |
match predicate { | |
true => self.set_flag(C), | |
false => self.clear_flag(C), | |
} | |
} | |
fn update_overflow_flag(&mut self, predicate: bool) { | |
match predicate { | |
true => self.set_flag(V), | |
false => self.clear_flag(V), | |
} | |
} | |
fn get_flag(&self, flag: StatusFlag) -> u8 { | |
self.status_register >> flag as u8 & 1 | |
} | |
fn get_address(&self, mode: AddressMode) -> Address { | |
match mode { | |
Immediate => Address::MemoryAddress(self.program_counter), | |
Absolute => Address::MemoryAddress(self.read_u16(self.program_counter)), | |
AbsoluteX => { | |
let address = self.read_u16(self.program_counter); | |
Address::MemoryAddress(address.wrapping_add(self.register_x as u16)) | |
} | |
AbsoluteY => { | |
let address = self.read_u16(self.program_counter); | |
Address::MemoryAddress(address.wrapping_add(self.register_y as u16)) | |
} | |
ZeroPage => Address::MemoryAddress(self.read_u8(self.program_counter) as u16), | |
ZeroPageX => { | |
let address = self.read_u8(self.program_counter); | |
Address::MemoryAddress(address.wrapping_add(self.register_x) as u16) | |
} | |
ZeroPageY => { | |
let address = self.read_u8(self.program_counter); | |
Address::MemoryAddress(address.wrapping_add(self.register_y) as u16) | |
} | |
Indirect => { | |
let ptr_address = self.read_u16(self.program_counter); | |
let address = match ptr_address & 0x00FF == 0x00FF { | |
true => { | |
let right = self.read_u8(ptr_address) as u16; | |
let left = self.read_u8(ptr_address & 0xFF00) as u16; | |
(left << 8) | right | |
} | |
false => self.read_u16(ptr_address), | |
}; | |
Address::MemoryAddress(address) | |
} | |
IndirectX => { | |
let ptr = self.read_u8(self.program_counter); | |
let ptr = ptr.wrapping_add(self.register_x); | |
Address::MemoryAddress(self.read_u16(ptr as u16)) | |
} | |
IndirectY => { | |
let ptr = self.read_u8(self.program_counter); | |
let address = self.read_u16(ptr as u16); | |
Address::MemoryAddress(address.wrapping_add(self.register_y as u16)) | |
} | |
Relative => { | |
let offset = self.read_u8(self.program_counter); | |
let new_address = match offset > 0x7F { | |
true => self.program_counter.wrapping_sub(!offset as u16), | |
false => self | |
.program_counter | |
.wrapping_add(offset as u16) | |
.wrapping_add(1), | |
}; | |
Address::MemoryAddress(new_address) | |
} | |
Implied(register) => Address::Register(register), | |
} | |
} | |
fn get_memory_address(&self, adr_mode: AddressMode) -> Option<u16> { | |
match self.get_address(adr_mode) { | |
Address::MemoryAddress(address) => Some(address), | |
_ => None, | |
} | |
} | |
fn read_address(&mut self, address: &Address) -> u8 { | |
match &address { | |
Address::MemoryAddress(address) => self.memory_map[*address as usize], | |
Address::Register(register) => self.get_register(*register), | |
} | |
} | |
fn write_address(&mut self, data: u8, address: Address) { | |
let ptr = match address { | |
Address::MemoryAddress(address) => &mut self.memory_map[address as usize], | |
Address::Register(register) => self.get_register_mut(register), | |
}; | |
*ptr = data; | |
} | |
/* | |
MOS6502 CPU legal instructions | |
*/ | |
fn lda(&mut self, adr_mode: AddressMode) { | |
self.load(Register::A, adr_mode); | |
} | |
fn ldx(&mut self, adr_mode: AddressMode) { | |
self.load(Register::X, adr_mode); | |
} | |
fn ldy(&mut self, adr_mode: AddressMode) { | |
self.load(Register::Y, adr_mode); | |
} | |
fn sta(&mut self, adr_mode: AddressMode) { | |
self.store(Register::A, adr_mode); | |
} | |
fn stx(&mut self, adr_mode: AddressMode) { | |
self.store(Register::X, adr_mode); | |
} | |
fn sty(&mut self, adr_mode: AddressMode) { | |
self.store(Register::Y, adr_mode); | |
} | |
fn tax(&mut self) { | |
self.transfert(Register::A, Register::X); | |
} | |
fn tay(&mut self) { | |
self.transfert(Register::A, Register::Y); | |
} | |
fn tsx(&mut self) { | |
self.transfert(Register::SP, Register::X); | |
} | |
fn txa(&mut self) { | |
self.transfert(Register::X, Register::A); | |
} | |
fn txs(&mut self) { | |
self.transfert(Register::X, Register::SP); | |
} | |
fn tya(&mut self) { | |
self.transfert(Register::Y, Register::A); | |
} | |
fn pha(&mut self) { | |
self.push_stack_u8(self.register_a); | |
} | |
fn php(&mut self) { | |
self.set_flag(B); | |
self.push_stack_u8(self.status_register); | |
} | |
fn pla(&mut self) { | |
self.register_a = self.pull_stack_u8(); | |
} | |
fn plp(&mut self) { | |
let status = self.pull_stack_u8(); | |
let mask = 0b1110_1111; | |
self.status_register = (status & mask) | (self.get_flag(B) << 4); | |
} | |
fn dec(&mut self, adr_mode: AddressMode) { | |
self.decrement(adr_mode); | |
} | |
fn dex(&mut self, adr_mode: AddressMode) { | |
self.decrement(adr_mode); | |
} | |
fn dey(&mut self, adr_mode: AddressMode) { | |
self.decrement(adr_mode); | |
} | |
fn inc(&mut self, adr_mode: AddressMode) { | |
self.increment(adr_mode); | |
} | |
fn inx(&mut self, adr_mode: AddressMode) { | |
self.increment(adr_mode); | |
} | |
fn iny(&mut self, adr_mode: AddressMode) { | |
self.increment(adr_mode); | |
} | |
fn adc(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
self.add_to_register_a(value); | |
} | |
fn sbc(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
self.add_to_register_a(!value); | |
} | |
fn and(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
let result = self.register_a & value; | |
self.update_negative_zero_flag(result); | |
} | |
fn eor(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
let result = self.register_a ^ value; | |
self.update_negative_zero_flag(result); | |
} | |
fn ora(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
let result = self.register_a | value; | |
self.update_negative_zero_flag(result); | |
} | |
fn asl(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
let result = value.wrapping_shl(1); | |
self.update_carry_flag(value >> 7 == 1); | |
self.update_negative_zero_flag(result); | |
self.write_address(result, address); | |
} | |
fn lsr(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
let result = value.wrapping_shr(1); | |
self.update_carry_flag(value << 7 == 128); | |
self.update_negative_zero_flag(result); | |
self.write_address(result, address); | |
} | |
fn rol(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
let result = value.rotate_left(1); | |
self.update_carry_flag(value >> 7 == 1); | |
self.update_negative_zero_flag(result); | |
self.write_address(result, address); | |
} | |
fn ror(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
let result = value.rotate_right(1); | |
self.update_carry_flag(value << 7 == 128); | |
self.update_negative_zero_flag(result); | |
self.write_address(result, address); | |
} | |
fn clc(&mut self) { | |
self.clear_flag(C); | |
} | |
fn cld(&mut self) { | |
self.clear_flag(D); | |
} | |
fn cli(&mut self) { | |
self.clear_flag(I); | |
} | |
fn clv(&mut self) { | |
self.clear_flag(V); | |
} | |
fn sec(&mut self) { | |
self.set_flag(C); | |
} | |
fn sed(&mut self) { | |
self.set_flag(D); | |
} | |
fn sei(&mut self) { | |
self.set_flag(I); | |
} | |
fn cmp(&mut self, adr_mode: AddressMode) { | |
self.compare(Register::A, adr_mode); | |
} | |
fn cpx(&mut self, adr_mode: AddressMode) { | |
self.compare(Register::X, adr_mode); | |
} | |
fn cpy(&mut self, adr_mode: AddressMode) { | |
self.compare(Register::Y, adr_mode); | |
} | |
fn bcc(&mut self) { | |
self.branch(self.get_flag(C) == 0); | |
} | |
fn bcs(&mut self) { | |
self.branch(self.get_flag(C) == 1); | |
} | |
fn beq(&mut self) { | |
self.branch(self.get_flag(Z) == 1); | |
} | |
fn bmi(&mut self) { | |
self.branch(self.get_flag(N) == 1); | |
} | |
fn bne(&mut self) { | |
self.branch(self.get_flag(Z) == 0); | |
} | |
fn bpl(&mut self) { | |
self.branch(self.get_flag(N) == 0); | |
} | |
fn bvc(&mut self) { | |
self.branch(self.get_flag(V) == 0); | |
} | |
fn bvs(&mut self) { | |
self.branch(self.get_flag(V) == 1); | |
} | |
fn jmp(&mut self, adr_mode: AddressMode) { | |
self.program_counter = self.get_memory_address(adr_mode).unwrap(); | |
} | |
fn jsr(&mut self) { | |
self.push_stack_u16(self.program_counter + 1); | |
self.program_counter = self.get_memory_address(AddressMode::Absolute).unwrap(); | |
} | |
fn rts(&mut self) { | |
self.program_counter = self.pull_stack_u16() + 1; | |
} | |
fn brk(&mut self) { | |
self.set_flag(I); | |
self.set_flag(B); | |
self.push_stack_u16(self.program_counter + 1); | |
self.push_stack_u8(self.status_register); | |
} | |
fn rti(&mut self) { | |
self.plp(); | |
self.program_counter = self.pull_stack_u16(); | |
} | |
fn bit(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
let result = self.register_a & value; | |
self.update_zero_flag(result); | |
self.update_negative_flag(value); | |
self.update_overflow_flag((value >> 6 & 1) == 1); | |
} | |
fn nop(&self) {} | |
/* | |
Helper functions | |
*/ | |
fn load(&mut self, register: Register, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
self.write_address(value, Address::Register(register)); | |
self.update_negative_zero_flag(value); | |
} | |
fn store(&mut self, register: Register, adr_mode: AddressMode) { | |
let value = self.get_register(register); | |
let address = self.get_address(adr_mode); | |
self.write_address(value, address); | |
} | |
fn transfert(&mut self, register_a: Register, register_b: Register) { | |
let value = self.get_register(register_a); | |
let ptr = self.get_register_mut(register_b); | |
*ptr = value; | |
self.update_negative_zero_flag(value); | |
} | |
fn decrement(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
let value = value.wrapping_sub(1); | |
self.write_address(value, address); | |
self.update_negative_zero_flag(value); | |
} | |
fn increment(&mut self, adr_mode: AddressMode) { | |
let address = self.get_address(adr_mode); | |
let value = self.read_address(&address); | |
let value = value.wrapping_add(1); | |
self.write_address(value, address); | |
self.update_negative_zero_flag(value); | |
} | |
fn add_to_register_a(&mut self, value: u8) { | |
let carry = self.get_flag(C); | |
let (sum, c1) = self.register_a.overflowing_add(value); | |
let (sum, c2) = sum.overflowing_add(carry); | |
let overflow = (self.register_a ^ sum) & (value ^ sum) >> 7 != 0; | |
self.update_carry_flag(c1 || c2); | |
self.update_overflow_flag(overflow); | |
self.update_negative_zero_flag(sum); | |
self.register_a = sum; | |
} | |
fn push_stack_u8(&mut self, data: u8) { | |
self.write_u8(data, self.stack_pointer as u16 + 0x100); | |
self.stack_pointer = self.stack_pointer.wrapping_sub(1); | |
} | |
fn push_stack_u16(&mut self, data: u16) { | |
let left = (data >> 8) as u8; | |
let right = (data & 0xFF) as u8; | |
self.push_stack_u8(left); | |
self.push_stack_u8(right); | |
} | |
fn pull_stack_u8(&mut self) -> u8 { | |
self.stack_pointer = self.stack_pointer.wrapping_add(1); | |
self.read_u8(self.stack_pointer as u16 + 0x100) | |
} | |
fn pull_stack_u16(&mut self) -> u16 { | |
let right = self.pull_stack_u8() as u16; | |
let left = self.pull_stack_u8() as u16; | |
(left << 8) | (right) | |
} | |
fn compare(&mut self, register: Register, adr_mode: AddressMode) { | |
let value_a = self.get_register(register); | |
let address_b = self.get_address(adr_mode); | |
let value_b = self.read_address(&address_b); | |
let (sum, c1) = value_a.overflowing_add(!value_b); | |
let (sum, c2) = sum.overflowing_add(1); | |
self.update_carry_flag(c1 || c2); | |
self.update_negative_zero_flag(sum); | |
} | |
fn branch(&mut self, predicate: bool) { | |
if predicate { | |
self.program_counter = self.get_memory_address(AddressMode::Relative).unwrap(); | |
} | |
} | |
} | |
#[cfg(test)] | |
mod test { | |
use crate::cpu::StatusFlag::*; | |
use super::Cpu; | |
#[test] | |
fn test_status_flag() { | |
let mut cpu = Cpu::new(); | |
cpu.set_flag(C); | |
cpu.set_flag(N); | |
cpu.set_flag(V); | |
assert_eq!(cpu.status_register, 0b1111_0001); | |
cpu.clear_flag(V); | |
assert_eq!(cpu.status_register, 0b1011_0001); | |
} | |
#[test] | |
fn test_immediate_address() { | |
let mut cpu = Cpu::new(); | |
cpu.load_program(vec![0xA9, 0x0F]); | |
cpu.run(); | |
assert_eq!(cpu.register_a, 0x0F); | |
} | |
#[test] | |
fn test_absolute_address() { | |
let mut cpu = Cpu::new(); | |
cpu.write_u16(0x0F, 0x8030); | |
cpu.load_program(vec![0xAD, 0x30, 0x80]); | |
cpu.run(); | |
assert_eq!(cpu.register_a, 0x0F); | |
} | |
#[test] | |
fn test_zero_page_address() { | |
let mut cpu = Cpu::new(); | |
cpu.write_u8(0x0F, 0x30); | |
cpu.load_program(vec![0xA5, 0x30]); | |
cpu.run(); | |
assert_eq!(cpu.register_a, 0x0F); | |
} | |
#[test] | |
fn test_indirect_address_x() { | |
let mut cpu = Cpu::new(); | |
cpu.register_x = 0x05; | |
cpu.write_u16(0x23, 0x0075); | |
cpu.write_u16(0x30, 0x0076); | |
cpu.write_u16(0xA5, 0x3023); | |
cpu.load_program(vec![0xA1, 0x70]); | |
cpu.run(); | |
assert_eq!(cpu.register_a, 0xA5); | |
} | |
#[test] | |
fn test_indirect_address_y() { | |
let mut cpu = Cpu::new(); | |
cpu.register_y = 0x10; | |
cpu.write_u16(0x43, 0x0070); | |
cpu.write_u16(0x35, 0x0071); | |
cpu.write_u16(0x23, 0x3553); | |
cpu.load_program(vec![0xB1, 0x70]); | |
cpu.run(); | |
assert_eq!(cpu.register_a, 0x23); | |
} | |
#[test] | |
fn test_relative_address_pos() { | |
let mut cpu = Cpu::new(); | |
cpu.load_program(vec![0xA9, 0xFF, 0x69, 0x01, 0xB0, 0x02, 0xA9, 0x01]); | |
cpu.run(); | |
assert_eq!(cpu.program_counter, 0x8008 + 1); // BRK increments PC | |
} | |
#[test] | |
fn test_relative_address_neg() { | |
let mut cpu = Cpu::new(); | |
cpu.load_program(vec![0xA9, 0xFF, 0x69, 0x01, 0xB0, 0xFC, 0xA9, 0x01]); | |
cpu.run(); | |
assert_eq!(cpu.get_flag(C), 0); | |
} | |
#[test] | |
fn test_indirect_address() { | |
let mut cpu = Cpu::new(); | |
cpu.load_program(vec![ | |
0xA9, 0xC4, 0x8D, 0x82, 0xFF, 0xA9, 0x80, 0x8D, 0x83, 0xFF, 0x6C, 0x82, 0xFF, | |
]); | |
cpu.run(); | |
assert_eq!(cpu.program_counter, 0x80C4 + 1); | |
} | |
#[test] | |
fn test_indirect_address_boundary_page() { | |
let mut cpu = Cpu::new(); | |
cpu.load_program(vec![ | |
0xA9, 0x50, 0x8D, 0x00, 0x30, 0xA9, 0x80, 0x8D, 0xFF, 0x30, 0xA9, 0x40, 0x8D, 0x00, | |
0x31, 0x6C, 0xFF, 0x30, | |
]); | |
cpu.run(); | |
assert_eq!(cpu.program_counter, 0x5080 + 1); | |
} | |
#[test] | |
fn test_inx() { | |
let mut cpu = Cpu::new(); | |
cpu.load_program(vec![0xA2, 0x01, 0xE8]); | |
cpu.run(); | |
assert_eq!(cpu.register_x, 2); | |
} | |
#[test] | |
fn test_adc() { | |
let mut cpu = Cpu::new(); | |
cpu.load_program(vec![0xA9, 0xFF, 0x69, 0x01, 0x69, 0x00, 0x69, 0x7F]); | |
cpu.run(); | |
assert_eq!(cpu.register_a, 0x80); | |
assert_eq!(cpu.get_flag(N), 1); | |
assert_eq!(cpu.get_flag(C), 0); | |
assert_eq!(cpu.get_flag(V), 1); | |
} | |
#[test] | |
fn test_sbc() { | |
let mut cpu = Cpu::new(); | |
cpu.load_program(vec![0xA9, 0x05, 0xE9, 0x03]); | |
cpu.run(); | |
assert_eq!(cpu.register_a, 0x01); | |
} | |
#[test] | |
fn test_cmp() { | |
let mut cpu = Cpu::new(); | |
cpu.load_program(vec![0xA9, 0x05, 0xC9, 0x05]); | |
cpu.run(); | |
assert_eq!(cpu.get_flag(N), 0); | |
assert_eq!(cpu.get_flag(Z), 1); | |
assert_eq!(cpu.get_flag(C), 1); | |
} | |
#[test] | |
fn test_jsr_rts() { | |
let mut cpu = Cpu::new(); | |
cpu.load_program(vec![ | |
0xA9, 0x01, 0x20, 0x08, 0x80, 0x69, 0x01, 0x00, 0x69, 0x01, 0x60, | |
]); | |
cpu.run(); | |
assert_eq!(cpu.register_a, 3); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use lazy_static::lazy_static; | |
use std::collections::HashMap; | |
use crate::cpu::{ | |
AddressMode::{self, *}, | |
AsmInstr::{self, *}, | |
Register::*, | |
}; | |
#[derive(Debug, Clone, Copy)] | |
pub struct Opcode { | |
pub asm: AsmInstr, | |
pub adr_mode: AddressMode, | |
pub cycle: u8, | |
} | |
impl Opcode { | |
pub fn len(&self) -> u8 { | |
match self.adr_mode { | |
Implied(_) => 1, | |
Immediate | ZeroPage | ZeroPageX | ZeroPageY | IndirectX | IndirectY | Relative => 2, | |
Absolute | AbsoluteX | AbsoluteY | Indirect => 3, | |
} | |
} | |
fn new(asm: AsmInstr, adr_mode: AddressMode, cycle: u8) -> Self { | |
Self { | |
asm, | |
adr_mode, | |
cycle, | |
} | |
} | |
} | |
lazy_static! { | |
#[rustfmt::skip] | |
pub static ref CPU_OPCODES: HashMap<u8, Opcode> = HashMap::from([ | |
(0xA9, Opcode::new(LDA, Immediate, 2)), | |
(0xA5, Opcode::new(LDA, ZeroPage, 3)), | |
(0xB5, Opcode::new(LDA, ZeroPageX, 4)), | |
(0xAD, Opcode::new(LDA, Absolute, 4)), | |
(0xBD, Opcode::new(LDA, AbsoluteX, 4)), | |
(0xB9, Opcode::new(LDA, AbsoluteY, 4)), | |
(0xA1, Opcode::new(LDA, IndirectX, 6)), | |
(0xB1, Opcode::new(LDA, IndirectY, 5)), | |
(0xA2, Opcode::new(LDX, Immediate, 2)), | |
(0xA6, Opcode::new(LDX, ZeroPage, 3)), | |
(0xB6, Opcode::new(LDX, ZeroPageY, 4)), | |
(0xAE, Opcode::new(LDX, Absolute, 4)), | |
(0xBE, Opcode::new(LDX, AbsoluteY, 4)), | |
(0xA0, Opcode::new(LDY, Immediate, 2)), | |
(0xA4, Opcode::new(LDY, ZeroPage, 3)), | |
(0xB4, Opcode::new(LDY, ZeroPageX, 4)), | |
(0xAC, Opcode::new(LDY, Absolute, 4)), | |
(0xBC, Opcode::new(LDY, AbsoluteX, 4)), | |
(0x85, Opcode::new(STA, ZeroPage, 3)), | |
(0x95, Opcode::new(STA, ZeroPageX, 4)), | |
(0x8D, Opcode::new(STA, Absolute, 4)), | |
(0x9D, Opcode::new(STA, AbsoluteX, 5)), | |
(0x99, Opcode::new(STA, AbsoluteY, 5)), | |
(0x81, Opcode::new(STA, IndirectX, 6)), | |
(0x91, Opcode::new(STA, IndirectY, 6)), | |
(0x86, Opcode::new(STX, ZeroPage, 3)), | |
(0x96, Opcode::new(STX, ZeroPageY, 4)), | |
(0x8E, Opcode::new(STX, Absolute, 4)), | |
(0x84, Opcode::new(STY, ZeroPage, 3)), | |
(0x94, Opcode::new(STY, ZeroPageX, 4)), | |
(0x8C, Opcode::new(STY, Absolute, 4)), | |
(0xAA, Opcode::new(TAX, Implied(X), 2)), | |
(0xA8, Opcode::new(TAY, Implied(Y), 2)), | |
(0xBA, Opcode::new(TSX, Implied(X), 2)), | |
(0x8A, Opcode::new(TXA, Implied(A), 2)), | |
(0x9A, Opcode::new(TXS, Implied(SP), 2)), | |
(0x98, Opcode::new(TYA, Implied(A), 2)), | |
(0xC6, Opcode::new(DEC, ZeroPage, 5)), | |
(0xD6, Opcode::new(DEC, ZeroPageX, 6)), | |
(0xCE, Opcode::new(DEC, Absolute, 6)), | |
(0xDE, Opcode::new(DEC, AbsoluteX, 7)), | |
(0xCA, Opcode::new(DEX, Implied(X), 2)), | |
(0x88, Opcode::new(DEY, Implied(Y), 2)), | |
(0xE6, Opcode::new(INC, ZeroPage, 5)), | |
(0xF6, Opcode::new(INC, ZeroPageX, 6)), | |
(0xEE, Opcode::new(INC, Absolute, 6)), | |
(0xFE, Opcode::new(INC, AbsoluteX, 7)), | |
(0xE8, Opcode::new(INX, Implied(X), 2)), | |
(0xC8, Opcode::new(INY, Implied(Y), 2)), | |
(0x48, Opcode::new(PHA, Implied(A), 3)), | |
(0x08, Opcode::new(PHP, Implied(SR), 3)), | |
(0x68, Opcode::new(PLA, Implied(A), 4)), | |
(0x28, Opcode::new(PLP, Implied(SR), 4)), | |
(0x69, Opcode::new(ADC, Immediate, 2)), | |
(0x65, Opcode::new(ADC, ZeroPage, 3)), | |
(0x75, Opcode::new(ADC, ZeroPageX, 4)), | |
(0x6D, Opcode::new(ADC, Absolute, 4)), | |
(0x7D, Opcode::new(ADC, AbsoluteX, 4)), | |
(0x79, Opcode::new(ADC, AbsoluteY, 4)), | |
(0x61, Opcode::new(ADC, IndirectX, 6)), | |
(0x71, Opcode::new(ADC, IndirectY, 5)), | |
(0xE9, Opcode::new(SBC, Immediate, 2)), | |
(0xE5, Opcode::new(SBC, ZeroPage, 3)), | |
(0xF5, Opcode::new(SBC, ZeroPageX, 4)), | |
(0xED, Opcode::new(SBC, Absolute, 4)), | |
(0xFD, Opcode::new(SBC, AbsoluteX, 4)), | |
(0xF9, Opcode::new(SBC, AbsoluteY, 4)), | |
(0xE1, Opcode::new(SBC, IndirectX, 6)), | |
(0xF1, Opcode::new(SBC, IndirectY, 5)), | |
(0x29, Opcode::new(AND, Immediate, 2)), | |
(0x25, Opcode::new(AND, ZeroPage, 3)), | |
(0x35, Opcode::new(AND, ZeroPageX, 4)), | |
(0x2D, Opcode::new(AND, Absolute, 4)), | |
(0x3D, Opcode::new(AND, AbsoluteX, 4)), | |
(0x39, Opcode::new(AND, AbsoluteY, 4)), | |
(0x21, Opcode::new(AND, IndirectX, 6)), | |
(0x31, Opcode::new(AND, IndirectY, 5)), | |
(0x49, Opcode::new(EOR, Immediate, 2)), | |
(0x45, Opcode::new(EOR, ZeroPage, 3)), | |
(0x55, Opcode::new(EOR, ZeroPageX, 4)), | |
(0x4D, Opcode::new(EOR, Absolute, 4)), | |
(0x5D, Opcode::new(EOR, AbsoluteX, 4)), | |
(0x59, Opcode::new(EOR, AbsoluteY, 4)), | |
(0x41, Opcode::new(EOR, IndirectX, 6)), | |
(0x51, Opcode::new(EOR, IndirectY, 5)), | |
(0x09, Opcode::new(ORA, Immediate, 2)), | |
(0x05, Opcode::new(ORA, ZeroPage, 3)), | |
(0x15, Opcode::new(ORA, ZeroPageX, 4)), | |
(0x0D, Opcode::new(ORA, Absolute, 4)), | |
(0x1D, Opcode::new(ORA, AbsoluteX, 4)), | |
(0x19, Opcode::new(ORA, AbsoluteY, 4)), | |
(0x01, Opcode::new(ORA, IndirectX, 6)), | |
(0x11, Opcode::new(ORA, IndirectY, 5)), | |
(0x0A, Opcode::new(ASL, Implied(A), 2)), | |
(0x06, Opcode::new(ASL, ZeroPage, 5)), | |
(0x16, Opcode::new(ASL, ZeroPageX, 6)), | |
(0x0E, Opcode::new(ASL, Absolute, 6)), | |
(0x1E, Opcode::new(ASL, AbsoluteX, 7)), | |
(0x4A, Opcode::new(LSR, Implied(A), 2)), | |
(0x46, Opcode::new(LSR, ZeroPage, 5)), | |
(0x56, Opcode::new(LSR, ZeroPageX, 6)), | |
(0x4E, Opcode::new(LSR, Absolute, 6)), | |
(0x5E, Opcode::new(LSR, AbsoluteX, 7)), | |
(0x2A, Opcode::new(ROL, Implied(A), 2)), | |
(0x26, Opcode::new(ROL, ZeroPage, 5)), | |
(0x36, Opcode::new(ROL, ZeroPageX, 6)), | |
(0x2E, Opcode::new(ROL, Absolute, 6)), | |
(0x3E, Opcode::new(ROL, AbsoluteX, 7)), | |
(0x6A, Opcode::new(ROR, Implied(A), 2)), | |
(0x66, Opcode::new(ROR, ZeroPage, 5)), | |
(0x76, Opcode::new(ROR, ZeroPageX, 6)), | |
(0x6E, Opcode::new(ROR, Absolute, 6)), | |
(0x7E, Opcode::new(ROR, AbsoluteX, 7)), | |
(0x18, Opcode::new(CLC, Implied(SR), 2)), | |
(0xD8, Opcode::new(CLD, Implied(SR), 2)), | |
(0x58, Opcode::new(CLI, Implied(SR), 2)), | |
(0xB8, Opcode::new(CLV, Implied(SR), 2)), | |
(0x38, Opcode::new(SEC, Implied(SR), 2)), | |
(0xF8, Opcode::new(SED, Implied(SR), 2)), | |
(0x78, Opcode::new(SEI, Implied(SR), 2)), | |
(0xC9, Opcode::new(CMP, Immediate, 2)), | |
(0xC5, Opcode::new(CMP, ZeroPage, 3)), | |
(0xD5, Opcode::new(CMP, ZeroPageX, 4)), | |
(0xCD, Opcode::new(CMP, Absolute, 4)), | |
(0xDD, Opcode::new(CMP, AbsoluteX, 4)), | |
(0xD9, Opcode::new(CMP, AbsoluteY, 4)), | |
(0xC1, Opcode::new(CMP, IndirectX, 6)), | |
(0xD1, Opcode::new(CMP, IndirectY, 5)), | |
(0xE0, Opcode::new(CPX, Immediate, 2)), | |
(0xE4, Opcode::new(CPX, ZeroPage, 3)), | |
(0xEC, Opcode::new(CPX, Absolute, 4)), | |
(0xC0, Opcode::new(CPY, Immediate, 2)), | |
(0xC4, Opcode::new(CPY, ZeroPage, 3)), | |
(0xCC, Opcode::new(CPY, Absolute, 4)), | |
(0x90, Opcode::new(BCC, Relative, 2)), | |
(0xB0, Opcode::new(BCS, Relative, 2)), | |
(0xF0, Opcode::new(BEQ, Relative, 2)), | |
(0x30, Opcode::new(BMI, Relative, 2)), | |
(0xD0, Opcode::new(BNE, Relative, 2)), | |
(0x10, Opcode::new(BPL, Relative, 2)), | |
(0x50, Opcode::new(BVC, Relative, 2)), | |
(0x70, Opcode::new(BVS, Relative, 2)), | |
(0x4C, Opcode::new(JMP, Absolute, 3)), | |
(0x6C, Opcode::new(JMP, Indirect, 5)), | |
(0x20, Opcode::new(JSR, Absolute, 6)), | |
(0x60, Opcode::new(RTS, Implied(PC), 6)), | |
(0x00, Opcode::new(BRK, Implied(PC), 7)), | |
(0x40, Opcode::new(RTI, Implied(PC), 6)), | |
(0x24, Opcode::new(BIT, ZeroPage, 3)), | |
(0x2C, Opcode::new(BIT, Absolute, 4)), | |
(0xEA, Opcode::new(NOP, Implied(PC), 2)), | |
]); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment