Files
nes-emu/src/debugger.rs
Matthew Pomes af770d232c
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 26s
Finally find (& fix) bug in BIT instructions
- BIT not longer ANDs the A register
- I now a pretty good debug view for debugging the CPU
- I wrote a number_input element for iced
- I upgraded to iced 0.14
- I added images for play and pause
- The debug log now displays in the debug view
2025-12-14 13:10:57 -06:00

233 lines
8.3 KiB
Rust

use iced::{
Element,
Length::Fill,
widget::{
self, button, checkbox, column, container::bordered_box, image, number_input, row,
scrollable, text,
},
};
use crate::{CycleResult, NES};
#[derive(Debug, Clone)]
pub struct DebuggerState {
ppu_cycles: usize,
cpu_cycles: usize,
instructions: usize,
scan_lines: usize,
to_scan_line: usize,
frames: usize,
}
#[derive(Debug, Clone)]
pub enum DebuggerMessage {
Run,
Pause,
SetPPUCycles(usize),
RunPPUCycles,
SetCPUCycles(usize),
RunCPUCycles,
SetInstructions(usize),
RunInstructions,
SetScanLines(usize),
RunScanLines,
SetToScanLine(usize),
RunToScanLine,
SetFrames(usize),
RunFrames,
}
impl DebuggerState {
pub fn new() -> Self {
Self {
ppu_cycles: 1,
cpu_cycles: 1,
instructions: 1,
scan_lines: 1,
to_scan_line: 1,
frames: 1,
// cpu_cycles: 1,
}
}
pub fn view<'s>(&'s self, nes: &'s NES) -> Element<'s, DebuggerMessage> {
column![
row![
button(image("./images/ic_fluent_play_24_filled.png"))
.on_press(DebuggerMessage::Run),
button(image("./images/ic_fluent_pause_24_filled.png"))
.on_press(DebuggerMessage::Pause),
],
iced::widget::rule::horizontal(2.),
row![column![
text("Status"),
row![
labelled("A:", text(format!("{:02X}", nes.cpu.a))),
labelled("X:", text(format!("{:02X}", nes.cpu.x))),
labelled("Y:", text(format!("{:02X}", nes.cpu.y))),
labelled("PC:", text(format!("{:04X}", nes.cpu.pc))),
labelled("Cycle:", text(format!("{}", nes.cycle))),
labelled("SP:", text(format!("{:02X}", nes.cpu.sp))),
]
.spacing(5.),
row![
labelled("P:", text(format!("{:02X}", nes.cpu.status.0))),
labelled_box("Carry", nes.cpu.status.carry()),
labelled_box("Zero", nes.cpu.status.zero()),
labelled_box("Interrupt", nes.cpu.status.interrupt_disable()),
labelled_box("--", false),
labelled_box("--", false),
labelled_box("Overflow", nes.cpu.status.overflow()),
labelled_box("Negative", nes.cpu.status.negative()),
]
.spacing(5.),
row![
text("IRQs:"),
labelled_box("NMI", nes.peek_nmi()),
labelled_box("Cart", false),
labelled_box("Frame Counter", false),
labelled_box("DMC", false),
]
.spacing(5.),
row![
column![
labelled("Cycle", text(nes.ppu.pixel)),
labelled("Scanline", text(nes.ppu.scanline)),
labelled("PPU Cycle", text(nes.ppu.cycle)),
],
column![
labelled_box("Sprite 0 Hit", false),
labelled_box("Sprite 0 Overflow", false),
labelled_box("Vertical Blank", nes.ppu.vblank),
labelled_box("Write Toggle", false),
labelled_box("", false),
labelled_box("Large Sprites", false),
labelled_box("Vertical Write", false),
labelled_box("NMI on VBlank", false),
labelled_box("BG at $1000", false),
labelled_box("Sprites at $1000", false),
],
column![
run_type(
"PPU Cycles:",
self.ppu_cycles,
DebuggerMessage::SetPPUCycles,
DebuggerMessage::RunPPUCycles
),
run_type(
"CPU Cycles:",
self.cpu_cycles,
DebuggerMessage::SetCPUCycles,
DebuggerMessage::RunCPUCycles
),
run_type(
"Instructions:",
self.instructions,
DebuggerMessage::SetInstructions,
DebuggerMessage::RunInstructions
),
run_type(
"Scanlines:",
self.scan_lines,
DebuggerMessage::SetScanLines,
DebuggerMessage::RunScanLines
),
run_type(
"To Scanline:",
self.to_scan_line,
DebuggerMessage::SetToScanLine,
DebuggerMessage::RunToScanLine
),
run_type(
"Frames:",
self.frames,
DebuggerMessage::SetFrames,
DebuggerMessage::RunFrames
),
],
]
.spacing(5.),
scrollable(column(
nes.debug_log()
.history()
.into_iter()
.rev()
.map(|s| text(s).line_height(0.9).into())
).spacing(0))
.width(Fill),
],],
]
.width(Fill)
.height(Fill)
.into()
}
fn run_n_clock_cycles(nes: &mut NES, n: usize) {
for _ in 0..n {
nes.run_one_clock_cycle();
}
}
fn run_until(nes: &mut NES, mut f: impl FnMut(CycleResult, &NES) -> bool) {
loop {
if f(nes.run_one_clock_cycle(), nes) {
break;
}
}
}
pub fn update(&mut self, message: DebuggerMessage, nes: &mut NES) {
match message {
DebuggerMessage::SetPPUCycles(n) => self.ppu_cycles = n,
DebuggerMessage::SetCPUCycles(n) => self.cpu_cycles = n,
DebuggerMessage::SetInstructions(n) => self.instructions = n,
DebuggerMessage::SetScanLines(n) => self.scan_lines = n,
DebuggerMessage::SetToScanLine(n) => self.to_scan_line = n,
DebuggerMessage::SetFrames(n) => self.frames = 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 => Self::run_until(nes, |c, _| c.cpu_exec),
DebuggerMessage::RunScanLines => Self::run_n_clock_cycles(nes, self.scan_lines * 341),
DebuggerMessage::RunToScanLine => {
Self::run_until(nes, |_, n| n.ppu.scanline == self.to_scan_line)
}
DebuggerMessage::RunFrames => Self::run_n_clock_cycles(nes, self.frames * 341 * 261),
DebuggerMessage::Run => todo!(),
DebuggerMessage::Pause => todo!(),
}
}
}
fn run_type<'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(number_input(val).on_input(update)).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,
content: impl Into<Element<'a, Message>>,
) -> Element<'a, Message> {
row![
widget::container(text(label)).padding(2.),
widget::container(content).style(bordered_box).padding(2.),
]
.spacing(1.)
.into()
}
pub fn labelled_box<'a, Message: 'a>(label: &'a str, value: bool) -> Element<'a, Message> {
row![checkbox(value), widget::container(text(label)),]
.spacing(1.)
.into()
}