#![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 }