Compare commits

...

4 Commits

Author SHA1 Message Date
b5e1d1a4c3 Fix 'oops cycle' timing, to pass timing test rom
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 25s
2026-01-20 00:14:17 -06:00
2e5e2ed1e7 Fix most basic timing issues 2026-01-19 17:30:34 -06:00
42c3af28b4 Add run to address to debugger 2026-01-19 17:28:52 -06:00
ac745f60e9 Implement more features for PPU and APU 2026-01-19 17:28:12 -06:00
5 changed files with 376 additions and 102 deletions

View File

@@ -4,7 +4,7 @@ bitfield::bitfield! {
pub struct DutyVol(u8);
impl Debug;
duty, set_duty: 7, 6;
r#loop, set_loop: 5;
length_counter_halt, set_length_counter_halt: 5;
const_vol, set_const_vol: 4;
volume, set_volume: 3, 0;
}
@@ -27,6 +27,62 @@ bitfield::bitfield! {
struct PulseChannel {
enabled: bool,
duty_vol: DutyVol,
sweep: Sweep,
timer_low: u8,
length_timer_high: LengthTimerHigh,
cur_time: u16,
cur_length: u8,
}
impl PulseChannel {
pub fn new() -> Self {
Self {
enabled: false,
duty_vol: DutyVol(0),
sweep: Sweep(0),
timer_low: 0,
length_timer_high: LengthTimerHigh(0),
cur_time: 0,
cur_length: 0,
}
}
fn use_envelope(&self) -> bool {
!self.duty_vol.const_vol()
}
fn volume(&self) -> u8 {
self.duty_vol.volume()
}
fn timer(&self) -> u16 {
self.timer_low as u16 | ((self.length_timer_high.timer_high() as u16) << 8)
}
fn length(&self) -> u8 {
self.length_timer_high.length()
}
pub fn reset(&mut self) {
// TODO
self.cur_time = self.timer();
}
pub fn clock(&mut self) {
if self.cur_time == 0 {
self.cur_time = self.timer();
} else {
self.cur_time -= 1;
}
// TODO
}
pub fn q_frame_clock(&mut self) {
if !self.duty_vol.length_counter_halt() && self.cur_length > 0 {
self.cur_length -= 1;
}
}
pub fn h_frame_clock(&mut self) {
if !self.duty_vol.length_counter_halt() && self.cur_length > 0 {
self.cur_length -= 1;
}
}
}
bitfield::bitfield! {
@@ -46,6 +102,12 @@ struct DeltaChannel {
enabled: bool,
}
impl DeltaChannel {
pub fn int(&self) -> bool {
false
}
}
struct FrameCounter {
count: usize,
mode_5_step: bool,
@@ -76,8 +138,8 @@ impl std::fmt::Debug for APU {
impl APU {
pub fn init() -> Self {
Self {
pulse_1: PulseChannel { enabled: false },
pulse_2: PulseChannel { enabled: false },
pulse_1: PulseChannel::new(),
pulse_2: PulseChannel::new(),
triangle: TriangleChannel { enabled: false },
noise: NoiseChannel { enabled: false },
dmc: DeltaChannel { enabled: false },
@@ -90,18 +152,38 @@ impl APU {
}
}
pub fn read_reg(&mut self, offset: u16) -> u8 {
// println!("APU read: {offset:02X}");
match offset {
0x15 => {
let val = (u8::from(self.dmc.int()) << 7)
| (u8::from(self.frame_counter.irq) << 6)
| (u8::from(self.dmc.enabled) << 4)
| (u8::from(self.noise.enabled) << 3)
| (u8::from(self.triangle.enabled) << 2)
| (u8::from(self.pulse_2.enabled) << 1)
| (u8::from(self.pulse_1.enabled) << 0);
self.frame_counter.irq = false;
val
}
_ => panic!("No register at {:X}", offset),
}
}
pub fn write_reg(&mut self, offset: u16, val: u8) {
// println!("APU write: {offset:02X} <= {val:02X}");
match offset {
0x15 => {
self.dmc.enabled = val & 0b0001_0000 != 0;
self.noise.enabled = val & 0b0000_1000 != 0;
self.triangle.enabled = val & 0b0000_0100 != 0;
self.pulse_2.enabled = val & 0b0000_0010 != 0;
self.pulse_1.enabled = val & 0b0000_0001 != 0;
0x00 => self.pulse_1.duty_vol.0 = val,
0x01 => self.pulse_1.sweep.0 = val,
0x02 => self.pulse_1.timer_low = val,
0x03 => {
self.pulse_1.length_timer_high.0 = val;
self.pulse_1.reset();
}
0x04 => self.pulse_2.duty_vol.0 = val,
0x05 => self.pulse_2.sweep.0 = val,
0x06 => self.pulse_2.timer_low = val,
0x07 => {
self.pulse_2.length_timer_high.0 = val;
self.pulse_2.reset();
}
0x10 => {
assert_eq!(val, 0x00);
@@ -110,27 +192,60 @@ impl APU {
0x11 => {
// TODO: load dmc counter with (val & 7F)
}
_ => (),
// _ => panic!("No register at {:X}", offset),
0x15 => {
self.dmc.enabled = val & 0b0001_0000 != 0;
self.noise.enabled = val & 0b0000_1000 != 0;
self.triangle.enabled = val & 0b0000_0100 != 0;
self.pulse_2.enabled = val & 0b0000_0010 != 0;
self.pulse_1.enabled = val & 0b0000_0001 != 0;
}
0x17 => {
self.frame_counter.mode_5_step = val & 0b1000_0000 == 0;
self.frame_counter.interrupt_enabled = val & 0b0100_0000 == 0;
if !self.frame_counter.interrupt_enabled {
self.frame_counter.irq = false;
}
}
// _ => (),
_ => panic!("No register at {:X}", offset),
}
}
fn q_frame_clock(&mut self) {
self.pulse_1.q_frame_clock();
self.pulse_2.q_frame_clock(); // TODO: clock all
}
fn h_frame_clock(&mut self) {
self.pulse_1.q_frame_clock();
self.pulse_1.h_frame_clock();
self.pulse_2.q_frame_clock();
self.pulse_2.h_frame_clock();
}
pub fn run_one_clock_cycle(&mut self, ppu_cycle: usize) -> bool {
if ppu_cycle % 6 == 1 {
// APU Frame Counter clock cycle
if !self.frame_counter.mode_5_step
&& self.frame_counter.interrupt_enabled
&& self.frame_counter.count == 14914
{
self.frame_counter.irq = true;
} else if !self.frame_counter.mode_5_step
&& self.frame_counter.interrupt_enabled
&& self.frame_counter.count == 14915
{
self.frame_counter.irq = true;
if self.frame_counter.mode_5_step {
todo!()
} else {
if self.frame_counter.count == 3728 {
self.q_frame_clock();
} else if self.frame_counter.count == 7456 {
self.h_frame_clock();
} else if self.frame_counter.count == 11185 {
self.q_frame_clock();
} else if self.frame_counter.count == 14914 {
self.frame_counter.irq = self.frame_counter.interrupt_enabled;
self.h_frame_clock();
} else if self.frame_counter.count == 14915 {
self.frame_counter.count = 0;
self.frame_counter.irq = self.frame_counter.interrupt_enabled;
}
}
self.frame_counter.count += 1;
self.pulse_1.clock();
self.pulse_2.clock();
}
false
}
@@ -144,8 +259,6 @@ impl APU {
self.frame_counter.irq
}
pub fn irq_waiting(&mut self) -> bool {
let res = self.frame_counter.irq;
self.frame_counter.irq = false;
res
self.frame_counter.irq
}
}

View File

@@ -1,13 +1,28 @@
use std::rc::Rc;
use iced::{
Element,
Length::{self, Fill},
Point, Renderer, Size, Theme,
advanced::{
layout::Node, widget::{
tree::{State, Tag}, Tree
}, Widget
}, mouse, widget::{
self, button, canvas::{Frame, Program}, checkbox, column, container::bordered_box, image, number_input, row, rule::horizontal, scrollable, text, Canvas, Text
}, window::Event, Element, Length::{self, Fill}, Point, Renderer, Size, Theme
Widget,
layout::Node,
widget::{
Tree,
tree::{State, Tag},
},
},
mouse,
widget::{
self, Canvas, Text, button,
canvas::{Frame, Program},
checkbox, column,
container::bordered_box,
image, number_input, hex_input, row,
rule::horizontal,
scrollable, text,
},
window::Event,
};
use crate::{CycleResult, NES, PPU};
@@ -20,6 +35,7 @@ pub struct DebuggerState {
scan_lines: usize,
to_scan_line: usize,
frames: usize,
breakpoint: usize,
}
#[derive(Debug, Clone)]
@@ -38,6 +54,8 @@ pub enum DebuggerMessage {
RunToScanLine,
SetFrames(usize),
RunFrames,
SetBreakpoint(usize),
RunBreakpoint,
}
pub fn hex16<'a, Theme, Renderer>(val: u16) -> Text<'a, Theme, Renderer>
@@ -78,6 +96,7 @@ impl DebuggerState {
scan_lines: 1,
to_scan_line: 1,
frames: 1,
breakpoint: 0xEA5A,
// cpu_cycles: 1,
}
}
@@ -126,6 +145,7 @@ impl DebuggerState {
labelled("Cycle", text(nes.ppu().pixel)),
labelled("Scanline", text(nes.ppu().scanline)),
labelled("PPU Cycle", text(nes.ppu().cycle)),
labelled("Frame", text(nes.ppu().frame_count)),
labelled("V:", hex16(nes.ppu().background.v)),
labelled("T:", hex16(nes.ppu().background.t)),
labelled("X:", hex8(nes.ppu().background.x)),
@@ -205,6 +225,12 @@ impl DebuggerState {
DebuggerMessage::SetFrames,
DebuggerMessage::RunFrames
),
run_type_hex(
"To Address:",
self.breakpoint,
DebuggerMessage::SetBreakpoint,
DebuggerMessage::RunBreakpoint
),
],
]
.spacing(5.),
@@ -257,6 +283,7 @@ impl DebuggerState {
DebuggerMessage::SetScanLines(n) => self.scan_lines = n,
DebuggerMessage::SetToScanLine(n) => self.to_scan_line = n,
DebuggerMessage::SetFrames(n) => self.frames = n,
DebuggerMessage::SetBreakpoint(n) => self.breakpoint = n,
DebuggerMessage::RunPPUCycles => Self::run_n_clock_cycles(nes, self.ppu_cycles),
DebuggerMessage::RunCPUCycles => Self::run_n_clock_cycles(nes, self.cpu_cycles * 3),
DebuggerMessage::RunInstructions => {
@@ -267,6 +294,7 @@ impl DebuggerState {
Self::run_until(nes, |_, n| n.ppu.scanline == self.to_scan_line, 1)
}
DebuggerMessage::RunFrames => Self::run_n_clock_cycles(nes, self.frames * 341 * 262),
DebuggerMessage::RunBreakpoint => Self::run_until(nes, |_, nes| nes.cpu.pc as usize == self.breakpoint, 1),
DebuggerMessage::Run => Self::run_until(nes, |_, nes| nes.halted, 1),
DebuggerMessage::Pause => todo!(),
}
@@ -288,6 +316,21 @@ fn run_type<'a, Message: Clone + 'a>(
.spacing(1.)
.into()
}
fn run_type_hex<'a, Message: Clone + 'a>(
label: &'a str,
val: usize,
update: impl Fn(usize) -> Message + 'a,
run: Message,
) -> Element<'a, Message> {
row![
widget::container(text(label)).padding(2.),
widget::container(hex_input(val).on_input(update).on_submit(run.clone())).padding(2.),
widget::container(button(image("./images/ic_fluent_play_24_filled.png")).on_press(run))
.padding(2.),
]
.spacing(1.)
.into()
}
pub fn labelled<'a, Message: 'a>(
label: &'a str,

View File

@@ -182,6 +182,12 @@ pub enum ClockState {
count: u8,
addr: u16,
},
Oops {
instruction: u8,
ops: [u8; 5],
count: u8,
addr: u16,
},
HoldNmi {
cycles: u8,
},
@@ -224,6 +230,17 @@ impl std::fmt::Debug for ClockState {
ClockState::HoldIrq { cycles } => {
f.debug_struct("HoldIrq").field("cycles", cycles).finish()
}
ClockState::Oops {
instruction,
ops,
count,
addr,
} => f
.debug_struct("Oops")
.field("instruction", instruction)
.field("addr", addr)
.field("ops", &&ops[..*count as usize])
.finish(),
}
}
}
@@ -232,6 +249,7 @@ enum ExecState {
Done,
MoreParams,
Hold(u8),
Oops,
}
pub enum CPUMMRegisters {
@@ -313,8 +331,15 @@ impl NES {
Segment::mirror("Mirror of APU & IO", 0x4018, 0x0008, 2),
];
// let mut cur = 0x4020;
assert!(prg_rom.len() <= 0x8000, "Mappers for larger sizes not supported");
segments.push(Segment::rom("PROG ROM", 0x8000 + (0x8000 - prg_rom.len() as u16), prg_rom));
assert!(
prg_rom.len() <= 0x8000,
"Mappers for larger sizes not supported"
);
segments.push(Segment::rom(
"PROG ROM",
0x8000 + (0x8000 - prg_rom.len() as u16),
prg_rom,
));
Self {
cycle: 7,
dbg_int: false,
@@ -360,7 +385,6 @@ impl NES {
}
}
0x16 => self.controller.write_joy_strobe(val),
0x17 => (), // TODO: frame counter control
_ => self.apu.write_reg(offset, val),
},
});
@@ -430,7 +454,14 @@ impl NES {
}
/// Returns true if more bytes are needed
fn exec_instruction(&mut self, ins: u8, mut params: &[u8], held: bool, addr: u16) -> ExecState {
fn exec_instruction(
&mut self,
ins: u8,
mut params: &[u8],
held: bool,
oops: bool,
addr: u16,
) -> ExecState {
macro_rules! inst {
($val:expr, $hold:expr, |$($name:pat_param),*| $eval:expr) => {{
let hold_time: u8 = ($hold).into();
@@ -582,6 +613,7 @@ impl NES {
);
}),
0xBD => inst!("LDA abs,x", 1, |low, high| {
if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; }
self.cpu.a = self.read_abs_x(low, high);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -593,6 +625,7 @@ impl NES {
);
}),
0xB9 => inst!("LDA abs,y", 1, |low, high| {
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
self.cpu.a = self.read_abs_y(low, high);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -614,6 +647,7 @@ impl NES {
0xB1 => inst!("LDA (ind),y", 3, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
self.cpu.a = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -631,7 +665,7 @@ impl NES {
self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80);
log!("{addr:04X}: LDX ${:02X} | {:02X}", off, self.cpu.x);
}),
0xB6 => inst!("LDX zp,y", 1, |off| {
0xB6 => inst!("LDX zp,y", 2, |off| {
self.cpu.x = self.read_zp_y(off);
self.cpu.status.set_zero(self.cpu.x == 0);
self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80);
@@ -649,6 +683,7 @@ impl NES {
);
}),
0xBE => inst!("LDX abs,y", 1, |low, high| {
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
self.cpu.x = self.read_abs_y(low, high);
self.cpu.status.set_zero(self.cpu.x == 0);
self.cpu.status.set_negative(self.cpu.x & 0x80 == 0x80);
@@ -671,7 +706,7 @@ impl NES {
self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80);
log!("{addr:04X}: LDY ${:02X} | {:02X}", off, self.cpu.y);
}),
0xB4 => inst!("LDX zp,x", 1, |off| {
0xB4 => inst!("LDX zp,x", 2, |off| {
self.cpu.y = self.read_zp_x(off);
self.cpu.status.set_zero(self.cpu.y == 0);
self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80);
@@ -689,6 +724,7 @@ impl NES {
);
}),
0xBC => inst!("LDX abs,x", 1, |low, high| {
if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; }
self.cpu.y = self.read_abs_x(low, high);
self.cpu.status.set_zero(self.cpu.y == 0);
self.cpu.status.set_negative(self.cpu.y & 0x80 == 0x80);
@@ -718,7 +754,7 @@ impl NES {
self.cpu.a
);
}),
0x9D => inst!("STA abs,x", 1, |low, high| {
0x9D => inst!("STA abs,x", 2, |low, high| {
self.write_abs_x(low, high, self.cpu.a);
log!(
"{addr:04X}: STA ${:02X}{:02X},x | {:02X}",
@@ -727,7 +763,7 @@ impl NES {
self.cpu.a
);
}),
0x99 => inst!("STA abs,y", 1, |low, high| {
0x99 => inst!("STA abs,y", 2, |low, high| {
self.write_abs_y(low, high, self.cpu.a);
log!(
"{addr:04X}: STA ${:02X}{:02X},y | {:02X}",
@@ -865,11 +901,11 @@ impl NES {
log!(" : - JMP ${:04X}", self.cpu.pc);
}
}),
0x4C => inst!("JMP abs", 3, |low, high| {
0x4C => inst!("JMP abs", 0, |low, high| {
self.cpu.pc = u16::from_le_bytes([low, high]);
log!("{addr:04X}: JMP ${:04X}", self.cpu.pc);
}),
0x6C => inst!("JMP abs", 3, |low, high| {
0x6C => inst!("JMP abs", 2, |low, high| {
self.cpu.pc = u16::from_le_bytes([
self.read_abs(low, high),
self.read_abs(low.wrapping_add(1), high), // Known CPU bug
@@ -950,6 +986,7 @@ impl NES {
);
}),
0xDD => inst!("CMP abs,x", 1, |low, high| {
if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; }
let val = self.read_abs_x(low, high);
let v = self.cpu.a.wrapping_sub(val);
self.cpu.status.set_zero(v == 0);
@@ -965,6 +1002,42 @@ impl NES {
);
}),
0xD9 => inst!("CMP abs,y", 1, |low, high| {
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
let val = self.read_abs_y(low, high);
let v = self.cpu.a.wrapping_sub(val);
self.cpu.status.set_zero(v == 0);
self.cpu.status.set_negative(v & 0x80 == 0x80);
self.cpu.status.set_carry(self.cpu.a >= val);
log!(
"{addr:04X}: CMP ${:02X}{:02X},y | {:02X} - {:02X} -> {:?}",
high,
low,
self.cpu.a,
val,
self.cpu.status
);
}),
0xC1 => inst!("CMP (ind,x)", 4, |off| {
let low = self.read_zp_x(off);
let high = self.read_zp_x(off.wrapping_add(1));
let val = self.read_abs(low, high);
let v = self.cpu.a.wrapping_sub(val);
self.cpu.status.set_zero(v == 0);
self.cpu.status.set_negative(v & 0x80 == 0x80);
self.cpu.status.set_carry(self.cpu.a >= val);
log!(
"{addr:04X}: CMP ${:02X}{:02X},y | {:02X} - {:02X} -> {:?}",
high,
low,
self.cpu.a,
val,
self.cpu.status
);
}),
0xD1 => inst!("CMP (ind),y", 3, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
let val = self.read_abs_y(low, high);
let v = self.cpu.a.wrapping_sub(val);
self.cpu.status.set_zero(v == 0);
@@ -1077,7 +1150,7 @@ impl NES {
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
log!("{addr:04X}: ADC #${:02X} | {:02X}", val, self.cpu.a);
}),
0x65 => inst!("ADC zp", 0, |off| {
0x65 => inst!("ADC zp", 1, |off| {
let val = self.read_abs(off, 0);
let (a, carry_1) = self.cpu.a.overflowing_add(val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1090,7 +1163,7 @@ impl NES {
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
log!("{addr:04X}: ADC ${:02X} | {:02X}", off, self.cpu.a);
}),
0x75 => inst!("ADC zp,x", 0, |off| {
0x75 => inst!("ADC zp,x", 2, |off| {
let val = self.read_zp_x(off);
let (a, carry_1) = self.cpu.a.overflowing_add(val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1103,7 +1176,7 @@ impl NES {
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
log!("{addr:04X}: ADC ${:02X},x | {:02X}", off, self.cpu.a);
}),
0x6D => inst!("ADC abs", 0, |low, high| {
0x6D => inst!("ADC abs", 1, |low, high| {
let val = self.read_abs(low, high);
let (a, carry_1) = self.cpu.a.overflowing_add(val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1121,7 +1194,8 @@ impl NES {
self.cpu.a
);
}),
0x7D => inst!("ADC abs,x", 0, |low, high| {
0x7D => inst!("ADC abs,x", 1, |low, high| {
if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; }
let val = self.read_abs_x(low, high);
let (a, carry_1) = self.cpu.a.overflowing_add(val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1139,7 +1213,8 @@ impl NES {
self.cpu.a
);
}),
0x79 => inst!("ADC abs,y", 0, |low, high| {
0x79 => inst!("ADC abs,y", 1, |low, high| {
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
let val = self.read_abs_y(low, high);
let (a, carry_1) = self.cpu.a.overflowing_add(val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1157,7 +1232,7 @@ impl NES {
self.cpu.a
);
}),
0x61 => inst!("ADC (ind,x)", 0, |off| {
0x61 => inst!("ADC (ind,x)", 4, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16);
@@ -1177,9 +1252,10 @@ impl NES {
self.cpu.a
);
}),
0x71 => inst!("ADC (ind),y", 0, |off| {
0x71 => inst!("ADC (ind),y", 3, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16);
let (a, carry_1) = self.cpu.a.overflowing_add(val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1210,7 +1286,7 @@ impl NES {
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
log!("{addr:04X}: SBC #${:02X} | {:02X}", val, self.cpu.a);
}),
0xE5 => inst!("SBC zp", 0, |off| {
0xE5 => inst!("SBC zp", 1, |off| {
let val = self.read_abs(off, 0);
let (a, carry_1) = self.cpu.a.overflowing_add(!val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1222,7 +1298,7 @@ impl NES {
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
}),
0xF5 => inst!("SBC zp,x", 0, |off| {
0xF5 => inst!("SBC zp,x", 2, |off| {
let val = self.read_zp_x(off);
let (a, carry_1) = self.cpu.a.overflowing_add(!val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1235,7 +1311,7 @@ impl NES {
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
log!("{addr:04X}: SBC ${:02X},x | {:02X}", off, self.cpu.a);
}),
0xED => inst!("SBC abs", 0, |low, high| {
0xED => inst!("SBC abs", 1, |low, high| {
let val = self.read_abs(low, high);
let (a, carry_1) = self.cpu.a.overflowing_add(!val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1253,7 +1329,8 @@ impl NES {
self.cpu.a
);
}),
0xFD => inst!("SBC abs,x", 0, |low, high| {
0xFD => inst!("SBC abs,x", 1, |low, high| {
if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; }
let val = self.read_abs_x(low, high);
let (a, carry_1) = self.cpu.a.overflowing_add(!val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1271,7 +1348,8 @@ impl NES {
self.cpu.a
);
}),
0xF9 => inst!("SBC abs,y", 0, |low, high| {
0xF9 => inst!("SBC abs,y", 1, |low, high| {
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
let val = self.read_abs_y(low, high);
let (a, carry_1) = self.cpu.a.overflowing_add(!val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1289,7 +1367,7 @@ impl NES {
self.cpu.a
);
}),
0xE1 => inst!("SBC (ind,x)", 0, |off| {
0xE1 => inst!("SBC (ind,x)", 4, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16);
@@ -1309,9 +1387,10 @@ impl NES {
self.cpu.a
);
}),
0xF1 => inst!("SBC (ind),y", 0, |off| {
0xF1 => inst!("SBC (ind),y", 3, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
let val = self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16);
let (a, carry_1) = self.cpu.a.overflowing_add(!val);
let (a, carry_2) = a.overflowing_add(self.cpu.status.carry().into());
@@ -1355,7 +1434,7 @@ impl NES {
self.cpu.a
);
}),
0xDE => inst!("DEC abs,x", 3, |low, high| {
0xDE => inst!("DEC abs,x", 4, |low, high| {
let val = self.read_abs_x(low, high).wrapping_sub(1);
self.write_abs_x(low, high, val);
self.cpu.status.set_zero(val == 0);
@@ -1405,7 +1484,7 @@ impl NES {
self.cpu.a
);
}),
0xFE => inst!("INC abs,x", 3, |low, high| {
0xFE => inst!("INC abs,x", 4, |low, high| {
let val = self.read_abs_x(low, high).wrapping_add(1);
self.write_abs_x(low, high, val);
self.cpu.status.set_zero(val == 0);
@@ -1460,7 +1539,8 @@ impl NES {
self.cpu.a
);
}),
0x1D => inst!("ORA abs,x", 1, |low, high| { // TODO: page crossing
0x1D => inst!("ORA abs,x", 1, |low, high| {
if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; }
self.cpu.a |= self.read_abs_x(low, high);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -1472,6 +1552,7 @@ impl NES {
);
}),
0x19 => inst!("ORA abs,y", 1, |low, high| {
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
self.cpu.a |= self.read_abs_y(low, high);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -1498,6 +1579,10 @@ impl NES {
0x11 => inst!("ORA (ind),y", 3, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
if !oops && low.checked_add(self.cpu.y).is_none() {
return ExecState::Oops;
}
self.cpu.a |= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -1526,7 +1611,7 @@ impl NES {
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
log!("{addr:04X}: AND ${:02X},x | {:02X}", off, self.cpu.a);
}),
0x2D => inst!("AND abs", 2, |low, high| {
0x2D => inst!("AND abs", 1, |low, high| {
self.cpu.a &= self.read_abs(low, high);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -1537,7 +1622,8 @@ impl NES {
self.cpu.a
);
}),
0x3D => inst!("AND abs,x", 2, |low, high| {
0x3D => inst!("AND abs,x", 1, |low, high| {
if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; }
self.cpu.a &= self.read_abs_x(low, high);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -1549,6 +1635,7 @@ impl NES {
);
}),
0x39 => inst!("AND abs,y", 1, |low, high| {
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
self.cpu.a &= self.read_abs_y(low, high);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -1572,9 +1659,10 @@ impl NES {
self.cpu.a
);
}),
0x31 => inst!("AND (ind),y", 4, |off| {
0x31 => inst!("AND (ind),y", 3, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
self.cpu.a &= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -1615,6 +1703,7 @@ impl NES {
);
}),
0x5D => inst!("EOR abs,x", 1, |low, high| {
if !oops && low.checked_add(self.cpu.x).is_none() { return ExecState::Oops; }
self.cpu.a ^= self.read_abs_x(low, high);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -1626,6 +1715,7 @@ impl NES {
);
}),
0x59 => inst!("EOR abs,y", 1, |low, high| {
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
self.cpu.a ^= self.read_abs_y(low, high);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -1652,6 +1742,7 @@ impl NES {
0x51 => inst!("EOR (ind),y", 3, |off| {
let low = self.read_abs(off, 0);
let high = self.read_abs(off.wrapping_add(1), 0);
if !oops && low.checked_add(self.cpu.y).is_none() { return ExecState::Oops; }
self.cpu.a ^= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16);
self.cpu.status.set_zero(self.cpu.a == 0);
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
@@ -1931,24 +2022,12 @@ impl NES {
}
}
ClockState::ReadInstruction => {
// if self.cpu.nmi_pending {
// self.cpu.nmi_pending = false;
// writeln!(self.debug_log, "NMI detected").unwrap();
// ClockState::HoldNmi { cycles: 5 }
// } else if !self.cpu.status.interrupt_disable()
// && (self.ppu.irq_waiting() || self.apu.irq_waiting())
// {
// // TODO: handle proper irq detection
// writeln!(self.debug_log, "IRQ detected").unwrap();
// ClockState::HoldIrq { cycles: 6 }
// } else
{
let addr = self.cpu.pc;
let instruction = self.read(self.cpu.pc);
if instruction != 0x02 {
self.cpu.pc = self.cpu.pc.wrapping_add(1);
}
match self.exec_instruction(instruction, &[], false, addr) {
match self.exec_instruction(instruction, &[], false, false, addr) {
ExecState::Done => ClockState::ReadInstruction,
ExecState::MoreParams => ClockState::ReadOperands {
instruction,
@@ -1963,7 +2042,12 @@ impl NES {
count: 0,
addr,
},
}
ExecState::Oops => ClockState::Oops {
instruction,
ops: [0u8; 5],
count: 0,
addr,
},
}
}
ClockState::ReadOperands {
@@ -1977,7 +2061,13 @@ impl NES {
}
ops[count as usize] = self.read(self.cpu.pc);
self.cpu.pc = self.cpu.pc.wrapping_add(1);
match self.exec_instruction(instruction, &ops[..count as usize + 1], false, addr) {
match self.exec_instruction(
instruction,
&ops[..count as usize + 1],
false,
false,
addr,
) {
ExecState::Done => ClockState::ReadInstruction,
ExecState::MoreParams => ClockState::ReadOperands {
instruction,
@@ -1992,6 +2082,12 @@ impl NES {
count: count + 1,
addr,
},
ExecState::Oops => ClockState::Oops {
instruction,
ops,
count: count + 1,
addr,
},
}
}
ClockState::Hold {
@@ -2002,15 +2098,24 @@ impl NES {
addr,
} => {
if cycles == 0 {
match self.exec_instruction(instruction, &ops[..count as usize], true, addr) {
match self.exec_instruction(
instruction,
&ops[..count as usize],
true,
false,
addr,
) {
ExecState::Done => ClockState::ReadInstruction,
ExecState::MoreParams => ClockState::ReadOperands {
ExecState::MoreParams => {
panic!("Should never return MoreParams after holding")
}
ExecState::Hold(_) => panic!("Should never return Hold after holding"),
ExecState::Oops => ClockState::Oops {
instruction,
ops,
count: count + 1,
count,
addr,
},
ExecState::Hold(_) => panic!("Should never return Hold after holding"),
}
} else {
ClockState::Hold {
@@ -2022,12 +2127,25 @@ impl NES {
}
}
}
ClockState::Oops {
instruction,
ops,
count,
addr,
} => {
match self.exec_instruction(instruction, &ops[..count as usize], true, true, addr) {
ExecState::Done => ClockState::ReadInstruction,
_ => panic!("Must execute after oops"),
}
}
};
if self.cpu.clock_state == ClockState::ReadInstruction {
if self.cpu.nmi_pending {
self.cpu.nmi_pending = false;
self.cpu.clock_state = ClockState::HoldNmi { cycles: 6 };
writeln!(self.debug_log, "NMI detected").unwrap();
} else if self.cpu.irq_pending && !self.cpu.status.interrupt_disable() {
writeln!(self.debug_log, "IRQ detected").unwrap();
self.cpu.clock_state = ClockState::HoldIrq { cycles: 6 };
}
}

View File

@@ -26,8 +26,8 @@ use tracing_subscriber::EnvFilter;
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_fill_red.nes");
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_fill_name_table.nes");
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "int_nmi_exit_timing.nes");
const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
// const ROM_FILE: &str = "./cpu_timing_test.nes";
// const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
const ROM_FILE: &str = "./cpu_timing_test.nes";
extern crate nes_emu;

View File

@@ -467,7 +467,7 @@ impl Palette {
pub struct PPU {
// registers: PPURegisters,
frame_count: usize,
pub frame_count: usize,
nmi_enabled: bool,
// nmi_waiting: bool,
pub even: bool,