Compare commits
3 Commits
e882b3b325
...
f84009aeb9
| Author | SHA1 | Date | |
|---|---|---|---|
|
f84009aeb9
|
|||
|
875e493cd2
|
|||
|
d140f1e122
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@
|
||||
*.nes
|
||||
*.zip
|
||||
*.dmp
|
||||
wasm/
|
||||
!wasm/wasm.html
|
||||
|
||||
37
src/apu.rs
37
src/apu.rs
@@ -1,7 +1,3 @@
|
||||
use std::iter::repeat_n;
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
macro_rules! lut {
|
||||
($name:ident: [$ty:ty; $len:expr] = |$n:ident| $expr:expr) => {
|
||||
const $name: [$ty; $len] = {
|
||||
@@ -264,7 +260,7 @@ Output: {16:>4} ${16:02X}
|
||||
self.sweep.period(),
|
||||
self.sweep.enable(),
|
||||
self.period,
|
||||
0,
|
||||
self.counter.current,
|
||||
self.enabled,
|
||||
self.period_timer,
|
||||
self.cur, // ?
|
||||
@@ -423,6 +419,7 @@ bitfield::bitfield! {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
struct NoiseChannel {
|
||||
enabled: bool,
|
||||
evelope: NoiseEnvelope,
|
||||
@@ -447,7 +444,7 @@ impl NoiseChannel {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, offset: u16, val: u8) {
|
||||
pub fn write(&mut self, offset: u16, _val: u8) {
|
||||
match offset {
|
||||
0x00 => (),
|
||||
0x01 => (),
|
||||
@@ -505,7 +502,7 @@ impl DeltaChannel {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, offset: u16, val: u8) {
|
||||
pub fn write(&mut self, offset: u16, _val: u8) {
|
||||
match offset {
|
||||
0x00 => (),
|
||||
0x01 => (),
|
||||
@@ -553,12 +550,21 @@ pub struct APU {
|
||||
|
||||
impl std::fmt::Debug for APU {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// write!(
|
||||
// f,
|
||||
// "PPU: f {}, s {}, p {}",
|
||||
// self.frame_count, self.scanline, self.pixel
|
||||
// )
|
||||
Ok(())
|
||||
write!(
|
||||
f,
|
||||
"APU
|
||||
{:#?}
|
||||
{:#?}
|
||||
{:#?}
|
||||
{:#?}
|
||||
{:#?}
|
||||
",
|
||||
self.pulse_1,
|
||||
self.pulse_2,
|
||||
self.triangle,
|
||||
self.noise,
|
||||
self.dmc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,7 +583,7 @@ impl APU {
|
||||
irq: false,
|
||||
},
|
||||
|
||||
samples: vec![0; 100],
|
||||
samples: Vec::with_capacity(10000),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -739,3 +745,6 @@ mod apu_iced {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
98
src/apu/tests.rs
Normal file
98
src/apu/tests.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use super::*;
|
||||
|
||||
#[track_caller]
|
||||
fn run_ppu_cycles(apu: &mut APU, cycles: usize) {
|
||||
for i in 0..cycles {
|
||||
apu.run_one_clock_cycle(i);
|
||||
}
|
||||
}
|
||||
|
||||
trait Pattern {
|
||||
fn matches(&mut self, val: u8) -> bool;
|
||||
}
|
||||
|
||||
impl Pattern for u8 {
|
||||
fn matches(&mut self, val: u8) -> bool {
|
||||
val == *self
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Iterator<Item = u8>> Pattern for I {
|
||||
fn matches(&mut self, val: u8) -> bool {
|
||||
self.next().is_some_and(|v| v == val)
|
||||
}
|
||||
}
|
||||
|
||||
fn is(samples: &[u8], mut pattern: impl Pattern) -> bool {
|
||||
for s in samples {
|
||||
if !pattern.matches(*s) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_is() {
|
||||
assert!(&[0; 20], 0..5);
|
||||
}
|
||||
|
||||
macro_rules! cycle_check {
|
||||
(|$apu:ident| $init:expr; $([$pat:expr; $count:expr]),* ; [$final:expr; _]) => {
|
||||
let mut $apu = APU::init();
|
||||
$init;
|
||||
run_ppu_cycles(&mut $apu, 10000);
|
||||
let mut s = $apu.get_frame_samples();
|
||||
let mut cur = 0;
|
||||
$(
|
||||
let count = $count;
|
||||
if !is(&s[..count], $pat) {
|
||||
panic!("Incorrect samples from {} to {}:\nExpected: {:?}\n Found: {:?}", cur, cur + count, $pat, &s[..count]);
|
||||
}
|
||||
s = &s[count..];
|
||||
cur += count;
|
||||
)*
|
||||
if !is(s, $final) {
|
||||
panic!("Incorrect samples from {} to {}:\nExpected: {:?}\n Found: {:?}", cur, cur + count, $final, s);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const SNDCHN: u16 = 0x15;
|
||||
|
||||
const PULSE_CH1_DLCV: u16 = 0x00;
|
||||
const PULSE_CH1_SWEEP: u16 = 0x01;
|
||||
const PULSE_CH1_TLOW: u16 = 0x02;
|
||||
const PULSE_CH1_LCTH: u16 = 0x03;
|
||||
|
||||
#[test]
|
||||
fn run_apu() {
|
||||
let mut apu = APU::init();
|
||||
run_ppu_cycles(&mut apu, 10000);
|
||||
println!("Count: {}", apu.get_frame_samples().len());
|
||||
assert_eq!(apu.get_frame_samples(), &[0; 417]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_apu_pulse_1() {
|
||||
// Base? case
|
||||
cycle_check!(|apu| {
|
||||
apu.write_reg(SNDCHN, 0x01);
|
||||
apu.write_reg(PULSE_CH1_DLCV, 0xBF);
|
||||
apu.write_reg(PULSE_CH1_SWEEP, 0x7F);
|
||||
apu.write_reg(PULSE_CH1_TLOW, 0x6F);
|
||||
apu.write_reg(PULSE_CH1_LCTH, 0xF0);
|
||||
// 1666 APU cycles (every fourth is a sample)
|
||||
// Advance in duty cycle every 111 + 1 apu cycle (28 samples)
|
||||
}; [0; 28], [37; 28*4], [0; 28*4], [37; 28*4] ; [0; _]);
|
||||
|
||||
cycle_check!(|apu| {
|
||||
apu.write_reg(SNDCHN, 0x01);
|
||||
apu.write_reg(PULSE_CH1_DLCV, 0xBF);
|
||||
apu.write_reg(PULSE_CH1_SWEEP, 0x7F);
|
||||
apu.write_reg(PULSE_CH1_TLOW, 0x60);
|
||||
apu.write_reg(PULSE_CH1_LCTH, 0xF0);
|
||||
// 1666 APU cycles (every fourth is a sample)
|
||||
// Advance in duty cycle every 96 + 1 apu cycle (24.25 samples)
|
||||
}; [0; 24], [37; 24*4+1], [0; 24*4+1], [37; 24*4+1], [0; 24*4 + 1] ; [37; _]);
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::AtomicBool, mpsc::{channel, Sender, TryRecvError}, Arc
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
use std::sync::{
|
||||
atomic::AtomicBool, Arc
|
||||
};
|
||||
|
||||
use cpal::{
|
||||
Device, FrameCount, Host, SampleFormat, Stream,
|
||||
Device, FrameCount, Host, Stream,
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
};
|
||||
use ringbuf::{
|
||||
|
||||
@@ -70,13 +70,13 @@ where
|
||||
{
|
||||
text(format!("{val:02X}"))
|
||||
}
|
||||
pub fn bin8<'a, Theme, Renderer>(val: u8) -> Text<'a, Theme, Renderer>
|
||||
where
|
||||
Theme: text::Catalog + 'a,
|
||||
Renderer: iced::advanced::text::Renderer,
|
||||
{
|
||||
text(format!("{val:08b}"))
|
||||
}
|
||||
// pub fn bin8<'a, Theme, Renderer>(val: u8) -> Text<'a, Theme, Renderer>
|
||||
// where
|
||||
// Theme: text::Catalog + 'a,
|
||||
// Renderer: iced::advanced::text::Renderer,
|
||||
// {
|
||||
// text(format!("{val:08b}"))
|
||||
// }
|
||||
pub fn bin32<'a, Theme, Renderer>(val: u32) -> Text<'a, Theme, Renderer>
|
||||
where
|
||||
Theme: text::Catalog + 'a,
|
||||
@@ -408,7 +408,7 @@ pub fn labelled_box<'a, Message: 'a>(label: &'a str, value: bool) -> Element<'a,
|
||||
pub enum DbgImage<'a> {
|
||||
NameTable(&'a Mapped, &'a PPU),
|
||||
PatternTable(&'a Mapped, &'a PPU),
|
||||
Palette(&'a Mapped, &'a PPU),
|
||||
Palette(&'a PPU),
|
||||
}
|
||||
|
||||
impl<Message, T> Program<Message, T> for DbgImage<'_> {
|
||||
@@ -432,7 +432,7 @@ impl<Message, T> Program<Message, T> for DbgImage<'_> {
|
||||
DbgImage::PatternTable(mem, ppu) => {
|
||||
ppu.render_pattern_tables(mem, &mut name_table_frame)
|
||||
}
|
||||
DbgImage::Palette(_, ppu) => ppu.render_palette(&mut name_table_frame),
|
||||
DbgImage::Palette(ppu) => ppu.render_palette(&mut name_table_frame),
|
||||
};
|
||||
vec![name_table_frame.into_geometry()]
|
||||
}
|
||||
@@ -443,21 +443,21 @@ impl DbgImage<'_> {
|
||||
match self {
|
||||
DbgImage::NameTable(_, _) => Length::Fixed(512. * 2.),
|
||||
DbgImage::PatternTable(_, _) => Length::Fixed(16. * 8. * 2.),
|
||||
DbgImage::Palette(_, _) => Length::Fixed(40. * 2.),
|
||||
DbgImage::Palette(_) => Length::Fixed(40. * 2.),
|
||||
}
|
||||
}
|
||||
fn height(&self) -> Length {
|
||||
match self {
|
||||
DbgImage::NameTable(_, _) => Length::Fixed(512. * 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> {
|
||||
match self {
|
||||
DbgImage::NameTable(mem, ppu) => ppu.name_cursor_info(mem, 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
use std::{
|
||||
fmt::{self, Display}, ops::Deref
|
||||
};
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use iced::{
|
||||
advanced::{layout::{Limits, Node}, overlay, renderer::{Quad, Style}, text::Renderer, widget::{tree::{State, Tag}, Operation, Tree}, Clipboard, Layout, Shell, Text, Widget}, alignment::Vertical, keyboard::{key::Named, Key}, mouse::{Button, Cursor, Interaction, ScrollDelta}, widget::{column, lazy, text}, Color, Element, Event, Fill, Font, Length, Padding, Pixels, Point, Rectangle, Size, Task, Vector
|
||||
Color, Element, Event, Font, Length, Padding, Pixels, Point, Rectangle, Size, Task, Vector,
|
||||
advanced::{
|
||||
Clipboard, Layout, Shell, Text, Widget,
|
||||
layout::{Limits, Node},
|
||||
overlay,
|
||||
renderer::{Quad, Style},
|
||||
text::Renderer,
|
||||
widget::{
|
||||
Operation, Tree,
|
||||
tree::{State, Tag},
|
||||
},
|
||||
},
|
||||
alignment::Vertical,
|
||||
keyboard::{Key, key::Named},
|
||||
mouse::{Button, Cursor, Interaction, ScrollDelta},
|
||||
};
|
||||
// use iced_core::{
|
||||
// Clipboard, Color, Event, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Text, Vector,
|
||||
// Widget,
|
||||
// layout::{Limits, Node},
|
||||
// mouse::{Cursor, Interaction},
|
||||
// overlay,
|
||||
// renderer::{Quad, Style},
|
||||
// widget::{
|
||||
// Operation, Tree,
|
||||
// tree::{State, Tag},
|
||||
// },
|
||||
// };
|
||||
use nes_emu::Memory;
|
||||
|
||||
use nes_emu::{PPU, Mapped, PPUMMRegisters};
|
||||
use nes_emu::{Mapped, PPU, PPUMMRegisters};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Cpu<'a>(&'a Mapped);
|
||||
@@ -82,53 +82,53 @@ pub enum HexEvent {}
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct HexView {}
|
||||
|
||||
struct Val(Option<u8>);
|
||||
impl fmt::Display for Val {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(val) = self.0 {
|
||||
write!(f, "{:02X}", val)
|
||||
} else {
|
||||
write!(f, "XX")
|
||||
}
|
||||
}
|
||||
}
|
||||
// struct Val(Option<u8>);
|
||||
// impl fmt::Display for Val {
|
||||
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// if let Some(val) = self.0 {
|
||||
// write!(f, "{:02X}", val)
|
||||
// } else {
|
||||
// write!(f, "XX")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl HexView {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
// pub fn new() -> Self {
|
||||
// Self {}
|
||||
// }
|
||||
|
||||
pub fn render_any<'a, M: Memory + Copy + 'a>(&self, mem: M) -> Element<'a, HexEvent> {
|
||||
struct Row<M: Memory>(usize, M);
|
||||
impl<'a, M: Memory + 'a> fmt::Display for Row<M> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", Val(self.1.peek(self.0)))?;
|
||||
for i in 1..16 {
|
||||
write!(f, " {}", Val(self.1.peek(self.0 + i)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
column![
|
||||
text!("Hex view"),
|
||||
iced::widget::scrollable(lazy(mem.edit_ver(), move |_| column(
|
||||
[
|
||||
text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F")
|
||||
.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_any<'a, M: Memory + Copy + 'a>(&self, mem: M) -> Element<'a, HexEvent> {
|
||||
// struct Row<M: Memory>(usize, M);
|
||||
// impl<'a, M: Memory + 'a> fmt::Display for Row<M> {
|
||||
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// write!(f, "{}", Val(self.1.peek(self.0)))?;
|
||||
// for i in 1..16 {
|
||||
// write!(f, " {}", Val(self.1.peek(self.0 + i)))?;
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
// column![
|
||||
// text!("Hex view"),
|
||||
// iced::widget::scrollable(lazy(mem.edit_ver(), move |_| column(
|
||||
// [
|
||||
// text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F")
|
||||
// .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))
|
||||
Element::new(
|
||||
@@ -147,35 +147,35 @@ impl HexView {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update(&mut self, ev: HexEvent) -> Task<HexEvent> {
|
||||
pub fn update(&mut self, _ev: HexEvent) -> Task<HexEvent> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BufferSlice<'a>(pub &'a [u8]);
|
||||
// pub struct BufferSlice<'a>(pub &'a [u8]);
|
||||
|
||||
impl Deref for BufferSlice<'_> {
|
||||
type Target = [u8];
|
||||
// impl Deref for BufferSlice<'_> {
|
||||
// type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
// fn deref(&self) -> &Self::Target {
|
||||
// self.0
|
||||
// }
|
||||
// }
|
||||
|
||||
pub trait Buffer {
|
||||
fn peek(&self, val: usize) -> Option<u8>;
|
||||
fn len(&self) -> usize;
|
||||
}
|
||||
|
||||
impl Buffer for BufferSlice<'_> {
|
||||
fn peek(&self, val: usize) -> Option<u8> {
|
||||
self.get(val).copied()
|
||||
}
|
||||
// impl Buffer for BufferSlice<'_> {
|
||||
// fn peek(&self, val: usize) -> Option<u8> {
|
||||
// self.get(val).copied()
|
||||
// }
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.iter().len()
|
||||
}
|
||||
}
|
||||
// fn len(&self) -> usize {
|
||||
// self.iter().len()
|
||||
// }
|
||||
// }
|
||||
impl<M: Memory> Buffer for M {
|
||||
fn peek(&self, val: usize) -> Option<u8> {
|
||||
self.peek(val)
|
||||
@@ -202,6 +202,7 @@ pub struct HexEdit {
|
||||
new_value: u8,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct HexEditor<B, M, R: Renderer> {
|
||||
val: B,
|
||||
font_size: Option<Pixels>,
|
||||
@@ -279,6 +280,7 @@ where
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[allow(dead_code)]
|
||||
struct HexEditorState {
|
||||
offset_y: f32,
|
||||
selected: usize,
|
||||
@@ -311,10 +313,10 @@ where
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut R,
|
||||
theme: &T,
|
||||
style: &Style,
|
||||
_theme: &T,
|
||||
_style: &Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: Cursor,
|
||||
_cursor: Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state: &HexEditorState = tree.state.downcast_ref();
|
||||
|
||||
@@ -46,11 +46,12 @@ use audio::Audio;
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "sprites.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_pulse_channel_1.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_pulse_channel_1_evelope.nes");
|
||||
const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_pulse_1.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_pulse_1.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_triangle.nes");
|
||||
// const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
|
||||
// const ROM_FILE: &str = "./cpu_timing_test.nes";
|
||||
// const ROM_FILE: &str = "../nes-test-roms/instr_test-v5/official_only.nes";
|
||||
const ROM_FILE: &str = "../Downloads/Legend of Zelda, The (USA).nes";
|
||||
|
||||
extern crate nes_emu;
|
||||
|
||||
@@ -453,7 +454,7 @@ impl Emulator {
|
||||
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()
|
||||
dbg_image(DbgImage::Palette(self.nes.ppu())).into()
|
||||
}
|
||||
Some(WindowType::Debugger) => column![
|
||||
row![
|
||||
|
||||
@@ -1729,7 +1729,12 @@ impl Cpu {
|
||||
0xEA => inst!("NOP", 1, || {
|
||||
log!("{addr:04X}: NOP");
|
||||
}),
|
||||
_ => todo!("ins: 0x{:04X}: 0x{ins:X}, {params:X?}", self.pc - 1),
|
||||
// _ => todo!("ins: 0x{:04X}: 0x{ins:X}, {params:X?}", self.pc - 1),
|
||||
_ => {
|
||||
println!("ins: 0x{:04X}: 0x{ins:X}, {params:X?}", self.pc - 1);
|
||||
self.halt();
|
||||
ExecState::Done
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1936,7 +1941,9 @@ impl Cpu {
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, mem: &mut CpuMem<'_>) {
|
||||
let debug_log = self.debug_log.enabled();
|
||||
*self = Self::init();
|
||||
if debug_log { self.debug_log.enable(); }
|
||||
self.pc = u16::from_le_bytes([mem.read(0xFFFC), mem.read(0xFFFD)]);
|
||||
self.sp = 0xFD;
|
||||
self.status.set_interrupt_disable(true);
|
||||
|
||||
14
src/debug.rs
14
src/debug.rs
@@ -1,12 +1,12 @@
|
||||
use std::num::NonZeroUsize;
|
||||
// use std::num::NonZeroUsize;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DebugLog {
|
||||
enabled: bool,
|
||||
current: String,
|
||||
history: Vec<String>,
|
||||
max_history: Option<NonZeroUsize>,
|
||||
pos: usize,
|
||||
// max_history: Option<NonZeroUsize>,
|
||||
// pos: usize,
|
||||
}
|
||||
|
||||
impl DebugLog {
|
||||
@@ -15,8 +15,8 @@ impl DebugLog {
|
||||
enabled: false,
|
||||
current: String::new(),
|
||||
history: vec![],
|
||||
max_history: None,
|
||||
pos: 0,
|
||||
// max_history: None,
|
||||
// pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,10 @@ impl DebugLog {
|
||||
pub fn enable(&mut self) {
|
||||
self.enabled = true;
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Write for DebugLog {
|
||||
|
||||
32
src/lib.rs
32
src/lib.rs
@@ -9,10 +9,10 @@ mod mem;
|
||||
mod ppu;
|
||||
#[cfg(test)]
|
||||
mod test_roms;
|
||||
pub use mem::{Memory, Mapped};
|
||||
pub use mem::{Mapped, Memory};
|
||||
|
||||
pub use ppu::{Color, PPU, RenderBuffer, PPUMMRegisters};
|
||||
pub use cpu::{Cpu, CycleResult, CPUMMRegisters};
|
||||
pub use cpu::{CPUMMRegisters, Cpu, CycleResult};
|
||||
pub use ppu::{Color, PPU, PPUMMRegisters, RenderBuffer};
|
||||
// #[cfg(not(target_arch = "wasm32"))]
|
||||
// use tokio::io::AsyncReadExt as _;
|
||||
|
||||
@@ -260,16 +260,16 @@ impl NES {
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.clock_count = 0;
|
||||
// dbg_int: false,
|
||||
// dbg_int: false,
|
||||
|
||||
// clock_count: 0,
|
||||
// mapped,
|
||||
// cpu: Cpu::init(),
|
||||
// dma: DmaState::Passive,
|
||||
// // ppu: PPU::with_chr_rom(chr_rom, mapper),
|
||||
// ppu: PPU::init(),
|
||||
// apu: APU::init(),
|
||||
// controller: Controllers::init(),
|
||||
// clock_count: 0,
|
||||
// mapped,
|
||||
// cpu: Cpu::init(),
|
||||
// dma: DmaState::Passive,
|
||||
// // ppu: PPU::with_chr_rom(chr_rom, mapper),
|
||||
// ppu: PPU::init(),
|
||||
// apu: APU::init(),
|
||||
// controller: Controllers::init(),
|
||||
self.cpu.reset(&mut CpuMem::new(
|
||||
&mut self.mapped,
|
||||
&mut self.ppu,
|
||||
@@ -284,11 +284,11 @@ impl NES {
|
||||
}
|
||||
|
||||
pub fn power_cycle(&mut self) {
|
||||
// self.memory.clear();
|
||||
// self.ppu.reset();
|
||||
// self.ppu.memory.clear();
|
||||
let debug_log = self.debug_log().enabled();
|
||||
*self = Self::from_mem(self.mapped.power_cylce());
|
||||
// self.memory.power_cycle();
|
||||
if debug_log {
|
||||
self.debug_log_mut().enable();
|
||||
}
|
||||
self.reset();
|
||||
}
|
||||
|
||||
|
||||
125
src/mem.rs
125
src/mem.rs
@@ -1,8 +1,9 @@
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
use bitfield::bitfield;
|
||||
|
||||
use crate::{
|
||||
CPUMMRegisters, PPU, apu::APU, controllers::Controllers, cpu::DmaState,
|
||||
ppu::PPUMMRegisters,
|
||||
CPUMMRegisters, PPU, apu::APU, controllers::Controllers, cpu::DmaState, ppu::PPUMMRegisters,
|
||||
};
|
||||
|
||||
pub trait Memory {
|
||||
@@ -32,7 +33,7 @@ pub enum Data<R> {
|
||||
ROM(Arc<[u8]>),
|
||||
Mirror(u16),
|
||||
Reg(R),
|
||||
Disabled,
|
||||
// Disabled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -139,8 +140,7 @@ impl<R: Copy> MemoryMap<R> {
|
||||
self.read(pos + offset)
|
||||
// let s = &self.segments[*index];
|
||||
// self.read(s.position + offset % s.size)
|
||||
}
|
||||
Data::Disabled => todo!(),
|
||||
} // Data::Disabled => todo!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -167,8 +167,7 @@ impl<R: Copy> MemoryMap<R> {
|
||||
// let index = *index;
|
||||
// let s = &self.segments[index];
|
||||
// self.write(s.position + offset % s.size, val)
|
||||
}
|
||||
Data::Disabled => todo!(),
|
||||
} // Data::Disabled => todo!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -177,14 +176,14 @@ impl<R: Copy> MemoryMap<R> {
|
||||
// todo!("Open bus (${addr:04X})")
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
for s in &mut self.segments {
|
||||
match &mut s.mem {
|
||||
Data::RAM(items) => items.fill(0),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
// pub fn clear(&mut self) {
|
||||
// for s in &mut self.segments {
|
||||
// match &mut s.mem {
|
||||
// Data::RAM(items) => items.fill(0),
|
||||
// _ => (),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn power_cycle(&mut self) -> Self {
|
||||
for seg in &mut self.segments {
|
||||
@@ -217,8 +216,7 @@ impl<R: Copy> MemoryMap<R> {
|
||||
Data::Mirror(pos) => {
|
||||
let offset = addr - segment.position;
|
||||
self.peek_val(pos + offset)
|
||||
}
|
||||
Data::Disabled => Err(None),
|
||||
} // Data::Disabled => Err(None),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -244,13 +242,38 @@ impl<R: Copy> Memory for MemoryMap<R> {
|
||||
}
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
struct ShiftPair(u8);
|
||||
impl Debug;
|
||||
high, set_high: 7, 4;
|
||||
low, set_low: 3, 0;
|
||||
}
|
||||
|
||||
impl ShiftPair {
|
||||
fn low_count(&self) -> usize {
|
||||
if self.low() == 0 { 0 } else { 64 << self.low() }
|
||||
}
|
||||
fn high_count(&self) -> usize {
|
||||
if self.high() == 0 {
|
||||
0
|
||||
} else {
|
||||
64 << self.high()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
struct Mapper {
|
||||
is_nes_2: bool,
|
||||
horizontal_name_table: bool,
|
||||
mapper: u16,
|
||||
sub_mapper: u8,
|
||||
prg_rom_size: u8,
|
||||
chr_rom_size: u8,
|
||||
|
||||
prg_nv_ram_size: ShiftPair,
|
||||
chr_nv_ram_size: ShiftPair,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@@ -279,6 +302,8 @@ enum SegmentId {
|
||||
PrgBank1,
|
||||
ChrBank0,
|
||||
ChrBank1,
|
||||
|
||||
NVRam,
|
||||
}
|
||||
|
||||
impl fmt::Display for SegmentId {
|
||||
@@ -304,10 +329,11 @@ impl Mapped {
|
||||
// }
|
||||
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_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 {
|
||||
is_nes_2: true,
|
||||
horizontal_name_table: header[6] & (1 << 0) == 1,
|
||||
mapper: ((header[6] as u16 & 0xF0) >> 4)
|
||||
| ((header[7] as u16 & 0xF0) >> 0)
|
||||
@@ -315,18 +341,39 @@ impl Mapped {
|
||||
sub_mapper: (header[8] & 0xF0) >> 4,
|
||||
prg_rom_size: header[4],
|
||||
chr_rom_size: header[5],
|
||||
prg_nv_ram_size: ShiftPair(header[10]),
|
||||
chr_nv_ram_size: ShiftPair(header[11]),
|
||||
}
|
||||
} else {
|
||||
Mapper {
|
||||
is_nes_2: false,
|
||||
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],
|
||||
prg_nv_ram_size: ShiftPair(0),
|
||||
chr_nv_ram_size: ShiftPair(0),
|
||||
}
|
||||
};
|
||||
let (prg_rom, rom) = rom.split_at(mapper.prg_rom_size as usize * 0x4000);
|
||||
let (chr_rom, rom) = rom.split_at(mapper.chr_rom_size as usize * 0x2000);
|
||||
let (chr_rom, _rom) = rom.split_at(mapper.chr_rom_size as usize * 0x2000);
|
||||
println!("Mapper: {}/{}", mapper.mapper, mapper.sub_mapper);
|
||||
assert_eq!(
|
||||
mapper.prg_nv_ram_size.low_count(),
|
||||
0,
|
||||
"No support for PRG-RAM"
|
||||
);
|
||||
// assert_eq!(
|
||||
// mapper.chr_nv_ram_size.low_count(),
|
||||
// 0,
|
||||
// "No support for CHR-RAM"
|
||||
// );
|
||||
assert_eq!(
|
||||
mapper.chr_nv_ram_size.high_count(),
|
||||
0,
|
||||
"No support for CHR-NVRAM"
|
||||
);
|
||||
// assert_eq!(rom.len(), 0);
|
||||
// let prg_rom = &file[self.cpu_offset()..self.ppu_offset()];
|
||||
let mut cpu_segments = vec![
|
||||
@@ -340,6 +387,14 @@ impl Mapped {
|
||||
CPUMMRegisters::APU,
|
||||
),
|
||||
];
|
||||
// CHR-NVRAM
|
||||
if mapper.prg_nv_ram_size.high_count() > 0 {
|
||||
cpu_segments.push(Segment::ram(
|
||||
SegmentId::NVRam,
|
||||
0x6000,
|
||||
mapper.prg_nv_ram_size.high_count() as u16,
|
||||
));
|
||||
}
|
||||
let mut ppu_segments = vec![
|
||||
Segment::mirror(SegmentId::VramMirror, 0x3000, 0x0F00, 0x0000),
|
||||
Segment::reg(
|
||||
@@ -387,10 +442,22 @@ impl Mapped {
|
||||
0x8000 + (0x8000 - prg_rom.len() as u16),
|
||||
prg_rom,
|
||||
));
|
||||
if chr_rom.len() == 0 {
|
||||
ppu_segments.push(Segment::ram(SegmentId::PpuRam, 0, 0x2000));
|
||||
if mapper.is_nes_2 {
|
||||
if mapper.chr_nv_ram_size.low_count() > 0 {
|
||||
ppu_segments.push(Segment::ram(
|
||||
SegmentId::PpuRam,
|
||||
0,
|
||||
mapper.chr_nv_ram_size.low_count() as u16,
|
||||
));
|
||||
} else {
|
||||
ppu_segments.push(Segment::rom(SegmentId::PpuRom, 0, chr_rom));
|
||||
}
|
||||
} else {
|
||||
ppu_segments.push(Segment::rom(SegmentId::PpuRom, 0, chr_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 {
|
||||
@@ -550,7 +617,7 @@ impl<'a> CpuMem<'a> {
|
||||
*shift_reg = 0;
|
||||
*count = 0;
|
||||
} else if *count == 4 {
|
||||
let val = (*shift_reg << 1) | (val & 0x01);
|
||||
let val = (*shift_reg >> 1) | ((val & 0x01) << 4);
|
||||
if (addr & 0x6000) >> 13 == 0 {
|
||||
// TODO: fix mem layout if it's changed
|
||||
if val & 0b01100 == 0b01000 {
|
||||
@@ -591,6 +658,7 @@ impl<'a> CpuMem<'a> {
|
||||
}
|
||||
} else if (addr & 0x6000) >> 13 == 3 {
|
||||
*cur_prg_bank = (val & 0x0F) as usize;
|
||||
println!("Updating ROM: new {:X}", val);
|
||||
match mode {
|
||||
MMC1Mode::Full => {
|
||||
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||
@@ -600,11 +668,13 @@ impl<'a> CpuMem<'a> {
|
||||
}
|
||||
MMC1Mode::FirstFixed => {
|
||||
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
||||
bank1.swap_rom(prg_banks[(val & 0x0F) as usize].clone());
|
||||
// Highest bit is ignored
|
||||
bank1.swap_rom(prg_banks[(val & 0x07) as usize].clone());
|
||||
}
|
||||
MMC1Mode::LastFixed => {
|
||||
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||
bank0.swap_rom(prg_banks[(val & 0x0F) as usize].clone());
|
||||
// Highest bit is ignored
|
||||
bank0.swap_rom(prg_banks[(val & 0x07) as usize].clone());
|
||||
}
|
||||
}
|
||||
// TODO: handle MSB, changes some stuff...
|
||||
@@ -614,8 +684,9 @@ impl<'a> CpuMem<'a> {
|
||||
*shift_reg = 0;
|
||||
*count = 0;
|
||||
} else {
|
||||
*shift_reg = (*shift_reg << 1) | (val & 0x01);
|
||||
*shift_reg = (*shift_reg >> 1) | ((val & 0x01) << 4);
|
||||
*count += 1;
|
||||
println!("Mapper SHR {:05b}", *shift_reg);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -107,7 +107,7 @@ enum OamState {
|
||||
ReadTile,
|
||||
ReadAttrs,
|
||||
ReadX,
|
||||
OverflowScan,
|
||||
// OverflowScan,
|
||||
Wait,
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ impl OAM {
|
||||
self.state = OamState::Wait; // Should be Overflow scan...
|
||||
}
|
||||
}
|
||||
OamState::OverflowScan => todo!(),
|
||||
// OamState::OverflowScan => todo!(),
|
||||
OamState::Wait => (),
|
||||
}
|
||||
}
|
||||
|
||||
33
src/test_roms/apu_pulse_1_test.s
Normal file
33
src/test_roms/apu_pulse_1_test.s
Normal file
@@ -0,0 +1,33 @@
|
||||
.include "testing.s"
|
||||
|
||||
zp_res TIMER_LOW
|
||||
zp_res SWEEP
|
||||
zp_res DLCV
|
||||
; zp_res TIMER_LOW
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
lda #$01
|
||||
sta SNDCHN
|
||||
lda #$BF ; Duty 2, LC halted, evelope enabled, volume = F
|
||||
sta PULSE_CH1_DLCV
|
||||
sta DLCV
|
||||
lda #$7F ;
|
||||
sta PULSE_CH1_SWEEP
|
||||
sta SWEEP
|
||||
lda #$6F
|
||||
sta PULSE_CH1_TLOW
|
||||
sta TIMER_LOW
|
||||
lda #$F0
|
||||
sta PULSE_CH1_LCTH
|
||||
brk
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
irq:
|
||||
nmi:
|
||||
rti
|
||||
@@ -10,28 +10,28 @@ macro_rules! rom_test {
|
||||
($name:ident, $rom:literal, timeout = $timeout:expr, |$nes:ident| $eval:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let rom_file = concat!(env!("ROM_DIR"), "/", $rom);
|
||||
println!("{}: {}", stringify!($name), rom_file);
|
||||
let mut $nes = crate::NES::load_nes_file(rom_file).expect("Failed to create nes object");
|
||||
$nes.reset_and_run_with_timeout($timeout);
|
||||
println!("Final: {:?}", $nes);
|
||||
$eval
|
||||
}
|
||||
};
|
||||
($name:ident, . $rom:literal, |$nes:ident| $eval:expr) => {
|
||||
rom_test!($name, . $rom, timeout = 10000000, |$nes| $eval);
|
||||
};
|
||||
($name:ident, . $rom:literal, timeout = $timeout:expr, |$nes:ident| $eval:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let rom_file = $rom;
|
||||
println!("{}: {}", stringify!($name), rom_file);
|
||||
let mut $nes = crate::NES::load_nes_file(rom_file).expect("Failed to create nes object");
|
||||
let rom_file = include_bytes!(concat!(env!("ROM_DIR"), "/", $rom));
|
||||
println!("{}: {}", stringify!($name), concat!(env!("ROM_DIR"), "/", $rom));
|
||||
let mut $nes = crate::NES::load_nes_file_mem(rom_file).expect("Failed to create nes object");
|
||||
$nes.reset_and_run_with_timeout($timeout);
|
||||
println!("Final: {:?}", $nes);
|
||||
$eval
|
||||
}
|
||||
};
|
||||
// ($name:ident, . $rom:literal, |$nes:ident| $eval:expr) => {
|
||||
// rom_test!($name, . $rom, timeout = 10000000, |$nes| $eval);
|
||||
// };
|
||||
// ($name:ident, . $rom:literal, timeout = $timeout:expr, |$nes:ident| $eval:expr) => {
|
||||
// #[test]
|
||||
// fn $name() {
|
||||
// let rom_file = $rom;
|
||||
// println!("{}: {}", stringify!($name), rom_file);
|
||||
// let mut $nes = crate::NES::load_nes_file(rom_file).expect("Failed to create nes object");
|
||||
// $nes.reset_and_run_with_timeout($timeout);
|
||||
// println!("Final: {:?}", $nes);
|
||||
// $eval
|
||||
// }
|
||||
// };
|
||||
}
|
||||
|
||||
pub(crate) use rom_test;
|
||||
|
||||
Reference in New Issue
Block a user