This commit is contained in:
baltdev 2024-04-01 23:32:56 -05:00
parent 7f40ffc5ee
commit e6050511f0
5 changed files with 224 additions and 196 deletions

View File

@ -21,8 +21,9 @@ pub trait WriteCallback {
/// A helper for easily creating simple implementations of [`ReadCallback`]. /// A helper for easily creating simple implementations of [`ReadCallback`].
pub struct FunctionReadCallback<F>(pub F); pub struct FunctionReadCallback<F>(pub F);
impl<F> ReadCallback for FunctionReadCallback<F> where impl<F> ReadCallback for FunctionReadCallback<F>
F: FnMut(&mut State, u16) -> u8 where
F: FnMut(&mut State, u16) -> u8,
{ {
fn callback(&mut self, state: &mut State, address: u16) -> u8 { fn callback(&mut self, state: &mut State, address: u16) -> u8 {
self.0(state, address) self.0(state, address)
@ -32,8 +33,9 @@ impl<F> ReadCallback for FunctionReadCallback<F> where
/// A helper for easily creating simple implementations of [`WriteCallback`]. /// A helper for easily creating simple implementations of [`WriteCallback`].
pub struct FunctionWriteCallback<F>(pub F); pub struct FunctionWriteCallback<F>(pub F);
impl<F> WriteCallback for FunctionWriteCallback<F> where impl<F> WriteCallback for FunctionWriteCallback<F>
F: FnMut(&mut State, u16, u8) where
F: FnMut(&mut State, u16, u8),
{ {
fn callback(&mut self, state: &mut State, address: u16, byte: u8) { fn callback(&mut self, state: &mut State, address: u16, byte: u8) {
self.0(state, address, byte); self.0(state, address, byte);
@ -116,7 +118,8 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn with_rom_from(mut self, slice: &[u8], location: u16) -> Self { pub fn with_rom_from(mut self, slice: &[u8], location: u16) -> Self {
self.state.memory[location as usize..location as usize + slice.len()].copy_from_slice(slice); self.state.memory[location as usize..location as usize + slice.len()]
.copy_from_slice(slice);
self self
} }
@ -181,7 +184,6 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
self.read(0x100 + u16::from(self.state.stack_pointer)) self.read(0x100 + u16::from(self.state.stack_pointer))
} }
/// Sets a callback to be called when memory is read from, allowing for memory-mapped reads. /// Sets a callback to be called when memory is read from, allowing for memory-mapped reads.
/// See [`ReadCallback`]. /// See [`ReadCallback`].
#[inline] #[inline]
@ -190,7 +192,7 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
Emulator { Emulator {
state: self.state, state: self.state,
read_callback: callback, read_callback: callback,
write_callback: self.write_callback write_callback: self.write_callback,
} }
} }
@ -202,7 +204,7 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
Emulator { Emulator {
state: self.state, state: self.state,
read_callback: self.read_callback, read_callback: self.read_callback,
write_callback: callback write_callback: callback,
} }
} }
} }
@ -212,14 +214,15 @@ impl Default for Emulator<DefaultReadCallback, DefaultWriteCallback> {
Self { Self {
state: State::default(), state: State::default(),
read_callback: DefaultReadCallback, read_callback: DefaultReadCallback,
write_callback: DefaultWriteCallback write_callback: DefaultWriteCallback,
} }
} }
} }
#[allow(clippy::missing_fields_in_debug)] #[allow(clippy::missing_fields_in_debug)]
impl<R: ReadCallback + core::fmt::Debug, W: WriteCallback + core::fmt::Debug> core::fmt::Debug for Emulator<R, W> { impl<R: ReadCallback + core::fmt::Debug, W: WriteCallback + core::fmt::Debug> core::fmt::Debug
for Emulator<R, W>
{
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fmt.debug_struct("Emulator") fmt.debug_struct("Emulator")
.field("state", &self.state) .field("state", &self.state)
@ -242,7 +245,10 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
Some(match mode { Some(match mode {
Absolute(addr) => addr, Absolute(addr) => addr,
ZeroPage(addr) => u16::from(addr), ZeroPage(addr) => u16::from(addr),
Relative(offset) => self.state.program_counter.wrapping_add_signed(i16::from(offset)), Relative(offset) => self
.state
.program_counter
.wrapping_add_signed(i16::from(offset)),
AbsoluteIndirect(addr) => { AbsoluteIndirect(addr) => {
let high = self.read(addr); let high = self.read(addr);
let low = self.read(addr.wrapping_add(1)); let low = self.read(addr.wrapping_add(1));
@ -384,13 +390,19 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
let (high_lhs, low_lhs) = to_bcd_nibbles(lhs); let (high_lhs, low_lhs) = to_bcd_nibbles(lhs);
let (high_rhs, low_rhs) = to_bcd_nibbles(rhs); let (high_rhs, low_rhs) = to_bcd_nibbles(rhs);
let low_sum = low_lhs + low_rhs + u8::from(self.state.status.contains(Status::CARRY)); let low_sum =
low_lhs + low_rhs + u8::from(self.state.status.contains(Status::CARRY));
let (low_carry, low) = (low_sum / 10, low_sum % 10); let (low_carry, low) = (low_sum / 10, low_sum % 10);
let high_sum = high_lhs + high_rhs + low_carry; let high_sum = high_lhs + high_rhs + low_carry;
let (sum, carry) = from_bcd_nibbles(low, high_sum); let (sum, carry) = from_bcd_nibbles(low, high_sum);
self.state.status.set(Status::CARRY, carry); self.state.status.set(Status::CARRY, carry);
let overflow_sum = u16::from(from_bcd(lhs)) + u16::from(from_bcd(rhs)) + u16::from(self.state.status.contains(Status::CARRY)); let overflow_sum = u16::from(from_bcd(lhs))
self.state.status.set(Status::OVERFLOW, (lhs & 0x80) != (overflow_sum & 0x80) as u8); + u16::from(from_bcd(rhs))
+ u16::from(self.state.status.contains(Status::CARRY));
self.state.status.set(
Status::OVERFLOW,
(lhs & 0x80) != (overflow_sum & 0x80) as u8,
);
self.state.status.set(Status::ZERO, sum == 0); self.state.status.set(Status::ZERO, sum == 0);
self.state.status.set(Status::NEGATIVE, (sum as i8) < 0); self.state.status.set(Status::NEGATIVE, (sum as i8) < 0);
self.state.accumulator = sum; self.state.accumulator = sum;
@ -405,7 +417,9 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
let (high_lhs, low_lhs) = to_bcd_nibbles(lhs); let (high_lhs, low_lhs) = to_bcd_nibbles(lhs);
let (high_rhs, low_rhs) = to_bcd_nibbles(rhs); let (high_rhs, low_rhs) = to_bcd_nibbles(rhs);
let mut low = low_lhs as i8 - low_rhs as i8 - i8::from(!self.state.status.contains(Status::CARRY)); let mut low = low_lhs as i8
- low_rhs as i8
- i8::from(!self.state.status.contains(Status::CARRY));
let mut low_borrow = 0; let mut low_borrow = 0;
if low < 0 { if low < 0 {
low += 10; low += 10;
@ -493,18 +507,22 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
let byte = self.byte(mode); let byte = self.byte(mode);
let result = self.state.accumulator & byte; let result = self.state.accumulator & byte;
self.state.status.set(Status::ZERO, result == 0); self.state.status.set(Status::ZERO, result == 0);
self.state.status.set(Status::NEGATIVE, byte & 0b1000_0000 != 0); self.state
self.state.status.set(Status::OVERFLOW, byte & 0b0100_0000 != 0); .status
.set(Status::NEGATIVE, byte & 0b1000_0000 != 0);
self.state
.status
.set(Status::OVERFLOW, byte & 0b0100_0000 != 0);
})?, })?,
CMP => opcode.address_mode.map(|mode| CMP => opcode
self.compare(self.state.accumulator, mode) .address_mode
)?, .map(|mode| self.compare(self.state.accumulator, mode))?,
CPX => opcode.address_mode.map(|mode| CPX => opcode
self.compare(self.state.x_register, mode) .address_mode
)?, .map(|mode| self.compare(self.state.x_register, mode))?,
CPY => opcode.address_mode.map(|mode| CPY => opcode
self.compare(self.state.y_register, mode) .address_mode
)?, .map(|mode| self.compare(self.state.y_register, mode))?,
inst @ (BCC | BNE | BPL | BVC | BCS | BEQ | BMI | BVS) => { inst @ (BCC | BNE | BPL | BVC | BCS | BEQ | BMI | BVS) => {
opcode.address_mode.and_then(|mode| { opcode.address_mode.and_then(|mode| {
let mask = match inst { let mask = match inst {
@ -531,7 +549,7 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
PLA => { PLA => {
let new_acc = self.pop(); let new_acc = self.pop();
self.state.accumulator = self.set_flags(new_acc); self.state.accumulator = self.set_flags(new_acc);
}, }
PHP => self.push((self.state.status | Status::UNUSED | Status::BREAK).bits()), PHP => self.push((self.state.status | Status::UNUSED | Status::BREAK).bits()),
PLP => self.state.status = Status::from_bits_retain(self.pop()) | Status::UNUSED, PLP => self.state.status = Status::from_bits_retain(self.pop()) | Status::UNUSED,
JMP => opcode.address_mode.and_then(|mode| { JMP => opcode.address_mode.and_then(|mode| {
@ -540,8 +558,9 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
Some(()) Some(())
})?, })?,
JSR => opcode.address_mode.and_then(|mode| { JSR => opcode.address_mode.and_then(|mode| {
let [high, low] = let [high, low] = self
self.state.program_counter .state
.program_counter
.wrapping_add(u16::from(opcode_length)) .wrapping_add(u16::from(opcode_length))
.wrapping_sub(1) .wrapping_sub(1)
.to_le_bytes(); .to_le_bytes();
@ -580,7 +599,10 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
} }
if increment { if increment {
self.state.program_counter = self.state.program_counter.wrapping_add(u16::from(opcode_length)); self.state.program_counter = self
.state
.program_counter
.wrapping_add(u16::from(opcode_length));
} }
Some(false) Some(false)
@ -617,16 +639,17 @@ impl<R: ReadCallback, W: WriteCallback> Emulator<R, W> {
/// Computes binary add with carry /// Computes binary add with carry
fn adc(&mut self, rhs: u8) -> u8 { fn adc(&mut self, rhs: u8) -> u8 {
let sum = let sum = u16::from(self.state.accumulator)
u16::from(self.state.accumulator) + + u16::from(rhs)
u16::from(rhs) + + u16::from(self.state.status.contains(Status::CARRY));
u16::from(self.state.status.contains(Status::CARRY));
let acc = u16::from(self.state.accumulator); let acc = u16::from(self.state.accumulator);
let rhs = u16::from(rhs); let rhs = u16::from(rhs);
self.state.status.set(Status::CARRY, sum > 0xFF); self.state.status.set(Status::CARRY, sum > 0xFF);
self.state.status.set(Status::OVERFLOW, !(acc ^ rhs) & (acc ^ sum) & 0x80 != 0); self.state
.status
.set(Status::OVERFLOW, !(acc ^ rhs) & (acc ^ sum) & 0x80 != 0);
let sum = sum as u8; let sum = sum as u8;

View File

@ -51,7 +51,7 @@ impl fmt::Display for AddressMode {
AddressMode::ZeroPageX(n) => write!(f, "${n:02X},X"), AddressMode::ZeroPageX(n) => write!(f, "${n:02X},X"),
AddressMode::ZeroPageY(n) => write!(f, "${n:02X},Y"), AddressMode::ZeroPageY(n) => write!(f, "${n:02X},Y"),
AddressMode::ZeroPageIndirectX(n) => write!(f, "(${n:02X},X)"), AddressMode::ZeroPageIndirectX(n) => write!(f, "(${n:02X},X)"),
AddressMode::ZeroPageYIndirect(n) => write!(f, "(${n:02X}),Y") AddressMode::ZeroPageYIndirect(n) => write!(f, "(${n:02X}),Y"),
} }
} }
} }
@ -179,7 +179,7 @@ pub enum Instruction {
/// Forces a hardware interrupt. /// Forces a hardware interrupt.
BRK, BRK,
/// Does nothing. /// Does nothing.
NOP NOP,
} }
impl Instruction { impl Instruction {
@ -222,7 +222,10 @@ impl Opcode {
#[must_use] #[must_use]
#[inline] #[inline]
pub fn new(instruction: Instruction, address_mode: Option<AddressMode>) -> Self { pub fn new(instruction: Instruction, address_mode: Option<AddressMode>) -> Self {
Self { instruction, address_mode } Self {
instruction,
address_mode,
}
} }
} }
@ -378,7 +381,6 @@ macro_rules! opcodes {
} }
} }
opcodes! { opcodes! {
Opcode::new(BRK, None) => 0x00, Opcode::new(BRK, None) => 0x00,
Opcode::new(ORA, Some(ZeroPageIndirectX)) => 0x01, Opcode::new(ORA, Some(ZeroPageIndirectX)) => 0x01,

View File

@ -4,10 +4,13 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
pub mod state;
pub mod instructions;
pub mod emulation; pub mod emulation;
pub mod instructions;
pub mod state;
pub use instructions::{Opcode, Instruction, AddressMode}; pub use emulation::{
DefaultReadCallback, DefaultWriteCallback, Emulator, FunctionReadCallback,
FunctionWriteCallback, ReadCallback, WriteCallback,
};
pub use instructions::{AddressMode, Instruction, Opcode};
pub use state::{State, Status}; pub use state::{State, Status};
pub use emulation::{Emulator, ReadCallback, WriteCallback, DefaultReadCallback, DefaultWriteCallback, FunctionReadCallback, FunctionWriteCallback};

View File

@ -4,8 +4,8 @@ extern crate std;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use { use {
serde::{Serialize, Deserialize}, serde::{Deserialize, Serialize},
serde_with::{As, Bytes} serde_with::{As, Bytes},
}; };
#[derive(Copy, Clone, PartialEq, Eq, core::hash::Hash)] #[derive(Copy, Clone, PartialEq, Eq, core::hash::Hash)]
@ -39,7 +39,7 @@ pub struct State {
/// but grows the struct's size considerably. /// but grows the struct's size considerably.
/// Consider storing this struct on the heap if this is undesirable and you have one. /// Consider storing this struct on the heap if this is undesirable and you have one.
#[cfg_attr(feature = "serde", serde(with = "As::<Bytes>"))] #[cfg_attr(feature = "serde", serde(with = "As::<Bytes>"))]
pub memory: [u8; 256 * 256] pub memory: [u8; 256 * 256],
} }
#[allow(clippy::missing_fields_in_debug)] #[allow(clippy::missing_fields_in_debug)]
@ -66,7 +66,7 @@ impl Default for State {
stack_pointer: 0xFF, stack_pointer: 0xFF,
status: Status::default(), status: Status::default(),
memory: [0; 256 * 256], memory: [0; 256 * 256],
_padding: 0 _padding: 0,
} }
} }
} }

View File

@ -57,15 +57,15 @@ fn functional_test() -> Result<(), usize> {
} }
eprintln!("----------------------------[STACK]-----------------------------"); eprintln!("----------------------------[STACK]-----------------------------");
for i in 0..16 { for i in 0..16 {
eprintln!("{:02x?}", &emulator.state.memory[0x100 + (i * 16)..0x100 + ((i + 1) * 16)]) eprintln!(
"{:02x?}",
&emulator.state.memory[0x100 + (i * 16)..0x100 + ((i + 1) * 16)]
)
} }
return Err(0); return Err(0);
} }
if needs_interrupt { if needs_interrupt {
let vector = u16::from_le_bytes([ let vector = u16::from_le_bytes([emulator.read(0xFFFE), emulator.read(0xFFFF)]);
emulator.read(0xFFFE),
emulator.read(0xFFFF)
]);
eprintln!("[MASKABLE INTERRUPT, GOING TO IRQ VECTOR @ {vector:02X}]"); eprintln!("[MASKABLE INTERRUPT, GOING TO IRQ VECTOR @ {vector:02X}]");
// Go to interrupt vector // Go to interrupt vector
emulator.state.program_counter = vector; emulator.state.program_counter = vector;