Complete initial tests for startup
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 8s

This commit is contained in:
2025-12-14 14:44:54 -06:00
parent af770d232c
commit ce4532bcdf
8 changed files with 429 additions and 64 deletions

View File

@@ -107,6 +107,17 @@ impl DebuggerState {
labelled_box("BG at $1000", false), labelled_box("BG at $1000", false),
labelled_box("Sprites at $1000", false), 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),
],
column![ column![
run_type( run_type(
"PPU Cycles:", "PPU Cycles:",
@@ -190,7 +201,7 @@ impl DebuggerState {
DebuggerMessage::RunToScanLine => { DebuggerMessage::RunToScanLine => {
Self::run_until(nes, |_, n| n.ppu.scanline == self.to_scan_line) Self::run_until(nes, |_, n| n.ppu.scanline == self.to_scan_line)
} }
DebuggerMessage::RunFrames => Self::run_n_clock_cycles(nes, self.frames * 341 * 261), DebuggerMessage::RunFrames => Self::run_n_clock_cycles(nes, self.frames * 341 * 262),
DebuggerMessage::Run => todo!(), DebuggerMessage::Run => todo!(),
DebuggerMessage::Pause => todo!(), DebuggerMessage::Pause => todo!(),
} }
@@ -205,7 +216,7 @@ fn run_type<'a, Message: Clone + 'a>(
) -> Element<'a, Message> { ) -> Element<'a, Message> {
row![ row![
widget::container(text(label)).padding(2.), widget::container(text(label)).padding(2.),
widget::container(number_input(val).on_input(update)).padding(2.), widget::container(number_input(val).on_input(update).on_submit(run.clone())).padding(2.),
widget::container(button(image("./images/ic_fluent_play_24_filled.png")).on_press(run)) widget::container(button(image("./images/ic_fluent_play_24_filled.png")).on_press(run))
.padding(2.), .padding(2.),
] ]

View File

@@ -1,7 +1,4 @@
use std::{ use std::{collections::HashMap, fmt};
collections::HashMap,
fmt,
};
use iced::{ use iced::{
Color, Element, Font, Color, Element, Font,
@@ -15,11 +12,14 @@ use iced::{
window::{self, Id, Settings}, window::{self, Id, Settings},
}; };
use nes_emu::{ use nes_emu::{
debugger::{DebuggerMessage, DebuggerState}, header_menu::header_menu, hex_view::{HexEvent, HexView}, NES, PPU NES, PPU,
debugger::{DebuggerMessage, DebuggerState},
header_menu::header_menu,
hex_view::{HexEvent, HexView},
}; };
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "basic_init_1.nes"); const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "even_odd.nes");
// const ROM_FILE: &str = "./Super Mario Bros. (World).nes"; // const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
extern crate nes_emu; extern crate nes_emu;
@@ -45,6 +45,7 @@ enum WindowType {
Memory(MemoryTy, HexView), Memory(MemoryTy, HexView),
TileMap, TileMap,
TileViewer, TileViewer,
Palette,
Debugger, Debugger,
} }
@@ -93,8 +94,7 @@ enum Message {
impl Emulator { impl Emulator {
fn new() -> (Self, Task<Message>) { fn new() -> (Self, Task<Message>) {
let mut nes = nes_emu::NES::load_nes_file(ROM_FILE) let mut nes = nes_emu::NES::load_nes_file(ROM_FILE).expect("Failed to load nes file");
.expect("Failed to load nes file");
nes.reset(); nes.reset();
let (win, task) = iced::window::open(Settings::default()); let (win, task) = iced::window::open(Settings::default());
( (
@@ -112,6 +112,7 @@ impl Emulator {
Some(WindowType::Memory(_, _)) => "NES MemoryView".into(), Some(WindowType::Memory(_, _)) => "NES MemoryView".into(),
Some(WindowType::TileMap) => "NES TileMap".into(), Some(WindowType::TileMap) => "NES TileMap".into(),
Some(WindowType::TileViewer) => "NES Tile Viewer".into(), Some(WindowType::TileViewer) => "NES Tile Viewer".into(),
Some(WindowType::Palette) => "NES Palette Viewer".into(),
Some(WindowType::Debugger) => "NES Debugger".into(), Some(WindowType::Debugger) => "NES Debugger".into(),
None => todo!(), None => todo!(),
} }
@@ -126,42 +127,48 @@ impl Emulator {
fn update(&mut self, message: Message) -> Task<Message> { fn update(&mut self, message: Message) -> Task<Message> {
match message { match message {
Message::Tick(count) => { Message::Tick(count) => {
for _ in 0..count { for _ in 0..count {
self.nes.run_one_clock_cycle(); self.nes.run_one_clock_cycle();
} }
} }
Message::Frame => while !self.nes.run_one_clock_cycle().ppu_frame {}, Message::Frame => while !self.nes.run_one_clock_cycle().ppu_frame {},
Message::DMA => while !self.nes.run_one_clock_cycle().dma {}, Message::DMA => while !self.nes.run_one_clock_cycle().dma {},
Message::CPU => while !self.nes.run_one_clock_cycle().cpu_exec {}, Message::CPU => while !self.nes.run_one_clock_cycle().cpu_exec {},
Message::DebugInt => while !self.nes.run_one_clock_cycle().dbg_int {}, Message::DebugInt => while !self.nes.run_one_clock_cycle().dbg_int {},
Message::WindowOpened(_id) => { Message::WindowOpened(_id) => {
// Window // Window
} }
Message::WindowClosed(id) => { Message::WindowClosed(id) => {
if let Some(WindowType::Main) = self.windows.remove(&id) { if let Some(WindowType::Main) = self.windows.remove(&id) {
return iced::exit(); return iced::exit();
} }
} }
Message::Header(HeaderButton::OpenMemory) => { Message::Header(HeaderButton::OpenMemory) => {
return self.open(WindowType::Memory(MemoryTy::Cpu, HexView::new())); return self.open(WindowType::Memory(MemoryTy::Cpu, HexView::new()));
} }
Message::Header(HeaderButton::OpenTileMap) => { Message::Header(HeaderButton::OpenTileMap) => {
return self.open(WindowType::TileMap); return self.open(WindowType::TileMap);
} }
Message::Header(HeaderButton::OpenTileViewer) => { Message::Header(HeaderButton::OpenTileViewer) => {
return self.open(WindowType::TileViewer); return self.open(WindowType::TileViewer);
} }
Message::Header(HeaderButton::OpenDebugger) => { Message::Header(HeaderButton::OpenDebugger) => {
return self.open(WindowType::Debugger); return self.open(WindowType::Debugger);
} }
Message::Hex(id, val) => { Message::Hex(id, val) => {
if let Some(WindowType::Memory(_, view)) = self.windows.get_mut(&id) { if let Some(WindowType::Memory(_, view)) = self.windows.get_mut(&id) {
return view.update(val).map(move |e| Message::Hex(id, e)); return view.update(val).map(move |e| Message::Hex(id, e));
} }
} }
Message::Header(HeaderButton::Reset) => { self.nes.reset(); } Message::Header(HeaderButton::Reset) => {
Message::Header(HeaderButton::PowerCycle) => { self.nes.power_cycle(); } self.nes.reset();
Message::Debugger(debugger_message) => self.debugger.update(debugger_message, &mut self.nes), }
Message::Header(HeaderButton::PowerCycle) => {
self.nes.power_cycle();
}
Message::Debugger(debugger_message) => {
self.debugger.update(debugger_message, &mut self.nes)
}
} }
// self.image.0.clone_from(self.nes.image()); // self.image.0.clone_from(self.nes.image());
Task::none() Task::none()
@@ -193,13 +200,28 @@ impl Emulator {
container(content).width(Fill).height(Fill).into() container(content).width(Fill).height(Fill).into()
} }
Some(WindowType::TileMap) => { Some(WindowType::TileMap) => {
container(Canvas::new(DbgImage::PatternTable(self.nes.ppu()))).width(Fill).height(Fill).into() container(Canvas::new(DbgImage::NameTable(self.nes.ppu())))
.width(Fill)
.height(Fill)
.into()
} }
Some(WindowType::TileViewer) => { Some(WindowType::TileViewer) => {
container(Canvas::new(DbgImage::NameTable(self.nes.ppu()))).width(Fill).height(Fill).into() container(Canvas::new(DbgImage::PatternTable(self.nes.ppu())))
.width(Fill)
.height(Fill)
.into()
}
Some(WindowType::Palette) => {
container(Canvas::new(DbgImage::Palette(self.nes.ppu())))
.width(Fill)
.height(Fill)
.into()
} }
Some(WindowType::Debugger) => { Some(WindowType::Debugger) => {
container(self.debugger.view(&self.nes).map(Message::Debugger)).width(Fill).height(Fill).into() container(self.debugger.view(&self.nes).map(Message::Debugger))
.width(Fill)
.height(Fill)
.into()
} }
None => panic!("Window not found"), None => panic!("Window not found"),
// _ => todo!(), // _ => todo!(),
@@ -231,10 +253,7 @@ impl Emulator {
row![ row![
header_menu( header_menu(
"Console", "Console",
[ [HeaderButton::Reset, HeaderButton::PowerCycle,],
HeaderButton::Reset,
HeaderButton::PowerCycle,
],
Message::Header Message::Header
), ),
header_menu( header_menu(
@@ -290,6 +309,7 @@ impl Program<Message> for Emulator {
enum DbgImage<'a> { enum DbgImage<'a> {
NameTable(&'a PPU), NameTable(&'a PPU),
PatternTable(&'a PPU), PatternTable(&'a PPU),
Palette(&'a PPU),
} }
impl Program<Message> for DbgImage<'_> { impl Program<Message> for DbgImage<'_> {
@@ -310,6 +330,7 @@ impl Program<Message> for DbgImage<'_> {
match self { match self {
DbgImage::NameTable(ppu) => ppu.render_name_table(&mut name_table_frame), DbgImage::NameTable(ppu) => ppu.render_name_table(&mut name_table_frame),
DbgImage::PatternTable(ppu) => ppu.render_pattern_tables(&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()] vec![name_table_frame.into_geometry()]
} }

View File

@@ -1,6 +1,13 @@
use iced::{advanced::graphics::geometry::Renderer, widget::canvas::{Fill, Frame}, Point, Size}; use iced::{
Point, Size,
advanced::graphics::geometry::Renderer,
widget::canvas::{Fill, Frame},
};
use crate::{hex_view::Memory, mem::{MemoryMap, Segment}}; use crate::{
hex_view::Memory,
mem::{MemoryMap, Segment},
};
#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Color { pub struct Color {
@@ -91,6 +98,15 @@ pub struct Mask {
em_blue: bool, em_blue: bool,
} }
const COLORS: &'static [Color; 0b11_1111] = &[
Color { r: 0, g: 0, b: 0}; 0b11_1111
];
pub struct Palette {
colors: &'static [Color; 0b11_1111],
ram: [u8; 0x20]
}
pub struct PPU { pub struct PPU {
// registers: PPURegisters, // registers: PPURegisters,
frame_count: usize, frame_count: usize,
@@ -104,6 +120,7 @@ pub struct PPU {
pub vblank: bool, pub vblank: bool,
pub(crate) memory: MemoryMap<PPUMMRegisters>, pub(crate) memory: MemoryMap<PPUMMRegisters>,
palette: Palette,
background: Background, background: Background,
oam: OAM, oam: OAM,
pub render_buffer: RenderBuffer<256, 240>, pub render_buffer: RenderBuffer<256, 240>,
@@ -145,11 +162,12 @@ impl PPU {
em_green: false, em_green: false,
em_blue: false, em_blue: false,
}, },
palette: Palette { colors: COLORS, ram: [0; _] },
vblank: false, vblank: false,
frame_count: 0, frame_count: 0,
nmi_enabled: false, nmi_enabled: false,
nmi_waiting: false, nmi_waiting: false,
even: true, // ?? even: false,
scanline: 0, scanline: 0,
pixel: 25, pixel: 25,
render_buffer: RenderBuffer::empty(), render_buffer: RenderBuffer::empty(),
@@ -157,7 +175,8 @@ impl PPU {
Segment::rom("CHR ROM", 0x0000, rom), Segment::rom("CHR ROM", 0x0000, rom),
Segment::ram("Internal VRAM", 0x2000, 0x1000), Segment::ram("Internal VRAM", 0x2000, 0x1000),
Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1), Segment::mirror("Mirror of VRAM", 0x3000, 0x0F00, 1),
Segment::reg("Palette Control", 0x3F00, 0x0100, PPUMMRegisters::Palette), Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette),
Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 3),
]), ]),
background: Background { background: Background {
v: 0, v: 0,
@@ -182,6 +201,10 @@ impl PPU {
}; };
} }
pub fn rendering_enabled(&self) -> bool {
self.mask.enable_background || self.mask.enable_sprites
}
pub fn read_reg(&mut self, offset: u16) -> u8 { pub fn read_reg(&mut self, offset: u16) -> u8 {
match offset { match offset {
0 => panic!("ppuctrl is write-only"), 0 => panic!("ppuctrl is write-only"),
@@ -190,7 +213,6 @@ impl PPU {
let tmp = if self.vblank { 0b1000_0000 } else { 0 }; let tmp = if self.vblank { 0b1000_0000 } else { 0 };
self.vblank = false; self.vblank = false;
self.background.w = false; self.background.w = false;
println!("Reading status: {:02X}", tmp);
tmp tmp
} }
3 => panic!("oamaddr is write-only"), 3 => panic!("oamaddr is write-only"),
@@ -227,7 +249,7 @@ impl PPU {
self.mask.background_on_left_edge = val & 0b0000_0010 != 0; self.mask.background_on_left_edge = val & 0b0000_0010 != 0;
self.mask.sprites_on_left_edge = val & 0b0000_0100 != 0; self.mask.sprites_on_left_edge = val & 0b0000_0100 != 0;
self.mask.enable_background = val & 0b0000_1000 != 0; self.mask.enable_background = val & 0b0000_1000 != 0;
self.mask.enable_background = val & 0b0001_0000 != 0; self.mask.enable_sprites = val & 0b0001_0000 != 0;
self.mask.em_red = val & 0b0010_0000 != 0; self.mask.em_red = val & 0b0010_0000 != 0;
self.mask.em_green = val & 0b0100_0000 != 0; self.mask.em_green = val & 0b0100_0000 != 0;
self.mask.em_blue = val & 0b1000_0000 != 0; self.mask.em_blue = val & 0b1000_0000 != 0;
@@ -284,7 +306,9 @@ impl PPU {
pub fn run_one_clock_cycle(&mut self) -> bool { pub fn run_one_clock_cycle(&mut self) -> bool {
self.cycle += 1; self.cycle += 1;
self.pixel += 1; self.pixel += 1;
if self.scanline == 261 && (self.pixel == 341 || (self.pixel == 340 && self.even)) { if self.scanline == 261
&& (self.pixel == 341 || (self.pixel == 340 && self.even && self.rendering_enabled()))
{
self.scanline = 0; self.scanline = 0;
self.pixel = 0; self.pixel = 0;
self.even = !self.even; self.even = !self.even;
@@ -388,10 +412,22 @@ impl PPU {
Size::new(1., 1.), Size::new(1., 1.),
match (low & (1 << bit) != 0, high & (1 << bit) != 0) { match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
(false, false) => Color { r: 0, g: 0, b: 0 }, (false, false) => Color { r: 0, g: 0, b: 0 },
(true, false) => Color { r: 64, g: 64, b: 64 }, (true, false) => Color {
(false, true) => Color { r: 128, g: 128, b: 128 }, r: 64,
(true, true) => Color { r: 255, g: 255, b: 255 }, g: 64,
} b: 64,
},
(false, true) => Color {
r: 128,
g: 128,
b: 128,
},
(true, true) => Color {
r: 255,
g: 255,
b: 255,
},
},
); );
} }
} }
@@ -409,14 +445,29 @@ impl PPU {
let high = self.memory.peek(name + y_off + 8).unwrap(); let high = self.memory.peek(name + y_off + 8).unwrap();
for bit in 0..8 { for bit in 0..8 {
frame.fill_rectangle( frame.fill_rectangle(
Point::new(x as f32 * 8. + 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.), Size::new(1., 1.),
match (low & (1 << bit) != 0, high & (1 << bit) != 0) { match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
(false, false) => Color { r: 0, g: 0, b: 0 }, (false, false) => Color { r: 0, g: 0, b: 0 },
(true, false) => Color { r: 64, g: 64, b: 64 }, (true, false) => Color {
(false, true) => Color { r: 128, g: 128, b: 128 }, r: 64,
(true, true) => Color { r: 255, g: 255, b: 255 }, g: 64,
} b: 64,
},
(false, true) => Color {
r: 128,
g: 128,
b: 128,
},
(true, true) => Color {
r: 255,
g: 255,
b: 255,
},
},
); );
} }
} }
@@ -431,14 +482,29 @@ impl PPU {
let high = self.memory.peek(name + y_off + 8 + 0x1000).unwrap(); let high = self.memory.peek(name + y_off + 8 + 0x1000).unwrap();
for bit in 0..8 { for bit in 0..8 {
frame.fill_rectangle( frame.fill_rectangle(
Point::new(x as f32 * 8. + 8. - bit as f32, y as f32 * 8. + y_off as f32 + 130.), Point::new(
x as f32 * 8. + 8. - bit as f32,
y as f32 * 8. + y_off as f32 + 130.,
),
Size::new(1., 1.), Size::new(1., 1.),
match (low & (1 << bit) != 0, high & (1 << bit) != 0) { match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
(false, false) => Color { r: 0, g: 0, b: 0 }, (false, false) => Color { r: 0, g: 0, b: 0 },
(true, false) => Color { r: 64, g: 64, b: 64 }, (true, false) => Color {
(false, true) => Color { r: 128, g: 128, b: 128 }, r: 64,
(true, true) => Color { r: 255, g: 255, b: 255 }, g: 64,
} b: 64,
},
(false, true) => Color {
r: 128,
g: 128,
b: 128,
},
(true, true) => Color {
r: 255,
g: 255,
b: 255,
},
},
); );
} }
} }
@@ -447,6 +513,17 @@ impl PPU {
} }
} }
} }
pub fn render_palette<R: Renderer>(&self, frame: &mut Frame<R>) {
frame.fill_rectangle(
Point::new(0., 0.),
Size::new(10., 10.),
Color {
r: todo!(),
g: todo!(),
b: todo!(),
},
);
}
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -0,0 +1,43 @@
.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"

View File

@@ -0,0 +1,46 @@
.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
VBLANKWAIT3:
bit $2002
bpl VBLANKWAIT3
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"

View File

@@ -0,0 +1,44 @@
.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 $4010 ; Disable DMC IRQs
ldx #$08
stx $2001 ; Disable rendering
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"

View File

@@ -15,6 +15,20 @@ macro_rules! rom_test {
$eval $eval
} }
}; };
($name:ident, . $rom:literal, |$nes:ident| $eval:expr) => {
rom_test!($name, . $rom, timeout = 10000000, |$nes| $eval);
};
($name:ident, . $rom:literal, timeout = $timeout:expr, |$nes:ident| $eval:expr) => {
#[test]
fn $name() {
let rom_file = $rom;
println!("{}: {}", stringify!($name), rom_file);
let mut $nes = NES::load_nes_file(rom_file).expect("Failed to create nes object");
$nes.reset_and_run_with_timeout($timeout);
println!("Final: {:?}", $nes);
$eval
}
};
} }
rom_test!(basic_cpu, "basic-cpu.nes", |nes| { rom_test!(basic_cpu, "basic-cpu.nes", |nes| {
@@ -23,6 +37,8 @@ rom_test!(basic_cpu, "basic-cpu.nes", |nes| {
assert_eq!(nes.cycle, 11); assert_eq!(nes.cycle, 11);
// This is off by one from Mesen, because Mesen is left pointing at the 'invalid' opcode // This is off by one from Mesen, because Mesen is left pointing at the 'invalid' opcode
assert_eq!(nes.cpu.pc, 0x8002); 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.cpu.sp, 0xFD); assert_eq!(nes.cpu.sp, 0xFD);
// assert_eq!(nes.cpu.a, 0x00); // assert_eq!(nes.cpu.a, 0x00);
@@ -35,6 +51,7 @@ rom_test!(read_write, "read_write.nes", |nes| {
assert_eq!(nes.cycle, 31); assert_eq!(nes.cycle, 31);
assert_eq!(nes.cpu.pc, 0x8012); assert_eq!(nes.cpu.pc, 0x8012);
assert_eq!(nes.cpu.sp, 0xFD); assert_eq!(nes.cpu.sp, 0xFD);
assert_eq!(nes.cpu.a, 0xAA); assert_eq!(nes.cpu.a, 0xAA);
assert_eq!(nes.cpu.x, 0xAA); assert_eq!(nes.cpu.x, 0xAA);
assert_eq!(nes.cpu.y, 0xAA); assert_eq!(nes.cpu.y, 0xAA);
@@ -48,6 +65,7 @@ rom_test!(basic_init_0, "basic_init_0.nes", |nes| {
assert_eq!(nes.cycle, 41); assert_eq!(nes.cycle, 41);
assert_eq!(nes.cpu.pc, 0x8018); assert_eq!(nes.cpu.pc, 0x8018);
assert_eq!(nes.cpu.sp, 0xFF); assert_eq!(nes.cpu.sp, 0xFF);
assert_eq!(nes.cpu.a, 0x00); assert_eq!(nes.cpu.a, 0x00);
assert_eq!(nes.cpu.x, 0x00); assert_eq!(nes.cpu.x, 0x00);
assert_eq!(nes.cpu.y, 0x00); assert_eq!(nes.cpu.y, 0x00);
@@ -56,10 +74,59 @@ rom_test!(basic_init_0, "basic_init_0.nes", |nes| {
rom_test!(basic_init_1, "basic_init_1.nes", |nes| { rom_test!(basic_init_1, "basic_init_1.nes", |nes| {
assert_eq!(nes.last_instruction, "0x801C HLT :2 []"); assert_eq!(nes.last_instruction, "0x801C HLT :2 []");
assert_eq!(nes.cycle, 27403); assert_eq!(nes.cycle, 27403);
assert_eq!(nes.ppu.pixel, 30);
assert_eq!(nes.cpu.pc, 0x801D); assert_eq!(nes.cpu.pc, 0x801D);
assert_eq!(nes.ppu.pixel, 30);
assert_eq!(nes.cpu.sp, 0xFF); assert_eq!(nes.cpu.sp, 0xFF);
assert_eq!(nes.cpu.a, 0x00); assert_eq!(nes.cpu.a, 0x00);
assert_eq!(nes.cpu.x, 0x00); assert_eq!(nes.cpu.x, 0x00);
assert_eq!(nes.cpu.y, 0x00); assert_eq!(nes.cpu.y, 0x00);
}); });
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.cpu.sp, 0xFF);
assert_eq!(nes.cpu.a, 0x00);
assert_eq!(nes.cpu.x, 0x00);
assert_eq!(nes.cpu.y, 0x00);
});
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.cpu.sp, 0xFF);
assert_eq!(nes.cpu.a, 0x00);
assert_eq!(nes.cpu.x, 0x00);
assert_eq!(nes.cpu.y, 0x00);
});
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.cpu.sp, 0xFF);
assert_eq!(nes.cpu.a, 0x00);
assert_eq!(nes.cpu.x, 0x08);
assert_eq!(nes.cpu.y, 0x00);
});
// 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.cpu.sp, 0xFF);
// assert_eq!(nes.cpu.a, 0x00);
// assert_eq!(nes.cpu.x, 0x08);
// assert_eq!(nes.cpu.y, 0x00);
// });

View File

@@ -0,0 +1,56 @@
.inesprg 2 ; 2 banks
.ineschr 1 ;
.inesmap 0 ; mapper 0 = NROM
.inesmir 0 ; background mirroring, horizontal
; .org $0000 ; "ZEROPAGE"
ppu_emphasis = $0000
color = $0001
temp = $0002
gamepad = $0003
gamepad_last = $0004
; .org $0200 ; "OAM"
; .assert ((* & $FF) = 0),error,"oam not aligned to page"
oam = $0200
.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 $4010 ; Disable DMC IRQs
ldx #$08
stx $2001 ; Disable rendering
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 nmi; NMI
.word reset ; Reset
.word irq; IRQ
;;;; NESASM COMPILER STUFF, ADDING THE PATTERN DATA ;;;;
.incbin "Sprites.pcx"
.incbin "Tiles.pcx"