Compare commits
2 Commits
b5e1d1a4c3
...
148ab2004d
| Author | SHA1 | Date | |
|---|---|---|---|
|
148ab2004d
|
|||
|
f861f75b21
|
36
Cargo.lock
generated
36
Cargo.lock
generated
@@ -871,6 +871,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
|
"block2 0.6.2",
|
||||||
|
"libc",
|
||||||
"objc2 0.6.3",
|
"objc2 0.6.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2195,6 +2197,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitfield",
|
"bitfield",
|
||||||
"iced",
|
"iced",
|
||||||
|
"rfd",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -2875,6 +2878,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pollster"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -3172,6 +3181,33 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rfd"
|
||||||
|
version = "0.17.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220"
|
||||||
|
dependencies = [
|
||||||
|
"block2 0.6.2",
|
||||||
|
"dispatch2",
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"objc2 0.6.3",
|
||||||
|
"objc2-app-kit 0.3.2",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
"percent-encoding",
|
||||||
|
"pollster",
|
||||||
|
"raw-window-handle",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-protocols",
|
||||||
|
"web-sys",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rgb"
|
name = "rgb"
|
||||||
version = "0.8.52"
|
version = "0.8.52"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ edition = "2024"
|
|||||||
bitfield = "0.19.3"
|
bitfield = "0.19.3"
|
||||||
# iced = { version = "0.14.0", features = ["debug", "canvas", "tokio", "lazy", "image", "advanced"] }
|
# iced = { version = "0.14.0", features = ["debug", "canvas", "tokio", "lazy", "image", "advanced"] }
|
||||||
iced = { path = "../iced", features = ["debug", "canvas", "tokio", "lazy", "image", "advanced"] }
|
iced = { path = "../iced", features = ["debug", "canvas", "tokio", "lazy", "image", "advanced"] }
|
||||||
|
rfd = "0.17.2"
|
||||||
# iced_graphics = { version = "0.14.0", features = ["geometry", "image"] }
|
# iced_graphics = { version = "0.14.0", features = ["geometry", "image"] }
|
||||||
# iced_widget = { version = "0.13.4", features = ["canvas", "image"] }
|
# iced_widget = { version = "0.13.4", features = ["canvas", "image"] }
|
||||||
thiserror = "2.0.17"
|
thiserror = "2.0.17"
|
||||||
|
|||||||
38
src/apu.rs
38
src/apu.rs
@@ -1,6 +1,7 @@
|
|||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
bitfield::bitfield! {
|
bitfield::bitfield! {
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct DutyVol(u8);
|
pub struct DutyVol(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
duty, set_duty: 7, 6;
|
duty, set_duty: 7, 6;
|
||||||
@@ -10,6 +11,7 @@ bitfield::bitfield! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitfield::bitfield! {
|
bitfield::bitfield! {
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Sweep(u8);
|
pub struct Sweep(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
enable, set_enable: 7;
|
enable, set_enable: 7;
|
||||||
@@ -19,12 +21,14 @@ bitfield::bitfield! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitfield::bitfield! {
|
bitfield::bitfield! {
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct LengthTimerHigh(u8);
|
pub struct LengthTimerHigh(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
length, set_length: 7, 3;
|
length, set_length: 7, 3;
|
||||||
timer_high, set_timer_high: 2, 0;
|
timer_high, set_timer_high: 2, 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct PulseChannel {
|
struct PulseChannel {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
duty_vol: DutyVol,
|
duty_vol: DutyVol,
|
||||||
@@ -92,12 +96,15 @@ bitfield::bitfield! {
|
|||||||
value, set_value: 6, 0;
|
value, set_value: 6, 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct TriangleChannel {
|
struct TriangleChannel {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
}
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct NoiseChannel {
|
struct NoiseChannel {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
}
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct DeltaChannel {
|
struct DeltaChannel {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
}
|
}
|
||||||
@@ -108,6 +115,7 @@ impl DeltaChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct FrameCounter {
|
struct FrameCounter {
|
||||||
count: usize,
|
count: usize,
|
||||||
mode_5_step: bool,
|
mode_5_step: bool,
|
||||||
@@ -115,6 +123,7 @@ struct FrameCounter {
|
|||||||
irq: bool,
|
irq: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct APU {
|
pub struct APU {
|
||||||
pulse_1: PulseChannel,
|
pulse_1: PulseChannel,
|
||||||
pulse_2: PulseChannel,
|
pulse_2: PulseChannel,
|
||||||
@@ -185,6 +194,14 @@ impl APU {
|
|||||||
self.pulse_2.length_timer_high.0 = val;
|
self.pulse_2.length_timer_high.0 = val;
|
||||||
self.pulse_2.reset();
|
self.pulse_2.reset();
|
||||||
}
|
}
|
||||||
|
0x09 => (), // Unused, technically noise channel?
|
||||||
|
0x08 | 0x0A | 0x0B => {
|
||||||
|
// TODO: Triangle channel
|
||||||
|
}
|
||||||
|
0x0D => (), // Unused, technically noise channel?
|
||||||
|
0x0C | 0x0E | 0x0F => {
|
||||||
|
// TODO: Noise channel
|
||||||
|
}
|
||||||
0x10 => {
|
0x10 => {
|
||||||
assert_eq!(val, 0x00);
|
assert_eq!(val, 0x00);
|
||||||
// TODO: implement this value
|
// TODO: implement this value
|
||||||
@@ -192,6 +209,12 @@ impl APU {
|
|||||||
0x11 => {
|
0x11 => {
|
||||||
// TODO: load dmc counter with (val & 7F)
|
// TODO: load dmc counter with (val & 7F)
|
||||||
}
|
}
|
||||||
|
0x12 => {
|
||||||
|
// TODO: DMC
|
||||||
|
}
|
||||||
|
0x13 => {
|
||||||
|
// TODO: DMC
|
||||||
|
}
|
||||||
0x15 => {
|
0x15 => {
|
||||||
self.dmc.enabled = val & 0b0001_0000 != 0;
|
self.dmc.enabled = val & 0b0001_0000 != 0;
|
||||||
self.noise.enabled = val & 0b0000_1000 != 0;
|
self.noise.enabled = val & 0b0000_1000 != 0;
|
||||||
@@ -200,7 +223,7 @@ impl APU {
|
|||||||
self.pulse_1.enabled = val & 0b0000_0001 != 0;
|
self.pulse_1.enabled = val & 0b0000_0001 != 0;
|
||||||
}
|
}
|
||||||
0x17 => {
|
0x17 => {
|
||||||
self.frame_counter.mode_5_step = val & 0b1000_0000 == 0;
|
self.frame_counter.mode_5_step = val & 0b1000_0000 != 0;
|
||||||
self.frame_counter.interrupt_enabled = val & 0b0100_0000 == 0;
|
self.frame_counter.interrupt_enabled = val & 0b0100_0000 == 0;
|
||||||
if !self.frame_counter.interrupt_enabled {
|
if !self.frame_counter.interrupt_enabled {
|
||||||
self.frame_counter.irq = false;
|
self.frame_counter.irq = false;
|
||||||
@@ -225,24 +248,23 @@ impl APU {
|
|||||||
pub fn run_one_clock_cycle(&mut self, ppu_cycle: usize) -> bool {
|
pub fn run_one_clock_cycle(&mut self, ppu_cycle: usize) -> bool {
|
||||||
if ppu_cycle % 6 == 1 {
|
if ppu_cycle % 6 == 1 {
|
||||||
// APU Frame Counter clock cycle
|
// APU Frame Counter clock cycle
|
||||||
if self.frame_counter.mode_5_step {
|
self.frame_counter.count += 1;
|
||||||
todo!()
|
|
||||||
} else {
|
|
||||||
if self.frame_counter.count == 3728 {
|
if self.frame_counter.count == 3728 {
|
||||||
self.q_frame_clock();
|
self.q_frame_clock();
|
||||||
} else if self.frame_counter.count == 7456 {
|
} else if self.frame_counter.count == 7456 {
|
||||||
self.h_frame_clock();
|
self.h_frame_clock();
|
||||||
} else if self.frame_counter.count == 11185 {
|
} else if self.frame_counter.count == 11185 {
|
||||||
self.q_frame_clock();
|
self.q_frame_clock();
|
||||||
} else if self.frame_counter.count == 14914 {
|
} else if self.frame_counter.count == 14914 && !self.frame_counter.mode_5_step {
|
||||||
self.frame_counter.irq = self.frame_counter.interrupt_enabled;
|
self.frame_counter.irq = self.frame_counter.interrupt_enabled;
|
||||||
self.h_frame_clock();
|
self.h_frame_clock();
|
||||||
} else if self.frame_counter.count == 14915 {
|
} else if self.frame_counter.count == 14915 && !self.frame_counter.mode_5_step {
|
||||||
self.frame_counter.count = 0;
|
self.frame_counter.count = 0;
|
||||||
self.frame_counter.irq = self.frame_counter.interrupt_enabled;
|
self.frame_counter.irq = self.frame_counter.interrupt_enabled;
|
||||||
|
} else if self.frame_counter.count == 18640 {
|
||||||
|
self.h_frame_clock();
|
||||||
|
self.frame_counter.count = 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
self.frame_counter.count += 1;
|
|
||||||
|
|
||||||
self.pulse_1.clock();
|
self.pulse_1.clock();
|
||||||
self.pulse_2.clock();
|
self.pulse_2.clock();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Controllers {}
|
pub struct Controllers {}
|
||||||
|
|
||||||
impl Controllers {
|
impl Controllers {
|
||||||
|
|||||||
2024
src/cpu.rs
Normal file
2024
src/cpu.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct DebugLog {
|
pub struct DebugLog {
|
||||||
current: String,
|
current: String,
|
||||||
history: Vec<String>,
|
history: Vec<String>,
|
||||||
|
|||||||
129
src/debugger.rs
129
src/debugger.rs
@@ -3,7 +3,7 @@ use std::rc::Rc;
|
|||||||
use iced::{
|
use iced::{
|
||||||
Element,
|
Element,
|
||||||
Length::{self, Fill},
|
Length::{self, Fill},
|
||||||
Point, Renderer, Size, Theme,
|
Point, Renderer, Size,
|
||||||
advanced::{
|
advanced::{
|
||||||
Widget,
|
Widget,
|
||||||
layout::Node,
|
layout::Node,
|
||||||
@@ -18,14 +18,13 @@ use iced::{
|
|||||||
canvas::{Frame, Program},
|
canvas::{Frame, Program},
|
||||||
checkbox, column,
|
checkbox, column,
|
||||||
container::bordered_box,
|
container::bordered_box,
|
||||||
image, number_input, hex_input, row,
|
hex_input, image, number_input, row,
|
||||||
rule::horizontal,
|
rule::horizontal,
|
||||||
scrollable, text,
|
scrollable, text,
|
||||||
},
|
},
|
||||||
window::Event,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{CycleResult, NES, PPU};
|
use crate::{Break, CycleResult, NES, PPU, mem::Mapped};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DebuggerState {
|
pub struct DebuggerState {
|
||||||
@@ -117,7 +116,7 @@ impl DebuggerState {
|
|||||||
labelled("X:", text(format!("{:02X}", nes.cpu.x))),
|
labelled("X:", text(format!("{:02X}", nes.cpu.x))),
|
||||||
labelled("Y:", text(format!("{:02X}", nes.cpu.y))),
|
labelled("Y:", text(format!("{:02X}", nes.cpu.y))),
|
||||||
labelled("PC:", text(format!("{:04X}", nes.cpu.pc))),
|
labelled("PC:", text(format!("{:04X}", nes.cpu.pc))),
|
||||||
labelled("Cycle:", text(format!("{}", nes.cycle))),
|
labelled("Cycle:", text(format!("{}", nes.cpu_cycle()))),
|
||||||
labelled("SP:", text(format!("{:02X}", nes.cpu.sp))),
|
labelled("SP:", text(format!("{:02X}", nes.cpu.sp))),
|
||||||
]
|
]
|
||||||
.spacing(5.),
|
.spacing(5.),
|
||||||
@@ -255,23 +254,41 @@ impl DebuggerState {
|
|||||||
|
|
||||||
fn run_n_clock_cycles(nes: &mut NES, n: usize) {
|
fn run_n_clock_cycles(nes: &mut NES, n: usize) {
|
||||||
for _ in 0..n {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn run_until(nes: &mut NES, mut f: impl FnMut(CycleResult, &NES) -> bool, mut count: usize) {
|
fn run_until(
|
||||||
loop {
|
nes: &mut NES,
|
||||||
let res = nes.run_one_clock_cycle();
|
br: &Break,
|
||||||
if res.dbg_int || f(res, nes) {
|
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;
|
count -= 1;
|
||||||
if count <= 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if nes.halted {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,21 +298,49 @@ impl DebuggerState {
|
|||||||
DebuggerMessage::SetCPUCycles(n) => self.cpu_cycles = n,
|
DebuggerMessage::SetCPUCycles(n) => self.cpu_cycles = n,
|
||||||
DebuggerMessage::SetInstructions(n) => self.instructions = n,
|
DebuggerMessage::SetInstructions(n) => self.instructions = n,
|
||||||
DebuggerMessage::SetScanLines(n) => self.scan_lines = 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::SetFrames(n) => self.frames = n,
|
||||||
DebuggerMessage::SetBreakpoint(n) => self.breakpoint = n,
|
DebuggerMessage::SetBreakpoint(n) => self.breakpoint = n,
|
||||||
DebuggerMessage::RunPPUCycles => Self::run_n_clock_cycles(nes, self.ppu_cycles),
|
DebuggerMessage::RunPPUCycles => Self::run_n_clock_cycles(nes, self.ppu_cycles),
|
||||||
DebuggerMessage::RunCPUCycles => Self::run_n_clock_cycles(nes, self.cpu_cycles * 3),
|
DebuggerMessage::RunCPUCycles => Self::run_n_clock_cycles(nes, self.cpu_cycles * 3),
|
||||||
DebuggerMessage::RunInstructions => {
|
// DebuggerMessage::RunInstructions => Self::run_until_n(
|
||||||
Self::run_until(nes, |c, _| c.cpu_exec, self.instructions)
|
// 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::RunScanLines => Self::run_n_clock_cycles(nes, self.scan_lines * 341),
|
||||||
DebuggerMessage::RunToScanLine => {
|
DebuggerMessage::RunToScanLine => Self::run_until(
|
||||||
Self::run_until(nes, |_, n| n.ppu.scanline == self.to_scan_line, 1)
|
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::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(
|
||||||
DebuggerMessage::Run => Self::run_until(nes, |_, nes| nes.halted, 1),
|
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::Pause => todo!(),
|
DebuggerMessage::Pause => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,9 +411,9 @@ pub fn labelled_box<'a, Message: 'a>(label: &'a str, value: bool) -> Element<'a,
|
|||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum DbgImage<'a> {
|
pub enum DbgImage<'a> {
|
||||||
NameTable(&'a PPU),
|
NameTable(&'a Mapped, &'a PPU),
|
||||||
PatternTable(&'a PPU),
|
PatternTable(&'a Mapped, &'a PPU),
|
||||||
Palette(&'a PPU),
|
Palette(&'a Mapped, &'a PPU),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, T> Program<Message, T> for DbgImage<'_> {
|
impl<Message, T> Program<Message, T> for DbgImage<'_> {
|
||||||
@@ -388,9 +433,11 @@ impl<Message, T> Program<Message, T> for DbgImage<'_> {
|
|||||||
name_table_frame.scale(2.);
|
name_table_frame.scale(2.);
|
||||||
// println!("Position: {:?}", cursor.position());
|
// println!("Position: {:?}", cursor.position());
|
||||||
match self {
|
match self {
|
||||||
DbgImage::NameTable(ppu) => ppu.render_name_table(&mut name_table_frame),
|
DbgImage::NameTable(mem, ppu) => ppu.render_name_table(mem, &mut name_table_frame),
|
||||||
DbgImage::PatternTable(ppu) => ppu.render_pattern_tables(&mut name_table_frame),
|
DbgImage::PatternTable(mem, ppu) => {
|
||||||
DbgImage::Palette(ppu) => ppu.render_palette(&mut name_table_frame),
|
ppu.render_pattern_tables(mem, &mut name_table_frame)
|
||||||
|
}
|
||||||
|
DbgImage::Palette(_, ppu) => ppu.render_palette(&mut name_table_frame),
|
||||||
};
|
};
|
||||||
vec![name_table_frame.into_geometry()]
|
vec![name_table_frame.into_geometry()]
|
||||||
}
|
}
|
||||||
@@ -399,23 +446,23 @@ impl<Message, T> Program<Message, T> for DbgImage<'_> {
|
|||||||
impl DbgImage<'_> {
|
impl DbgImage<'_> {
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
match self {
|
match self {
|
||||||
DbgImage::NameTable(_) => Length::Fixed(512. * 2.),
|
DbgImage::NameTable(_, _) => Length::Fixed(512. * 2.),
|
||||||
DbgImage::PatternTable(_) => Length::Fixed(16. * 8. * 2.),
|
DbgImage::PatternTable(_, _) => Length::Fixed(16. * 8. * 2.),
|
||||||
DbgImage::Palette(_) => Length::Fixed(40. * 2.),
|
DbgImage::Palette(_, _) => Length::Fixed(40. * 2.),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn height(&self) -> Length {
|
fn height(&self) -> Length {
|
||||||
match self {
|
match self {
|
||||||
DbgImage::NameTable(_) => Length::Fixed(512. * 2.),
|
DbgImage::NameTable(_, _) => Length::Fixed(512. * 2.),
|
||||||
DbgImage::PatternTable(_) => Length::Fixed(16. * 8. * 2. * 2.),
|
DbgImage::PatternTable(_, _) => Length::Fixed(16. * 8. * 2. * 2.),
|
||||||
DbgImage::Palette(_) => Length::Fixed(80. * 2.),
|
DbgImage::Palette(_, _) => Length::Fixed(80. * 2.),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn help(&self, cursor: Point) -> Option<String> {
|
fn help(&self, cursor: Point) -> Option<String> {
|
||||||
match self {
|
match self {
|
||||||
DbgImage::NameTable(ppu) => ppu.name_cursor_info(cursor),
|
DbgImage::NameTable(mem, ppu) => ppu.name_cursor_info(mem, cursor),
|
||||||
DbgImage::PatternTable(ppu) => ppu.pattern_cursor_info(cursor),
|
DbgImage::PatternTable(_, ppu) => ppu.pattern_cursor_info(cursor),
|
||||||
DbgImage::Palette(ppu) => ppu.palette_cursor_info(cursor),
|
DbgImage::Palette(_, ppu) => ppu.palette_cursor_info(cursor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
108
src/hex_view.rs
108
src/hex_view.rs
@@ -1,19 +1,76 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use iced::{widget::{column, lazy, row, text}, Element, Font, Length::Fill, Task};
|
use iced::{
|
||||||
|
Element, Font,
|
||||||
|
Length::Fill,
|
||||||
|
Task,
|
||||||
|
widget::{column, lazy, row, text},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{mem::Mapped, PPU};
|
||||||
|
|
||||||
pub trait Memory {
|
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;
|
fn edit_ver(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Cpu<'a>(pub &'a Mapped);
|
||||||
|
|
||||||
|
impl Memory for Cpu<'_> {
|
||||||
|
fn peek(&self, val: usize) -> Option<u8> {
|
||||||
|
self.0.peek_cpu(val as u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
0x10000
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit_ver(&self) -> usize {
|
||||||
|
self.0.cpu_edit_ver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Ppu<'a>(pub &'a Mapped);
|
||||||
|
|
||||||
|
impl Memory for Ppu<'_> {
|
||||||
|
fn peek(&self, val: usize) -> Option<u8> {
|
||||||
|
self.0.peek_ppu(val as u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
0x10000
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit_ver(&self) -> usize {
|
||||||
|
self.0.ppu_edit_ver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum HexEvent {}
|
pub enum HexEvent {}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct HexView {
|
pub struct HexView {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Val(Option<u8>);
|
struct Val(Option<u8>);
|
||||||
impl fmt::Display for Val {
|
impl fmt::Display for Val {
|
||||||
@@ -31,9 +88,9 @@ impl HexView {
|
|||||||
Self {}
|
Self {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<'a>(&self, mem: &'a impl Memory) -> Element<'a, HexEvent> {
|
pub fn render_any<'a, M: Memory + Copy + 'a>(&self, mem: M) -> Element<'a, HexEvent> {
|
||||||
struct Row<'a, M>(u16, &'a M);
|
struct Row<M: Memory>(usize, M);
|
||||||
impl<M: Memory> fmt::Display for Row<'_, M> {
|
impl<'a, M: Memory + 'a> fmt::Display for Row<M> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", Val(self.1.peek(self.0)))?;
|
write!(f, "{}", Val(self.1.peek(self.0)))?;
|
||||||
for i in 1..16 {
|
for i in 1..16 {
|
||||||
@@ -44,12 +101,35 @@ impl HexView {
|
|||||||
}
|
}
|
||||||
column![
|
column![
|
||||||
text!("Hex view"),
|
text!("Hex view"),
|
||||||
iced::widget::scrollable(lazy(mem.edit_ver(), |_| column([
|
iced::widget::scrollable(lazy(
|
||||||
text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F").font(Font::MONOSPACE).into()
|
mem.edit_ver(),
|
||||||
].into_iter().chain((0..u16::MAX).step_by(16).map(|off| {
|
move |_| column(
|
||||||
text!(" {off:04X} | {}", Row(off, mem)).font(Font::MONOSPACE).into()
|
[
|
||||||
}))))).width(Fill),
|
text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F")
|
||||||
].width(Fill).into()
|
.font(Font::MONOSPACE)
|
||||||
|
.into()
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.chain((0..mem.len()).step_by(16).map(|off| {
|
||||||
|
text!(" {off:04X} | {}", Row(off, mem))
|
||||||
|
.font(Font::MONOSPACE)
|
||||||
|
.into()
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.width(Fill),
|
||||||
|
]
|
||||||
|
.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> {
|
pub fn update(&mut self, ev: HexEvent) -> Task<HexEvent> {
|
||||||
|
|||||||
2344
src/lib.rs
2344
src/lib.rs
File diff suppressed because it is too large
Load Diff
139
src/main.rs
139
src/main.rs
@@ -1,24 +1,26 @@
|
|||||||
use std::{collections::HashMap, fmt, time::Duration};
|
use std::{collections::HashMap, fmt, time::Duration};
|
||||||
|
|
||||||
use iced::{
|
use iced::{
|
||||||
Color, Element, Font,
|
Color, Element,
|
||||||
Length::{Fill, Shrink},
|
Length::{Fill, Shrink},
|
||||||
Point, Rectangle, Renderer, Size, Subscription, Task, Theme, mouse,
|
Point, Renderer, Size, Subscription, Task, Theme,
|
||||||
|
advanced::graphics::compositor::Display,
|
||||||
|
mouse,
|
||||||
widget::{
|
widget::{
|
||||||
Action, Canvas, button,
|
Canvas,
|
||||||
canvas::{Frame, Program},
|
canvas::{Frame, Program},
|
||||||
column, container, mouse_area, row, text,
|
column, container, row,
|
||||||
},
|
},
|
||||||
window::{self, Id, Settings},
|
window::{self, Id, Settings},
|
||||||
};
|
};
|
||||||
use nes_emu::{
|
use nes_emu::{
|
||||||
NES, PPU,
|
NES,
|
||||||
debugger::{DbgImage, DebuggerMessage, DebuggerState, dbg_image},
|
debugger::{DbgImage, DebuggerMessage, DebuggerState, dbg_image},
|
||||||
header_menu::header_menu,
|
header_menu::header_menu,
|
||||||
hex_view::{HexEvent, HexView},
|
hex_view::{HexEvent, HexView},
|
||||||
resize_watcher::resize_watcher,
|
resize_watcher::resize_watcher,
|
||||||
};
|
};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::{io::AsyncWriteExt, runtime::Runtime};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "even_odd.nes");
|
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "even_odd.nes");
|
||||||
@@ -26,8 +28,9 @@ 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_red.nes");
|
||||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_fill_name_table.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 = concat!(env!("ROM_DIR"), "/", "int_nmi_exit_timing.nes");
|
||||||
// const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
|
const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
|
||||||
const ROM_FILE: &str = "./cpu_timing_test.nes";
|
// const ROM_FILE: &str = "./cpu_timing_test.nes";
|
||||||
|
// const ROM_FILE: &str = "../nes-test-roms/instr_test-v5/official_only.nes";
|
||||||
|
|
||||||
extern crate nes_emu;
|
extern crate nes_emu;
|
||||||
|
|
||||||
@@ -44,10 +47,17 @@ fn main() -> Result<(), iced::Error> {
|
|||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum MemoryTy {
|
enum MemoryTy {
|
||||||
Cpu,
|
Cpu,
|
||||||
PPU,
|
PPU,
|
||||||
|
OAM,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MemoryTy {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Export {self:?} Memory")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -67,6 +77,7 @@ enum HeaderButton {
|
|||||||
// OpenTileViewer,
|
// OpenTileViewer,
|
||||||
// OpenDebugger,
|
// OpenDebugger,
|
||||||
Open(WindowType),
|
Open(WindowType),
|
||||||
|
OpenRom,
|
||||||
Reset,
|
Reset,
|
||||||
PowerCycle,
|
PowerCycle,
|
||||||
}
|
}
|
||||||
@@ -76,6 +87,7 @@ impl fmt::Display for HeaderButton {
|
|||||||
match self {
|
match self {
|
||||||
Self::Open(WindowType::Memory(MemoryTy::Cpu, _)) => write!(f, "Open Memory Viewer"),
|
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::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::TileMap) => write!(f, "Open TileMap Viewer"),
|
||||||
Self::Open(WindowType::TileViewer) => write!(f, "Open Tile Viewer"),
|
Self::Open(WindowType::TileViewer) => write!(f, "Open Tile Viewer"),
|
||||||
Self::Open(WindowType::Debugger) => write!(f, "Open Debugger"),
|
Self::Open(WindowType::Debugger) => write!(f, "Open Debugger"),
|
||||||
@@ -83,6 +95,7 @@ impl fmt::Display for HeaderButton {
|
|||||||
Self::Open(WindowType::Main) => write!(f, "Create new Main window"),
|
Self::Open(WindowType::Main) => write!(f, "Create new Main window"),
|
||||||
Self::Reset => write!(f, "Reset"),
|
Self::Reset => write!(f, "Reset"),
|
||||||
Self::PowerCycle => write!(f, "Power Cycle"),
|
Self::PowerCycle => write!(f, "Power Cycle"),
|
||||||
|
Self::OpenRom => write!(f, "Open ROM file"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,17 +109,19 @@ struct Emulator {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
Tick(usize),
|
OpenRom(NES),
|
||||||
Frame,
|
// Tick(usize),
|
||||||
DMA,
|
// Frame,
|
||||||
CPU,
|
// DMA,
|
||||||
DebugInt,
|
// CPU,
|
||||||
|
// DebugInt,
|
||||||
WindowClosed(Id),
|
WindowClosed(Id),
|
||||||
WindowOpened(Id),
|
WindowOpened(Id),
|
||||||
Header(HeaderButton),
|
Header(HeaderButton),
|
||||||
Hex(Id, HexEvent),
|
Hex(Id, HexEvent),
|
||||||
Debugger(DebuggerMessage),
|
Debugger(DebuggerMessage),
|
||||||
SetSize(window::Id, Size),
|
SetSize(window::Id, Size),
|
||||||
|
Export(MemoryTy),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Emulator {
|
impl Emulator {
|
||||||
@@ -157,15 +172,15 @@ impl Emulator {
|
|||||||
|
|
||||||
fn update(&mut self, message: Message) -> Task<Message> {
|
fn update(&mut self, message: Message) -> Task<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Tick(count) => {
|
// Message::Tick(count) => {
|
||||||
for _ in 0..count {
|
// for _ in 0..count {
|
||||||
self.nes.run_one_clock_cycle();
|
// self.nes.run_one_clock_cycle();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Message::Frame => while !self.nes.run_one_clock_cycle().ppu_frame {},
|
// Message::Frame => while !self.nes.run_one_clock_cycle().ppu_frame {},
|
||||||
Message::DMA => while !self.nes.run_one_clock_cycle().dma {},
|
// Message::DMA => while !self.nes.run_one_clock_cycle().dma {},
|
||||||
Message::CPU => while !self.nes.run_one_clock_cycle().cpu_exec {},
|
// Message::CPU => while !self.nes.run_one_clock_cycle().cpu_exec {},
|
||||||
Message::DebugInt => while !self.nes.run_one_clock_cycle().dbg_int {},
|
// Message::DebugInt => while !self.nes.run_one_clock_cycle().dbg_int {},
|
||||||
Message::WindowClosed(id) => {
|
Message::WindowClosed(id) => {
|
||||||
if let Some(WindowType::Main) = self.windows.remove(&id) {
|
if let Some(WindowType::Main) = self.windows.remove(&id) {
|
||||||
return iced::exit();
|
return iced::exit();
|
||||||
@@ -180,6 +195,22 @@ impl Emulator {
|
|||||||
Message::Header(HeaderButton::Open(w)) => {
|
Message::Header(HeaderButton::Open(w)) => {
|
||||||
return self.open(w);
|
return self.open(w);
|
||||||
}
|
}
|
||||||
|
Message::Header(HeaderButton::OpenRom) => {
|
||||||
|
return Task::future(
|
||||||
|
rfd::AsyncFileDialog::new()
|
||||||
|
.set_directory(".")
|
||||||
|
.add_filter("NES", &["nes"])
|
||||||
|
.set_title("Open NES Rom file")
|
||||||
|
.pick_file(),
|
||||||
|
)
|
||||||
|
.and_then(|p| {
|
||||||
|
Task::future(async move {
|
||||||
|
// println!("Opening: {}", p.path().display());
|
||||||
|
NES::async_load_nes_file(p.path()).await.ok()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.and_then(|n| Task::done(Message::OpenRom(n)));
|
||||||
|
}
|
||||||
Message::Hex(id, val) => {
|
Message::Hex(id, val) => {
|
||||||
if let Some(WindowType::Memory(_, view)) = self.windows.get_mut(&id) {
|
if let Some(WindowType::Memory(_, view)) = self.windows.get_mut(&id) {
|
||||||
return view.update(val).map(move |e| Message::Hex(id, e));
|
return view.update(val).map(move |e| Message::Hex(id, e));
|
||||||
@@ -203,6 +234,38 @@ impl Emulator {
|
|||||||
})
|
})
|
||||||
.then(move |_| iced::window::resize(id, size));
|
.then(move |_| iced::window::resize(id, size));
|
||||||
}
|
}
|
||||||
|
Message::OpenRom(nes) => {
|
||||||
|
self.nes = nes;
|
||||||
|
self.nes.power_cycle();
|
||||||
|
}
|
||||||
|
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::OAM => (0..=0xFF).map(|i| self.nes.ppu().peek_oam(i)).collect(),
|
||||||
|
};
|
||||||
|
return Task::future(async move {
|
||||||
|
if let Some(file) = rfd::AsyncFileDialog::new()
|
||||||
|
.set_directory(".")
|
||||||
|
.set_file_name("output.dmp")
|
||||||
|
.set_title("Save memory dump")
|
||||||
|
.save_file()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tokio::fs::File::create(file.path())
|
||||||
|
.await
|
||||||
|
.expect("Failed to save file")
|
||||||
|
.write_all(&raw)
|
||||||
|
.await
|
||||||
|
.expect("Failed to write dump");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.discard();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// self.image.0.clone_from(self.nes.image());
|
// self.image.0.clone_from(self.nes.image());
|
||||||
Task::none()
|
Task::none()
|
||||||
@@ -234,20 +297,29 @@ impl Emulator {
|
|||||||
Some(WindowType::Memory(ty, view)) => {
|
Some(WindowType::Memory(ty, view)) => {
|
||||||
let hex = match ty {
|
let hex = match ty {
|
||||||
MemoryTy::Cpu => view
|
MemoryTy::Cpu => view
|
||||||
.render(self.nes.cpu_mem())
|
.render_cpu(self.nes.mem())
|
||||||
.map(move |e| Message::Hex(win, e)),
|
.map(move |e| Message::Hex(win, e)),
|
||||||
MemoryTy::PPU => view
|
MemoryTy::PPU => view
|
||||||
.render(self.nes.ppu().mem())
|
.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)),
|
.map(move |e| Message::Hex(win, e)),
|
||||||
};
|
};
|
||||||
let content = column![hex].width(Fill).height(Fill);
|
let content = column![row![header_menu("Export", [*ty], Message::Export)], hex]
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill);
|
||||||
container(content).width(Fill).height(Fill).into()
|
container(content).width(Fill).height(Fill).into()
|
||||||
}
|
}
|
||||||
Some(WindowType::TileMap) => dbg_image(DbgImage::NameTable(self.nes.ppu())).into(),
|
Some(WindowType::TileMap) => {
|
||||||
Some(WindowType::TileViewer) => {
|
dbg_image(DbgImage::NameTable(self.nes.mem(), self.nes.ppu())).into()
|
||||||
dbg_image(DbgImage::PatternTable(self.nes.ppu())).into()
|
}
|
||||||
|
Some(WindowType::TileViewer) => {
|
||||||
|
dbg_image(DbgImage::PatternTable(self.nes.mem(), self.nes.ppu())).into()
|
||||||
|
}
|
||||||
|
Some(WindowType::Palette) => {
|
||||||
|
dbg_image(DbgImage::Palette(self.nes.mem(), self.nes.ppu())).into()
|
||||||
}
|
}
|
||||||
Some(WindowType::Palette) => dbg_image(DbgImage::Palette(self.nes.ppu())).into(),
|
|
||||||
Some(WindowType::Debugger) => {
|
Some(WindowType::Debugger) => {
|
||||||
container(self.debugger.view(&self.nes).map(Message::Debugger))
|
container(self.debugger.view(&self.nes).map(Message::Debugger))
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
@@ -263,7 +335,11 @@ impl Emulator {
|
|||||||
row![
|
row![
|
||||||
header_menu(
|
header_menu(
|
||||||
"Console",
|
"Console",
|
||||||
[HeaderButton::Reset, HeaderButton::PowerCycle,],
|
[
|
||||||
|
HeaderButton::OpenRom,
|
||||||
|
HeaderButton::Reset,
|
||||||
|
HeaderButton::PowerCycle,
|
||||||
|
],
|
||||||
Message::Header
|
Message::Header
|
||||||
),
|
),
|
||||||
header_menu(
|
header_menu(
|
||||||
@@ -272,6 +348,7 @@ impl Emulator {
|
|||||||
HeaderButton::Open(WindowType::Debugger),
|
HeaderButton::Open(WindowType::Debugger),
|
||||||
HeaderButton::Open(WindowType::Memory(MemoryTy::Cpu, HexView {})),
|
HeaderButton::Open(WindowType::Memory(MemoryTy::Cpu, HexView {})),
|
||||||
HeaderButton::Open(WindowType::Memory(MemoryTy::PPU, HexView {})),
|
HeaderButton::Open(WindowType::Memory(MemoryTy::PPU, HexView {})),
|
||||||
|
HeaderButton::Open(WindowType::Memory(MemoryTy::OAM, HexView {})),
|
||||||
HeaderButton::Open(WindowType::TileMap),
|
HeaderButton::Open(WindowType::TileMap),
|
||||||
HeaderButton::Open(WindowType::TileViewer),
|
HeaderButton::Open(WindowType::TileViewer),
|
||||||
HeaderButton::Open(WindowType::Palette),
|
HeaderButton::Open(WindowType::Palette),
|
||||||
|
|||||||
678
src/mem.rs
678
src/mem.rs
@@ -1,5 +1,11 @@
|
|||||||
use crate::{hex_view::Memory, ppu::PPUMMRegisters};
|
use std::{fmt, sync::Arc};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
CPUMMRegisters, PPU, apu::APU, controllers::Controllers, cpu::DmaState, hex_view::Memory,
|
||||||
|
ppu::PPUMMRegisters,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum Value<'a, R> {
|
pub enum Value<'a, R> {
|
||||||
Value(u8),
|
Value(u8),
|
||||||
Register { reg: &'a R, offset: u16 },
|
Register { reg: &'a R, offset: u16 },
|
||||||
@@ -14,23 +20,29 @@ impl<R> Value<'_, R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum Data<R> {
|
pub enum Data<R> {
|
||||||
RAM(Vec<u8>),
|
RAM(Vec<u8>),
|
||||||
ROM(Vec<u8>),
|
ROM(Arc<[u8]>),
|
||||||
Mirror(usize),
|
Mirror(u16),
|
||||||
Reg(R),
|
Reg(R),
|
||||||
// Disabled(),
|
Disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Segment<R> {
|
pub struct Segment<R> {
|
||||||
name: &'static str,
|
name: SegmentId,
|
||||||
position: u16,
|
position: u16,
|
||||||
size: u16,
|
size: u16,
|
||||||
mem: Data<R>,
|
mem: Data<R>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> Segment<R> {
|
impl<R> Segment<R> {
|
||||||
pub fn ram(name: &'static str, position: u16, size: u16) -> Self {
|
fn is(&self, id: SegmentId) -> bool {
|
||||||
|
self.name == id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ram(name: SegmentId, position: u16, size: u16) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
position,
|
position,
|
||||||
@@ -38,15 +50,19 @@ impl<R> Segment<R> {
|
|||||||
mem: Data::RAM(vec![0u8; size as usize]),
|
mem: Data::RAM(vec![0u8; size as usize]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn rom(name: &'static str, position: u16, raw: &[u8]) -> Self {
|
fn rom<Raw>(name: SegmentId, position: u16, raw: Raw) -> Self
|
||||||
|
where
|
||||||
|
Arc<[u8]>: From<Raw>,
|
||||||
|
{
|
||||||
|
let raw = Arc::from(raw);
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
position,
|
position,
|
||||||
size: raw.len() as u16,
|
size: raw.len() as u16,
|
||||||
mem: Data::ROM(Vec::from(raw)),
|
mem: Data::ROM(raw),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn reg(name: &'static str, position: u16, size: u16, reg: R) -> Self {
|
fn reg(name: SegmentId, position: u16, size: u16, reg: R) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
position,
|
position,
|
||||||
@@ -54,7 +70,7 @@ impl<R> Segment<R> {
|
|||||||
mem: Data::Reg(reg),
|
mem: Data::Reg(reg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn mirror(name: &'static str, position: u16, size: u16, of: usize) -> Self {
|
fn mirror(name: SegmentId, position: u16, size: u16, of: u16) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
position,
|
position,
|
||||||
@@ -62,20 +78,45 @@ impl<R> Segment<R> {
|
|||||||
mem: Data::Mirror(of),
|
mem: Data::Mirror(of),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn take_rom(&mut self) -> Vec<u8> {
|
||||||
|
// match std::mem::replace(&mut self.mem, Data::Disabled) {
|
||||||
|
// Data::ROM(items) => items,
|
||||||
|
// _ => panic!("Cannot take rom since memory is not rom"),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn set_rom(&mut self, rom: Vec<u8>) {
|
||||||
|
// assert!(
|
||||||
|
// matches!(self.mem, Data::Disabled),
|
||||||
|
// "Cannot set non-disabled memory to rom"
|
||||||
|
// );
|
||||||
|
// self.mem = Data::ROM(rom);
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn swap_rom(&mut self, rom: Arc<[u8]>) {
|
||||||
|
match &mut self.mem {
|
||||||
|
Data::ROM(items) => *items = rom,
|
||||||
|
_ => panic!("Cannot swap rom since memory is not rom"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn power_cycle(&mut self) {
|
||||||
|
match &mut self.mem {
|
||||||
|
Data::RAM(v) => v.fill(0),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct MemoryMap<R> {
|
pub struct MemoryMap<R> {
|
||||||
edit_ver: usize,
|
edit_ver: usize,
|
||||||
segments: Vec<Segment<R>>,
|
segments: Vec<Segment<R>>,
|
||||||
|
// map: Remapper,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> MemoryMap<R> {
|
impl<R: Copy> MemoryMap<R> {
|
||||||
pub fn new(segments: Vec<Segment<R>>) -> Self {
|
|
||||||
Self {
|
|
||||||
edit_ver: 0,
|
|
||||||
segments,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn read(&self, addr: u16) -> Value<'_, R> {
|
pub fn read(&self, addr: u16) -> Value<'_, R> {
|
||||||
// self.edit_ver += 1;
|
// self.edit_ver += 1;
|
||||||
for segment in &self.segments {
|
for segment in &self.segments {
|
||||||
@@ -87,11 +128,13 @@ impl<R> MemoryMap<R> {
|
|||||||
reg,
|
reg,
|
||||||
offset: addr - segment.position,
|
offset: addr - segment.position,
|
||||||
},
|
},
|
||||||
Data::Mirror(index) => {
|
Data::Mirror(pos) => {
|
||||||
let offset = addr - segment.position;
|
let offset = addr - segment.position;
|
||||||
let s = &self.segments[*index];
|
self.read(pos + offset)
|
||||||
self.read(s.position + offset % s.size)
|
// let s = &self.segments[*index];
|
||||||
} // Data::Disabled() => todo!(),
|
// self.read(s.position + offset % s.size)
|
||||||
|
}
|
||||||
|
Data::Disabled => todo!(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,26 +143,32 @@ impl<R> MemoryMap<R> {
|
|||||||
// todo!("Open bus")
|
// todo!("Open bus")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&mut self, addr: u16, val: u8, reg_fn: impl FnOnce(&R, u16, u8)) {
|
pub fn write(&mut self, addr: u16, val: u8) -> Option<(R, u16, u8)> {
|
||||||
self.edit_ver += 1;
|
self.edit_ver += 1;
|
||||||
for segment in &mut self.segments {
|
for segment in &mut self.segments {
|
||||||
if segment.position <= addr && addr - segment.position < segment.size {
|
if segment.position <= addr && addr - segment.position < segment.size {
|
||||||
return match &mut segment.mem {
|
return match &mut segment.mem {
|
||||||
Data::RAM(items) => {
|
Data::RAM(items) => {
|
||||||
items[(addr - segment.position) as usize] = val;
|
items[(addr - segment.position) as usize] = val;
|
||||||
|
None
|
||||||
}
|
}
|
||||||
Data::ROM(_items) => (),
|
Data::ROM(_items) => None,
|
||||||
Data::Reg(reg) => reg_fn(reg, addr - segment.position, val),
|
Data::Reg(reg) => Some((*reg, addr - segment.position, val)),
|
||||||
Data::Mirror(index) => {
|
Data::Mirror(pos) => {
|
||||||
|
let pos = *pos;
|
||||||
let offset = addr - segment.position;
|
let offset = addr - segment.position;
|
||||||
let index = *index;
|
self.write(pos + offset, val)
|
||||||
let s = &self.segments[index];
|
// let index = *index;
|
||||||
self.write(s.position + offset % s.size, val, reg_fn)
|
// let s = &self.segments[index];
|
||||||
} // Data::Disabled() => todo!(),
|
// self.write(s.position + offset % s.size, val)
|
||||||
|
}
|
||||||
|
Data::Disabled => todo!(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
todo!("Open bus")
|
// iirc, open bus just drops writes
|
||||||
|
None
|
||||||
|
// todo!("Open bus (${addr:04X})")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
@@ -131,71 +180,58 @@ impl<R> MemoryMap<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rom(&self, idx: usize) -> Option<&[u8]> {
|
pub fn power_cycle(&mut self) -> Self {
|
||||||
if let Some(Segment {
|
for seg in &mut self.segments {
|
||||||
mem: Data::ROM(val),
|
seg.power_cycle();
|
||||||
..
|
}
|
||||||
}) = self.segments.get(idx)
|
let segments = std::mem::take(&mut self.segments);
|
||||||
{
|
Self {
|
||||||
Some(val)
|
edit_ver: self.edit_ver + 1,
|
||||||
} else {
|
segments,
|
||||||
|
// map: self.map.power_cycle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find(&mut self, id: SegmentId) -> Option<&mut Segment<R>> {
|
||||||
|
for s in &mut self.segments {
|
||||||
|
if s.is(id) {
|
||||||
|
return Some(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn rom_or_ram(&self, idx: usize) -> Option<&[u8]> {
|
fn peek_val(&self, addr: u16) -> Option<u8> {
|
||||||
if let Some(Segment {
|
|
||||||
mem: Data::ROM(val),
|
|
||||||
..
|
|
||||||
}) = self.segments.get(idx)
|
|
||||||
{
|
|
||||||
Some(val)
|
|
||||||
} else if let Some(Segment {
|
|
||||||
mem: Data::RAM(_), ..
|
|
||||||
}) = self.segments.get(idx)
|
|
||||||
{
|
|
||||||
Some(&[])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn with_peek_reg<'s>(&'s self, f: impl (Fn(&R, u16) -> Option<u8>) + 's) -> impl Memory + 's {
|
|
||||||
// struct MemImpl<'a, R>(&'a MemoryMap<R>, Box<dyn Fn(&R, u16) -> Option<u8> + 'a>);
|
|
||||||
// impl<R> Memory for MemImpl<'_, R> {
|
|
||||||
// fn peek(&self, val: u16) -> Option<u8> {
|
|
||||||
// match self.0.read(val) {
|
|
||||||
// Value::Value(v) => Some(v),
|
|
||||||
// Value::Register { reg, offset } => self.1(reg, offset),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn edit_ver(&self) -> usize {
|
|
||||||
// self.0.edit_ver()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// MemImpl(self, Box::new(f))
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R> Memory for MemoryMap<R> {
|
|
||||||
fn peek(&self, addr: u16) -> Option<u8> {
|
|
||||||
for segment in &self.segments {
|
for segment in &self.segments {
|
||||||
if segment.position <= addr && addr - segment.position < segment.size {
|
if segment.position <= addr && addr - segment.position < segment.size {
|
||||||
return match &segment.mem {
|
return match &segment.mem {
|
||||||
Data::RAM(items) => Some(items[(addr - segment.position) as usize]),
|
Data::RAM(items) => Some(items[(addr - segment.position) as usize]),
|
||||||
Data::ROM(items) => Some(items[(addr - segment.position) as usize]),
|
Data::ROM(items) => Some(items[(addr - segment.position) as usize]),
|
||||||
Data::Reg(_) => None,
|
Data::Reg(_) => None,
|
||||||
Data::Mirror(index) => {
|
Data::Mirror(pos) => {
|
||||||
let offset = addr - segment.position;
|
let offset = addr - segment.position;
|
||||||
let s = &self.segments[*index];
|
self.peek_val(pos + offset)
|
||||||
self.peek(s.position + offset % s.size)
|
}
|
||||||
} // Data::Disabled() => todo!(),
|
Data::Disabled => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
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 {
|
fn edit_ver(&self) -> usize {
|
||||||
self.edit_ver
|
self.edit_ver
|
||||||
@@ -203,48 +239,466 @@ impl<R> Memory for MemoryMap<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Mapper {
|
struct Mapper {
|
||||||
horizontal_name_table: bool,
|
horizontal_name_table: bool,
|
||||||
|
mapper: u16,
|
||||||
|
sub_mapper: u8,
|
||||||
|
prg_rom_size: u8,
|
||||||
|
chr_rom_size: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mapper {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub fn from_flags(flags: u8) -> Self {
|
enum SegmentId {
|
||||||
if flags & 0b11111110 != 0 {
|
#[allow(unused)]
|
||||||
todo!("Support other mapper flags");
|
TestRam,
|
||||||
|
|
||||||
|
InternalRam,
|
||||||
|
InternalRamMirror,
|
||||||
|
PpuRegisters,
|
||||||
|
ApuIoRegisters,
|
||||||
|
PrgRom,
|
||||||
|
|
||||||
|
Nametable0,
|
||||||
|
Nametable1,
|
||||||
|
Nametable2,
|
||||||
|
Nametable3,
|
||||||
|
VramMirror,
|
||||||
|
PaletteControl,
|
||||||
|
PaletteMirror,
|
||||||
|
PpuRom,
|
||||||
|
PpuRam,
|
||||||
|
|
||||||
|
// CpuBank(u32),
|
||||||
|
PrgBank0,
|
||||||
|
PrgBank1,
|
||||||
|
ChrBank0,
|
||||||
|
ChrBank1,
|
||||||
}
|
}
|
||||||
Self {
|
|
||||||
horizontal_name_table: flags & (1 << 0) == 1,
|
impl fmt::Display for SegmentId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{self:?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ppu_map(&self, rom: &[u8]) -> MemoryMap<PPUMMRegisters> {
|
#[derive(Debug, Clone)]
|
||||||
let chr = if rom.len() == 0 {
|
pub struct Mapped {
|
||||||
Segment::ram("CHR RAM", 0x0000, 0x2000)
|
cpu: MemoryMap<CPUMMRegisters>,
|
||||||
|
ppu: MemoryMap<PPUMMRegisters>,
|
||||||
|
mapper: Remapper,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mapped {
|
||||||
|
pub fn from_rom(rom: &[u8]) -> Self {
|
||||||
|
let (header, rom) = rom.split_at(0x10);
|
||||||
|
let nes_20 = header[7] & 0x0C == 0x08;
|
||||||
|
// assert!(nes_20, "Only supports nes 2.0 format");
|
||||||
|
// if header[6] & 0b11111110 != 0 {
|
||||||
|
// todo!("Support other mapper flags, {:08b}", header[6]);
|
||||||
|
// }
|
||||||
|
let mapper = if nes_20 {
|
||||||
|
assert_eq!(header[9], 0, "No support for larger PRG/CHR roms");
|
||||||
|
assert_eq!(header[10], 0, "No support for PRG-RAM/EEPROM");
|
||||||
|
assert_eq!(header[11], 0, "No support for CHR-RAM");
|
||||||
|
assert!(header[12] == 0 || header[12] == 2, "Only support NTSC NES");
|
||||||
|
Mapper {
|
||||||
|
horizontal_name_table: header[6] & (1 << 0) == 1,
|
||||||
|
mapper: ((header[6] as u16 & 0xF0) >> 4)
|
||||||
|
| ((header[7] as u16 & 0xF0) >> 0)
|
||||||
|
| ((header[8] as u16 & 0x0F) << 8),
|
||||||
|
sub_mapper: (header[8] & 0xF0) >> 4,
|
||||||
|
prg_rom_size: header[4],
|
||||||
|
chr_rom_size: header[5],
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Segment::rom("CHR ROM", 0x0000, rom)
|
Mapper {
|
||||||
|
horizontal_name_table: header[6] & (1 << 0) == 1,
|
||||||
|
mapper: ((header[6] as u16 & 0xF0) >> 4) | ((header[7] as u16 & 0xF0) >> 0),
|
||||||
|
sub_mapper: 0,
|
||||||
|
prg_rom_size: header[4],
|
||||||
|
chr_rom_size: header[5],
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if self.horizontal_name_table {
|
let (prg_rom, rom) = rom.split_at(mapper.prg_rom_size as usize * 0x4000);
|
||||||
MemoryMap::new(vec![
|
let (chr_rom, rom) = rom.split_at(mapper.chr_rom_size as usize * 0x2000);
|
||||||
chr,
|
// assert_eq!(rom.len(), 0);
|
||||||
Segment::ram("Internal VRAM", 0x2000, 0x400),
|
// let prg_rom = &file[self.cpu_offset()..self.ppu_offset()];
|
||||||
Segment::ram("Internal VRAM", 0x2400, 0x400),
|
let mut cpu_segments = vec![
|
||||||
Segment::mirror("Internal VRAM", 0x2800, 0x400, 1),
|
Segment::ram(SegmentId::InternalRam, 0x0000, 0x0800),
|
||||||
Segment::mirror("Internal VRAM", 0x2C00, 0x400, 2),
|
Segment::mirror(SegmentId::InternalRamMirror, 0x0800, 0x1800, 0x0000),
|
||||||
Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1),
|
Segment::reg(SegmentId::PpuRegisters, 0x2000, 0x0008, CPUMMRegisters::PPU),
|
||||||
Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette),
|
Segment::reg(
|
||||||
Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 6),
|
SegmentId::ApuIoRegisters,
|
||||||
])
|
0x4000,
|
||||||
|
0x0018,
|
||||||
|
CPUMMRegisters::APU,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let mut ppu_segments = vec![
|
||||||
|
Segment::mirror(SegmentId::VramMirror, 0x3000, 0x0F00, 0x0000),
|
||||||
|
Segment::reg(
|
||||||
|
SegmentId::PaletteControl,
|
||||||
|
0x3F00,
|
||||||
|
0x0020,
|
||||||
|
PPUMMRegisters::Palette,
|
||||||
|
),
|
||||||
|
Segment::mirror(SegmentId::PaletteMirror, 0x3F20, 0x00E0, 0x3F00),
|
||||||
|
];
|
||||||
|
if mapper.horizontal_name_table {
|
||||||
|
ppu_segments.push(Segment::ram(SegmentId::Nametable0, 0x2000, 0x400));
|
||||||
|
ppu_segments.push(Segment::ram(SegmentId::Nametable1, 0x2400, 0x400));
|
||||||
|
ppu_segments.push(Segment::mirror(
|
||||||
|
SegmentId::Nametable2,
|
||||||
|
0x2800,
|
||||||
|
0x400,
|
||||||
|
0x2000,
|
||||||
|
));
|
||||||
|
ppu_segments.push(Segment::mirror(
|
||||||
|
SegmentId::Nametable3,
|
||||||
|
0x2C00,
|
||||||
|
0x400,
|
||||||
|
0x2400,
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
MemoryMap::new(vec![
|
ppu_segments.push(Segment::ram(SegmentId::Nametable0, 0x2000, 0x400));
|
||||||
chr,
|
ppu_segments.push(Segment::mirror(
|
||||||
Segment::ram("Internal VRAM", 0x2000, 0x400),
|
SegmentId::Nametable1,
|
||||||
Segment::mirror("Internal VRAM", 0x2400, 0x400, 1),
|
0x2400,
|
||||||
Segment::ram("Internal VRAM", 0x2800, 0x400),
|
0x400,
|
||||||
Segment::mirror("Internal VRAM", 0x2C00, 0x400, 3),
|
0x2000,
|
||||||
Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1),
|
));
|
||||||
Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette),
|
ppu_segments.push(Segment::ram(SegmentId::Nametable2, 0x2800, 0x400));
|
||||||
Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 6),
|
ppu_segments.push(Segment::mirror(
|
||||||
])
|
SegmentId::Nametable3,
|
||||||
|
0x2C00,
|
||||||
|
0x400,
|
||||||
|
0x2800,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let remap = if mapper.mapper == 0 {
|
||||||
|
cpu_segments.push(Segment::rom(
|
||||||
|
SegmentId::PrgRom,
|
||||||
|
0x8000 + (0x8000 - prg_rom.len() as u16),
|
||||||
|
prg_rom,
|
||||||
|
));
|
||||||
|
if chr_rom.len() == 0 {
|
||||||
|
ppu_segments.push(Segment::ram(SegmentId::PpuRam, 0, 0x2000));
|
||||||
|
} else {
|
||||||
|
ppu_segments.push(Segment::rom(SegmentId::PpuRom, 0, chr_rom));
|
||||||
|
}
|
||||||
|
Remapper::None
|
||||||
|
} else if mapper.mapper == 1 && mapper.sub_mapper == 0 {
|
||||||
|
let prg_banks: Vec<Arc<[u8]>> =
|
||||||
|
prg_rom.chunks(0x4000).map(|ch| Arc::from(ch)).collect();
|
||||||
|
for (i, b) in prg_banks.iter().enumerate() {
|
||||||
|
println!("{i}: {:X?}", &b[0x3FF0..]);
|
||||||
|
}
|
||||||
|
cpu_segments.push(Segment::rom(
|
||||||
|
SegmentId::PrgBank0,
|
||||||
|
0x8000,
|
||||||
|
prg_banks.first().unwrap().clone(),
|
||||||
|
));
|
||||||
|
cpu_segments.push(Segment::rom(
|
||||||
|
SegmentId::PrgBank1,
|
||||||
|
0xC000,
|
||||||
|
prg_banks.last().unwrap().clone(),
|
||||||
|
));
|
||||||
|
println!("CHR_ROM: {}", chr_rom.len());
|
||||||
|
let chr_banks: Vec<Arc<[u8]>> =
|
||||||
|
chr_rom.chunks(0x1000).map(|ch| Arc::from(ch)).collect();
|
||||||
|
if chr_rom.len() == 0 {
|
||||||
|
ppu_segments.push(Segment::ram(SegmentId::PpuRam, 0, 0x2000));
|
||||||
|
} else {
|
||||||
|
ppu_segments.push(Segment::rom(
|
||||||
|
SegmentId::ChrBank0,
|
||||||
|
0x0000,
|
||||||
|
chr_banks[0].clone(),
|
||||||
|
));
|
||||||
|
ppu_segments.push(Segment::rom(
|
||||||
|
SegmentId::ChrBank1,
|
||||||
|
0x1000,
|
||||||
|
chr_banks[1].clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Remapper::MMC1 {
|
||||||
|
shift_reg: 0,
|
||||||
|
count: 0,
|
||||||
|
prg_banks,
|
||||||
|
chr_banks,
|
||||||
|
mode: MMC1Mode::LastFixed,
|
||||||
|
cur_prg_bank: 0,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
cpu: MemoryMap {
|
||||||
|
edit_ver: 0,
|
||||||
|
segments: cpu_segments,
|
||||||
|
},
|
||||||
|
ppu: MemoryMap {
|
||||||
|
edit_ver: 0,
|
||||||
|
segments: ppu_segments,
|
||||||
|
},
|
||||||
|
mapper: remap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn power_cylce(&mut self) -> Self {
|
||||||
|
// TODO: mapper needs to reset cpu and ppu mem maps
|
||||||
|
let mut cpu = self.cpu.power_cycle();
|
||||||
|
let mut ppu = self.ppu.power_cycle();
|
||||||
|
Self {
|
||||||
|
mapper: self.mapper.power_cycle(&mut cpu, &mut ppu),
|
||||||
|
cpu,
|
||||||
|
ppu,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek_ppu(&self, addr: u16) -> Option<u8> {
|
||||||
|
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_val(addr)
|
||||||
|
}
|
||||||
|
pub fn cpu_edit_ver(&self) -> usize {
|
||||||
|
self.cpu.edit_ver
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn test_ram() -> Self {
|
||||||
|
Self {
|
||||||
|
cpu: MemoryMap {
|
||||||
|
edit_ver: 0,
|
||||||
|
segments: vec![Segment::ram(SegmentId::TestRam, 0, 0x8000)],
|
||||||
|
},
|
||||||
|
ppu: MemoryMap {
|
||||||
|
edit_ver: 0,
|
||||||
|
segments: vec![Segment::ram(SegmentId::TestRam, 0, 0x8000)],
|
||||||
|
},
|
||||||
|
mapper: Remapper::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CpuMem<'a> {
|
||||||
|
mem: &'a mut Mapped,
|
||||||
|
ppu: &'a mut PPU,
|
||||||
|
apu: &'a mut APU,
|
||||||
|
dma: &'a mut DmaState,
|
||||||
|
controllers: &'a mut Controllers,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CpuMem<'a> {
|
||||||
|
pub fn new(
|
||||||
|
mem: &'a mut Mapped,
|
||||||
|
ppu: &'a mut PPU,
|
||||||
|
apu: &'a mut APU,
|
||||||
|
dma: &'a mut DmaState,
|
||||||
|
controllers: &'a mut Controllers,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
mem,
|
||||||
|
ppu,
|
||||||
|
apu,
|
||||||
|
dma,
|
||||||
|
controllers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn read(&mut self, addr: u16) -> u8 {
|
||||||
|
match self.mem.mapper {
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
match self.mem.cpu.read(addr) {
|
||||||
|
Value::Value(v) => v,
|
||||||
|
Value::Register { reg, offset } => match reg {
|
||||||
|
CPUMMRegisters::PPU => self.ppu.read_reg(&mut PpuMem::new(self.mem), offset),
|
||||||
|
CPUMMRegisters::APU => {
|
||||||
|
if offset == 0x014 {
|
||||||
|
todo!("OAM DMA")
|
||||||
|
} else if offset == 0x16 {
|
||||||
|
self.controllers.read_joy1()
|
||||||
|
} else if offset == 0x17 {
|
||||||
|
self.controllers.read_joy2()
|
||||||
|
} else {
|
||||||
|
self.apu.read_reg(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn write(&mut self, addr: u16, val: u8) {
|
||||||
|
match &mut self.mem.mapper {
|
||||||
|
Remapper::MMC1 {
|
||||||
|
shift_reg,
|
||||||
|
count,
|
||||||
|
mode,
|
||||||
|
prg_banks,
|
||||||
|
chr_banks,
|
||||||
|
cur_prg_bank,
|
||||||
|
} if addr & 0x8000 != 0 => {
|
||||||
|
if val & 0x80 != 0 {
|
||||||
|
*shift_reg = 0;
|
||||||
|
*count = 0;
|
||||||
|
} else if *count == 4 {
|
||||||
|
let val = (*shift_reg << 1) | (val & 0x01);
|
||||||
|
if (addr & 0x6000) >> 13 == 0 {
|
||||||
|
// TODO: fix mem layout if it's changed
|
||||||
|
if val & 0b01100 == 0b01000 {
|
||||||
|
*mode = MMC1Mode::FirstFixed;
|
||||||
|
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||||
|
bank0.swap_rom(prg_banks[0].clone());
|
||||||
|
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
||||||
|
bank1.swap_rom(prg_banks[*cur_prg_bank].clone());
|
||||||
|
} else if val & 0b01100 == 0b01100 {
|
||||||
|
*mode = MMC1Mode::LastFixed;
|
||||||
|
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||||
|
bank0.swap_rom(prg_banks[*cur_prg_bank].clone());
|
||||||
|
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
||||||
|
bank1.swap_rom(prg_banks.last().unwrap().clone());
|
||||||
|
} else {
|
||||||
|
*mode = MMC1Mode::Full;
|
||||||
|
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||||
|
bank0.swap_rom(prg_banks[*cur_prg_bank & !1].clone());
|
||||||
|
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
||||||
|
bank1.swap_rom(prg_banks[(*cur_prg_bank & !1) + 1].clone());
|
||||||
|
}
|
||||||
|
if val & 0b10000 == 0b10000 {
|
||||||
|
// TODO: CHR-ROM mode
|
||||||
|
}
|
||||||
|
// TODO: Set name table mirroring
|
||||||
|
if val & 0b00011 == 0b00000 {
|
||||||
|
} else if val & 0b00011 == 0b00001 {
|
||||||
|
} else if val & 0b00011 == 0b00010 {
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
} else if (addr & 0x6000) >> 13 == 1 {
|
||||||
|
if chr_banks.len() != 0 {
|
||||||
|
todo!("Swap CHR bank 0")
|
||||||
|
}
|
||||||
|
} else if (addr & 0x6000) >> 13 == 2 {
|
||||||
|
if chr_banks.len() != 0 {
|
||||||
|
todo!("Swap CHR bank 1")
|
||||||
|
}
|
||||||
|
} else if (addr & 0x6000) >> 13 == 3 {
|
||||||
|
*cur_prg_bank = (val & 0x0F) as usize;
|
||||||
|
match mode {
|
||||||
|
MMC1Mode::Full => {
|
||||||
|
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||||
|
bank0.swap_rom(prg_banks[*cur_prg_bank & !1].clone());
|
||||||
|
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
||||||
|
bank1.swap_rom(prg_banks[(*cur_prg_bank & !1) + 1].clone());
|
||||||
|
}
|
||||||
|
MMC1Mode::FirstFixed => {
|
||||||
|
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
||||||
|
bank1.swap_rom(prg_banks[(val & 0x0F) as usize].clone());
|
||||||
|
}
|
||||||
|
MMC1Mode::LastFixed => {
|
||||||
|
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||||
|
bank0.swap_rom(prg_banks[(val & 0x0F) as usize].clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: handle MSB, changes some stuff...
|
||||||
|
} else {
|
||||||
|
todo!("Handle reg write {} => {}", val, (addr & 0x6000) >> 13,)
|
||||||
|
}
|
||||||
|
*shift_reg = 0;
|
||||||
|
*count = 0;
|
||||||
|
} else {
|
||||||
|
*shift_reg = (*shift_reg << 1) | (val & 0x01);
|
||||||
|
*count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
match self.mem.cpu.write(addr, val) {
|
||||||
|
Some((CPUMMRegisters::PPU, offset, val)) => {
|
||||||
|
self.ppu.write_reg(&mut PpuMem::new(self.mem), offset, val)
|
||||||
|
}
|
||||||
|
Some((CPUMMRegisters::APU, offset, val)) => {
|
||||||
|
if offset == 0x014 {
|
||||||
|
*self.dma = DmaState::Idle((val as u16) << 8);
|
||||||
|
} else if offset == 0x16 {
|
||||||
|
self.controllers.write_joy_strobe(val);
|
||||||
|
} else {
|
||||||
|
self.apu.write_reg(offset, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PpuMem<'a>(&'a mut Mapped);
|
||||||
|
|
||||||
|
impl<'a> PpuMem<'a> {
|
||||||
|
pub fn new(mem: &'a mut Mapped) -> Self {
|
||||||
|
Self(mem)
|
||||||
|
}
|
||||||
|
pub fn read(&mut self, addr: u16) -> Value<'_, PPUMMRegisters> {
|
||||||
|
self.0.ppu.read(addr)
|
||||||
|
}
|
||||||
|
pub fn write(&mut self, addr: u16, val: u8, reg_fn: impl FnOnce(&PPUMMRegisters, u16, u8)) {
|
||||||
|
match self.0.ppu.write(addr, val) {
|
||||||
|
Some((r, o, v)) => reg_fn(&r, o, v),
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum MMC1Mode {
|
||||||
|
Full,
|
||||||
|
FirstFixed,
|
||||||
|
LastFixed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Remapper {
|
||||||
|
None,
|
||||||
|
MMC1 {
|
||||||
|
shift_reg: u8,
|
||||||
|
count: u8,
|
||||||
|
prg_banks: Vec<Arc<[u8]>>,
|
||||||
|
chr_banks: Vec<Arc<[u8]>>,
|
||||||
|
mode: MMC1Mode,
|
||||||
|
cur_prg_bank: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Remapper {
|
||||||
|
fn power_cycle(
|
||||||
|
&mut self,
|
||||||
|
cpu: &mut MemoryMap<CPUMMRegisters>,
|
||||||
|
_ppu: &mut MemoryMap<PPUMMRegisters>,
|
||||||
|
) -> Self {
|
||||||
|
match self {
|
||||||
|
Remapper::None => Remapper::None,
|
||||||
|
Remapper::MMC1 {
|
||||||
|
prg_banks,
|
||||||
|
chr_banks,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let prg_banks = std::mem::take(prg_banks);
|
||||||
|
let chr_banks = std::mem::take(chr_banks);
|
||||||
|
cpu.find(SegmentId::PrgBank0)
|
||||||
|
.unwrap()
|
||||||
|
.swap_rom(prg_banks[0].clone());
|
||||||
|
cpu.find(SegmentId::PrgBank1)
|
||||||
|
.unwrap()
|
||||||
|
.swap_rom(prg_banks.last().unwrap().clone());
|
||||||
|
Remapper::MMC1 {
|
||||||
|
shift_reg: 0,
|
||||||
|
count: 0,
|
||||||
|
prg_banks,
|
||||||
|
chr_banks,
|
||||||
|
mode: MMC1Mode::LastFixed,
|
||||||
|
cur_prg_bank: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
124
src/ppu.rs
124
src/ppu.rs
@@ -8,7 +8,7 @@ use iced::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
hex_view::Memory,
|
hex_view::Memory,
|
||||||
mem::{Mapper, MemoryMap, Segment},
|
mem::{Mapped, PpuMem},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
@@ -72,15 +72,19 @@ impl<const W: usize, const H: usize> RenderBuffer<W, H> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum PPUMMRegisters {
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum PPUMMRegisters {
|
||||||
Palette,
|
Palette,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct OAM {
|
pub struct OAM {
|
||||||
mem: Vec<u8>,
|
mem: Vec<u8>,
|
||||||
addr: u8,
|
addr: u8,
|
||||||
|
edit_ver: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum BackgroundState {
|
enum BackgroundState {
|
||||||
NameTableBytePre,
|
NameTableBytePre,
|
||||||
NameTableByte,
|
NameTableByte,
|
||||||
@@ -92,6 +96,7 @@ enum BackgroundState {
|
|||||||
PatternTableTileHigh,
|
PatternTableTileHigh,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Background {
|
pub struct Background {
|
||||||
/// Current vram address, 15 bits
|
/// Current vram address, 15 bits
|
||||||
pub v: u16,
|
pub v: u16,
|
||||||
@@ -119,6 +124,7 @@ pub struct Background {
|
|||||||
pub cur_shift_low: u32,
|
pub cur_shift_low: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Mask {
|
pub struct Mask {
|
||||||
pub grayscale: bool,
|
pub grayscale: bool,
|
||||||
pub background_on_left_edge: bool,
|
pub background_on_left_edge: bool,
|
||||||
@@ -453,6 +459,7 @@ const COLORS: &'static [Color; 0b100_0000] = &[
|
|||||||
}, // 3F
|
}, // 3F
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Palette {
|
pub struct Palette {
|
||||||
colors: &'static [Color; 0x40],
|
colors: &'static [Color; 0x40],
|
||||||
ram: [u8; 0x20],
|
ram: [u8; 0x20],
|
||||||
@@ -465,6 +472,7 @@ impl Palette {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct PPU {
|
pub struct PPU {
|
||||||
// registers: PPURegisters,
|
// registers: PPURegisters,
|
||||||
pub frame_count: usize,
|
pub frame_count: usize,
|
||||||
@@ -477,7 +485,6 @@ pub struct PPU {
|
|||||||
pub mask: Mask,
|
pub mask: Mask,
|
||||||
pub vblank: bool,
|
pub vblank: bool,
|
||||||
|
|
||||||
pub(crate) memory: MemoryMap<PPUMMRegisters>,
|
|
||||||
palette: Palette,
|
palette: Palette,
|
||||||
pub background: Background,
|
pub background: Background,
|
||||||
oam: OAM,
|
oam: OAM,
|
||||||
@@ -505,7 +512,10 @@ impl std::fmt::Debug for PPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PPU {
|
impl PPU {
|
||||||
pub fn with_chr_rom(rom: &[u8], mapper: Mapper) -> Self {
|
// pub fn with_chr_rom(rom: &[u8], mapper: Mapper) -> Self {
|
||||||
|
// todo!()
|
||||||
|
// }
|
||||||
|
pub fn init() -> Self {
|
||||||
Self {
|
Self {
|
||||||
cycle: 25,
|
cycle: 25,
|
||||||
dbg_int: false,
|
dbg_int: false,
|
||||||
@@ -536,7 +546,6 @@ impl PPU {
|
|||||||
scanline: 0,
|
scanline: 0,
|
||||||
pixel: 25,
|
pixel: 25,
|
||||||
render_buffer: RenderBuffer::empty(),
|
render_buffer: RenderBuffer::empty(),
|
||||||
memory: mapper.ppu_map(rom),
|
|
||||||
background: Background {
|
background: Background {
|
||||||
v: 0,
|
v: 0,
|
||||||
t: 0,
|
t: 0,
|
||||||
@@ -557,21 +566,19 @@ impl PPU {
|
|||||||
oam: OAM {
|
oam: OAM {
|
||||||
mem: vec![0u8; 256],
|
mem: vec![0u8; 256],
|
||||||
addr: 0,
|
addr: 0,
|
||||||
|
edit_ver: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
*self = Self {
|
*self = Self::init();
|
||||||
memory: std::mem::replace(&mut self.memory, MemoryMap::new(vec![])),
|
|
||||||
..Self::with_chr_rom(&[], Mapper::from_flags(0))
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rendering_enabled(&self) -> bool {
|
pub fn rendering_enabled(&self) -> bool {
|
||||||
self.mask.enable_background || self.mask.enable_sprites
|
self.mask.enable_background || self.mask.enable_sprites
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_reg(&mut self, offset: u16) -> u8 {
|
pub fn read_reg(&mut self, mem: &mut PpuMem<'_>, offset: u16) -> u8 {
|
||||||
match offset {
|
match offset {
|
||||||
0 => panic!("ppuctrl is write-only"),
|
0 => panic!("ppuctrl is write-only"),
|
||||||
1 => panic!("ppumask is write-only"),
|
1 => panic!("ppumask is write-only"),
|
||||||
@@ -587,10 +594,7 @@ impl PPU {
|
|||||||
6 => panic!("ppuaddr is write-only"),
|
6 => panic!("ppuaddr is write-only"),
|
||||||
7 => {
|
7 => {
|
||||||
// println!("Updating v for ppudata read");
|
// println!("Updating v for ppudata read");
|
||||||
let val = self
|
let val = mem.read(self.background.v).reg_map(|a, off| match a {
|
||||||
.memory
|
|
||||||
.read(self.background.v)
|
|
||||||
.reg_map(|a, off| match a {
|
|
||||||
PPUMMRegisters::Palette => self.palette.ram[off as usize],
|
PPUMMRegisters::Palette => self.palette.ram[off as usize],
|
||||||
});
|
});
|
||||||
// if self.background
|
// if self.background
|
||||||
@@ -601,7 +605,7 @@ impl PPU {
|
|||||||
_ => panic!("No register at {:02X}", offset),
|
_ => panic!("No register at {:02X}", offset),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn write_reg(&mut self, offset: u16, val: u8) {
|
pub fn write_reg(&mut self, mem: &mut PpuMem, offset: u16, mut val: u8) {
|
||||||
match offset {
|
match offset {
|
||||||
0x00 => {
|
0x00 => {
|
||||||
self.nmi_enabled = val & 0b1000_0000 != 0;
|
self.nmi_enabled = val & 0b1000_0000 != 0;
|
||||||
@@ -629,8 +633,12 @@ impl PPU {
|
|||||||
}
|
}
|
||||||
0x03 => self.oam.addr = val,
|
0x03 => self.oam.addr = val,
|
||||||
0x04 => {
|
0x04 => {
|
||||||
|
if self.oam.addr % 4 == 2 {
|
||||||
|
val &= 0b11100011;
|
||||||
|
}
|
||||||
self.oam.mem[self.oam.addr as usize] = val;
|
self.oam.mem[self.oam.addr as usize] = val;
|
||||||
self.oam.addr = self.oam.addr.wrapping_add(1);
|
self.oam.addr = self.oam.addr.wrapping_add(1);
|
||||||
|
self.oam.edit_ver += 1;
|
||||||
}
|
}
|
||||||
0x05 => {
|
0x05 => {
|
||||||
if self.background.w {
|
if self.background.w {
|
||||||
@@ -664,8 +672,7 @@ impl PPU {
|
|||||||
}
|
}
|
||||||
0x07 => {
|
0x07 => {
|
||||||
// println!("Writing: {:02X}, @{:04X}", val, self.background.v);
|
// println!("Writing: {:02X}, @{:04X}", val, self.background.v);
|
||||||
self.memory
|
mem.write(self.background.v, val, |r, o, v| match r {
|
||||||
.write(self.background.v, val, |r, o, v| match r {
|
|
||||||
PPUMMRegisters::Palette => {
|
PPUMMRegisters::Palette => {
|
||||||
// println!("Writing {:02X} to {:02X}", v & 0x3F, o);
|
// println!("Writing {:02X} to {:02X}", v & 0x3F, o);
|
||||||
self.palette.ram[o as usize] = v & 0x3F;
|
self.palette.ram[o as usize] = v & 0x3F;
|
||||||
@@ -694,7 +701,7 @@ impl PPU {
|
|||||||
// // TODO: OAM high addr
|
// // TODO: OAM high addr
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub fn run_one_clock_cycle(&mut self) -> bool {
|
pub fn run_one_clock_cycle(&mut self, mem: &mut PpuMem<'_>) -> bool {
|
||||||
self.cycle += 1;
|
self.cycle += 1;
|
||||||
self.pixel += 1;
|
self.pixel += 1;
|
||||||
if self.scanline == 261
|
if self.scanline == 261
|
||||||
@@ -761,7 +768,7 @@ impl PPU {
|
|||||||
// let addr = 0x2000 + self.pixel / 8 + self.scanline / 8;
|
// let addr = 0x2000 + self.pixel / 8 + self.scanline / 8;
|
||||||
let addr = 0x2000 | (self.background.v & 0x0FFF);
|
let addr = 0x2000 | (self.background.v & 0x0FFF);
|
||||||
// println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
|
// println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
|
||||||
let val = self.memory.read(addr).reg_map(|_, _| 0);
|
let val = mem.read(addr).reg_map(|_, _| 0);
|
||||||
self.background.cur_nametable = val;
|
self.background.cur_nametable = val;
|
||||||
} else if self.pixel % 8 == 4 {
|
} else if self.pixel % 8 == 4 {
|
||||||
// Attr table fetch
|
// Attr table fetch
|
||||||
@@ -772,7 +779,7 @@ impl PPU {
|
|||||||
| ((self.background.v >> 2) & 0x07);
|
| ((self.background.v >> 2) & 0x07);
|
||||||
// println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
|
// println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
|
||||||
// assert_eq!(addr, addr_2);
|
// assert_eq!(addr, addr_2);
|
||||||
let val = self.memory.read(addr).reg_map(|_, _| 0); // TODO: handle reg reads
|
let val = mem.read(addr).reg_map(|_, _| 0); // TODO: handle reg reads
|
||||||
self.background.next_attr_2 = val;
|
self.background.next_attr_2 = val;
|
||||||
} else if self.pixel % 8 == 6 {
|
} else if self.pixel % 8 == 6 {
|
||||||
// BG pattern low
|
// BG pattern low
|
||||||
@@ -784,7 +791,7 @@ impl PPU {
|
|||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
let val = self.memory.read(addr).reg_map(|_, _| 0);
|
let val = mem.read(addr).reg_map(|_, _| 0);
|
||||||
self.background.cur_low = val;
|
self.background.cur_low = val;
|
||||||
} else if self.pixel % 8 == 0 && self.pixel > 0 {
|
} else if self.pixel % 8 == 0 && self.pixel > 0 {
|
||||||
let addr = self.background.cur_nametable as u16 * 16
|
let addr = self.background.cur_nametable as u16 * 16
|
||||||
@@ -796,7 +803,7 @@ impl PPU {
|
|||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
let val = self.memory.read(addr).reg_map(|_, _| todo!());
|
let val = mem.read(addr).reg_map(|_, _| todo!());
|
||||||
self.background.cur_high = val;
|
self.background.cur_high = val;
|
||||||
self.background.cur_shift_low |= self.background.cur_low as u32;
|
self.background.cur_shift_low |= self.background.cur_low as u32;
|
||||||
self.background.cur_shift_high |= self.background.cur_high as u32;
|
self.background.cur_shift_high |= self.background.cur_high as u32;
|
||||||
@@ -842,7 +849,6 @@ impl PPU {
|
|||||||
}
|
}
|
||||||
if self.scanline == 241 && self.pixel == 1 {
|
if self.scanline == 241 && self.pixel == 1 {
|
||||||
self.vblank = true;
|
self.vblank = true;
|
||||||
// self.nmi_waiting = self.nmi_enabled;
|
|
||||||
self.frame_count += 1;
|
self.frame_count += 1;
|
||||||
self.background.state = BackgroundState::NameTableBytePre;
|
self.background.state = BackgroundState::NameTableBytePre;
|
||||||
return true;
|
return true;
|
||||||
@@ -858,12 +864,6 @@ impl PPU {
|
|||||||
}
|
}
|
||||||
pub fn nmi_waiting(&mut self) -> bool {
|
pub fn nmi_waiting(&mut self) -> bool {
|
||||||
self.vblank && self.nmi_enabled
|
self.vblank && self.nmi_enabled
|
||||||
// if self.nmi_waiting {
|
|
||||||
// self.nmi_waiting = false;
|
|
||||||
// return true;
|
|
||||||
// } else {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
pub fn peek_irq(&self) -> bool {
|
pub fn peek_irq(&self) -> bool {
|
||||||
false
|
false
|
||||||
@@ -871,31 +871,36 @@ impl PPU {
|
|||||||
pub fn irq_waiting(&mut self) -> bool {
|
pub fn irq_waiting(&mut self) -> bool {
|
||||||
false
|
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, frame: &mut Frame<R>) {
|
pub fn render_name_table<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
|
||||||
for y in 0..60 {
|
for y in 0..60 {
|
||||||
for x in 0..64 {
|
for x in 0..64 {
|
||||||
let row = y % 30;
|
let row = y % 30;
|
||||||
let col = x % 32;
|
let col = x % 32;
|
||||||
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
|
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
|
||||||
let name = self.memory.peek((off + col + row * 32) as u16).unwrap() as u16 * 16
|
let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as u16 * 16
|
||||||
+ if self.background.second_pattern {
|
+ if self.background.second_pattern {
|
||||||
0x1000
|
0x1000
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
let attr = self
|
let attr = mem
|
||||||
.memory
|
.peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
|
||||||
.peek((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// attr << (((col & 1) << 1) | ((row & 1) << 0)) * 2
|
// attr << (((col & 1) << 1) | ((row & 1) << 0)) * 2
|
||||||
// let h_off = ((self.pixel - 1) / 16) % 2;
|
// let h_off = ((self.pixel - 1) / 16) % 2;
|
||||||
// let v_off = (self.scanline / 16) % 2;
|
// let v_off = (self.scanline / 16) % 2;
|
||||||
// let off = v_off * 4 + h_off * 2;
|
// let off = v_off * 4 + h_off * 2;
|
||||||
let palette = (attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3;
|
let palette = (attr >> (((col & 2) << 0) | ((row & 2) << 1))) & 0x3;
|
||||||
for y_off in 0..8 {
|
for y_off in 0..8 {
|
||||||
let low = self.memory.peek(name + y_off).unwrap();
|
let low = mem.peek_ppu(name + y_off).unwrap();
|
||||||
let high = self.memory.peek(name + y_off + 8).unwrap();
|
let high = mem.peek_ppu(name + y_off + 8).unwrap();
|
||||||
for bit in 0..8 {
|
for bit in 0..8 {
|
||||||
frame.fill_rectangle(
|
frame.fill_rectangle(
|
||||||
Point::new(
|
Point::new(
|
||||||
@@ -929,21 +934,20 @@ impl PPU {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// for
|
// for
|
||||||
// let pat = self.memory.peek();
|
// let pat = mem.peek();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn name_cursor_info(&self, cursor: Point) -> Option<String> {
|
pub fn name_cursor_info(&self, mem: &Mapped, cursor: Point) -> Option<String> {
|
||||||
let x = (cursor.x / 8.) as usize;
|
let x = (cursor.x / 8.) as usize;
|
||||||
let y = (cursor.y / 8.) as usize;
|
let y = (cursor.y / 8.) as usize;
|
||||||
if x < 64 && y < 60 {
|
if x < 64 && y < 60 {
|
||||||
let row = y % 30;
|
let row = y % 30;
|
||||||
let col = x % 32;
|
let col = x % 32;
|
||||||
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
|
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
|
||||||
let name = self.memory.peek((off + col + row * 32) as u16).unwrap() as usize;
|
let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as usize;
|
||||||
let attr = self
|
let attr = mem
|
||||||
.memory
|
.peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
|
||||||
.peek((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Some(format!(
|
Some(format!(
|
||||||
"Row, Column: {}, {}
|
"Row, Column: {}, {}
|
||||||
@@ -988,13 +992,13 @@ Attribute data: ${:02X}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_pattern_tables<R: Renderer>(&self, frame: &mut Frame<R>) {
|
pub fn render_pattern_tables<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
|
||||||
for y in 0..16 {
|
for y in 0..16 {
|
||||||
for x in 0..16 {
|
for x in 0..16 {
|
||||||
let name = (y * 16 + x) * 16;
|
let name = (y * 16 + x) * 16;
|
||||||
for y_off in 0..8 {
|
for y_off in 0..8 {
|
||||||
let low = self.memory.peek(name + y_off).unwrap();
|
let low = mem.peek_ppu(name + y_off).unwrap();
|
||||||
let high = self.memory.peek(name + y_off + 8).unwrap();
|
let high = mem.peek_ppu(name + y_off + 8).unwrap();
|
||||||
for bit in 0..8 {
|
for bit in 0..8 {
|
||||||
frame.fill_rectangle(
|
frame.fill_rectangle(
|
||||||
Point::new(
|
Point::new(
|
||||||
@@ -1030,8 +1034,8 @@ Attribute data: ${:02X}
|
|||||||
for x in 0..16 {
|
for x in 0..16 {
|
||||||
let name = (y * 16 + x) * 16;
|
let name = (y * 16 + x) * 16;
|
||||||
for y_off in 0..8 {
|
for y_off in 0..8 {
|
||||||
let low = self.memory.peek(name + y_off + 0x1000).unwrap();
|
let low = mem.peek_ppu(name + y_off + 0x1000).unwrap();
|
||||||
let high = self.memory.peek(name + y_off + 8 + 0x1000).unwrap();
|
let high = mem.peek_ppu(name + y_off + 8 + 0x1000).unwrap();
|
||||||
for bit in 0..8 {
|
for bit in 0..8 {
|
||||||
frame.fill_rectangle(
|
frame.fill_rectangle(
|
||||||
Point::new(
|
Point::new(
|
||||||
@@ -1061,7 +1065,7 @@ Attribute data: ${:02X}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// for
|
// for
|
||||||
// let pat = self.memory.peek();
|
// let pat = mem.peek();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1105,10 +1109,6 @@ Attribute data: ${:02X}
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mem(&self) -> &impl Memory {
|
|
||||||
&self.memory
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -1117,14 +1117,18 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ppu_registers() {
|
fn ppu_registers() {
|
||||||
let mut ppu = PPU::with_chr_rom(&[0u8; 8192], Mapper::from_flags(0));
|
// let mut ppu = PPU::with_chr_rom(&[0u8; 8192], Mapper::from_header(0));
|
||||||
|
let mut ppu = PPU::init();
|
||||||
|
// let mut mem = MemoryMap::new(vec![Segment::ram("")]);
|
||||||
|
let mut mem = Mapped::test_ram();
|
||||||
|
let mut mem = PpuMem::new(&mut mem);
|
||||||
assert_eq!(ppu.background.v, 0);
|
assert_eq!(ppu.background.v, 0);
|
||||||
assert_eq!(ppu.background.t, 0);
|
assert_eq!(ppu.background.t, 0);
|
||||||
assert_eq!(ppu.background.x, 0);
|
assert_eq!(ppu.background.x, 0);
|
||||||
assert_eq!(ppu.background.w, false);
|
assert_eq!(ppu.background.w, false);
|
||||||
ppu.write_reg(0, 0);
|
ppu.write_reg(&mut mem, 0, 0);
|
||||||
assert_eq!(ppu.background.v, 0);
|
assert_eq!(ppu.background.v, 0);
|
||||||
ppu.write_reg(0, 0b11);
|
ppu.write_reg(&mut mem, 0, 0b11);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ppu.background.t, 0b0001100_00000000,
|
ppu.background.t, 0b0001100_00000000,
|
||||||
"Actual: {:016b}",
|
"Actual: {:016b}",
|
||||||
@@ -1132,7 +1136,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(ppu.background.w, false);
|
assert_eq!(ppu.background.w, false);
|
||||||
|
|
||||||
ppu.write_reg(5, 0x7D);
|
ppu.write_reg(&mut mem, 5, 0x7D);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ppu.background.t, 0b0001100_00001111,
|
ppu.background.t, 0b0001100_00001111,
|
||||||
"Actual: {:016b}",
|
"Actual: {:016b}",
|
||||||
@@ -1140,7 +1144,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(ppu.background.x, 0b101);
|
assert_eq!(ppu.background.x, 0b101);
|
||||||
assert_eq!(ppu.background.w, true);
|
assert_eq!(ppu.background.w, true);
|
||||||
ppu.write_reg(5, 0x5E);
|
ppu.write_reg(&mut mem, 5, 0x5E);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ppu.background.t, 0b1101101_01101111,
|
ppu.background.t, 0b1101101_01101111,
|
||||||
"Actual: {:016b}",
|
"Actual: {:016b}",
|
||||||
@@ -1149,7 +1153,7 @@ mod tests {
|
|||||||
assert_eq!(ppu.background.x, 0b101);
|
assert_eq!(ppu.background.x, 0b101);
|
||||||
assert_eq!(ppu.background.w, false);
|
assert_eq!(ppu.background.w, false);
|
||||||
|
|
||||||
ppu.write_reg(5, 0x7D);
|
ppu.write_reg(&mut mem, 5, 0x7D);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ppu.background.t, 0b1101101_01101111,
|
ppu.background.t, 0b1101101_01101111,
|
||||||
"Actual: {:016b}",
|
"Actual: {:016b}",
|
||||||
@@ -1157,7 +1161,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(ppu.background.x, 0b101);
|
assert_eq!(ppu.background.x, 0b101);
|
||||||
assert_eq!(ppu.background.w, true);
|
assert_eq!(ppu.background.w, true);
|
||||||
ppu.read_reg(2);
|
ppu.read_reg(&mut mem, 2);
|
||||||
assert_eq!(ppu.background.w, false);
|
assert_eq!(ppu.background.w, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/test_roms/alu_bit.s
Normal file
28
src/test_roms/alu_bit.s
Normal 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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use crate::{NES, hex_view::Memory};
|
use crate::NES;
|
||||||
|
|
||||||
use super::rom_test;
|
use super::rom_test;
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ impl HexDump<'_> {
|
|||||||
fn get(&self, off: usize) -> Option<u8> {
|
fn get(&self, off: usize) -> Option<u8> {
|
||||||
match self {
|
match self {
|
||||||
Self::NES(_, _, len) if off >= *len => None,
|
Self::NES(_, _, len) if off >= *len => None,
|
||||||
Self::NES(nes, addr, _) => nes.cpu_mem().peek(addr + off as u16),
|
Self::NES(nes, addr, _) => nes.mem().peek_cpu(addr + off as u16),
|
||||||
Self::Lit(items) => items.get(off).copied(),
|
Self::Lit(items) => items.get(off).copied(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ impl fmt::Display for HexDump<'_> {
|
|||||||
|
|
||||||
fn mem_cmp(nes: &NES, addr: u16, vals: &[u8]) {
|
fn mem_cmp(nes: &NES, addr: u16, vals: &[u8]) {
|
||||||
for (i, v) in vals.iter().enumerate() {
|
for (i, v) in vals.iter().enumerate() {
|
||||||
if nes.cpu_mem().peek(addr + i as u16) != Some(*v) {
|
if nes.mem().peek_cpu(addr + i as u16) != Some(*v) {
|
||||||
panic!(
|
panic!(
|
||||||
"memcmp assertion failed:\nNES:{}\nTest:{}",
|
"memcmp assertion failed:\nNES:{}\nTest:{}",
|
||||||
HexDump::NES(nes, addr, vals.len()),
|
HexDump::NES(nes, addr, vals.len()),
|
||||||
@@ -46,7 +46,7 @@ fn mem_cmp(nes: &NES, addr: u16, vals: &[u8]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rom_test!(crc_check, "crc_check.nes", |nes| {
|
rom_test!(crc_check, "crc_check.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x8026 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []");
|
||||||
assert_eq!(nes.cpu.a, 0x00);
|
assert_eq!(nes.cpu.a, 0x00);
|
||||||
mem_cmp(&nes, 0x20, &[0xFF, 0xFF, 0xFF, 0xFF]);
|
mem_cmp(&nes, 0x20, &[0xFF, 0xFF, 0xFF, 0xFF]);
|
||||||
mem_cmp(&nes, 0x24, &[0x69, 0xCF, 0xF8, 0x88]);
|
mem_cmp(&nes, 0x24, &[0x69, 0xCF, 0xF8, 0x88]);
|
||||||
@@ -58,6 +58,11 @@ rom_test!(crc_check, "crc_check.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(implied_instructions, "implied_instrs.nes", |nes| {
|
rom_test!(implied_instructions, "implied_instrs.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x8026 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []");
|
||||||
assert_eq!(nes.cpu.a, 0x00);
|
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]);
|
||||||
|
});
|
||||||
@@ -1,27 +1,25 @@
|
|||||||
use crate::hex_view::Memory;
|
|
||||||
|
|
||||||
use super::rom_test;
|
use super::rom_test;
|
||||||
|
|
||||||
rom_test!(int_nmi_timing, "int_nmi_timing.nes", |nes| {
|
rom_test!(int_nmi_timing, "int_nmi_timing.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x801B HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x801B HLT :2 []");
|
||||||
assert_eq!(nes.clock_count, 260881);
|
assert_eq!(nes.clock_count, 260881);
|
||||||
assert_eq!(nes.cycle, 86967);
|
assert_eq!(nes.cpu_cycle(), 86967);
|
||||||
assert_eq!(nes.ppu().pixel, 40);
|
assert_eq!(nes.ppu().pixel, 40);
|
||||||
assert_eq!(nes.ppu().scanline, 241);
|
assert_eq!(nes.ppu().scanline, 241);
|
||||||
assert_eq!(nes.ppu().cycle, 260905);
|
assert_eq!(nes.ppu().cycle, 260905);
|
||||||
assert_eq!(nes.cpu_mem().peek(0x1FF), Some(0x80));
|
assert_eq!(nes.mem().peek_cpu(0x1FF), Some(0x80));
|
||||||
assert_eq!(nes.cpu_mem().peek(0x1FE), Some(0x17));
|
assert_eq!(nes.mem().peek_cpu(0x1FE), Some(0x17));
|
||||||
assert_eq!(nes.cpu_mem().peek(0x1FD), Some(0xA4));
|
assert_eq!(nes.mem().peek_cpu(0x1FD), Some(0xA4));
|
||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(int_nmi_exit_timing, "int_nmi_exit_timing.nes", |nes| {
|
rom_test!(int_nmi_exit_timing, "int_nmi_exit_timing.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x801F HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x801F HLT :2 []");
|
||||||
// assert_eq!(nes.clock_count, 260881);
|
// assert_eq!(nes.clock_count, 260881);
|
||||||
assert_eq!(nes.cycle, 86980);
|
assert_eq!(nes.cpu_cycle(), 86980);
|
||||||
assert_eq!(nes.ppu().pixel, 79);
|
assert_eq!(nes.ppu().pixel, 79);
|
||||||
assert_eq!(nes.ppu().scanline, 241);
|
assert_eq!(nes.ppu().scanline, 241);
|
||||||
assert_eq!(nes.ppu().cycle, 260905 - 40 + 79);
|
assert_eq!(nes.ppu().cycle, 260905 - 40 + 79);
|
||||||
assert_eq!(nes.cpu_mem().peek(0x1FF), Some(0x80));
|
assert_eq!(nes.mem().peek_cpu(0x1FF), Some(0x80));
|
||||||
assert_eq!(nes.cpu_mem().peek(0x1FE), Some(0x1B));
|
assert_eq!(nes.mem().peek_cpu(0x1FE), Some(0x1B));
|
||||||
assert_eq!(nes.cpu_mem().peek(0x1FD), Some(0x26));
|
assert_eq!(nes.mem().peek_cpu(0x1FD), Some(0x26));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
mod cpu_reset_ram;
|
mod cpu_reset_ram;
|
||||||
mod instr_test_v3;
|
mod instructions;
|
||||||
mod ppu;
|
mod ppu;
|
||||||
mod interrupts;
|
mod interrupts;
|
||||||
|
|
||||||
use crate::hex_view::Memory;
|
|
||||||
|
|
||||||
macro_rules! rom_test {
|
macro_rules! rom_test {
|
||||||
($name:ident, $rom:literal, |$nes:ident| $eval:expr) => {
|
($name:ident, $rom:literal, |$nes:ident| $eval:expr) => {
|
||||||
rom_test!($name, $rom, timeout = 10000000, |$nes| $eval);
|
rom_test!($name, $rom, timeout = 10000000, |$nes| $eval);
|
||||||
@@ -39,8 +37,8 @@ macro_rules! rom_test {
|
|||||||
pub(crate) use rom_test;
|
pub(crate) use rom_test;
|
||||||
|
|
||||||
rom_test!(basic_cpu, "basic-cpu.nes", |nes| {
|
rom_test!(basic_cpu, "basic-cpu.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x8001 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x8001 HLT :2 []");
|
||||||
assert_eq!(nes.cycle, 10);
|
assert_eq!(nes.cpu_cycle(), 10);
|
||||||
assert_eq!(nes.cpu.pc, 0x8001);
|
assert_eq!(nes.cpu.pc, 0x8001);
|
||||||
assert_eq!(nes.ppu.pixel, 34);
|
assert_eq!(nes.ppu.pixel, 34);
|
||||||
|
|
||||||
@@ -48,8 +46,8 @@ rom_test!(basic_cpu, "basic-cpu.nes", |nes| {
|
|||||||
|
|
||||||
nes.repl_nop();
|
nes.repl_nop();
|
||||||
nes.run_with_timeout(200);
|
nes.run_with_timeout(200);
|
||||||
assert_eq!(nes.last_instruction, "0x8002 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x8002 HLT :2 []");
|
||||||
assert_eq!(nes.cycle, 12);
|
assert_eq!(nes.cpu_cycle(), 12);
|
||||||
assert_eq!(nes.cpu.pc, 0x8002);
|
assert_eq!(nes.cpu.pc, 0x8002);
|
||||||
assert_eq!(nes.ppu.pixel, 40);
|
assert_eq!(nes.ppu.pixel, 40);
|
||||||
|
|
||||||
@@ -57,8 +55,8 @@ rom_test!(basic_cpu, "basic-cpu.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(basic_cpu_with_nop, "basic-cpu-nop.nes", |nes| {
|
rom_test!(basic_cpu_with_nop, "basic-cpu-nop.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x8002 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x8002 HLT :2 []");
|
||||||
assert_eq!(nes.cycle, 12);
|
assert_eq!(nes.cpu_cycle(), 12);
|
||||||
assert_eq!(nes.cpu.pc, 0x8002);
|
assert_eq!(nes.cpu.pc, 0x8002);
|
||||||
assert_eq!(nes.ppu.pixel, 40);
|
assert_eq!(nes.ppu.pixel, 40);
|
||||||
|
|
||||||
@@ -66,22 +64,22 @@ rom_test!(basic_cpu_with_nop, "basic-cpu-nop.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(read_write, "read_write.nes", |nes| {
|
rom_test!(read_write, "read_write.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x800C HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x800C HLT :2 []");
|
||||||
assert_eq!(nes.cycle, 25);
|
assert_eq!(nes.cpu_cycle(), 25);
|
||||||
assert_eq!(nes.cpu.pc, 0x800C);
|
assert_eq!(nes.cpu.pc, 0x800C);
|
||||||
assert_eq!(nes.cpu.sp, 0xFD);
|
assert_eq!(nes.cpu.sp, 0xFD);
|
||||||
|
|
||||||
assert_eq!(nes.cpu.a, 0xAA);
|
assert_eq!(nes.cpu.a, 0xAA);
|
||||||
assert_eq!(nes.cpu.x, 0xAA);
|
assert_eq!(nes.cpu.x, 0xAA);
|
||||||
assert_eq!(nes.cpu.y, 0xAA);
|
assert_eq!(nes.cpu.y, 0xAA);
|
||||||
assert_eq!(nes.cpu_mem().peek(0x0000).unwrap(), 0xAA);
|
assert_eq!(nes.mem().peek_cpu(0x0000).unwrap(), 0xAA);
|
||||||
assert_eq!(nes.cpu_mem().peek(0x0001).unwrap(), 0xAA);
|
assert_eq!(nes.mem().peek_cpu(0x0001).unwrap(), 0xAA);
|
||||||
assert_eq!(nes.cpu_mem().peek(0x0002).unwrap(), 0xAA);
|
assert_eq!(nes.mem().peek_cpu(0x0002).unwrap(), 0xAA);
|
||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(basic_init_0, "basic_init_0.nes", |nes| {
|
rom_test!(basic_init_0, "basic_init_0.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x8017 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x8017 HLT :2 []");
|
||||||
assert_eq!(nes.cycle, 40);
|
assert_eq!(nes.cpu_cycle(), 40);
|
||||||
assert_eq!(nes.cpu.pc, 0x8017);
|
assert_eq!(nes.cpu.pc, 0x8017);
|
||||||
assert_eq!(nes.cpu.sp, 0xFF);
|
assert_eq!(nes.cpu.sp, 0xFF);
|
||||||
|
|
||||||
@@ -91,8 +89,8 @@ rom_test!(basic_init_0, "basic_init_0.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(basic_init_1, "basic_init_1.nes", |nes| {
|
rom_test!(basic_init_1, "basic_init_1.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x801C HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x801C HLT :2 []");
|
||||||
assert_eq!(nes.cycle, 27402);
|
assert_eq!(nes.cpu_cycle(), 27402);
|
||||||
assert_eq!(nes.cpu.pc, 0x801C);
|
assert_eq!(nes.cpu.pc, 0x801C);
|
||||||
assert_eq!(nes.ppu.pixel, 29);
|
assert_eq!(nes.ppu.pixel, 29);
|
||||||
|
|
||||||
@@ -103,8 +101,8 @@ rom_test!(basic_init_1, "basic_init_1.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(basic_init_2, "basic_init_2.nes", |nes| {
|
rom_test!(basic_init_2, "basic_init_2.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x8021 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x8021 HLT :2 []");
|
||||||
assert_eq!(nes.cycle, 57179);
|
assert_eq!(nes.cpu_cycle(), 57179);
|
||||||
assert_eq!(nes.cpu.pc, 0x8021);
|
assert_eq!(nes.cpu.pc, 0x8021);
|
||||||
assert_eq!(nes.ppu.pixel, 18);
|
assert_eq!(nes.ppu.pixel, 18);
|
||||||
|
|
||||||
@@ -115,8 +113,8 @@ rom_test!(basic_init_2, "basic_init_2.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(basic_init_3, "basic_init_3.nes", |nes| {
|
rom_test!(basic_init_3, "basic_init_3.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x8026 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []");
|
||||||
assert_eq!(nes.cycle, 86963);
|
assert_eq!(nes.cpu_cycle(), 86963);
|
||||||
assert_eq!(nes.cpu.pc, 0x8026);
|
assert_eq!(nes.cpu.pc, 0x8026);
|
||||||
assert_eq!(nes.ppu.pixel, 28);
|
assert_eq!(nes.ppu.pixel, 28);
|
||||||
|
|
||||||
@@ -127,8 +125,8 @@ rom_test!(basic_init_3, "basic_init_3.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(even_odd, "even_odd.nes", |nes| {
|
rom_test!(even_odd, "even_odd.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x8023 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x8023 HLT :2 []");
|
||||||
assert_eq!(nes.cycle, 57181);
|
assert_eq!(nes.cpu_cycle(), 57181);
|
||||||
assert_eq!(nes.cpu.pc, 0x8023);
|
assert_eq!(nes.cpu.pc, 0x8023);
|
||||||
assert_eq!(nes.ppu.pixel, 24);
|
assert_eq!(nes.ppu.pixel, 24);
|
||||||
|
|
||||||
@@ -139,8 +137,8 @@ rom_test!(even_odd, "even_odd.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// rom_test!(even_odd, "even_odd.nes", |nes| {
|
// rom_test!(even_odd, "even_odd.nes", |nes| {
|
||||||
// assert_eq!(nes.last_instruction, "0x8023 HLT :2 []");
|
// assert_eq!(nes.last_instruction(), "0x8023 HLT :2 []");
|
||||||
// assert_eq!(nes.cycle, 57182);
|
// assert_eq!(nes.cpu_cycle(), 57182);
|
||||||
// assert_eq!(nes.cpu.pc, 0x8024);
|
// assert_eq!(nes.cpu.pc, 0x8024);
|
||||||
// assert_eq!(nes.ppu.pixel, 25);
|
// assert_eq!(nes.ppu.pixel, 25);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{hex_view::Memory, Color, RenderBuffer};
|
use crate::{Color, RenderBuffer};
|
||||||
|
|
||||||
use super::rom_test;
|
use super::rom_test;
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ const COLOR_0B: Color = Color {
|
|||||||
};
|
};
|
||||||
|
|
||||||
rom_test!(ppu_fill, "ppu_fill.nes", |nes| {
|
rom_test!(ppu_fill, "ppu_fill.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x802B HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x802B HLT :2 []");
|
||||||
assert_eq!(nes.cpu.a, 0x01);
|
assert_eq!(nes.cpu.a, 0x01);
|
||||||
|
|
||||||
let mut buffer = RenderBuffer::empty();
|
let mut buffer = RenderBuffer::empty();
|
||||||
@@ -32,7 +32,7 @@ rom_test!(ppu_fill, "ppu_fill.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(ppu_fill_red, "ppu_fill_red.nes", |nes| {
|
rom_test!(ppu_fill_red, "ppu_fill_red.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x803A HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x803A HLT :2 []");
|
||||||
assert_eq!(nes.cpu.a, 0x01);
|
assert_eq!(nes.cpu.a, 0x01);
|
||||||
|
|
||||||
let mut buffer = RenderBuffer::empty();
|
let mut buffer = RenderBuffer::empty();
|
||||||
@@ -45,7 +45,7 @@ rom_test!(ppu_fill_red, "ppu_fill_red.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(ppu_fill_palette_1, "ppu_fill_palette_1.nes", |nes| {
|
rom_test!(ppu_fill_palette_1, "ppu_fill_palette_1.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x8064 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x8064 HLT :2 []");
|
||||||
assert_eq!(nes.cpu.a, 0x01);
|
assert_eq!(nes.cpu.a, 0x01);
|
||||||
|
|
||||||
let mut buffer = RenderBuffer::empty();
|
let mut buffer = RenderBuffer::empty();
|
||||||
@@ -62,7 +62,7 @@ rom_test!(ppu_fill_palette_1, "ppu_fill_palette_1.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(ppu_fill_name_table, "ppu_fill_name_table.nes", |nes| {
|
rom_test!(ppu_fill_name_table, "ppu_fill_name_table.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x80B3 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x80B3 HLT :2 []");
|
||||||
assert_eq!(nes.cpu.a, 0x01);
|
assert_eq!(nes.cpu.a, 0x01);
|
||||||
|
|
||||||
let mut buffer = RenderBuffer::empty();
|
let mut buffer = RenderBuffer::empty();
|
||||||
@@ -81,10 +81,10 @@ rom_test!(ppu_fill_name_table, "ppu_fill_name_table.nes", |nes| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rom_test!(ppu_vertical_write, "ppu_vertical_write.nes", |nes| {
|
rom_test!(ppu_vertical_write, "ppu_vertical_write.nes", |nes| {
|
||||||
assert_eq!(nes.last_instruction, "0x8040 HLT :2 []");
|
assert_eq!(nes.last_instruction(), "0x8040 HLT :2 []");
|
||||||
|
|
||||||
assert_eq!(nes.ppu().mem().peek(0x2000), Some(1));
|
assert_eq!(nes.mem().peek_ppu(0x2000), Some(1));
|
||||||
assert_eq!(nes.ppu().mem().peek(0x2020), Some(1));
|
assert_eq!(nes.mem().peek_ppu(0x2020), Some(1));
|
||||||
assert_eq!(nes.ppu().mem().peek(0x2400), Some(2));
|
assert_eq!(nes.mem().peek_ppu(0x2400), Some(2));
|
||||||
assert_eq!(nes.ppu().mem().peek(0x2401), Some(2));
|
assert_eq!(nes.mem().peek_ppu(0x2401), Some(2));
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user