532 lines
18 KiB
Rust
532 lines
18 KiB
Rust
use crate::address_mode::AddressMode;
|
|
use crate::constants::constants_system::{OFFSET_INT_VECTOR, OFFSET_RESET_VECTOR, SIZE_64KB};
|
|
use crate::instruction::Instruction;
|
|
use crate::mos6502flags::{Mos6502Flag, Mos6502Flags};
|
|
use crate::mos6502flags::Mos6502Flag::{Carry, Decimal, Interrupt, Overflow};
|
|
use crate::op_info::OpInfo;
|
|
use crate::operand::Operand;
|
|
use crate::operation::Operation;
|
|
|
|
pub struct Mos6502Cpu {
|
|
// this is public for rendering quickly.
|
|
pub memory: Box<[u8]>,
|
|
a: u8,
|
|
x: u8,
|
|
y: u8,
|
|
flags: Mos6502Flags,
|
|
pc: u16,
|
|
s: u8,
|
|
pub microcode_step: u8,
|
|
pub address_bus: u16,
|
|
pub data_bus: u8,
|
|
ir: Instruction, // Instruction Register
|
|
oi: OpInfo,
|
|
has_reset: bool,
|
|
iv: u16 // Interrupt Vector
|
|
}
|
|
|
|
impl Default for Mos6502Cpu {
|
|
fn default() -> Self {
|
|
let vec = vec![0x00; SIZE_64KB];
|
|
let boxed_slize: Box<[u8]> = vec.into_boxed_slice();
|
|
let boxed_array: Box<[u8; SIZE_64KB]> = boxed_slize.try_into().expect("Failed to allocate system memory");
|
|
|
|
let mut working = Mos6502Cpu {
|
|
memory: boxed_array,
|
|
a: 0,
|
|
x: 0,
|
|
y: 0,
|
|
flags: Default::default(),
|
|
pc: 0xfffd,
|
|
s: 0,
|
|
microcode_step: 0,
|
|
address_bus: 0,
|
|
data_bus: 0,
|
|
ir: Instruction {
|
|
op: Operation::NOP,
|
|
mode: AddressMode::Implied,
|
|
operand: Operand::None,
|
|
},
|
|
oi: OpInfo {
|
|
operation: Operation::NOP,
|
|
mode: AddressMode::Implied,
|
|
length: 1,
|
|
cycles: 2,
|
|
},
|
|
has_reset: false,
|
|
iv: 0xfffe
|
|
};
|
|
working.reset_cpu();
|
|
working
|
|
}
|
|
}
|
|
|
|
impl Mos6502Cpu {
|
|
pub fn new() -> Mos6502Cpu {
|
|
let vec = vec![0x00; SIZE_64KB];
|
|
let boxed_slize: Box<[u8]> = vec.into_boxed_slice();
|
|
let boxed_array: Box<[u8; SIZE_64KB]> = boxed_slize.try_into().expect("Failed to allocate system memory");
|
|
let mut working = Mos6502Cpu {
|
|
memory: boxed_array,
|
|
a: 0,
|
|
x: 0,
|
|
y: 0,
|
|
flags: Mos6502Flags::default(),
|
|
pc: 0,
|
|
s: 0xfd,
|
|
microcode_step: 0,
|
|
address_bus: 0x0000,
|
|
data_bus: 0x00,
|
|
ir: Instruction {
|
|
op: Operation::NOP,
|
|
mode: AddressMode::Implied,
|
|
operand: Operand::None,
|
|
},
|
|
oi: OpInfo {
|
|
operation: Operation::NOP,
|
|
mode: AddressMode::Implied,
|
|
length: 1,
|
|
cycles: 2,
|
|
},
|
|
has_reset: false,
|
|
iv: 0xfffe
|
|
};
|
|
working.reset_cpu();
|
|
working
|
|
}
|
|
|
|
fn reset_cpu(&mut self) {
|
|
// self = &mut Mos6502Cpu::default();
|
|
println!("Should tick 7 times.");
|
|
// read the value at 0xfffc 0xfffd for our reset vector.
|
|
// read the value at 0xfffe 0xffff for our int vector
|
|
self.pc = self.read_word(&OFFSET_RESET_VECTOR);
|
|
self.iv = self.read_word(&OFFSET_INT_VECTOR);
|
|
println!("PC and IV are now set from ROM addresses");
|
|
}
|
|
|
|
fn read_word(&self, offset: &u16) -> u16 {
|
|
let low = self.memory[*offset as usize];
|
|
let high = self.memory[*offset as usize + 1];
|
|
(high as u16) << 8 | low as u16
|
|
}
|
|
|
|
pub fn peek_flag(&self, flag_to_read: Mos6502Flag) -> bool {
|
|
self.flags.flag(flag_to_read)
|
|
}
|
|
pub fn poke_flag(&mut self, flag_to_set: Mos6502Flag, new_value: bool) {
|
|
if new_value { self.flags.set_flag(flag_to_set) } else { self.flags.clear_flag(flag_to_set) }
|
|
}
|
|
|
|
pub fn peek(&self, offset: u16) -> u8 {
|
|
self.memory[offset as usize]
|
|
}
|
|
|
|
pub fn poke(&mut self, offset: u16, value: u8) {
|
|
self.memory[offset as usize] = value
|
|
}
|
|
|
|
pub fn peek_a(&self) -> u8 {
|
|
self.a
|
|
}
|
|
pub fn poke_a(&mut self, new_a: u8) {
|
|
self.a = new_a;
|
|
}
|
|
|
|
pub fn peek_x(&self) -> u8 {
|
|
self.x
|
|
}
|
|
|
|
pub fn poke_x(&mut self, new_x: u8) {
|
|
println!("Updating register X from [{}] to [{}]", self.x, new_x);
|
|
self.x = new_x
|
|
}
|
|
|
|
pub fn peek_y(&self) -> u8 {
|
|
self.y
|
|
}
|
|
|
|
pub fn poke_y(&mut self, new_y: u8) {
|
|
self.y = new_y
|
|
}
|
|
|
|
fn advance_pc(&mut self, how_far: u16) {
|
|
self.pc += how_far;
|
|
}
|
|
|
|
fn set_pc_to(&mut self, new_pc: u16) {
|
|
self.pc = new_pc;
|
|
}
|
|
|
|
/// Ticks the CPU
|
|
/// Returns
|
|
/// AddressBus, DataBus, RW flag
|
|
pub fn tick(&mut self) -> (u16, u8, bool) {
|
|
println!("PREPARiNG TO TICK CPU AT PC 0x{:04x}", self.pc);
|
|
if self.microcode_step == 0 {
|
|
println!("OUT OF MICROSTEPS. Decoding the next instruction");
|
|
let offset = self.pc as usize;
|
|
// TODO: this calls opinfo 2x
|
|
self.oi = Instruction::opinfo(&self.memory[offset..offset + 4]).unwrap();
|
|
self.ir = Instruction::decode(&self.memory[offset..offset + 4]).unwrap();
|
|
self.microcode_step = self.oi.cycles;
|
|
println!("Decoded [[{:?}]]", self.ir);
|
|
self.advance_pc(self.oi.length as u16);
|
|
// load the microstep buffer with what steps to run
|
|
// set the counter to the number of steps left
|
|
} else {
|
|
// run 1 microcode step
|
|
println!("Microstep {} for {:?}", self.microcode_step, self.ir.op);
|
|
match self.ir.op {
|
|
Operation::ADC => {
|
|
}
|
|
Operation::AND => {}
|
|
Operation::ASL => {}
|
|
Operation::BCC => {}
|
|
Operation::BCS => {}
|
|
Operation::BEQ => {}
|
|
Operation::BIT => {}
|
|
Operation::BMI => {}
|
|
Operation::BNE => {}
|
|
Operation::BPL => {}
|
|
Operation::BRK => {}
|
|
Operation::BVC => {}
|
|
Operation::BVS => {}
|
|
Operation::CLC => {
|
|
self.flags.clear_flag(Carry);
|
|
}
|
|
Operation::CLD => {
|
|
self.flags.clear_flag(Decimal);
|
|
}
|
|
Operation::CLI => {
|
|
self.flags.clear_flag(Interrupt);
|
|
}
|
|
Operation::CLV => {
|
|
self.flags.clear_flag(Overflow);
|
|
}
|
|
Operation::CMP => {}
|
|
Operation::CPX => {}
|
|
Operation::CPY => {}
|
|
Operation::DEC => {}
|
|
Operation::DEX => {
|
|
if self.microcode_step == 1 {
|
|
let (new_x, new_carry) = self.x.overflowing_sub(1);
|
|
self.poke_x(new_x);
|
|
self.poke_flag(Carry, new_carry);
|
|
}
|
|
}
|
|
Operation::DEY => {
|
|
if self.microcode_step == 1 {
|
|
(self.y, _) = self.y.overflowing_sub(1);
|
|
}
|
|
}
|
|
Operation::EOR => { }
|
|
Operation::INC => { }
|
|
Operation::INX => {
|
|
if self.microcode_step == 1 {
|
|
let (new_x, new_carry) = self.x.overflowing_add(1);
|
|
self.poke_x(new_x);
|
|
self.poke_flag(Carry, new_carry);
|
|
}
|
|
}
|
|
Operation::INY => {
|
|
if self.microcode_step == 1 {
|
|
let (new_y, new_carry) = self.y.overflowing_add(1);
|
|
self.poke_y(new_y);
|
|
self.poke_flag(Carry, new_carry);
|
|
} }
|
|
Operation::JMP => {
|
|
match self.ir.operand {
|
|
Operand::Word(offset) => {
|
|
self.pc = offset;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
Operation::JSR => {}
|
|
Operation::LDA => {
|
|
match self.oi.mode {
|
|
AddressMode::Immediate => {
|
|
match self.ir.operand {
|
|
Operand::Byte(value) => {
|
|
println!("Loading 0x{value:02x} ({value}) into A");
|
|
self.a = value;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
AddressMode::ZeroPage => {
|
|
match self.ir.operand {
|
|
Operand::Byte(value) => {
|
|
println!("Loading from zero page at 0x{value:02x} ({value})");
|
|
self.a = self.memory[value as usize];
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
AddressMode::ZeroPageX => {}
|
|
AddressMode::ZeroPageY => {}
|
|
AddressMode::Absolute => {}
|
|
AddressMode::AbsoluteX => {}
|
|
AddressMode::AbsoluteY => {}
|
|
AddressMode::Indirect => {}
|
|
AddressMode::IndirectX => {}
|
|
AddressMode::IndirectY => {}
|
|
_ => {
|
|
println!("INVALID ADDRESS MODE FOR LDA");
|
|
}
|
|
}
|
|
}
|
|
Operation::LDX => {}
|
|
Operation::LDY => {}
|
|
Operation::LSR => {}
|
|
Operation::NOP => {
|
|
// do nothing.
|
|
}
|
|
Operation::ORA => {}
|
|
Operation::PHA => {}
|
|
Operation::PHP => {}
|
|
Operation::PLA => {}
|
|
Operation::PLP => {}
|
|
Operation::ROL => {
|
|
if self.microcode_step == 1 {
|
|
self.a = self.a.rotate_left(1);
|
|
}
|
|
}
|
|
Operation::ROR => {
|
|
// rotate A
|
|
if self.microcode_step == 1 {
|
|
self.a = self.a.rotate_right(1);
|
|
}
|
|
}
|
|
Operation::RTI => {}
|
|
Operation::RTS => {}
|
|
Operation::SBC => {}
|
|
Operation::SEC => {
|
|
self.flags.set_flag(Carry);
|
|
}
|
|
Operation::SED => {
|
|
self.flags.set_flag(Decimal);
|
|
}
|
|
Operation::SEI => {
|
|
self.flags.set_flag(Interrupt);
|
|
}
|
|
Operation::STA => {
|
|
match self.oi.mode {
|
|
AddressMode::ZeroPage => {
|
|
// write to the zero page.
|
|
match self.ir.operand {
|
|
Operand::Byte(target) => {
|
|
self.memory[target as usize] = self.a;
|
|
}
|
|
_ => {
|
|
// Invalid parameter
|
|
}
|
|
}
|
|
}
|
|
AddressMode::ZeroPageX => {
|
|
match self.ir.operand {
|
|
Operand::Byte(target) => {
|
|
let x = self.x;
|
|
self.memory[(x + target) as usize] = self.a;
|
|
}
|
|
_ => {
|
|
// Invalid Parameter
|
|
}
|
|
}
|
|
}
|
|
AddressMode::Absolute => {
|
|
// write from A to the specified memory location
|
|
match self.ir.operand {
|
|
Operand::Word(offset) => {
|
|
self.memory[offset as usize] = self.a;
|
|
}
|
|
_ => {
|
|
// Invalid Parameter
|
|
}
|
|
}
|
|
}
|
|
AddressMode::AbsoluteX => {
|
|
match self.ir.operand {
|
|
Operand::Word(offset) => {
|
|
self.memory[(offset + self.x as u16) as usize] = self.a;
|
|
}
|
|
_ => {
|
|
// Invalid Parameter
|
|
}
|
|
}
|
|
}
|
|
AddressMode::AbsoluteY => {}
|
|
AddressMode::IndirectX => {}
|
|
AddressMode::IndirectY => {}
|
|
_ => {
|
|
// invalid memory mode
|
|
}
|
|
}
|
|
}
|
|
Operation::STX => {}
|
|
Operation::STY => {}
|
|
Operation::TAX => {
|
|
self.x = self.a;
|
|
}
|
|
Operation::TAY => {
|
|
self.y = self.a;
|
|
}
|
|
Operation::TSX => {
|
|
}
|
|
Operation::TXA => {
|
|
self.a = self.x;
|
|
}
|
|
Operation::TXS => {
|
|
}
|
|
Operation::TYA => {
|
|
self.y = self.a;
|
|
}
|
|
}
|
|
self.microcode_step -= 1;
|
|
}
|
|
|
|
(0,0,false)
|
|
}
|
|
|
|
pub fn dump(&self) {
|
|
println!("CPU State: PC: {:04x} / A: {:02x} / X: {:02x} / Y: {:02x} / ADDRESS: {:04x} / DATA: {:02x} / MICROSTEPS: {:02x} / S: {}",
|
|
self.pc, self.a, self.x, self.y, self.address_bus, self.data_bus, self.microcode_step, self.flags.dump());
|
|
}
|
|
|
|
pub fn dump_data(&self) -> ( u16, u8, u8, u8, u16, u8, u8) {
|
|
(self.pc, self.a, self.x, self.y, self.address_bus, self.data_bus, self.microcode_step)
|
|
}
|
|
|
|
fn run_microstep(&self, instruction: Instruction, step: u8) {
|
|
|
|
}
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use crate::constants::constants_isa_op::*;
|
|
use crate::instruction_table::INSTRUCTION_TABLE;
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn clc() {
|
|
// setup the CPU for our test
|
|
let mut cpu = Mos6502Cpu::default();
|
|
cpu.flags.set_flag(Carry);
|
|
// Load our 'test program'
|
|
cpu.memory[0x6000] = ISA_OP_CLC;
|
|
// Start the PC at our program
|
|
cpu.pc = 0x6000;
|
|
|
|
// Tick the CPU through the instruction
|
|
for _ in 0..INSTRUCTION_TABLE[ISA_OP_CLC as usize].unwrap().cycles { cpu.tick(); }
|
|
|
|
assert!(!cpu.peek_flag(Carry));
|
|
}
|
|
|
|
#[test]
|
|
fn cld() {
|
|
let mut cpu = Mos6502Cpu::default();
|
|
cpu.flags.set_flag(Decimal);
|
|
cpu.memory[0x6000] = ISA_OP_CLD;
|
|
cpu.pc = 0x6000;
|
|
|
|
for _ in 0..INSTRUCTION_TABLE[ISA_OP_CLD as usize].unwrap().cycles { cpu.tick(); }
|
|
|
|
assert!(!cpu.peek_flag(Decimal));
|
|
}
|
|
|
|
#[test]
|
|
fn cli() {
|
|
let mut cpu = Mos6502Cpu::default();
|
|
cpu.flags.set_flag(Interrupt);
|
|
cpu.memory[0x6000] = ISA_OP_CLI;
|
|
cpu.pc = 0x6000;
|
|
|
|
for _ in 0..INSTRUCTION_TABLE[ISA_OP_CLI as usize].unwrap().cycles { cpu.tick(); }
|
|
|
|
assert!(!cpu.peek_flag(Interrupt));
|
|
|
|
}
|
|
|
|
#[test]
|
|
fn clv() {
|
|
let mut cpu = Mos6502Cpu::default();
|
|
cpu.flags.set_flag(Overflow);
|
|
cpu.memory[0x6000] = ISA_OP_CLV;
|
|
cpu.pc = 0x6000;
|
|
|
|
for _ in 0..INSTRUCTION_TABLE[ISA_OP_CLV as usize].unwrap().cycles { cpu.tick(); }
|
|
|
|
assert!(!cpu.peek_flag(Overflow));
|
|
}
|
|
|
|
#[test]
|
|
fn lda_immediate() {
|
|
let mut cpu = Mos6502Cpu::default();
|
|
cpu.memory[0x6000] = ISA_OP_LDA_I;
|
|
cpu.memory[0x6001] = 0xab;
|
|
cpu.pc = 0x6000;
|
|
|
|
for _ in 0..INSTRUCTION_TABLE[ISA_OP_LDA_I as usize].unwrap().cycles { cpu.tick(); }
|
|
|
|
assert_eq!(cpu.a, 0xab);
|
|
}
|
|
|
|
#[test]
|
|
fn lda_zeropage() {
|
|
let mut cpu = Mos6502Cpu::default();
|
|
cpu.memory[0x6000] = ISA_OP_LDA_Z;
|
|
cpu.memory[0x6001] = 0xab;
|
|
cpu.memory[0x00ab] = 0xbe;
|
|
cpu.pc = 0x6000;
|
|
|
|
for _ in 0..INSTRUCTION_TABLE[ISA_OP_LDA_Z as usize].unwrap().cycles + 1 { cpu.tick(); }
|
|
|
|
assert_eq!(cpu.a, 0xbe);
|
|
}
|
|
|
|
#[test]
|
|
fn dex_inx() {
|
|
let mut cpu = Mos6502Cpu::default();
|
|
cpu.x = 0xab;
|
|
cpu.memory[0x6000] = ISA_OP_DEX;
|
|
cpu.memory[0x6001] = ISA_OP_INX;
|
|
cpu.pc = 0x6000;
|
|
|
|
for _ in 0..=INSTRUCTION_TABLE[ISA_OP_DEX as usize].unwrap().cycles { cpu.tick(); }
|
|
assert_eq!(0xaa, cpu.x);
|
|
for _ in 0..=INSTRUCTION_TABLE[ISA_OP_INX as usize].unwrap().cycles { cpu.tick(); }
|
|
assert_eq!(0xab, cpu.x);
|
|
}
|
|
|
|
#[test]
|
|
fn dey_iny() {
|
|
let mut cpu = Mos6502Cpu::default();
|
|
cpu.poke_y(0xab);
|
|
cpu.memory[0x6000] = ISA_OP_DEY;
|
|
cpu.memory[0x6001] = ISA_OP_INY;
|
|
cpu.pc = 0x6000;
|
|
|
|
for _ in 0..=INSTRUCTION_TABLE[ISA_OP_DEY as usize].unwrap().cycles { cpu.tick(); }
|
|
assert_eq!(0xaa, cpu.peek_y());
|
|
for _ in 0..=INSTRUCTION_TABLE[ISA_OP_INY as usize].unwrap().cycles { cpu.tick(); }
|
|
assert_eq!(0xab, cpu.peek_y());
|
|
}
|
|
|
|
#[test]
|
|
fn rol_a_ror_a() {
|
|
let mut cpu = Mos6502Cpu::default();
|
|
cpu.poke_a(0b1010_1010); // 0xaa
|
|
cpu.memory[0x6000] = ISA_OP_ROL_A;
|
|
cpu.memory[0x6001] = ISA_OP_ROR_A;
|
|
cpu.pc = 0x6000;
|
|
|
|
for _ in 0..=INSTRUCTION_TABLE[ISA_OP_ROL_A as usize].unwrap().cycles { cpu.tick(); }
|
|
assert_eq!(cpu.peek_a(), 0b0101_0101);
|
|
for _ in 0..=INSTRUCTION_TABLE[ISA_OP_ROR_A as usize].unwrap().cycles { cpu.tick(); }
|
|
assert_eq!(cpu.peek_a(), 0b1010_1010);
|
|
}
|
|
} |