use crate::{
apu::{Apu, Channel},
bus::CpuBus,
cart::Cart,
common::{Clock, Kind, NesRegion, Regional, Reset},
input::{FourPlayer, Joypad, Slot, Zapper},
mapper::Mapper,
mem::{Access, Mem},
ppu::Ppu,
NesResult,
};
use bitflags::bitflags;
use instr::{
AddrMode::{ABS, ABX, ABY, ACC, IDX, IDY, IMM, IMP, IND, REL, ZP0, ZPX, ZPY},
Instr,
Operation::{
ADC, AHX, ALR, ANC, AND, 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, IGN, INC, INX, INY, ISB, JMP,
JSR, LAS, LAX, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA, PLP, RLA, ROL, ROR, RRA, RTI,
RTS, SAX, SBC, SEC, SED, SEI, SKB, SLO, SRE, STA, STX, STY, SXA, SYA, TAS, TAX, TAY, TSX,
TXA, TXS, TYA, XAA, XXX,
},
};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Write};
pub mod instr;
bitflags! {
#[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]
#[must_use]
pub struct Irq: u8 {
const MAPPER = 1 << 1;
const FRAME_COUNTER = 1 << 2;
const DMC = 1 << 3;
}
}
bitflags! {
#[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]
#[must_use]
pub struct Status: u8 {
const C = 1; const Z = 1 << 1; const I = 1 << 2; const D = 1 << 3; const B = 1 << 4; const U = 1 << 5; const V = 1 << 6; const N = 1 << 7; }
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
enum Cycle {
Read,
Write,
}
#[derive(Clone, Serialize, Deserialize)]
#[must_use]
pub struct Cpu {
cycle: usize, region: NesRegion,
master_clock: u64,
clock_divider: u64,
start_clocks: u64,
end_clocks: u64,
pc: u16, sp: u8, acc: u8, x: u8, y: u8, status: Status, bus: CpuBus,
instr: Instr, abs_addr: u16, rel_addr: u16, fetched_data: u8, irq: Irq, run_irq: bool,
prev_run_irq: bool,
nmi: bool,
prev_nmi: bool,
prev_nmi_pending: bool,
#[serde(skip)]
corrupted: bool, dmc_dma: bool,
halt: bool,
dummy_read: bool,
cycle_accurate: bool,
disasm: String,
}
impl Cpu {
const NTSC_MASTER_CLOCK_RATE: f32 = 21_477_272.0;
const NTSC_CPU_CLOCK_RATE: f32 = Self::NTSC_MASTER_CLOCK_RATE / 12.0;
const PAL_MASTER_CLOCK_RATE: f32 = 26_601_712.0;
const PAL_CPU_CLOCK_RATE: f32 = Self::PAL_MASTER_CLOCK_RATE / 16.0;
const DENDY_CPU_CLOCK_RATE: f32 = Self::PAL_MASTER_CLOCK_RATE / 15.0;
const PPU_OFFSET: u64 = 1;
const NMI_VECTOR: u16 = 0xFFFA; const IRQ_VECTOR: u16 = 0xFFFE; const RESET_VECTOR: u16 = 0xFFFC; const POWER_ON_STATUS: Status = Status::U.union(Status::I);
const POWER_ON_SP: u8 = 0xFD;
const SP_BASE: u16 = 0x0100; pub fn new(bus: CpuBus) -> Self {
let mut cpu = Self {
cycle: 0,
region: NesRegion::default(),
master_clock: 0,
clock_divider: 0,
start_clocks: 0,
end_clocks: 0,
pc: 0x0000,
sp: 0x00,
acc: 0x00,
x: 0x00,
y: 0x00,
status: Self::POWER_ON_STATUS,
bus,
instr: Cpu::INSTRUCTIONS[0x00],
abs_addr: 0x0000,
rel_addr: 0x0000,
fetched_data: 0x00,
irq: Irq::empty(),
run_irq: false,
prev_run_irq: false,
nmi: false,
prev_nmi: false,
prev_nmi_pending: false,
corrupted: false,
dmc_dma: false,
halt: false,
dummy_read: false,
cycle_accurate: true,
disasm: String::with_capacity(100),
};
cpu.set_region(cpu.region);
cpu
}
#[inline]
#[must_use]
pub const fn region_clock_rate(region: NesRegion) -> f32 {
match region {
NesRegion::Ntsc => Self::NTSC_CPU_CLOCK_RATE,
NesRegion::Pal => Self::PAL_CPU_CLOCK_RATE,
NesRegion::Dendy => Self::DENDY_CPU_CLOCK_RATE,
}
}
#[inline]
#[must_use]
pub const fn clock_rate(&self) -> f32 {
Self::region_clock_rate(self.region)
}
#[inline]
#[must_use]
pub const fn cycle(&self) -> usize {
self.cycle
}
#[inline]
#[must_use]
pub const fn pc(&self) -> u16 {
self.pc
}
#[inline]
#[must_use]
pub const fn sp(&self) -> u8 {
self.sp
}
#[inline]
#[must_use]
pub const fn a(&self) -> u8 {
self.acc
}
#[inline]
#[must_use]
pub const fn x(&self) -> u8 {
self.x
}
#[inline]
#[must_use]
pub const fn y(&self) -> u8 {
self.y
}
#[inline]
pub const fn status(&self) -> Status {
self.status
}
#[inline]
#[must_use]
pub const fn corrupted(&self) -> bool {
self.corrupted
}
#[inline]
#[must_use]
pub fn disasm(&self) -> &str {
&self.disasm
}
#[inline]
pub const fn ppu(&self) -> &Ppu {
self.bus.ppu()
}
#[inline]
pub fn ppu_mut(&mut self) -> &mut Ppu {
self.bus.ppu_mut()
}
#[inline]
pub const fn apu(&self) -> &Apu {
self.bus.apu()
}
#[inline]
pub fn apu_mut(&mut self) -> &mut Apu {
self.bus.apu_mut()
}
#[inline]
pub const fn mapper(&self) -> &Mapper {
self.bus.mapper()
}
#[inline]
pub fn mapper_mut(&mut self) -> &mut Mapper {
self.bus.mapper_mut()
}
#[inline]
pub const fn joypad(&self, slot: Slot) -> &Joypad {
self.bus.joypad(slot)
}
#[inline]
pub fn joypad_mut(&mut self, slot: Slot) -> &mut Joypad {
self.bus.joypad_mut(slot)
}
#[inline]
pub fn connect_zapper(&mut self, enabled: bool) {
self.bus.connect_zapper(enabled);
}
#[inline]
pub const fn zapper(&self) -> &Zapper {
self.bus.zapper()
}
#[inline]
pub fn zapper_mut(&mut self) -> &mut Zapper {
self.bus.zapper_mut()
}
#[inline]
pub fn load_cart(&mut self, cart: Cart) {
self.bus.load_cart(cart);
}
#[inline]
#[must_use]
pub const fn cart_battery_backed(&self) -> bool {
self.bus.cart_battery_backed()
}
#[inline]
#[must_use]
pub fn sram(&self) -> &[u8] {
self.bus.sram()
}
#[inline]
pub fn load_sram(&mut self, sram: Vec<u8>) {
self.bus.load_sram(sram);
}
#[inline]
#[must_use]
pub fn wram(&self) -> &[u8] {
self.bus.wram()
}
#[inline]
pub fn add_genie_code(&mut self, genie_code: String) -> NesResult<()> {
self.bus.add_genie_code(genie_code)
}
#[inline]
pub fn remove_genie_code(&mut self, genie_code: &str) {
self.bus.remove_genie_code(genie_code);
}
#[inline]
#[must_use]
pub const fn ppu_cycle(&self) -> u32 {
self.bus.ppu_cycle()
}
#[inline]
#[must_use]
pub const fn ppu_scanline(&self) -> u32 {
self.bus.ppu_scanline()
}
#[inline]
#[must_use]
pub fn frame_buffer(&self) -> &[u16] {
self.bus.frame_buffer()
}
#[inline]
#[must_use]
pub const fn frame_number(&self) -> u32 {
self.bus.frame_number()
}
#[inline]
#[must_use]
pub const fn audio_channel_enabled(&self, channel: Channel) -> bool {
self.bus.audio_channel_enabled(channel)
}
#[inline]
pub fn toggle_audio_channel(&mut self, channel: Channel) {
self.bus.toggle_audio_channel(channel);
}
#[inline]
#[must_use]
pub fn audio_samples(&self) -> &[f32] {
self.bus.audio_samples()
}
#[inline]
pub fn clear_audio_samples(&mut self) {
self.bus.clear_audio_samples();
}
#[inline]
pub const fn four_player(&self) -> FourPlayer {
self.bus.four_player()
}
#[inline]
pub fn set_four_player(&mut self, four_player: FourPlayer) {
self.bus.set_four_player(four_player);
}
#[inline]
pub fn set_cycle_accurate(&mut self, enabled: bool) {
self.cycle_accurate = enabled;
}
pub fn irq(&mut self) {
self.read(self.pc, Access::Dummy);
self.read(self.pc, Access::Dummy);
self.push_u16(self.pc);
let status = ((self.status | Status::U) & !Status::B).bits();
if self.nmi {
self.nmi = false;
self.push(status);
self.status.set(Status::I, true);
self.pc = self.read_u16(Self::NMI_VECTOR);
log::trace!("NMI: {}", self.cycle);
} else {
self.push(status);
self.status.set(Status::I, true);
self.pc = self.read_u16(Self::IRQ_VECTOR);
log::trace!("IRQ: {}", self.cycle);
}
}
fn handle_interrupts(&mut self) {
self.prev_nmi = self.nmi;
let nmi_pending = self.bus.nmi_pending();
if !self.prev_nmi_pending && nmi_pending {
self.nmi = true;
log::trace!("NMI Edge Detected: {}", self.cycle);
}
self.prev_nmi_pending = nmi_pending;
self.irq = self.bus.irqs_pending();
self.prev_run_irq = self.run_irq;
self.run_irq = !self.irq.is_empty() && !self.status.intersects(Status::I);
if self.run_irq {
log::trace!("IRQ Level Detected: {}: {:?}", self.cycle, self.irq);
}
if self.bus.dmc_dma() {
self.dmc_dma = true;
self.halt = true;
self.dummy_read = true;
}
}
fn start_cycle(&mut self, cycle: Cycle) {
self.master_clock += if cycle == Cycle::Read {
self.start_clocks - 1
} else {
self.start_clocks + 1
};
self.cycle = self.cycle.wrapping_add(1);
if self.cycle_accurate {
self.bus.clock_to(self.master_clock - Self::PPU_OFFSET);
self.bus.clock();
}
}
fn end_cycle(&mut self, cycle: Cycle) {
self.master_clock += if cycle == Cycle::Read {
self.end_clocks + 1
} else {
self.end_clocks - 1
};
if self.cycle_accurate {
self.bus.clock_to(self.master_clock - Self::PPU_OFFSET);
}
self.handle_interrupts();
}
fn process_dma_cycle(&mut self) {
if self.halt {
self.halt = false;
} else if self.dummy_read {
self.dummy_read = false;
}
self.start_cycle(Cycle::Read);
}
fn handle_dma(&mut self, addr: u16) {
self.start_cycle(Cycle::Read);
self.bus.read(addr, Access::Dummy);
self.end_cycle(Cycle::Read);
self.halt = false;
let skip_dummy_reads = addr == 0x4016 || addr == 0x4017;
let oam_base_addr = self.bus.oam_dma_addr();
let mut oam_offset = 0;
let mut oam_dma_count = 0;
let mut read_val = 0;
while self.bus.oam_dma() || self.dmc_dma {
if self.cycle & 0x01 == 0x00 {
if self.dmc_dma && !self.halt && !self.dummy_read {
self.process_dma_cycle();
read_val = self.bus.read(self.bus.dmc_dma_addr(), Access::Dummy);
self.end_cycle(Cycle::Read);
self.bus.load_dmc_buffer(read_val);
self.dmc_dma = false;
} else if self.bus.oam_dma() {
self.process_dma_cycle();
read_val = self.bus.read(oam_base_addr + oam_offset, Access::Dummy);
self.end_cycle(Cycle::Read);
oam_offset += 1;
oam_dma_count += 1;
} else {
debug_assert!(self.halt || self.dummy_read);
self.process_dma_cycle();
if !skip_dummy_reads {
self.bus.read(addr, Access::Dummy); }
self.end_cycle(Cycle::Read);
}
} else if self.bus.oam_dma() && oam_dma_count & 0x01 == 0x01 {
self.process_dma_cycle();
self.bus.write(0x2004, read_val, Access::Dummy);
self.end_cycle(Cycle::Read);
oam_dma_count += 1;
if oam_dma_count == 0x200 {
self.bus.oam_dma_finish();
}
} else {
self.process_dma_cycle();
if !skip_dummy_reads {
self.bus.read(addr, Access::Dummy); }
self.end_cycle(Cycle::Read);
}
}
}
#[inline]
fn set_zn_status(&mut self, val: u8) {
self.status.set(Status::Z, val == 0x00);
self.status.set(Status::N, val & 0x80 == 0x80);
}
#[inline]
const fn status_bit(&self, reg: Status) -> u8 {
self.status.intersection(reg).bits()
}
#[inline]
fn push(&mut self, val: u8) {
self.write(Self::SP_BASE | u16::from(self.sp), val, Access::Write);
self.sp = self.sp.wrapping_sub(1);
}
#[must_use]
#[inline]
fn pop(&mut self) -> u8 {
self.sp = self.sp.wrapping_add(1);
self.read(Self::SP_BASE | u16::from(self.sp), Access::Read)
}
#[must_use]
#[inline]
pub fn peek_stack(&self) -> u8 {
self.peek(
Self::SP_BASE | u16::from(self.sp.wrapping_add(1)),
Access::Dummy,
)
}
#[must_use]
#[inline]
pub fn peek_stack_u16(&self) -> u16 {
let lo = self.peek(Self::SP_BASE | u16::from(self.sp), Access::Dummy);
let hi = self.peek(
Self::SP_BASE | u16::from(self.sp.wrapping_add(1)),
Access::Dummy,
);
u16::from_le_bytes([lo, hi])
}
#[inline]
fn push_u16(&mut self, val: u16) {
let [lo, hi] = val.to_le_bytes();
self.push(hi);
self.push(lo);
}
#[inline]
fn pop_u16(&mut self) -> u16 {
let lo = self.pop();
let hi = self.pop();
u16::from_le_bytes([lo, hi])
}
fn fetch_data(&mut self) {
let mode = self.instr.addr_mode();
self.fetched_data = match mode {
IMP | ACC => self.acc,
ABX | ABY | IDY => {
match self.instr.op() {
LDA | LDX | LDY | EOR | AND | ORA | ADC | SBC | CMP | BIT | LAX | NOP | IGN
| LAS => {
let reg = match mode {
ABX => self.x,
ABY | IDY => self.y,
_ => unreachable!("not possible"),
};
if (self.abs_addr & 0x00FF) < u16::from(reg) {
self.read(self.abs_addr, Access::Read)
} else {
self.fetched_data
}
}
_ => self.read(self.abs_addr, Access::Read), }
}
_ => self.read(self.abs_addr, Access::Read), };
}
#[inline]
fn write_fetched(&mut self, val: u8) {
match self.instr.addr_mode() {
IMP | ACC => self.acc = val,
IMM => (), _ => self.write(self.abs_addr, val, Access::Write),
}
}
#[must_use]
#[inline]
fn read_instr(&mut self) -> u8 {
let val = self.read(self.pc, Access::Read);
self.pc = self.pc.wrapping_add(1);
val
}
#[must_use]
#[inline]
fn read_instr_u16(&mut self) -> u16 {
let lo = self.read_instr();
let hi = self.read_instr();
u16::from_le_bytes([lo, hi])
}
#[must_use]
#[inline]
pub fn read_u16(&mut self, addr: u16) -> u16 {
let lo = self.read(addr, Access::Read);
let hi = self.read(addr.wrapping_add(1), Access::Read);
u16::from_le_bytes([lo, hi])
}
#[must_use]
#[inline]
pub fn peek_u16(&self, addr: u16) -> u16 {
let lo = self.peek(addr, Access::Dummy);
let hi = self.peek(addr.wrapping_add(1), Access::Dummy);
u16::from_le_bytes([lo, hi])
}
#[must_use]
#[inline]
fn read_zp_u16(&mut self, addr: u8) -> u16 {
let lo = self.read(addr.into(), Access::Read);
let hi = self.read(addr.wrapping_add(1).into(), Access::Read);
u16::from_le_bytes([lo, hi])
}
#[must_use]
#[inline]
fn peek_zp_u16(&self, addr: u8) -> u16 {
let lo = self.peek(addr.into(), Access::Dummy);
let hi = self.peek(addr.wrapping_add(1).into(), Access::Dummy);
u16::from_le_bytes([lo, hi])
}
pub fn disassemble(&mut self, pc: &mut u16) {
let opcode = self.peek(*pc, Access::Dummy);
let instr = Cpu::INSTRUCTIONS[opcode as usize];
let mut bytes = Vec::with_capacity(3);
self.disasm.clear();
let _ = write!(self.disasm, "{pc:04X} ");
bytes.push(opcode);
let mut addr = pc.wrapping_add(1);
let mode = match instr.addr_mode() {
IMM => {
bytes.push(self.peek(addr, Access::Dummy));
addr = addr.wrapping_add(1);
format!(" #${:02X}", bytes[1])
}
ZP0 => {
bytes.push(self.peek(addr, Access::Dummy));
addr = addr.wrapping_add(1);
let val = self.peek(bytes[1].into(), Access::Dummy);
format!(" ${:02X} = #${val:02X}", bytes[1])
}
ZPX => {
bytes.push(self.peek(addr, Access::Dummy));
addr = addr.wrapping_add(1);
let x_offset = bytes[1].wrapping_add(self.x);
let val = self.peek(x_offset.into(), Access::Dummy);
format!(" ${:02X},X @ ${x_offset:02X} = #${val:02X}", bytes[1])
}
ZPY => {
bytes.push(self.peek(addr, Access::Dummy));
addr = addr.wrapping_add(1);
let y_offset = bytes[1].wrapping_add(self.y);
let val = self.peek(y_offset.into(), Access::Dummy);
format!(" ${:02X},Y @ ${y_offset:02X} = #${val:02X}", bytes[1])
}
ABS => {
bytes.push(self.peek(addr, Access::Dummy));
bytes.push(self.peek(addr.wrapping_add(1), Access::Dummy));
let abs_addr = self.peek_u16(addr);
addr = addr.wrapping_add(2);
if instr.op() == JMP || instr.op() == JSR {
format!(" ${abs_addr:04X}")
} else {
let val = self.peek(abs_addr, Access::Dummy);
format!(" ${abs_addr:04X} = #${val:02X}")
}
}
ABX => {
bytes.push(self.peek(addr, Access::Dummy));
bytes.push(self.peek(addr.wrapping_add(1), Access::Dummy));
let abs_addr = self.peek_u16(addr);
addr = addr.wrapping_add(2);
let x_offset = abs_addr.wrapping_add(self.x.into());
let val = self.peek(x_offset, Access::Dummy);
format!(" ${abs_addr:04X},X @ ${x_offset:04X} = #${val:02X}")
}
ABY => {
bytes.push(self.peek(addr, Access::Dummy));
bytes.push(self.peek(addr.wrapping_add(1), Access::Dummy));
let abs_addr = self.peek_u16(addr);
addr = addr.wrapping_add(2);
let y_offset = abs_addr.wrapping_add(self.y.into());
let val = self.peek(y_offset, Access::Dummy);
format!(" ${abs_addr:04X},Y @ ${y_offset:04X} = #${val:02X}")
}
IND => {
bytes.push(self.peek(addr, Access::Dummy));
bytes.push(self.peek(addr.wrapping_add(1), Access::Dummy));
let abs_addr = self.peek_u16(addr);
addr = addr.wrapping_add(2);
let lo = self.peek(abs_addr, Access::Dummy);
let hi = if abs_addr & 0x00FF == 0x00FF {
self.peek(abs_addr & 0xFF00, Access::Dummy)
} else {
self.peek(abs_addr + 1, Access::Dummy)
};
let val = u16::from_le_bytes([lo, hi]);
format!(" (${abs_addr:04X}) = ${val:04X}")
}
IDX => {
bytes.push(self.peek(addr, Access::Dummy));
addr = addr.wrapping_add(1);
let x_offset = bytes[1].wrapping_add(self.x);
let abs_addr = self.peek_zp_u16(x_offset);
let val = self.peek(abs_addr, Access::Dummy);
format!(" (${:02X},X) @ ${abs_addr:04X} = #${val:02X}", bytes[1])
}
IDY => {
bytes.push(self.peek(addr, Access::Dummy));
addr = addr.wrapping_add(1);
let abs_addr = self.peek_zp_u16(bytes[1]);
let y_offset = abs_addr.wrapping_add(self.y.into());
let val = self.peek(y_offset, Access::Dummy);
format!(" (${:02X}),Y @ ${y_offset:04X} = #${val:02X}", bytes[1])
}
REL => {
bytes.push(self.peek(addr, Access::Dummy));
let mut rel_addr = self.peek(addr, Access::Dummy).into();
addr = addr.wrapping_add(1);
if rel_addr & 0x80 == 0x80 {
rel_addr |= 0xFF00;
}
format!(" ${:04X}", addr.wrapping_add(rel_addr))
}
ACC | IMP => "".to_string(),
};
*pc = addr;
for byte in &bytes {
let _ = write!(self.disasm, "{byte:02X} ");
}
for _ in 0..(3 - bytes.len()) {
self.disasm.push_str(" ");
}
let _ = write!(self.disasm, "{instr:?}{mode}");
}
pub fn trace_instr(&mut self) {
let mut pc = self.pc;
self.disassemble(&mut pc);
let status_str = |status: Status, set: char, clear: char| {
if self.status.contains(status) {
set
} else {
clear
}
};
log::trace!(
"{:<50} A:{:02X} X:{:02X} Y:{:02X} P:{}{}--{}{}{}{} SP:{:02X} PPU:{:3},{:3} CYC:{}",
self.disasm,
self.acc,
self.x,
self.y,
status_str(Status::N, 'N', 'n'),
status_str(Status::V, 'V', 'v'),
status_str(Status::D, 'd', 'd'),
status_str(Status::I, 'I', 'i'),
status_str(Status::Z, 'Z', 'z'),
status_str(Status::C, 'C', 'c'),
self.sp,
self.bus.ppu_cycle(),
self.bus.ppu_scanline(),
self.cycle,
);
}
#[must_use]
#[inline]
const fn pages_differ(addr1: u16, addr2: u16) -> bool {
(addr1 & 0xFF00) != (addr2 & 0xFF00)
}
}
impl Cpu {
pub fn clock_inspect<F>(&mut self, mut inspect: F) -> usize
where
F: FnMut(&mut Cpu),
{
let start_cycle = self.cycle;
if log::log_enabled!(log::Level::Trace) {
self.trace_instr();
}
inspect(self);
let opcode = self.read_instr(); self.instr = Cpu::INSTRUCTIONS[opcode as usize];
match self.instr.addr_mode() {
IMM => self.imm(),
ZP0 => self.zp0(),
ZPX => self.zpx(),
ZPY => self.zpy(),
ABS => self.abs(),
ABX => self.abx(),
ABY => self.aby(),
IND => self.ind(),
IDX => self.idx(),
IDY => self.idy(),
REL => self.rel(),
ACC => self.acc(),
IMP => self.imp(),
};
match self.instr.op() {
ADC => self.adc(), AND => self.and(), ASL => self.asl(), BCC => self.bcc(), BCS => self.bcs(), BEQ => self.beq(), BIT => self.bit(), BMI => self.bmi(), BNE => self.bne(), BPL => self.bpl(), BRK => self.brk(), BVC => self.bvc(), BVS => self.bvs(), CLC => self.clc(), CLD => self.cld(), CLI => self.cli(), CLV => self.clv(), CMP => self.cmp(), CPX => self.cpx(), CPY => self.cpy(), DEC => self.dec(), DEX => self.dex(), DEY => self.dey(), EOR => self.eor(), INC => self.inc(), INX => self.inx(), INY => self.iny(), JMP => self.jmp(), JSR => self.jsr(), LDA => self.lda(), LDX => self.ldx(), LDY => self.ldy(), LSR => self.lsr(), NOP => self.nop(), SKB => self.skb(), IGN => self.ign(), ORA => self.ora(), PHA => self.pha(), PHP => self.php(), PLA => self.pla(), PLP => self.plp(), ROL => self.rol(), ROR => self.ror(), RTI => self.rti(), RTS => self.rts(), SBC => self.sbc(), SEC => self.sec(), SED => self.sed(), SEI => self.sei(), STA => self.sta(), STX => self.stx(), STY => self.sty(), TAX => self.tax(), TAY => self.tay(), TSX => self.tsx(), TXA => self.txa(), TXS => self.txs(), TYA => self.tya(), ISB => self.isb(), DCP => self.dcp(), AXS => self.axs(), LAS => self.las(), LAX => self.lax(), AHX => self.ahx(), SAX => self.sax(), XAA => self.xaa(), SXA => self.sxa(), RRA => self.rra(), TAS => self.tas(), SYA => self.sya(), ARR => self.arr(), SRE => self.sre(), ALR => self.alr(), RLA => self.rla(), ANC => self.anc(), SLO => self.slo(), XXX => self.xxx(), }
if self.prev_run_irq || self.prev_nmi {
self.irq();
}
if !self.cycle_accurate {
self.bus.clock_to(self.master_clock - Self::PPU_OFFSET);
let cycles = self.cycle - start_cycle;
for _ in 0..cycles {
self.bus.clock();
}
self.handle_interrupts();
}
self.cycle - start_cycle
}
}
impl Clock for Cpu {
fn clock(&mut self) -> usize {
self.clock_inspect(|_| {})
}
}
impl Mem for Cpu {
fn read(&mut self, addr: u16, access: Access) -> u8 {
if self.halt || self.bus.oam_dma() {
self.handle_dma(addr);
}
self.start_cycle(Cycle::Read);
let val = self.bus.read(addr, access);
self.end_cycle(Cycle::Read);
val
}
fn peek(&self, addr: u16, access: Access) -> u8 {
self.bus.peek(addr, access)
}
fn write(&mut self, addr: u16, val: u8, access: Access) {
self.start_cycle(Cycle::Write);
self.bus.write(addr, val, access);
self.end_cycle(Cycle::Write);
}
}
impl Regional for Cpu {
#[inline]
fn region(&self) -> NesRegion {
self.region
}
fn set_region(&mut self, region: NesRegion) {
let (clock_divider, start_clocks, end_clocks) = match region {
NesRegion::Ntsc => (12, 6, 6),
NesRegion::Pal => (16, 8, 8),
NesRegion::Dendy => (15, 7, 8),
};
self.region = region;
self.clock_divider = clock_divider;
self.start_clocks = start_clocks;
self.end_clocks = end_clocks;
self.bus.set_region(region);
}
}
impl Reset for Cpu {
fn reset(&mut self, kind: Kind) {
log::trace!("{:?} RESET", kind);
match kind {
Kind::Soft => {
self.status.set(Status::I, true);
self.sp = self.sp.wrapping_sub(0x03);
}
Kind::Hard => {
self.acc = 0x00;
self.x = 0x00;
self.y = 0x00;
self.status = Self::POWER_ON_STATUS;
self.sp = Self::POWER_ON_SP;
}
}
self.bus.reset(kind);
self.cycle = 0;
self.master_clock = 0;
self.irq = Irq::empty();
self.run_irq = false;
self.prev_run_irq = false;
self.nmi = false;
self.prev_nmi = false;
self.prev_nmi_pending = false;
self.corrupted = false;
self.halt = false;
self.dummy_read = false;
let lo = self.bus.read(Self::RESET_VECTOR, Access::Read);
let hi = self.bus.read(Self::RESET_VECTOR + 1, Access::Read);
self.pc = u16::from_le_bytes([lo, hi]);
for _ in 0..7 {
self.start_cycle(Cycle::Read);
self.end_cycle(Cycle::Read);
}
}
}
impl fmt::Debug for Cpu {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
f.debug_struct("Cpu")
.field("cycle", &self.cycle)
.field("pc", &format_args!("${:04X}", self.pc))
.field("sp", &format_args!("${:02X}", self.sp))
.field("acc", &format_args!("${:02X}", self.acc))
.field("x", &format_args!("${:02X}", self.x))
.field("y", &format_args!("${:02X}", self.y))
.field("status", &self.status)
.field("bus", &self.bus)
.field("instr", &self.instr)
.field("abs_addr", &format_args!("${:04X}", self.abs_addr))
.field("rel_addr", &format_args!("${:04X}", self.rel_addr))
.field("fetched_data", &format_args!("${:02X}", self.fetched_data))
.field("irq", &self.irq)
.field("nmi", &self.nmi)
.field("prev_nmi", &self.prev_nmi)
.field("prev_nmi_pending", &self.prev_nmi_pending)
.field("corrupted", &self.corrupted)
.field("run_irq", &self.run_irq)
.field("last_run_irq", &self.prev_run_irq)
.field("halt", &self.halt)
.field("dummy_read", &self.dummy_read)
.finish()
}
}
#[cfg(test)]
mod tests {
use crate::test_roms;
#[test]
fn cycle_timing() {
use super::*;
let mut cpu = Cpu::new(CpuBus::default());
let cart = Cart::empty();
cpu.load_cart(cart);
cpu.reset(Kind::Hard);
cpu.clock();
assert_eq!(cpu.cycle, 14, "cpu after power + one clock");
for instr in Cpu::INSTRUCTIONS.iter() {
let extra_cycle = match instr.op() {
BCC | BNE | BPL | BVC => 1,
_ => 0,
};
if instr.op() == XXX {
continue;
}
cpu.reset(Kind::Hard);
cpu.bus.write(0x0000, instr.opcode(), Access::Write);
cpu.clock();
let cpu_cyc = 7 + instr.cycles() + extra_cycle;
assert_eq!(
cpu.cycle,
cpu_cyc,
"cpu ${:02X} {:?} #{:?}",
instr.opcode(),
instr.op(),
instr.addr_mode()
);
}
}
test_roms!(
"test_roms/cpu",
branch_backward,
nestest,
ram_after_reset,
regs_after_reset,
branch_basics,
branch_forward,
dummy_reads,
dummy_writes_oam,
dummy_writes_ppumem,
exec_space_apu,
exec_space_ppuio,
flag_concurrency,
instr_abs,
instr_abs_xy,
instr_basics,
instr_branches,
instr_brk,
instr_imm,
instr_imp,
instr_ind_x,
instr_ind_y,
instr_jmp_jsr,
instr_misc,
instr_rti,
instr_rts,
instr_special,
instr_stack,
instr_timing,
instr_zp,
instr_zp_xy,
int_branch_delays_irq,
int_cli_latency,
int_irq_and_dma,
int_nmi_and_brk,
int_nmi_and_irq,
#[ignore = "need to fix frame timing"]
overclock,
sprdma_and_dmc_dma,
sprdma_and_dmc_dma_512,
timing_test,
);
}