Minor refactors and bug fixes
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Has been cancelled
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Has been cancelled
- Bit instruction now sets Z flag correctly - DMA is no longer handled by cpu.rs
This commit is contained in:
131
src/cpu.rs
131
src/cpu.rs
@@ -1,7 +1,7 @@
|
||||
use std::fmt::Write;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{debug::DebugLog, mem::CpuMem};
|
||||
use crate::{Break, debug::DebugLog, mem::CpuMem};
|
||||
|
||||
bitfield::bitfield! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
@@ -634,7 +634,9 @@ impl Cpu {
|
||||
if self.status.carry() {
|
||||
let page = self.jmp_rel(off);
|
||||
log!(" : - JMP ${:04X}", self.pc);
|
||||
if page { return ExecState::PageCross };
|
||||
if page {
|
||||
return ExecState::PageCross;
|
||||
};
|
||||
}
|
||||
}),
|
||||
0xF0 => inst!("BEQ", self.status.zero(), |off| {
|
||||
@@ -642,7 +644,9 @@ impl Cpu {
|
||||
if self.status.zero() {
|
||||
let page = self.jmp_rel(off);
|
||||
log!(" : - JMP ${:04X}", self.pc);
|
||||
if page { return ExecState::PageCross };
|
||||
if page {
|
||||
return ExecState::PageCross;
|
||||
};
|
||||
}
|
||||
}),
|
||||
0x30 => inst!("BMI", self.status.negative(), |off| {
|
||||
@@ -650,7 +654,9 @@ impl Cpu {
|
||||
if self.status.negative() {
|
||||
let page = self.jmp_rel(off);
|
||||
log!(" : - JMP ${:04X}", self.pc);
|
||||
if page { return ExecState::PageCross };
|
||||
if page {
|
||||
return ExecState::PageCross;
|
||||
};
|
||||
}
|
||||
}),
|
||||
0xD0 => inst!("BNE", !self.status.zero(), |off| {
|
||||
@@ -658,7 +664,9 @@ impl Cpu {
|
||||
if !self.status.zero() {
|
||||
let page = self.jmp_rel(off);
|
||||
log!(" : - JMP ${:04X}", self.pc);
|
||||
if page { return ExecState::PageCross };
|
||||
if page {
|
||||
return ExecState::PageCross;
|
||||
};
|
||||
}
|
||||
}),
|
||||
0x10 => inst!("BPL", !self.status.negative(), |off| {
|
||||
@@ -666,7 +674,9 @@ impl Cpu {
|
||||
if !self.status.negative() {
|
||||
let page = self.jmp_rel(off);
|
||||
log!(" : - JMP ${:04X}", self.pc);
|
||||
if page { return ExecState::PageCross };
|
||||
if page {
|
||||
return ExecState::PageCross;
|
||||
};
|
||||
}
|
||||
}),
|
||||
0x4C => inst!("JMP abs", 0, |low, high| {
|
||||
@@ -1509,14 +1519,14 @@ impl Cpu {
|
||||
}),
|
||||
0x24 => inst!("BIT zp", 1, |off| {
|
||||
let val = self.read_abs(mem, off, 0);
|
||||
self.status.set_zero(val == 0);
|
||||
self.status.set_zero(val & self.a == 0);
|
||||
self.status.set_overflow(val & 0x40 == 0x40);
|
||||
self.status.set_negative(val & 0x80 == 0x80);
|
||||
log!("{addr:04X}: BIT ${:02X} | {:02X}", off, val);
|
||||
}),
|
||||
0x2C => inst!("BIT abs", 1, |low, high| {
|
||||
let val = self.read_abs(mem, low, high);
|
||||
self.status.set_zero(val == 0);
|
||||
self.status.set_zero(val & self.a == 0);
|
||||
self.status.set_overflow(val & 0x40 == 0x40);
|
||||
self.status.set_negative(val & 0x80 == 0x80);
|
||||
log!("{addr:04X}: BIT ${:02X}{:02X} | {:02X}", high, low, val);
|
||||
@@ -1723,7 +1733,7 @@ impl Cpu {
|
||||
}
|
||||
}
|
||||
|
||||
fn clock_cpu(&mut self, mem: &mut CpuMem<'_>, nmi: bool, irq: bool) {
|
||||
pub fn clock_cpu(&mut self, mem: &mut CpuMem<'_>, nmi: bool, irq: bool, br: &Break) -> bool {
|
||||
self.clock_state = match self.clock_state {
|
||||
ClockState::HoldNmi { cycles } => {
|
||||
if cycles == 0 {
|
||||
@@ -1750,6 +1760,10 @@ impl Cpu {
|
||||
}
|
||||
}
|
||||
ClockState::ReadInstruction => {
|
||||
if br.cpu_exec {
|
||||
println!("Returning early from clock_cpu");
|
||||
return true;
|
||||
}
|
||||
let addr = self.pc;
|
||||
let instruction = mem.read(self.pc);
|
||||
if instruction != 0x02 {
|
||||
@@ -1900,59 +1914,13 @@ impl Cpu {
|
||||
}
|
||||
// self.irq_pending = self.ppu.irq_waiting() || self.apu.irq_waiting();
|
||||
self.irq_pending = irq;
|
||||
false
|
||||
}
|
||||
|
||||
pub fn cpu_cycle(
|
||||
&mut self,
|
||||
mem: &mut CpuMem<'_>,
|
||||
dma: &mut DmaState,
|
||||
nmi: bool,
|
||||
irq: bool,
|
||||
) {
|
||||
match *dma {
|
||||
DmaState::Passive => self.clock_cpu(mem, nmi, irq),
|
||||
// TODO: Validate that this takes the correct number of cycles (513 or 514 cycles)
|
||||
DmaState::Running {
|
||||
cpu_addr,
|
||||
rem,
|
||||
read: Some(read),
|
||||
} => {
|
||||
mem.write(0x2004, read);
|
||||
// self.ppu.write_reg(4, read);
|
||||
debug!("OAM DMA write {:02X}", read);
|
||||
if rem == 0 {
|
||||
*dma = DmaState::Passive;
|
||||
} else {
|
||||
*dma = DmaState::Running {
|
||||
cpu_addr: cpu_addr + 1,
|
||||
rem: rem - 1,
|
||||
read: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
DmaState::Running {
|
||||
cpu_addr,
|
||||
rem,
|
||||
read: None,
|
||||
} => {
|
||||
let read = mem.read(cpu_addr);
|
||||
debug!("OAM DMA read {:04X} {:02X}", cpu_addr, read);
|
||||
*dma = DmaState::Running {
|
||||
cpu_addr,
|
||||
rem,
|
||||
read: Some(read),
|
||||
};
|
||||
}
|
||||
}
|
||||
// if [0x8031, 0x8014].contains(&self.cpu.pc) {
|
||||
// self.dbg_int = true;
|
||||
// }
|
||||
}
|
||||
pub fn cpu_cycle_update(&mut self) {
|
||||
if !self.halted {
|
||||
self.cycle += 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn executed(&self) -> bool {
|
||||
@@ -1978,6 +1946,7 @@ impl Cpu {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum DmaState {
|
||||
Passive,
|
||||
Idle(u16),
|
||||
Running {
|
||||
cpu_addr: u16,
|
||||
rem: u8,
|
||||
@@ -1995,6 +1964,56 @@ impl DmaState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cycle(self, mem: &mut CpuMem<'_>, cpu: &mut Cpu) -> Option<Self> {
|
||||
match self {
|
||||
DmaState::Passive => None,
|
||||
DmaState::Idle(cpu_addr) => Some(DmaState::Running {
|
||||
cpu_addr,
|
||||
rem: 0xFF,
|
||||
read: None,
|
||||
}),
|
||||
DmaState::Running {
|
||||
cpu_addr,
|
||||
rem,
|
||||
read: Some(read),
|
||||
} => {
|
||||
mem.write(0x2004, read);
|
||||
// self.ppu.write_reg(4, read);
|
||||
writeln!(&mut cpu.debug_log, "OAM DMA write {:02X}", read).unwrap();
|
||||
if rem == 0 {
|
||||
Some(DmaState::Passive)
|
||||
} else {
|
||||
Some(DmaState::Running {
|
||||
cpu_addr: cpu_addr + 1,
|
||||
rem: rem - 1,
|
||||
read: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
DmaState::Running {
|
||||
cpu_addr,
|
||||
rem,
|
||||
read: None,
|
||||
} => {
|
||||
if cpu.cycle % 2 != 1 {
|
||||
return Some(self);
|
||||
}
|
||||
let read = mem.read(cpu_addr);
|
||||
writeln!(
|
||||
&mut cpu.debug_log,
|
||||
"OAM DMA read {:04X} {:02X}",
|
||||
cpu_addr, read
|
||||
)
|
||||
.unwrap();
|
||||
Some(DmaState::Running {
|
||||
cpu_addr,
|
||||
rem,
|
||||
read: Some(read),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CycleResult {
|
||||
|
||||
@@ -24,7 +24,7 @@ use iced::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{CycleResult, NES, PPU, mem::Mapped};
|
||||
use crate::{Break, CycleResult, NES, PPU, mem::Mapped};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DebuggerState {
|
||||
@@ -254,23 +254,41 @@ impl DebuggerState {
|
||||
|
||||
fn run_n_clock_cycles(nes: &mut NES, n: usize) {
|
||||
for _ in 0..n {
|
||||
if nes.run_one_clock_cycle().dbg_int || nes.halted() {
|
||||
if nes.run_one_clock_cycle(&Break::default()).dbg_int || nes.halted() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn run_until(nes: &mut NES, mut f: impl FnMut(CycleResult, &NES) -> bool, mut count: usize) {
|
||||
loop {
|
||||
let res = nes.run_one_clock_cycle();
|
||||
if res.dbg_int || f(res, nes) {
|
||||
fn run_until(
|
||||
nes: &mut NES,
|
||||
br: &Break,
|
||||
mut f: impl FnMut(CycleResult, &NES) -> bool,
|
||||
// mut count: usize,
|
||||
) {
|
||||
// Always run at least 1 cycle
|
||||
let mut res = nes.run_one_clock_cycle(&Break::default());
|
||||
while !nes.halted() && !res.dbg_int && !f(res, nes) {
|
||||
// if res.dbg_int || f(res, nes) {
|
||||
// count -= 1;
|
||||
// if count <= 0 {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if nes.halted() {
|
||||
// break;
|
||||
// }
|
||||
res = nes.run_one_clock_cycle(br);
|
||||
}
|
||||
}
|
||||
fn run_until_n(
|
||||
nes: &mut NES,
|
||||
br: &Break,
|
||||
mut f: impl FnMut(CycleResult, &NES) -> bool,
|
||||
mut count: usize,
|
||||
) {
|
||||
while count > 0 {
|
||||
Self::run_until(nes, br, &mut f);
|
||||
count -= 1;
|
||||
if count <= 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if nes.halted() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,23 +298,49 @@ impl DebuggerState {
|
||||
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::SetToScanLine(n) => self.to_scan_line = n.min(261), // Max scanline is 261
|
||||
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 => {
|
||||
Self::run_until(nes, |c, _| c.cpu_exec, self.instructions)
|
||||
}
|
||||
// DebuggerMessage::RunInstructions => Self::run_until_n(
|
||||
// nes,
|
||||
// &Break {
|
||||
// cpu_exec: true,
|
||||
// ..Break::default()
|
||||
// },
|
||||
// |_, _| false,
|
||||
// self.instructions,
|
||||
// ),
|
||||
DebuggerMessage::RunInstructions => Self::run_until_n(
|
||||
nes,
|
||||
&Break {
|
||||
..Break::default()
|
||||
},
|
||||
|res, _| res.cpu_exec,
|
||||
self.instructions,
|
||||
),
|
||||
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, 1)
|
||||
}
|
||||
DebuggerMessage::RunToScanLine => Self::run_until(
|
||||
nes,
|
||||
&Break {
|
||||
ppu_scanline: true,
|
||||
..Break::default()
|
||||
},
|
||||
|_, n| n.ppu.scanline == self.to_scan_line,
|
||||
),
|
||||
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::RunBreakpoint => Self::run_until(
|
||||
nes,
|
||||
&Break {
|
||||
break_points: vec![self.breakpoint as u16],
|
||||
..Break::default()
|
||||
},
|
||||
|_, nes| nes.cpu.pc as usize == self.breakpoint,
|
||||
),
|
||||
DebuggerMessage::Run => {
|
||||
Self::run_until(nes, &Break { ..Break::default() }, |_, nes| nes.halted())
|
||||
}
|
||||
DebuggerMessage::Run => Self::run_until(nes, |_, nes| nes.halted(), 1),
|
||||
DebuggerMessage::Pause => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,24 @@ use iced::{
|
||||
widget::{column, lazy, row, text},
|
||||
};
|
||||
|
||||
use crate::mem::Mapped;
|
||||
use crate::{mem::Mapped, PPU};
|
||||
|
||||
pub trait Memory {
|
||||
fn peek(&self, val: u16) -> Option<u8>;
|
||||
fn peek(&self, val: usize) -> Option<u8>;
|
||||
fn len(&self) -> usize;
|
||||
fn edit_ver(&self) -> usize;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Cpu<'a>(pub &'a Mapped);
|
||||
|
||||
impl Memory for Cpu<'_> {
|
||||
fn peek(&self, val: u16) -> Option<u8> {
|
||||
self.0.peek_cpu(val)
|
||||
fn peek(&self, val: usize) -> Option<u8> {
|
||||
self.0.peek_cpu(val as u16)
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
0x10000
|
||||
}
|
||||
|
||||
fn edit_ver(&self) -> usize {
|
||||
@@ -26,11 +32,16 @@ impl Memory for Cpu<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Ppu<'a>(pub &'a Mapped);
|
||||
|
||||
impl Memory for Ppu<'_> {
|
||||
fn peek(&self, val: u16) -> Option<u8> {
|
||||
self.0.peek_ppu(val)
|
||||
fn peek(&self, val: usize) -> Option<u8> {
|
||||
self.0.peek_ppu(val as u16)
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
0x10000
|
||||
}
|
||||
|
||||
fn edit_ver(&self) -> usize {
|
||||
@@ -38,6 +49,23 @@ impl Memory for Ppu<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Oam<'a>(pub &'a PPU);
|
||||
|
||||
impl Memory for Oam<'_> {
|
||||
fn peek(&self, val: usize) -> Option<u8> {
|
||||
Some(self.0.peek_oam(val as u8))
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
0x100
|
||||
}
|
||||
|
||||
fn edit_ver(&self) -> usize {
|
||||
self.0.oam_edit_ver()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HexEvent {}
|
||||
|
||||
@@ -60,29 +88,13 @@ impl HexView {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn render<'a>(&self, mem: &'a Mapped, ppu: bool) -> Element<'a, HexEvent> {
|
||||
struct Row<'a>(u16, &'a Mapped, bool);
|
||||
impl fmt::Display for Row<'_> {
|
||||
pub fn render_any<'a, M: Memory + Copy + 'a>(&self, mem: M) -> Element<'a, HexEvent> {
|
||||
struct Row<M: Memory>(usize, M);
|
||||
impl<'a, M: Memory + 'a> fmt::Display for Row<M> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
Val(if self.2 {
|
||||
self.1.peek_ppu(self.0)
|
||||
} else {
|
||||
self.1.peek_cpu(self.0)
|
||||
})
|
||||
)?;
|
||||
write!(f, "{}", Val(self.1.peek(self.0)))?;
|
||||
for i in 1..16 {
|
||||
write!(
|
||||
f,
|
||||
" {}",
|
||||
Val(if self.2 {
|
||||
self.1.peek_ppu(self.0 + i)
|
||||
} else {
|
||||
self.1.peek_cpu(self.0 + i)
|
||||
})
|
||||
)?;
|
||||
write!(f, " {}", Val(self.1.peek(self.0 + i)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -90,11 +102,7 @@ impl HexView {
|
||||
column![
|
||||
text!("Hex view"),
|
||||
iced::widget::scrollable(lazy(
|
||||
if ppu {
|
||||
mem.ppu_edit_ver()
|
||||
} else {
|
||||
mem.cpu_edit_ver()
|
||||
},
|
||||
mem.edit_ver(),
|
||||
move |_| column(
|
||||
[
|
||||
text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F")
|
||||
@@ -102,8 +110,8 @@ impl HexView {
|
||||
.into()
|
||||
]
|
||||
.into_iter()
|
||||
.chain((0..u16::MAX).step_by(16).map(|off| {
|
||||
text!(" {off:04X} | {}", Row(off, mem, ppu))
|
||||
.chain((0..mem.len()).step_by(16).map(|off| {
|
||||
text!(" {off:04X} | {}", Row(off, mem))
|
||||
.font(Font::MONOSPACE)
|
||||
.into()
|
||||
}))
|
||||
@@ -114,6 +122,15 @@ impl HexView {
|
||||
.width(Fill)
|
||||
.into()
|
||||
}
|
||||
pub fn render_cpu<'a>(&self, mem: &'a Mapped) -> Element<'a, HexEvent> {
|
||||
self.render_any(Cpu(mem))
|
||||
}
|
||||
pub fn render_ppu<'a>(&self, mem: &'a Mapped) -> Element<'a, HexEvent> {
|
||||
self.render_any(Ppu(mem))
|
||||
}
|
||||
pub fn render_oam<'a>(&self, ppu: &'a PPU) -> Element<'a, HexEvent> {
|
||||
self.render_any(Oam(ppu))
|
||||
}
|
||||
|
||||
pub fn update(&mut self, ev: HexEvent) -> Task<HexEvent> {
|
||||
todo!()
|
||||
|
||||
101
src/lib.rs
101
src/lib.rs
@@ -163,7 +163,45 @@ impl NES {
|
||||
self.ppu.peek_irq() || self.apu.peek_irq()
|
||||
}
|
||||
|
||||
pub fn run_one_clock_cycle(&mut self) -> CycleResult {
|
||||
pub fn run_cpu_cycle(
|
||||
&mut self,
|
||||
// mem: &mut CpuMem<'_>,
|
||||
// dma: &mut DmaState,
|
||||
// nmi: bool,
|
||||
// irq: bool,
|
||||
br: &Break,
|
||||
) -> bool {
|
||||
if let Some(dma) = self.dma.cycle(
|
||||
&mut CpuMem::new(
|
||||
&mut self.mapped,
|
||||
&mut self.ppu,
|
||||
&mut self.apu,
|
||||
&mut self.dma,
|
||||
&mut self.controller,
|
||||
),
|
||||
&mut self.cpu
|
||||
) {
|
||||
self.dma = dma;
|
||||
false
|
||||
} else {
|
||||
let nmi = self.ppu.nmi_waiting() || self.apu.nmi_waiting();
|
||||
let irq = self.ppu.irq_waiting() || self.apu.irq_waiting();
|
||||
self.cpu.clock_cpu(
|
||||
&mut CpuMem::new(
|
||||
&mut self.mapped,
|
||||
&mut self.ppu,
|
||||
&mut self.apu,
|
||||
&mut self.dma,
|
||||
&mut self.controller,
|
||||
),
|
||||
nmi,
|
||||
irq,
|
||||
br,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_one_clock_cycle(&mut self, br: &Break) -> CycleResult {
|
||||
if self.cpu.halted() {
|
||||
return CycleResult {
|
||||
cpu_exec: false,
|
||||
@@ -172,31 +210,40 @@ impl NES {
|
||||
dbg_int: true,
|
||||
};
|
||||
}
|
||||
self.clock_count += 1;
|
||||
let cpu_exec = if self.clock_count % 3 == 0 {
|
||||
let cpu_exec = if self.clock_count % 3 == 0 && self.clock_count > 0 {
|
||||
let nmi = self.ppu.nmi_waiting() || self.apu.nmi_waiting();
|
||||
let irq = self.ppu.irq_waiting() || self.apu.irq_waiting();
|
||||
let mut dma = DmaState::Passive;
|
||||
self.cpu.cpu_cycle(
|
||||
&mut CpuMem::new(
|
||||
&mut self.mapped,
|
||||
&mut self.ppu,
|
||||
&mut self.apu,
|
||||
&mut dma,
|
||||
&mut self.controller,
|
||||
),
|
||||
&mut self.dma,
|
||||
nmi,
|
||||
irq,
|
||||
);
|
||||
if self.run_cpu_cycle(
|
||||
// &mut CpuMem::new(
|
||||
// &mut self.mapped,
|
||||
// &mut self.ppu,
|
||||
// &mut self.apu,
|
||||
// &mut dma,
|
||||
// &mut self.controller,
|
||||
// ),
|
||||
// &mut self.dma,
|
||||
// nmi,
|
||||
// irq,
|
||||
br,
|
||||
) {
|
||||
println!("Returning early from clock_cycle");
|
||||
return CycleResult {
|
||||
cpu_exec: true,
|
||||
ppu_frame: false,
|
||||
dma: false,
|
||||
dbg_int: true,
|
||||
};
|
||||
}
|
||||
self.dma.merge(dma);
|
||||
self.cpu.cpu_cycle_update();
|
||||
self.cpu.executed()
|
||||
} else if self.clock_count == 0 {
|
||||
self.cpu.cpu_cycle_update();
|
||||
false
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if self.clock_count % 3 == 1 {
|
||||
self.cpu.cpu_cycle_update();
|
||||
}
|
||||
// let cpu_exec = self.clock_count % 3 == CPU_CLOCK_OFFSET && self.cpu.executed();
|
||||
if !self.cpu.halted() {
|
||||
let _apu_exec = self.apu.run_one_clock_cycle(self.clock_count);
|
||||
@@ -211,6 +258,7 @@ impl NES {
|
||||
let dbg_int = self.dbg_int | self.ppu.dbg_int;
|
||||
self.dbg_int = false;
|
||||
self.ppu.dbg_int = false;
|
||||
self.clock_count += 1;
|
||||
CycleResult {
|
||||
cpu_exec,
|
||||
ppu_frame,
|
||||
@@ -243,7 +291,7 @@ impl NES {
|
||||
self.reset();
|
||||
while !self.cpu.halted() {
|
||||
// info!("Running clock cycle: {}", self.clock_count);
|
||||
self.run_one_clock_cycle();
|
||||
self.run_one_clock_cycle(&Break::default());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +329,7 @@ impl NES {
|
||||
let mut cur = 0;
|
||||
while !self.cpu.halted() && cur < max_clock_cycles {
|
||||
// info!("Running clock cycle: {}", self.clock_count);
|
||||
self.run_one_clock_cycle();
|
||||
self.run_one_clock_cycle(&Break::default());
|
||||
cur += 1;
|
||||
}
|
||||
}
|
||||
@@ -326,3 +374,16 @@ impl NES {
|
||||
self.cpu.cycle
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct Break {
|
||||
pub dma: bool,
|
||||
pub nmi: bool,
|
||||
pub irq: bool,
|
||||
pub cpu_exec: bool,
|
||||
pub ppu_frame: bool,
|
||||
pub ppu_scanline: bool,
|
||||
pub break_points: Vec<u16>,
|
||||
// pub dma: bool,
|
||||
// pub dbg_int: bool,
|
||||
}
|
||||
|
||||
47
src/main.rs
47
src/main.rs
@@ -51,6 +51,7 @@ fn main() -> Result<(), iced::Error> {
|
||||
enum MemoryTy {
|
||||
Cpu,
|
||||
PPU,
|
||||
OAM,
|
||||
}
|
||||
|
||||
impl fmt::Display for MemoryTy {
|
||||
@@ -86,6 +87,7 @@ impl fmt::Display for HeaderButton {
|
||||
match self {
|
||||
Self::Open(WindowType::Memory(MemoryTy::Cpu, _)) => write!(f, "Open Memory Viewer"),
|
||||
Self::Open(WindowType::Memory(MemoryTy::PPU, _)) => write!(f, "Open PPU Memory Viewer"),
|
||||
Self::Open(WindowType::Memory(MemoryTy::OAM, _)) => write!(f, "Open OAM Memory Viewer"),
|
||||
Self::Open(WindowType::TileMap) => write!(f, "Open TileMap Viewer"),
|
||||
Self::Open(WindowType::TileViewer) => write!(f, "Open Tile Viewer"),
|
||||
Self::Open(WindowType::Debugger) => write!(f, "Open Debugger"),
|
||||
@@ -108,11 +110,11 @@ struct Emulator {
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
OpenRom(NES),
|
||||
Tick(usize),
|
||||
Frame,
|
||||
DMA,
|
||||
CPU,
|
||||
DebugInt,
|
||||
// Tick(usize),
|
||||
// Frame,
|
||||
// DMA,
|
||||
// CPU,
|
||||
// DebugInt,
|
||||
WindowClosed(Id),
|
||||
WindowOpened(Id),
|
||||
Header(HeaderButton),
|
||||
@@ -170,15 +172,15 @@ impl Emulator {
|
||||
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::Tick(count) => {
|
||||
for _ in 0..count {
|
||||
self.nes.run_one_clock_cycle();
|
||||
}
|
||||
}
|
||||
Message::Frame => while !self.nes.run_one_clock_cycle().ppu_frame {},
|
||||
Message::DMA => while !self.nes.run_one_clock_cycle().dma {},
|
||||
Message::CPU => while !self.nes.run_one_clock_cycle().cpu_exec {},
|
||||
Message::DebugInt => while !self.nes.run_one_clock_cycle().dbg_int {},
|
||||
// Message::Tick(count) => {
|
||||
// for _ in 0..count {
|
||||
// self.nes.run_one_clock_cycle();
|
||||
// }
|
||||
// }
|
||||
// Message::Frame => while !self.nes.run_one_clock_cycle().ppu_frame {},
|
||||
// Message::DMA => while !self.nes.run_one_clock_cycle().dma {},
|
||||
// Message::CPU => while !self.nes.run_one_clock_cycle().cpu_exec {},
|
||||
// Message::DebugInt => while !self.nes.run_one_clock_cycle().dbg_int {},
|
||||
Message::WindowClosed(id) => {
|
||||
if let Some(WindowType::Main) = self.windows.remove(&id) {
|
||||
return iced::exit();
|
||||
@@ -238,8 +240,13 @@ impl Emulator {
|
||||
}
|
||||
Message::Export(ty) => {
|
||||
let raw: Vec<_> = match ty {
|
||||
MemoryTy::Cpu => (0..=0xFFFF).map(|i| self.nes.mem().peek_cpu(i).unwrap_or(0)).collect(),
|
||||
MemoryTy::PPU => (0..=0xFFFF).map(|i| self.nes.mem().peek_ppu(i).unwrap_or(0)).collect(),
|
||||
MemoryTy::Cpu => (0..=0xFFFF)
|
||||
.map(|i| self.nes.mem().peek_cpu(i).unwrap_or(0))
|
||||
.collect(),
|
||||
MemoryTy::PPU => (0..=0xFFFF)
|
||||
.map(|i| self.nes.mem().peek_ppu(i).unwrap_or(0))
|
||||
.collect(),
|
||||
MemoryTy::OAM => (0..=0xFF).map(|i| self.nes.ppu().peek_oam(i)).collect(),
|
||||
};
|
||||
return Task::future(async move {
|
||||
if let Some(file) = rfd::AsyncFileDialog::new()
|
||||
@@ -290,10 +297,13 @@ impl Emulator {
|
||||
Some(WindowType::Memory(ty, view)) => {
|
||||
let hex = match ty {
|
||||
MemoryTy::Cpu => view
|
||||
.render(self.nes.mem(), false)
|
||||
.render_cpu(self.nes.mem())
|
||||
.map(move |e| Message::Hex(win, e)),
|
||||
MemoryTy::PPU => view
|
||||
.render(self.nes.mem(), true)
|
||||
.render_ppu(self.nes.mem())
|
||||
.map(move |e| Message::Hex(win, e)),
|
||||
MemoryTy::OAM => view
|
||||
.render_oam(self.nes.ppu())
|
||||
.map(move |e| Message::Hex(win, e)),
|
||||
};
|
||||
let content = column![row![header_menu("Export", [*ty], Message::Export)], hex]
|
||||
@@ -338,6 +348,7 @@ impl Emulator {
|
||||
HeaderButton::Open(WindowType::Debugger),
|
||||
HeaderButton::Open(WindowType::Memory(MemoryTy::Cpu, HexView {})),
|
||||
HeaderButton::Open(WindowType::Memory(MemoryTy::PPU, HexView {})),
|
||||
HeaderButton::Open(WindowType::Memory(MemoryTy::OAM, HexView {})),
|
||||
HeaderButton::Open(WindowType::TileMap),
|
||||
HeaderButton::Open(WindowType::TileViewer),
|
||||
HeaderButton::Open(WindowType::Palette),
|
||||
|
||||
32
src/mem.rs
32
src/mem.rs
@@ -200,10 +200,8 @@ impl<R: Copy> MemoryMap<R> {
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Memory for MemoryMap<R> {
|
||||
fn peek(&self, addr: u16) -> Option<u8> {
|
||||
fn peek_val(&self, addr: u16) -> Option<u8> {
|
||||
for segment in &self.segments {
|
||||
if segment.position <= addr && addr - segment.position < segment.size {
|
||||
return match &segment.mem {
|
||||
@@ -212,7 +210,7 @@ impl<R> Memory for MemoryMap<R> {
|
||||
Data::Reg(_) => None,
|
||||
Data::Mirror(pos) => {
|
||||
let offset = addr - segment.position;
|
||||
self.peek(pos + offset)
|
||||
self.peek_val(pos + offset)
|
||||
}
|
||||
Data::Disabled => None,
|
||||
};
|
||||
@@ -220,6 +218,20 @@ impl<R> Memory for MemoryMap<R> {
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Copy> Memory for MemoryMap<R> {
|
||||
fn peek(&self, addr: usize) -> Option<u8> {
|
||||
self.peek_val(addr as u16)
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.segments
|
||||
.iter()
|
||||
.map(|s| s.position as usize + s.size as usize)
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn edit_ver(&self) -> usize {
|
||||
self.edit_ver
|
||||
@@ -269,7 +281,7 @@ impl fmt::Display for SegmentId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Mapped {
|
||||
cpu: MemoryMap<CPUMMRegisters>,
|
||||
ppu: MemoryMap<PPUMMRegisters>,
|
||||
@@ -444,13 +456,13 @@ impl Mapped {
|
||||
}
|
||||
|
||||
pub fn peek_ppu(&self, addr: u16) -> Option<u8> {
|
||||
self.ppu.peek(addr)
|
||||
self.ppu.peek_val(addr)
|
||||
}
|
||||
pub fn ppu_edit_ver(&self) -> usize {
|
||||
self.ppu.edit_ver
|
||||
}
|
||||
pub fn peek_cpu(&self, addr: u16) -> Option<u8> {
|
||||
self.cpu.peek(addr)
|
||||
self.cpu.peek_val(addr)
|
||||
}
|
||||
pub fn cpu_edit_ver(&self) -> usize {
|
||||
self.cpu.edit_ver
|
||||
@@ -608,11 +620,7 @@ impl<'a> CpuMem<'a> {
|
||||
}
|
||||
Some((CPUMMRegisters::APU, offset, val)) => {
|
||||
if offset == 0x014 {
|
||||
*self.dma = DmaState::Running {
|
||||
cpu_addr: (val as u16) << 8,
|
||||
rem: 0xFF,
|
||||
read: None,
|
||||
};
|
||||
*self.dma = DmaState::Idle((val as u16) << 8);
|
||||
} else if offset == 0x16 {
|
||||
self.controllers.write_joy_strobe(val);
|
||||
} else {
|
||||
|
||||
14
src/ppu.rs
14
src/ppu.rs
@@ -81,6 +81,7 @@ pub enum PPUMMRegisters {
|
||||
pub struct OAM {
|
||||
mem: Vec<u8>,
|
||||
addr: u8,
|
||||
edit_ver: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -565,6 +566,7 @@ impl PPU {
|
||||
oam: OAM {
|
||||
mem: vec![0u8; 256],
|
||||
addr: 0,
|
||||
edit_ver: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -603,7 +605,7 @@ impl PPU {
|
||||
_ => panic!("No register at {:02X}", offset),
|
||||
}
|
||||
}
|
||||
pub fn write_reg(&mut self, mem: &mut PpuMem, offset: u16, val: u8) {
|
||||
pub fn write_reg(&mut self, mem: &mut PpuMem, offset: u16, mut val: u8) {
|
||||
match offset {
|
||||
0x00 => {
|
||||
self.nmi_enabled = val & 0b1000_0000 != 0;
|
||||
@@ -631,8 +633,12 @@ impl PPU {
|
||||
}
|
||||
0x03 => self.oam.addr = val,
|
||||
0x04 => {
|
||||
if self.oam.addr % 4 == 2 {
|
||||
val &= 0b11100011;
|
||||
}
|
||||
self.oam.mem[self.oam.addr as usize] = val;
|
||||
self.oam.addr = self.oam.addr.wrapping_add(1);
|
||||
self.oam.edit_ver += 1;
|
||||
}
|
||||
0x05 => {
|
||||
if self.background.w {
|
||||
@@ -865,6 +871,12 @@ impl PPU {
|
||||
pub fn irq_waiting(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
pub fn peek_oam(&self, addr: u8) -> u8 {
|
||||
self.oam.mem[addr as usize]
|
||||
}
|
||||
pub fn oam_edit_ver(&self) -> usize {
|
||||
self.oam.edit_ver
|
||||
}
|
||||
|
||||
pub fn render_name_table<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
|
||||
for y in 0..60 {
|
||||
|
||||
36
src/test_roms/alu_bit.s
Normal file
36
src/test_roms/alu_bit.s
Normal file
@@ -0,0 +1,36 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
.include "testing.s"
|
||||
|
||||
zp_res VAL
|
||||
RAM = $300
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
lda #$F0
|
||||
sta VAL
|
||||
lda #$0F
|
||||
bit VAL
|
||||
php
|
||||
pla
|
||||
sta RAM+0
|
||||
|
||||
lda #$F0
|
||||
sta $400
|
||||
lda #$0F
|
||||
bit $400
|
||||
php
|
||||
pla
|
||||
sta RAM+1
|
||||
|
||||
stp
|
||||
|
||||
nmi:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
@@ -61,3 +61,8 @@ rom_test!(implied_instructions, "implied_instrs.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []");
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
});
|
||||
|
||||
rom_test!(bit, "alu_bit.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction(), "0x8021 HLT :2 []");
|
||||
mem_cmp(&nes, 0x300, &[0xF6, 0xF6]);
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
mod cpu_reset_ram;
|
||||
mod instr_test_v3;
|
||||
mod instructions;
|
||||
mod ppu;
|
||||
mod interrupts;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user