Complete initial tests for startup
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 8s
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 8s
This commit is contained in:
@@ -107,6 +107,17 @@ impl DebuggerState {
|
||||
labelled_box("BG 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![
|
||||
run_type(
|
||||
"PPU Cycles:",
|
||||
@@ -190,7 +201,7 @@ impl DebuggerState {
|
||||
DebuggerMessage::RunToScanLine => {
|
||||
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::Pause => todo!(),
|
||||
}
|
||||
@@ -205,7 +216,7 @@ fn run_type<'a, Message: Clone + 'a>(
|
||||
) -> Element<'a, Message> {
|
||||
row![
|
||||
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))
|
||||
.padding(2.),
|
||||
]
|
||||
|
||||
101
src/main.rs
101
src/main.rs
@@ -1,7 +1,4 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
};
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use iced::{
|
||||
Color, Element, Font,
|
||||
@@ -15,11 +12,14 @@ use iced::{
|
||||
window::{self, Id, Settings},
|
||||
};
|
||||
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;
|
||||
|
||||
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";
|
||||
|
||||
extern crate nes_emu;
|
||||
@@ -45,6 +45,7 @@ enum WindowType {
|
||||
Memory(MemoryTy, HexView),
|
||||
TileMap,
|
||||
TileViewer,
|
||||
Palette,
|
||||
Debugger,
|
||||
}
|
||||
|
||||
@@ -93,8 +94,7 @@ enum Message {
|
||||
|
||||
impl Emulator {
|
||||
fn new() -> (Self, Task<Message>) {
|
||||
let mut nes = nes_emu::NES::load_nes_file(ROM_FILE)
|
||||
.expect("Failed to load nes file");
|
||||
let mut nes = nes_emu::NES::load_nes_file(ROM_FILE).expect("Failed to load nes file");
|
||||
nes.reset();
|
||||
let (win, task) = iced::window::open(Settings::default());
|
||||
(
|
||||
@@ -112,6 +112,7 @@ impl Emulator {
|
||||
Some(WindowType::Memory(_, _)) => "NES MemoryView".into(),
|
||||
Some(WindowType::TileMap) => "NES TileMap".into(),
|
||||
Some(WindowType::TileViewer) => "NES Tile Viewer".into(),
|
||||
Some(WindowType::Palette) => "NES Palette Viewer".into(),
|
||||
Some(WindowType::Debugger) => "NES Debugger".into(),
|
||||
None => todo!(),
|
||||
}
|
||||
@@ -126,42 +127,48 @@ impl Emulator {
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::Tick(count) => {
|
||||
for _ in 0..count {
|
||||
self.nes.run_one_clock_cycle();
|
||||
}
|
||||
}
|
||||
for _ in 0..count {
|
||||
self.nes.run_one_clock_cycle();
|
||||
}
|
||||
}
|
||||
Message::Frame => while !self.nes.run_one_clock_cycle().ppu_frame {},
|
||||
Message::DMA => while !self.nes.run_one_clock_cycle().dma {},
|
||||
Message::CPU => while !self.nes.run_one_clock_cycle().cpu_exec {},
|
||||
Message::DebugInt => while !self.nes.run_one_clock_cycle().dbg_int {},
|
||||
Message::WindowOpened(_id) => {
|
||||
// Window
|
||||
}
|
||||
// Window
|
||||
}
|
||||
Message::WindowClosed(id) => {
|
||||
if let Some(WindowType::Main) = self.windows.remove(&id) {
|
||||
return iced::exit();
|
||||
}
|
||||
}
|
||||
if let Some(WindowType::Main) = self.windows.remove(&id) {
|
||||
return iced::exit();
|
||||
}
|
||||
}
|
||||
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) => {
|
||||
return self.open(WindowType::TileMap);
|
||||
}
|
||||
return self.open(WindowType::TileMap);
|
||||
}
|
||||
Message::Header(HeaderButton::OpenTileViewer) => {
|
||||
return self.open(WindowType::TileViewer);
|
||||
}
|
||||
return self.open(WindowType::TileViewer);
|
||||
}
|
||||
Message::Header(HeaderButton::OpenDebugger) => {
|
||||
return self.open(WindowType::Debugger);
|
||||
}
|
||||
return self.open(WindowType::Debugger);
|
||||
}
|
||||
Message::Hex(id, val) => {
|
||||
if let Some(WindowType::Memory(_, view)) = self.windows.get_mut(&id) {
|
||||
return view.update(val).map(move |e| Message::Hex(id, e));
|
||||
}
|
||||
}
|
||||
Message::Header(HeaderButton::Reset) => { self.nes.reset(); }
|
||||
Message::Header(HeaderButton::PowerCycle) => { self.nes.power_cycle(); }
|
||||
Message::Debugger(debugger_message) => self.debugger.update(debugger_message, &mut self.nes),
|
||||
if let Some(WindowType::Memory(_, view)) = self.windows.get_mut(&id) {
|
||||
return view.update(val).map(move |e| Message::Hex(id, e));
|
||||
}
|
||||
}
|
||||
Message::Header(HeaderButton::Reset) => {
|
||||
self.nes.reset();
|
||||
}
|
||||
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());
|
||||
Task::none()
|
||||
@@ -193,13 +200,28 @@ impl Emulator {
|
||||
container(content).width(Fill).height(Fill).into()
|
||||
}
|
||||
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) => {
|
||||
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) => {
|
||||
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"),
|
||||
// _ => todo!(),
|
||||
@@ -231,10 +253,7 @@ impl Emulator {
|
||||
row![
|
||||
header_menu(
|
||||
"Console",
|
||||
[
|
||||
HeaderButton::Reset,
|
||||
HeaderButton::PowerCycle,
|
||||
],
|
||||
[HeaderButton::Reset, HeaderButton::PowerCycle,],
|
||||
Message::Header
|
||||
),
|
||||
header_menu(
|
||||
@@ -290,6 +309,7 @@ impl Program<Message> for Emulator {
|
||||
enum DbgImage<'a> {
|
||||
NameTable(&'a PPU),
|
||||
PatternTable(&'a PPU),
|
||||
Palette(&'a PPU),
|
||||
}
|
||||
|
||||
impl Program<Message> for DbgImage<'_> {
|
||||
@@ -310,6 +330,7 @@ impl Program<Message> for DbgImage<'_> {
|
||||
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()]
|
||||
}
|
||||
|
||||
119
src/ppu.rs
119
src/ppu.rs
@@ -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)]
|
||||
pub struct Color {
|
||||
@@ -91,6 +98,15 @@ pub struct Mask {
|
||||
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 {
|
||||
// registers: PPURegisters,
|
||||
frame_count: usize,
|
||||
@@ -104,6 +120,7 @@ pub struct PPU {
|
||||
pub vblank: bool,
|
||||
|
||||
pub(crate) memory: MemoryMap<PPUMMRegisters>,
|
||||
palette: Palette,
|
||||
background: Background,
|
||||
oam: OAM,
|
||||
pub render_buffer: RenderBuffer<256, 240>,
|
||||
@@ -145,11 +162,12 @@ impl PPU {
|
||||
em_green: false,
|
||||
em_blue: false,
|
||||
},
|
||||
palette: Palette { colors: COLORS, ram: [0; _] },
|
||||
vblank: false,
|
||||
frame_count: 0,
|
||||
nmi_enabled: false,
|
||||
nmi_waiting: false,
|
||||
even: true, // ??
|
||||
even: false,
|
||||
scanline: 0,
|
||||
pixel: 25,
|
||||
render_buffer: RenderBuffer::empty(),
|
||||
@@ -157,7 +175,8 @@ impl PPU {
|
||||
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, 0x0100, PPUMMRegisters::Palette),
|
||||
Segment::reg("Palette Control", 0x3F00, 0x0020, PPUMMRegisters::Palette),
|
||||
Segment::mirror("Mirror of Palette", 0x3F20, 0x00E0, 3),
|
||||
]),
|
||||
background: Background {
|
||||
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 {
|
||||
match offset {
|
||||
0 => panic!("ppuctrl is write-only"),
|
||||
@@ -190,7 +213,6 @@ impl PPU {
|
||||
let tmp = if self.vblank { 0b1000_0000 } else { 0 };
|
||||
self.vblank = false;
|
||||
self.background.w = false;
|
||||
println!("Reading status: {:02X}", tmp);
|
||||
tmp
|
||||
}
|
||||
3 => panic!("oamaddr is write-only"),
|
||||
@@ -227,7 +249,7 @@ impl PPU {
|
||||
self.mask.background_on_left_edge = val & 0b0000_0010 != 0;
|
||||
self.mask.sprites_on_left_edge = val & 0b0000_0100 != 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_green = val & 0b0100_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 {
|
||||
self.cycle += 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.pixel = 0;
|
||||
self.even = !self.even;
|
||||
@@ -388,10 +412,22 @@ impl PPU {
|
||||
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 },
|
||||
}
|
||||
(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,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -409,14 +445,29 @@ impl PPU {
|
||||
let high = self.memory.peek(name + y_off + 8).unwrap();
|
||||
for bit in 0..8 {
|
||||
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.),
|
||||
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 },
|
||||
}
|
||||
(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,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -431,14 +482,29 @@ impl PPU {
|
||||
let high = self.memory.peek(name + y_off + 8 + 0x1000).unwrap();
|
||||
for bit in 0..8 {
|
||||
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.),
|
||||
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 },
|
||||
}
|
||||
(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,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)]
|
||||
|
||||
43
src/test_roms/basic_init_2.asm
Normal file
43
src/test_roms/basic_init_2.asm
Normal 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"
|
||||
46
src/test_roms/basic_init_3.asm
Normal file
46
src/test_roms/basic_init_3.asm
Normal 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"
|
||||
44
src/test_roms/even_odd.asm
Normal file
44
src/test_roms/even_odd.asm
Normal 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"
|
||||
@@ -15,6 +15,20 @@ macro_rules! rom_test {
|
||||
$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| {
|
||||
@@ -23,6 +37,8 @@ rom_test!(basic_cpu, "basic-cpu.nes", |nes| {
|
||||
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.cpu.sp, 0xFD);
|
||||
// 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.cpu.pc, 0x8012);
|
||||
assert_eq!(nes.cpu.sp, 0xFD);
|
||||
|
||||
assert_eq!(nes.cpu.a, 0xAA);
|
||||
assert_eq!(nes.cpu.x, 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.cpu.pc, 0x8018);
|
||||
assert_eq!(nes.cpu.sp, 0xFF);
|
||||
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
assert_eq!(nes.cpu.x, 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| {
|
||||
assert_eq!(nes.last_instruction, "0x801C HLT :2 []");
|
||||
assert_eq!(nes.cycle, 27403);
|
||||
assert_eq!(nes.ppu.pixel, 30);
|
||||
assert_eq!(nes.cpu.pc, 0x801D);
|
||||
assert_eq!(nes.ppu.pixel, 30);
|
||||
|
||||
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_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);
|
||||
// });
|
||||
|
||||
56
src/test_roms/palette_test.asm.old
Normal file
56
src/test_roms/palette_test.asm.old
Normal 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"
|
||||
Reference in New Issue
Block a user