Major work
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 10s
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 10s
This commit is contained in:
44
src/apu.rs
44
src/apu.rs
@@ -46,13 +46,20 @@ struct DeltaChannel {
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
struct FrameCounter {
|
||||
count: usize,
|
||||
mode_5_step: bool,
|
||||
interrupt_enabled: bool,
|
||||
irq: bool,
|
||||
}
|
||||
|
||||
pub struct APU {
|
||||
pulse_1: PulseChannel,
|
||||
pulse_2: PulseChannel,
|
||||
triangle: TriangleChannel,
|
||||
noise: NoiseChannel,
|
||||
dmc: DeltaChannel,
|
||||
frame_counter: u8,
|
||||
frame_counter: FrameCounter,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for APU {
|
||||
@@ -74,7 +81,12 @@ impl APU {
|
||||
triangle: TriangleChannel { enabled: false },
|
||||
noise: NoiseChannel { enabled: false },
|
||||
dmc: DeltaChannel { enabled: false },
|
||||
frame_counter: 0,
|
||||
frame_counter: FrameCounter {
|
||||
mode_5_step: false,
|
||||
interrupt_enabled: true,
|
||||
count: 0,
|
||||
irq: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn read_reg(&mut self, offset: u16) -> u8 {
|
||||
@@ -98,11 +110,28 @@ impl APU {
|
||||
0x11 => {
|
||||
// TODO: load dmc counter with (val & 7F)
|
||||
}
|
||||
_ => panic!("No register at {:X}", offset),
|
||||
_ => (),
|
||||
// _ => panic!("No register at {:X}", offset),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_one_clock_cycle(&mut self) -> bool {
|
||||
pub fn run_one_clock_cycle(&mut self, ppu_cycle: usize) -> bool {
|
||||
if ppu_cycle % 6 == 1 {
|
||||
// APU Frame Counter clock cycle
|
||||
if !self.frame_counter.mode_5_step
|
||||
&& self.frame_counter.interrupt_enabled
|
||||
&& self.frame_counter.count == 14914
|
||||
{
|
||||
self.frame_counter.irq = true;
|
||||
} else if !self.frame_counter.mode_5_step
|
||||
&& self.frame_counter.interrupt_enabled
|
||||
&& self.frame_counter.count == 14915
|
||||
{
|
||||
self.frame_counter.irq = true;
|
||||
self.frame_counter.count = 0;
|
||||
}
|
||||
self.frame_counter.count += 1;
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn peek_nmi(&self) -> bool {
|
||||
@@ -112,10 +141,11 @@ impl APU {
|
||||
false
|
||||
}
|
||||
pub fn peek_irq(&self) -> bool {
|
||||
false
|
||||
self.frame_counter.irq
|
||||
}
|
||||
pub fn irq_waiting(&mut self) -> bool {
|
||||
// TODO: implement logic
|
||||
false
|
||||
let res = self.frame_counter.irq;
|
||||
self.frame_counter.irq = false;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ impl DebugLog {
|
||||
pub fn history(&self) -> &[String] {
|
||||
&self.history[self.history.len().saturating_sub(100)..]
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<String> {
|
||||
self.history.pop()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Write for DebugLog {
|
||||
|
||||
378
src/debugger.rs
378
src/debugger.rs
@@ -1,13 +1,16 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use iced::{
|
||||
Element,
|
||||
Length::Fill,
|
||||
widget::{
|
||||
self, button, checkbox, column, container::bordered_box, image, number_input, row,
|
||||
scrollable, text,
|
||||
},
|
||||
advanced::{
|
||||
layout::Node, widget::{
|
||||
tree::{State, Tag}, Tree
|
||||
}, Widget
|
||||
}, mouse, widget::{
|
||||
self, button, canvas::{Frame, Program}, checkbox, column, container::bordered_box, image, number_input, row, rule::horizontal, scrollable, text, Canvas, Text
|
||||
}, window::Event, Element, Length::{self, Fill}, Point, Renderer, Size, Theme
|
||||
};
|
||||
|
||||
use crate::{CycleResult, NES};
|
||||
use crate::{CycleResult, NES, PPU};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DebuggerState {
|
||||
@@ -37,6 +40,35 @@ pub enum DebuggerMessage {
|
||||
RunFrames,
|
||||
}
|
||||
|
||||
pub fn hex16<'a, Theme, Renderer>(val: u16) -> Text<'a, Theme, Renderer>
|
||||
where
|
||||
Theme: text::Catalog + 'a,
|
||||
Renderer: iced::advanced::text::Renderer,
|
||||
{
|
||||
text(format!("{val:04X}"))
|
||||
}
|
||||
pub fn hex8<'a, Theme, Renderer>(val: u8) -> Text<'a, Theme, Renderer>
|
||||
where
|
||||
Theme: text::Catalog + 'a,
|
||||
Renderer: iced::advanced::text::Renderer,
|
||||
{
|
||||
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 bin32<'a, Theme, Renderer>(val: u32) -> Text<'a, Theme, Renderer>
|
||||
where
|
||||
Theme: text::Catalog + 'a,
|
||||
Renderer: iced::advanced::text::Renderer,
|
||||
{
|
||||
text(format!("{val:032b}"))
|
||||
}
|
||||
|
||||
impl DebuggerState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -85,38 +117,56 @@ impl DebuggerState {
|
||||
text("IRQs:"),
|
||||
labelled_box("NMI", nes.peek_nmi()),
|
||||
labelled_box("Cart", false),
|
||||
labelled_box("Frame Counter", false),
|
||||
labelled_box("Frame Counter", nes.apu().peek_irq()),
|
||||
labelled_box("DMC", false),
|
||||
]
|
||||
.spacing(5.),
|
||||
row![
|
||||
column![
|
||||
labelled("Cycle", text(nes.ppu.pixel)),
|
||||
labelled("Scanline", text(nes.ppu.scanline)),
|
||||
labelled("PPU Cycle", text(nes.ppu.cycle)),
|
||||
labelled("Cycle", text(nes.ppu().pixel)),
|
||||
labelled("Scanline", text(nes.ppu().scanline)),
|
||||
labelled("PPU Cycle", text(nes.ppu().cycle)),
|
||||
labelled("V:", hex16(nes.ppu().background.v)),
|
||||
labelled("T:", hex16(nes.ppu().background.t)),
|
||||
labelled("X:", hex8(nes.ppu().background.x)),
|
||||
text(""),
|
||||
labelled("NT:", hex8(nes.ppu().background.cur_nametable)),
|
||||
labelled2(
|
||||
"AT:",
|
||||
hex8(nes.ppu().background.next_attr),
|
||||
hex16(
|
||||
0x23C0
|
||||
| (nes.ppu().background.v & 0x0C00)
|
||||
| ((nes.ppu().background.v >> 4) & 0x38)
|
||||
| ((nes.ppu().background.v >> 2) & 0x07)
|
||||
)
|
||||
),
|
||||
labelled("AT:", hex8(nes.ppu().background.cur_attr)),
|
||||
labelled("HI:", bin32(nes.ppu().background.cur_shift_high)),
|
||||
labelled("LO:", bin32(nes.ppu().background.cur_shift_low)),
|
||||
],
|
||||
column![
|
||||
labelled_box("Sprite 0 Hit", false),
|
||||
labelled_box("Sprite 0 Overflow", false),
|
||||
labelled_box("Vertical Blank", nes.ppu.vblank),
|
||||
labelled_box("Write Toggle", false),
|
||||
labelled_box("", false),
|
||||
labelled_box("Vertical Blank", nes.ppu().vblank),
|
||||
labelled_box("Write Toggle", nes.ppu().background.w),
|
||||
text(""),
|
||||
labelled_box("Large Sprites", false),
|
||||
labelled_box("Vertical Write", false),
|
||||
labelled_box("NMI on VBlank", false),
|
||||
labelled_box("BG at $1000", false),
|
||||
labelled_box("Vertical Write", nes.ppu().background.vram_column),
|
||||
labelled_box("NMI on VBlank", nes.ppu().nmi_on_vblank()),
|
||||
labelled_box("BG at $1000", nes.ppu().background.second_pattern),
|
||||
labelled_box("Sprites at $1000", false),
|
||||
],
|
||||
column![
|
||||
labelled_box("Even frame", nes.ppu.even),
|
||||
labelled_box("BG Enabled", false),
|
||||
labelled_box("Sprites Enabled", false),
|
||||
labelled_box("BG Mask", false),
|
||||
labelled_box("Sprites Mask", false),
|
||||
labelled_box("Grayscale", false),
|
||||
labelled_box("Intensify Red", false),
|
||||
labelled_box("Intensify Green", false),
|
||||
labelled_box("Intensify Blue", false),
|
||||
labelled_box("Even frame", nes.ppu().even),
|
||||
labelled_box("BG Enabled", nes.ppu().mask.enable_background),
|
||||
labelled_box("Sprites Enabled", nes.ppu().mask.enable_sprites),
|
||||
labelled_box("BG Mask", nes.ppu().mask.background_on_left_edge),
|
||||
labelled_box("Sprites Mask", nes.ppu().mask.sprites_on_left_edge),
|
||||
labelled_box("Grayscale", nes.ppu().mask.grayscale),
|
||||
labelled_box("Intensify Red", nes.ppu().mask.em_red),
|
||||
labelled_box("Intensify Green", nes.ppu().mask.em_green),
|
||||
labelled_box("Intensify Blue", nes.ppu().mask.em_blue),
|
||||
],
|
||||
column![
|
||||
run_type(
|
||||
@@ -158,6 +208,7 @@ impl DebuggerState {
|
||||
],
|
||||
]
|
||||
.spacing(5.),
|
||||
horizontal(2),
|
||||
scrollable(
|
||||
column(
|
||||
nes.debug_log()
|
||||
@@ -178,15 +229,15 @@ impl DebuggerState {
|
||||
|
||||
fn run_n_clock_cycles(nes: &mut NES, n: usize) {
|
||||
for _ in 0..n {
|
||||
nes.run_one_clock_cycle();
|
||||
if nes.halted {
|
||||
if nes.run_one_clock_cycle().dbg_int || nes.halted {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn run_until(nes: &mut NES, mut f: impl FnMut(CycleResult, &NES) -> bool, mut count: usize) {
|
||||
loop {
|
||||
if f(nes.run_one_clock_cycle(), nes) {
|
||||
let res = nes.run_one_clock_cycle();
|
||||
if res.dbg_int || f(res, nes) {
|
||||
count -= 1;
|
||||
if count <= 0 {
|
||||
break;
|
||||
@@ -250,8 +301,277 @@ pub fn labelled<'a, Message: 'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn labelled2<'a, Message: 'a>(
|
||||
label: &'a str,
|
||||
content: impl Into<Element<'a, Message>>,
|
||||
content2: impl Into<Element<'a, Message>>,
|
||||
) -> Element<'a, Message> {
|
||||
row![
|
||||
widget::container(text(label)).padding(2.),
|
||||
widget::container(content).style(bordered_box).padding(2.),
|
||||
widget::container(content2).style(bordered_box).padding(2.),
|
||||
]
|
||||
.spacing(1.)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn labelled_box<'a, Message: 'a>(label: &'a str, value: bool) -> Element<'a, Message> {
|
||||
row![checkbox(value), widget::container(text(label)),]
|
||||
.spacing(1.)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DbgImage<'a> {
|
||||
NameTable(&'a PPU),
|
||||
PatternTable(&'a PPU),
|
||||
Palette(&'a PPU),
|
||||
}
|
||||
|
||||
impl<Message, T> Program<Message, T> for DbgImage<'_> {
|
||||
type State = ();
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &T,
|
||||
_bounds: iced::Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<iced::widget::canvas::Geometry<Renderer>> {
|
||||
// const SIZE: f32 = 2.;
|
||||
let mut name_table_frame =
|
||||
Frame::new(renderer, Size::new(256. * 4. + 260. * 2., 256. * 4.));
|
||||
name_table_frame.scale(2.);
|
||||
// println!("Position: {:?}", cursor.position());
|
||||
match self {
|
||||
DbgImage::NameTable(ppu) => ppu.render_name_table(&mut name_table_frame),
|
||||
DbgImage::PatternTable(ppu) => ppu.render_pattern_tables(&mut name_table_frame),
|
||||
DbgImage::Palette(ppu) => ppu.render_palette(&mut name_table_frame),
|
||||
};
|
||||
vec![name_table_frame.into_geometry()]
|
||||
}
|
||||
}
|
||||
|
||||
impl DbgImage<'_> {
|
||||
fn width(&self) -> Length {
|
||||
match self {
|
||||
DbgImage::NameTable(_) => Length::Fixed(512. * 2.),
|
||||
DbgImage::PatternTable(_) => Length::Fixed(16. * 8. * 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.),
|
||||
}
|
||||
}
|
||||
fn help(&self, cursor: Point) -> Option<String> {
|
||||
match self {
|
||||
DbgImage::NameTable(ppu) => ppu.name_cursor_info(cursor),
|
||||
DbgImage::PatternTable(ppu) => ppu.pattern_cursor_info(cursor),
|
||||
DbgImage::Palette(ppu) => ppu.palette_cursor_info(cursor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DbgImageSetup<'a, M, T: text::Catalog> {
|
||||
dbg: DbgImage<'a>,
|
||||
image: Canvas<DbgImage<'a>, M, T>,
|
||||
text: Text<'a, T>,
|
||||
padding: f32,
|
||||
drawn: bool,
|
||||
// image: DbgImage<'a>,
|
||||
// text: te,
|
||||
}
|
||||
|
||||
// pub trait Container<Message, Theme, Renderer> {
|
||||
// // Not ideal
|
||||
// fn children(&self) -> &[&dyn Widget<Message, Theme, Renderer>];
|
||||
// }
|
||||
|
||||
impl<'s, Message, Theme> Widget<Message, Theme, Renderer> for DbgImageSetup<'s, Message, Theme>
|
||||
where
|
||||
Theme: text::Catalog + 's,
|
||||
{
|
||||
fn size(&self) -> Size<iced::Length> {
|
||||
// self.
|
||||
Size::new(Fill, Fill)
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &iced::advanced::layout::Limits,
|
||||
) -> Node {
|
||||
let img_node = self.image.layout(&mut tree.children[0], renderer, limits);
|
||||
let txt_node = Widget::<Message, Theme, _>::layout(
|
||||
&mut self.text,
|
||||
&mut tree.children[1],
|
||||
renderer,
|
||||
&limits.shrink(Size::new(
|
||||
img_node.size().width + self.padding * 2.,
|
||||
self.padding * 2.,
|
||||
)),
|
||||
)
|
||||
.move_to(Point::new(
|
||||
img_node.size().width + self.padding,
|
||||
self.padding,
|
||||
));
|
||||
Node::with_children(limits.max(), vec![img_node, txt_node])
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Theme,
|
||||
style: &iced::advanced::renderer::Style,
|
||||
layout: iced::advanced::Layout<'_>,
|
||||
cursor: iced::advanced::mouse::Cursor,
|
||||
viewport: &iced::Rectangle,
|
||||
) {
|
||||
self.image.draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout.child(0),
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
Widget::<Message, Theme, _>::draw(
|
||||
&self.text,
|
||||
&tree.children[1],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout.child(1),
|
||||
cursor,
|
||||
viewport,
|
||||
)
|
||||
}
|
||||
|
||||
fn tag(&self) -> Tag {
|
||||
Tag::of::<Rc<String>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::new(Rc::new(String::new()))
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![
|
||||
Tree::new(&self.image as &dyn Widget<Message, Theme, Renderer>),
|
||||
Tree::new(&self.text as &dyn Widget<Message, Theme, Renderer>),
|
||||
]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children(&[
|
||||
&self.image as &dyn Widget<Message, Theme, Renderer>,
|
||||
&self.text as &dyn Widget<Message, Theme, Renderer>,
|
||||
]);
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: iced::advanced::Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced::advanced::widget::Operation,
|
||||
) {
|
||||
operation.container(None, layout.bounds());
|
||||
operation.traverse(&mut |op| {
|
||||
self.image
|
||||
.operate(&mut tree.children[0], layout.child(0), renderer, op);
|
||||
Widget::<Message, Theme, _>::operate(
|
||||
&mut self.text,
|
||||
&mut tree.children[1],
|
||||
layout.child(1),
|
||||
renderer,
|
||||
op,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: &iced::Event,
|
||||
layout: iced::advanced::Layout<'_>,
|
||||
cursor: iced::advanced::mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn iced::advanced::Clipboard,
|
||||
shell: &mut iced::advanced::Shell<'_, Message>,
|
||||
viewport: &iced::Rectangle,
|
||||
) {
|
||||
self.image.update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout.child(0),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
if matches!(event, iced::Event::Mouse(mouse::Event::CursorMoved { .. })) || !self.drawn {
|
||||
if let Some(help) = cursor
|
||||
.position_in(layout.child(0).bounds())
|
||||
.and_then(|pos| self.dbg.help(Point::new(pos.x / 2., pos.y / 2.)))
|
||||
{
|
||||
self.text = text(help);
|
||||
shell.invalidate_layout();
|
||||
shell.request_redraw();
|
||||
}
|
||||
self.drawn = true;
|
||||
}
|
||||
Widget::<Message, Theme, _>::update(
|
||||
&mut self.text,
|
||||
&mut tree.children[1],
|
||||
event,
|
||||
layout.child(1),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &iced::advanced::widget::Tree,
|
||||
_layout: iced::advanced::Layout<'_>,
|
||||
_cursor: iced::advanced::mouse::Cursor,
|
||||
_viewport: &iced::Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> iced::advanced::mouse::Interaction {
|
||||
iced::advanced::mouse::Interaction::default()
|
||||
}
|
||||
|
||||
fn overlay<'a>(
|
||||
&'a mut self,
|
||||
_tree: &'a mut iced::advanced::widget::Tree,
|
||||
_layout: iced::advanced::Layout<'a>,
|
||||
_renderer: &Renderer,
|
||||
_viewport: &iced::Rectangle,
|
||||
_translation: iced::Vector,
|
||||
) -> Option<iced::advanced::overlay::Element<'a, Message, Theme, Renderer>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dbg_image<'a, Message: 'a>(img: DbgImage<'a>) -> Element<'a, Message> {
|
||||
Element::new(DbgImageSetup {
|
||||
dbg: img,
|
||||
image: Canvas::new(img).width(img.width()).height(img.height()),
|
||||
text: text(""),
|
||||
padding: 10.,
|
||||
drawn: false,
|
||||
})
|
||||
}
|
||||
|
||||
187
src/lib.rs
187
src/lib.rs
@@ -6,9 +6,9 @@ pub mod header_menu;
|
||||
pub mod hex_view;
|
||||
mod mem;
|
||||
mod ppu;
|
||||
pub mod resize_watcher;
|
||||
#[cfg(test)]
|
||||
mod test_roms;
|
||||
pub mod resize_watcher;
|
||||
|
||||
pub use ppu::{Color, PPU, RenderBuffer};
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::{
|
||||
controllers::Controllers,
|
||||
debug::DebugLog,
|
||||
hex_view::Memory,
|
||||
mem::{MemoryMap, Segment},
|
||||
mem::{Mapper, MemoryMap, Segment},
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -39,6 +39,7 @@ pub struct NES {
|
||||
cpu: Cpu,
|
||||
dma: DmaState,
|
||||
memory: MemoryMap<CPUMMRegisters>,
|
||||
mapper: Mapper,
|
||||
ppu: PPU,
|
||||
apu: APU,
|
||||
controller: Controllers,
|
||||
@@ -159,8 +160,13 @@ pub struct Cpu {
|
||||
pub sp: u8,
|
||||
pub status: CpuStatus,
|
||||
pub clock_state: ClockState,
|
||||
|
||||
pub nmi_line_state: bool,
|
||||
pub nmi_pending: bool,
|
||||
pub irq_pending: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ClockState {
|
||||
ReadInstruction,
|
||||
ReadOperands {
|
||||
@@ -249,6 +255,9 @@ impl Cpu {
|
||||
count: 0,
|
||||
addr: 0,
|
||||
},
|
||||
nmi_pending: false,
|
||||
irq_pending: false,
|
||||
nmi_line_state: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -287,16 +296,14 @@ impl NES {
|
||||
info!("PRG: {prg_rom_size}");
|
||||
info!("CHR: {chr_rom_size}");
|
||||
info!("FLAGS: {mapper_flags:b}");
|
||||
if mapper_flags & 0b11111110 != 0 {
|
||||
todo!("Support other mapper flags");
|
||||
}
|
||||
Ok(Self::from_rom(
|
||||
&raw[16..][..16384 * prg_rom_size as usize],
|
||||
&raw[16 + 16384 * prg_rom_size as usize..][..8192 * chr_rom_size as usize],
|
||||
Mapper::from_flags(mapper_flags),
|
||||
))
|
||||
}
|
||||
|
||||
fn from_rom(prg_rom: &[u8], chr_rom: &[u8]) -> Self {
|
||||
fn from_rom(prg_rom: &[u8], chr_rom: &[u8], mapper: Mapper) -> Self {
|
||||
let mut segments = vec![
|
||||
Segment::ram("Internal RAM", 0x0000, 0x0800),
|
||||
Segment::mirror("Mirror of iRAM", 0x0800, 0x1800, 0),
|
||||
@@ -306,7 +313,8 @@ impl NES {
|
||||
Segment::mirror("Mirror of APU & IO", 0x4018, 0x0008, 2),
|
||||
];
|
||||
// let mut cur = 0x4020;
|
||||
segments.push(Segment::rom("PROG ROM", 0x8000, prg_rom));
|
||||
assert!(prg_rom.len() <= 0x8000, "Mappers for larger sizes not supported");
|
||||
segments.push(Segment::rom("PROG ROM", 0x8000 + (0x8000 - prg_rom.len() as u16), prg_rom));
|
||||
Self {
|
||||
cycle: 7,
|
||||
dbg_int: false,
|
||||
@@ -315,8 +323,9 @@ impl NES {
|
||||
|
||||
clock_count: 0,
|
||||
memory: MemoryMap::new(segments),
|
||||
mapper,
|
||||
cpu: Cpu::init(),
|
||||
ppu: PPU::with_chr_rom(chr_rom),
|
||||
ppu: PPU::with_chr_rom(chr_rom, mapper),
|
||||
apu: APU::init(),
|
||||
controller: Controllers::init(),
|
||||
dma: DmaState::Passive,
|
||||
@@ -430,9 +439,9 @@ impl NES {
|
||||
} else if !held && hold_time != 0 {
|
||||
ExecState::Hold(hold_time - 1)
|
||||
} else {
|
||||
debug!("Running 0x{:04X} {} :{:X} {:X?}", self.cpu.pc - (1 + params.len() as u16), $val, ins, params);
|
||||
// debug!("Running 0x{:04X} {} :{:X} {:X?}", self.cpu.pc - (1 + params.len() as u16), $val, ins, params);
|
||||
self.last_instruction = format!("0x{:04X} {} :{:X} {:X?}", self.cpu.pc - (1 + params.len() as u16), $val, ins, params);
|
||||
// debug!("Running 0x{:04X} {} :{:X} {:X?}", self.cpu.pc - (1 + params.len() as u16), $val, ins, params);
|
||||
self.last_instruction = format!("0x{:04X} {} :{:X} {:X?}", addr, $val, ins, params);
|
||||
$(
|
||||
let $name = params[0];
|
||||
#[allow(unused_assignments)]
|
||||
@@ -462,6 +471,13 @@ impl NES {
|
||||
log!("{addr:04X}: HLT");
|
||||
self.halt()
|
||||
}),
|
||||
0x12 => inst!("DBG", 0, || {
|
||||
log!("{addr:04X}: DBG");
|
||||
self.halt()
|
||||
}),
|
||||
0x22 | 0x32 | 0x42 | 0x52 | 0x62 | 0x72 | 0x92 | 0xB2 | 0xD3 | 0xF2 => {
|
||||
panic!("Game crash by executing {ins:02X} at {addr:04X}")
|
||||
}
|
||||
0x38 => inst!("SEC", 1, || {
|
||||
log!("{addr:04X}: SEC");
|
||||
self.cpu.status.set_carry(true)
|
||||
@@ -490,7 +506,8 @@ impl NES {
|
||||
log!("{addr:04X}: CLV");
|
||||
self.cpu.status.set_overflow(false)
|
||||
}),
|
||||
0x00 => inst!("BRK", 7, |_ignored| {
|
||||
0x00 => inst!("BRK", 5, |_ignored| {
|
||||
// TODO: this should probably just set `self.cpu.irq_pending` ...
|
||||
log!("{addr:04X}: BRK #${_ignored:02X}");
|
||||
self.push_16(self.cpu.pc);
|
||||
self.push(self.cpu.status.0 | 0b00110000);
|
||||
@@ -1432,7 +1449,7 @@ impl NES {
|
||||
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
|
||||
log!("{addr:04X}: ORA ${:02X},x | {:02X}", off, self.cpu.a);
|
||||
}),
|
||||
0x0D => inst!("ORA abs", 2, |low, high| {
|
||||
0x0D => inst!("ORA abs", 1, |low, high| {
|
||||
self.cpu.a |= self.read_abs(low, high);
|
||||
self.cpu.status.set_zero(self.cpu.a == 0);
|
||||
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
|
||||
@@ -1443,7 +1460,7 @@ impl NES {
|
||||
self.cpu.a
|
||||
);
|
||||
}),
|
||||
0x1D => inst!("ORA abs,x", 2, |low, high| {
|
||||
0x1D => inst!("ORA abs,x", 1, |low, high| { // TODO: page crossing
|
||||
self.cpu.a |= self.read_abs_x(low, high);
|
||||
self.cpu.status.set_zero(self.cpu.a == 0);
|
||||
self.cpu.status.set_negative(self.cpu.a & 0x80 == 0x80);
|
||||
@@ -1478,7 +1495,7 @@ impl NES {
|
||||
self.cpu.a
|
||||
);
|
||||
}),
|
||||
0x11 => inst!("ORA (ind),y", 4, |off| {
|
||||
0x11 => inst!("ORA (ind),y", 3, |off| {
|
||||
let low = self.read_abs(off, 0);
|
||||
let high = self.read_abs(off.wrapping_add(1), 0);
|
||||
self.cpu.a |= self.read(u16::from_le_bytes([low, high]) + self.cpu.y as u16);
|
||||
@@ -1657,12 +1674,7 @@ impl NES {
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_overflow(val & 0x40 == 0x40);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
log!(
|
||||
"{addr:04X}: BIT ${:02X}{:02X} | {:02X}",
|
||||
high,
|
||||
low,
|
||||
val
|
||||
);
|
||||
log!("{addr:04X}: BIT ${:02X}{:02X} | {:02X}", high, low, val);
|
||||
}),
|
||||
|
||||
// Shifts
|
||||
@@ -1808,12 +1820,7 @@ impl NES {
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
self.write_abs(low, high, val);
|
||||
log!(
|
||||
"{addr:04X}: ROR ${:02X}{:02X} | {:02X}",
|
||||
high,
|
||||
low,
|
||||
val
|
||||
);
|
||||
log!("{addr:04X}: ROR ${:02X}{:02X} | {:02X}", high, low, val);
|
||||
}),
|
||||
0x7E => inst!("ROR abs,x", 4, |low, high| {
|
||||
let old_carry = if self.cpu.status.carry() { 0x80 } else { 0x00 };
|
||||
@@ -1823,12 +1830,7 @@ impl NES {
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
self.write_abs_x(low, high, val);
|
||||
log!(
|
||||
"{addr:04X}: ROR ${:02X}{:02X},x | {:02X}",
|
||||
high,
|
||||
low,
|
||||
val
|
||||
);
|
||||
log!("{addr:04X}: ROR ${:02X}{:02X},x | {:02X}", high, low, val);
|
||||
}),
|
||||
0x2A => inst!("ROL A", 1, || {
|
||||
let old_carry = if self.cpu.status.carry() { 0x01 } else { 0x00 };
|
||||
@@ -1881,12 +1883,7 @@ impl NES {
|
||||
self.cpu.status.set_zero(val == 0);
|
||||
self.cpu.status.set_negative(val & 0x80 == 0x80);
|
||||
self.write_abs_x(low, high, val);
|
||||
log!(
|
||||
"{addr:04X}: ROL ${:02X}{:02X},x | {:02X}",
|
||||
high,
|
||||
low,
|
||||
val
|
||||
);
|
||||
log!("{addr:04X}: ROL ${:02X}{:02X},x | {:02X}", high, low, val);
|
||||
}),
|
||||
|
||||
0xEA => inst!("NOP", 1, || {
|
||||
@@ -1912,8 +1909,9 @@ impl NES {
|
||||
ClockState::HoldNmi { cycles } => {
|
||||
if cycles == 0 {
|
||||
self.push_16(self.cpu.pc);
|
||||
self.push(self.cpu.status.0);
|
||||
self.push(self.cpu.status.0 | 0x20);
|
||||
self.cpu.pc = u16::from_le_bytes([self.read(0xFFFA), self.read(0xFFFB)]);
|
||||
writeln!(self.debug_log, "Starting NMI Handler").unwrap();
|
||||
ClockState::ReadInstruction
|
||||
} else {
|
||||
ClockState::HoldNmi { cycles: cycles - 1 }
|
||||
@@ -1921,20 +1919,35 @@ impl NES {
|
||||
}
|
||||
ClockState::HoldIrq { cycles } => {
|
||||
if cycles == 0 {
|
||||
todo!("Run NMI");
|
||||
// todo!("Run IRQ");
|
||||
self.push_16(self.cpu.pc);
|
||||
self.push(self.cpu.status.0 | 0b00110000);
|
||||
self.cpu.status.set_interrupt_disable(true);
|
||||
self.cpu.pc = u16::from_le_bytes([self.read(0xFFFE), self.read(0xFFFF)]);
|
||||
writeln!(self.debug_log, "Starting IRQ handler").unwrap();
|
||||
ClockState::ReadInstruction
|
||||
} else {
|
||||
ClockState::HoldIrq { cycles: cycles - 1 }
|
||||
}
|
||||
}
|
||||
ClockState::ReadInstruction => {
|
||||
if self.ppu.nmi_waiting() || self.apu.nmi_waiting() {
|
||||
ClockState::HoldNmi { cycles: 6 }
|
||||
} else if self.ppu.irq_waiting() || self.apu.irq_waiting() {
|
||||
ClockState::HoldIrq { cycles: 6 }
|
||||
} else {
|
||||
// if self.cpu.nmi_pending {
|
||||
// self.cpu.nmi_pending = false;
|
||||
// writeln!(self.debug_log, "NMI detected").unwrap();
|
||||
// ClockState::HoldNmi { cycles: 5 }
|
||||
// } else if !self.cpu.status.interrupt_disable()
|
||||
// && (self.ppu.irq_waiting() || self.apu.irq_waiting())
|
||||
// {
|
||||
// // TODO: handle proper irq detection
|
||||
// writeln!(self.debug_log, "IRQ detected").unwrap();
|
||||
// ClockState::HoldIrq { cycles: 6 }
|
||||
// } else
|
||||
{
|
||||
let addr = self.cpu.pc;
|
||||
let instruction = self.read(self.cpu.pc);
|
||||
self.cpu.pc = self.cpu.pc.wrapping_add(1);
|
||||
if instruction != 0x02 {
|
||||
self.cpu.pc = self.cpu.pc.wrapping_add(1);
|
||||
}
|
||||
match self.exec_instruction(instruction, &[], false, addr) {
|
||||
ExecState::Done => ClockState::ReadInstruction,
|
||||
ExecState::MoreParams => ClockState::ReadOperands {
|
||||
@@ -2010,10 +2023,26 @@ impl NES {
|
||||
}
|
||||
}
|
||||
};
|
||||
if self.cpu.clock_state == ClockState::ReadInstruction {
|
||||
if self.cpu.nmi_pending {
|
||||
self.cpu.nmi_pending = false;
|
||||
self.cpu.clock_state = ClockState::HoldNmi { cycles: 6 };
|
||||
} else if self.cpu.irq_pending && !self.cpu.status.interrupt_disable() {
|
||||
self.cpu.clock_state = ClockState::HoldIrq { cycles: 6 };
|
||||
}
|
||||
}
|
||||
// Check NMI and IRQ line state. This happens in phi2, i.e. the second half of the instruction
|
||||
let new_line_state = self.ppu.nmi_waiting() || self.apu.nmi_waiting();
|
||||
if new_line_state != self.cpu.nmi_line_state {
|
||||
// println!("ppu: {}, apu: {}", self.ppu.nmi_waiting(), self.apu.nmi_waiting());
|
||||
// println!("New: {new_line_state}, old: {}", self.cpu.nmi_line_state);
|
||||
self.cpu.nmi_line_state = new_line_state;
|
||||
self.cpu.nmi_pending |= new_line_state;
|
||||
}
|
||||
self.cpu.irq_pending = self.ppu.irq_waiting() || self.apu.irq_waiting();
|
||||
}
|
||||
|
||||
fn cpu_cycle(&mut self) {
|
||||
self.cycle += 1;
|
||||
match self.dma {
|
||||
DmaState::Passive => self.clock_cpu(),
|
||||
// TODO: Validate that this takes the correct number of cycles (513 or 514 cycles)
|
||||
@@ -2048,9 +2077,12 @@ impl NES {
|
||||
};
|
||||
}
|
||||
}
|
||||
if [0x8031, 0x8014].contains(&self.cpu.pc) {
|
||||
self.dbg_int = true;
|
||||
if !self.halted {
|
||||
self.cycle += 1;
|
||||
}
|
||||
// if [0x8031, 0x8014].contains(&self.cpu.pc) {
|
||||
// self.dbg_int = true;
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn run_one_clock_cycle(&mut self) -> CycleResult {
|
||||
@@ -2069,8 +2101,15 @@ impl NES {
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !self.halted {
|
||||
let apu_exec = self.apu.run_one_clock_cycle(self.clock_count);
|
||||
}
|
||||
let ppu_frame = if !self.halted {
|
||||
self.ppu.run_one_clock_cycle()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
// 3 PPU clock cycles for each CPU clock cycle
|
||||
let ppu_frame = self.ppu.run_one_clock_cycle();
|
||||
let dbg_int = self.dbg_int | self.ppu.dbg_int;
|
||||
self.dbg_int = false;
|
||||
self.ppu.dbg_int = false;
|
||||
@@ -2095,7 +2134,8 @@ impl NES {
|
||||
// self.ppu.memory.clear();
|
||||
*self = Self::from_rom(
|
||||
self.memory.rom(6).expect("PRG ROM"),
|
||||
self.ppu.memory.rom(0).expect("CHR ROM"),
|
||||
self.ppu.memory.rom_or_ram(0).expect("CHR ROM"),
|
||||
self.mapper,
|
||||
);
|
||||
self.reset();
|
||||
}
|
||||
@@ -2108,8 +2148,36 @@ impl NES {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_and_run_with_timeout(&mut self, max_clock_cycles: usize) {
|
||||
self.reset();
|
||||
/// Act as though the last STP instruction was a NOP.
|
||||
///
|
||||
/// Clocks the PPU & APU forward once if appropriate
|
||||
pub fn repl_nop(&mut self) {
|
||||
assert!(
|
||||
self.halted,
|
||||
"This method may only be called when the CPU is halted"
|
||||
);
|
||||
assert!(
|
||||
// self.debug_log.pop().is_some_and(|s| s.contains("HLT")),
|
||||
self.last_instruction.contains("HLT"),
|
||||
"This method may only be called after a STP instruction"
|
||||
);
|
||||
self.halted = false;
|
||||
// Turn final STP into NOP
|
||||
self.ppu.run_one_clock_cycle();
|
||||
self.apu.run_one_clock_cycle(self.clock_count);
|
||||
self.cpu.clock_state = ClockState::Hold {
|
||||
cycles: 0,
|
||||
instruction: 0xEA,
|
||||
ops: [0; 5],
|
||||
count: 0,
|
||||
addr: self.cpu.pc,
|
||||
};
|
||||
self.cpu.pc += 1;
|
||||
self.cycle += 1;
|
||||
// self.clock_cpu();
|
||||
}
|
||||
|
||||
pub fn run_with_timeout(&mut self, max_clock_cycles: usize) {
|
||||
let mut cur = 0;
|
||||
while !self.halted && cur < max_clock_cycles {
|
||||
// info!("Running clock cycle: {}", self.clock_count);
|
||||
@@ -2118,6 +2186,11 @@ impl NES {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_and_run_with_timeout(&mut self, max_clock_cycles: usize) {
|
||||
self.reset();
|
||||
self.run_with_timeout(max_clock_cycles);
|
||||
}
|
||||
|
||||
pub fn image(&self) -> &RenderBuffer<256, 240> {
|
||||
&self.ppu.render_buffer
|
||||
}
|
||||
@@ -2126,6 +2199,10 @@ impl NES {
|
||||
&self.ppu
|
||||
}
|
||||
|
||||
pub fn apu(&self) -> &APU {
|
||||
&self.apu
|
||||
}
|
||||
|
||||
pub fn cpu_mem(&self) -> &impl Memory {
|
||||
&self.memory
|
||||
}
|
||||
@@ -2137,4 +2214,8 @@ impl NES {
|
||||
pub fn debug_log_mut(&mut self) -> &mut DebugLog {
|
||||
&mut self.debug_log
|
||||
}
|
||||
|
||||
pub fn halted(&self) -> bool {
|
||||
self.halted
|
||||
}
|
||||
}
|
||||
|
||||
89
src/main.rs
89
src/main.rs
@@ -3,17 +3,17 @@ use std::{collections::HashMap, fmt, time::Duration};
|
||||
use iced::{
|
||||
Color, Element, Font,
|
||||
Length::{Fill, Shrink},
|
||||
Point, Renderer, Size, Subscription, Task, Theme, mouse,
|
||||
Point, Rectangle, Renderer, Size, Subscription, Task, Theme, mouse,
|
||||
widget::{
|
||||
Canvas, button,
|
||||
Action, Canvas, button,
|
||||
canvas::{Frame, Program},
|
||||
column, container, row, text,
|
||||
column, container, mouse_area, row, text,
|
||||
},
|
||||
window::{self, Id, Settings},
|
||||
};
|
||||
use nes_emu::{
|
||||
NES, PPU,
|
||||
debugger::{DebuggerMessage, DebuggerState},
|
||||
debugger::{DbgImage, DebuggerMessage, DebuggerState, dbg_image},
|
||||
header_menu::header_menu,
|
||||
hex_view::{HexEvent, HexView},
|
||||
resize_watcher::resize_watcher,
|
||||
@@ -22,8 +22,12 @@ use tokio::runtime::Runtime;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "even_odd.nes");
|
||||
const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "crc_check.nes");
|
||||
// const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "crc_check.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"), "/", "int_nmi_exit_timing.nes");
|
||||
const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
|
||||
// const ROM_FILE: &str = "./cpu_timing_test.nes";
|
||||
|
||||
extern crate nes_emu;
|
||||
|
||||
@@ -43,6 +47,7 @@ fn main() -> Result<(), iced::Error> {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum MemoryTy {
|
||||
Cpu,
|
||||
PPU,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -70,6 +75,7 @@ impl fmt::Display for HeaderButton {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Open(WindowType::Memory(MemoryTy::Cpu, _)) => write!(f, "Open Memory Viewer"),
|
||||
Self::Open(WindowType::Memory(MemoryTy::PPU, _)) => write!(f, "Open PPU Memory Viewer"),
|
||||
Self::Open(WindowType::TileMap) => write!(f, "Open TileMap Viewer"),
|
||||
Self::Open(WindowType::TileViewer) => write!(f, "Open Tile Viewer"),
|
||||
Self::Open(WindowType::Debugger) => write!(f, "Open Debugger"),
|
||||
@@ -96,6 +102,7 @@ enum Message {
|
||||
CPU,
|
||||
DebugInt,
|
||||
WindowClosed(Id),
|
||||
WindowOpened(Id),
|
||||
Header(HeaderButton),
|
||||
Hex(Id, HexEvent),
|
||||
Debugger(DebuggerMessage),
|
||||
@@ -106,6 +113,16 @@ impl Emulator {
|
||||
fn new() -> (Self, Task<Message>) {
|
||||
let mut nes = nes_emu::NES::load_nes_file(ROM_FILE).expect("Failed to load nes file");
|
||||
nes.reset();
|
||||
// TODO: remove
|
||||
// let mut count = 10;
|
||||
// while !nes.halted() {
|
||||
// if nes.run_one_clock_cycle().ppu_frame {
|
||||
// count -= 1;
|
||||
// if count <= 0 {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
let (win, task) = iced::window::open(Settings {
|
||||
min_size: None,
|
||||
..Settings::default()
|
||||
@@ -154,6 +171,12 @@ impl Emulator {
|
||||
return iced::exit();
|
||||
}
|
||||
}
|
||||
Message::WindowOpened(_id) => {
|
||||
// if let Some(WindowType::Main) = self.windows.get(&id) {
|
||||
// // println!("Running resize");
|
||||
// return iced::window::resize(id, self.main_win_size);
|
||||
// }
|
||||
}
|
||||
Message::Header(HeaderButton::Open(w)) => {
|
||||
return self.open(w);
|
||||
}
|
||||
@@ -188,8 +211,7 @@ impl Emulator {
|
||||
fn subscriptions(&self) -> Subscription<Message> {
|
||||
Subscription::batch([
|
||||
window::close_events().map(Message::WindowClosed),
|
||||
// window::events().map(Message::Window),
|
||||
// window::resize_events().map(Message::WindowResized),
|
||||
window::open_events().map(Message::WindowOpened),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -214,26 +236,18 @@ impl Emulator {
|
||||
MemoryTy::Cpu => view
|
||||
.render(self.nes.cpu_mem())
|
||||
.map(move |e| Message::Hex(win, e)),
|
||||
MemoryTy::PPU => view
|
||||
.render(self.nes.ppu().mem())
|
||||
.map(move |e| Message::Hex(win, e)),
|
||||
};
|
||||
let content = column![hex].width(Fill).height(Fill);
|
||||
container(content).width(Fill).height(Fill).into()
|
||||
}
|
||||
Some(WindowType::TileMap) => {
|
||||
container(Canvas::new(DbgImage::NameTable(self.nes.ppu())))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into()
|
||||
}
|
||||
Some(WindowType::TileMap) => dbg_image(DbgImage::NameTable(self.nes.ppu())).into(),
|
||||
Some(WindowType::TileViewer) => {
|
||||
container(Canvas::new(DbgImage::PatternTable(self.nes.ppu())))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into()
|
||||
dbg_image(DbgImage::PatternTable(self.nes.ppu())).into()
|
||||
}
|
||||
Some(WindowType::Palette) => container(Canvas::new(DbgImage::Palette(self.nes.ppu())))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into(),
|
||||
Some(WindowType::Palette) => dbg_image(DbgImage::Palette(self.nes.ppu())).into(),
|
||||
Some(WindowType::Debugger) => {
|
||||
container(self.debugger.view(&self.nes).map(Message::Debugger))
|
||||
.width(Fill)
|
||||
@@ -257,6 +271,7 @@ impl Emulator {
|
||||
[
|
||||
HeaderButton::Open(WindowType::Debugger),
|
||||
HeaderButton::Open(WindowType::Memory(MemoryTy::Cpu, HexView {})),
|
||||
HeaderButton::Open(WindowType::Memory(MemoryTy::PPU, HexView {})),
|
||||
HeaderButton::Open(WindowType::TileMap),
|
||||
HeaderButton::Open(WindowType::TileViewer),
|
||||
HeaderButton::Open(WindowType::Palette),
|
||||
@@ -302,33 +317,3 @@ impl Program<Message> for Emulator {
|
||||
vec![frame.into_geometry()]
|
||||
}
|
||||
}
|
||||
|
||||
enum DbgImage<'a> {
|
||||
NameTable(&'a PPU),
|
||||
PatternTable(&'a PPU),
|
||||
Palette(&'a PPU),
|
||||
}
|
||||
|
||||
impl Program<Message> for DbgImage<'_> {
|
||||
type State = ();
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
_bounds: iced::Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<iced::widget::canvas::Geometry<Renderer>> {
|
||||
// const SIZE: f32 = 2.;
|
||||
let mut name_table_frame =
|
||||
Frame::new(renderer, Size::new(256. * 4. + 260. * 2., 256. * 4.));
|
||||
name_table_frame.scale(2.);
|
||||
match self {
|
||||
DbgImage::NameTable(ppu) => ppu.render_name_table(&mut name_table_frame),
|
||||
DbgImage::PatternTable(ppu) => ppu.render_pattern_tables(&mut name_table_frame),
|
||||
DbgImage::Palette(ppu) => ppu.render_palette(&mut name_table_frame),
|
||||
}
|
||||
vec![name_table_frame.into_geometry()]
|
||||
}
|
||||
}
|
||||
|
||||
107
src/mem.rs
107
src/mem.rs
@@ -1,4 +1,4 @@
|
||||
use crate::hex_view::Memory;
|
||||
use crate::{hex_view::Memory, ppu::PPUMMRegisters};
|
||||
|
||||
pub enum Value<'a, R> {
|
||||
Value(u8),
|
||||
@@ -71,7 +71,10 @@ pub struct MemoryMap<R> {
|
||||
|
||||
impl<R> MemoryMap<R> {
|
||||
pub fn new(segments: Vec<Segment<R>>) -> Self {
|
||||
Self { edit_ver: 0, segments }
|
||||
Self {
|
||||
edit_ver: 0,
|
||||
segments,
|
||||
}
|
||||
}
|
||||
pub fn read(&self, addr: u16) -> Value<'_, R> {
|
||||
// self.edit_ver += 1;
|
||||
@@ -88,12 +91,13 @@ impl<R> MemoryMap<R> {
|
||||
let offset = addr - segment.position;
|
||||
let s = &self.segments[*index];
|
||||
self.read(s.position + offset % s.size)
|
||||
}
|
||||
// Data::Disabled() => todo!(),
|
||||
} // Data::Disabled() => todo!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
todo!("Open bus")
|
||||
// TODO: Open bus
|
||||
Value::Value(0)
|
||||
// todo!("Open bus")
|
||||
}
|
||||
|
||||
pub fn write(&mut self, addr: u16, val: u8, reg_fn: impl FnOnce(&R, u16, u8)) {
|
||||
@@ -111,8 +115,7 @@ impl<R> MemoryMap<R> {
|
||||
let index = *index;
|
||||
let s = &self.segments[index];
|
||||
self.write(s.position + offset % s.size, val, reg_fn)
|
||||
}
|
||||
// Data::Disabled() => todo!(),
|
||||
} // Data::Disabled() => todo!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -129,12 +132,50 @@ impl<R> MemoryMap<R> {
|
||||
}
|
||||
|
||||
pub(crate) fn rom(&self, idx: usize) -> Option<&[u8]> {
|
||||
if let Some(Segment { mem: Data::ROM(val), .. }) = self.segments.get(idx) {
|
||||
if let Some(Segment {
|
||||
mem: Data::ROM(val),
|
||||
..
|
||||
}) = self.segments.get(idx)
|
||||
{
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn rom_or_ram(&self, idx: usize) -> 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> {
|
||||
@@ -149,8 +190,7 @@ impl<R> Memory for MemoryMap<R> {
|
||||
let offset = addr - segment.position;
|
||||
let s = &self.segments[*index];
|
||||
self.peek(s.position + offset % s.size)
|
||||
}
|
||||
// Data::Disabled() => todo!(),
|
||||
} // Data::Disabled() => todo!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -161,3 +201,50 @@ impl<R> Memory for MemoryMap<R> {
|
||||
self.edit_ver
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Mapper {
|
||||
horizontal_name_table: bool,
|
||||
}
|
||||
|
||||
impl Mapper {
|
||||
pub fn from_flags(flags: u8) -> Self {
|
||||
if flags & 0b11111110 != 0 {
|
||||
todo!("Support other mapper flags");
|
||||
}
|
||||
Self {
|
||||
horizontal_name_table: flags & (1 << 0) == 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ppu_map(&self, rom: &[u8]) -> MemoryMap<PPUMMRegisters> {
|
||||
let chr = if rom.len() == 0 {
|
||||
Segment::ram("CHR RAM", 0x0000, 0x2000)
|
||||
} else {
|
||||
Segment::rom("CHR ROM", 0x0000, rom)
|
||||
};
|
||||
if self.horizontal_name_table {
|
||||
MemoryMap::new(vec![
|
||||
chr,
|
||||
Segment::ram("Internal VRAM", 0x2000, 0x400),
|
||||
Segment::ram("Internal VRAM", 0x2400, 0x400),
|
||||
Segment::mirror("Internal VRAM", 0x2800, 0x400, 1),
|
||||
Segment::mirror("Internal VRAM", 0x2C00, 0x400, 2),
|
||||
Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1),
|
||||
Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette),
|
||||
Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 6),
|
||||
])
|
||||
} else {
|
||||
MemoryMap::new(vec![
|
||||
chr,
|
||||
Segment::ram("Internal VRAM", 0x2000, 0x400),
|
||||
Segment::mirror("Internal VRAM", 0x2400, 0x400, 1),
|
||||
Segment::ram("Internal VRAM", 0x2800, 0x400),
|
||||
Segment::mirror("Internal VRAM", 0x2C00, 0x400, 3),
|
||||
Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1),
|
||||
Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette),
|
||||
Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 6),
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
481
src/ppu.rs
481
src/ppu.rs
@@ -1,3 +1,5 @@
|
||||
use std::fmt;
|
||||
|
||||
use iced::{
|
||||
Point, Size,
|
||||
advanced::graphics::geometry::Renderer,
|
||||
@@ -6,10 +8,10 @@ use iced::{
|
||||
|
||||
use crate::{
|
||||
hex_view::Memory,
|
||||
mem::{MemoryMap, Segment},
|
||||
mem::{Mapper, MemoryMap, Segment},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Color {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
@@ -22,6 +24,13 @@ impl Into<Fill> for Color {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct RenderBuffer<const W: usize, const H: usize> {
|
||||
buffer: Box<[Color]>,
|
||||
}
|
||||
@@ -45,6 +54,22 @@ impl<const W: usize, const H: usize> RenderBuffer<W, H> {
|
||||
pub fn clone_from(&mut self, other: &Self) {
|
||||
self.buffer.copy_from_slice(&other.buffer);
|
||||
}
|
||||
|
||||
pub fn assert_eq(&self, other: &Self) {
|
||||
// if self.buffer != other.buffer {
|
||||
for y in 0..H {
|
||||
for x in 0..W {
|
||||
if self.read(y, x) != other.read(y, x) {
|
||||
panic!(
|
||||
"Rendered Buffers do not match\n Mismatch at ({x}, {y})\n Left: {}\n Right: {}",
|
||||
self.read(y, x),
|
||||
other.read(y, x)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum PPUMMRegisters {
|
||||
@@ -69,37 +94,40 @@ enum BackgroundState {
|
||||
|
||||
pub struct Background {
|
||||
/// Current vram address, 15 bits
|
||||
v: u16,
|
||||
pub v: u16,
|
||||
/// Temp vram address, 15 bits
|
||||
t: u16,
|
||||
pub t: u16,
|
||||
/// Fine X control, 3 bits
|
||||
x: u8,
|
||||
pub x: u8,
|
||||
/// Whether this is the first or second write to PPUSCROLL
|
||||
/// When false, writes to x
|
||||
w: bool,
|
||||
pub w: bool,
|
||||
|
||||
/// When true, v is incremented by 32 after each read
|
||||
vram_column: bool,
|
||||
pub vram_column: bool,
|
||||
pub second_pattern: bool,
|
||||
|
||||
state: BackgroundState,
|
||||
|
||||
cur_nametable: u8,
|
||||
cur_attr: u8,
|
||||
cur_high: u8,
|
||||
cur_low: u8,
|
||||
cur_shift_high: u8,
|
||||
cur_shift_low: u8,
|
||||
pub cur_nametable: u8,
|
||||
pub cur_attr: u8,
|
||||
pub next_attr: u8,
|
||||
pub next_attr_2: u8,
|
||||
pub cur_high: u8,
|
||||
pub cur_low: u8,
|
||||
pub cur_shift_high: u32,
|
||||
pub cur_shift_low: u32,
|
||||
}
|
||||
|
||||
pub struct Mask {
|
||||
grayscale: bool,
|
||||
background_on_left_edge: bool,
|
||||
sprites_on_left_edge: bool,
|
||||
enable_background: bool,
|
||||
enable_sprites: bool,
|
||||
em_red: bool,
|
||||
em_green: bool,
|
||||
em_blue: bool,
|
||||
pub grayscale: bool,
|
||||
pub background_on_left_edge: bool,
|
||||
pub sprites_on_left_edge: bool,
|
||||
pub enable_background: bool,
|
||||
pub enable_sprites: bool,
|
||||
pub em_red: bool,
|
||||
pub em_green: bool,
|
||||
pub em_blue: bool,
|
||||
}
|
||||
|
||||
const COLORS: &'static [Color; 0b100_0000] = &[
|
||||
@@ -431,9 +459,9 @@ pub struct Palette {
|
||||
}
|
||||
|
||||
impl Palette {
|
||||
pub fn color(&self, idx: u8) -> Color {
|
||||
pub fn color(&self, idx: u8, palette: u8) -> Color {
|
||||
debug_assert!(idx < 0x20, "Palette index out of range");
|
||||
self.colors[(self.ram[idx as usize] & 0x3F) as usize]
|
||||
self.colors[(self.ram[idx as usize + palette as usize * 4] & 0x3F) as usize]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,7 +469,7 @@ pub struct PPU {
|
||||
// registers: PPURegisters,
|
||||
frame_count: usize,
|
||||
nmi_enabled: bool,
|
||||
nmi_waiting: bool,
|
||||
// nmi_waiting: bool,
|
||||
pub even: bool,
|
||||
pub scanline: usize,
|
||||
pub pixel: usize,
|
||||
@@ -451,7 +479,7 @@ pub struct PPU {
|
||||
|
||||
pub(crate) memory: MemoryMap<PPUMMRegisters>,
|
||||
palette: Palette,
|
||||
background: Background,
|
||||
pub background: Background,
|
||||
oam: OAM,
|
||||
pub render_buffer: RenderBuffer<256, 240>,
|
||||
pub dbg_int: bool,
|
||||
@@ -477,7 +505,7 @@ impl std::fmt::Debug for PPU {
|
||||
}
|
||||
|
||||
impl PPU {
|
||||
pub fn with_chr_rom(rom: &[u8]) -> Self {
|
||||
pub fn with_chr_rom(rom: &[u8], mapper: Mapper) -> Self {
|
||||
Self {
|
||||
cycle: 25,
|
||||
dbg_int: false,
|
||||
@@ -503,24 +531,19 @@ impl PPU {
|
||||
vblank: false,
|
||||
frame_count: 0,
|
||||
nmi_enabled: false,
|
||||
nmi_waiting: false,
|
||||
// nmi_waiting: false,
|
||||
even: false,
|
||||
scanline: 0,
|
||||
pixel: 25,
|
||||
render_buffer: RenderBuffer::empty(),
|
||||
memory: MemoryMap::new(vec![
|
||||
Segment::rom("CHR ROM", 0x0000, rom),
|
||||
Segment::ram("Internal VRAM", 0x2000, 0x1000),
|
||||
Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1),
|
||||
Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette),
|
||||
Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 3),
|
||||
]),
|
||||
memory: mapper.ppu_map(rom),
|
||||
background: Background {
|
||||
v: 0,
|
||||
t: 0,
|
||||
x: 0,
|
||||
w: false,
|
||||
vram_column: false,
|
||||
second_pattern: false,
|
||||
state: BackgroundState::NameTableBytePre,
|
||||
cur_high: 0,
|
||||
cur_low: 0,
|
||||
@@ -528,6 +551,8 @@ impl PPU {
|
||||
cur_shift_low: 0,
|
||||
cur_nametable: 0,
|
||||
cur_attr: 0,
|
||||
next_attr: 0,
|
||||
next_attr_2: 0,
|
||||
},
|
||||
oam: OAM {
|
||||
mem: vec![0u8; 256],
|
||||
@@ -538,7 +563,7 @@ impl PPU {
|
||||
pub fn reset(&mut self) {
|
||||
*self = Self {
|
||||
memory: std::mem::replace(&mut self.memory, MemoryMap::new(vec![])),
|
||||
..Self::with_chr_rom(&[])
|
||||
..Self::with_chr_rom(&[], Mapper::from_flags(0))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -561,6 +586,7 @@ impl PPU {
|
||||
5 => panic!("ppuscroll is write-only"),
|
||||
6 => panic!("ppuaddr is write-only"),
|
||||
7 => {
|
||||
// println!("Updating v for ppudata read");
|
||||
let val = self
|
||||
.memory
|
||||
.read(self.background.v)
|
||||
@@ -568,10 +594,7 @@ impl PPU {
|
||||
PPUMMRegisters::Palette => self.palette.ram[off as usize],
|
||||
});
|
||||
// if self.background
|
||||
self.background.v = self
|
||||
.background
|
||||
.v
|
||||
.wrapping_add(if self.background.vram_column { 32 } else { 1 });
|
||||
self.increment_v();
|
||||
val
|
||||
}
|
||||
// 7 => self.registers.data,
|
||||
@@ -584,7 +607,9 @@ impl PPU {
|
||||
self.nmi_enabled = val & 0b1000_0000 != 0;
|
||||
self.background.t =
|
||||
(self.background.t & 0b0001_1000_0000_0000) | (((val & 0b11) as u16) << 10);
|
||||
self.background.vram_column = val & 0b0000_0010 != 0;
|
||||
// self.background.vram_column = val & 0b0000_0010 != 0;
|
||||
self.background.vram_column = val & 0b0000_0100 != 0;
|
||||
self.background.second_pattern = val & 0b0001_0000 != 0;
|
||||
// TODO: other control fields
|
||||
}
|
||||
0x01 => {
|
||||
@@ -628,20 +653,43 @@ impl PPU {
|
||||
if self.background.w {
|
||||
self.background.v =
|
||||
u16::from_le_bytes([val, self.background.v.to_le_bytes()[1]]);
|
||||
self.background.w = true;
|
||||
self.background.w = false;
|
||||
} else {
|
||||
self.background.v =
|
||||
u16::from_le_bytes([self.background.v.to_le_bytes()[0], val & 0b0011_1111]);
|
||||
self.background.w = true;
|
||||
}
|
||||
// println!("Updating v for ppuaddr write: to {:04X}", self.background.v);
|
||||
// self.dbg_int = true;
|
||||
// todo!("PPUADDR write")
|
||||
}
|
||||
0x07 => {
|
||||
// TODO: ppu data
|
||||
// println!("Writing: {:02X}, @{:04X}", val, self.background.v);
|
||||
self.memory
|
||||
.write(self.background.v, val, |r, o, v| match r {
|
||||
PPUMMRegisters::Palette => {
|
||||
// println!("Writing {:02X} to {:02X}", v & 0x3F, o);
|
||||
self.palette.ram[o as usize] = v & 0x3F;
|
||||
}
|
||||
});
|
||||
self.increment_v();
|
||||
// self.background.v += 1; // TODO: implement inc behavior
|
||||
}
|
||||
_ => panic!("No register at {:02X}", offset),
|
||||
}
|
||||
// TODO: use data in PPU
|
||||
}
|
||||
/// Apply either row wise or column wise increment
|
||||
fn increment_v(&mut self) {
|
||||
if self.background.vram_column {
|
||||
// if self.background.v
|
||||
self.background.v += 32;
|
||||
} else {
|
||||
self.background.v += 1;
|
||||
}
|
||||
// self.background.v = self
|
||||
// .background
|
||||
// .v
|
||||
// .wrapping_add(if self.background.vram_column { 32 } else { 1 });
|
||||
}
|
||||
// pub fn write_oamdma(&mut self, val: u8) {
|
||||
// // TODO: OAM high addr
|
||||
@@ -662,90 +710,140 @@ impl PPU {
|
||||
self.scanline += 1;
|
||||
}
|
||||
if self.mask.enable_background || self.mask.enable_sprites {
|
||||
if self.pixel == 257 {
|
||||
self.background.v = (self.background.v & 0b0111_1011_1110_0000)
|
||||
| (self.background.t & 0b0000_0100_0001_1111);
|
||||
}
|
||||
if self.pixel == 280 && self.scanline == 260 {
|
||||
self.background.v = (self.background.v & 0b0000_0100_0001_1111)
|
||||
| (self.background.t & 0b0111_1011_1110_0000);
|
||||
}
|
||||
if self.scanline < 240 || self.scanline == 261 {
|
||||
if self.pixel == 257 {
|
||||
self.background.v = (self.background.v & 0b0111_1011_1110_0000)
|
||||
| (self.background.t & 0b0000_0100_0001_1111);
|
||||
}
|
||||
if self.pixel >= 280 && self.pixel <= 304 && self.scanline == 261 {
|
||||
self.background.v = (self.background.v & 0b0000_0100_0001_1111)
|
||||
| (self.background.t & 0b0111_1011_1110_0000);
|
||||
}
|
||||
if self.pixel > 280 && self.pixel < 320 {
|
||||
self.background.cur_shift_high <<= 1;
|
||||
self.background.cur_shift_low <<= 1;
|
||||
}
|
||||
// TODO
|
||||
if self.pixel == 0 {
|
||||
// self.dbg_int = true;
|
||||
// idle cycle
|
||||
} else if self.pixel < 257 {
|
||||
} else if self.pixel < 257 || self.pixel > 320 {
|
||||
// self.dbg_int = true;
|
||||
if self.scanline < 240 {
|
||||
// Determine background color
|
||||
let a = self.background.cur_shift_high & 0x80;
|
||||
let b = self.background.cur_shift_low & 0x80;
|
||||
let val = (a >> 6) | (b >> 7);
|
||||
debug_assert!(val < 4);
|
||||
const POS: u32 = 1 << 15;
|
||||
// Determine background color
|
||||
let a = self.background.cur_shift_high & POS;
|
||||
let b = self.background.cur_shift_low & POS;
|
||||
let val = (a >> 14) | (b >> 15);
|
||||
debug_assert!(val < 4);
|
||||
|
||||
let h_off = ((self.pixel - 1) / 16) % 2;
|
||||
let v_off = (self.scanline / 16) % 2;
|
||||
let off = v_off * 4 + h_off * 2;
|
||||
let palette = (self.background.cur_attr >> off) & 0x3;
|
||||
|
||||
if self.scanline < 240 && self.pixel < 257 {
|
||||
// Write to screen
|
||||
self.render_buffer.write(
|
||||
self.scanline,
|
||||
self.pixel - 1,
|
||||
self.palette.color(val)
|
||||
// self.palette.colors[val as usize],
|
||||
self.palette
|
||||
.color(val as u8, if val != 0 { palette } else { 0 }), // self.palette.colors[val as usize],
|
||||
); // TODO: this should come from shift registers
|
||||
}
|
||||
if self.pixel < 337 {
|
||||
self.background.cur_shift_high <<= 1;
|
||||
self.background.cur_shift_low <<= 1;
|
||||
}
|
||||
self.background.state = match self.background.state {
|
||||
BackgroundState::NameTableByte => {
|
||||
// TODO: Fetch name table byte
|
||||
let addr = 0x2000 + self.pixel / 8 + self.scanline / 8;
|
||||
let val = self.memory.read(addr as u16).reg_map(|_, _| todo!());
|
||||
self.background.cur_nametable = val;
|
||||
// self.background.v;
|
||||
BackgroundState::AttrTableBytePre
|
||||
|
||||
if self.scanline < 240 || self.scanline == 261 {
|
||||
if self.pixel <= 260 || self.pixel >= 321 {
|
||||
if self.pixel % 8 == 2 {
|
||||
// Name table fetch
|
||||
// let addr = 0x2000 + self.pixel / 8 + self.scanline / 8;
|
||||
let addr = 0x2000 | (self.background.v & 0x0FFF);
|
||||
// println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
|
||||
let val = self.memory.read(addr).reg_map(|_, _| 0);
|
||||
self.background.cur_nametable = val;
|
||||
} else if self.pixel % 8 == 4 {
|
||||
// Attr table fetch
|
||||
// let addr = 0x23C0 + self.pixel / 16 + self.scanline / 16;
|
||||
let addr = 0x23C0
|
||||
| (self.background.v & 0x0C00)
|
||||
| ((self.background.v >> 4) & 0x38)
|
||||
| ((self.background.v >> 2) & 0x07);
|
||||
// println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
|
||||
// assert_eq!(addr, addr_2);
|
||||
let val = self.memory.read(addr).reg_map(|_, _| 0); // TODO: handle reg reads
|
||||
self.background.next_attr_2 = val;
|
||||
} else if self.pixel % 8 == 6 {
|
||||
// BG pattern low
|
||||
let addr = self.background.cur_nametable as u16 * 16
|
||||
+ ((self.background.v & 0x7000) >> 12)
|
||||
// + (self.scanline % 8) as u16
|
||||
+ if self.background.second_pattern {
|
||||
0x1000
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let val = self.memory.read(addr).reg_map(|_, _| 0);
|
||||
self.background.cur_low = val;
|
||||
} else if self.pixel % 8 == 0 && self.pixel > 0 {
|
||||
let addr = self.background.cur_nametable as u16 * 16
|
||||
+ 8
|
||||
+ ((self.background.v & 0x7000) >> 12)
|
||||
// (self.scanline % 8) as u16
|
||||
+ if self.background.second_pattern {
|
||||
0x1000
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let val = self.memory.read(addr).reg_map(|_, _| todo!());
|
||||
self.background.cur_high = val;
|
||||
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_attr = self.background.next_attr;
|
||||
self.background.next_attr = self.background.next_attr_2;
|
||||
// Inc horizontal
|
||||
if self.background.v & 0x1F == 31 {
|
||||
self.background.v = (self.background.v & !0x1F) ^ 0x400;
|
||||
} else {
|
||||
self.background.v += 1;
|
||||
}
|
||||
// Inc vertical
|
||||
if self.pixel == 256 {
|
||||
// && self.scanline % 4 == 0
|
||||
if self.background.v & 0x7000 != 0x7000 {
|
||||
self.background.v += 0x1000;
|
||||
} else {
|
||||
self.background.v &= !0x7000;
|
||||
let mut y = (self.background.v & 0x03E0) >> 5;
|
||||
if y == 29 {
|
||||
y = 0;
|
||||
self.background.v ^= 0x0800
|
||||
} else if y == 31 {
|
||||
y = 0;
|
||||
} else {
|
||||
y += 1;
|
||||
}
|
||||
self.background.v =
|
||||
(self.background.v & !0x03E0) | (y << 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BackgroundState::AttrTableByte => {
|
||||
// TODO: Fetch attr table byte
|
||||
// let addr = 0x2000 + self.pixel / 8 + self.scanline / 8;
|
||||
// let val = self.memory.read(addr as u16).reg_map(|_, _| todo!());
|
||||
// self.background.cur_attr = val;
|
||||
BackgroundState::PatternTableTileLowPre
|
||||
}
|
||||
BackgroundState::PatternTableTileLow => {
|
||||
// TODO: Fetch
|
||||
let addr = self.background.cur_nametable as u16 * 16
|
||||
+ (self.scanline % 8) as u16;
|
||||
let val = self.memory.read(addr).reg_map(|_, _| todo!());
|
||||
self.background.cur_low = val;
|
||||
BackgroundState::PatternTableTileHighPre
|
||||
}
|
||||
BackgroundState::PatternTableTileHigh => {
|
||||
// TODO: Fetch
|
||||
let addr = self.background.cur_nametable as u16 * 16
|
||||
+ 8
|
||||
+ (self.scanline % 8) as u16;
|
||||
let val = self.memory.read(addr).reg_map(|_, _| todo!());
|
||||
self.background.cur_high = val;
|
||||
self.background.cur_shift_low = self.background.cur_low;
|
||||
self.background.cur_shift_high = self.background.cur_high;
|
||||
BackgroundState::NameTableBytePre
|
||||
}
|
||||
BackgroundState::NameTableBytePre => BackgroundState::NameTableByte,
|
||||
BackgroundState::AttrTableBytePre => BackgroundState::AttrTableByte,
|
||||
BackgroundState::PatternTableTileLowPre => {
|
||||
BackgroundState::PatternTableTileLow
|
||||
}
|
||||
BackgroundState::PatternTableTileHighPre => {
|
||||
BackgroundState::PatternTableTileHigh
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// TODO: Sprite fetches
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.scanline == 261 && self.pixel == 1 {
|
||||
self.vblank = false;
|
||||
// TODO: clear sprite 0 & sprite overflow
|
||||
}
|
||||
if self.scanline == 241 && self.pixel == 1 {
|
||||
self.vblank = true;
|
||||
self.nmi_waiting = self.nmi_enabled;
|
||||
// self.nmi_waiting = self.nmi_enabled;
|
||||
self.frame_count += 1;
|
||||
self.background.state = BackgroundState::NameTableBytePre;
|
||||
return true;
|
||||
@@ -753,52 +851,80 @@ impl PPU {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn nmi_on_vblank(&self) -> bool {
|
||||
self.nmi_enabled
|
||||
}
|
||||
pub fn peek_nmi(&self) -> bool {
|
||||
self.nmi_waiting
|
||||
self.vblank && self.nmi_enabled
|
||||
}
|
||||
pub fn nmi_waiting(&mut self) -> bool {
|
||||
if self.nmi_waiting {
|
||||
self.nmi_waiting = false;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
self.vblank && self.nmi_enabled
|
||||
// if self.nmi_waiting {
|
||||
// self.nmi_waiting = false;
|
||||
// return true;
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
pub fn peek_irq(&self) -> bool {
|
||||
self.nmi_waiting
|
||||
false
|
||||
}
|
||||
pub fn irq_waiting(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn render_name_table<R: Renderer>(&self, frame: &mut Frame<R>) {
|
||||
for y in 0..280 / 8 {
|
||||
for x in 0..512 / 8 {
|
||||
let name = self.memory.peek(x + y * 512 / 8 + 0x2000).unwrap() as u16 * 16;
|
||||
for y in 0..60 {
|
||||
for x in 0..64 {
|
||||
let row = y % 30;
|
||||
let col = 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
|
||||
+ if self.background.second_pattern {
|
||||
0x1000
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let attr = self
|
||||
.memory
|
||||
.peek((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
|
||||
.unwrap();
|
||||
// attr << (((col & 1) << 1) | ((row & 1) << 0)) * 2
|
||||
// let h_off = ((self.pixel - 1) / 16) % 2;
|
||||
// let v_off = (self.scanline / 16) % 2;
|
||||
// let off = v_off * 4 + h_off * 2;
|
||||
let palette = (attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3;
|
||||
for y_off in 0..8 {
|
||||
let low = self.memory.peek(name + y_off).unwrap();
|
||||
let high = self.memory.peek(name + y_off + 8).unwrap();
|
||||
for bit in (0..8).rev() {
|
||||
for bit in 0..8 {
|
||||
frame.fill_rectangle(
|
||||
Point::new(x as f32 * 8. + bit as f32, y as f32 * 8. + y_off as f32),
|
||||
Point::new(
|
||||
x as f32 * 8. + 8. - bit as f32,
|
||||
y as f32 * 8. + y_off as f32,
|
||||
),
|
||||
Size::new(1., 1.),
|
||||
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
|
||||
(false, false) => Color { r: 0, g: 0, b: 0 },
|
||||
(true, false) => Color {
|
||||
r: 64,
|
||||
g: 64,
|
||||
b: 64,
|
||||
},
|
||||
(false, true) => Color {
|
||||
r: 128,
|
||||
g: 128,
|
||||
b: 128,
|
||||
},
|
||||
(true, true) => Color {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
},
|
||||
(false, false) => self.palette.color(0, 0),
|
||||
(true, false) => self.palette.color(1, palette),
|
||||
(false, true) => self.palette.color(2, palette),
|
||||
(true, true) => self.palette.color(3, palette),
|
||||
// (false, false) => Color { r: 0, g: 0, b: 0 },
|
||||
// (true, false) => Color {
|
||||
// r: 64,
|
||||
// g: 64,
|
||||
// b: 64,
|
||||
// },
|
||||
// (false, true) => Color {
|
||||
// r: 128,
|
||||
// g: 128,
|
||||
// b: 128,
|
||||
// },
|
||||
// (true, true) => Color {
|
||||
// r: 255,
|
||||
// g: 255,
|
||||
// b: 255,
|
||||
// },
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -808,6 +934,61 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn name_cursor_info(&self, cursor: Point) -> Option<String> {
|
||||
let x = (cursor.x / 8.) as usize;
|
||||
let y = (cursor.y / 8.) as usize;
|
||||
if x < 64 && y < 60 {
|
||||
let row = y % 30;
|
||||
let col = 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 attr = self
|
||||
.memory
|
||||
.peek((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
|
||||
.unwrap();
|
||||
Some(format!(
|
||||
"Row, Column: {}, {}
|
||||
X, Y: {}, {}
|
||||
Tilemap address: ${:04X}
|
||||
|
||||
Tile Index: ${:02X}
|
||||
Tile Address (PPU): ${:04X}
|
||||
Tile Address (CHR): ${:04X}
|
||||
|
||||
Palette Index {}
|
||||
Palette Address ${:04X}
|
||||
|
||||
Attribute Address ${:04X}
|
||||
Attribute data: ${:02X}
|
||||
",
|
||||
row,
|
||||
col,
|
||||
col * 8,
|
||||
row * 8,
|
||||
off + col + row * 32,
|
||||
name,
|
||||
name * 16
|
||||
+ if self.background.second_pattern {
|
||||
0x1000
|
||||
} else {
|
||||
0
|
||||
},
|
||||
name * 16
|
||||
+ if self.background.second_pattern {
|
||||
0x1000
|
||||
} else {
|
||||
0
|
||||
},
|
||||
(attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3,
|
||||
((attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3) as usize * 4 + 0x3F00,
|
||||
col / 4 + row / 4 * 8 + 0x3C0 + off,
|
||||
attr,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_pattern_tables<R: Renderer>(&self, frame: &mut Frame<R>) {
|
||||
for y in 0..16 {
|
||||
for x in 0..16 {
|
||||
@@ -885,6 +1066,20 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn pattern_cursor_info(&self, cursor: Point) -> Option<String> {
|
||||
let x = (cursor.x / 8.) as usize;
|
||||
let y = (cursor.y / 8.) as usize;
|
||||
if x < 16 && y < 32 {
|
||||
Some(format!(
|
||||
"Tile address (PPU): {:04X}\nTile address (CHR): {:04X}\nIndex: {:02X}",
|
||||
(y * 16 + x) * 16,
|
||||
(y * 16 + x) * 16,
|
||||
((y % 16) * 16 + x),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_palette<R: Renderer>(&self, frame: &mut Frame<R>) {
|
||||
for y in 0..8 {
|
||||
@@ -897,6 +1092,24 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn palette_cursor_info(&self, cursor: Point) -> Option<String> {
|
||||
let x = (cursor.x / 10.) as usize;
|
||||
let y = (cursor.y / 10.) as usize;
|
||||
if x < 4 && y < 8 {
|
||||
Some(format!(
|
||||
"Index: {:02X}\nValue: {:02X}\nColor code: {}",
|
||||
x + y * 4,
|
||||
self.palette.ram[x + y * 4] & 0x3F,
|
||||
self.palette.colors[(self.palette.ram[x + y * 4] & 0x3F) as usize],
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mem(&self) -> &impl Memory {
|
||||
&self.memory
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -905,7 +1118,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn ppu_registers() {
|
||||
let mut ppu = PPU::with_chr_rom(&[0u8; 8192]);
|
||||
let mut ppu = PPU::with_chr_rom(&[0u8; 8192], Mapper::from_flags(0));
|
||||
assert_eq!(ppu.background.v, 0);
|
||||
assert_eq!(ppu.background.t, 0);
|
||||
assert_eq!(ppu.background.x, 0);
|
||||
|
||||
Binary file not shown.
13
src/test_roms/basic-cpu-nop.s
Normal file
13
src/test_roms/basic-cpu-nop.s
Normal file
@@ -0,0 +1,13 @@
|
||||
.include "testing.s"
|
||||
|
||||
reset:
|
||||
sed
|
||||
nop
|
||||
stp
|
||||
|
||||
nmi:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
; FINAL = ""
|
||||
|
||||
.inesprg 2 ; 2 banks
|
||||
.ineschr 1 ;
|
||||
.inesmap 0 ; mapper 0 = NROM
|
||||
.inesmir 0 ; background mirroring, horizontal
|
||||
|
||||
.org $8000
|
||||
RESET:
|
||||
sed
|
||||
hlt
|
||||
ERROR_:
|
||||
hlt
|
||||
|
||||
IGNORE:
|
||||
rti
|
||||
|
||||
.org $FFFA ; Interrupt vectors go here:
|
||||
.word IGNORE ; NMI
|
||||
.word RESET ; Reset
|
||||
.word IGNORE; IRQ
|
||||
|
||||
;;;; NESASM COMPILER STUFF, ADDING THE PATTERN DATA ;;;;
|
||||
|
||||
.incbin "Sprites.pcx"
|
||||
.incbin "Tiles.pcx"
|
||||
13
src/test_roms/basic-cpu.s
Normal file
13
src/test_roms/basic-cpu.s
Normal file
@@ -0,0 +1,13 @@
|
||||
.include "testing.s"
|
||||
|
||||
reset:
|
||||
sed
|
||||
stp
|
||||
stp
|
||||
|
||||
nmi:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
.inesprg 2 ; 2 banks
|
||||
.ineschr 1 ;
|
||||
.inesmap 0 ; mapper 0 = NROM
|
||||
.inesmir 0 ; background mirroring, horizontal
|
||||
.include "testing.s"
|
||||
|
||||
.org $8000
|
||||
RESET:
|
||||
reset:
|
||||
sei ; Ignore IRQs while starting up
|
||||
cld ; disabled decimal mode (iirc it doesn't work properly on NES anyway)
|
||||
ldx #$40
|
||||
@@ -23,21 +19,11 @@ RESET:
|
||||
; VBLANKWAIT2:
|
||||
; bit $2002
|
||||
; bpl VBLANKWAIT2
|
||||
hlt
|
||||
hlt
|
||||
stp
|
||||
stp
|
||||
|
||||
ERROR_:
|
||||
hlt
|
||||
nmi:
|
||||
stp
|
||||
|
||||
IGNORE:
|
||||
rti
|
||||
|
||||
.org $FFFA ; Interrupt vectors go here:
|
||||
.word IGNORE ; NMI
|
||||
.word RESET ; Reset
|
||||
.word IGNORE; IRQ
|
||||
|
||||
;;;; NESASM COMPILER STUFF, ADDING THE PATTERN DATA ;;;;
|
||||
|
||||
.incbin "Sprites.pcx"
|
||||
.incbin "Tiles.pcx"
|
||||
irq:
|
||||
stp
|
||||
@@ -1,43 +0,0 @@
|
||||
.inesprg 2 ; 2 banks
|
||||
.ineschr 1 ;
|
||||
.inesmap 0 ; mapper 0 = NROM
|
||||
.inesmir 0 ; background mirroring, horizontal
|
||||
|
||||
.org $8000
|
||||
RESET:
|
||||
sei ; Ignore IRQs while starting up
|
||||
cld ; disabled decimal mode (iirc it doesn't work properly on NES anyway)
|
||||
ldx #$40
|
||||
stx $4017 ; Disable APU frame IRQ
|
||||
ldx #$ff
|
||||
txs ; Set stack pointer to 0x1ff
|
||||
inx ; Set x to zero
|
||||
stx $2000 ; Disable NMI (by writing zero)
|
||||
stx $2001 ; Disable rendering
|
||||
stx $4010 ; Disable DMC IRQs
|
||||
|
||||
bit $2002 ; Clear vblank flag by reading ppu status
|
||||
VBLANKWAIT1:
|
||||
bit $2002
|
||||
bpl VBLANKWAIT1
|
||||
; VBLANKWAIT2:
|
||||
; bit $2002
|
||||
; bpl VBLANKWAIT2
|
||||
hlt
|
||||
hlt
|
||||
|
||||
ERROR_:
|
||||
hlt
|
||||
|
||||
IGNORE:
|
||||
rti
|
||||
|
||||
.org $FFFA ; Interrupt vectors go here:
|
||||
.word IGNORE ; NMI
|
||||
.word RESET ; Reset
|
||||
.word IGNORE; IRQ
|
||||
|
||||
;;;; NESASM COMPILER STUFF, ADDING THE PATTERN DATA ;;;;
|
||||
|
||||
.incbin "Sprites.pcx"
|
||||
.incbin "Tiles.pcx"
|
||||
26
src/test_roms/basic_init_1.s
Normal file
26
src/test_roms/basic_init_1.s
Normal file
@@ -0,0 +1,26 @@
|
||||
.include "testing.s"
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld ; disabled decimal mode (iirc it doesn't work properly on NES anyway)
|
||||
ldx #$40
|
||||
stx $4017 ; Disable APU frame IRQ
|
||||
ldx #$ff
|
||||
txs ; Set stack pointer to 0x1ff
|
||||
inx ; Set x to zero
|
||||
stx $2000 ; Disable NMI (by writing zero)
|
||||
stx $2001 ; Disable rendering
|
||||
stx $4010 ; Disable DMC IRQs
|
||||
|
||||
bit $2002 ; Clear vblank flag by reading ppu status
|
||||
VBLANKWAIT1:
|
||||
bit $2002
|
||||
bpl VBLANKWAIT1
|
||||
stp
|
||||
stp
|
||||
|
||||
nmi:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
@@ -1,10 +1,6 @@
|
||||
.inesprg 2 ; 2 banks
|
||||
.ineschr 1 ;
|
||||
.inesmap 0 ; mapper 0 = NROM
|
||||
.inesmir 0 ; background mirroring, horizontal
|
||||
.include "testing.s"
|
||||
|
||||
.org $8000
|
||||
RESET:
|
||||
reset:
|
||||
sei ; Ignore IRQs while starting up
|
||||
cld ; disabled decimal mode (iirc it doesn't work properly on NES anyway)
|
||||
ldx #$40
|
||||
@@ -23,21 +19,11 @@ VBLANKWAIT1:
|
||||
VBLANKWAIT2:
|
||||
bit $2002
|
||||
bpl VBLANKWAIT2
|
||||
hlt
|
||||
hlt
|
||||
stp
|
||||
stp
|
||||
|
||||
ERROR_:
|
||||
hlt
|
||||
nmi:
|
||||
stp
|
||||
|
||||
IGNORE:
|
||||
rti
|
||||
|
||||
.org $FFFA ; Interrupt vectors go here:
|
||||
.word IGNORE ; NMI
|
||||
.word RESET ; Reset
|
||||
.word IGNORE; IRQ
|
||||
|
||||
;;;; NESASM COMPILER STUFF, ADDING THE PATTERN DATA ;;;;
|
||||
|
||||
.incbin "Sprites.pcx"
|
||||
.incbin "Tiles.pcx"
|
||||
irq:
|
||||
stp
|
||||
@@ -1,10 +1,6 @@
|
||||
.inesprg 2 ; 2 banks
|
||||
.ineschr 1 ;
|
||||
.inesmap 0 ; mapper 0 = NROM
|
||||
.inesmir 0 ; background mirroring, horizontal
|
||||
.include "testing.s"
|
||||
|
||||
.org $8000
|
||||
RESET:
|
||||
reset:
|
||||
sei ; Ignore IRQs while starting up
|
||||
cld ; disabled decimal mode (iirc it doesn't work properly on NES anyway)
|
||||
ldx #$40
|
||||
@@ -26,21 +22,9 @@ VBLANKWAIT2:
|
||||
VBLANKWAIT3:
|
||||
bit $2002
|
||||
bpl VBLANKWAIT3
|
||||
hlt
|
||||
hlt
|
||||
stp
|
||||
nmi:
|
||||
stp
|
||||
|
||||
ERROR_:
|
||||
hlt
|
||||
|
||||
IGNORE:
|
||||
rti
|
||||
|
||||
.org $FFFA ; Interrupt vectors go here:
|
||||
.word IGNORE ; NMI
|
||||
.word RESET ; Reset
|
||||
.word IGNORE; IRQ
|
||||
|
||||
;;;; NESASM COMPILER STUFF, ADDING THE PATTERN DATA ;;;;
|
||||
|
||||
.incbin "Sprites.pcx"
|
||||
.incbin "Tiles.pcx"
|
||||
irq:
|
||||
stp
|
||||
@@ -1,14 +1,14 @@
|
||||
; Included at beginning of program
|
||||
|
||||
.macro stp
|
||||
.byte #$02
|
||||
.byte $02
|
||||
.endmacro
|
||||
.include "macros.inc"
|
||||
.include "neshw.inc"
|
||||
|
||||
.ifdef CUSTOM_PREFIX
|
||||
.include "custom_prefix.s"
|
||||
.endif
|
||||
; .ifdef CUSTOM_PREFIX
|
||||
; .include "custom_prefix.s"
|
||||
; .endif
|
||||
|
||||
; ; Devcart
|
||||
; .ifdef BUILD_DEVCART
|
||||
@@ -16,9 +16,9 @@
|
||||
; .endif
|
||||
|
||||
; NES internal RAM
|
||||
.ifdef BUILD_NOCART
|
||||
.include "build_nocart.s"
|
||||
.endif
|
||||
; .ifdef BUILD_NOCART
|
||||
; .include "build_nocart.s"
|
||||
; .endif
|
||||
|
||||
; NES ROM (default)
|
||||
.ifndef SHELL_INCLUDED
|
||||
|
||||
@@ -1,106 +1,36 @@
|
||||
; Utilities for writing test ROMs
|
||||
|
||||
; In NVRAM so these can be used before initializing runtime,
|
||||
; then runtime initialized without clearing them
|
||||
nv_res test_code ; code of current test
|
||||
nv_res test_name,2 ; address of name of current test, or 0 of none
|
||||
|
||||
|
||||
; Sets current test code and optional name. Also resets
|
||||
; checksum.
|
||||
; Preserved: A, X, Y
|
||||
.macro set_test code,name
|
||||
pha
|
||||
lda #code
|
||||
jsr set_test_
|
||||
.ifblank name
|
||||
setb test_name+1,0
|
||||
.else
|
||||
.local Addr
|
||||
setw test_name,Addr
|
||||
seg_data "RODATA",{Addr: .byte name,0}
|
||||
.endif
|
||||
pla
|
||||
; testing.inc
|
||||
;
|
||||
.macro stp
|
||||
.byte $02
|
||||
.endmacro
|
||||
.macro zp_res name,size
|
||||
.pushseg
|
||||
.segment "ZEROPAGE"
|
||||
name:
|
||||
.ifblank size
|
||||
.res 1
|
||||
.else
|
||||
.res size
|
||||
.endif
|
||||
.popseg
|
||||
.endmacro
|
||||
|
||||
set_test_:
|
||||
sta test_code
|
||||
jmp reset_crc
|
||||
.include "neshw.inc"
|
||||
|
||||
; ROM parts
|
||||
.segment "HEADER"
|
||||
.byte $4E,$45,$53,26 ; "NES" EOF
|
||||
.byte 2,1 ; 32K PRG, 8K CHR
|
||||
.byte $01 ; vertical mirroring
|
||||
.segment "VECTORS"
|
||||
.word $FFFF,$FFFF,$FFFF, nmi, reset, irq
|
||||
|
||||
; Initializes testing module
|
||||
init_testing:
|
||||
jmp init_crc
|
||||
|
||||
|
||||
; Reports that all tests passed
|
||||
tests_passed:
|
||||
jsr print_filename
|
||||
print_str newline,"Passed"
|
||||
lda #0
|
||||
jmp exit
|
||||
|
||||
|
||||
; Reports "Done" if set_test has never been used,
|
||||
; "Passed" if set_test 0 was last used, or
|
||||
; failure if set_test n was last used.
|
||||
tests_done:
|
||||
ldx test_code
|
||||
jeq tests_passed
|
||||
inx
|
||||
bne test_failed
|
||||
jsr print_filename
|
||||
print_str newline,"Done"
|
||||
lda #0
|
||||
jmp exit
|
||||
|
||||
|
||||
; Reports that the current test failed. Prints code and
|
||||
; name last set with set_test, or just "Failed" if none
|
||||
; have been set yet.
|
||||
test_failed:
|
||||
ldx test_code
|
||||
|
||||
; Treat $FF as 1, in case it wasn't ever set
|
||||
inx
|
||||
bne :+
|
||||
inx
|
||||
stx test_code
|
||||
:
|
||||
; If code >= 2, print name
|
||||
cpx #2-1 ; -1 due to inx above
|
||||
blt :+
|
||||
lda test_name+1
|
||||
beq :+
|
||||
jsr print_newline
|
||||
sta addr+1
|
||||
lda test_name
|
||||
sta addr
|
||||
jsr print_str_addr
|
||||
jsr print_newline
|
||||
:
|
||||
jsr print_filename
|
||||
|
||||
; End program
|
||||
lda test_code
|
||||
jmp exit
|
||||
|
||||
|
||||
; If checksum doesn't match expected, reports failed test.
|
||||
; Clears checksum afterwards.
|
||||
; Preserved: A, X, Y
|
||||
.macro check_crc expected
|
||||
jsr_with_addr check_crc_,{.dword expected}
|
||||
.macro patterns_bin name
|
||||
.pushseg
|
||||
.segment "CHARS"
|
||||
.incbin name
|
||||
.popseg
|
||||
.endmacro
|
||||
|
||||
check_crc_:
|
||||
pha
|
||||
jsr is_crc_
|
||||
bne :+
|
||||
jsr reset_crc
|
||||
pla
|
||||
rts
|
||||
.segment "CODE"
|
||||
|
||||
: jsr print_newline
|
||||
jsr print_crc
|
||||
jmp test_failed
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
.inesprg 2 ; 2 banks
|
||||
.ineschr 1 ;
|
||||
.inesmap 0 ; mapper 0 = NROM
|
||||
.inesmir 0 ; background mirroring, horizontal
|
||||
.include "testing.s"
|
||||
|
||||
.org $8000
|
||||
RESET:
|
||||
reset:
|
||||
sei ; Ignore IRQs while starting up
|
||||
cld ; disabled decimal mode (iirc it doesn't work properly on NES anyway)
|
||||
ldx #$40
|
||||
@@ -24,21 +20,11 @@ VBLANKWAIT1:
|
||||
VBLANKWAIT2:
|
||||
bit $2002
|
||||
bpl VBLANKWAIT2
|
||||
hlt
|
||||
hlt
|
||||
stp
|
||||
stp
|
||||
|
||||
ERROR_:
|
||||
hlt
|
||||
nmi:
|
||||
stp
|
||||
|
||||
IGNORE:
|
||||
rti
|
||||
|
||||
.org $FFFA ; Interrupt vectors go here:
|
||||
.word IGNORE ; NMI
|
||||
.word RESET ; Reset
|
||||
.word IGNORE; IRQ
|
||||
|
||||
;;;; NESASM COMPILER STUFF, ADDING THE PATTERN DATA ;;;;
|
||||
|
||||
.incbin "Sprites.pcx"
|
||||
.incbin "Tiles.pcx"
|
||||
irq:
|
||||
stp
|
||||
38
src/test_roms/int_nmi_exit_timing.s
Normal file
38
src/test_roms/int_nmi_exit_timing.s
Normal file
@@ -0,0 +1,38 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
.include "testing.s"
|
||||
|
||||
zp_res FLAG
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; Init PPU
|
||||
bit PPUSTATUS
|
||||
vwait1:
|
||||
bit PPUSTATUS
|
||||
bpl vwait1
|
||||
vwait2:
|
||||
bit PPUSTATUS
|
||||
bpl vwait2
|
||||
|
||||
lda #$00
|
||||
sta FLAG
|
||||
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
loop:
|
||||
lda FLAG
|
||||
beq loop
|
||||
stp
|
||||
|
||||
nmi:
|
||||
lda #$01
|
||||
sta FLAG
|
||||
rti
|
||||
|
||||
irq:
|
||||
stp
|
||||
31
src/test_roms/int_nmi_timing.s
Normal file
31
src/test_roms/int_nmi_timing.s
Normal file
@@ -0,0 +1,31 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
.include "testing.s"
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; Init PPU
|
||||
bit PPUSTATUS
|
||||
vwait1:
|
||||
bit PPUSTATUS
|
||||
bpl vwait1
|
||||
vwait2:
|
||||
bit PPUSTATUS
|
||||
bpl vwait2
|
||||
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
nmi:
|
||||
nop
|
||||
stp
|
||||
rti
|
||||
|
||||
irq:
|
||||
stp
|
||||
39
src/test_roms/int_nmi_while_nmi.s
Normal file
39
src/test_roms/int_nmi_while_nmi.s
Normal file
@@ -0,0 +1,39 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
.include "testing.s"
|
||||
|
||||
zp_res FLAG
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; Init PPU
|
||||
bit PPUSTATUS
|
||||
vwait1:
|
||||
bit PPUSTATUS
|
||||
bpl vwait1
|
||||
vwait2:
|
||||
bit PPUSTATUS
|
||||
bpl vwait2
|
||||
|
||||
lda #$00
|
||||
sta FLAG
|
||||
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
loop:
|
||||
lda FLAG
|
||||
beq loop
|
||||
stp
|
||||
|
||||
nmi:
|
||||
lda PPUSTATUS
|
||||
l:
|
||||
jmp l
|
||||
rti
|
||||
|
||||
irq:
|
||||
stp
|
||||
27
src/test_roms/interrupts.rs
Normal file
27
src/test_roms/interrupts.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::hex_view::Memory;
|
||||
|
||||
use super::rom_test;
|
||||
|
||||
rom_test!(int_nmi_timing, "int_nmi_timing.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x801B HLT :2 []");
|
||||
assert_eq!(nes.clock_count, 260881);
|
||||
assert_eq!(nes.cycle, 86967);
|
||||
assert_eq!(nes.ppu().pixel, 40);
|
||||
assert_eq!(nes.ppu().scanline, 241);
|
||||
assert_eq!(nes.ppu().cycle, 260905);
|
||||
assert_eq!(nes.cpu_mem().peek(0x1FF), Some(0x80));
|
||||
assert_eq!(nes.cpu_mem().peek(0x1FE), Some(0x17));
|
||||
assert_eq!(nes.cpu_mem().peek(0x1FD), Some(0xA4));
|
||||
});
|
||||
|
||||
rom_test!(int_nmi_exit_timing, "int_nmi_exit_timing.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x801F HLT :2 []");
|
||||
// assert_eq!(nes.clock_count, 260881);
|
||||
assert_eq!(nes.cycle, 86980);
|
||||
assert_eq!(nes.ppu().pixel, 79);
|
||||
assert_eq!(nes.ppu().scanline, 241);
|
||||
assert_eq!(nes.ppu().cycle, 260905 - 40 + 79);
|
||||
assert_eq!(nes.cpu_mem().peek(0x1FF), Some(0x80));
|
||||
assert_eq!(nes.cpu_mem().peek(0x1FE), Some(0x1B));
|
||||
assert_eq!(nes.cpu_mem().peek(0x1FD), Some(0x26));
|
||||
});
|
||||
@@ -1,5 +1,7 @@
|
||||
mod cpu_reset_ram;
|
||||
mod instr_test_v3;
|
||||
mod ppu;
|
||||
mod interrupts;
|
||||
|
||||
use crate::hex_view::Memory;
|
||||
|
||||
@@ -38,23 +40,35 @@ pub(crate) use rom_test;
|
||||
|
||||
rom_test!(basic_cpu, "basic-cpu.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8001 HLT :2 []");
|
||||
// Off by one from Mesen, since Mesen doesn't count the clock cycle attempting to execute the 'invalid' opcode
|
||||
assert_eq!(nes.cycle, 11);
|
||||
// This is off by one from Mesen, because Mesen is left pointing at the 'invalid' opcode
|
||||
assert_eq!(nes.cpu.pc, 0x8002);
|
||||
// Off by one from Mesen, since Mesen doesn't count the clock cycle attempting to execute the 'invalid' opcode
|
||||
assert_eq!(nes.ppu.pixel, 35);
|
||||
assert_eq!(nes.cycle, 10);
|
||||
assert_eq!(nes.cpu.pc, 0x8001);
|
||||
assert_eq!(nes.ppu.pixel, 34);
|
||||
|
||||
assert_eq!(nes.cpu.sp, 0xFD);
|
||||
|
||||
nes.repl_nop();
|
||||
nes.run_with_timeout(200);
|
||||
assert_eq!(nes.last_instruction, "0x8002 HLT :2 []");
|
||||
assert_eq!(nes.cycle, 12);
|
||||
assert_eq!(nes.cpu.pc, 0x8002);
|
||||
assert_eq!(nes.ppu.pixel, 40);
|
||||
|
||||
assert_eq!(nes.cpu.sp, 0xFD);
|
||||
});
|
||||
|
||||
rom_test!(basic_cpu_with_nop, "basic-cpu-nop.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8002 HLT :2 []");
|
||||
assert_eq!(nes.cycle, 12);
|
||||
assert_eq!(nes.cpu.pc, 0x8002);
|
||||
assert_eq!(nes.ppu.pixel, 40);
|
||||
|
||||
assert_eq!(nes.cpu.sp, 0xFD);
|
||||
// assert_eq!(nes.cpu.a, 0x00);
|
||||
// assert_eq!(nes.cpu.x, 0x00);
|
||||
// assert_eq!(nes.cpu.y, 0x00);
|
||||
});
|
||||
|
||||
rom_test!(read_write, "read_write.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8011 HLT :2 []");
|
||||
assert_eq!(nes.cycle, 31);
|
||||
assert_eq!(nes.cpu.pc, 0x8012);
|
||||
assert_eq!(nes.last_instruction, "0x800C HLT :2 []");
|
||||
assert_eq!(nes.cycle, 25);
|
||||
assert_eq!(nes.cpu.pc, 0x800C);
|
||||
assert_eq!(nes.cpu.sp, 0xFD);
|
||||
|
||||
assert_eq!(nes.cpu.a, 0xAA);
|
||||
@@ -67,8 +81,8 @@ rom_test!(read_write, "read_write.nes", |nes| {
|
||||
|
||||
rom_test!(basic_init_0, "basic_init_0.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8017 HLT :2 []");
|
||||
assert_eq!(nes.cycle, 41);
|
||||
assert_eq!(nes.cpu.pc, 0x8018);
|
||||
assert_eq!(nes.cycle, 40);
|
||||
assert_eq!(nes.cpu.pc, 0x8017);
|
||||
assert_eq!(nes.cpu.sp, 0xFF);
|
||||
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
@@ -78,9 +92,9 @@ rom_test!(basic_init_0, "basic_init_0.nes", |nes| {
|
||||
|
||||
rom_test!(basic_init_1, "basic_init_1.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x801C HLT :2 []");
|
||||
assert_eq!(nes.cycle, 27403);
|
||||
assert_eq!(nes.cpu.pc, 0x801D);
|
||||
assert_eq!(nes.ppu.pixel, 30);
|
||||
assert_eq!(nes.cycle, 27402);
|
||||
assert_eq!(nes.cpu.pc, 0x801C);
|
||||
assert_eq!(nes.ppu.pixel, 29);
|
||||
|
||||
assert_eq!(nes.cpu.sp, 0xFF);
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
@@ -90,9 +104,9 @@ rom_test!(basic_init_1, "basic_init_1.nes", |nes| {
|
||||
|
||||
rom_test!(basic_init_2, "basic_init_2.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8021 HLT :2 []");
|
||||
assert_eq!(nes.cycle, 57180);
|
||||
assert_eq!(nes.cpu.pc, 0x8022);
|
||||
assert_eq!(nes.ppu.pixel, 19);
|
||||
assert_eq!(nes.cycle, 57179);
|
||||
assert_eq!(nes.cpu.pc, 0x8021);
|
||||
assert_eq!(nes.ppu.pixel, 18);
|
||||
|
||||
assert_eq!(nes.cpu.sp, 0xFF);
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
@@ -102,9 +116,9 @@ rom_test!(basic_init_2, "basic_init_2.nes", |nes| {
|
||||
|
||||
rom_test!(basic_init_3, "basic_init_3.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8026 HLT :2 []");
|
||||
assert_eq!(nes.cycle, 86964);
|
||||
assert_eq!(nes.cpu.pc, 0x8027);
|
||||
assert_eq!(nes.ppu.pixel, 29);
|
||||
assert_eq!(nes.cycle, 86963);
|
||||
assert_eq!(nes.cpu.pc, 0x8026);
|
||||
assert_eq!(nes.ppu.pixel, 28);
|
||||
|
||||
assert_eq!(nes.cpu.sp, 0xFF);
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
@@ -114,9 +128,9 @@ rom_test!(basic_init_3, "basic_init_3.nes", |nes| {
|
||||
|
||||
rom_test!(even_odd, "even_odd.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8023 HLT :2 []");
|
||||
assert_eq!(nes.cycle, 57182);
|
||||
assert_eq!(nes.cpu.pc, 0x8024);
|
||||
assert_eq!(nes.ppu.pixel, 25);
|
||||
assert_eq!(nes.cycle, 57181);
|
||||
assert_eq!(nes.cpu.pc, 0x8023);
|
||||
assert_eq!(nes.ppu.pixel, 24);
|
||||
|
||||
assert_eq!(nes.cpu.sp, 0xFF);
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
|
||||
@@ -12,7 +12,7 @@ MEMORY
|
||||
FF00: start = $FF00, size = $F4, fill=yes, fillval=$FF;
|
||||
VECTORS:start = $FFF4, size = $C, fill=yes;
|
||||
|
||||
CHARS: start = 0, size = $3000, fillval=$FF;
|
||||
CHARS: start = 0, size = $3000, fill=yes, fillval=$FF;
|
||||
}
|
||||
|
||||
SEGMENTS
|
||||
|
||||
BIN
src/test_roms/pat.bin
Normal file
BIN
src/test_roms/pat.bin
Normal file
Binary file not shown.
90
src/test_roms/ppu.rs
Normal file
90
src/test_roms/ppu.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use crate::{hex_view::Memory, Color, RenderBuffer};
|
||||
|
||||
use super::rom_test;
|
||||
|
||||
const COLOR_01: Color = Color {
|
||||
r: 0x00,
|
||||
g: 0x2A,
|
||||
b: 0x88,
|
||||
};
|
||||
const COLOR_16: Color = Color {
|
||||
r: 0xB5,
|
||||
g: 0x31,
|
||||
b: 0x20,
|
||||
};
|
||||
const COLOR_0B: Color = Color {
|
||||
r: 0x00,
|
||||
g: 0x4F,
|
||||
b: 0x08,
|
||||
};
|
||||
|
||||
rom_test!(ppu_fill, "ppu_fill.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x802B HLT :2 []");
|
||||
assert_eq!(nes.cpu.a, 0x01);
|
||||
|
||||
let mut buffer = RenderBuffer::empty();
|
||||
for l in 0..240 {
|
||||
for p in 0..256 {
|
||||
buffer.write(l, p, COLOR_01);
|
||||
}
|
||||
}
|
||||
nes.ppu().render_buffer.assert_eq(&buffer);
|
||||
});
|
||||
|
||||
rom_test!(ppu_fill_red, "ppu_fill_red.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x803A HLT :2 []");
|
||||
assert_eq!(nes.cpu.a, 0x01);
|
||||
|
||||
let mut buffer = RenderBuffer::empty();
|
||||
for l in 0..240 {
|
||||
for p in 0..256 {
|
||||
buffer.write(l, p, COLOR_16);
|
||||
}
|
||||
}
|
||||
nes.ppu().render_buffer.assert_eq(&buffer);
|
||||
});
|
||||
|
||||
rom_test!(ppu_fill_palette_1, "ppu_fill_palette_1.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8064 HLT :2 []");
|
||||
assert_eq!(nes.cpu.a, 0x01);
|
||||
|
||||
let mut buffer = RenderBuffer::empty();
|
||||
for l in 0..240 {
|
||||
for p in 0..256 {
|
||||
if (p / 16) % 2 == (l / 16) % 2 {
|
||||
buffer.write(l, p, COLOR_01);
|
||||
} else {
|
||||
buffer.write(l, p, COLOR_16);
|
||||
}
|
||||
}
|
||||
}
|
||||
nes.ppu().render_buffer.assert_eq(&buffer);
|
||||
});
|
||||
|
||||
rom_test!(ppu_fill_name_table, "ppu_fill_name_table.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x80B3 HLT :2 []");
|
||||
assert_eq!(nes.cpu.a, 0x01);
|
||||
|
||||
let mut buffer = RenderBuffer::empty();
|
||||
for l in 0..240 {
|
||||
for p in 0..256 {
|
||||
if p % 2 == l % 2 {
|
||||
buffer.write(l, p, COLOR_01);
|
||||
} else if (p / 16) % 2 == (l / 16) % 2 {
|
||||
buffer.write(l, p, COLOR_0B);
|
||||
} else {
|
||||
buffer.write(l, p, COLOR_16);
|
||||
}
|
||||
}
|
||||
}
|
||||
nes.ppu().render_buffer.assert_eq(&buffer);
|
||||
});
|
||||
|
||||
rom_test!(ppu_vertical_write, "ppu_vertical_write.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction, "0x8040 HLT :2 []");
|
||||
|
||||
assert_eq!(nes.ppu().mem().peek(0x2000), Some(1));
|
||||
assert_eq!(nes.ppu().mem().peek(0x2020), Some(1));
|
||||
assert_eq!(nes.ppu().mem().peek(0x2400), Some(2));
|
||||
assert_eq!(nes.ppu().mem().peek(0x2401), Some(2));
|
||||
});
|
||||
44
src/test_roms/ppu_fill.s
Normal file
44
src/test_roms/ppu_fill.s
Normal file
@@ -0,0 +1,44 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
CUSTOM_RESET=1
|
||||
.include "testing.s"
|
||||
|
||||
; nv_res bad_addr,2
|
||||
|
||||
; PPUSTATUS = $2002
|
||||
zp_res COUNT
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; Init PPU
|
||||
bit PPUSTATUS
|
||||
vwait1:
|
||||
bit PPUSTATUS
|
||||
bpl vwait1
|
||||
vwait2:
|
||||
bit PPUSTATUS
|
||||
bpl vwait2
|
||||
|
||||
lda #$00
|
||||
sta PPUCTRL ; NMI off, PPU slave, Small sprites, 0 addresses
|
||||
lda #$0A
|
||||
sta PPUMASK; No EM, only background rendering, normal color
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
nmi:
|
||||
lda COUNT
|
||||
bne exit
|
||||
inc COUNT
|
||||
rti
|
||||
exit:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
137
src/test_roms/ppu_fill_name_table.s
Normal file
137
src/test_roms/ppu_fill_name_table.s
Normal file
@@ -0,0 +1,137 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
.include "testing.s"
|
||||
; patterns_bin "pat.bin"
|
||||
.macro chr b,s
|
||||
.repeat s
|
||||
.byte b
|
||||
.endrepeat
|
||||
.endmacro
|
||||
.pushseg
|
||||
.segment "CHARS"
|
||||
chr $FF,16 ; Full
|
||||
chr $00,8
|
||||
chr $FF,8 ; Gray
|
||||
chr $55,16 ; Vertical stripes
|
||||
chr $AA,16 ; Vertical stripes - reverse
|
||||
.repeat 8
|
||||
.byte $FF
|
||||
.byte $00
|
||||
.endrepeat ; Horizontal stripes
|
||||
.repeat 8
|
||||
.byte $00
|
||||
.byte $FF
|
||||
.endrepeat ; Horizontal stripes - reverse
|
||||
.repeat 8
|
||||
.byte $55
|
||||
.byte $AA
|
||||
.endrepeat ; Checkerboard
|
||||
.repeat 8
|
||||
.byte $AA
|
||||
.byte $55
|
||||
.endrepeat ; Checkerboard - reverse
|
||||
.popseg
|
||||
|
||||
zp_res COUNT
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; Init PPU
|
||||
bit PPUSTATUS
|
||||
vwait1:
|
||||
bit PPUSTATUS
|
||||
bpl vwait1
|
||||
vwait2:
|
||||
bit PPUSTATUS
|
||||
bpl vwait2
|
||||
|
||||
lda #$00
|
||||
sta PPUCTRL ; NMI off, PPU slave, Small sprites, 0 addresses
|
||||
lda #$3F
|
||||
sta PPUADDR
|
||||
lda #$00
|
||||
sta PPUADDR ; Load $3F03
|
||||
lda #$01
|
||||
sta PPUDATA ; Load $16 into palette 0, color 0
|
||||
lda #$16
|
||||
sta PPUDATA ; Load $16 into palette 0, color 1
|
||||
sta PPUDATA ; Load $16 into palette 0, color 2
|
||||
sta PPUDATA ; Load $16 into palette 0, color 3
|
||||
lda #$0E
|
||||
sta PPUDATA ; Load $16 into palette 0, color 0
|
||||
lda #$0B
|
||||
sta PPUDATA ; Load $16 into palette 0, color 1
|
||||
sta PPUDATA ; Load $16 into palette 0, color 2
|
||||
sta PPUDATA ; Load $16 into palette 0, color 3
|
||||
lda #$0E
|
||||
sta PPUDATA ; Load $16 into palette 0, color 0
|
||||
lda #$0B
|
||||
sta PPUDATA ; Load $16 into palette 0, color 1
|
||||
sta PPUDATA ; Load $16 into palette 0, color 2
|
||||
sta PPUDATA ; Load $16 into palette 0, color 3
|
||||
lda #$0E
|
||||
sta PPUDATA ; Load $16 into palette 0, color 0
|
||||
lda #$0B
|
||||
sta PPUDATA ; Load $16 into palette 0, color 1
|
||||
sta PPUDATA ; Load $16 into palette 0, color 2
|
||||
sta PPUDATA ; Load $16 into palette 0, color 3
|
||||
|
||||
lda #$20
|
||||
sta PPUADDR
|
||||
lda #$00
|
||||
sta PPUADDR ; Load $2000
|
||||
;lda #$01
|
||||
;sta PPUDATA ; Load $01 into name table 1
|
||||
lda #$06
|
||||
ldx #$00
|
||||
fill_loop_0:
|
||||
sta PPUDATA ; Load $01 into name table 1
|
||||
dex
|
||||
bne fill_loop_0
|
||||
ldx #$00
|
||||
fill_loop_1:
|
||||
sta PPUDATA ; Load $01 into name table 1
|
||||
dex
|
||||
bne fill_loop_1
|
||||
ldx #$00
|
||||
fill_loop_2:
|
||||
sta PPUDATA ; Load $01 into name table 1
|
||||
dex
|
||||
bne fill_loop_2
|
||||
ldx #$C0
|
||||
fill_loop_3:
|
||||
sta PPUDATA ; Load $01 into name table 1
|
||||
dex
|
||||
bne fill_loop_3
|
||||
lda #$41
|
||||
ldx #$40
|
||||
fill_loop:
|
||||
sta PPUDATA ; Load $16 into palette 0, color 0
|
||||
dex
|
||||
bne fill_loop
|
||||
|
||||
lda #$0A
|
||||
sta PPUMASK; No EM, only background rendering, normal color
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
nmi:
|
||||
lda COUNT
|
||||
bne exit
|
||||
inc COUNT
|
||||
rti
|
||||
exit:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
67
src/test_roms/ppu_fill_palette_1.s
Normal file
67
src/test_roms/ppu_fill_palette_1.s
Normal file
@@ -0,0 +1,67 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
.include "testing.s"
|
||||
|
||||
zp_res COUNT
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; Init PPU
|
||||
bit PPUSTATUS
|
||||
vwait1:
|
||||
bit PPUSTATUS
|
||||
bpl vwait1
|
||||
vwait2:
|
||||
bit PPUSTATUS
|
||||
bpl vwait2
|
||||
|
||||
lda #$00
|
||||
sta PPUCTRL ; NMI off, PPU slave, Small sprites, 0 addresses
|
||||
lda #$3F
|
||||
sta PPUADDR
|
||||
lda #$03
|
||||
sta PPUADDR ; Load $3F03
|
||||
lda #$16
|
||||
sta PPUDATA ; Load $16 into palette 0, color 3
|
||||
lda #$01
|
||||
sta PPUDATA ; Load $01 into palette 1
|
||||
sta PPUDATA ; Load $01 into palette 1
|
||||
sta PPUDATA ; Load $01 into palette 1
|
||||
sta PPUDATA ; Load $01 into palette 1
|
||||
|
||||
lda #$23
|
||||
sta PPUADDR
|
||||
lda #$C0
|
||||
sta PPUADDR ; Load $23C0
|
||||
lda #$41
|
||||
ldx #$40
|
||||
fill_loop:
|
||||
sta PPUDATA ; Load $16 into palette 0, color 0
|
||||
dex
|
||||
bne fill_loop
|
||||
|
||||
lda #$0A
|
||||
sta PPUMASK; No EM, only background rendering, normal color
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
nmi:
|
||||
lda COUNT
|
||||
bne exit
|
||||
inc COUNT
|
||||
rti
|
||||
exit:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
46
src/test_roms/ppu_fill_red.s
Normal file
46
src/test_roms/ppu_fill_red.s
Normal file
@@ -0,0 +1,46 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
.include "testing.s"
|
||||
|
||||
zp_res COUNT
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; Init PPU
|
||||
bit PPUSTATUS
|
||||
vwait1:
|
||||
bit PPUSTATUS
|
||||
bpl vwait1
|
||||
vwait2:
|
||||
bit PPUSTATUS
|
||||
bpl vwait2
|
||||
|
||||
lda #$00
|
||||
sta PPUCTRL ; NMI off, PPU slave, Small sprites, 0 addresses
|
||||
lda #$3F
|
||||
sta PPUADDR
|
||||
lda #$03
|
||||
sta PPUADDR ; Load $3F03
|
||||
lda #$16
|
||||
sta PPUDATA ; Load $16 into palette 0, color 0
|
||||
lda #$0A
|
||||
sta PPUMASK; No EM, only background rendering, normal color
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
nmi:
|
||||
lda COUNT
|
||||
bne exit
|
||||
inc COUNT
|
||||
rti
|
||||
exit:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
48
src/test_roms/ppu_vertical_write.s
Normal file
48
src/test_roms/ppu_vertical_write.s
Normal file
@@ -0,0 +1,48 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
.include "testing.s"
|
||||
|
||||
zp_res COUNT
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; Init PPU
|
||||
bit PPUSTATUS
|
||||
vwait1:
|
||||
bit PPUSTATUS
|
||||
bpl vwait1
|
||||
vwait2:
|
||||
bit PPUSTATUS
|
||||
bpl vwait2
|
||||
|
||||
lda #$04
|
||||
sta PPUCTRL ; NMI off, PPU slave, Small sprites, 0 addresses, vertical write
|
||||
lda #$20
|
||||
sta PPUADDR
|
||||
lda #$00
|
||||
sta PPUADDR ; Load $2000
|
||||
ldx #$01
|
||||
stx PPUDATA ; Load values into name table
|
||||
stx PPUDATA ; Load values into name table
|
||||
|
||||
lda #$00
|
||||
sta PPUCTRL ; NMI off, PPU slave, Small sprites, 0 addresses, normal write
|
||||
lda #$24
|
||||
sta PPUADDR
|
||||
lda #$00
|
||||
sta PPUADDR ; Load $2000
|
||||
ldx #$02
|
||||
stx PPUDATA ; Load values into name table
|
||||
stx PPUDATA ; Load values into name table
|
||||
|
||||
stp
|
||||
|
||||
nmi:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
@@ -1,29 +0,0 @@
|
||||
.inesprg 2 ; 2 banks
|
||||
.ineschr 1 ;
|
||||
.inesmap 0 ; mapper 0 = NROM
|
||||
.inesmir 0 ; background mirroring, horizontal
|
||||
|
||||
.org $8000
|
||||
RESET:
|
||||
lda #$aa
|
||||
sta $0000
|
||||
ldx $0000
|
||||
ldy $0000
|
||||
stx $0001
|
||||
sty $0002
|
||||
hlt
|
||||
ERROR_:
|
||||
hlt
|
||||
|
||||
IGNORE:
|
||||
rti
|
||||
|
||||
.org $FFFA ; Interrupt vectors go here:
|
||||
.word IGNORE ; NMI
|
||||
.word RESET ; Reset
|
||||
.word IGNORE; IRQ
|
||||
|
||||
;;;; NESASM COMPILER STUFF, ADDING THE PATTERN DATA ;;;;
|
||||
|
||||
.incbin "Sprites.pcx"
|
||||
.incbin "Tiles.pcx"
|
||||
17
src/test_roms/read_write.s
Normal file
17
src/test_roms/read_write.s
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
.include "testing.s"
|
||||
|
||||
reset:
|
||||
lda #$aa
|
||||
sta $0000
|
||||
ldx $0000
|
||||
ldy $0000
|
||||
stx $0001
|
||||
sty $0002
|
||||
stp
|
||||
|
||||
nmi:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
Reference in New Issue
Block a user