//! A module containing all structures pertaining to instructions and opcodes. use core::fmt; use core::fmt::Formatter; #[cfg(feature = "arbitrary")] extern crate std; /// An enumeration over all 6502 memory addressing modes. #[derive(Debug, Copy, Clone, PartialEq, Eq, core::hash::Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum AddressMode { /// Use the accumulator as the operand. Accumulator, /// Use the next byte as the operand. Immediate(u8), /// Use the byte at the address as the operand. Absolute(u16), /// Use the byte at the address in the zero page as the operand. ZeroPage(u8), /// Use a byte offset from the current program counter as the operand. Relative(i8), /// Use a byte at the address stored at the address as the operand. AbsoluteIndirect(u16), /// Use a byte at the address specified plus the X register as the operand. AbsoluteX(u16), /// Use a byte at the address specified plus the Y register as the operand. AbsoluteY(u16), /// Use a byte at the address in the zero page plus the X register as the operand. ZeroPageX(u8), /// Use a byte at the address in the zero page plus the Y register as the operand. ZeroPageY(u8), /// Use a byte at the address stored at "the address in the zero page plus the X register" as the operand. ZeroPageIndirectX(u8), /// Use a byte at the address stored at "the address in the zero page" plus the Y register as the operand. ZeroPageYIndirect(u8), } impl fmt::Display for AddressMode { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { AddressMode::Accumulator => Ok(()), AddressMode::Immediate(n) => write!(f, "#${n:02X}"), AddressMode::Absolute(n) => write!(f, "${n:04X}"), AddressMode::ZeroPage(n) => write!(f, "${n:02X}"), AddressMode::Relative(n) => write!(f, "${n:02X}"), AddressMode::AbsoluteIndirect(n) => write!(f, "(${n:04X})"), AddressMode::AbsoluteX(n) => write!(f, "${n:04X},X"), AddressMode::AbsoluteY(n) => write!(f, "${n:04X},Y"), AddressMode::ZeroPageX(n) => write!(f, "${n:02X},X"), AddressMode::ZeroPageY(n) => write!(f, "${n:02X},Y"), AddressMode::ZeroPageIndirectX(n) => write!(f, "(${n:02X},X)"), AddressMode::ZeroPageYIndirect(n) => write!(f, "(${n:02X}),Y") } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, core::hash::Hash)] #[allow(non_camel_case_types)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] /// An enumeration over all instructions in a 6502. pub enum Instruction { /// Load a byte into the accumulator. LDA, /// Load a byte into the X register. LDX, /// Load a byte into the Y register. LDY, /// Store the accumulator into memory. STA, /// Store the X register into memory. STX, /// Store the Y register into memory. STY, /// Add a byte to the accumulator. ADC, /// Subtract a byte from the accumulator. SBC, /// Increment a byte by one. INC, /// Increment the X register by one. INX, /// Increment the Y register by one. INY, /// Decrement a byte by one. DEC, /// Decrement the X register by one. DEX, /// Decrement the Y register by one. DEY, /// Arithmetically bit-shift a byte to the left by one. ASL, /// Logically bit-shift a byte to the right by one. LSR, /// Rotate the bits of a byte leftwards, in and out of the carry bit. ROL, /// Rotate the bits of a byte rightwards, in and out of the carry bit. ROR, /// Take the bitwise AND of the accumulator and a byte. AND, /// Take the bitwise OR of the accumulator and a byte. ORA, /// Take the bitwise XOR of the accumulator and a byte. EOR, /// Tests a byte's bits with the accumulator. BIT, /// Compare a byte with the value in the accumulator. CMP, /// Compare a byte with the value in the X register. CPX, /// Compare a byte with the value in the Y register. CPY, /// Branches if the carry bit of the processor status is clear. BCC, /// Branches if the carry bit of the processor status is set. BCS, /// Branches if the zero bit of the processor status is clear. BNE, /// Branches if the zero bit of the processor status is set. BEQ, /// Branches if the negative bit of the processor status is clear. BPL, /// Branches if the negative bit of the processor status is set. BMI, /// Branches if the overflow bit of the processor status is clear. BVC, /// Branches if the overflow bit of the processor status is set. BVS, /// Transfers the byte in the accumulator to the X register. TAX, /// Transfers the byte in the accumulator to the Y register. TAY, /// Transfers the byte in the X register to the accumulator. TXA, /// Transfers the byte in the Y register to the accumulator. TYA, /// Transfers the stack pointer to the X register. TSX, /// Transfers the X register to the stack pointer. TXS, /// Pushes the accumulator to the stack. PHA, /// Pulls the accumulator from the stack. PLA, /// Pushes the processor status to the stack. PHP, /// Pulls the processor status from the stack. PLP, /// Jumps the program counter to a new location. /// /// # Note /// Due to a hardware bug in the original 6502 microprocessor (which is reproduced here for accuracy), /// in [AddressMode::AbsoluteIndirect] addressing mode, the high byte will be read from the /// start of the current page instead of the next one when the low byte's address is /// at the end of a page (i.e. the address mod 256 is 255). JMP, /// Jumps the program counter to a new location, pushing the current location to the stack. JSR, /// Pops a return address from the stack and sets the program counter to it. RTS, /// Handles returning from an interrupt by popping the status and program counter from the stack. RTI, /// Clears the carry flag. CLC, /// Sets the carry flag. SEC, /// Clears the decimal mode flag. CLD, /// Sets the decimal mode flag. SED, /// Clears the interrupt disabling flag. CLI, /// Sets the interrupt disabling flag. SEI, /// Clears the overflow flag. CLV, /// Forces a hardware interrupt. BRK, /// Does nothing. NOP } impl Instruction { /// Alias for [`Instruction::EOR`] that fits the modern language of calling exclusive or XOR. pub const XOR: Instruction = Self::EOR; } impl fmt::Display for Instruction { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") // The Debug impl is good for this as well } } /// A struct representing a ([`Instruction`], [`AddressMode`]) pair as an opcode. /// /// Not all possible states of this struct are valid opcodes - /// some may have address modes that are invalid for the instruction. #[derive(Debug, Copy, Clone, PartialEq, Eq, core::hash::Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Opcode { /// The instruction of the opcode. pub instruction: Instruction, /// The address mode of the opcode if it has one. pub address_mode: Option, } impl fmt::Display for Opcode { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.instruction)?; if let Some(mode) = self.address_mode { write!(f, " {mode}")?; } Ok(()) } } impl Opcode { /// Creates a new opcode from an instruction and an addressing mode. #[must_use] #[inline] pub fn new(instruction: Instruction, address_mode: Option) -> Self { Self { instruction, address_mode } } } macro_rules! opcodes { ($(Opcode::new($inst: ident, $($tt: tt)+) => $repr: literal),* $(,)?) => { impl Opcode { /// Loads an opcode from a byte slice. /// /// # Errors /// If not enough bytes are supplied, returns an `Err` with the amount of bytes needed. /// If the bit pattern is not a valid opcode, returns `Ok(None)`. pub fn load(data: &[u8]) -> Result, usize> { if data.is_empty() { return Err(1) } match data[0] { $($repr => opcodes!(__handle data $inst $($tt)+)),*, _ => Ok(None) } } /// Dumps an opcode into a buffer. /// /// # Errors /// If the opcode is not valid, returns an `Ok(false)`. /// If the opcode won't fit in the buffer, returns an `Err` with how many bytes it needs. pub fn dump(&self, buf: &mut [u8]) -> Result { if buf.is_empty() { return Err(1) } $(opcodes!{__dump self $inst buf $repr $($tt)+})* Ok(false) } } }; (__handle $data: ident $inst: ident None) => { Ok(Some(Opcode::new(Instruction::$inst, None))) }; (__handle $data: ident $inst: ident Some(Accumulator)) => { Ok(Some(Opcode::new(Instruction::$inst, Some(AddressMode::Accumulator)))) }; (__handle $data: ident $inst: ident Some(Immediate)) => { opcodes!(__handle_u8 $data $inst Immediate) }; (__handle $data: ident $inst: ident Some(Absolute)) => { opcodes!(__handle_u16 $data $inst Absolute) }; (__handle $data: ident $inst: ident Some(ZeroPage)) => { opcodes!(__handle_u8 $data $inst ZeroPage) }; (__handle $data: ident $inst: ident Some(Relative)) => {{ let Some(immediate_byte) = $data.get(1) else { return Err(2) }; Ok(Some(Opcode::new(Instruction::$inst, Some(AddressMode::Relative(*immediate_byte as i8))))) }}; (__handle $data: ident $inst: ident Some(AbsoluteIndirect)) => { opcodes!(__handle_u16 $data $inst AbsoluteIndirect) }; (__handle $data: ident $inst: ident Some(AbsoluteX)) => { opcodes!(__handle_u16 $data $inst AbsoluteX) }; (__handle $data: ident $inst: ident Some(AbsoluteY)) => { opcodes!(__handle_u16 $data $inst AbsoluteY) }; (__handle $data: ident $inst: ident Some(ZeroPageX)) => { opcodes!(__handle_u8 $data $inst ZeroPageX) }; (__handle $data: ident $inst: ident Some(ZeroPageY)) => { opcodes!(__handle_u8 $data $inst ZeroPageY) }; (__handle $data: ident $inst: ident Some(ZeroPageIndirectX)) => { opcodes!(__handle_u8 $data $inst ZeroPageIndirectX) }; (__handle $data: ident $inst: ident Some(ZeroPageYIndirect)) => { opcodes!(__handle_u8 $data $inst ZeroPageYIndirect) }; (__dump $self: ident $inst: ident $buf: ident $repr: literal None) => { if let Opcode {instruction: Instruction::$inst, address_mode: None} = $self { $buf[0] = $repr; return Ok(true); }; }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(Accumulator)) => { if let Opcode {instruction: Instruction::$inst, address_mode: Some(AddressMode::Accumulator)} = $self { $buf[0] = $repr; return Ok(true); }; }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(Immediate)) => { opcodes!{__dump_u8 $self $inst $buf $repr Immediate} }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(Absolute)) => { opcodes!{__dump_u16 $self $inst $buf $repr Absolute} }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(ZeroPage)) => { opcodes!{__dump_u8 $self $inst $buf $repr ZeroPage} }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(Relative)) => { if let Opcode {instruction: Instruction::$inst, address_mode: Some(AddressMode::Relative(v))} = $self { if $buf.len() > 2 { return Err(2) } $buf[0] = $repr; $buf[1] = *v as u8; return Ok(true); }; }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(AbsoluteIndirect)) => { opcodes!{__dump_u16 $self $inst $buf $repr AbsoluteIndirect} }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(AbsoluteX)) => { opcodes!{__dump_u16 $self $inst $buf $repr AbsoluteX} }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(AbsoluteY)) => { opcodes!{__dump_u16 $self $inst $buf $repr AbsoluteY} }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(ZeroPageX)) => { opcodes!{__dump_u8 $self $inst $buf $repr ZeroPageX} }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(ZeroPageY)) => { opcodes!{__dump_u8 $self $inst $buf $repr ZeroPageY} }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(ZeroPageIndirectX)) => { opcodes!{__dump_u8 $self $inst $buf $repr ZeroPageIndirectX} }; (__dump $self: ident $inst: ident $buf: ident $repr: literal Some(ZeroPageYIndirect)) => { opcodes!{__dump_u8 $self $inst $buf $repr ZeroPageYIndirect} }; (__handle_u8 $data: ident $inst: ident $name: ident) => {{ let Some(immediate_byte) = $data.get(1) else { return Err(2) }; Ok(Some(Opcode::new(Instruction::$inst, Some(AddressMode::$name(*immediate_byte))))) }}; (__handle_u16 $data: ident $inst: ident $name: ident) => {{ let [Some(absolute_1), Some(absolute_2)] = [$data.get(1), $data.get(2)] else { return Err(3) }; Ok(Some(Opcode::new(Instruction::$inst, Some(AddressMode::$name( u16::from_le_bytes([*absolute_1, *absolute_2]) ))))) }}; (__dump_u8 $self: ident $inst: ident $buf: ident $repr: literal $name: ident) => { if let Opcode {instruction: Instruction::$inst, address_mode: Some(AddressMode::$name(v))} = $self { if $buf.len() > 2 { return Err(2) } $buf[0] = $repr; $buf[1] = *v; return Ok(true); }; }; (__dump_u16 $self: ident $inst: ident $buf: ident $repr: literal $name: ident) => { if let Opcode {instruction: Instruction::$inst, address_mode: Some(AddressMode::$name(v))} = $self { if $buf.len() > 3 { return Err(3) } let [low, high] = v.to_le_bytes(); $buf[0] = $repr; $buf[1] = low; $buf[2] = high; return Ok(true); }; } } opcodes! { Opcode::new(BRK, None) => 0x00, Opcode::new(ORA, Some(ZeroPageIndirectX)) => 0x01, Opcode::new(ORA, Some(ZeroPage)) => 0x05, Opcode::new(ASL, Some(ZeroPage)) => 0x06, Opcode::new(PHP, None) => 0x08, Opcode::new(ORA, Some(Immediate)) => 0x09, Opcode::new(ASL, Some(Accumulator)) => 0x0A, Opcode::new(ORA, Some(Absolute)) => 0x0D, Opcode::new(ASL, Some(Absolute)) => 0x0E, Opcode::new(BPL, Some(Relative)) => 0x10, Opcode::new(ORA, Some(ZeroPageYIndirect)) => 0x11, Opcode::new(ORA, Some(ZeroPageX)) => 0x15, Opcode::new(ASL, Some(ZeroPageX)) => 0x16, Opcode::new(CLC, None) => 0x18, Opcode::new(ORA, Some(AbsoluteY)) => 0x19, Opcode::new(ORA, Some(AbsoluteX)) => 0x1D, Opcode::new(ASL, Some(AbsoluteX)) => 0x1E, Opcode::new(JSR, Some(Absolute)) => 0x20, Opcode::new(AND, Some(ZeroPageIndirectX)) => 0x21, Opcode::new(BIT, Some(ZeroPage)) => 0x24, Opcode::new(AND, Some(ZeroPage)) => 0x25, Opcode::new(ROL, Some(ZeroPage)) => 0x26, Opcode::new(PLP, None) => 0x28, Opcode::new(AND, Some(Immediate)) => 0x29, Opcode::new(ROL, Some(Accumulator)) => 0x2A, Opcode::new(BIT, Some(Absolute)) => 0x2C, Opcode::new(AND, Some(Absolute)) => 0x2D, Opcode::new(ROL, Some(Absolute)) => 0x2E, Opcode::new(BMI, Some(Relative)) => 0x30, Opcode::new(AND, Some(ZeroPageYIndirect)) => 0x31, Opcode::new(AND, Some(ZeroPageX)) => 0x35, Opcode::new(ROL, Some(ZeroPageX)) => 0x36, Opcode::new(SEC, None) => 0x38, Opcode::new(AND, Some(AbsoluteY)) => 0x39, Opcode::new(AND, Some(AbsoluteX)) => 0x3D, Opcode::new(ROL, Some(AbsoluteX)) => 0x3E, Opcode::new(RTI, None) => 0x40, Opcode::new(EOR, Some(ZeroPageIndirectX)) => 0x41, Opcode::new(EOR, Some(ZeroPage)) => 0x45, Opcode::new(LSR, Some(ZeroPage)) => 0x46, Opcode::new(PHA, None) => 0x48, Opcode::new(EOR, Some(Immediate)) => 0x49, Opcode::new(LSR, Some(Accumulator)) => 0x4A, Opcode::new(JMP, Some(Absolute)) => 0x4C, Opcode::new(EOR, Some(Absolute)) => 0x4D, Opcode::new(LSR, Some(Absolute)) => 0x4E, Opcode::new(BVC, Some(Relative)) => 0x50, Opcode::new(EOR, Some(ZeroPageYIndirect)) => 0x51, Opcode::new(EOR, Some(ZeroPageX)) => 0x55, Opcode::new(LSR, Some(ZeroPageX)) => 0x56, Opcode::new(CLI, None) => 0x58, Opcode::new(EOR, Some(AbsoluteY)) => 0x59, Opcode::new(EOR, Some(AbsoluteX)) => 0x5D, Opcode::new(LSR, Some(AbsoluteX)) => 0x5E, Opcode::new(RTS, None) => 0x60, Opcode::new(ADC, Some(ZeroPageIndirectX)) => 0x61, Opcode::new(ADC, Some(ZeroPage)) => 0x65, Opcode::new(ROR, Some(ZeroPage)) => 0x66, Opcode::new(PLA, None) => 0x68, Opcode::new(ADC, Some(Immediate)) => 0x69, Opcode::new(ROR, Some(Accumulator)) => 0x6A, Opcode::new(JMP, Some(AbsoluteIndirect)) => 0x6C, Opcode::new(ADC, Some(Absolute)) => 0x6D, Opcode::new(ROR, Some(Absolute)) => 0x6E, Opcode::new(BVS, Some(Relative)) => 0x70, Opcode::new(ADC, Some(ZeroPageYIndirect)) => 0x71, Opcode::new(ADC, Some(ZeroPageX)) => 0x75, Opcode::new(ROR, Some(ZeroPageX)) => 0x76, Opcode::new(SEI, None) => 0x78, Opcode::new(ADC, Some(AbsoluteY)) => 0x79, Opcode::new(ADC, Some(AbsoluteX)) => 0x7D, Opcode::new(ROR, Some(AbsoluteX)) => 0x7E, Opcode::new(STA, Some(ZeroPageIndirectX)) => 0x81, Opcode::new(STY, Some(ZeroPage)) => 0x84, Opcode::new(STA, Some(ZeroPage)) => 0x85, Opcode::new(STX, Some(ZeroPage)) => 0x86, Opcode::new(DEY, None) => 0x88, Opcode::new(BIT, Some(Immediate)) => 0x89, Opcode::new(TXA, None) => 0x8A, Opcode::new(STY, Some(Absolute)) => 0x8C, Opcode::new(STA, Some(Absolute)) => 0x8D, Opcode::new(STX, Some(Absolute)) => 0x8E, Opcode::new(BCC, Some(Relative)) => 0x90, Opcode::new(STA, Some(ZeroPageYIndirect)) => 0x91, Opcode::new(STY, Some(ZeroPageX)) => 0x94, Opcode::new(STA, Some(ZeroPageX)) => 0x95, Opcode::new(STX, Some(ZeroPageY)) => 0x96, Opcode::new(TYA, None) => 0x98, Opcode::new(STA, Some(AbsoluteY)) => 0x99, Opcode::new(TXS, None) => 0x9A, Opcode::new(STA, Some(AbsoluteX)) => 0x9D, Opcode::new(LDY, Some(Immediate)) => 0xA0, Opcode::new(LDA, Some(ZeroPageIndirectX)) => 0xA1, Opcode::new(LDX, Some(Immediate)) => 0xA2, Opcode::new(LDY, Some(ZeroPage)) => 0xA4, Opcode::new(LDA, Some(ZeroPage)) => 0xA5, Opcode::new(LDX, Some(ZeroPage)) => 0xA6, Opcode::new(TAY, None) => 0xA8, Opcode::new(LDA, Some(Immediate)) => 0xA9, Opcode::new(TAX, None) => 0xAA, Opcode::new(LDY, Some(Absolute)) => 0xAC, Opcode::new(LDA, Some(Absolute)) => 0xAD, Opcode::new(LDX, Some(Absolute)) => 0xAE, Opcode::new(BCS, Some(Relative)) => 0xB0, Opcode::new(LDA, Some(ZeroPageYIndirect)) => 0xB1, Opcode::new(LDY, Some(ZeroPageX)) => 0xB4, Opcode::new(LDA, Some(ZeroPageX)) => 0xB5, Opcode::new(LDX, Some(ZeroPageY)) => 0xB6, Opcode::new(CLV, None) => 0xB8, Opcode::new(LDA, Some(AbsoluteY)) => 0xB9, Opcode::new(TSX, None) => 0xBA, Opcode::new(LDY, Some(AbsoluteX)) => 0xBC, Opcode::new(LDA, Some(AbsoluteX)) => 0xBD, Opcode::new(LDX, Some(AbsoluteY)) => 0xBE, Opcode::new(CPY, Some(Immediate)) => 0xC0, Opcode::new(CMP, Some(ZeroPageIndirectX)) => 0xC1, Opcode::new(CPY, Some(ZeroPage)) => 0xC4, Opcode::new(CMP, Some(ZeroPage)) => 0xC5, Opcode::new(DEC, Some(ZeroPage)) => 0xC6, Opcode::new(INY, None) => 0xC8, Opcode::new(CMP, Some(Immediate)) => 0xC9, Opcode::new(DEX, None) => 0xCA, Opcode::new(CPY, Some(Absolute)) => 0xCC, Opcode::new(CMP, Some(Absolute)) => 0xCD, Opcode::new(DEC, Some(Absolute)) => 0xCE, Opcode::new(BNE, Some(Relative)) => 0xD0, Opcode::new(CMP, Some(ZeroPageYIndirect)) => 0xD1, Opcode::new(CMP, Some(ZeroPageX)) => 0xD5, Opcode::new(DEC, Some(ZeroPageX)) => 0xD6, Opcode::new(CLD, None) => 0xD8, Opcode::new(CMP, Some(AbsoluteY)) => 0xD9, Opcode::new(CMP, Some(AbsoluteX)) => 0xDD, Opcode::new(DEC, Some(AbsoluteX)) => 0xDE, Opcode::new(CPX, Some(Immediate)) => 0xE0, Opcode::new(SBC, Some(ZeroPageIndirectX)) => 0xE1, Opcode::new(CPX, Some(ZeroPage)) => 0xE4, Opcode::new(SBC, Some(ZeroPage)) => 0xE5, Opcode::new(INC, Some(ZeroPage)) => 0xE6, Opcode::new(INX, None) => 0xE8, Opcode::new(SBC, Some(Immediate)) => 0xE9, Opcode::new(NOP, None) => 0xEA, Opcode::new(CPX, Some(Absolute)) => 0xEC, Opcode::new(SBC, Some(Absolute)) => 0xED, Opcode::new(INC, Some(Absolute)) => 0xEE, Opcode::new(BEQ, Some(Relative)) => 0xF0, Opcode::new(SBC, Some(ZeroPageYIndirect)) => 0xF1, Opcode::new(SBC, Some(ZeroPageX)) => 0xF5, Opcode::new(INC, Some(ZeroPageX)) => 0xF6, Opcode::new(SED, None) => 0xF8, Opcode::new(SBC, Some(AbsoluteY)) => 0xF9, Opcode::new(SBC, Some(AbsoluteX)) => 0xFD, Opcode::new(INC, Some(AbsoluteX)) => 0xFE, }