357 lines
13 KiB
Rust
357 lines
13 KiB
Rust
#![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
|
|
}
|