Compare commits
3 Commits
e882b3b325
...
f84009aeb9
| Author | SHA1 | Date | |
|---|---|---|---|
|
f84009aeb9
|
|||
|
875e493cd2
|
|||
|
d140f1e122
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@
|
|||||||
*.nes
|
*.nes
|
||||||
*.zip
|
*.zip
|
||||||
*.dmp
|
*.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 {
|
macro_rules! lut {
|
||||||
($name:ident: [$ty:ty; $len:expr] = |$n:ident| $expr:expr) => {
|
($name:ident: [$ty:ty; $len:expr] = |$n:ident| $expr:expr) => {
|
||||||
const $name: [$ty; $len] = {
|
const $name: [$ty; $len] = {
|
||||||
@@ -264,7 +260,7 @@ Output: {16:>4} ${16:02X}
|
|||||||
self.sweep.period(),
|
self.sweep.period(),
|
||||||
self.sweep.enable(),
|
self.sweep.enable(),
|
||||||
self.period,
|
self.period,
|
||||||
0,
|
self.counter.current,
|
||||||
self.enabled,
|
self.enabled,
|
||||||
self.period_timer,
|
self.period_timer,
|
||||||
self.cur, // ?
|
self.cur, // ?
|
||||||
@@ -423,6 +419,7 @@ bitfield::bitfield! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[allow(dead_code)]
|
||||||
struct NoiseChannel {
|
struct NoiseChannel {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
evelope: NoiseEnvelope,
|
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 {
|
match offset {
|
||||||
0x00 => (),
|
0x00 => (),
|
||||||
0x01 => (),
|
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 {
|
match offset {
|
||||||
0x00 => (),
|
0x00 => (),
|
||||||
0x01 => (),
|
0x01 => (),
|
||||||
@@ -553,12 +550,21 @@ pub struct APU {
|
|||||||
|
|
||||||
impl std::fmt::Debug for APU {
|
impl std::fmt::Debug for APU {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
// write!(
|
write!(
|
||||||
// f,
|
f,
|
||||||
// "PPU: f {}, s {}, p {}",
|
"APU
|
||||||
// self.frame_count, self.scanline, self.pixel
|
{:#?}
|
||||||
// )
|
{:#?}
|
||||||
Ok(())
|
{:#?}
|
||||||
|
{:#?}
|
||||||
|
{:#?}
|
||||||
|
",
|
||||||
|
self.pulse_1,
|
||||||
|
self.pulse_2,
|
||||||
|
self.triangle,
|
||||||
|
self.noise,
|
||||||
|
self.dmc,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,7 +583,7 @@ impl APU {
|
|||||||
irq: false,
|
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::{
|
use std::sync::{
|
||||||
sync::{
|
atomic::AtomicBool, Arc
|
||||||
atomic::AtomicBool, mpsc::{channel, Sender, TryRecvError}, Arc
|
};
|
||||||
},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cpal::{
|
use cpal::{
|
||||||
Device, FrameCount, Host, SampleFormat, Stream,
|
Device, FrameCount, Host, Stream,
|
||||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||||
};
|
};
|
||||||
use ringbuf::{
|
use ringbuf::{
|
||||||
|
|||||||
@@ -70,13 +70,13 @@ where
|
|||||||
{
|
{
|
||||||
text(format!("{val:02X}"))
|
text(format!("{val:02X}"))
|
||||||
}
|
}
|
||||||
pub fn bin8<'a, Theme, Renderer>(val: u8) -> Text<'a, Theme, Renderer>
|
// pub fn bin8<'a, Theme, Renderer>(val: u8) -> Text<'a, Theme, Renderer>
|
||||||
where
|
// where
|
||||||
Theme: text::Catalog + 'a,
|
// Theme: text::Catalog + 'a,
|
||||||
Renderer: iced::advanced::text::Renderer,
|
// Renderer: iced::advanced::text::Renderer,
|
||||||
{
|
// {
|
||||||
text(format!("{val:08b}"))
|
// text(format!("{val:08b}"))
|
||||||
}
|
// }
|
||||||
pub fn bin32<'a, Theme, Renderer>(val: u32) -> Text<'a, Theme, Renderer>
|
pub fn bin32<'a, Theme, Renderer>(val: u32) -> Text<'a, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Theme: text::Catalog + 'a,
|
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> {
|
pub enum DbgImage<'a> {
|
||||||
NameTable(&'a Mapped, &'a PPU),
|
NameTable(&'a Mapped, &'a PPU),
|
||||||
PatternTable(&'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<'_> {
|
impl<Message, T> Program<Message, T> for DbgImage<'_> {
|
||||||
@@ -432,7 +432,7 @@ impl<Message, T> Program<Message, T> for DbgImage<'_> {
|
|||||||
DbgImage::PatternTable(mem, ppu) => {
|
DbgImage::PatternTable(mem, ppu) => {
|
||||||
ppu.render_pattern_tables(mem, &mut name_table_frame)
|
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()]
|
vec![name_table_frame.into_geometry()]
|
||||||
}
|
}
|
||||||
@@ -443,21 +443,21 @@ impl DbgImage<'_> {
|
|||||||
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(mem, ppu) => ppu.name_cursor_info(mem, 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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
use std::{
|
use std::fmt::{self, Display};
|
||||||
fmt::{self, Display}, ops::Deref
|
|
||||||
};
|
|
||||||
|
|
||||||
use iced::{
|
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::Memory;
|
||||||
|
|
||||||
use nes_emu::{PPU, Mapped, PPUMMRegisters};
|
use nes_emu::{Mapped, PPU, PPUMMRegisters};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
struct Cpu<'a>(&'a Mapped);
|
struct Cpu<'a>(&'a Mapped);
|
||||||
@@ -82,53 +82,53 @@ 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 {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if let Some(val) = self.0 {
|
// if let Some(val) = self.0 {
|
||||||
write!(f, "{:02X}", val)
|
// write!(f, "{:02X}", val)
|
||||||
} else {
|
// } else {
|
||||||
write!(f, "XX")
|
// write!(f, "XX")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl HexView {
|
impl HexView {
|
||||||
pub fn new() -> Self {
|
// pub fn new() -> Self {
|
||||||
Self {}
|
// Self {}
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn render_any<'a, M: Memory + Copy + 'a>(&self, mem: M) -> Element<'a, HexEvent> {
|
// pub fn render_any<'a, M: Memory + Copy + 'a>(&self, mem: M) -> Element<'a, HexEvent> {
|
||||||
struct Row<M: Memory>(usize, M);
|
// struct Row<M: Memory>(usize, M);
|
||||||
impl<'a, M: Memory + 'a> 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 {
|
||||||
write!(f, " {}", Val(self.1.peek(self.0 + i)))?;
|
// write!(f, " {}", Val(self.1.peek(self.0 + i)))?;
|
||||||
}
|
// }
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
column![
|
// column![
|
||||||
text!("Hex view"),
|
// text!("Hex view"),
|
||||||
iced::widget::scrollable(lazy(mem.edit_ver(), move |_| column(
|
// 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")
|
// text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F")
|
||||||
.font(Font::MONOSPACE)
|
// .font(Font::MONOSPACE)
|
||||||
.into()
|
// .into()
|
||||||
]
|
// ]
|
||||||
.into_iter()
|
// .into_iter()
|
||||||
.chain((0..mem.len()).step_by(16).map(|off| {
|
// .chain((0..mem.len()).step_by(16).map(|off| {
|
||||||
text!(" {off:04X} | {}", Row(off, mem))
|
// text!(" {off:04X} | {}", Row(off, mem))
|
||||||
.font(Font::MONOSPACE)
|
// .font(Font::MONOSPACE)
|
||||||
.into()
|
// .into()
|
||||||
}))
|
// }))
|
||||||
)))
|
// )))
|
||||||
.width(Fill),
|
// .width(Fill),
|
||||||
]
|
// ]
|
||||||
.width(Fill)
|
// .width(Fill)
|
||||||
.into()
|
// .into()
|
||||||
}
|
// }
|
||||||
pub fn render_cpu<'a>(&self, mem: &'a Mapped) -> Element<'a, HexEvent> {
|
pub fn render_cpu<'a>(&self, mem: &'a Mapped) -> Element<'a, HexEvent> {
|
||||||
// self.render_any(Cpu(mem))
|
// self.render_any(Cpu(mem))
|
||||||
Element::new(
|
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!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BufferSlice<'a>(pub &'a [u8]);
|
// pub struct BufferSlice<'a>(pub &'a [u8]);
|
||||||
|
|
||||||
impl Deref for BufferSlice<'_> {
|
// impl Deref for BufferSlice<'_> {
|
||||||
type Target = [u8];
|
// type Target = [u8];
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
// fn deref(&self) -> &Self::Target {
|
||||||
self.0
|
// self.0
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub trait Buffer {
|
pub trait Buffer {
|
||||||
fn peek(&self, val: usize) -> Option<u8>;
|
fn peek(&self, val: usize) -> Option<u8>;
|
||||||
fn len(&self) -> usize;
|
fn len(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buffer for BufferSlice<'_> {
|
// impl Buffer for BufferSlice<'_> {
|
||||||
fn peek(&self, val: usize) -> Option<u8> {
|
// fn peek(&self, val: usize) -> Option<u8> {
|
||||||
self.get(val).copied()
|
// self.get(val).copied()
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
// fn len(&self) -> usize {
|
||||||
self.iter().len()
|
// self.iter().len()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
impl<M: Memory> Buffer for M {
|
impl<M: Memory> Buffer for M {
|
||||||
fn peek(&self, val: usize) -> Option<u8> {
|
fn peek(&self, val: usize) -> Option<u8> {
|
||||||
self.peek(val)
|
self.peek(val)
|
||||||
@@ -202,6 +202,7 @@ pub struct HexEdit {
|
|||||||
new_value: u8,
|
new_value: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct HexEditor<B, M, R: Renderer> {
|
pub struct HexEditor<B, M, R: Renderer> {
|
||||||
val: B,
|
val: B,
|
||||||
font_size: Option<Pixels>,
|
font_size: Option<Pixels>,
|
||||||
@@ -279,6 +280,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
#[allow(dead_code)]
|
||||||
struct HexEditorState {
|
struct HexEditorState {
|
||||||
offset_y: f32,
|
offset_y: f32,
|
||||||
selected: usize,
|
selected: usize,
|
||||||
@@ -311,10 +313,10 @@ where
|
|||||||
&self,
|
&self,
|
||||||
tree: &Tree,
|
tree: &Tree,
|
||||||
renderer: &mut R,
|
renderer: &mut R,
|
||||||
theme: &T,
|
_theme: &T,
|
||||||
style: &Style,
|
_style: &Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: Cursor,
|
_cursor: Cursor,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let state: &HexEditorState = tree.state.downcast_ref();
|
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"), "/", "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.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_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 = concat!(env!("ROM_DIR"), "/", "apu_triangle.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";
|
// 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;
|
extern crate nes_emu;
|
||||||
|
|
||||||
@@ -453,7 +454,7 @@ impl Emulator {
|
|||||||
dbg_image(DbgImage::PatternTable(self.nes.mem(), self.nes.ppu())).into()
|
dbg_image(DbgImage::PatternTable(self.nes.mem(), self.nes.ppu())).into()
|
||||||
}
|
}
|
||||||
Some(WindowType::Palette) => {
|
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![
|
Some(WindowType::Debugger) => column![
|
||||||
row![
|
row![
|
||||||
|
|||||||
@@ -1729,7 +1729,12 @@ impl Cpu {
|
|||||||
0xEA => inst!("NOP", 1, || {
|
0xEA => inst!("NOP", 1, || {
|
||||||
log!("{addr:04X}: NOP");
|
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<'_>) {
|
pub fn reset(&mut self, mem: &mut CpuMem<'_>) {
|
||||||
|
let debug_log = self.debug_log.enabled();
|
||||||
*self = Self::init();
|
*self = Self::init();
|
||||||
|
if debug_log { self.debug_log.enable(); }
|
||||||
self.pc = u16::from_le_bytes([mem.read(0xFFFC), mem.read(0xFFFD)]);
|
self.pc = u16::from_le_bytes([mem.read(0xFFFC), mem.read(0xFFFD)]);
|
||||||
self.sp = 0xFD;
|
self.sp = 0xFD;
|
||||||
self.status.set_interrupt_disable(true);
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DebugLog {
|
pub struct DebugLog {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
current: String,
|
current: String,
|
||||||
history: Vec<String>,
|
history: Vec<String>,
|
||||||
max_history: Option<NonZeroUsize>,
|
// max_history: Option<NonZeroUsize>,
|
||||||
pos: usize,
|
// pos: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebugLog {
|
impl DebugLog {
|
||||||
@@ -15,8 +15,8 @@ impl DebugLog {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
current: String::new(),
|
current: String::new(),
|
||||||
history: vec![],
|
history: vec![],
|
||||||
max_history: None,
|
// max_history: None,
|
||||||
pos: 0,
|
// pos: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +53,10 @@ impl DebugLog {
|
|||||||
pub fn enable(&mut self) {
|
pub fn enable(&mut self) {
|
||||||
self.enabled = true;
|
self.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn enabled(&self) -> bool {
|
||||||
|
self.enabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Write for DebugLog {
|
impl std::fmt::Write for DebugLog {
|
||||||
|
|||||||
32
src/lib.rs
32
src/lib.rs
@@ -9,10 +9,10 @@ mod mem;
|
|||||||
mod ppu;
|
mod ppu;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_roms;
|
mod test_roms;
|
||||||
pub use mem::{Memory, Mapped};
|
pub use mem::{Mapped, Memory};
|
||||||
|
|
||||||
pub use ppu::{Color, PPU, RenderBuffer, PPUMMRegisters};
|
pub use cpu::{CPUMMRegisters, Cpu, CycleResult};
|
||||||
pub use cpu::{Cpu, CycleResult, CPUMMRegisters};
|
pub use ppu::{Color, PPU, PPUMMRegisters, RenderBuffer};
|
||||||
// #[cfg(not(target_arch = "wasm32"))]
|
// #[cfg(not(target_arch = "wasm32"))]
|
||||||
// use tokio::io::AsyncReadExt as _;
|
// use tokio::io::AsyncReadExt as _;
|
||||||
|
|
||||||
@@ -260,16 +260,16 @@ impl NES {
|
|||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.clock_count = 0;
|
self.clock_count = 0;
|
||||||
// dbg_int: false,
|
// dbg_int: false,
|
||||||
|
|
||||||
// clock_count: 0,
|
// clock_count: 0,
|
||||||
// mapped,
|
// mapped,
|
||||||
// cpu: Cpu::init(),
|
// cpu: Cpu::init(),
|
||||||
// dma: DmaState::Passive,
|
// dma: DmaState::Passive,
|
||||||
// // ppu: PPU::with_chr_rom(chr_rom, mapper),
|
// // ppu: PPU::with_chr_rom(chr_rom, mapper),
|
||||||
// ppu: PPU::init(),
|
// ppu: PPU::init(),
|
||||||
// apu: APU::init(),
|
// apu: APU::init(),
|
||||||
// controller: Controllers::init(),
|
// controller: Controllers::init(),
|
||||||
self.cpu.reset(&mut CpuMem::new(
|
self.cpu.reset(&mut CpuMem::new(
|
||||||
&mut self.mapped,
|
&mut self.mapped,
|
||||||
&mut self.ppu,
|
&mut self.ppu,
|
||||||
@@ -284,11 +284,11 @@ impl NES {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn power_cycle(&mut self) {
|
pub fn power_cycle(&mut self) {
|
||||||
// self.memory.clear();
|
let debug_log = self.debug_log().enabled();
|
||||||
// self.ppu.reset();
|
|
||||||
// self.ppu.memory.clear();
|
|
||||||
*self = Self::from_mem(self.mapped.power_cylce());
|
*self = Self::from_mem(self.mapped.power_cylce());
|
||||||
// self.memory.power_cycle();
|
if debug_log {
|
||||||
|
self.debug_log_mut().enable();
|
||||||
|
}
|
||||||
self.reset();
|
self.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
125
src/mem.rs
125
src/mem.rs
@@ -1,8 +1,9 @@
|
|||||||
use std::{fmt, sync::Arc};
|
use std::{fmt, sync::Arc};
|
||||||
|
|
||||||
|
use bitfield::bitfield;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
CPUMMRegisters, PPU, apu::APU, controllers::Controllers, cpu::DmaState,
|
CPUMMRegisters, PPU, apu::APU, controllers::Controllers, cpu::DmaState, ppu::PPUMMRegisters,
|
||||||
ppu::PPUMMRegisters,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait Memory {
|
pub trait Memory {
|
||||||
@@ -32,7 +33,7 @@ pub enum Data<R> {
|
|||||||
ROM(Arc<[u8]>),
|
ROM(Arc<[u8]>),
|
||||||
Mirror(u16),
|
Mirror(u16),
|
||||||
Reg(R),
|
Reg(R),
|
||||||
Disabled,
|
// Disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -139,8 +140,7 @@ impl<R: Copy> MemoryMap<R> {
|
|||||||
self.read(pos + offset)
|
self.read(pos + offset)
|
||||||
// let s = &self.segments[*index];
|
// let s = &self.segments[*index];
|
||||||
// self.read(s.position + offset % s.size)
|
// 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 index = *index;
|
||||||
// let s = &self.segments[index];
|
// let s = &self.segments[index];
|
||||||
// self.write(s.position + offset % s.size, val)
|
// 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})")
|
// todo!("Open bus (${addr:04X})")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
// pub fn clear(&mut self) {
|
||||||
for s in &mut self.segments {
|
// for s in &mut self.segments {
|
||||||
match &mut s.mem {
|
// match &mut s.mem {
|
||||||
Data::RAM(items) => items.fill(0),
|
// Data::RAM(items) => items.fill(0),
|
||||||
_ => (),
|
// _ => (),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn power_cycle(&mut self) -> Self {
|
pub fn power_cycle(&mut self) -> Self {
|
||||||
for seg in &mut self.segments {
|
for seg in &mut self.segments {
|
||||||
@@ -217,8 +216,7 @@ impl<R: Copy> MemoryMap<R> {
|
|||||||
Data::Mirror(pos) => {
|
Data::Mirror(pos) => {
|
||||||
let offset = addr - segment.position;
|
let offset = addr - segment.position;
|
||||||
self.peek_val(pos + offset)
|
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)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
struct Mapper {
|
struct Mapper {
|
||||||
|
is_nes_2: bool,
|
||||||
horizontal_name_table: bool,
|
horizontal_name_table: bool,
|
||||||
mapper: u16,
|
mapper: u16,
|
||||||
sub_mapper: u8,
|
sub_mapper: u8,
|
||||||
prg_rom_size: u8,
|
prg_rom_size: u8,
|
||||||
chr_rom_size: u8,
|
chr_rom_size: u8,
|
||||||
|
|
||||||
|
prg_nv_ram_size: ShiftPair,
|
||||||
|
chr_nv_ram_size: ShiftPair,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
@@ -279,6 +302,8 @@ enum SegmentId {
|
|||||||
PrgBank1,
|
PrgBank1,
|
||||||
ChrBank0,
|
ChrBank0,
|
||||||
ChrBank1,
|
ChrBank1,
|
||||||
|
|
||||||
|
NVRam,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SegmentId {
|
impl fmt::Display for SegmentId {
|
||||||
@@ -304,10 +329,11 @@ impl Mapped {
|
|||||||
// }
|
// }
|
||||||
let mapper = if nes_20 {
|
let mapper = if nes_20 {
|
||||||
assert_eq!(header[9], 0, "No support for larger PRG/CHR roms");
|
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[10], 0, "No support for PRG-RAM/EEPROM");
|
||||||
assert_eq!(header[11], 0, "No support for CHR-RAM");
|
// assert_eq!(header[11], 0, "No support for CHR-RAM");
|
||||||
assert!(header[12] == 0 || header[12] == 2, "Only support NTSC NES");
|
assert!(header[12] == 0 || header[12] == 2, "Only support NTSC NES");
|
||||||
Mapper {
|
Mapper {
|
||||||
|
is_nes_2: true,
|
||||||
horizontal_name_table: header[6] & (1 << 0) == 1,
|
horizontal_name_table: header[6] & (1 << 0) == 1,
|
||||||
mapper: ((header[6] as u16 & 0xF0) >> 4)
|
mapper: ((header[6] as u16 & 0xF0) >> 4)
|
||||||
| ((header[7] as u16 & 0xF0) >> 0)
|
| ((header[7] as u16 & 0xF0) >> 0)
|
||||||
@@ -315,18 +341,39 @@ impl Mapped {
|
|||||||
sub_mapper: (header[8] & 0xF0) >> 4,
|
sub_mapper: (header[8] & 0xF0) >> 4,
|
||||||
prg_rom_size: header[4],
|
prg_rom_size: header[4],
|
||||||
chr_rom_size: header[5],
|
chr_rom_size: header[5],
|
||||||
|
prg_nv_ram_size: ShiftPair(header[10]),
|
||||||
|
chr_nv_ram_size: ShiftPair(header[11]),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Mapper {
|
Mapper {
|
||||||
|
is_nes_2: false,
|
||||||
horizontal_name_table: header[6] & (1 << 0) == 1,
|
horizontal_name_table: header[6] & (1 << 0) == 1,
|
||||||
mapper: ((header[6] as u16 & 0xF0) >> 4) | ((header[7] as u16 & 0xF0) >> 0),
|
mapper: ((header[6] as u16 & 0xF0) >> 4) | ((header[7] as u16 & 0xF0) >> 0),
|
||||||
sub_mapper: 0,
|
sub_mapper: 0,
|
||||||
prg_rom_size: header[4],
|
prg_rom_size: header[4],
|
||||||
chr_rom_size: header[5],
|
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 (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);
|
// assert_eq!(rom.len(), 0);
|
||||||
// let prg_rom = &file[self.cpu_offset()..self.ppu_offset()];
|
// let prg_rom = &file[self.cpu_offset()..self.ppu_offset()];
|
||||||
let mut cpu_segments = vec![
|
let mut cpu_segments = vec![
|
||||||
@@ -340,6 +387,14 @@ impl Mapped {
|
|||||||
CPUMMRegisters::APU,
|
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![
|
let mut ppu_segments = vec![
|
||||||
Segment::mirror(SegmentId::VramMirror, 0x3000, 0x0F00, 0x0000),
|
Segment::mirror(SegmentId::VramMirror, 0x3000, 0x0F00, 0x0000),
|
||||||
Segment::reg(
|
Segment::reg(
|
||||||
@@ -387,10 +442,22 @@ impl Mapped {
|
|||||||
0x8000 + (0x8000 - prg_rom.len() as u16),
|
0x8000 + (0x8000 - prg_rom.len() as u16),
|
||||||
prg_rom,
|
prg_rom,
|
||||||
));
|
));
|
||||||
if chr_rom.len() == 0 {
|
if mapper.is_nes_2 {
|
||||||
ppu_segments.push(Segment::ram(SegmentId::PpuRam, 0, 0x2000));
|
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 {
|
} 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
|
Remapper::None
|
||||||
} else if mapper.mapper == 1 && mapper.sub_mapper == 0 {
|
} else if mapper.mapper == 1 && mapper.sub_mapper == 0 {
|
||||||
@@ -550,7 +617,7 @@ impl<'a> CpuMem<'a> {
|
|||||||
*shift_reg = 0;
|
*shift_reg = 0;
|
||||||
*count = 0;
|
*count = 0;
|
||||||
} else if *count == 4 {
|
} else if *count == 4 {
|
||||||
let val = (*shift_reg << 1) | (val & 0x01);
|
let val = (*shift_reg >> 1) | ((val & 0x01) << 4);
|
||||||
if (addr & 0x6000) >> 13 == 0 {
|
if (addr & 0x6000) >> 13 == 0 {
|
||||||
// TODO: fix mem layout if it's changed
|
// TODO: fix mem layout if it's changed
|
||||||
if val & 0b01100 == 0b01000 {
|
if val & 0b01100 == 0b01000 {
|
||||||
@@ -591,6 +658,7 @@ impl<'a> CpuMem<'a> {
|
|||||||
}
|
}
|
||||||
} else if (addr & 0x6000) >> 13 == 3 {
|
} else if (addr & 0x6000) >> 13 == 3 {
|
||||||
*cur_prg_bank = (val & 0x0F) as usize;
|
*cur_prg_bank = (val & 0x0F) as usize;
|
||||||
|
println!("Updating ROM: new {:X}", val);
|
||||||
match mode {
|
match mode {
|
||||||
MMC1Mode::Full => {
|
MMC1Mode::Full => {
|
||||||
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||||
@@ -600,11 +668,13 @@ impl<'a> CpuMem<'a> {
|
|||||||
}
|
}
|
||||||
MMC1Mode::FirstFixed => {
|
MMC1Mode::FirstFixed => {
|
||||||
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
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 => {
|
MMC1Mode::LastFixed => {
|
||||||
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
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...
|
// TODO: handle MSB, changes some stuff...
|
||||||
@@ -614,8 +684,9 @@ impl<'a> CpuMem<'a> {
|
|||||||
*shift_reg = 0;
|
*shift_reg = 0;
|
||||||
*count = 0;
|
*count = 0;
|
||||||
} else {
|
} else {
|
||||||
*shift_reg = (*shift_reg << 1) | (val & 0x01);
|
*shift_reg = (*shift_reg >> 1) | ((val & 0x01) << 4);
|
||||||
*count += 1;
|
*count += 1;
|
||||||
|
println!("Mapper SHR {:05b}", *shift_reg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ enum OamState {
|
|||||||
ReadTile,
|
ReadTile,
|
||||||
ReadAttrs,
|
ReadAttrs,
|
||||||
ReadX,
|
ReadX,
|
||||||
OverflowScan,
|
// OverflowScan,
|
||||||
Wait,
|
Wait,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ impl OAM {
|
|||||||
self.state = OamState::Wait; // Should be Overflow scan...
|
self.state = OamState::Wait; // Should be Overflow scan...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OamState::OverflowScan => todo!(),
|
// OamState::OverflowScan => todo!(),
|
||||||
OamState::Wait => (),
|
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) => {
|
($name:ident, $rom:literal, timeout = $timeout:expr, |$nes:ident| $eval:expr) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let rom_file = concat!(env!("ROM_DIR"), "/", $rom);
|
let rom_file = include_bytes!(concat!(env!("ROM_DIR"), "/", $rom));
|
||||||
println!("{}: {}", stringify!($name), rom_file);
|
println!("{}: {}", stringify!($name), concat!(env!("ROM_DIR"), "/", $rom));
|
||||||
let mut $nes = crate::NES::load_nes_file(rom_file).expect("Failed to create nes object");
|
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);
|
$nes.reset_and_run_with_timeout($timeout);
|
||||||
println!("Final: {:?}", $nes);
|
println!("Final: {:?}", $nes);
|
||||||
$eval
|
$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;
|
pub(crate) use rom_test;
|
||||||
|
|||||||
Reference in New Issue
Block a user