use crate::{
bus::Bus,
common::{Addr, Byte, Clocked, Powered},
mapper::Mapper,
memory::{MemRead, MemWrite},
serialization::Savable,
NesResult,
};
use instr::{AddrMode::*, Instr, Operation::*, INSTRUCTIONS};
use log::{log_enabled, trace, Level};
use std::{
collections::VecDeque,
fmt,
io::{Read, Write},
};
pub mod instr;
pub const MASTER_CLOCK_RATE: f32 = 21_477_270.0;
pub const CPU_CLOCK_RATE: f32 = MASTER_CLOCK_RATE / 12.0;
const NMI_ADDR: Addr = 0xFFFA;
const IRQ_ADDR: Addr = 0xFFFE;
const RESET_ADDR: Addr = 0xFFFC;
const POWER_ON_STATUS: Byte = 0x24;
const SP_BASE: Addr = 0x0100;
const PC_LOG_LEN: usize = 20;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Irq {
Reset = 1,
Mapper = 1 << 1,
FrameCounter = 1 << 2,
Dmc = 1 << 3,
}
pub enum StatusRegs {
C = 1,
Z = 1 << 1,
I = 1 << 2,
D = 1 << 3,
B = 1 << 4,
U = 1 << 5,
V = 1 << 6,
N = 1 << 7,
}
use StatusRegs::*;
#[derive(Clone)]
pub struct Cpu {
pub cycle_count: usize,
pub step: usize,
pub pc: Addr,
pub sp: Byte,
pub acc: Byte,
pub x: Byte,
pub y: Byte,
pub status: Byte,
pub bus: Bus,
pub pc_log: VecDeque<Addr>,
pub stall: usize,
pub instr: Instr,
pub abs_addr: Addr,
pub rel_addr: Addr,
pub fetched_data: Byte,
pub irq_pending: u8,
pub nmi_pending: bool,
last_irq: bool,
last_nmi: bool,
}
impl Cpu {
pub fn init(bus: Bus) -> Self {
Self {
cycle_count: 0,
step: 0,
pc: 0x0000,
sp: 0x00,
acc: 0x00,
x: 0x00,
y: 0x00,
status: POWER_ON_STATUS,
bus,
pc_log: VecDeque::with_capacity(PC_LOG_LEN),
stall: 0,
instr: INSTRUCTIONS[0x00],
abs_addr: 0x0000,
rel_addr: 0x0000,
fetched_data: 0x00,
irq_pending: Irq::Reset as u8,
nmi_pending: false,
last_irq: false,
last_nmi: false,
}
}
pub fn next_instr(&self) -> Instr {
let opcode = self.peek(self.pc);
INSTRUCTIONS[opcode as usize]
}
pub fn set_irq(&mut self, irq: Irq, val: bool) {
if val {
self.irq_pending |= irq as u8;
} else {
self.irq_pending &= !(irq as u8);
}
}
pub fn has_irq(&mut self, irq: Irq) -> bool {
(self.irq_pending & irq as u8) > 0
}
pub fn irq(&mut self) {
self.read(self.pc);
self.read(self.pc);
if self.has_irq(Irq::Reset) {
self.push_read_stackw(self.pc);
self.push_read_stackb((self.status | U as u8) & !(B as u8));
self.pc = self.readw(RESET_ADDR);
self.set_irq(Irq::Reset, false);
} else {
self.push_stackw(self.pc);
self.push_stackb((self.status | U as u8) & !(B as u8));
self.set_flag(I, true);
if self.has_irq(Irq::Reset) {
self.pc = self.readw(RESET_ADDR);
self.set_irq(Irq::Reset, false);
} else if self.last_nmi {
self.nmi_pending = false;
self.bus.ppu.nmi_pending = false;
self.pc = self.readw(NMI_ADDR);
} else {
self.pc = self.readw(IRQ_ADDR);
}
if self.last_nmi {
self.last_nmi = false;
}
}
}
pub fn set_nmi(&mut self, val: bool) {
self.nmi_pending = val;
self.bus.ppu.nmi_pending = val;
}
fn nmi(&mut self) {
self.read(self.pc);
self.read(self.pc);
self.push_stackw(self.pc);
self.push_stackb((self.status | U as u8) & !(B as u8));
self.set_flag(I, true);
self.pc = self.readw(NMI_ADDR);
self.nmi_pending = false;
self.bus.ppu.nmi_pending = false;
}
fn run_cycle(&mut self) {
self.cycle_count = self.cycle_count.wrapping_add(1);
self.last_nmi = self.nmi_pending;
self.last_irq = self.irq_pending > 0 && self.get_flag(I) == 0;
let ppu_cycles = self.bus.ppu.clock();
self.set_nmi(self.bus.ppu.nmi_pending);
for _ in 0..ppu_cycles {
let irq_pending = {
let _ = self.bus.mapper.clock();
self.bus.mapper.irq_pending()
};
self.set_irq(Irq::Mapper, irq_pending);
}
let _ = self.bus.apu.clock();
self.set_irq(Irq::FrameCounter, self.bus.apu.irq_pending);
self.set_irq(Irq::Dmc, self.bus.apu.dmc.irq_pending);
}
fn set_flags_zn(&mut self, val: Byte) {
self.set_flag(Z, val == 0x00);
self.set_flag(N, val & 0x80 == 0x80);
}
fn get_flag(&self, flag: StatusRegs) -> u8 {
if (self.status & flag as u8) > 0 {
1
} else {
0
}
}
fn set_flag(&mut self, flag: StatusRegs, val: bool) {
if val {
self.status |= flag as u8;
} else {
self.status &= !(flag as u8);
}
}
fn push_stackb(&mut self, val: Byte) {
self.write(SP_BASE | Addr::from(self.sp), val);
self.sp = self.sp.wrapping_sub(1);
}
fn push_read_stackb(&mut self, _val: Byte) {
let _ = self.read(SP_BASE | Addr::from(self.sp));
self.sp = self.sp.wrapping_sub(1);
}
fn pop_stackb(&mut self) -> Byte {
self.sp = self.sp.wrapping_add(1);
self.read(SP_BASE | Addr::from(self.sp))
}
pub fn peek_stackb(&self) -> Byte {
let sp = self.sp.wrapping_add(1);
self.peek(SP_BASE | Addr::from(sp))
}
fn push_stackw(&mut self, val: Addr) {
let lo = (val & 0xFF) as Byte;
let hi = (val >> 8) as Byte;
self.push_stackb(hi);
self.push_stackb(lo);
}
fn push_read_stackw(&mut self, val: Addr) {
let lo = (val & 0xFF) as Byte;
let hi = (val >> 8) as Byte;
self.push_read_stackb(hi);
self.push_read_stackb(lo);
}
fn pop_stackw(&mut self) -> Addr {
let lo = Addr::from(self.pop_stackb());
let hi = Addr::from(self.pop_stackb());
hi << 8 | lo
}
pub fn peek_stackw(&self) -> Addr {
let sp = self.sp.wrapping_add(1);
let lo = Addr::from(self.peek(SP_BASE | Addr::from(sp)));
let sp = sp.wrapping_add(1);
let hi = Addr::from(self.peek(SP_BASE | Addr::from(sp)));
hi << 8 | lo
}
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,
_ => panic!("not possible"),
};
if (self.abs_addr & 0x00FF) < Addr::from(reg) {
self.read(self.abs_addr)
} else {
self.fetched_data
}
}
_ => self.read(self.abs_addr),
}
}
_ => self.read(self.abs_addr),
};
}
fn write_fetched(&mut self, val: Byte) {
match self.instr.addr_mode() {
IMP | ACC => self.acc = val,
IMM => (),
_ => self.write(self.abs_addr, val),
}
}
pub fn readw(&mut self, addr: Addr) -> Addr {
let lo = Addr::from(self.read(addr));
let hi = Addr::from(self.read(addr.wrapping_add(1)));
(hi << 8) | lo
}
pub fn peekw(&self, addr: Addr) -> Addr {
let lo = Addr::from(self.peek(addr));
let hi = Addr::from(self.peek(addr.wrapping_add(1)));
(hi << 8) | lo
}
fn readw_zp(&mut self, addr: Byte) -> Addr {
let lo = Addr::from(self.read(addr.into()));
let hi = Addr::from(self.read(addr.wrapping_add(1).into()));
(hi << 8) | lo
}
fn peekw_zp(&self, addr: Byte) -> Addr {
let lo = Addr::from(self.peek(addr.into()));
let hi = Addr::from(self.peek(addr.wrapping_add(1).into()));
(hi << 8) | lo
}
fn write_oamdma(&mut self, addr: Byte) {
let mut addr = Addr::from(addr) << 8;
let oam_addr = 0x2004;
self.run_cycle();
if self.cycle_count & 0x01 == 1 {
self.run_cycle();
}
for _ in 0..256 {
let val = self.read(addr);
self.write(oam_addr, val);
addr = addr.saturating_add(1);
}
}
pub fn disassemble(&self, pc: &mut Addr) -> String {
let opcode = self.peek(*pc);
let instr = INSTRUCTIONS[opcode as usize];
let mut bytes = Vec::new();
let mut disasm = String::with_capacity(50);
disasm.push_str(&format!("${:04X}:", pc));
bytes.push(self.peek(*pc));
*pc = pc.wrapping_add(1);
let mode = match instr.addr_mode() {
IMM => {
bytes.push(self.peek(*pc));
*pc = pc.wrapping_add(1);
format!("#${:02X}", bytes[1])
}
ZP0 => {
bytes.push(self.peek(*pc));
*pc = pc.wrapping_add(1);
let val = self.peek(bytes[1].into());
format!("${:02X} = #${:02X}", bytes[1], val)
}
ZPX => {
bytes.push(self.peek(*pc));
*pc = pc.wrapping_add(1);
let x_offset = bytes[1].wrapping_add(self.x);
let val = self.peek(x_offset.into());
format!("${:02X},X @ ${:04X} = #${:02X}", bytes[1], x_offset, val)
}
ZPY => {
bytes.push(self.peek(*pc));
*pc = pc.wrapping_add(1);
let y_offset = bytes[1].wrapping_add(self.y);
let val = self.peek(y_offset.into());
format!("${:02X},Y @ ${:02X} = #${:02X}", bytes[1], y_offset, val)
}
ABS => {
bytes.push(self.peek(*pc));
bytes.push(self.peek(pc.wrapping_add(1)));
let addr = self.peekw(*pc);
*pc = pc.wrapping_add(2);
if instr.op() == JMP || instr.op() == JSR {
format!("${:04X}", addr)
} else {
let val = self.peek(addr);
format!("${:04X} = #${:02X}", addr, val)
}
}
ABX => {
bytes.push(self.peek(*pc));
bytes.push(self.peek(pc.wrapping_add(1)));
let addr = self.peekw(*pc);
*pc = pc.wrapping_add(2);
let x_offset = addr.wrapping_add(self.x.into());
let val = self.peek(x_offset);
format!("${:04X},X @ ${:04X} = #${:02X}", addr, x_offset, val)
}
ABY => {
bytes.push(self.peek(*pc));
bytes.push(self.peek(pc.wrapping_add(1)));
let addr = self.peekw(*pc);
*pc = pc.wrapping_add(2);
let y_offset = addr.wrapping_add(self.y.into());
let val = self.peek(y_offset);
format!("${:04X},Y @ ${:04X} = #${:02X}", addr, y_offset, val)
}
IND => {
bytes.push(self.peek(*pc));
bytes.push(self.peek(pc.wrapping_add(1)));
let addr = self.peekw(*pc);
*pc = pc.wrapping_add(2);
let val = if addr & 0x00FF == 0x00FF {
(Addr::from(self.peek(addr & 0xFF00)) << 8) | Addr::from(self.peek(addr))
} else {
(Addr::from(self.peek(addr + 1)) << 8) | Addr::from(self.peek(addr))
};
format!("(${:04X}) = ${:04X}", addr, val)
}
IDX => {
bytes.push(self.peek(*pc));
*pc = pc.wrapping_add(1);
let x_offset = bytes[1].wrapping_add(self.x);
let addr = self.peekw_zp(x_offset);
let val = self.peek(addr);
format!("(${:02X},X) @ ${:04X} = #${:02X}", bytes[1], addr, val)
}
IDY => {
bytes.push(self.peek(*pc));
*pc = pc.wrapping_add(1);
let addr = self.peekw_zp(bytes[1]);
let y_offset = addr.wrapping_add(self.y.into());
let val = self.peek(y_offset);
format!("(${:02X}),Y @ ${:04X} = #${:02X}", bytes[1], y_offset, val)
}
REL => {
bytes.push(self.peek(*pc));
let mut rel_addr = self.peek(*pc).into();
*pc = pc.wrapping_add(1);
if rel_addr & 0x80 == 0x80 {
rel_addr |= 0xFF00;
}
format!("${:04X}", pc.wrapping_add(rel_addr))
}
ACC => "".to_string(),
IMP => "".to_string(),
};
for i in 0..3 {
if i < bytes.len() {
disasm.push_str(&format!("{:02X} ", bytes[i]));
} else {
disasm.push_str(&" ".to_string());
}
}
disasm.push_str(&format!("{:?} {}", instr, mode));
disasm
}
pub fn print_instruction(&mut self, mut pc: Addr) {
let disasm = self.disassemble(&mut pc);
let status_flags = vec!['n', 'v', '-', 'b', 'd', 'i', 'z', 'c'];
let mut status_str = String::with_capacity(8);
for (i, s) in status_flags.iter().enumerate() {
if ((self.status >> (7 - i)) & 1) > 0 {
status_str.push(s.to_ascii_uppercase());
} else {
status_str.push(*s);
}
}
trace!(
"{:<50} A:{:02X} X:{:02X} Y:{:02X} P:{} SP:{:02X} PPU:{:3},{:3} CYC:{}",
disasm,
self.acc,
self.x,
self.y,
status_str,
self.sp,
self.bus.ppu.cycle,
self.bus.ppu.scanline,
self.cycle_count,
);
}
fn pages_differ(&self, addr1: Addr, addr2: Addr) -> bool {
(addr1 & 0xFF00) != (addr2 & 0xFF00)
}
}
impl Clocked for Cpu {
fn clock(&mut self) -> usize {
if self.stall > 0 {
self.cycle_count = self.cycle_count.wrapping_add(1);
self.stall -= 1;
return 1;
}
let start_cycles = self.cycle_count;
if self.has_irq(Irq::Reset) {
self.irq();
} else if self.last_nmi {
self.nmi();
} else if self.last_irq {
self.irq();
}
if log_enabled!(Level::Trace) {
self.print_instruction(self.pc);
}
self.pc_log.push_front(self.pc);
if self.pc_log.len() > PC_LOG_LEN {
self.pc_log.pop_back();
}
let opcode = self.read(self.pc);
self.pc = self.pc.wrapping_add(1);
self.instr = 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(),
}
self.step += 1;
self.cycle_count - start_cycles
}
}
impl MemRead for Cpu {
fn read(&mut self, addr: Addr) -> Byte {
self.run_cycle();
self.bus.read(addr)
}
fn peek(&self, addr: Addr) -> Byte {
self.bus.peek(addr)
}
}
impl MemWrite for Cpu {
fn write(&mut self, addr: Addr, val: Byte) {
if addr == 0x4014 {
self.write_oamdma(val);
} else {
self.run_cycle();
self.bus.write(addr, val);
}
}
}
impl Powered for Cpu {
fn power_on(&mut self) {
self.cycle_count = 0;
self.stall = 0;
self.pc_log.clear();
self.set_irq(Irq::Reset, true);
}
fn reset(&mut self) {
self.bus.reset();
self.set_flag(I, true);
self.power_on();
}
fn power_cycle(&mut self) {
self.bus.power_cycle();
self.acc = 0x00;
self.x = 0x00;
self.y = 0x00;
self.status = POWER_ON_STATUS;
self.sp = 0;
self.power_on();
}
}
impl Savable for Cpu {
fn save<F: Write>(&self, fh: &mut F) -> NesResult<()> {
self.cycle_count.save(fh)?;
self.step.save(fh)?;
self.pc.save(fh)?;
self.sp.save(fh)?;
self.acc.save(fh)?;
self.x.save(fh)?;
self.y.save(fh)?;
self.status.save(fh)?;
self.bus.save(fh)?;
self.stall.save(fh)?;
self.instr.save(fh)?;
self.abs_addr.save(fh)?;
self.rel_addr.save(fh)?;
self.fetched_data.save(fh)?;
self.irq_pending.save(fh)?;
self.nmi_pending.save(fh)?;
self.last_irq.save(fh)?;
self.last_nmi.save(fh)?;
Ok(())
}
fn load<F: Read>(&mut self, fh: &mut F) -> NesResult<()> {
self.cycle_count.load(fh)?;
self.step.load(fh)?;
self.pc.load(fh)?;
self.sp.load(fh)?;
self.acc.load(fh)?;
self.x.load(fh)?;
self.y.load(fh)?;
self.status.load(fh)?;
self.bus.load(fh)?;
self.stall.load(fh)?;
self.instr.load(fh)?;
self.abs_addr.load(fh)?;
self.rel_addr.load(fh)?;
self.fetched_data.load(fh)?;
self.irq_pending.load(fh)?;
self.nmi_pending.load(fh)?;
self.last_irq.load(fh)?;
self.last_nmi.load(fh)?;
Ok(())
}
}
impl fmt::Debug for Cpu {
fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
write!(
f,
"Cpu {{ {:04X} A:{:02X} X:{:02X} Y:{:02X} P:{:02X} SP:{:02X} CYC:{} rel_addr:{} }}",
self.pc,
self.acc,
self.x,
self.y,
self.status,
self.sp,
self.cycle_count,
self.rel_addr
)
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "no-randomize-ram")]
fn cpu_cycle_timing() {
use super::*;
let mut cpu = Cpu::init(Bus::new());
cpu.power_on();
cpu.clock();
assert_eq!(cpu.cycle_count, 14, "cpu after power + one clock");
assert_eq!(cpu.bus.ppu.cycle_count, 42, "ppu after power + one clock");
for instr in INSTRUCTIONS.iter() {
let extra_cycle = match instr.op() {
BCC | BNE | BPL | BVC => 1,
_ => 0,
};
if instr.op() == XXX {
continue;
}
cpu.pc = 0;
cpu.cycle_count = 0;
cpu.bus.ppu.cycle_count = 0;
cpu.status = POWER_ON_STATUS;
cpu.acc = 0;
cpu.x = 0;
cpu.y = 0;
cpu.bus.wram.write(0x0000, instr.opcode());
cpu.clock();
let cpu_cyc = instr.cycles() + extra_cycle;
let ppu_cyc = 3 * (instr.cycles() + extra_cycle);
assert_eq!(
cpu.cycle_count,
cpu_cyc,
"cpu ${:02X} {:?} #{:?}",
instr.opcode(),
instr.op(),
instr.addr_mode()
);
assert_eq!(
cpu.bus.ppu.cycle_count,
ppu_cyc as usize,
"ppu ${:02X} {:?} #{:?}",
instr.opcode(),
instr.op(),
instr.addr_mode()
);
}
}
}