Compare commits

...

1 Commits

Author SHA1 Message Date
148ab2004d Minor refactors and bug fixes
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
2026-01-26 01:21:37 -06:00
10 changed files with 371 additions and 166 deletions

View File

@@ -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 {

View File

@@ -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) {
count -= 1;
if count <= 0 {
break;
}
}
if nes.halted() {
break;
}
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;
}
}
@@ -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!(),
}
}

View File

@@ -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!()

View File

@@ -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,
}

View File

@@ -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),

View File

@@ -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 {

View File

@@ -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 {

28
src/test_roms/alu_bit.s Normal file
View File

@@ -0,0 +1,28 @@
; 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
stp
nmi:
stp
irq:
stp

View File

@@ -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(), "0x8012 HLT :2 []");
mem_cmp(&nes, 0x300, &[0xF6]);
});

View File

@@ -1,5 +1,5 @@
mod cpu_reset_ram;
mod instr_test_v3;
mod instructions;
mod ppu;
mod interrupts;