Compare commits
16 Commits
5c3d537cfd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
3010469c8a
|
|||
|
fa88c825a6
|
|||
|
b9a30c286a
|
|||
|
3372559c19
|
|||
|
22c586f15a
|
|||
|
825e245df1
|
|||
|
7b76026ade
|
|||
|
f861f75b21
|
|||
|
b5e1d1a4c3
|
|||
|
2e5e2ed1e7
|
|||
|
42c3af28b4
|
|||
|
ac745f60e9
|
|||
|
13e4158b7b
|
|||
|
cd3de5e361
|
|||
|
c535e4e76d
|
|||
|
c8d441297e
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/target
|
||||
*.nes
|
||||
*.zip
|
||||
*.dmp
|
||||
|
||||
182
Cargo.lock
generated
182
Cargo.lock
generated
@@ -70,6 +70,28 @@ dependencies = [
|
||||
"equator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c88dbbce13b232b26250e1e2e6ac18b6a891a646b8148285036ebce260ac5c3"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa-sys"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.6.0"
|
||||
@@ -771,6 +793,20 @@ dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coreaudio-rs"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aae284fbaf7d27aa0e292f7677dfbe26503b0d555026f702940805a630eac17"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
"objc2-audio-toolbox",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmic-text"
|
||||
version = "0.15.0"
|
||||
@@ -794,6 +830,36 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpal"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b1f9c7312f19fc2fa12fd7acaf38de54e8320ba10d1a02dcbe21038def51ccb"
|
||||
dependencies = [
|
||||
"alsa",
|
||||
"coreaudio-rs",
|
||||
"dasp_sample",
|
||||
"jni",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"mach2",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"objc2 0.6.3",
|
||||
"objc2-audio-toolbox",
|
||||
"objc2-avf-audio",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
@@ -858,6 +924,12 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
||||
|
||||
[[package]]
|
||||
name = "dasp_sample"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
@@ -871,6 +943,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2 0.6.2",
|
||||
"libc",
|
||||
"objc2 0.6.3",
|
||||
]
|
||||
|
||||
@@ -2004,6 +2078,15 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
@@ -2194,7 +2277,12 @@ name = "nes-emu"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitfield",
|
||||
"bytes",
|
||||
"cpal",
|
||||
"iced",
|
||||
"iced_core",
|
||||
"rfd",
|
||||
"ringbuf",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -2398,6 +2486,31 @@ dependencies = [
|
||||
"objc2-quartz-core 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-audio-toolbox"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libc",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-avf-audio"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
|
||||
dependencies = [
|
||||
"objc2 0.6.3",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-cloud-kit"
|
||||
version = "0.2.2"
|
||||
@@ -2433,6 +2546,29 @@ dependencies = [
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-audio"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
|
||||
dependencies = [
|
||||
"dispatch2",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-audio-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"objc2 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.2.2"
|
||||
@@ -2463,7 +2599,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2 0.6.2",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
"objc2 0.6.3",
|
||||
]
|
||||
|
||||
@@ -2875,6 +3013,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pollster"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
@@ -3172,12 +3316,50 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220"
|
||||
dependencies = [
|
||||
"block2 0.6.2",
|
||||
"dispatch2",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"objc2 0.6.3",
|
||||
"objc2-app-kit 0.3.2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
"percent-encoding",
|
||||
"pollster",
|
||||
"raw-window-handle",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"web-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
|
||||
|
||||
[[package]]
|
||||
name = "ringbuf"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe47b720588c8702e34b5979cb3271a8b1842c7cb6f57408efa70c779363488c"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
|
||||
@@ -7,9 +7,14 @@ edition = "2024"
|
||||
bitfield = "0.19.3"
|
||||
# iced = { version = "0.14.0", features = ["debug", "canvas", "tokio", "lazy", "image", "advanced"] }
|
||||
iced = { path = "../iced", features = ["debug", "canvas", "tokio", "lazy", "image", "advanced"] }
|
||||
iced_core = { path = "../iced/core", features = ["advanced"] }
|
||||
rfd = "0.17.2"
|
||||
# iced_graphics = { version = "0.14.0", features = ["geometry", "image"] }
|
||||
# iced_widget = { version = "0.13.4", features = ["canvas", "image"] }
|
||||
thiserror = "2.0.17"
|
||||
tokio = { version = "1.48.0", features = ["full"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.20", features = ["ansi", "chrono", "env-filter", "json", "serde"] }
|
||||
bytes = "*"
|
||||
cpal = "0.17.1"
|
||||
ringbuf = "0.4.8"
|
||||
|
||||
78
build.rs
78
build.rs
@@ -1,4 +1,31 @@
|
||||
use std::{io::Read, process::Stdio};
|
||||
use std::{
|
||||
fmt::Arguments,
|
||||
io::Read,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
fn run(name: Arguments, cmd: &mut Command) {
|
||||
let mut proc = cmd
|
||||
.current_dir("src/test_roms")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to start process");
|
||||
let rc = proc.wait();
|
||||
if rc.is_err() || rc.is_ok_and(|s| !s.success()) {
|
||||
let mut stdout = String::new();
|
||||
proc.stdout
|
||||
.unwrap()
|
||||
.read_to_string(&mut stdout)
|
||||
.expect("Failed to read stdout");
|
||||
let mut stderr = String::new();
|
||||
proc.stderr
|
||||
.unwrap()
|
||||
.read_to_string(&mut stderr)
|
||||
.expect("Failed to read stderr");
|
||||
panic!("Failed to run {name}\n=== STDOUT ===\n{stdout}\n=== STDERR ===\n{stderr}");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("cargo::rerun-if-changed=src/test_roms/");
|
||||
@@ -16,30 +43,31 @@ fn main() {
|
||||
let file_name = file.file_name();
|
||||
let file_name = file_name.to_str().unwrap();
|
||||
if let Some(file_name) = file_name.strip_suffix(".asm") {
|
||||
let mut proc = std::process::Command::new("./asm6f")
|
||||
.arg(file.file_name())
|
||||
.arg(format!("{name}/{file_name}.nes"))
|
||||
.current_dir("src/test_roms")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to start process");
|
||||
let rc = proc.wait();
|
||||
if rc.is_err() || rc.is_ok_and(|s| !s.success()) {
|
||||
let mut stdout = String::new();
|
||||
proc.stdout
|
||||
.unwrap()
|
||||
.read_to_string(&mut stdout)
|
||||
.expect("Failed to read stdout");
|
||||
let mut stderr = String::new();
|
||||
proc.stderr
|
||||
.unwrap()
|
||||
.read_to_string(&mut stderr)
|
||||
.expect("Failed to read stderr");
|
||||
panic!(
|
||||
"Failed to compile {file_name}\n=== STDOUT ===\n{stdout}\n=== STDERR ===\n{stderr}"
|
||||
);
|
||||
}
|
||||
run(
|
||||
format_args!("asm6f {file_name}"),
|
||||
Command::new("./asm6f")
|
||||
.arg(file.file_name())
|
||||
.arg(format!("{name}/{file_name}.nes")),
|
||||
);
|
||||
} else if let Some(file_name) = file_name.strip_suffix(".s") {
|
||||
run(
|
||||
format_args!("ca65 {file_name}"),
|
||||
Command::new("/home/matthew/cc65/bin/ca65")
|
||||
.arg("-I")
|
||||
.arg("common")
|
||||
.arg("-o")
|
||||
.arg(format!("{name}/{file_name}.o"))
|
||||
.arg(file.file_name()),
|
||||
);
|
||||
run(
|
||||
format_args!("ld65 {file_name}"),
|
||||
Command::new("/home/matthew/cc65/bin/ld65")
|
||||
.arg("-C")
|
||||
.arg("nes.cfg")
|
||||
.arg("-o")
|
||||
.arg(format!("{name}/{file_name}.nes"))
|
||||
.arg(format!("{name}/{file_name}.o"))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
652
src/apu.rs
652
src/apu.rs
@@ -1,15 +1,82 @@
|
||||
use std::iter::repeat_n;
|
||||
|
||||
use iced::{
|
||||
Element, Font,
|
||||
widget::{column, text},
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
macro_rules! lut {
|
||||
($name:ident: [$ty:ty; $len:expr] = |$n:ident| $expr:expr) => {
|
||||
const $name: [$ty; $len] = {
|
||||
let mut table = [0; $len];
|
||||
let mut $n = 0;
|
||||
while $n < $len {
|
||||
table[$n] = $expr;
|
||||
$n += 1;
|
||||
}
|
||||
table
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
bitfield::bitfield! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct LengthTimerHigh(u8);
|
||||
impl Debug;
|
||||
length, set_length: 7, 3;
|
||||
u16;
|
||||
timer_high, set_timer_high: 2, 0;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct LengthCounter {
|
||||
current: u16,
|
||||
silenced: bool,
|
||||
}
|
||||
|
||||
impl LengthCounter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current: 0,
|
||||
silenced: true,
|
||||
}
|
||||
}
|
||||
pub fn clock(&mut self) {
|
||||
if self.current == 0 {
|
||||
self.silenced = true;
|
||||
} else {
|
||||
self.current -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, length: u8) {
|
||||
// self.reg.0 = val;
|
||||
self.silenced = false;
|
||||
const LENGTH_LUT: [u16; 0x20] = [
|
||||
10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20,
|
||||
96, 22, 192, 24, 72, 26, 16, 28, 32, 3,
|
||||
];
|
||||
self.current = LENGTH_LUT[length as usize] + 1; // I think?
|
||||
}
|
||||
|
||||
pub fn silenced(&self) -> bool {
|
||||
self.silenced
|
||||
}
|
||||
}
|
||||
|
||||
bitfield::bitfield! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct DutyVol(u8);
|
||||
impl Debug;
|
||||
duty, set_duty: 7, 6;
|
||||
r#loop, set_loop: 5;
|
||||
length_counter_halt, set_length_counter_halt: 5;
|
||||
const_vol, set_const_vol: 4;
|
||||
volume, set_volume: 3, 0;
|
||||
}
|
||||
|
||||
bitfield::bitfield! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Sweep(u8);
|
||||
impl Debug;
|
||||
enable, set_enable: 7;
|
||||
@@ -18,41 +85,449 @@ bitfield::bitfield! {
|
||||
shift, set_shift: 2, 0;
|
||||
}
|
||||
|
||||
bitfield::bitfield! {
|
||||
pub struct LengthTimerHigh(u8);
|
||||
impl Debug;
|
||||
length, set_length: 7, 3;
|
||||
timer_high, set_timer_high: 2, 0;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PulseChannel {
|
||||
enabled: bool,
|
||||
duty_vol: DutyVol,
|
||||
sweep: Sweep,
|
||||
sweep_reload: bool,
|
||||
|
||||
counter: LengthCounter,
|
||||
|
||||
period: u16,
|
||||
period_timer: u16,
|
||||
cur: u8,
|
||||
sample: u8,
|
||||
|
||||
envelope_start: bool,
|
||||
envelope_counter: u8,
|
||||
envelope_divider: u8,
|
||||
}
|
||||
|
||||
impl PulseChannel {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
duty_vol: DutyVol(0),
|
||||
sweep: Sweep(0),
|
||||
sweep_reload: false,
|
||||
counter: LengthCounter::new(),
|
||||
period: 0,
|
||||
period_timer: 0,
|
||||
cur: 0,
|
||||
sample: 0,
|
||||
envelope_start: false,
|
||||
envelope_counter: 0,
|
||||
envelope_divider: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, offset: u16, val: u8) {
|
||||
match offset {
|
||||
0x00 => {
|
||||
self.duty_vol.0 = val;
|
||||
self.envelope_start = true;
|
||||
}
|
||||
0x01 => {
|
||||
self.sweep.0 = val;
|
||||
self.sweep_reload = true;
|
||||
},
|
||||
0x02 => self.period = self.period & 0x700 | (val as u16),
|
||||
0x03 => {
|
||||
let reg = LengthTimerHigh(val);
|
||||
self.counter.write(reg.length());
|
||||
self.period = (self.period & 0xFF) | (reg.timer_high() << 8);
|
||||
self.period_timer = self.period;
|
||||
self.cur = 0;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn volume(&self) -> u8 {
|
||||
if self.duty_vol.const_vol() {
|
||||
self.duty_vol.volume()
|
||||
} else {
|
||||
self.envelope_counter
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clock(&mut self) {
|
||||
if !self.enabled {
|
||||
self.sample = 0;
|
||||
return;
|
||||
}
|
||||
if self.period_timer == 0 {
|
||||
self.period_timer = self.period;
|
||||
self.cur = (self.cur + 1) % 8;
|
||||
const DUTY: [[bool; 8]; 4] = [
|
||||
[false, true, false, false, false, false, false, false],
|
||||
[false, true, true, false, false, false, false, false],
|
||||
[false, true, true, true, true, false, false, false],
|
||||
[true, false, false, true, true, true, true, true],
|
||||
];
|
||||
self.sample = if DUTY[self.duty_vol.duty() as usize][self.cur as usize] {
|
||||
self.volume()
|
||||
} else {
|
||||
0x00
|
||||
};
|
||||
} else {
|
||||
self.period_timer -= 1;
|
||||
}
|
||||
// TODO
|
||||
}
|
||||
pub fn cur_sample(&self) -> u8 {
|
||||
if self.enabled && !self.counter.silenced() {
|
||||
self.sample
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
pub fn q_frame_clock(&mut self) {
|
||||
if !self.enabled {
|
||||
return;
|
||||
}
|
||||
if !self.duty_vol.length_counter_halt() {
|
||||
self.counter.clock();
|
||||
} else {
|
||||
self.counter.silenced = false;
|
||||
}
|
||||
if !self.duty_vol.const_vol() {
|
||||
if self.envelope_start || self.envelope_counter == 0 {
|
||||
self.envelope_counter = 0xF;
|
||||
self.envelope_divider = self.duty_vol.volume();
|
||||
self.envelope_start = false;
|
||||
} else if self.envelope_divider == 0 {
|
||||
self.envelope_divider = self.duty_vol.volume();
|
||||
self.envelope_counter -= 1;
|
||||
} else {
|
||||
self.envelope_divider -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn h_frame_clock(&mut self) {
|
||||
self.q_frame_clock();
|
||||
}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!(
|
||||
"Square Channel
|
||||
Evelope Volume: {0:>3} ${0:02X}
|
||||
Constant Volume: {1}
|
||||
Length Counter - Halted: {2}
|
||||
Duty: {3:>3} ${3:02X}
|
||||
Sweep - Shift: {4:>3} ${4:02X}
|
||||
Sweep - Negate: {5}
|
||||
Sweep - Period: {6:>3} ${6:02X}
|
||||
Sweep - Enabled: {7}
|
||||
Period: {8:>3} ${8:04X}
|
||||
Length Counter - Reload Value: {9:>3} ${9:04X}
|
||||
Enabled: {10}
|
||||
Timer: {11:>3} ${11:04X}
|
||||
Duty Position: {12:>3} ${12:02X}
|
||||
Length Counter - Counter: {13:>3} ${13:02X}
|
||||
Envelope - Counter: {14:>3} ${14:02X}
|
||||
Envelope - Divider: {15:>3} ${15:02X}
|
||||
Output: {16:>3} ${16:02X}
|
||||
",
|
||||
self.duty_vol.volume(),
|
||||
self.duty_vol.const_vol(),
|
||||
self.duty_vol.length_counter_halt(),
|
||||
self.duty_vol.duty(),
|
||||
self.sweep.shift(),
|
||||
self.sweep.negate(),
|
||||
self.sweep.period(),
|
||||
self.sweep.enable(),
|
||||
self.period,
|
||||
0,
|
||||
self.enabled,
|
||||
self.period_timer,
|
||||
self.cur, // ?
|
||||
self.counter.current,
|
||||
self.envelope_counter,
|
||||
self.envelope_divider,
|
||||
self.cur_sample(),
|
||||
)
|
||||
.font(Font::MONOSPACE)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
bitfield::bitfield! {
|
||||
pub struct CounterLoad(u8);
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct LengthCounterReg(u8);
|
||||
impl Debug;
|
||||
halt, set_halt: 7;
|
||||
value, set_value: 6, 0;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TriangleChannel {
|
||||
enabled: bool,
|
||||
}
|
||||
struct NoiseChannel {
|
||||
enabled: bool,
|
||||
}
|
||||
struct DeltaChannel {
|
||||
enabled: bool,
|
||||
length: LengthCounterReg,
|
||||
reload: bool,
|
||||
counter: LengthCounter,
|
||||
|
||||
length_counter: u16,
|
||||
cur: u8,
|
||||
period: u16,
|
||||
period_timer: u16,
|
||||
sample: u8,
|
||||
}
|
||||
|
||||
impl TriangleChannel {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
length: LengthCounterReg(0),
|
||||
counter: LengthCounter::new(),
|
||||
reload: false,
|
||||
enabled: false,
|
||||
sample: 0,
|
||||
period: 0,
|
||||
period_timer: 0,
|
||||
cur: 0,
|
||||
length_counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, offset: u16, val: u8) {
|
||||
match offset {
|
||||
0x00 => {
|
||||
self.length.0 = val;
|
||||
}
|
||||
0x01 => (),
|
||||
0x02 => self.period = self.period & 0x700 | (val as u16),
|
||||
0x03 => {
|
||||
// self.length_load.0 = val;
|
||||
// self.reload = true;
|
||||
let reg = LengthTimerHigh(val);
|
||||
self.counter.write(reg.length());
|
||||
self.period = (self.period & 0xFF) | (reg.timer_high() << 8);
|
||||
self.period_timer = self.period;
|
||||
self.cur = 0;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cur_sample(&self) -> u8 {
|
||||
self.sample
|
||||
}
|
||||
|
||||
pub fn clock(&mut self) {
|
||||
if self.length_counter > 0 && self.period > 0 {
|
||||
if self.period_timer == 0 {
|
||||
self.period_timer = self.period;
|
||||
const SAMPLES: [u8; 32] = [
|
||||
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15,
|
||||
];
|
||||
self.cur = (self.cur + 1) % SAMPLES.len() as u8;
|
||||
self.sample = SAMPLES[self.cur as usize];
|
||||
} else {
|
||||
self.period_timer -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn q_frame_clock(&mut self) {
|
||||
if self.reload {
|
||||
self.length_counter = self.length.value() as u16;
|
||||
self.reload = self.length.halt();
|
||||
} else if self.length_counter == 0 {
|
||||
} else {
|
||||
self.length_counter -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn h_frame_clock(&mut self) {
|
||||
self.q_frame_clock();
|
||||
}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!(
|
||||
"Triangle Channel
|
||||
Linear Counter - Reload: {0:>3} ${0:02X}
|
||||
Linear Counter - Halted: {1}
|
||||
Period: {2:>3} ${2:04X}
|
||||
Length Counter - Reload Value: {3:>3} ${3:04X}
|
||||
Enabled: {4}
|
||||
Timer: {5:>3} ${5:02X}
|
||||
Frequency: ???
|
||||
Sequence Position: {6:>3} ${6:02X}
|
||||
Length Counter - Counter: {7:>3} ${7:02X}
|
||||
Linear Counter - Counter: {8:>3} ${8:02X}
|
||||
Linear Counter - Reload Flag: {9}
|
||||
Output: {10:>3} ${10:02X}
|
||||
",
|
||||
self.length.value(),
|
||||
self.length.halt(),
|
||||
self.period,
|
||||
0,
|
||||
self.enabled,
|
||||
self.period_timer,
|
||||
self.cur,
|
||||
0,
|
||||
self.length_counter,
|
||||
self.reload,
|
||||
self.cur_sample(),
|
||||
)
|
||||
.font(Font::MONOSPACE)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
bitfield::bitfield! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct NoiseEnvelope(u8);
|
||||
impl Debug;
|
||||
length_counter_halt, set_length_counter_halt: 5;
|
||||
constant_volume, set_constant_volume: 4;
|
||||
volume, set_volume: 3, 0;
|
||||
}
|
||||
|
||||
bitfield::bitfield! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ModePeriod(u8);
|
||||
impl Debug;
|
||||
mode, set_mode: 7;
|
||||
period, set_period: 3, 0;
|
||||
}
|
||||
|
||||
bitfield::bitfield! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct LengthCounterLoad(u8);
|
||||
impl Debug;
|
||||
length, set_length: 7, 3;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct NoiseChannel {
|
||||
enabled: bool,
|
||||
evelope: NoiseEnvelope,
|
||||
mode_period: ModePeriod,
|
||||
length_counter_load: LengthCounterLoad,
|
||||
|
||||
shr: u16,
|
||||
count: u16,
|
||||
length_counter: u8,
|
||||
}
|
||||
|
||||
impl NoiseChannel {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
evelope: NoiseEnvelope(0),
|
||||
mode_period: ModePeriod(0),
|
||||
length_counter_load: LengthCounterLoad(0),
|
||||
shr: 1,
|
||||
count: 0,
|
||||
length_counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, offset: u16, val: u8) {
|
||||
match offset {
|
||||
0x00 => (),
|
||||
0x01 => (),
|
||||
0x02 => (),
|
||||
0x03 => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cur_sample(&self) -> u8 {
|
||||
if self.length_counter == 0 || self.shr & 1 == 1 {
|
||||
0
|
||||
} else {
|
||||
self.evelope.volume()
|
||||
}
|
||||
}
|
||||
pub fn clock(&mut self) {
|
||||
const PERIODS: &[u16] = &[
|
||||
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4096,
|
||||
];
|
||||
if self.count == PERIODS[self.mode_period.period() as usize] / 2 {
|
||||
self.count = 0;
|
||||
let bit_0 = (self.shr & 0b1) << 14;
|
||||
let other = if self.mode_period.mode() {
|
||||
(self.shr & 0b100_0000) << 8
|
||||
} else {
|
||||
(self.shr & 0b10) << 13
|
||||
};
|
||||
self.shr = (self.shr >> 1) | (bit_0 ^ other);
|
||||
} else {
|
||||
self.count += 1;
|
||||
}
|
||||
}
|
||||
pub fn q_frame_clock(&mut self) {}
|
||||
pub fn h_frame_clock(&mut self) {
|
||||
self.q_frame_clock();
|
||||
}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!("").font(Font::MONOSPACE).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DeltaChannel {
|
||||
enabled: bool,
|
||||
sample: u8,
|
||||
}
|
||||
|
||||
impl DeltaChannel {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
sample: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, offset: u16, val: u8) {
|
||||
match offset {
|
||||
0x00 => (),
|
||||
0x01 => (),
|
||||
0x02 => (),
|
||||
0x03 => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cur_sample(&self) -> u8 {
|
||||
self.sample
|
||||
}
|
||||
pub fn clock(&mut self) {}
|
||||
pub fn q_frame_clock(&mut self) {}
|
||||
pub fn h_frame_clock(&mut self) {}
|
||||
|
||||
pub fn int(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!("").font(Font::MONOSPACE).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct FrameCounter {
|
||||
count: usize,
|
||||
mode_5_step: bool,
|
||||
interrupt_enabled: bool,
|
||||
irq: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct APU {
|
||||
pulse_1: PulseChannel,
|
||||
pulse_2: PulseChannel,
|
||||
triangle: TriangleChannel,
|
||||
noise: NoiseChannel,
|
||||
dmc: DeltaChannel,
|
||||
frame_counter: u8,
|
||||
frame_counter: FrameCounter,
|
||||
|
||||
samples: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for APU {
|
||||
@@ -69,21 +544,53 @@ impl std::fmt::Debug for APU {
|
||||
impl APU {
|
||||
pub fn init() -> Self {
|
||||
Self {
|
||||
pulse_1: PulseChannel { enabled: false },
|
||||
pulse_2: PulseChannel { enabled: false },
|
||||
triangle: TriangleChannel { enabled: false },
|
||||
noise: NoiseChannel { enabled: false },
|
||||
dmc: DeltaChannel { enabled: false },
|
||||
frame_counter: 0,
|
||||
pulse_1: PulseChannel::new(),
|
||||
pulse_2: PulseChannel::new(),
|
||||
triangle: TriangleChannel::new(),
|
||||
noise: NoiseChannel::new(),
|
||||
dmc: DeltaChannel::new(),
|
||||
frame_counter: FrameCounter {
|
||||
mode_5_step: false,
|
||||
interrupt_enabled: true,
|
||||
count: 0,
|
||||
irq: false,
|
||||
},
|
||||
|
||||
samples: vec![0; 100],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
*self = Self::init();
|
||||
}
|
||||
|
||||
pub fn read_reg(&mut self, offset: u16) -> u8 {
|
||||
// println!("APU read: {offset:02X}");
|
||||
match offset {
|
||||
0x15 => {
|
||||
let val = (u8::from(self.dmc.int()) << 7)
|
||||
| (u8::from(self.frame_counter.irq) << 6)
|
||||
| (u8::from(self.dmc.enabled) << 4)
|
||||
| (u8::from(self.noise.enabled) << 3)
|
||||
| (u8::from(self.triangle.enabled) << 2)
|
||||
| (u8::from(self.pulse_2.enabled) << 1)
|
||||
| (u8::from(self.pulse_1.enabled) << 0);
|
||||
self.frame_counter.irq = false;
|
||||
val
|
||||
}
|
||||
_ => panic!("No register at {:X}", offset),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_reg(&mut self, offset: u16, val: u8) {
|
||||
// println!("APU write: {offset:02X} <= {val:02X}");
|
||||
match offset {
|
||||
0x00..0x04 => self.pulse_1.write(offset - 0x00, val),
|
||||
0x04..0x08 => self.pulse_2.write(offset - 0x04, val),
|
||||
0x08..0x0C => self.triangle.write(offset - 0x08, val),
|
||||
0x0C..0x10 => self.noise.write(offset - 0x0C, val),
|
||||
0x10..0x14 => self.dmc.write(offset - 0x10, val),
|
||||
0x14 => (),
|
||||
0x15 => {
|
||||
self.dmc.enabled = val & 0b0001_0000 != 0;
|
||||
self.noise.enabled = val & 0b0000_1000 != 0;
|
||||
@@ -91,31 +598,112 @@ impl APU {
|
||||
self.pulse_2.enabled = val & 0b0000_0010 != 0;
|
||||
self.pulse_1.enabled = val & 0b0000_0001 != 0;
|
||||
}
|
||||
0x10 => {
|
||||
assert_eq!(val, 0x00);
|
||||
// TODO: implement this value
|
||||
}
|
||||
0x11 => {
|
||||
// TODO: load dmc counter with (val & 7F)
|
||||
0x17 => {
|
||||
self.frame_counter.mode_5_step = val & 0b1000_0000 != 0;
|
||||
self.frame_counter.interrupt_enabled = val & 0b0100_0000 == 0;
|
||||
if !self.frame_counter.interrupt_enabled {
|
||||
self.frame_counter.irq = false;
|
||||
}
|
||||
}
|
||||
// _ => (),
|
||||
_ => panic!("No register at {:X}", offset),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_one_clock_cycle(&mut self) -> bool {
|
||||
fn q_frame_clock(&mut self) {
|
||||
self.pulse_1.q_frame_clock();
|
||||
self.pulse_2.q_frame_clock();
|
||||
self.triangle.q_frame_clock();
|
||||
self.noise.q_frame_clock();
|
||||
self.dmc.q_frame_clock();
|
||||
}
|
||||
|
||||
fn h_frame_clock(&mut self) {
|
||||
self.pulse_1.h_frame_clock();
|
||||
self.pulse_2.h_frame_clock();
|
||||
self.triangle.h_frame_clock();
|
||||
self.noise.h_frame_clock();
|
||||
self.dmc.h_frame_clock();
|
||||
}
|
||||
|
||||
fn gen_sample(&mut self) {
|
||||
lut!(P_LUT: [u8; 32] = |n| (95.52 / (8128.0 / n as f64 + 100.0) * 255.0) as u8);
|
||||
let pulse_out = P_LUT[(self.pulse_1.cur_sample() + self.pulse_2.cur_sample()) as usize];
|
||||
lut!(TND_LUT: [u8; 204] = |n| (163.67 / (24329.0 / n as f64 + 100.0) * 255.0) as u8);
|
||||
let tnd_out = TND_LUT[3 * self.triangle.cur_sample() as usize
|
||||
+ 2 * self.noise.cur_sample() as usize
|
||||
+ self.dmc.cur_sample() as usize];
|
||||
self.samples.push(pulse_out + tnd_out);
|
||||
}
|
||||
|
||||
pub fn run_one_clock_cycle(&mut self, ppu_cycle: usize) -> bool {
|
||||
if ppu_cycle % 6 == 1 {
|
||||
// APU Frame Counter clock cycle
|
||||
self.frame_counter.count += 1;
|
||||
if self.frame_counter.count == 3728 {
|
||||
self.q_frame_clock();
|
||||
} else if self.frame_counter.count == 7456 {
|
||||
self.h_frame_clock();
|
||||
} else if self.frame_counter.count == 11185 {
|
||||
self.q_frame_clock();
|
||||
} else if self.frame_counter.count == 14914 && !self.frame_counter.mode_5_step {
|
||||
self.frame_counter.irq = self.frame_counter.interrupt_enabled;
|
||||
self.h_frame_clock();
|
||||
} else if self.frame_counter.count == 14915 && !self.frame_counter.mode_5_step {
|
||||
self.frame_counter.count = 0;
|
||||
self.frame_counter.irq = self.frame_counter.interrupt_enabled;
|
||||
} else if self.frame_counter.count == 18640 {
|
||||
self.h_frame_clock();
|
||||
self.frame_counter.count = 0;
|
||||
}
|
||||
|
||||
self.pulse_1.clock();
|
||||
self.pulse_2.clock();
|
||||
self.noise.clock();
|
||||
self.dmc.clock();
|
||||
}
|
||||
if ppu_cycle % 3 == 1 {
|
||||
self.triangle.clock();
|
||||
}
|
||||
if ppu_cycle % (6 * 4) == 1 {
|
||||
self.gen_sample();
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_frame_samples(&self) -> &[u8] {
|
||||
// println!("'Frame' of samples: {}", self.samples.len());
|
||||
&self.samples
|
||||
}
|
||||
|
||||
pub fn reset_frame_samples(&mut self) {
|
||||
self.samples.clear();
|
||||
}
|
||||
|
||||
pub fn peek_nmi(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn nmi_waiting(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn peek_irq(&self) -> bool {
|
||||
false
|
||||
self.frame_counter.irq
|
||||
}
|
||||
|
||||
pub fn irq_waiting(&mut self) -> bool {
|
||||
// TODO: implement logic
|
||||
false
|
||||
self.frame_counter.irq
|
||||
}
|
||||
|
||||
pub fn view<'s, T: 's>(&'s self) -> Element<'s, T> {
|
||||
column![
|
||||
self.pulse_1.view(),
|
||||
self.pulse_2.view(),
|
||||
self.triangle.view(),
|
||||
self.noise.view(),
|
||||
self.dmc.view(),
|
||||
]
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
96
src/audio.rs
Normal file
96
src/audio.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::AtomicBool, mpsc::{channel, Sender, TryRecvError}, Arc
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use cpal::{
|
||||
Device, FrameCount, Host, SampleFormat, Stream,
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
};
|
||||
use ringbuf::{
|
||||
HeapRb, SharedRb,
|
||||
traits::{Consumer, Observer, Producer, Split},
|
||||
wrap::caching::Caching,
|
||||
};
|
||||
|
||||
type SampleTx = Caching<Arc<HeapRb<u8>>, true, false>;
|
||||
|
||||
// TODO: Audio should be run through a low-pass filter,
|
||||
// a high-pass filter, as well as some kind of envelope
|
||||
// around pause events
|
||||
pub struct Audio {
|
||||
_host: Host,
|
||||
_device: Device,
|
||||
_stream: Stream,
|
||||
rb: SampleTx,
|
||||
last: usize,
|
||||
max: usize,
|
||||
paused: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Audio {
|
||||
pub fn init() -> Self {
|
||||
const BUFFER_SIZE: usize = 1 << 10;
|
||||
let host = cpal::default_host();
|
||||
let device = host.default_output_device().unwrap();
|
||||
// let mut configs = device.supported_output_configs().unwrap();
|
||||
// dbg!(configs.find(|c| c.sample_format() == SampleFormat::U8));
|
||||
// let v = dbg!(configs.next().unwrap().buffer_size());
|
||||
// let mut t = 0;
|
||||
let (prod, mut cons) = SharedRb::new(BUFFER_SIZE * 1024 * 1024).split();
|
||||
let paused = Arc::new(AtomicBool::new(true));
|
||||
let paused_inner = Arc::clone(&paused);
|
||||
|
||||
let stream = device
|
||||
.build_output_stream(
|
||||
&cpal::StreamConfig {
|
||||
channels: 1,
|
||||
sample_rate: 60 * 3723,
|
||||
buffer_size: cpal::BufferSize::Fixed(BUFFER_SIZE as FrameCount),
|
||||
},
|
||||
move |a: &mut [u8], _b| {
|
||||
if !paused_inner.load(std::sync::atomic::Ordering::Acquire) {
|
||||
let taken = cons.pop_slice(a);
|
||||
a[taken..].fill(128);
|
||||
}
|
||||
},
|
||||
|e| eprintln!("Audio: {e}"),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
stream.play().unwrap();
|
||||
Self {
|
||||
_host: host,
|
||||
_device: device,
|
||||
_stream: stream,
|
||||
rb: prod,
|
||||
paused,
|
||||
last: 0,
|
||||
max: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pause(&mut self) {
|
||||
self.paused.store(true, std::sync::atomic::Ordering::Release);
|
||||
let _ = self._stream.pause();
|
||||
}
|
||||
|
||||
pub fn submit(&mut self, samples: &[u8]) {
|
||||
let start = self.rb.occupied_len();
|
||||
self.max = self.max.max(self.last - start);
|
||||
println!("Buffer size: {:07}, Max: {:07}", start, self.max);
|
||||
println!(
|
||||
"Adding: {:07}, Played: {:07}",
|
||||
samples.len(),
|
||||
self.last - start
|
||||
);
|
||||
self.rb.push_slice(samples);
|
||||
self.last = self.rb.occupied_len();
|
||||
if self.last > 9000 {
|
||||
self.paused.store(false, std::sync::atomic::Ordering::Release);
|
||||
let _ = self._stream.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,98 @@
|
||||
pub struct Controllers {}
|
||||
use bitfield::bitfield;
|
||||
bitfield! {
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub struct ControllerState(u8);
|
||||
impl Debug;
|
||||
pub a, set_a: 0;
|
||||
pub b, set_b: 1;
|
||||
pub select, set_select: 2;
|
||||
pub start, set_start: 3;
|
||||
pub up, set_up: 4;
|
||||
pub down, set_down: 5;
|
||||
pub left, set_left: 6;
|
||||
pub right, set_right: 7;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct ControllerShiftReg {
|
||||
state: ControllerState,
|
||||
shift: u8,
|
||||
}
|
||||
|
||||
impl ControllerShiftReg {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
state: ControllerState(0),
|
||||
shift: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self) -> u8 {
|
||||
let val = self.shift & 1;
|
||||
// Reads after the first 8 are always 1
|
||||
self.shift = (self.shift >> 1) | 0x80;
|
||||
val
|
||||
}
|
||||
|
||||
fn load(&mut self) {
|
||||
self.shift = self.state.0;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Controllers {
|
||||
strobe: bool,
|
||||
primary: ControllerShiftReg,
|
||||
secondary: ControllerShiftReg,
|
||||
}
|
||||
|
||||
impl Controllers {
|
||||
pub fn init() -> Self {
|
||||
Self {}
|
||||
Self {
|
||||
strobe: false,
|
||||
primary: ControllerShiftReg::new(),
|
||||
secondary: ControllerShiftReg::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
*self = Self::init();
|
||||
}
|
||||
|
||||
pub fn read_joy1(&mut self) -> u8 {
|
||||
0
|
||||
if self.strobe {
|
||||
self.primary.load();
|
||||
}
|
||||
// TODO: Technically some kind of open-bus for all but the lowest 3 bits
|
||||
self.primary.read() | 0x40
|
||||
}
|
||||
|
||||
pub fn read_joy2(&mut self) -> u8 {
|
||||
0
|
||||
if self.strobe {
|
||||
self.secondary.load();
|
||||
}
|
||||
self.secondary.read() | 0x40
|
||||
}
|
||||
|
||||
pub fn write_joy_strobe(&mut self, val: u8) {
|
||||
if val & 1 == 0 {
|
||||
if self.strobe {
|
||||
self.primary.load();
|
||||
self.secondary.load();
|
||||
}
|
||||
self.strobe = false;
|
||||
} else {
|
||||
self.strobe = true;
|
||||
self.primary.load();
|
||||
self.secondary.load();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn controller_1(&mut self) -> &mut ControllerState {
|
||||
&mut self.primary.state
|
||||
}
|
||||
|
||||
pub fn controller_2(&mut self) -> &mut ControllerState {
|
||||
&mut self.secondary.state
|
||||
}
|
||||
pub fn write_joy_strobe(&mut self, val: u8) { }
|
||||
}
|
||||
|
||||
2019
src/cpu.rs
Normal file
2019
src/cpu.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DebugLog {
|
||||
current: String,
|
||||
history: Vec<String>,
|
||||
@@ -42,6 +43,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 {
|
||||
|
||||
524
src/debugger.rs
524
src/debugger.rs
@@ -1,13 +1,30 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use iced::{
|
||||
Element,
|
||||
Length::Fill,
|
||||
Length::{self, Fill},
|
||||
Point, Renderer, Size,
|
||||
advanced::{
|
||||
Widget,
|
||||
layout::Node,
|
||||
widget::{
|
||||
Tree,
|
||||
tree::{State, Tag},
|
||||
},
|
||||
},
|
||||
mouse,
|
||||
widget::{
|
||||
self, button, checkbox, column, container::bordered_box, image, number_input, row,
|
||||
self, Canvas, Text, button,
|
||||
canvas::{Frame, Program},
|
||||
checkbox, column,
|
||||
container::bordered_box,
|
||||
hex_input, image, number_input, row,
|
||||
rule::horizontal,
|
||||
scrollable, text,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{CycleResult, NES};
|
||||
use crate::{Break, CycleResult, NES, PPU, mem::Mapped};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DebuggerState {
|
||||
@@ -17,12 +34,13 @@ pub struct DebuggerState {
|
||||
scan_lines: usize,
|
||||
to_scan_line: usize,
|
||||
frames: usize,
|
||||
breakpoint: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DebuggerMessage {
|
||||
Run,
|
||||
Pause,
|
||||
// Run,
|
||||
// Pause,
|
||||
SetPPUCycles(usize),
|
||||
RunPPUCycles,
|
||||
SetCPUCycles(usize),
|
||||
@@ -35,6 +53,37 @@ pub enum DebuggerMessage {
|
||||
RunToScanLine,
|
||||
SetFrames(usize),
|
||||
RunFrames,
|
||||
SetBreakpoint(usize),
|
||||
RunBreakpoint,
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -46,18 +95,13 @@ impl DebuggerState {
|
||||
scan_lines: 1,
|
||||
to_scan_line: 1,
|
||||
frames: 1,
|
||||
breakpoint: 0xEA5A,
|
||||
// cpu_cycles: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view<'s>(&'s self, nes: &'s NES) -> Element<'s, DebuggerMessage> {
|
||||
column![
|
||||
row![
|
||||
button(image("./images/ic_fluent_play_24_filled.png"))
|
||||
.on_press(DebuggerMessage::Run),
|
||||
button(image("./images/ic_fluent_pause_24_filled.png"))
|
||||
.on_press(DebuggerMessage::Pause),
|
||||
],
|
||||
iced::widget::rule::horizontal(2.),
|
||||
row![column![
|
||||
text("Status"),
|
||||
@@ -66,7 +110,7 @@ impl DebuggerState {
|
||||
labelled("X:", text(format!("{:02X}", nes.cpu.x))),
|
||||
labelled("Y:", text(format!("{:02X}", nes.cpu.y))),
|
||||
labelled("PC:", text(format!("{:04X}", nes.cpu.pc))),
|
||||
labelled("Cycle:", text(format!("{}", nes.cycle))),
|
||||
labelled("Cycle:", text(format!("{}", nes.cpu_cycle()))),
|
||||
labelled("SP:", text(format!("{:02X}", nes.cpu.sp))),
|
||||
]
|
||||
.spacing(5.),
|
||||
@@ -85,38 +129,57 @@ 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(if nes.ppu().scanline == 261 { -1 } else { nes.ppu().scanline as isize })),
|
||||
labelled("PPU Cycle", text(nes.ppu().cycle)),
|
||||
labelled("Frame", text(nes.ppu().frame_count)),
|
||||
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.next_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("Sprite 0 Hit", nes.ppu().sprite_zero_hit),
|
||||
labelled_box("Sprite 0 Overflow", nes.ppu().oam.overflow),
|
||||
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(
|
||||
@@ -155,16 +218,26 @@ impl DebuggerState {
|
||||
DebuggerMessage::SetFrames,
|
||||
DebuggerMessage::RunFrames
|
||||
),
|
||||
run_type_hex(
|
||||
"To Address:",
|
||||
self.breakpoint,
|
||||
DebuggerMessage::SetBreakpoint,
|
||||
DebuggerMessage::RunBreakpoint
|
||||
),
|
||||
],
|
||||
]
|
||||
.spacing(5.),
|
||||
scrollable(column(
|
||||
nes.debug_log()
|
||||
.history()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|s| text(s).line_height(0.9).into())
|
||||
).spacing(0))
|
||||
horizontal(2),
|
||||
scrollable(
|
||||
column(
|
||||
nes.debug_log()
|
||||
.history()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|s| text(s).line_height(0.9).into())
|
||||
)
|
||||
.spacing(0)
|
||||
)
|
||||
.width(Fill),
|
||||
],],
|
||||
]
|
||||
@@ -175,16 +248,43 @@ impl DebuggerState {
|
||||
|
||||
fn run_n_clock_cycles(nes: &mut NES, n: usize) {
|
||||
for _ in 0..n {
|
||||
nes.run_one_clock_cycle();
|
||||
}
|
||||
}
|
||||
fn run_until(nes: &mut NES, mut f: impl FnMut(CycleResult, &NES) -> bool) {
|
||||
loop {
|
||||
if f(nes.run_one_clock_cycle(), nes) {
|
||||
if nes.run_one_clock_cycle(&Break::default()).dbg_int || nes.halted() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn run_until(
|
||||
nes: &mut NES,
|
||||
br: &Break,
|
||||
mut f: impl FnMut(CycleResult, &NES) -> bool,
|
||||
// mut count: usize,
|
||||
) {
|
||||
// Always run at least 1 cycle
|
||||
let mut res = nes.run_one_clock_cycle(&Break::default());
|
||||
while !nes.halted() && !res.dbg_int && !f(res, nes) {
|
||||
// if res.dbg_int || f(res, nes) {
|
||||
// count -= 1;
|
||||
// if count <= 0 {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if nes.halted() {
|
||||
// break;
|
||||
// }
|
||||
res = nes.run_one_clock_cycle(br);
|
||||
}
|
||||
}
|
||||
fn run_until_n(
|
||||
nes: &mut NES,
|
||||
br: &Break,
|
||||
mut f: impl FnMut(CycleResult, &NES) -> bool,
|
||||
mut count: usize,
|
||||
) {
|
||||
while count > 0 {
|
||||
Self::run_until(nes, br, &mut f);
|
||||
count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, message: DebuggerMessage, nes: &mut NES) {
|
||||
match message {
|
||||
@@ -192,18 +292,50 @@ impl DebuggerState {
|
||||
DebuggerMessage::SetCPUCycles(n) => self.cpu_cycles = n,
|
||||
DebuggerMessage::SetInstructions(n) => self.instructions = n,
|
||||
DebuggerMessage::SetScanLines(n) => self.scan_lines = n,
|
||||
DebuggerMessage::SetToScanLine(n) => self.to_scan_line = n,
|
||||
DebuggerMessage::SetToScanLine(n) => self.to_scan_line = n.min(261), // Max scanline is 261
|
||||
DebuggerMessage::SetFrames(n) => self.frames = n,
|
||||
DebuggerMessage::SetBreakpoint(n) => self.breakpoint = n,
|
||||
DebuggerMessage::RunPPUCycles => Self::run_n_clock_cycles(nes, self.ppu_cycles),
|
||||
DebuggerMessage::RunCPUCycles => Self::run_n_clock_cycles(nes, self.cpu_cycles * 3),
|
||||
DebuggerMessage::RunInstructions => Self::run_until(nes, |c, _| c.cpu_exec),
|
||||
// DebuggerMessage::RunInstructions => Self::run_until_n(
|
||||
// nes,
|
||||
// &Break {
|
||||
// cpu_exec: true,
|
||||
// ..Break::default()
|
||||
// },
|
||||
// |_, _| false,
|
||||
// self.instructions,
|
||||
// ),
|
||||
DebuggerMessage::RunInstructions => Self::run_until_n(
|
||||
nes,
|
||||
&Break {
|
||||
..Break::default()
|
||||
},
|
||||
|res, _| res.cpu_exec,
|
||||
self.instructions,
|
||||
),
|
||||
DebuggerMessage::RunScanLines => Self::run_n_clock_cycles(nes, self.scan_lines * 341),
|
||||
DebuggerMessage::RunToScanLine => {
|
||||
Self::run_until(nes, |_, n| n.ppu.scanline == self.to_scan_line)
|
||||
}
|
||||
DebuggerMessage::RunToScanLine => Self::run_until(
|
||||
nes,
|
||||
&Break {
|
||||
ppu_scanline: true,
|
||||
..Break::default()
|
||||
},
|
||||
|_, n| n.ppu.scanline == self.to_scan_line,
|
||||
),
|
||||
DebuggerMessage::RunFrames => Self::run_n_clock_cycles(nes, self.frames * 341 * 262),
|
||||
DebuggerMessage::Run => todo!(),
|
||||
DebuggerMessage::Pause => todo!(),
|
||||
DebuggerMessage::RunBreakpoint => Self::run_until(
|
||||
nes,
|
||||
&Break {
|
||||
break_points: vec![self.breakpoint as u16],
|
||||
..Break::default()
|
||||
},
|
||||
|_, nes| nes.cpu.pc as usize == self.breakpoint,
|
||||
),
|
||||
// DebuggerMessage::Run => {
|
||||
// Self::run_until(nes, &Break { ..Break::default() }, |_, nes| nes.halted())
|
||||
// }
|
||||
// DebuggerMessage::Pause => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,6 +355,21 @@ fn run_type<'a, Message: Clone + 'a>(
|
||||
.spacing(1.)
|
||||
.into()
|
||||
}
|
||||
fn run_type_hex<'a, Message: Clone + 'a>(
|
||||
label: &'a str,
|
||||
val: usize,
|
||||
update: impl Fn(usize) -> Message + 'a,
|
||||
run: Message,
|
||||
) -> Element<'a, Message> {
|
||||
row![
|
||||
widget::container(text(label)).padding(2.),
|
||||
widget::container(hex_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.),
|
||||
]
|
||||
.spacing(1.)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn labelled<'a, Message: 'a>(
|
||||
label: &'a str,
|
||||
@@ -236,8 +383,279 @@ 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 Mapped, &'a PPU),
|
||||
PatternTable(&'a Mapped, &'a PPU),
|
||||
Palette(&'a Mapped, &'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(mem, ppu) => ppu.render_name_table(mem, &mut name_table_frame),
|
||||
DbgImage::PatternTable(mem, ppu) => {
|
||||
ppu.render_pattern_tables(mem, &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(mem, ppu) => ppu.name_cursor_info(mem, 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,
|
||||
})
|
||||
}
|
||||
|
||||
576
src/hex_view.rs
576
src/hex_view.rs
@@ -1,19 +1,103 @@
|
||||
use std::fmt;
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use iced::{widget::{column, lazy, row, text}, Element, Font, Length::Fill, Task};
|
||||
use iced::{
|
||||
Element, Fill, Font, Padding, Task,
|
||||
advanced::graphics::text::{
|
||||
FontSystem,
|
||||
cosmic_text::{
|
||||
Attrs, Family, Metrics,
|
||||
fontdb::{ID, Query},
|
||||
},
|
||||
font_system,
|
||||
},
|
||||
keyboard::{Key, key::Named},
|
||||
mouse::{Button, ScrollDelta},
|
||||
widget::{column, lazy, row, text},
|
||||
};
|
||||
use iced_core::{
|
||||
Clipboard, Color, Event, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Text, Vector,
|
||||
Widget,
|
||||
layout::{Limits, Node},
|
||||
mouse::{Cursor, Interaction},
|
||||
overlay,
|
||||
renderer::{Quad, Style},
|
||||
widget::{
|
||||
Operation, Tree,
|
||||
tree::{State, Tag},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{PPU, mem::Mapped, ppu::PPUMMRegisters};
|
||||
|
||||
pub trait Memory {
|
||||
fn peek(&self, val: u16) -> Option<u8>;
|
||||
fn peek(&self, val: usize) -> Option<u8>;
|
||||
fn len(&self) -> usize;
|
||||
fn edit_ver(&self) -> usize;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Cpu<'a>(&'a Mapped);
|
||||
|
||||
impl Memory for Cpu<'_> {
|
||||
fn peek(&self, val: usize) -> Option<u8> {
|
||||
self.0.peek_cpu(val as u16)
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
0x10000
|
||||
}
|
||||
|
||||
fn edit_ver(&self) -> usize {
|
||||
self.0.cpu_edit_ver()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Ppu<'a>(&'a Mapped, &'a PPU);
|
||||
|
||||
impl Memory for Ppu<'_> {
|
||||
fn peek(&self, val: usize) -> Option<u8> {
|
||||
match self.0.peek_ppu(val as u16) {
|
||||
Ok(v) => Some(v),
|
||||
Err(Some((PPUMMRegisters::Palette, off))) => Some(self.1.palette.ram(off as u8)),
|
||||
Err(None) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
0x4000
|
||||
}
|
||||
|
||||
fn edit_ver(&self) -> usize {
|
||||
self.0.ppu_edit_ver()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Oam<'a>(pub &'a PPU);
|
||||
|
||||
impl Memory for Oam<'_> {
|
||||
fn peek(&self, val: usize) -> Option<u8> {
|
||||
Some(self.0.peek_oam(val as u8))
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
0x100
|
||||
}
|
||||
|
||||
fn edit_ver(&self) -> usize {
|
||||
self.0.oam_edit_ver()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HexEvent {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct HexView {
|
||||
|
||||
}
|
||||
pub struct HexView {}
|
||||
|
||||
struct Val(Option<u8>);
|
||||
impl fmt::Display for Val {
|
||||
@@ -31,9 +115,9 @@ impl HexView {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn render<'a>(&self, mem: &'a impl Memory) -> Element<'a, HexEvent> {
|
||||
struct Row<'a, M>(u16, &'a M);
|
||||
impl<M: Memory> fmt::Display for Row<'_, M> {
|
||||
pub fn render_any<'a, M: Memory + Copy + 'a>(&self, mem: M) -> Element<'a, HexEvent> {
|
||||
struct Row<M: Memory>(usize, M);
|
||||
impl<'a, M: Memory + 'a> fmt::Display for Row<M> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", Val(self.1.peek(self.0)))?;
|
||||
for i in 1..16 {
|
||||
@@ -44,15 +128,477 @@ impl HexView {
|
||||
}
|
||||
column![
|
||||
text!("Hex view"),
|
||||
iced::widget::scrollable(lazy(mem.edit_ver(), |_| column([
|
||||
text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F").font(Font::MONOSPACE).into()
|
||||
].into_iter().chain((0..u16::MAX).step_by(16).map(|off| {
|
||||
text!(" {off:04X} | {}", Row(off, mem)).font(Font::MONOSPACE).into()
|
||||
}))))).width(Fill),
|
||||
].width(Fill).into()
|
||||
iced::widget::scrollable(lazy(mem.edit_ver(), move |_| column(
|
||||
[
|
||||
text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F")
|
||||
.font(Font::MONOSPACE)
|
||||
.into()
|
||||
]
|
||||
.into_iter()
|
||||
.chain((0..mem.len()).step_by(16).map(|off| {
|
||||
text!(" {off:04X} | {}", Row(off, mem))
|
||||
.font(Font::MONOSPACE)
|
||||
.into()
|
||||
}))
|
||||
)))
|
||||
.width(Fill),
|
||||
]
|
||||
.width(Fill)
|
||||
.into()
|
||||
}
|
||||
pub fn render_cpu<'a>(&self, mem: &'a Mapped) -> Element<'a, HexEvent> {
|
||||
// self.render_any(Cpu(mem))
|
||||
Element::new(
|
||||
hex_editor::<Cpu<'a>, HexEvent, iced::Renderer>(Cpu(mem)).font(Font::MONOSPACE),
|
||||
)
|
||||
}
|
||||
pub fn render_ppu<'a>(&self, mem: &'a Mapped, ppu: &'a PPU) -> Element<'a, HexEvent> {
|
||||
// self.render_any(Ppu(mem, ppu))
|
||||
Element::new(
|
||||
hex_editor::<Ppu<'a>, HexEvent, iced::Renderer>(Ppu(mem, ppu)).font(Font::MONOSPACE),
|
||||
)
|
||||
}
|
||||
pub fn render_oam<'a>(&self, ppu: &'a PPU) -> Element<'a, HexEvent> {
|
||||
Element::new(
|
||||
hex_editor::<Oam<'a>, HexEvent, iced::Renderer>(Oam(ppu)).font(Font::MONOSPACE),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update(&mut self, ev: HexEvent) -> Task<HexEvent> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Buffer {
|
||||
fn peek(&self, val: usize) -> Option<u8>;
|
||||
fn len(&self) -> usize;
|
||||
}
|
||||
|
||||
impl Buffer for &[u8] {
|
||||
fn peek(&self, val: usize) -> Option<u8> {
|
||||
self.get(val).copied()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.iter().len()
|
||||
}
|
||||
}
|
||||
impl<M: Memory> Buffer for M {
|
||||
fn peek(&self, val: usize) -> Option<u8> {
|
||||
self.peek(val)
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hex_editor<B: Buffer, M, R: iced_core::text::Renderer>(raw: B) -> HexEditor<B, M, R> {
|
||||
HexEditor {
|
||||
val: raw,
|
||||
on_edit: None,
|
||||
font: None,
|
||||
font_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct HexEdit {
|
||||
position: usize,
|
||||
old_value: u8,
|
||||
new_value: u8,
|
||||
}
|
||||
|
||||
pub struct HexEditor<B, M, R: iced_core::text::Renderer> {
|
||||
val: B,
|
||||
font_size: Option<Pixels>,
|
||||
font: Option<R::Font>,
|
||||
on_edit: Option<Box<dyn Fn(HexEdit) -> M>>,
|
||||
}
|
||||
|
||||
impl<B, M, R> HexEditor<B, M, R>
|
||||
where
|
||||
R: iced_core::text::Renderer,
|
||||
{
|
||||
pub fn font(mut self, font: R::Font) -> Self {
|
||||
self.font = Some(font);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Buffer, M, R> HexEditor<B, M, R>
|
||||
where
|
||||
R: iced_core::text::Renderer,
|
||||
{
|
||||
fn value_bounds(&self, renderer: &R, layout: Layout<'_>) -> Rectangle {
|
||||
let size = self.font_size.unwrap_or(renderer.default_size());
|
||||
layout
|
||||
.bounds()
|
||||
.shrink(Padding::new(0.).top(size.0 * 1.5).right(10.))
|
||||
}
|
||||
|
||||
fn scrollbar_bounds(&self, renderer: &R, layout: Layout<'_>) -> Rectangle {
|
||||
let size = self.font_size.unwrap_or(renderer.default_size());
|
||||
layout.bounds().shrink(
|
||||
Padding::new(0.)
|
||||
.top(size.0 * 1.5)
|
||||
.left(layout.bounds().width - 10.),
|
||||
)
|
||||
}
|
||||
|
||||
fn row_height(&self, renderer: &R) -> f32 {
|
||||
self.font_size.unwrap_or(renderer.default_size()).0 * LINE_HEIGHT
|
||||
}
|
||||
|
||||
fn scroll_max(&self, renderer: &R, layout: Layout<'_>) -> f32 {
|
||||
let size = self.font_size.unwrap_or(renderer.default_size());
|
||||
let bounds = layout.bounds().shrink(Padding::new(0.).top(size.0 * 1.5));
|
||||
let rows = self.val.len().div_ceil(0x10);
|
||||
(self.row_height(renderer) * rows as f32 - bounds.height).max(0.)
|
||||
}
|
||||
|
||||
fn scroll(
|
||||
&self,
|
||||
state: &mut HexEditorState,
|
||||
renderer: &R,
|
||||
layout: Layout<'_>,
|
||||
delta: &ScrollDelta,
|
||||
) {
|
||||
// let size = self.font_size.unwrap_or(renderer.default_size());
|
||||
// let bounds = layout.bounds().shrink(Padding::new(0.).top(size.0 * 1.5));
|
||||
// let rows = self.val.len().div_ceil(0x10);
|
||||
let max = self.scroll_max(renderer, layout);
|
||||
// println!("max: {max}, rows: {rows}");
|
||||
match delta {
|
||||
ScrollDelta::Lines { y, .. } => {
|
||||
state.offset_y += y * self.row_height(renderer);
|
||||
}
|
||||
ScrollDelta::Pixels { y, .. } => {
|
||||
state.offset_y += y;
|
||||
}
|
||||
}
|
||||
if state.offset_y > 0. {
|
||||
state.offset_y = 0.;
|
||||
} else if state.offset_y < -max {
|
||||
state.offset_y = -max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct HexEditorState {
|
||||
offset_y: f32,
|
||||
selected: usize,
|
||||
dragging: bool,
|
||||
}
|
||||
|
||||
const LINE_HEIGHT: f32 = 1.3;
|
||||
|
||||
impl<B: Buffer, M, T, R> Widget<M, T, R> for HexEditor<B, M, R>
|
||||
where
|
||||
R: iced_core::text::Renderer,
|
||||
{
|
||||
fn tag(&self) -> Tag {
|
||||
Tag::of::<HexEditorState>()
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::new(HexEditorState::default())
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size::new(Length::Fill, Length::Fill)
|
||||
}
|
||||
|
||||
fn layout(&mut self, _tree: &mut Tree, _renderer: &R, limits: &Limits) -> Node {
|
||||
Node::new(limits.max())
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut R,
|
||||
theme: &T,
|
||||
style: &Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state: &HexEditorState = tree.state.downcast_ref();
|
||||
let size = self.font_size.unwrap_or(renderer.default_size());
|
||||
let font = self.font.unwrap_or(renderer.default_font());
|
||||
|
||||
// let fonts = font_system();
|
||||
// let mut font_sys = fonts.write().unwrap();
|
||||
// let id = font_sys.raw().db().query(&Query {
|
||||
// families: &[Family::Monospace],
|
||||
// ..Query::default()
|
||||
// });
|
||||
// let f = font_sys.raw().get_font(id.unwrap(), iced::advanced::graphics::text::cosmic_text::Weight(1)).unwrap();
|
||||
// let width = f.metrics();
|
||||
// println!("Width: {width:?}");
|
||||
// iced::advanced::graphics::text::cosmic_text::Buffer::new(
|
||||
// font_sys.raw(),
|
||||
// Metrics::new(size.0, size.0),
|
||||
// ).layout_runs();
|
||||
// TODO: this needs to be computed from the font
|
||||
let col_width = 0.6 * size.0;
|
||||
|
||||
let rows = self.val.len().div_ceil(0x10);
|
||||
let row_label_length = rows.ilog2().div_ceil(4) as usize;
|
||||
let mut draw = |v: &str, pos: Point| {
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: format!("{}", v),
|
||||
bounds: viewport.size(),
|
||||
size,
|
||||
line_height: size.into(),
|
||||
font,
|
||||
align_x: iced_core::text::Alignment::Left,
|
||||
align_y: iced_core::alignment::Vertical::Top,
|
||||
shaping: iced_core::text::Shaping::Basic,
|
||||
wrapping: iced_core::text::Wrapping::None,
|
||||
hint_factor: None,
|
||||
},
|
||||
pos,
|
||||
Color::WHITE,
|
||||
layout.bounds(),
|
||||
);
|
||||
};
|
||||
draw("0", Point::new(0., 0.));
|
||||
for i in 0..0x10 {
|
||||
draw(
|
||||
"0",
|
||||
layout.position()
|
||||
+ Vector::new((3 + row_label_length + 3 * i) as f32 * col_width, 0.),
|
||||
);
|
||||
draw(
|
||||
"0",
|
||||
layout.position()
|
||||
+ Vector::new((4 + row_label_length + 3 * i) as f32 * col_width, 0.),
|
||||
);
|
||||
}
|
||||
// renderer.fill_text(
|
||||
// Text {
|
||||
// content: format!(
|
||||
// "{:width$} 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F {}",
|
||||
// "",
|
||||
// state.offset_y,
|
||||
// width = row_label_length
|
||||
// ),
|
||||
// bounds: viewport.size(),
|
||||
// size,
|
||||
// line_height: size.into(),
|
||||
// font,
|
||||
// align_x: iced_core::text::Alignment::Left,
|
||||
// align_y: iced_core::alignment::Vertical::Top,
|
||||
// shaping: iced_core::text::Shaping::Basic,
|
||||
// wrapping: iced_core::text::Wrapping::None,
|
||||
// hint_factor: None,
|
||||
// },
|
||||
// layout.position(),
|
||||
// Color::WHITE,
|
||||
// layout.bounds(),
|
||||
// );
|
||||
struct HexV(Option<u8>);
|
||||
impl Display for HexV {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
Some(v) => write!(f, "{:02X}", v),
|
||||
None => write!(f, "XX"),
|
||||
}
|
||||
}
|
||||
}
|
||||
// if rows > 0 {
|
||||
// rows.ilog2()
|
||||
// }
|
||||
let bounds = self.value_bounds(renderer, layout);
|
||||
let mut pos = bounds.position() + Vector::new(0., state.offset_y);
|
||||
for row in 0..rows {
|
||||
if bounds.contains(pos) || bounds.contains(pos + Vector::new(0., size.0 * LINE_HEIGHT))
|
||||
{
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: format!(
|
||||
"{:0width$X}0: {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}",
|
||||
row,
|
||||
HexV(self.val.peek(row * 0x10 + 0x0)),
|
||||
HexV(self.val.peek(row * 0x10 + 0x1)),
|
||||
HexV(self.val.peek(row * 0x10 + 0x2)),
|
||||
HexV(self.val.peek(row * 0x10 + 0x3)),
|
||||
HexV(self.val.peek(row * 0x10 + 0x4)),
|
||||
HexV(self.val.peek(row * 0x10 + 0x5)),
|
||||
HexV(self.val.peek(row * 0x10 + 0x6)),
|
||||
HexV(self.val.peek(row * 0x10 + 0x7)),
|
||||
HexV(self.val.peek(row * 0x10 + 0x8)),
|
||||
HexV(self.val.peek(row * 0x10 + 0x9)),
|
||||
HexV(self.val.peek(row * 0x10 + 0xA)),
|
||||
HexV(self.val.peek(row * 0x10 + 0xB)),
|
||||
HexV(self.val.peek(row * 0x10 + 0xC)),
|
||||
HexV(self.val.peek(row * 0x10 + 0xD)),
|
||||
HexV(self.val.peek(row * 0x10 + 0xE)),
|
||||
HexV(self.val.peek(row * 0x10 + 0xF)),
|
||||
width = row_label_length,
|
||||
),
|
||||
bounds: viewport.size(),
|
||||
size,
|
||||
line_height: size.into(),
|
||||
font,
|
||||
align_x: iced_core::text::Alignment::Left,
|
||||
align_y: iced_core::alignment::Vertical::Top,
|
||||
shaping: iced_core::text::Shaping::Basic,
|
||||
wrapping: iced_core::text::Wrapping::None,
|
||||
hint_factor: None,
|
||||
},
|
||||
pos,
|
||||
Color::WHITE,
|
||||
bounds,
|
||||
);
|
||||
}
|
||||
pos += Vector::new(0., size.0 * LINE_HEIGHT);
|
||||
}
|
||||
|
||||
let scrollbar = self.scrollbar_bounds(renderer, layout);
|
||||
renderer.fill_quad(
|
||||
Quad {
|
||||
bounds: scrollbar,
|
||||
..Quad::default()
|
||||
},
|
||||
Color::BLACK,
|
||||
);
|
||||
let pos = state.offset_y / self.scroll_max(renderer, layout);
|
||||
renderer.fill_quad(
|
||||
Quad {
|
||||
bounds: Rectangle::new(
|
||||
Point::new(scrollbar.x, scrollbar.y - pos * (scrollbar.height - 20.)),
|
||||
Size::new(10., 20.),
|
||||
),
|
||||
..Quad::default()
|
||||
},
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&mut self,
|
||||
_tree: &mut Tree,
|
||||
_layout: Layout<'_>,
|
||||
_renderer: &R,
|
||||
_operation: &mut dyn Operation,
|
||||
) {
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: Cursor,
|
||||
renderer: &R,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, M>,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
// if !matches!(event, Event::Window(_)) {
|
||||
// println!("Event: {:#?}", event);
|
||||
// }
|
||||
match event {
|
||||
Event::Keyboard(iced::keyboard::Event::KeyPressed {
|
||||
key: Key::Named(Named::PageUp),
|
||||
..
|
||||
}) => {
|
||||
let state: &mut HexEditorState = tree.state.downcast_mut();
|
||||
self.scroll(
|
||||
state,
|
||||
renderer,
|
||||
layout,
|
||||
&ScrollDelta::Pixels {
|
||||
x: 0.,
|
||||
y: self.value_bounds(renderer, layout).height,
|
||||
},
|
||||
);
|
||||
shell.request_redraw();
|
||||
shell.capture_event();
|
||||
}
|
||||
Event::Keyboard(iced::keyboard::Event::KeyPressed {
|
||||
key: Key::Named(Named::PageDown),
|
||||
..
|
||||
}) => {
|
||||
let state: &mut HexEditorState = tree.state.downcast_mut();
|
||||
self.scroll(
|
||||
state,
|
||||
renderer,
|
||||
layout,
|
||||
&ScrollDelta::Pixels {
|
||||
x: 0.,
|
||||
y: -self.value_bounds(renderer, layout).height,
|
||||
},
|
||||
);
|
||||
shell.request_redraw();
|
||||
shell.capture_event();
|
||||
}
|
||||
Event::Mouse(iced::mouse::Event::WheelScrolled { delta }) => {
|
||||
let state: &mut HexEditorState = tree.state.downcast_mut();
|
||||
let bounds = self.value_bounds(renderer, layout);
|
||||
if cursor.is_over(bounds) {
|
||||
self.scroll(state, renderer, layout, delta);
|
||||
shell.request_redraw();
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Mouse(iced::mouse::Event::ButtonPressed(Button::Left)) => {
|
||||
let state: &mut HexEditorState = tree.state.downcast_mut();
|
||||
let bounds = self.scrollbar_bounds(renderer, layout);
|
||||
if let Some(pos) = cursor.position_in(bounds) {
|
||||
state.offset_y = -(pos.y / bounds.height * self.scroll_max(renderer, layout));
|
||||
state.dragging = true;
|
||||
shell.request_redraw();
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Mouse(iced::mouse::Event::ButtonReleased(Button::Left)) => {
|
||||
let state: &mut HexEditorState = tree.state.downcast_mut();
|
||||
state.dragging = false;
|
||||
}
|
||||
// Event::Mouse(iced::mouse::Event::CursorLeft) => {
|
||||
// let state: &mut HexEditorState = tree.state.downcast_mut();
|
||||
// state.dragging = false;
|
||||
// }
|
||||
Event::Mouse(iced::mouse::Event::CursorMoved { .. }) => {
|
||||
let state: &mut HexEditorState = tree.state.downcast_mut();
|
||||
if state.dragging {
|
||||
let bounds = self.scrollbar_bounds(renderer, layout);
|
||||
if let Some(pos) = cursor.position_in(bounds) {
|
||||
state.offset_y =
|
||||
-(pos.y / bounds.height * self.scroll_max(renderer, layout));
|
||||
shell.request_redraw();
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_layout: Layout<'_>,
|
||||
_cursor: Cursor,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &R,
|
||||
) -> Interaction {
|
||||
Interaction::None
|
||||
}
|
||||
|
||||
fn overlay<'a>(
|
||||
&'a mut self,
|
||||
_tree: &'a mut Tree,
|
||||
_layout: Layout<'a>,
|
||||
_renderer: &R,
|
||||
_viewport: &Rectangle,
|
||||
_translation: Vector,
|
||||
) -> Option<overlay::Element<'a, M, T, R>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
2245
src/lib.rs
2245
src/lib.rs
File diff suppressed because it is too large
Load Diff
414
src/main.rs
414
src/main.rs
@@ -1,28 +1,51 @@
|
||||
use std::{collections::HashMap, fmt, time::Duration};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use iced::{
|
||||
Color, Element, Font,
|
||||
Element,
|
||||
Length::{Fill, Shrink},
|
||||
Point, Renderer, Size, Subscription, Task, Theme, mouse,
|
||||
Point, Rectangle, Renderer, Size, Subscription, Task, Theme,
|
||||
keyboard::{self, Key, Modifiers, key::Named},
|
||||
mouse, time,
|
||||
widget::{
|
||||
Canvas, button,
|
||||
self, Canvas, button,
|
||||
canvas::{Frame, Program},
|
||||
column, container, row, text,
|
||||
column, container, image, row,
|
||||
},
|
||||
window::{self, Id, Settings},
|
||||
};
|
||||
use nes_emu::{
|
||||
NES, PPU,
|
||||
debugger::{DebuggerMessage, DebuggerState},
|
||||
Break, NES,
|
||||
audio::Audio,
|
||||
debugger::{DbgImage, DebuggerMessage, DebuggerState, dbg_image},
|
||||
header_menu::header_menu,
|
||||
hex_view::{HexEvent, HexView},
|
||||
resize_watcher::resize_watcher,
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::{io::AsyncWriteExt, 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"), "/", "even_odd.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 = concat!(env!("ROM_DIR"), "/", "render-updating.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_palette_shared.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "input_test.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "scrolling.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "scrolling_colors.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "sprites.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_pulse_channel_1.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_pulse_channel_1_evelope.nes");
|
||||
const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_pulse_1.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_triangle.nes");
|
||||
// const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
|
||||
// const ROM_FILE: &str = "./cpu_timing_test.nes";
|
||||
// const ROM_FILE: &str = "../nes-test-roms/instr_test-v5/official_only.nes";
|
||||
|
||||
extern crate nes_emu;
|
||||
|
||||
@@ -39,9 +62,17 @@ fn main() -> Result<(), iced::Error> {
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum MemoryTy {
|
||||
Cpu,
|
||||
PPU,
|
||||
OAM,
|
||||
}
|
||||
|
||||
impl fmt::Display for MemoryTy {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Export {self:?} Memory")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -52,6 +83,7 @@ enum WindowType {
|
||||
TileViewer,
|
||||
Palette,
|
||||
Debugger,
|
||||
Apu,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -61,6 +93,7 @@ enum HeaderButton {
|
||||
// OpenTileViewer,
|
||||
// OpenDebugger,
|
||||
Open(WindowType),
|
||||
OpenRom,
|
||||
Reset,
|
||||
PowerCycle,
|
||||
}
|
||||
@@ -69,36 +102,49 @@ 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::Memory(MemoryTy::OAM, _)) => write!(f, "Open OAM 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"),
|
||||
Self::Open(WindowType::Palette) => write!(f, "Open Palette"),
|
||||
Self::Open(WindowType::Main) => write!(f, "Create new Main window"),
|
||||
Self::Open(WindowType::Apu) => write!(f, "Open APU info"),
|
||||
Self::Reset => write!(f, "Reset"),
|
||||
Self::PowerCycle => write!(f, "Power Cycle"),
|
||||
Self::OpenRom => write!(f, "Open ROM file"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Emulator {
|
||||
running: bool,
|
||||
nes: NES,
|
||||
windows: HashMap<Id, WindowType>,
|
||||
debugger: DebuggerState,
|
||||
main_win_size: Size,
|
||||
prev: [Instant; 2],
|
||||
audio: Audio,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
Tick(usize),
|
||||
Frame,
|
||||
DMA,
|
||||
CPU,
|
||||
DebugInt,
|
||||
OpenRom(NES),
|
||||
// Tick(usize),
|
||||
// Frame,
|
||||
// DMA,
|
||||
// CPU,
|
||||
// DebugInt,
|
||||
WindowClosed(Id),
|
||||
WindowOpened(Id),
|
||||
Key(keyboard::Event),
|
||||
Periodic(Instant),
|
||||
SetRunning(bool),
|
||||
Header(HeaderButton),
|
||||
Hex(Id, HexEvent),
|
||||
Debugger(DebuggerMessage),
|
||||
SetSize(window::Id, Size),
|
||||
Export(MemoryTy),
|
||||
}
|
||||
|
||||
impl Emulator {
|
||||
@@ -109,24 +155,39 @@ impl Emulator {
|
||||
min_size: None,
|
||||
..Settings::default()
|
||||
});
|
||||
// let (win_2, task_2) = iced::window::open(Settings {
|
||||
// min_size: None,
|
||||
// ..Settings::default()
|
||||
// });
|
||||
(
|
||||
Self {
|
||||
nes,
|
||||
windows: HashMap::from_iter([(win, WindowType::Main)]),
|
||||
windows: HashMap::from_iter([
|
||||
(win, WindowType::Main),
|
||||
// (win_2, WindowType::Debugger),
|
||||
]),
|
||||
debugger: DebuggerState::new(),
|
||||
main_win_size: Size::new(0., 0.),
|
||||
running: false,
|
||||
prev: [Instant::now(); 2],
|
||||
audio: Audio::init(),
|
||||
},
|
||||
task.discard(),
|
||||
Task::batch([
|
||||
task,
|
||||
// task_2
|
||||
])
|
||||
.discard(), // task.discard(),
|
||||
)
|
||||
}
|
||||
fn title(&self, win: Id) -> String {
|
||||
match self.windows.get(&win) {
|
||||
Some(WindowType::Main) => "NES emu".into(),
|
||||
Some(WindowType::Memory(_, _)) => "NES MemoryView".into(),
|
||||
Some(WindowType::Memory(ty, _)) => format!("NES {ty:?} Memory"),
|
||||
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(),
|
||||
Some(WindowType::Apu) => "NES APU Debugger".into(),
|
||||
None => todo!(),
|
||||
}
|
||||
}
|
||||
@@ -139,23 +200,45 @@ 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();
|
||||
}
|
||||
}
|
||||
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::Tick(count) => {
|
||||
// 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::WindowClosed(id) => {
|
||||
if let Some(WindowType::Main) = self.windows.remove(&id) {
|
||||
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);
|
||||
}
|
||||
Message::Header(HeaderButton::OpenRom) => {
|
||||
return Task::future(
|
||||
rfd::AsyncFileDialog::new()
|
||||
.set_directory(".")
|
||||
.add_filter("NES", &["nes"])
|
||||
.set_title("Open NES Rom file")
|
||||
.pick_file(),
|
||||
)
|
||||
.and_then(|p| {
|
||||
Task::future(async move {
|
||||
// println!("Opening: {}", p.path().display());
|
||||
NES::async_load_nes_file(p.path()).await.ok()
|
||||
})
|
||||
})
|
||||
.and_then(|n| Task::done(Message::OpenRom(n)));
|
||||
}
|
||||
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));
|
||||
@@ -174,16 +257,138 @@ impl Emulator {
|
||||
if let Some(WindowType::Main) = self.windows.get(&id) {
|
||||
self.main_win_size = size;
|
||||
}
|
||||
println!("New size for {:?}, {:?}", id, size);
|
||||
// return iced::window::set_min_size(id, size.into())
|
||||
// .then(move |_: ()| iced::window::resize(id, size));
|
||||
return Task::future(async {
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
})
|
||||
.then(move |_| {
|
||||
iced::window::resize(id, size)
|
||||
});
|
||||
.then(move |_| iced::window::resize(id, size));
|
||||
}
|
||||
Message::OpenRom(nes) => {
|
||||
self.nes = nes;
|
||||
self.nes.power_cycle();
|
||||
}
|
||||
Message::Export(ty) => {
|
||||
let raw: Vec<_> = match ty {
|
||||
MemoryTy::Cpu => (0..=0xFFFF)
|
||||
.map(|i| self.nes.mem().peek_cpu(i).unwrap_or(0))
|
||||
.collect(),
|
||||
MemoryTy::PPU => (0..=0xFFFF)
|
||||
.map(|i| self.nes.mem().peek_ppu(i).unwrap_or(0))
|
||||
.collect(),
|
||||
MemoryTy::OAM => (0..=0xFF).map(|i| self.nes.ppu().peek_oam(i)).collect(),
|
||||
};
|
||||
return Task::future(async move {
|
||||
if let Some(file) = rfd::AsyncFileDialog::new()
|
||||
.set_directory(".")
|
||||
.set_file_name("output.dmp")
|
||||
.set_title("Save memory dump")
|
||||
.save_file()
|
||||
.await
|
||||
{
|
||||
tokio::fs::File::create(file.path())
|
||||
.await
|
||||
.expect("Failed to save file")
|
||||
.write_all(&raw)
|
||||
.await
|
||||
.expect("Failed to write dump");
|
||||
}
|
||||
})
|
||||
.discard();
|
||||
}
|
||||
Message::Key(key) => match key {
|
||||
keyboard::Event::KeyPressed {
|
||||
key: Key::Character(val),
|
||||
modifiers: Modifiers::CTRL,
|
||||
repeat: false,
|
||||
..
|
||||
} => {
|
||||
if val == "t" {
|
||||
self.nes.reset();
|
||||
}
|
||||
}
|
||||
keyboard::Event::KeyPressed {
|
||||
key,
|
||||
modifiers: Modifiers::NONE,
|
||||
repeat: false,
|
||||
..
|
||||
} => {
|
||||
if key == Key::Character("z".into()) {
|
||||
self.nes.controller_1().set_a(true);
|
||||
} else if key == Key::Character("x".into()) {
|
||||
self.nes.controller_1().set_b(true);
|
||||
} else if key == Key::Character("a".into()) {
|
||||
self.nes.controller_1().set_select(true);
|
||||
} else if key == Key::Character("s".into()) {
|
||||
self.nes.controller_1().set_start(true);
|
||||
} else if key == Key::Named(Named::ArrowDown) {
|
||||
self.nes.controller_1().set_down(true);
|
||||
} else if key == Key::Named(Named::ArrowUp) {
|
||||
self.nes.controller_1().set_up(true);
|
||||
} else if key == Key::Named(Named::ArrowLeft) {
|
||||
self.nes.controller_1().set_left(true);
|
||||
} else if key == Key::Named(Named::ArrowRight) {
|
||||
self.nes.controller_1().set_right(true);
|
||||
} else if key == Key::Named(Named::Play) || key == Key::Named(Named::MediaPlay)
|
||||
{
|
||||
self.running = true;
|
||||
} else if key == Key::Named(Named::Pause)
|
||||
|| key == Key::Named(Named::MediaPause)
|
||||
{
|
||||
self.running = false;
|
||||
} else if key == Key::Named(Named::MediaPlayPause)
|
||||
|| key == Key::Named(Named::Space)
|
||||
{
|
||||
self.running = !self.running;
|
||||
}
|
||||
}
|
||||
keyboard::Event::KeyReleased {
|
||||
key,
|
||||
modifiers: Modifiers::NONE,
|
||||
..
|
||||
} => {
|
||||
if key == Key::Character("z".into()) {
|
||||
self.nes.controller_1().set_a(false);
|
||||
} else if key == Key::Character("x".into()) {
|
||||
self.nes.controller_1().set_b(false);
|
||||
} else if key == Key::Character("a".into()) {
|
||||
self.nes.controller_1().set_select(false);
|
||||
} else if key == Key::Character("s".into()) {
|
||||
self.nes.controller_1().set_start(false);
|
||||
} else if key == Key::Named(Named::ArrowDown) {
|
||||
self.nes.controller_1().set_down(false);
|
||||
} else if key == Key::Named(Named::ArrowUp) {
|
||||
self.nes.controller_1().set_up(false);
|
||||
} else if key == Key::Named(Named::ArrowLeft) {
|
||||
self.nes.controller_1().set_left(false);
|
||||
} else if key == Key::Named(Named::ArrowRight) {
|
||||
self.nes.controller_1().set_right(false);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Message::Periodic(_i) => {
|
||||
if self.running {
|
||||
// TODO: Smarter frame skip
|
||||
if self.prev[0].elapsed() >= Duration::from_millis(2) {
|
||||
self.nes.run_one_clock_cycle(&Break::default());
|
||||
let mut count = 0;
|
||||
while !self.nes.run_one_clock_cycle(&Break::default()).ppu_frame
|
||||
&& !self.nes.halted()
|
||||
{
|
||||
count += 1;
|
||||
if count > 100000 {
|
||||
println!("Loop overran...");
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.prev[0] = Instant::now();
|
||||
self.audio.submit(self.nes.apu().get_frame_samples());
|
||||
self.nes.apu_mut().reset_frame_samples();
|
||||
}
|
||||
} else {
|
||||
self.audio.pause();
|
||||
}
|
||||
}
|
||||
Message::SetRunning(running) => self.running = running,
|
||||
}
|
||||
// self.image.0.clone_from(self.nes.image());
|
||||
Task::none()
|
||||
@@ -192,12 +397,14 @@ 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),
|
||||
keyboard::listen().map(Message::Key),
|
||||
time::every(Duration::from_secs_f64(1./60.)).map(Message::Periodic),
|
||||
])
|
||||
}
|
||||
|
||||
fn view(&self, win: Id) -> Element<'_, Message> {
|
||||
// println!("Running view");
|
||||
match self.windows.get(&win) {
|
||||
Some(WindowType::Main) => {
|
||||
let content = column![
|
||||
@@ -216,65 +423,58 @@ impl Emulator {
|
||||
Some(WindowType::Memory(ty, view)) => {
|
||||
let hex = match ty {
|
||||
MemoryTy::Cpu => view
|
||||
.render(self.nes.cpu_mem())
|
||||
.render_cpu(self.nes.mem())
|
||||
.map(move |e| Message::Hex(win, e)),
|
||||
MemoryTy::PPU => view
|
||||
.render_ppu(self.nes.mem(), self.nes.ppu())
|
||||
.map(move |e| Message::Hex(win, e)),
|
||||
MemoryTy::OAM => view
|
||||
.render_oam(self.nes.ppu())
|
||||
.map(move |e| Message::Hex(win, e)),
|
||||
};
|
||||
let content = column![hex].width(Fill).height(Fill);
|
||||
let content = column![row![header_menu("Export", [*ty], Message::Export)], 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()
|
||||
dbg_image(DbgImage::NameTable(self.nes.mem(), 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.mem(), self.nes.ppu())).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()
|
||||
Some(WindowType::Palette) => {
|
||||
dbg_image(DbgImage::Palette(self.nes.mem(), self.nes.ppu())).into()
|
||||
}
|
||||
Some(WindowType::Debugger) => column![
|
||||
row![
|
||||
button(image("./images/ic_fluent_play_24_filled.png"))
|
||||
.on_press(Message::SetRunning(true)),
|
||||
button(image("./images/ic_fluent_pause_24_filled.png"))
|
||||
.on_press(Message::SetRunning(false)),
|
||||
],
|
||||
self.debugger.view(&self.nes).map(Message::Debugger)
|
||||
]
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into(),
|
||||
Some(WindowType::Apu) => {
|
||||
self.nes.apu().view()
|
||||
}
|
||||
None => panic!("Window not found"),
|
||||
// _ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
// fn cpu_state(&self) -> Element<'_, Message> {
|
||||
// row![column![
|
||||
// // text!("Registers").font(Font::MONOSPACE),
|
||||
// text!("{:?}", self.nes).font(Font::MONOSPACE),
|
||||
// ],]
|
||||
// .width(Fill)
|
||||
// .into()
|
||||
// }
|
||||
|
||||
// fn controls(&self) -> Element<'_, Message> {
|
||||
// row![
|
||||
// button("Clock tick").on_press(Message::Tick(1)),
|
||||
// button("CPU tick").on_press(Message::CPU),
|
||||
// button("Next Frame").on_press(Message::Frame),
|
||||
// button("Next DMA").on_press(Message::DMA),
|
||||
// button("Next DBG").on_press(Message::DebugInt),
|
||||
// ]
|
||||
// .width(Fill)
|
||||
// .into()
|
||||
// }
|
||||
|
||||
fn dropdowns(&self) -> Element<'_, Message> {
|
||||
row![
|
||||
header_menu(
|
||||
"Console",
|
||||
[HeaderButton::Reset, HeaderButton::PowerCycle,],
|
||||
[
|
||||
HeaderButton::OpenRom,
|
||||
HeaderButton::Reset,
|
||||
HeaderButton::PowerCycle,
|
||||
],
|
||||
Message::Header
|
||||
),
|
||||
header_menu(
|
||||
@@ -282,9 +482,12 @@ impl Emulator {
|
||||
[
|
||||
HeaderButton::Open(WindowType::Debugger),
|
||||
HeaderButton::Open(WindowType::Memory(MemoryTy::Cpu, HexView {})),
|
||||
HeaderButton::Open(WindowType::Memory(MemoryTy::PPU, HexView {})),
|
||||
HeaderButton::Open(WindowType::Memory(MemoryTy::OAM, HexView {})),
|
||||
HeaderButton::Open(WindowType::TileMap),
|
||||
HeaderButton::Open(WindowType::TileViewer),
|
||||
HeaderButton::Open(WindowType::Palette),
|
||||
HeaderButton::Open(WindowType::Apu),
|
||||
],
|
||||
Message::Header
|
||||
)
|
||||
@@ -305,55 +508,18 @@ impl Program<Message> for Emulator {
|
||||
bounds: iced::Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<iced::widget::canvas::Geometry<Renderer>> {
|
||||
// const SIZE: f32 = 2.;
|
||||
let mut frame = Frame::new(
|
||||
renderer,
|
||||
bounds.size(), // iced::Size {
|
||||
// width: 256. * 2.,
|
||||
// height: 240. * 2.,
|
||||
// },
|
||||
);
|
||||
let mut frame = Frame::new(renderer, bounds.size());
|
||||
frame.scale(2.);
|
||||
for y in 0..240 {
|
||||
for x in 0..256 {
|
||||
let c = self.nes.image().read(y, x);
|
||||
frame.fill_rectangle(
|
||||
Point::new(x as f32, y as f32),
|
||||
Size::new(1., 1.),
|
||||
Color::from_rgb8(c.r, c.g, c.b),
|
||||
);
|
||||
}
|
||||
}
|
||||
frame.draw_image(
|
||||
Rectangle::new(Point::new(0., 0.), Size::new(256., 240.)),
|
||||
widget::canvas::Image::new(widget::image::Handle::from_rgba(
|
||||
256,
|
||||
240,
|
||||
self.nes.image().image(),
|
||||
))
|
||||
.filter_method(image::FilterMethod::Nearest)
|
||||
.snap(true),
|
||||
);
|
||||
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()]
|
||||
}
|
||||
}
|
||||
|
||||
651
src/mem.rs
651
src/mem.rs
@@ -1,12 +1,18 @@
|
||||
use crate::hex_view::Memory;
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
pub enum Value<'a, R> {
|
||||
use crate::{
|
||||
CPUMMRegisters, PPU, apu::APU, controllers::Controllers, cpu::DmaState, hex_view::Memory,
|
||||
ppu::PPUMMRegisters,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Value<R> {
|
||||
Value(u8),
|
||||
Register { reg: &'a R, offset: u16 },
|
||||
Register { reg: R, offset: u16 },
|
||||
}
|
||||
|
||||
impl<R> Value<'_, R> {
|
||||
pub fn reg_map(self, f: impl FnOnce(&R, u16) -> u8) -> u8 {
|
||||
impl<R> Value<R> {
|
||||
pub fn reg_map(self, f: impl FnOnce(R, u16) -> u8) -> u8 {
|
||||
match self {
|
||||
Value::Value(v) => v,
|
||||
Value::Register { reg, offset } => f(reg, offset),
|
||||
@@ -14,23 +20,29 @@ impl<R> Value<'_, R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Data<R> {
|
||||
RAM(Vec<u8>),
|
||||
ROM(Vec<u8>),
|
||||
Mirror(usize),
|
||||
ROM(Arc<[u8]>),
|
||||
Mirror(u16),
|
||||
Reg(R),
|
||||
// Disabled(),
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Segment<R> {
|
||||
name: &'static str,
|
||||
name: SegmentId,
|
||||
position: u16,
|
||||
size: u16,
|
||||
mem: Data<R>,
|
||||
}
|
||||
|
||||
impl<R> Segment<R> {
|
||||
pub fn ram(name: &'static str, position: u16, size: u16) -> Self {
|
||||
fn is(&self, id: SegmentId) -> bool {
|
||||
self.name == id
|
||||
}
|
||||
|
||||
fn ram(name: SegmentId, position: u16, size: u16) -> Self {
|
||||
Self {
|
||||
name,
|
||||
position,
|
||||
@@ -38,15 +50,19 @@ impl<R> Segment<R> {
|
||||
mem: Data::RAM(vec![0u8; size as usize]),
|
||||
}
|
||||
}
|
||||
pub fn rom(name: &'static str, position: u16, raw: &[u8]) -> Self {
|
||||
fn rom<Raw>(name: SegmentId, position: u16, raw: Raw) -> Self
|
||||
where
|
||||
Arc<[u8]>: From<Raw>,
|
||||
{
|
||||
let raw = Arc::from(raw);
|
||||
Self {
|
||||
name,
|
||||
position,
|
||||
size: raw.len() as u16,
|
||||
mem: Data::ROM(Vec::from(raw)),
|
||||
mem: Data::ROM(raw),
|
||||
}
|
||||
}
|
||||
pub fn reg(name: &'static str, position: u16, size: u16, reg: R) -> Self {
|
||||
fn reg(name: SegmentId, position: u16, size: u16, reg: R) -> Self {
|
||||
Self {
|
||||
name,
|
||||
position,
|
||||
@@ -54,7 +70,7 @@ impl<R> Segment<R> {
|
||||
mem: Data::Reg(reg),
|
||||
}
|
||||
}
|
||||
pub fn mirror(name: &'static str, position: u16, size: u16, of: usize) -> Self {
|
||||
fn mirror(name: SegmentId, position: u16, size: u16, of: u16) -> Self {
|
||||
Self {
|
||||
name,
|
||||
position,
|
||||
@@ -62,18 +78,46 @@ impl<R> Segment<R> {
|
||||
mem: Data::Mirror(of),
|
||||
}
|
||||
}
|
||||
|
||||
// fn take_rom(&mut self) -> Vec<u8> {
|
||||
// match std::mem::replace(&mut self.mem, Data::Disabled) {
|
||||
// Data::ROM(items) => items,
|
||||
// _ => panic!("Cannot take rom since memory is not rom"),
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn set_rom(&mut self, rom: Vec<u8>) {
|
||||
// assert!(
|
||||
// matches!(self.mem, Data::Disabled),
|
||||
// "Cannot set non-disabled memory to rom"
|
||||
// );
|
||||
// self.mem = Data::ROM(rom);
|
||||
// }
|
||||
|
||||
fn swap_rom(&mut self, rom: Arc<[u8]>) {
|
||||
match &mut self.mem {
|
||||
Data::ROM(items) => *items = rom,
|
||||
_ => panic!("Cannot swap rom since memory is not rom"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn power_cycle(&mut self) {
|
||||
match &mut self.mem {
|
||||
Data::RAM(v) => v.fill(0),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemoryMap<R> {
|
||||
edit_ver: usize,
|
||||
segments: Vec<Segment<R>>,
|
||||
// map: Remapper,
|
||||
}
|
||||
|
||||
impl<R> MemoryMap<R> {
|
||||
pub fn new(segments: Vec<Segment<R>>) -> Self {
|
||||
Self { edit_ver: 0, segments }
|
||||
}
|
||||
pub fn read(&self, addr: u16) -> Value<'_, R> {
|
||||
impl<R: Copy> MemoryMap<R> {
|
||||
pub fn read(&self, addr: u16) -> Value<R> {
|
||||
// self.edit_ver += 1;
|
||||
for segment in &self.segments {
|
||||
if segment.position <= addr && addr - segment.position < segment.size {
|
||||
@@ -81,42 +125,50 @@ impl<R> MemoryMap<R> {
|
||||
Data::RAM(items) => Value::Value(items[(addr - segment.position) as usize]),
|
||||
Data::ROM(items) => Value::Value(items[(addr - segment.position) as usize]),
|
||||
Data::Reg(reg) => Value::Register {
|
||||
reg,
|
||||
reg: *reg,
|
||||
offset: addr - segment.position,
|
||||
},
|
||||
Data::Mirror(index) => {
|
||||
Data::Mirror(pos) => {
|
||||
let offset = addr - segment.position;
|
||||
let s = &self.segments[*index];
|
||||
self.read(s.position + offset % s.size)
|
||||
self.read(pos + offset)
|
||||
// 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)) {
|
||||
pub fn write(&mut self, addr: u16, val: u8) -> Option<(R, u16, u8)> {
|
||||
self.edit_ver += 1;
|
||||
for segment in &mut self.segments {
|
||||
if segment.position <= addr && addr - segment.position < segment.size {
|
||||
return match &mut segment.mem {
|
||||
Data::RAM(items) => {
|
||||
items[(addr - segment.position) as usize] = val;
|
||||
None
|
||||
}
|
||||
Data::ROM(_items) => (),
|
||||
Data::Reg(reg) => reg_fn(reg, addr - segment.position, val),
|
||||
Data::Mirror(index) => {
|
||||
Data::ROM(_items) => None,
|
||||
Data::Reg(reg) => Some((*reg, addr - segment.position, val)),
|
||||
Data::Mirror(pos) => {
|
||||
let pos = *pos;
|
||||
let offset = addr - segment.position;
|
||||
let index = *index;
|
||||
let s = &self.segments[index];
|
||||
self.write(s.position + offset % s.size, val, reg_fn)
|
||||
self.write(pos + offset, val)
|
||||
// let index = *index;
|
||||
// let s = &self.segments[index];
|
||||
// self.write(s.position + offset % s.size, val)
|
||||
}
|
||||
// Data::Disabled() => todo!(),
|
||||
Data::Disabled => todo!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
todo!("Open bus")
|
||||
// iirc, open bus just drops writes
|
||||
None
|
||||
// todo!("Open bus (${addr:04X})")
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
@@ -128,36 +180,525 @@ 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) {
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
pub fn power_cycle(&mut self) -> Self {
|
||||
for seg in &mut self.segments {
|
||||
seg.power_cycle();
|
||||
}
|
||||
let segments = std::mem::take(&mut self.segments);
|
||||
Self {
|
||||
edit_ver: self.edit_ver + 1,
|
||||
segments,
|
||||
// map: self.map.power_cycle(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Memory for MemoryMap<R> {
|
||||
fn peek(&self, addr: u16) -> Option<u8> {
|
||||
for segment in &self.segments {
|
||||
if segment.position <= addr && addr - segment.position < segment.size {
|
||||
return match &segment.mem {
|
||||
Data::RAM(items) => Some(items[(addr - segment.position) as usize]),
|
||||
Data::ROM(items) => Some(items[(addr - segment.position) as usize]),
|
||||
Data::Reg(_) => None,
|
||||
Data::Mirror(index) => {
|
||||
let offset = addr - segment.position;
|
||||
let s = &self.segments[*index];
|
||||
self.peek(s.position + offset % s.size)
|
||||
}
|
||||
// Data::Disabled() => todo!(),
|
||||
};
|
||||
fn find(&mut self, id: SegmentId) -> Option<&mut Segment<R>> {
|
||||
for s in &mut self.segments {
|
||||
if s.is(id) {
|
||||
return Some(s);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn peek_val(&self, addr: u16) -> Result<u8, Option<(R, u16)>> {
|
||||
for segment in &self.segments {
|
||||
if segment.position <= addr && addr - segment.position < segment.size {
|
||||
return match &segment.mem {
|
||||
Data::RAM(items) => Ok(items[(addr - segment.position) as usize]),
|
||||
Data::ROM(items) => Ok(items[(addr - segment.position) as usize]),
|
||||
Data::Reg(r) => Err(Some((*r, addr - segment.position))),
|
||||
Data::Mirror(pos) => {
|
||||
let offset = addr - segment.position;
|
||||
self.peek_val(pos + offset)
|
||||
}
|
||||
Data::Disabled => Err(None),
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Copy> Memory for MemoryMap<R> {
|
||||
fn peek(&self, addr: usize) -> Option<u8> {
|
||||
self.peek_val(addr as u16).ok()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.segments
|
||||
.iter()
|
||||
.map(|s| s.position as usize + s.size as usize)
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn edit_ver(&self) -> usize {
|
||||
self.edit_ver
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
struct Mapper {
|
||||
horizontal_name_table: bool,
|
||||
mapper: u16,
|
||||
sub_mapper: u8,
|
||||
prg_rom_size: u8,
|
||||
chr_rom_size: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum SegmentId {
|
||||
#[allow(unused)]
|
||||
TestRam,
|
||||
|
||||
InternalRam,
|
||||
InternalRamMirror,
|
||||
PpuRegisters,
|
||||
ApuIoRegisters,
|
||||
PrgRom,
|
||||
|
||||
Nametable0,
|
||||
Nametable1,
|
||||
Nametable2,
|
||||
Nametable3,
|
||||
VramMirror,
|
||||
PaletteControl,
|
||||
PaletteMirror,
|
||||
PpuRom,
|
||||
PpuRam,
|
||||
|
||||
// CpuBank(u32),
|
||||
PrgBank0,
|
||||
PrgBank1,
|
||||
ChrBank0,
|
||||
ChrBank1,
|
||||
}
|
||||
|
||||
impl fmt::Display for SegmentId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Mapped {
|
||||
cpu: MemoryMap<CPUMMRegisters>,
|
||||
ppu: MemoryMap<PPUMMRegisters>,
|
||||
mapper: Remapper,
|
||||
}
|
||||
|
||||
impl Mapped {
|
||||
pub fn from_rom(rom: &[u8]) -> Self {
|
||||
let (header, rom) = rom.split_at(0x10);
|
||||
let nes_20 = header[7] & 0x0C == 0x08;
|
||||
// assert!(nes_20, "Only supports nes 2.0 format");
|
||||
// if header[6] & 0b11111110 != 0 {
|
||||
// todo!("Support other mapper flags, {:08b}", header[6]);
|
||||
// }
|
||||
let mapper = if nes_20 {
|
||||
assert_eq!(header[9], 0, "No support for larger PRG/CHR roms");
|
||||
assert_eq!(header[10], 0, "No support for PRG-RAM/EEPROM");
|
||||
assert_eq!(header[11], 0, "No support for CHR-RAM");
|
||||
assert!(header[12] == 0 || header[12] == 2, "Only support NTSC NES");
|
||||
Mapper {
|
||||
horizontal_name_table: header[6] & (1 << 0) == 1,
|
||||
mapper: ((header[6] as u16 & 0xF0) >> 4)
|
||||
| ((header[7] as u16 & 0xF0) >> 0)
|
||||
| ((header[8] as u16 & 0x0F) << 8),
|
||||
sub_mapper: (header[8] & 0xF0) >> 4,
|
||||
prg_rom_size: header[4],
|
||||
chr_rom_size: header[5],
|
||||
}
|
||||
} else {
|
||||
Mapper {
|
||||
horizontal_name_table: header[6] & (1 << 0) == 1,
|
||||
mapper: ((header[6] as u16 & 0xF0) >> 4) | ((header[7] as u16 & 0xF0) >> 0),
|
||||
sub_mapper: 0,
|
||||
prg_rom_size: header[4],
|
||||
chr_rom_size: header[5],
|
||||
}
|
||||
};
|
||||
let (prg_rom, rom) = rom.split_at(mapper.prg_rom_size as usize * 0x4000);
|
||||
let (chr_rom, rom) = rom.split_at(mapper.chr_rom_size as usize * 0x2000);
|
||||
// assert_eq!(rom.len(), 0);
|
||||
// let prg_rom = &file[self.cpu_offset()..self.ppu_offset()];
|
||||
let mut cpu_segments = vec![
|
||||
Segment::ram(SegmentId::InternalRam, 0x0000, 0x0800),
|
||||
Segment::mirror(SegmentId::InternalRamMirror, 0x0800, 0x1800, 0x0000),
|
||||
Segment::reg(SegmentId::PpuRegisters, 0x2000, 0x0008, CPUMMRegisters::PPU),
|
||||
Segment::reg(
|
||||
SegmentId::ApuIoRegisters,
|
||||
0x4000,
|
||||
0x0018,
|
||||
CPUMMRegisters::APU,
|
||||
),
|
||||
];
|
||||
let mut ppu_segments = vec![
|
||||
Segment::mirror(SegmentId::VramMirror, 0x3000, 0x0F00, 0x0000),
|
||||
Segment::reg(
|
||||
SegmentId::PaletteControl,
|
||||
0x3F00,
|
||||
0x0020,
|
||||
PPUMMRegisters::Palette,
|
||||
),
|
||||
Segment::mirror(SegmentId::PaletteMirror, 0x3F20, 0x00E0, 0x3F00),
|
||||
];
|
||||
if mapper.horizontal_name_table {
|
||||
ppu_segments.push(Segment::ram(SegmentId::Nametable0, 0x2000, 0x400));
|
||||
ppu_segments.push(Segment::ram(SegmentId::Nametable1, 0x2400, 0x400));
|
||||
ppu_segments.push(Segment::mirror(
|
||||
SegmentId::Nametable2,
|
||||
0x2800,
|
||||
0x400,
|
||||
0x2000,
|
||||
));
|
||||
ppu_segments.push(Segment::mirror(
|
||||
SegmentId::Nametable3,
|
||||
0x2C00,
|
||||
0x400,
|
||||
0x2400,
|
||||
));
|
||||
} else {
|
||||
ppu_segments.push(Segment::ram(SegmentId::Nametable0, 0x2000, 0x400));
|
||||
ppu_segments.push(Segment::mirror(
|
||||
SegmentId::Nametable1,
|
||||
0x2400,
|
||||
0x400,
|
||||
0x2000,
|
||||
));
|
||||
ppu_segments.push(Segment::ram(SegmentId::Nametable2, 0x2800, 0x400));
|
||||
ppu_segments.push(Segment::mirror(
|
||||
SegmentId::Nametable3,
|
||||
0x2C00,
|
||||
0x400,
|
||||
0x2800,
|
||||
));
|
||||
}
|
||||
let remap = if mapper.mapper == 0 {
|
||||
cpu_segments.push(Segment::rom(
|
||||
SegmentId::PrgRom,
|
||||
0x8000 + (0x8000 - prg_rom.len() as u16),
|
||||
prg_rom,
|
||||
));
|
||||
if chr_rom.len() == 0 {
|
||||
ppu_segments.push(Segment::ram(SegmentId::PpuRam, 0, 0x2000));
|
||||
} else {
|
||||
ppu_segments.push(Segment::rom(SegmentId::PpuRom, 0, chr_rom));
|
||||
}
|
||||
Remapper::None
|
||||
} else if mapper.mapper == 1 && mapper.sub_mapper == 0 {
|
||||
let prg_banks: Vec<Arc<[u8]>> =
|
||||
prg_rom.chunks(0x4000).map(|ch| Arc::from(ch)).collect();
|
||||
for (i, b) in prg_banks.iter().enumerate() {
|
||||
println!("{i}: {:X?}", &b[0x3FF0..]);
|
||||
}
|
||||
cpu_segments.push(Segment::rom(
|
||||
SegmentId::PrgBank0,
|
||||
0x8000,
|
||||
prg_banks.first().unwrap().clone(),
|
||||
));
|
||||
cpu_segments.push(Segment::rom(
|
||||
SegmentId::PrgBank1,
|
||||
0xC000,
|
||||
prg_banks.last().unwrap().clone(),
|
||||
));
|
||||
println!("CHR_ROM: {}", chr_rom.len());
|
||||
let chr_banks: Vec<Arc<[u8]>> =
|
||||
chr_rom.chunks(0x1000).map(|ch| Arc::from(ch)).collect();
|
||||
if chr_rom.len() == 0 {
|
||||
ppu_segments.push(Segment::ram(SegmentId::PpuRam, 0, 0x2000));
|
||||
} else {
|
||||
ppu_segments.push(Segment::rom(
|
||||
SegmentId::ChrBank0,
|
||||
0x0000,
|
||||
chr_banks[0].clone(),
|
||||
));
|
||||
ppu_segments.push(Segment::rom(
|
||||
SegmentId::ChrBank1,
|
||||
0x1000,
|
||||
chr_banks[1].clone(),
|
||||
));
|
||||
}
|
||||
Remapper::MMC1 {
|
||||
shift_reg: 0,
|
||||
count: 0,
|
||||
prg_banks,
|
||||
chr_banks,
|
||||
mode: MMC1Mode::LastFixed,
|
||||
cur_prg_bank: 0,
|
||||
}
|
||||
} else {
|
||||
todo!()
|
||||
};
|
||||
Self {
|
||||
cpu: MemoryMap {
|
||||
edit_ver: 0,
|
||||
segments: cpu_segments,
|
||||
},
|
||||
ppu: MemoryMap {
|
||||
edit_ver: 0,
|
||||
segments: ppu_segments,
|
||||
},
|
||||
mapper: remap,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn power_cylce(&mut self) -> Self {
|
||||
// TODO: mapper needs to reset cpu and ppu mem maps
|
||||
let mut cpu = self.cpu.power_cycle();
|
||||
let mut ppu = self.ppu.power_cycle();
|
||||
Self {
|
||||
mapper: self.mapper.power_cycle(&mut cpu, &mut ppu),
|
||||
cpu,
|
||||
ppu,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek_ppu(&self, addr: u16) -> Result<u8, Option<(PPUMMRegisters, u16)>> {
|
||||
self.ppu.peek_val(addr)
|
||||
}
|
||||
pub fn ppu_edit_ver(&self) -> usize {
|
||||
self.ppu.edit_ver
|
||||
}
|
||||
pub fn peek_cpu(&self, addr: u16) -> Option<u8> {
|
||||
self.cpu.peek_val(addr).ok()
|
||||
}
|
||||
pub fn cpu_edit_ver(&self) -> usize {
|
||||
self.cpu.edit_ver
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_ram() -> Self {
|
||||
Self {
|
||||
cpu: MemoryMap {
|
||||
edit_ver: 0,
|
||||
segments: vec![Segment::ram(SegmentId::TestRam, 0, 0x8000)],
|
||||
},
|
||||
ppu: MemoryMap {
|
||||
edit_ver: 0,
|
||||
segments: vec![Segment::ram(SegmentId::TestRam, 0, 0x8000)],
|
||||
},
|
||||
mapper: Remapper::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CpuMem<'a> {
|
||||
mem: &'a mut Mapped,
|
||||
ppu: &'a mut PPU,
|
||||
apu: &'a mut APU,
|
||||
dma: &'a mut DmaState,
|
||||
controllers: &'a mut Controllers,
|
||||
}
|
||||
|
||||
impl<'a> CpuMem<'a> {
|
||||
pub fn new(
|
||||
mem: &'a mut Mapped,
|
||||
ppu: &'a mut PPU,
|
||||
apu: &'a mut APU,
|
||||
dma: &'a mut DmaState,
|
||||
controllers: &'a mut Controllers,
|
||||
) -> Self {
|
||||
Self {
|
||||
mem,
|
||||
ppu,
|
||||
apu,
|
||||
dma,
|
||||
controllers,
|
||||
}
|
||||
}
|
||||
pub fn read(&mut self, addr: u16) -> u8 {
|
||||
match self.mem.mapper {
|
||||
_ => (),
|
||||
}
|
||||
match self.mem.cpu.read(addr) {
|
||||
Value::Value(v) => v,
|
||||
Value::Register { reg, offset } => match reg {
|
||||
CPUMMRegisters::PPU => self.ppu.read_reg(&mut PpuMem::new(self.mem), offset),
|
||||
CPUMMRegisters::APU => {
|
||||
if offset == 0x014 {
|
||||
todo!("OAM DMA")
|
||||
} else if offset == 0x16 {
|
||||
self.controllers.read_joy1()
|
||||
} else if offset == 0x17 {
|
||||
self.controllers.read_joy2()
|
||||
} else {
|
||||
self.apu.read_reg(offset)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn write(&mut self, addr: u16, val: u8) {
|
||||
match &mut self.mem.mapper {
|
||||
Remapper::MMC1 {
|
||||
shift_reg,
|
||||
count,
|
||||
mode,
|
||||
prg_banks,
|
||||
chr_banks,
|
||||
cur_prg_bank,
|
||||
} if addr & 0x8000 != 0 => {
|
||||
if val & 0x80 != 0 {
|
||||
*shift_reg = 0;
|
||||
*count = 0;
|
||||
} else if *count == 4 {
|
||||
let val = (*shift_reg << 1) | (val & 0x01);
|
||||
if (addr & 0x6000) >> 13 == 0 {
|
||||
// TODO: fix mem layout if it's changed
|
||||
if val & 0b01100 == 0b01000 {
|
||||
*mode = MMC1Mode::FirstFixed;
|
||||
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||
bank0.swap_rom(prg_banks[0].clone());
|
||||
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
||||
bank1.swap_rom(prg_banks[*cur_prg_bank].clone());
|
||||
} else if val & 0b01100 == 0b01100 {
|
||||
*mode = MMC1Mode::LastFixed;
|
||||
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||
bank0.swap_rom(prg_banks[*cur_prg_bank].clone());
|
||||
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
||||
bank1.swap_rom(prg_banks.last().unwrap().clone());
|
||||
} else {
|
||||
*mode = MMC1Mode::Full;
|
||||
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||
bank0.swap_rom(prg_banks[*cur_prg_bank & !1].clone());
|
||||
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
||||
bank1.swap_rom(prg_banks[(*cur_prg_bank & !1) + 1].clone());
|
||||
}
|
||||
if val & 0b10000 == 0b10000 {
|
||||
// TODO: CHR-ROM mode
|
||||
}
|
||||
// TODO: Set name table mirroring
|
||||
if val & 0b00011 == 0b00000 {
|
||||
} else if val & 0b00011 == 0b00001 {
|
||||
} else if val & 0b00011 == 0b00010 {
|
||||
} else {
|
||||
}
|
||||
} else if (addr & 0x6000) >> 13 == 1 {
|
||||
if chr_banks.len() != 0 {
|
||||
todo!("Swap CHR bank 0")
|
||||
}
|
||||
} else if (addr & 0x6000) >> 13 == 2 {
|
||||
if chr_banks.len() != 0 {
|
||||
todo!("Swap CHR bank 1")
|
||||
}
|
||||
} else if (addr & 0x6000) >> 13 == 3 {
|
||||
*cur_prg_bank = (val & 0x0F) as usize;
|
||||
match mode {
|
||||
MMC1Mode::Full => {
|
||||
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||
bank0.swap_rom(prg_banks[*cur_prg_bank & !1].clone());
|
||||
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
||||
bank1.swap_rom(prg_banks[(*cur_prg_bank & !1) + 1].clone());
|
||||
}
|
||||
MMC1Mode::FirstFixed => {
|
||||
let bank1 = self.mem.cpu.find(SegmentId::PrgBank1).unwrap();
|
||||
bank1.swap_rom(prg_banks[(val & 0x0F) as usize].clone());
|
||||
}
|
||||
MMC1Mode::LastFixed => {
|
||||
let bank0 = self.mem.cpu.find(SegmentId::PrgBank0).unwrap();
|
||||
bank0.swap_rom(prg_banks[(val & 0x0F) as usize].clone());
|
||||
}
|
||||
}
|
||||
// TODO: handle MSB, changes some stuff...
|
||||
} else {
|
||||
todo!("Handle reg write {} => {}", val, (addr & 0x6000) >> 13,)
|
||||
}
|
||||
*shift_reg = 0;
|
||||
*count = 0;
|
||||
} else {
|
||||
*shift_reg = (*shift_reg << 1) | (val & 0x01);
|
||||
*count += 1;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
match self.mem.cpu.write(addr, val) {
|
||||
Some((CPUMMRegisters::PPU, offset, val)) => {
|
||||
self.ppu.write_reg(&mut PpuMem::new(self.mem), offset, val)
|
||||
}
|
||||
Some((CPUMMRegisters::APU, offset, val)) => {
|
||||
if offset == 0x014 {
|
||||
*self.dma = DmaState::Idle((val as u16) << 8);
|
||||
} else if offset == 0x16 {
|
||||
self.controllers.write_joy_strobe(val);
|
||||
} else {
|
||||
self.apu.write_reg(offset, val);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PpuMem<'a>(&'a mut Mapped);
|
||||
|
||||
impl<'a> PpuMem<'a> {
|
||||
pub fn new(mem: &'a mut Mapped) -> Self {
|
||||
Self(mem)
|
||||
}
|
||||
pub fn read(&mut self, addr: u16) -> Value<PPUMMRegisters> {
|
||||
self.0.ppu.read(addr)
|
||||
}
|
||||
pub fn write(&mut self, addr: u16, val: u8, reg_fn: impl FnOnce(&PPUMMRegisters, u16, u8)) {
|
||||
match self.0.ppu.write(addr, val) {
|
||||
Some((r, o, v)) => reg_fn(&r, o, v),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum MMC1Mode {
|
||||
Full,
|
||||
FirstFixed,
|
||||
LastFixed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Remapper {
|
||||
None,
|
||||
MMC1 {
|
||||
shift_reg: u8,
|
||||
count: u8,
|
||||
prg_banks: Vec<Arc<[u8]>>,
|
||||
chr_banks: Vec<Arc<[u8]>>,
|
||||
mode: MMC1Mode,
|
||||
cur_prg_bank: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl Remapper {
|
||||
fn power_cycle(
|
||||
&mut self,
|
||||
cpu: &mut MemoryMap<CPUMMRegisters>,
|
||||
_ppu: &mut MemoryMap<PPUMMRegisters>,
|
||||
) -> Self {
|
||||
match self {
|
||||
Remapper::None => Remapper::None,
|
||||
Remapper::MMC1 {
|
||||
prg_banks,
|
||||
chr_banks,
|
||||
..
|
||||
} => {
|
||||
let prg_banks = std::mem::take(prg_banks);
|
||||
let chr_banks = std::mem::take(chr_banks);
|
||||
cpu.find(SegmentId::PrgBank0)
|
||||
.unwrap()
|
||||
.swap_rom(prg_banks[0].clone());
|
||||
cpu.find(SegmentId::PrgBank1)
|
||||
.unwrap()
|
||||
.swap_rom(prg_banks.last().unwrap().clone());
|
||||
Remapper::MMC1 {
|
||||
shift_reg: 0,
|
||||
count: 0,
|
||||
prg_banks,
|
||||
chr_banks,
|
||||
mode: MMC1Mode::LastFixed,
|
||||
cur_prg_bank: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1259
src/ppu.rs
1259
src/ppu.rs
File diff suppressed because it is too large
Load Diff
36
src/test_roms/alu_bit.s
Normal file
36
src/test_roms/alu_bit.s
Normal file
@@ -0,0 +1,36 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
.include "testing.s"
|
||||
|
||||
zp_res VAL
|
||||
RAM = $300
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
lda #$F0
|
||||
sta VAL
|
||||
lda #$0F
|
||||
bit VAL
|
||||
php
|
||||
pla
|
||||
sta RAM+0
|
||||
|
||||
lda #$F0
|
||||
sta $400
|
||||
lda #$0F
|
||||
bit $400
|
||||
php
|
||||
pla
|
||||
sta RAM+1
|
||||
|
||||
stp
|
||||
|
||||
nmi:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
97
src/test_roms/apu_pulse_1.s
Normal file
97
src/test_roms/apu_pulse_1.s
Normal file
@@ -0,0 +1,97 @@
|
||||
.include "testing.s"
|
||||
.include "audio_inp.s"
|
||||
|
||||
zp_res TIMER_LOW
|
||||
; zp_res TIMER_LOW
|
||||
; zp_res TIMER_LOW
|
||||
; zp_res TIMER_LOW
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
lda #$01
|
||||
sta SNDCHN
|
||||
lda #$AF ; Duty 2, LC halted, evelope enabled, volume = F
|
||||
sta PULSE_CH1_DLCV
|
||||
lda #$7F ;
|
||||
sta PULSE_CH1_SWEEP
|
||||
lda #$6F
|
||||
sta PULSE_CH1_TLOW
|
||||
sta TIMER_LOW
|
||||
lda #$F0
|
||||
sta PULSE_CH1_LCTH
|
||||
; PULSE_CH1_DLCV = $4000
|
||||
; PULSE_CH1_SWEEP = $4001
|
||||
; PULSE_CH1_TLOW = $4002
|
||||
; PULSE_CH1_LCTH = $4003
|
||||
|
||||
jsr init_view
|
||||
|
||||
load_ppu_addr $2041
|
||||
write_char_x 't'
|
||||
write_char_x 'i'
|
||||
write_char_x 'm'
|
||||
write_char_x 'e'
|
||||
write_char_x 'r'
|
||||
write_char_x ' '
|
||||
write_char_x 'l'
|
||||
write_char_x 'o'
|
||||
write_char_x 'w'
|
||||
write_char_x ':'
|
||||
|
||||
load_ppu_addr $2061
|
||||
; write_char_x 't'
|
||||
; write_char_x 'i'
|
||||
; write_char_x 'm'
|
||||
; write_char_x 'e'
|
||||
; write_char_x 'r'
|
||||
; write_char_x ' '
|
||||
; write_char_x 'h'
|
||||
; write_char_x 'o'
|
||||
; write_char_x 'w'
|
||||
; write_char_x ':'
|
||||
|
||||
load_ppu_addr $204C
|
||||
lda TIMER_LOW
|
||||
jsr write_hex_a
|
||||
|
||||
jsr enable_rendering
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
; Joystick is in A and JOYTEMP
|
||||
zp_res JOY_PREV
|
||||
audio_nmi:
|
||||
EOR JOY_PREV
|
||||
STA JOY_PREV
|
||||
lda JOYTEMP
|
||||
and #JOY_UP_MASK
|
||||
and JOY_PREV
|
||||
beq next_1
|
||||
load_ppu_addr $204C
|
||||
lda #$01
|
||||
adc TIMER_LOW
|
||||
sta TIMER_LOW
|
||||
sta PULSE_CH1_TLOW
|
||||
jsr write_hex_a
|
||||
next_1:
|
||||
lda JOYTEMP
|
||||
and #JOY_DOWN_MASK
|
||||
and JOY_PREV
|
||||
beq next_2
|
||||
load_ppu_addr $204C
|
||||
lda #$FF
|
||||
adc TIMER_LOW
|
||||
sta TIMER_LOW
|
||||
sta PULSE_CH1_TLOW
|
||||
jsr write_hex_a
|
||||
next_2:
|
||||
lda JOYTEMP
|
||||
STA JOY_PREV
|
||||
rts
|
||||
|
||||
irq:
|
||||
rti
|
||||
104
src/test_roms/apu_pulse_channel_1.s
Normal file
104
src/test_roms/apu_pulse_channel_1.s
Normal file
@@ -0,0 +1,104 @@
|
||||
.include "testing.s"
|
||||
.include "minimal_ppu.s"
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
lda #$01
|
||||
sta SNDCHN
|
||||
lda #$BF ; Duty 2, LC halted, Constant volume, volume = F
|
||||
sta PULSE_CH1_DLCV
|
||||
lda #$7F ;
|
||||
sta PULSE_CH1_SWEEP
|
||||
lda #$6F
|
||||
sta PULSE_CH1_TLOW
|
||||
sta TIMER_LOW
|
||||
lda #$00
|
||||
sta PULSE_CH1_LCTH
|
||||
; PULSE_CH1_DLCV = $4000
|
||||
; PULSE_CH1_SWEEP = $4001
|
||||
; PULSE_CH1_TLOW = $4002
|
||||
; PULSE_CH1_LCTH = $4003
|
||||
|
||||
jmp init_ppu_and_wait
|
||||
|
||||
; zp_res
|
||||
zp_res TIMER_LOW
|
||||
update_audio:
|
||||
; adc TIMER_LOW
|
||||
; sta PULSE_CH1_TLOW
|
||||
; sta TIMER_LOW
|
||||
|
||||
rts
|
||||
|
||||
zp_res PRESSED_A
|
||||
zp_res PRESSED_B
|
||||
|
||||
nmi:
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
|
||||
lda #$01
|
||||
sta JOY1
|
||||
lda #$00
|
||||
sta JOY1
|
||||
|
||||
lda JOY1 ; A
|
||||
and #$01
|
||||
tax
|
||||
eor PRESSED_A
|
||||
beq done
|
||||
stx PRESSED_A
|
||||
txa
|
||||
and #$01
|
||||
beq done
|
||||
|
||||
lda #$08
|
||||
jsr update_audio
|
||||
|
||||
done:
|
||||
bit JOY1 ; B
|
||||
and #$01
|
||||
tax
|
||||
eor PRESSED_B
|
||||
beq done_b
|
||||
stx PRESSED_B
|
||||
txa
|
||||
and #$01
|
||||
beq done_b
|
||||
|
||||
lda #$F7
|
||||
jsr update_audio
|
||||
|
||||
done_b:
|
||||
bit JOY1 ; Select
|
||||
bit JOY1 ; Start
|
||||
bit JOY1 ; Up
|
||||
bit JOY1 ; Down
|
||||
bit JOY1 ; Left
|
||||
bit JOY1 ; Right
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda #%00001110
|
||||
sta PPUMASK ; Enable rendering
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
|
||||
irq:
|
||||
rti
|
||||
60
src/test_roms/apu_pulse_channel_1_evelope.s
Normal file
60
src/test_roms/apu_pulse_channel_1_evelope.s
Normal file
@@ -0,0 +1,60 @@
|
||||
.include "testing.s"
|
||||
.include "joysticks.s"
|
||||
.include "minimal_ppu.s"
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
lda #$01
|
||||
sta SNDCHN
|
||||
lda #$AF ; Duty 2, LC halted, evelope enabled, volume = F
|
||||
sta PULSE_CH1_DLCV
|
||||
lda #$7F ;
|
||||
sta PULSE_CH1_SWEEP
|
||||
lda #$6F
|
||||
sta PULSE_CH1_TLOW
|
||||
sta TIMER_LOW
|
||||
lda #$F0
|
||||
sta PULSE_CH1_LCTH
|
||||
; PULSE_CH1_DLCV = $4000
|
||||
; PULSE_CH1_SWEEP = $4001
|
||||
; PULSE_CH1_TLOW = $4002
|
||||
; PULSE_CH1_LCTH = $4003
|
||||
|
||||
jmp init_ppu_and_wait
|
||||
|
||||
; zp_res
|
||||
zp_res TIMER_LOW
|
||||
update_audio:
|
||||
adc TIMER_LOW
|
||||
sta PULSE_CH1_TLOW
|
||||
sta TIMER_LOW
|
||||
|
||||
rts
|
||||
|
||||
zp_res PRESSED_A
|
||||
zp_res PRESSED_B
|
||||
|
||||
nmi:
|
||||
nmi_start
|
||||
jsr read_joy1
|
||||
|
||||
and #$80
|
||||
beq not_a
|
||||
lda #$08
|
||||
jsr update_audio
|
||||
not_a:
|
||||
lda JOYTEMP
|
||||
and #$40
|
||||
beq not_b
|
||||
lda #$01
|
||||
jsr update_audio
|
||||
not_b:
|
||||
nmi_end
|
||||
|
||||
|
||||
irq:
|
||||
rti
|
||||
135
src/test_roms/apu_triangle.s
Normal file
135
src/test_roms/apu_triangle.s
Normal file
@@ -0,0 +1,135 @@
|
||||
.include "testing.s"
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; Init PPU
|
||||
bit PPUSTATUS
|
||||
vwait1:
|
||||
bit PPUSTATUS
|
||||
bpl vwait1
|
||||
vwait2:
|
||||
bit PPUSTATUS
|
||||
bpl vwait2
|
||||
|
||||
lda #$04
|
||||
sta SNDCHN
|
||||
lda #$FF
|
||||
sta TRIANGLE_LINEAR_C
|
||||
lda #$37
|
||||
sta TRIANGLE_TIMER_LOW
|
||||
lda #$00
|
||||
sta TRIANGLE_LEN_T_HIGH
|
||||
; TRIANGLE_LINEAR_C = $4008
|
||||
; TRIANGLE_TIMER_LOW = $400A
|
||||
; TRIANGLE_LEN_T_HIGH = $400B
|
||||
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
lda #$00
|
||||
loop:
|
||||
; sta SNDMODE
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
; nop
|
||||
jmp loop
|
||||
|
||||
; zp_res
|
||||
zp_res TIMER_LOW
|
||||
update_audio:
|
||||
; adc TIMER_LOW
|
||||
; sta PULSE_CH1_TLOW
|
||||
; sta TIMER_LOW
|
||||
|
||||
rts
|
||||
|
||||
zp_res PRESSED_A
|
||||
zp_res PRESSED_B
|
||||
|
||||
nmi:
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
|
||||
lda #$01
|
||||
sta JOY1
|
||||
lda #$00
|
||||
sta JOY1
|
||||
|
||||
lda JOY1 ; A
|
||||
and #$01
|
||||
tax
|
||||
eor PRESSED_A
|
||||
beq done
|
||||
stx PRESSED_A
|
||||
txa
|
||||
and #$01
|
||||
beq done
|
||||
|
||||
lda #$08
|
||||
jsr update_audio
|
||||
|
||||
done:
|
||||
bit JOY1 ; B
|
||||
and #$01
|
||||
tax
|
||||
eor PRESSED_B
|
||||
beq done_b
|
||||
stx PRESSED_B
|
||||
txa
|
||||
and #$01
|
||||
beq done_b
|
||||
|
||||
lda #$F7
|
||||
jsr update_audio
|
||||
|
||||
done_b:
|
||||
bit JOY1 ; Select
|
||||
bit JOY1 ; Start
|
||||
bit JOY1 ; Up
|
||||
bit JOY1 ; Down
|
||||
bit JOY1 ; Left
|
||||
bit JOY1 ; Right
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda #%00001110
|
||||
sta PPUMASK ; Enable rendering
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
|
||||
irq:
|
||||
rti
|
||||
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
|
||||
BIN
src/test_roms/common/ascii.chr
Normal file
BIN
src/test_roms/common/ascii.chr
Normal file
Binary file not shown.
109
src/test_roms/common/audio_inp.s
Normal file
109
src/test_roms/common/audio_inp.s
Normal file
@@ -0,0 +1,109 @@
|
||||
.include "joysticks.s"
|
||||
; Characters for view
|
||||
.include "bitmap_font.s"
|
||||
|
||||
zp_res TEMP
|
||||
|
||||
.macro load_ppu_addr addr
|
||||
lda #.hibyte(addr)
|
||||
sta PPUADDR
|
||||
lda #.lobyte(addr)
|
||||
sta PPUADDR ; Load $2000
|
||||
.endmacro
|
||||
|
||||
init_view:
|
||||
; 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
|
||||
|
||||
; Fill NT0
|
||||
load_ppu_addr $2000
|
||||
ldx #$00
|
||||
ldy #$04
|
||||
lda #EMPTY
|
||||
fill_loop:
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne fill_loop
|
||||
dey
|
||||
bne fill_loop
|
||||
|
||||
load_ppu_addr $23C0
|
||||
ldx #$C0
|
||||
lda #$00
|
||||
palette_loop:
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne palette_loop
|
||||
|
||||
load_ppu_addr $3F00
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$20
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$09
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
rts
|
||||
|
||||
enable_rendering:
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
lda #%00001110
|
||||
sta PPUMASK
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
rts
|
||||
|
||||
nmi:
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
|
||||
jsr read_joy1
|
||||
jsr audio_nmi
|
||||
|
||||
.macro bit_lsr val
|
||||
.scope
|
||||
lsr
|
||||
bcs carry_set
|
||||
ldx #EMPTY
|
||||
jmp carry_clear
|
||||
carry_set:
|
||||
ldx val
|
||||
carry_clear:
|
||||
stx PPUDATA
|
||||
.endscope
|
||||
.endmacro
|
||||
load_ppu_addr $2008
|
||||
lda JOYTEMP
|
||||
bit_lsr #RIGHT
|
||||
bit_lsr #LEFT
|
||||
bit_lsr #DOWN
|
||||
bit_lsr #UP
|
||||
bit_lsr #PILL
|
||||
bit_lsr #PILL
|
||||
bit_lsr #$0B
|
||||
bit_lsr #$0A
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda #%00001110
|
||||
sta PPUMASK ; Enable rendering
|
||||
rti
|
||||
530
src/test_roms/common/bitmap_font.s
Normal file
530
src/test_roms/common/bitmap_font.s
Normal file
@@ -0,0 +1,530 @@
|
||||
.pushseg
|
||||
.segment "CHARS"
|
||||
.repeat 2 ; 0
|
||||
.byte %00000000
|
||||
.byte %00111000
|
||||
.byte %01000100
|
||||
.byte %01001100
|
||||
.byte %01010100
|
||||
.byte %01100100
|
||||
.byte %01000100
|
||||
.byte %00111000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; 1
|
||||
.byte %00000000
|
||||
.byte %00010000
|
||||
.byte %00110000
|
||||
.byte %01010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %01111000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; 2
|
||||
.byte %00000000
|
||||
.byte %00111000
|
||||
.byte %01000100
|
||||
.byte %00000100
|
||||
.byte %00001000
|
||||
.byte %00010000
|
||||
.byte %00100000
|
||||
.byte %01111100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; 3
|
||||
.byte %00000000
|
||||
.byte %00111000
|
||||
.byte %01000100
|
||||
.byte %00000100
|
||||
.byte %00011000
|
||||
.byte %00000100
|
||||
.byte %01000100
|
||||
.byte %00111000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; 4
|
||||
.byte %00000000
|
||||
.byte %00001000
|
||||
.byte %00011000
|
||||
.byte %00101000
|
||||
.byte %01001000
|
||||
.byte %11111100
|
||||
.byte %00001000
|
||||
.byte %00001000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; 5
|
||||
.byte %00000000
|
||||
.byte %01111110
|
||||
.byte %01000000
|
||||
.byte %01000000
|
||||
.byte %01111100
|
||||
.byte %00000010
|
||||
.byte %01000010
|
||||
.byte %00111100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; 6
|
||||
.byte %00000000
|
||||
.byte %00111100
|
||||
.byte %01000010
|
||||
.byte %01000000
|
||||
.byte %01111100
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %00111100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; 7
|
||||
.byte %00000000
|
||||
.byte %01111110
|
||||
.byte %00000100
|
||||
.byte %00000100
|
||||
.byte %00001000
|
||||
.byte %00001000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; 8
|
||||
.byte %00000000
|
||||
.byte %00111000
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %00111000
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %00111000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; 9
|
||||
.byte %00000000
|
||||
.byte %00111000
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %00111100
|
||||
.byte %00000100
|
||||
.byte %00000100
|
||||
.byte %00000100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; A
|
||||
.byte %00000000
|
||||
.byte %01111000
|
||||
.byte %10000100
|
||||
.byte %10000100
|
||||
.byte %11111100
|
||||
.byte %10000100
|
||||
.byte %10000100
|
||||
.byte %10000100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; B
|
||||
.byte %00000000
|
||||
.byte %11111000
|
||||
.byte %10000100
|
||||
.byte %10000100
|
||||
.byte %11111000
|
||||
.byte %10000100
|
||||
.byte %10000100
|
||||
.byte %11111000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; C
|
||||
.byte %00000000
|
||||
.byte %01111000
|
||||
.byte %10000100
|
||||
.byte %10000000
|
||||
.byte %10000000
|
||||
.byte %10000000
|
||||
.byte %10000100
|
||||
.byte %01111000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; D
|
||||
.byte %00000000
|
||||
.byte %11111000
|
||||
.byte %10000100
|
||||
.byte %10000100
|
||||
.byte %10000100
|
||||
.byte %10000100
|
||||
.byte %10000100
|
||||
.byte %11111000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; E
|
||||
.byte %00000000
|
||||
.byte %11111100
|
||||
.byte %10000000
|
||||
.byte %10000000
|
||||
.byte %11111000
|
||||
.byte %10000000
|
||||
.byte %10000000
|
||||
.byte %11111100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; F
|
||||
.byte %00000000
|
||||
.byte %11111100
|
||||
.byte %10000000
|
||||
.byte %10000000
|
||||
.byte %11110000
|
||||
.byte %10000000
|
||||
.byte %10000000
|
||||
.byte %10000000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; G
|
||||
.byte %00000000
|
||||
.byte %01111100
|
||||
.byte %10000010
|
||||
.byte %10000000
|
||||
.byte %10001110
|
||||
.byte %10000010
|
||||
.byte %10000010
|
||||
.byte %01111110
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; H
|
||||
.byte %00000000
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %01111110
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; I
|
||||
.byte %00000000
|
||||
.byte %01111100
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %01111100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; J
|
||||
.byte %00000000
|
||||
.byte %01111100
|
||||
.byte %00001000
|
||||
.byte %00001000
|
||||
.byte %00001000
|
||||
.byte %00001000
|
||||
.byte %01001000
|
||||
.byte %00110000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; K
|
||||
.byte %00000000
|
||||
.byte %01000100
|
||||
.byte %01001000
|
||||
.byte %01010000
|
||||
.byte %01100000
|
||||
.byte %01010000
|
||||
.byte %01001000
|
||||
.byte %01000100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; L
|
||||
.byte %00000000
|
||||
.byte %01000000
|
||||
.byte %01000000
|
||||
.byte %01000000
|
||||
.byte %01000000
|
||||
.byte %01000000
|
||||
.byte %01000000
|
||||
.byte %01111100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; M
|
||||
.byte %00000000
|
||||
.byte %01000010
|
||||
.byte %01100110
|
||||
.byte %01011010
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; N
|
||||
.byte %00000000
|
||||
.byte %01000010
|
||||
.byte %01100010
|
||||
.byte %01010010
|
||||
.byte %01001010
|
||||
.byte %01000110
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; O
|
||||
.byte %00000000
|
||||
.byte %00111000
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %00111000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; P
|
||||
.byte %00000000
|
||||
.byte %01111100
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %01111100
|
||||
.byte %01000000
|
||||
.byte %01000000
|
||||
.byte %01000000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; Q
|
||||
.byte %00000000
|
||||
.byte %00111000
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %01001100
|
||||
.byte %00111100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; R
|
||||
.byte %00000000
|
||||
.byte %01111100
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %01111100
|
||||
.byte %01010000
|
||||
.byte %01001000
|
||||
.byte %01000100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; S
|
||||
.byte %00000000
|
||||
.byte %00111100
|
||||
.byte %01000010
|
||||
.byte %00100000
|
||||
.byte %00011000
|
||||
.byte %00000100
|
||||
.byte %01000100
|
||||
.byte %00111000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; T
|
||||
.byte %00000000
|
||||
.byte %01111100
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; U
|
||||
.byte %00000000
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %01000010
|
||||
.byte %00111100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; V
|
||||
.byte %00000000
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %00101000
|
||||
.byte %00101000
|
||||
.byte %00101000
|
||||
.byte %00010000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; W
|
||||
.byte %00000000
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %01010100
|
||||
.byte %01101100
|
||||
.byte %01000100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; X
|
||||
.byte %00000000
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %00101000
|
||||
.byte %00010000
|
||||
.byte %00101000
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; Y
|
||||
.byte %00000000
|
||||
.byte %01000100
|
||||
.byte %01000100
|
||||
.byte %00101000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.endrepeat
|
||||
|
||||
.repeat 2 ; Z
|
||||
.byte %00000000
|
||||
.byte %01111110
|
||||
.byte %00000010
|
||||
.byte %00000100
|
||||
.byte %00001000
|
||||
.byte %00010000
|
||||
.byte %00100000
|
||||
.byte %01111110
|
||||
.endrepeat
|
||||
|
||||
HEX = $24
|
||||
.repeat 2 ; $
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00010100
|
||||
.byte %00001000
|
||||
.byte %00010100
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
COLON = $25
|
||||
.repeat 2 ; $
|
||||
.byte %00000000
|
||||
.byte %00011000
|
||||
.byte %00011000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00011000
|
||||
.byte %00011000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
UP = $26
|
||||
.repeat 2 ; $
|
||||
.byte %00000000
|
||||
.byte %00010000
|
||||
.byte %00111000
|
||||
.byte %01010100
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
DOWN = $27
|
||||
.repeat 2 ; $
|
||||
.byte %00000000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %00010000
|
||||
.byte %01010100
|
||||
.byte %00111000
|
||||
.byte %00010000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
LEFT = $28
|
||||
.repeat 2 ; $
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00010000
|
||||
.byte %00100000
|
||||
.byte %01111110
|
||||
.byte %00100000
|
||||
.byte %00010000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
RIGHT = $29
|
||||
.repeat 2 ; $
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00001000
|
||||
.byte %00000100
|
||||
.byte %01111110
|
||||
.byte %00000100
|
||||
.byte %00001000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
PILL = $2A
|
||||
.repeat 2 ; $
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00111100
|
||||
.byte %01111110
|
||||
.byte %00111100
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
EMPTY = $2B
|
||||
.repeat 2 ; $
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
FONT_END = $2C
|
||||
.popseg
|
||||
|
||||
.macro write_char_x chr
|
||||
.if chr >= 'a' && chr <= 'z'
|
||||
ldx #(chr - 'a' + 10)
|
||||
.elseif chr >= 'A' && chr <= 'Z'
|
||||
ldx #(chr - 'A' + 10)
|
||||
.elseif chr >= '0' && chr <= '9'
|
||||
ldx #(chr - '0')
|
||||
.elseif chr = ' '
|
||||
ldx #EMPTY
|
||||
.elseif chr = ':'
|
||||
ldx #COLON
|
||||
.elseif chr = '$'
|
||||
ldx #HEX
|
||||
.else
|
||||
.error "Unhandled character chr: " chr
|
||||
.endif
|
||||
stx PPUDATA
|
||||
.endmacro
|
||||
|
||||
zp_res HEX_TEMP
|
||||
|
||||
write_hex_a:
|
||||
sta HEX_TEMP
|
||||
lsr
|
||||
lsr
|
||||
lsr
|
||||
lsr
|
||||
and #$F
|
||||
sta PPUDATA
|
||||
lda HEX_TEMP
|
||||
and #$F
|
||||
sta PPUDATA
|
||||
rts
|
||||
118
src/test_roms/common/build_rom.s
Normal file
118
src/test_roms/common/build_rom.s
Normal file
@@ -0,0 +1,118 @@
|
||||
; Builds program as iNES ROM
|
||||
|
||||
; Default is 32K PRG and 8K CHR ROM, NROM (0)
|
||||
|
||||
.if 0 ; Options to set before .include "shell.inc":
|
||||
CHR_RAM=1 ; Use CHR-RAM instead of CHR-ROM
|
||||
CART_WRAM=1 ; Use mapper that supports 8K WRAM in cart
|
||||
CUSTOM_MAPPER=n ; Specify mapper number
|
||||
.endif
|
||||
|
||||
.ifndef CUSTOM_MAPPER
|
||||
.ifdef CART_WRAM
|
||||
CUSTOM_MAPPER = 2 ; UNROM
|
||||
.else
|
||||
CUSTOM_MAPPER = 0 ; NROM
|
||||
.endif
|
||||
.endif
|
||||
|
||||
;;;; iNES header
|
||||
.ifndef CUSTOM_HEADER
|
||||
.segment "HEADER"
|
||||
.byte $4E,$45,$53,26 ; "NES" EOF
|
||||
|
||||
.ifdef CHR_RAM
|
||||
.byte 2,0 ; 32K PRG, CHR RAM
|
||||
.else
|
||||
.byte 2,1 ; 32K PRG, 8K CHR
|
||||
.endif
|
||||
|
||||
.byte CUSTOM_MAPPER*$10+$01 ; vertical mirroring
|
||||
.endif
|
||||
|
||||
.ifndef CUSTOM_VECTORS
|
||||
.segment "VECTORS"
|
||||
.word $FFFF,$FFFF,$FFFF, nmi, reset, irq
|
||||
.endif
|
||||
|
||||
;;;; CHR-RAM/ROM
|
||||
.ifdef CHR_RAM
|
||||
.define CHARS "CHARS_PRG"
|
||||
.segment CHARS
|
||||
ascii_chr:
|
||||
|
||||
.segment "CHARS_PRG_ASCII"
|
||||
.align $200
|
||||
.incbin "ascii.chr"
|
||||
ascii_chr_end:
|
||||
.else
|
||||
.define CHARS "CHARS"
|
||||
.segment "CHARS_ASCII"
|
||||
.align $200
|
||||
.incbin "ascii.chr"
|
||||
.res $1800
|
||||
.endif
|
||||
|
||||
.segment "CODE"
|
||||
.ifndef CUSTOM_RESET
|
||||
reset:
|
||||
.endif
|
||||
std_reset:
|
||||
jmp main
|
||||
|
||||
zp_byte nmi_count
|
||||
zp_byte flags_from_nmi
|
||||
zp_byte pclo_from_nmi
|
||||
zp_byte nmi_temp
|
||||
.ifndef CUSTOM_NMI
|
||||
nmi:
|
||||
.endif
|
||||
std_nmi:
|
||||
sta nmi_temp
|
||||
pla
|
||||
sta flags_from_nmi
|
||||
pla
|
||||
sta pclo_from_nmi
|
||||
pha
|
||||
lda flags_from_nmi
|
||||
pha
|
||||
lda nmi_temp
|
||||
inc nmi_count
|
||||
rti
|
||||
|
||||
zp_byte flags_from_irq
|
||||
zp_byte pclo_from_irq
|
||||
zp_byte irq_count
|
||||
|
||||
.ifndef CUSTOM_IRQ
|
||||
irq:
|
||||
.endif
|
||||
std_irq: ; Record flags and PC low byte from stack
|
||||
pla
|
||||
sta flags_from_irq
|
||||
pla
|
||||
sta pclo_from_irq
|
||||
pha
|
||||
lda flags_from_irq
|
||||
pha
|
||||
inc irq_count
|
||||
bit SNDCHN ; clear frame IRQ flag
|
||||
rti
|
||||
|
||||
fail:
|
||||
lda #$01
|
||||
.ifndef CUSTOM_EXIT
|
||||
exit:
|
||||
.endif
|
||||
std_exit:
|
||||
.byte $02
|
||||
|
||||
.segment CHARS
|
||||
.res $10,0
|
||||
|
||||
; Move code to $C000
|
||||
.segment "CODE"
|
||||
.res $4000
|
||||
|
||||
.code
|
||||
.align 256
|
||||
331
src/test_roms/common/console.s
Normal file
331
src/test_roms/common/console.s
Normal file
@@ -0,0 +1,331 @@
|
||||
; Scrolling text console with line wrapping, 30x29 characters.
|
||||
; Buffers lines for speed. Will work even if PPU doesn't
|
||||
; support scrolling (until text reaches bottom). Keeps border
|
||||
; along bottom in case TV cuts it off.
|
||||
;
|
||||
; Defers most initialization until first newline, at which
|
||||
; point it clears nametable and makes palette non-black.
|
||||
;
|
||||
; ** ASCII font must already be in CHR, and mirroring
|
||||
; must be vertical or single-screen.
|
||||
|
||||
.ifndef CONSOLE_COLOR
|
||||
CONSOLE_COLOR = $30 ; white
|
||||
.endif
|
||||
|
||||
console_screen_width = 32 ; if lower than 32, left-justifies
|
||||
|
||||
; Number of characters of margin on left and right, to avoid
|
||||
; text getting cut off by common TVs. OK if either/both are 0.
|
||||
console_left_margin = 1
|
||||
console_right_margin = 1
|
||||
|
||||
console_width = console_screen_width - console_left_margin - console_right_margin
|
||||
|
||||
zp_byte console_pos ; 0 to console_width
|
||||
zp_byte console_scroll
|
||||
zp_byte console_temp
|
||||
bss_res console_buf,console_width
|
||||
|
||||
|
||||
; Initializes console
|
||||
console_init:
|
||||
; Flag that console hasn't been initialized
|
||||
setb console_scroll,$FF
|
||||
|
||||
setb console_pos,0
|
||||
rts
|
||||
|
||||
|
||||
; Hides console by disabling PPU rendering and blacking out
|
||||
; first four entries of palette.
|
||||
; Preserved: A, X, Y
|
||||
console_hide:
|
||||
pha
|
||||
jsr console_wait_vbl_
|
||||
setb PPUMASK,0
|
||||
lda #$0F
|
||||
jsr console_load_palette_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Shows console display
|
||||
; Preserved: X, Y
|
||||
console_show:
|
||||
pha
|
||||
lda #CONSOLE_COLOR
|
||||
jsr console_show_custom_color_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Prints char A to console. Will not appear until
|
||||
; a newline or flush occurs.
|
||||
; Preserved: A, X, Y
|
||||
console_print:
|
||||
cmp #10
|
||||
beq console_newline
|
||||
|
||||
sty console_temp
|
||||
|
||||
ldy console_pos
|
||||
cpy #console_width
|
||||
beq console_full_
|
||||
sta console_buf,y
|
||||
iny
|
||||
sty console_pos
|
||||
|
||||
ldy console_temp
|
||||
rts
|
||||
|
||||
|
||||
; Displays current line and starts new one
|
||||
; Preserved: A, X, Y
|
||||
console_newline:
|
||||
pha
|
||||
jsr console_wait_vbl_
|
||||
jsr console_flush_
|
||||
jsr console_scroll_up_
|
||||
setb console_pos,0
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Displays current line's contents without scrolling.
|
||||
; Preserved: A, X, Y
|
||||
console_flush:
|
||||
pha
|
||||
jsr console_wait_vbl_
|
||||
jsr console_flush_
|
||||
jsr console_apply_scroll_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
;**** Internal routines ****
|
||||
|
||||
console_full_:
|
||||
ldy console_temp
|
||||
|
||||
; Line is full
|
||||
|
||||
; If space, treat as newline
|
||||
cmp #' '
|
||||
beq console_newline
|
||||
|
||||
; Wrap current line at appropriate point
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
jsr console_wrap_
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
|
||||
jmp console_print
|
||||
|
||||
|
||||
; Inserts newline into buffer at appropriate position, leaving
|
||||
; next line ready in buffer
|
||||
; Preserved: X, console_temp
|
||||
console_wrap_:
|
||||
; Find beginning of last word
|
||||
ldy #console_width
|
||||
lda #' '
|
||||
: dey
|
||||
bmi console_newline
|
||||
cmp console_buf,y
|
||||
bne :-
|
||||
|
||||
; y = 0 to console_width-1
|
||||
|
||||
; Flush through current word and put remaining
|
||||
; in buffer for next line
|
||||
jsr console_wait_vbl_
|
||||
|
||||
; Time to last PPU write: 207 + 32*(26 + 10)
|
||||
|
||||
lda console_scroll
|
||||
jsr console_set_ppuaddr_
|
||||
|
||||
stx console_pos ; save X
|
||||
|
||||
ldx #0
|
||||
|
||||
; Print everything before last word
|
||||
: lda console_buf,x
|
||||
sta PPUDATA
|
||||
inx
|
||||
dey
|
||||
bpl :-
|
||||
|
||||
; x = 1 to console_width
|
||||
|
||||
; Move last word to beginning of buffer, and
|
||||
; print spaces for rest of line
|
||||
ldy #0
|
||||
beq :++
|
||||
: lda #' '
|
||||
sta PPUDATA
|
||||
lda console_buf,x
|
||||
inx
|
||||
sta console_buf,y
|
||||
iny
|
||||
: cpx #console_width
|
||||
bne :--
|
||||
|
||||
ldx console_pos ; restore X
|
||||
|
||||
; Append new text after that
|
||||
sty console_pos
|
||||
|
||||
; FALL THROUGH
|
||||
|
||||
|
||||
; Scrolls up 8 pixels and clears one line BELOW new line
|
||||
; Preserved: X, console_temp
|
||||
console_scroll_up_:
|
||||
; Scroll up 8 pixels
|
||||
lda console_scroll
|
||||
jsr console_add_8_to_scroll_
|
||||
sta console_scroll
|
||||
|
||||
; Clear line AFTER that on screen
|
||||
jsr console_add_8_to_scroll_
|
||||
jsr console_set_ppuaddr_
|
||||
|
||||
ldy #console_width
|
||||
lda #' '
|
||||
: sta PPUDATA
|
||||
dey
|
||||
bne :-
|
||||
; FALL THROUGH
|
||||
|
||||
|
||||
; Applies current scrolling position to PPU
|
||||
; Preserved: X, Y, console_temp
|
||||
console_apply_scroll_:
|
||||
lda #0
|
||||
sta PPUADDR
|
||||
sta PPUADDR
|
||||
|
||||
sta PPUSCROLL
|
||||
lda console_scroll
|
||||
jsr console_add_8_to_scroll_
|
||||
jsr console_add_8_to_scroll_
|
||||
sta PPUSCROLL
|
||||
rts
|
||||
|
||||
|
||||
; Sets PPU address for row
|
||||
; In: A = scroll position
|
||||
; Preserved: X, Y
|
||||
console_set_ppuaddr_:
|
||||
sta console_temp
|
||||
lda #$08
|
||||
asl console_temp
|
||||
rol a
|
||||
asl console_temp
|
||||
rol a
|
||||
sta PPUADDR
|
||||
lda console_temp
|
||||
ora #console_left_margin
|
||||
sta PPUADDR
|
||||
rts
|
||||
|
||||
|
||||
; A = (A + 8) % 240
|
||||
; Preserved: X, Y
|
||||
console_add_8_to_scroll_:
|
||||
cmp #240-8
|
||||
bcc :+
|
||||
adc #16-1;+1 for set carry
|
||||
: adc #8
|
||||
rts
|
||||
|
||||
|
||||
console_show_custom_color_:
|
||||
pha
|
||||
jsr console_wait_vbl_
|
||||
setb PPUMASK,PPUMASK_BG0
|
||||
pla
|
||||
jsr console_load_palette_
|
||||
jmp console_apply_scroll_
|
||||
|
||||
|
||||
console_load_palette_:
|
||||
pha
|
||||
setb PPUADDR,$3F
|
||||
setb PPUADDR,$00
|
||||
setb PPUDATA,$0F ; black
|
||||
pla
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
rts
|
||||
|
||||
|
||||
; Initializes PPU if necessary, then waits for VBL
|
||||
; Preserved: A, X, Y, console_temp
|
||||
console_wait_vbl_:
|
||||
lda console_scroll
|
||||
cmp #$FF
|
||||
bne @already_initialized
|
||||
|
||||
; Deferred initialization of PPU until first use of console
|
||||
|
||||
; In case PPU doesn't support scrolling, start a
|
||||
; couple of lines down
|
||||
setb console_scroll,16
|
||||
|
||||
jsr console_hide
|
||||
tya
|
||||
pha
|
||||
|
||||
; Fill nametable with spaces
|
||||
setb PPUADDR,$20
|
||||
setb PPUADDR,$00
|
||||
ldy #240
|
||||
lda #' '
|
||||
: sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
dey
|
||||
bne :-
|
||||
|
||||
; Clear attributes
|
||||
lda #0
|
||||
ldy #$40
|
||||
: sta PPUDATA
|
||||
dey
|
||||
bne :-
|
||||
|
||||
pla
|
||||
tay
|
||||
|
||||
jsr console_show
|
||||
@already_initialized:
|
||||
jmp wait_vbl_optional
|
||||
|
||||
|
||||
; Flushes current line
|
||||
; Preserved: X, Y
|
||||
console_flush_:
|
||||
lda console_scroll
|
||||
jsr console_set_ppuaddr_
|
||||
|
||||
sty console_temp
|
||||
|
||||
; Copy line
|
||||
ldy #0
|
||||
beq :++
|
||||
: lda console_buf,y
|
||||
sta PPUDATA
|
||||
iny
|
||||
: cpy console_pos
|
||||
bne :--
|
||||
|
||||
ldy console_temp
|
||||
rts
|
||||
118
src/test_roms/common/crc.s
Normal file
118
src/test_roms/common/crc.s
Normal file
@@ -0,0 +1,118 @@
|
||||
; CRC-32 checksum calculation
|
||||
|
||||
zp_res checksum,4
|
||||
zp_byte checksum_temp
|
||||
zp_byte checksum_off_
|
||||
|
||||
; Turns CRC updating on/off. Allows nesting.
|
||||
; Preserved: A, X, Y
|
||||
crc_off:
|
||||
dec checksum_off_
|
||||
rts
|
||||
|
||||
crc_on: inc checksum_off_
|
||||
beq :+
|
||||
jpl internal_error ; catch unbalanced crc calls
|
||||
: rts
|
||||
|
||||
|
||||
; Initializes checksum module. Might initialize tables
|
||||
; in the future.
|
||||
init_crc:
|
||||
jmp reset_crc
|
||||
|
||||
|
||||
; Clears checksum and turns it on
|
||||
; Preserved: X, Y
|
||||
reset_crc:
|
||||
lda #0
|
||||
sta checksum_off_
|
||||
lda #$FF
|
||||
sta checksum
|
||||
sta checksum + 1
|
||||
sta checksum + 2
|
||||
sta checksum + 3
|
||||
rts
|
||||
|
||||
|
||||
; Updates checksum with byte in A (unless disabled via crc_off)
|
||||
; Preserved: A, X, Y
|
||||
; Time: 357 clocks average
|
||||
update_crc:
|
||||
bit checksum_off_
|
||||
bmi update_crc_off
|
||||
update_crc_:
|
||||
pha
|
||||
stx checksum_temp
|
||||
eor checksum
|
||||
ldx #8
|
||||
@bit: lsr checksum+3
|
||||
ror checksum+2
|
||||
ror checksum+1
|
||||
ror a
|
||||
bcc :+
|
||||
sta checksum
|
||||
lda checksum+3
|
||||
eor #$ED
|
||||
sta checksum+3
|
||||
lda checksum+2
|
||||
eor #$B8
|
||||
sta checksum+2
|
||||
lda checksum+1
|
||||
eor #$83
|
||||
sta checksum+1
|
||||
lda checksum
|
||||
eor #$20
|
||||
: dex
|
||||
bne @bit
|
||||
sta checksum
|
||||
ldx checksum_temp
|
||||
pla
|
||||
update_crc_off:
|
||||
rts
|
||||
|
||||
|
||||
; Prints checksum as 8-character hex value
|
||||
print_crc:
|
||||
jsr crc_off
|
||||
|
||||
; Print complement
|
||||
ldx #3
|
||||
: lda checksum,x
|
||||
eor #$FF
|
||||
jsr print_hex
|
||||
dex
|
||||
bpl :-
|
||||
|
||||
jmp crc_on
|
||||
|
||||
|
||||
; EQ if checksum matches CRC
|
||||
; Out: A=0 and EQ if match, A>0 and NE if different
|
||||
; Preserved: X, Y
|
||||
.macro is_crc crc
|
||||
jsr_with_addr is_crc_,{.dword crc}
|
||||
.endmacro
|
||||
|
||||
is_crc_:
|
||||
tya
|
||||
pha
|
||||
|
||||
; Compare with complemented checksum
|
||||
ldy #3
|
||||
: lda (ptr),y
|
||||
sec
|
||||
adc checksum,y
|
||||
bne @wrong
|
||||
dey
|
||||
bpl :-
|
||||
pla
|
||||
tay
|
||||
lda #0
|
||||
rts
|
||||
|
||||
@wrong:
|
||||
pla
|
||||
tay
|
||||
lda #1
|
||||
rts
|
||||
124
src/test_roms/common/crc_fast.s
Normal file
124
src/test_roms/common/crc_fast.s
Normal file
@@ -0,0 +1,124 @@
|
||||
; Fast table-based CRC-32
|
||||
zp_res checksum,4
|
||||
zp_byte checksum_temp
|
||||
zp_byte checksum_off_
|
||||
; Clears checksum and turns it on
|
||||
; Preserved: X, Y
|
||||
reset_crc:
|
||||
lda #0
|
||||
sta checksum_off_
|
||||
lda #$FF
|
||||
sta checksum
|
||||
sta checksum + 1
|
||||
sta checksum + 2
|
||||
sta checksum + 3
|
||||
rts
|
||||
|
||||
; Initializes fast CRC tables and resets checksum.
|
||||
; Preserved: Y
|
||||
init_crc_fast = reset_crc
|
||||
|
||||
; Updates checksum with byte from A
|
||||
; Preserved: X, Y
|
||||
; Time: 54 clocks
|
||||
update_crc_fast:
|
||||
stx checksum_temp
|
||||
|
||||
; Updates checksum with byte from A
|
||||
; Preserved: Y
|
||||
; Time: 42 clocks
|
||||
.macro update_crc_fast
|
||||
eor checksum
|
||||
tax
|
||||
lda checksum+1
|
||||
eor checksum_t0,x
|
||||
sta checksum
|
||||
lda checksum+2
|
||||
eor checksum_t1,x
|
||||
sta checksum+1
|
||||
lda checksum+3
|
||||
eor checksum_t2,x
|
||||
sta checksum+2
|
||||
lda checksum_t3,x
|
||||
sta checksum+3
|
||||
.endmacro
|
||||
|
||||
update_crc_fast
|
||||
ldx checksum_temp
|
||||
rts
|
||||
|
||||
.pushseg
|
||||
.segment "RODATA"
|
||||
.align 256
|
||||
checksum_t0:
|
||||
.byte $8D,$1B,$A1,$37,$94,$02,$B8,$2E,$BF,$29,$93,$05,$A6,$30,$8A,$1C
|
||||
.byte $E9,$7F,$C5,$53,$F0,$66,$DC,$4A,$DB,$4D,$F7,$61,$C2,$54,$EE,$78
|
||||
.byte $45,$D3,$69,$FF,$5C,$CA,$70,$E6,$77,$E1,$5B,$CD,$6E,$F8,$42,$D4
|
||||
.byte $21,$B7,$0D,$9B,$38,$AE,$14,$82,$13,$85,$3F,$A9,$0A,$9C,$26,$B0
|
||||
.byte $1D,$8B,$31,$A7,$04,$92,$28,$BE,$2F,$B9,$03,$95,$36,$A0,$1A,$8C
|
||||
.byte $79,$EF,$55,$C3,$60,$F6,$4C,$DA,$4B,$DD,$67,$F1,$52,$C4,$7E,$E8
|
||||
.byte $D5,$43,$F9,$6F,$CC,$5A,$E0,$76,$E7,$71,$CB,$5D,$FE,$68,$D2,$44
|
||||
.byte $B1,$27,$9D,$0B,$A8,$3E,$84,$12,$83,$15,$AF,$39,$9A,$0C,$B6,$20
|
||||
.byte $AD,$3B,$81,$17,$B4,$22,$98,$0E,$9F,$09,$B3,$25,$86,$10,$AA,$3C
|
||||
.byte $C9,$5F,$E5,$73,$D0,$46,$FC,$6A,$FB,$6D,$D7,$41,$E2,$74,$CE,$58
|
||||
.byte $65,$F3,$49,$DF,$7C,$EA,$50,$C6,$57,$C1,$7B,$ED,$4E,$D8,$62,$F4
|
||||
.byte $01,$97,$2D,$BB,$18,$8E,$34,$A2,$33,$A5,$1F,$89,$2A,$BC,$06,$90
|
||||
.byte $3D,$AB,$11,$87,$24,$B2,$08,$9E,$0F,$99,$23,$B5,$16,$80,$3A,$AC
|
||||
.byte $59,$CF,$75,$E3,$40,$D6,$6C,$FA,$6B,$FD,$47,$D1,$72,$E4,$5E,$C8
|
||||
.byte $F5,$63,$D9,$4F,$EC,$7A,$C0,$56,$C7,$51,$EB,$7D,$DE,$48,$F2,$64
|
||||
.byte $91,$07,$BD,$2B,$88,$1E,$A4,$32,$A3,$35,$8F,$19,$BA,$2C,$96,$00
|
||||
|
||||
checksum_t1:
|
||||
.byte $EF,$DF,$8E,$BE,$2B,$1B,$4A,$7A,$67,$57,$06,$36,$A3,$93,$C2,$F2
|
||||
.byte $FF,$CF,$9E,$AE,$3B,$0B,$5A,$6A,$77,$47,$16,$26,$B3,$83,$D2,$E2
|
||||
.byte $CF,$FF,$AE,$9E,$0B,$3B,$6A,$5A,$47,$77,$26,$16,$83,$B3,$E2,$D2
|
||||
.byte $DF,$EF,$BE,$8E,$1B,$2B,$7A,$4A,$57,$67,$36,$06,$93,$A3,$F2,$C2
|
||||
.byte $AE,$9E,$CF,$FF,$6A,$5A,$0B,$3B,$26,$16,$47,$77,$E2,$D2,$83,$B3
|
||||
.byte $BE,$8E,$DF,$EF,$7A,$4A,$1B,$2B,$36,$06,$57,$67,$F2,$C2,$93,$A3
|
||||
.byte $8E,$BE,$EF,$DF,$4A,$7A,$2B,$1B,$06,$36,$67,$57,$C2,$F2,$A3,$93
|
||||
.byte $9E,$AE,$FF,$CF,$5A,$6A,$3B,$0B,$16,$26,$77,$47,$D2,$E2,$B3,$83
|
||||
.byte $6C,$5C,$0D,$3D,$A8,$98,$C9,$F9,$E4,$D4,$85,$B5,$20,$10,$41,$71
|
||||
.byte $7C,$4C,$1D,$2D,$B8,$88,$D9,$E9,$F4,$C4,$95,$A5,$30,$00,$51,$61
|
||||
.byte $4C,$7C,$2D,$1D,$88,$B8,$E9,$D9,$C4,$F4,$A5,$95,$00,$30,$61,$51
|
||||
.byte $5C,$6C,$3D,$0D,$98,$A8,$F9,$C9,$D4,$E4,$B5,$85,$10,$20,$71,$41
|
||||
.byte $2D,$1D,$4C,$7C,$E9,$D9,$88,$B8,$A5,$95,$C4,$F4,$61,$51,$00,$30
|
||||
.byte $3D,$0D,$5C,$6C,$F9,$C9,$98,$A8,$B5,$85,$D4,$E4,$71,$41,$10,$20
|
||||
.byte $0D,$3D,$6C,$5C,$C9,$F9,$A8,$98,$85,$B5,$E4,$D4,$41,$71,$20,$10
|
||||
.byte $1D,$2D,$7C,$4C,$D9,$E9,$B8,$88,$95,$A5,$F4,$C4,$51,$61,$30,$00
|
||||
|
||||
checksum_t2:
|
||||
.byte $02,$05,$0C,$0B,$6F,$68,$61,$66,$D9,$DE,$D7,$D0,$B4,$B3,$BA,$BD
|
||||
.byte $B5,$B2,$BB,$BC,$D8,$DF,$D6,$D1,$6E,$69,$60,$67,$03,$04,$0D,$0A
|
||||
.byte $6C,$6B,$62,$65,$01,$06,$0F,$08,$B7,$B0,$B9,$BE,$DA,$DD,$D4,$D3
|
||||
.byte $DB,$DC,$D5,$D2,$B6,$B1,$B8,$BF,$00,$07,$0E,$09,$6D,$6A,$63,$64
|
||||
.byte $DE,$D9,$D0,$D7,$B3,$B4,$BD,$BA,$05,$02,$0B,$0C,$68,$6F,$66,$61
|
||||
.byte $69,$6E,$67,$60,$04,$03,$0A,$0D,$B2,$B5,$BC,$BB,$DF,$D8,$D1,$D6
|
||||
.byte $B0,$B7,$BE,$B9,$DD,$DA,$D3,$D4,$6B,$6C,$65,$62,$06,$01,$08,$0F
|
||||
.byte $07,$00,$09,$0E,$6A,$6D,$64,$63,$DC,$DB,$D2,$D5,$B1,$B6,$BF,$B8
|
||||
.byte $BA,$BD,$B4,$B3,$D7,$D0,$D9,$DE,$61,$66,$6F,$68,$0C,$0B,$02,$05
|
||||
.byte $0D,$0A,$03,$04,$60,$67,$6E,$69,$D6,$D1,$D8,$DF,$BB,$BC,$B5,$B2
|
||||
.byte $D4,$D3,$DA,$DD,$B9,$BE,$B7,$B0,$0F,$08,$01,$06,$62,$65,$6C,$6B
|
||||
.byte $63,$64,$6D,$6A,$0E,$09,$00,$07,$B8,$BF,$B6,$B1,$D5,$D2,$DB,$DC
|
||||
.byte $66,$61,$68,$6F,$0B,$0C,$05,$02,$BD,$BA,$B3,$B4,$D0,$D7,$DE,$D9
|
||||
.byte $D1,$D6,$DF,$D8,$BC,$BB,$B2,$B5,$0A,$0D,$04,$03,$67,$60,$69,$6E
|
||||
.byte $08,$0F,$06,$01,$65,$62,$6B,$6C,$D3,$D4,$DD,$DA,$BE,$B9,$B0,$B7
|
||||
.byte $BF,$B8,$B1,$B6,$D2,$D5,$DC,$DB,$64,$63,$6A,$6D,$09,$0E,$07,$00
|
||||
|
||||
checksum_t3:
|
||||
.byte $D2,$A5,$3C,$4B,$D5,$A2,$3B,$4C,$DC,$AB,$32,$45,$DB,$AC,$35,$42
|
||||
.byte $CF,$B8,$21,$56,$C8,$BF,$26,$51,$C1,$B6,$2F,$58,$C6,$B1,$28,$5F
|
||||
.byte $E9,$9E,$07,$70,$EE,$99,$00,$77,$E7,$90,$09,$7E,$E0,$97,$0E,$79
|
||||
.byte $F4,$83,$1A,$6D,$F3,$84,$1D,$6A,$FA,$8D,$14,$63,$FD,$8A,$13,$64
|
||||
.byte $A4,$D3,$4A,$3D,$A3,$D4,$4D,$3A,$AA,$DD,$44,$33,$AD,$DA,$43,$34
|
||||
.byte $B9,$CE,$57,$20,$BE,$C9,$50,$27,$B7,$C0,$59,$2E,$B0,$C7,$5E,$29
|
||||
.byte $9F,$E8,$71,$06,$98,$EF,$76,$01,$91,$E6,$7F,$08,$96,$E1,$78,$0F
|
||||
.byte $82,$F5,$6C,$1B,$85,$F2,$6B,$1C,$8C,$FB,$62,$15,$8B,$FC,$65,$12
|
||||
.byte $3F,$48,$D1,$A6,$38,$4F,$D6,$A1,$31,$46,$DF,$A8,$36,$41,$D8,$AF
|
||||
.byte $22,$55,$CC,$BB,$25,$52,$CB,$BC,$2C,$5B,$C2,$B5,$2B,$5C,$C5,$B2
|
||||
.byte $04,$73,$EA,$9D,$03,$74,$ED,$9A,$0A,$7D,$E4,$93,$0D,$7A,$E3,$94
|
||||
.byte $19,$6E,$F7,$80,$1E,$69,$F0,$87,$17,$60,$F9,$8E,$10,$67,$FE,$89
|
||||
.byte $49,$3E,$A7,$D0,$4E,$39,$A0,$D7,$47,$30,$A9,$DE,$40,$37,$AE,$D9
|
||||
.byte $54,$23,$BA,$CD,$53,$24,$BD,$CA,$5A,$2D,$B4,$C3,$5D,$2A,$B3,$C4
|
||||
.byte $72,$05,$9C,$EB,$75,$02,$9B,$EC,$7C,$0B,$92,$E5,$7B,$0C,$95,$E2
|
||||
.byte $6F,$18,$81,$F6,$68,$1F,$86,$F1,$61,$16,$8F,$F8,$66,$11,$88,$FF
|
||||
.popseg
|
||||
190
src/test_roms/common/delay.s
Normal file
190
src/test_roms/common/delay.s
Normal file
@@ -0,0 +1,190 @@
|
||||
; Delays in CPU clocks, milliseconds, etc. All routines are re-entrant
|
||||
; (no global data). No routines touch X or Y during execution.
|
||||
; Code generated by macros is relocatable; it contains no JMPs to itself.
|
||||
|
||||
zp_byte delay_temp_ ; only written to
|
||||
|
||||
; Delays n clocks, from 2 to 16777215
|
||||
; Preserved: A, X, Y, flags
|
||||
.macro delay n
|
||||
.if (n) < 0 .or (n) = 1 .or (n) > 16777215
|
||||
.error "Delay out of range"
|
||||
.endif
|
||||
delay_ (n)
|
||||
.endmacro
|
||||
|
||||
|
||||
; Delays n milliseconds (1/1000 second)
|
||||
; n can range from 0 to 1100.
|
||||
; Preserved: A, X, Y, flags
|
||||
.macro delay_msec n
|
||||
.if (n) < 0 .or (n) > 1100
|
||||
.error "time out of range"
|
||||
.endif
|
||||
delay ((n)*CLOCK_RATE+500)/1000
|
||||
.endmacro
|
||||
|
||||
|
||||
; Delays n microseconds (1/1000000 second).
|
||||
; n can range from 0 to 100000.
|
||||
; Preserved: A, X, Y, flags
|
||||
.macro delay_usec n
|
||||
.if (n) < 0 .or (n) > 100000
|
||||
.error "time out of range"
|
||||
.endif
|
||||
delay ((n)*((CLOCK_RATE+50)/100)+5000)/10000
|
||||
.endmacro
|
||||
|
||||
.align 64
|
||||
|
||||
; Delays A clocks + overhead
|
||||
; Preserved: X, Y
|
||||
; Time: A+25 clocks (including JSR)
|
||||
: sbc #7 ; carry set by CMP
|
||||
delay_a_25_clocks:
|
||||
cmp #7
|
||||
bcs :- ; do multiples of 7
|
||||
lsr a ; bit 0
|
||||
bcs :+
|
||||
: ; A=clocks/2, either 0,1,2,3
|
||||
beq @zero ; 0: 5
|
||||
lsr a
|
||||
beq :+ ; 1: 7
|
||||
bcc :+ ; 2: 9
|
||||
@zero: bne :+ ; 3: 11
|
||||
: rts ; (thanks to dclxvi for the algorithm)
|
||||
|
||||
|
||||
; Delays A*256 clocks + overhead
|
||||
; Preserved: X, Y
|
||||
; Time: A*256+16 clocks (including JSR)
|
||||
delay_256a_16_clocks:
|
||||
cmp #0
|
||||
bne :+
|
||||
rts
|
||||
delay_256a_11_clocks_:
|
||||
: pha
|
||||
lda #256-19-22
|
||||
jsr delay_a_25_clocks
|
||||
pla
|
||||
clc
|
||||
adc #$FF
|
||||
bne :-
|
||||
rts
|
||||
|
||||
|
||||
; Delays A*65536 clocks + overhead
|
||||
; Preserved: X, Y
|
||||
; Time: A*65536+16 clocks (including JSR)
|
||||
delay_65536a_16_clocks:
|
||||
cmp #0
|
||||
bne :+
|
||||
rts
|
||||
delay_65536a_11_clocks_:
|
||||
: pha
|
||||
lda #256-19-22-13
|
||||
jsr delay_a_25_clocks
|
||||
lda #255
|
||||
jsr delay_256a_11_clocks_
|
||||
pla
|
||||
clc
|
||||
adc #$FF
|
||||
bne :-
|
||||
rts
|
||||
|
||||
max_short_delay = 41
|
||||
|
||||
; delay_short_ macro jumps into these
|
||||
.res (max_short_delay-12)/2,$EA ; NOP
|
||||
delay_unrolled_:
|
||||
rts
|
||||
|
||||
.macro delay_short_ n
|
||||
.if n < 0 .or n = 1 .or n > max_short_delay
|
||||
.error "Internal delay error"
|
||||
.endif
|
||||
.if n = 0
|
||||
; nothing
|
||||
.elseif n = 2
|
||||
nop
|
||||
.elseif n = 3
|
||||
sta <delay_temp_
|
||||
.elseif n = 4
|
||||
nop
|
||||
nop
|
||||
.elseif n = 5
|
||||
sta <delay_temp_
|
||||
nop
|
||||
.elseif n = 6
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
.elseif n = 7
|
||||
php
|
||||
plp
|
||||
.elseif n = 8
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
.elseif n = 9
|
||||
php
|
||||
plp
|
||||
nop
|
||||
.elseif n = 10
|
||||
sta <delay_temp_
|
||||
php
|
||||
plp
|
||||
.elseif n = 11
|
||||
php
|
||||
plp
|
||||
nop
|
||||
nop
|
||||
.elseif n = 13
|
||||
php
|
||||
plp
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
.elseif n & 1
|
||||
sta <delay_temp_
|
||||
jsr delay_unrolled_-((n-15)/2)
|
||||
.else
|
||||
jsr delay_unrolled_-((n-12)/2)
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
.macro delay_nosave_ n
|
||||
; 65536+17 = maximum delay using delay_256a_11_clocks_
|
||||
; 255+27 = maximum delay using delay_a_25_clocks
|
||||
; 27 = minimum delay using delay_a_25_clocks
|
||||
.if n > 65536+17
|
||||
lda #^(n - 15)
|
||||
jsr delay_65536a_11_clocks_
|
||||
; +2 ensures remaining clocks is never 1
|
||||
delay_nosave_ (((n - 15) & $FFFF) + 2)
|
||||
.elseif n > 255+27
|
||||
lda #>(n - 15)
|
||||
jsr delay_256a_11_clocks_
|
||||
; +2 ensures remaining clocks is never 1
|
||||
delay_nosave_ (<(n - 15) + 2)
|
||||
.elseif n >= 27
|
||||
lda #<(n - 27)
|
||||
jsr delay_a_25_clocks
|
||||
.else
|
||||
delay_short_ n
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
.macro delay_ n
|
||||
.if n > max_short_delay
|
||||
php
|
||||
pha
|
||||
delay_nosave_ (n - 14)
|
||||
pla
|
||||
plp
|
||||
.else
|
||||
delay_short_ n
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
BIN
src/test_roms/common/devcart.bin
Normal file
BIN
src/test_roms/common/devcart.bin
Normal file
Binary file not shown.
245
src/test_roms/common/instr_test.inc
Normal file
245
src/test_roms/common/instr_test.inc
Normal file
@@ -0,0 +1,245 @@
|
||||
REGION_FREE = 1
|
||||
.include "shell.inc"
|
||||
.include "crc_fast.s"
|
||||
|
||||
zp_byte in_p
|
||||
zp_byte in_a
|
||||
zp_byte in_x
|
||||
zp_byte in_y
|
||||
zp_byte in_s
|
||||
|
||||
.macro entry opcode,name,crc_0,crc_1,crc_2,crc_3
|
||||
jsr reset_crc
|
||||
lda #opcode
|
||||
sta opcode_byte - testcase_entry + testcase_instance
|
||||
jsr run_testcase
|
||||
; testcase_0 OPERANDS + 4,{testcase opcode}
|
||||
lda checksum
|
||||
cmp #crc_0
|
||||
jne fail
|
||||
lda checksum+1
|
||||
cmp #crc_1
|
||||
jne fail
|
||||
lda checksum+2
|
||||
cmp #crc_2
|
||||
jne fail
|
||||
lda checksum+3
|
||||
cmp #crc_3
|
||||
jne fail
|
||||
.endmacro
|
||||
|
||||
testcase_entry:
|
||||
lda in_p
|
||||
pha
|
||||
lda in_a
|
||||
ldx in_x
|
||||
ldy in_y
|
||||
plp
|
||||
opcode_byte: .byte 0
|
||||
.if OPERANDS > 0
|
||||
; .out .sprintf(" 1: %s", .string(op_1))
|
||||
.byte 0
|
||||
.endif
|
||||
.if OPERANDS > 1
|
||||
; .out .sprintf(" 2: %s", .string(op_2))
|
||||
.byte 0
|
||||
.endif
|
||||
php
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
; Stack: <y, x, a, p>
|
||||
pla
|
||||
jsr update_crc_fast
|
||||
pla
|
||||
jsr update_crc_fast
|
||||
pla
|
||||
jsr update_crc_fast
|
||||
pla
|
||||
jsr update_crc_fast
|
||||
rts
|
||||
testcase_entry_size = * - testcase_entry
|
||||
|
||||
; .macro testcase opcode, reg_p, reg_a, reg_x, reg_y, op_1, op_2
|
||||
; jsr crc_calc
|
||||
; .out .sprintf("OP: %s", .string(opcode))
|
||||
; .out .sprintf(" S: %s", .string(reg_p))
|
||||
; .out .sprintf(" A: %s", .string(reg_a))
|
||||
; .out .sprintf(" X: %s", .string(reg_x))
|
||||
; .out .sprintf(" Y: %s", .string(reg_y))
|
||||
; .endmacro
|
||||
; .macro testcase_6 opcode, ops
|
||||
; testcase opcode, ops
|
||||
; ; .mid(0, 1, ops), .mid(1, 1, ops), .mid(2, 1, ops), .mid(3, 1, ops), .mid(4, 1, ops), .mid(5, 1, ops)
|
||||
; .endmacro
|
||||
; .macro testcase_0 count,ins
|
||||
; .if count > 0
|
||||
; testcase_0 count - 1, {ins,0}
|
||||
; testcase_0 count - 1, {ins,1}
|
||||
; testcase_0 count - 1, {ins,2}
|
||||
; testcase_0 count - 1, {ins,$40}
|
||||
; testcase_0 count - 1, {ins,$7F}
|
||||
; testcase_0 count - 1, {ins,$80}
|
||||
; testcase_0 count - 1, {ins,$81}
|
||||
; testcase_0 count - 1, {ins,$FF}
|
||||
; ; .byte 0,1,2,$40,$7F,$80,$81,$FF
|
||||
; .else
|
||||
; ins
|
||||
; .endif
|
||||
; .endmacro
|
||||
.pushseg
|
||||
.segment "BSS"
|
||||
testcase_instance: .res testcase_entry_size
|
||||
.popseg
|
||||
|
||||
testcase_init:
|
||||
ldx #0
|
||||
@loop: lda testcase_entry,x
|
||||
sta testcase_instance,x
|
||||
inx
|
||||
cpx #testcase_entry_size
|
||||
bmi @loop
|
||||
rts
|
||||
|
||||
; values:
|
||||
; .byte 0,1,2,$40,$7F,$80,$81,$FF
|
||||
; values_size = * - values
|
||||
; .byte 0,1,2,$40,$7F,$80,$81,$FF
|
||||
.macro increment pos, next, done
|
||||
lda pos
|
||||
cmp #0
|
||||
bne @a_1
|
||||
lda #1
|
||||
sta pos
|
||||
jmp done
|
||||
@a_1: cmp #1
|
||||
bne @a_2
|
||||
lda #2
|
||||
sta pos
|
||||
jmp done
|
||||
@a_2: cmp #2
|
||||
bne @a_40
|
||||
lda #40
|
||||
sta pos
|
||||
jmp done
|
||||
@a_40: cmp #$40
|
||||
bne @a_7F
|
||||
lda #$7F
|
||||
sta pos
|
||||
jmp done
|
||||
@a_7F: cmp #$7F
|
||||
bne @a_80
|
||||
lda #$80
|
||||
sta pos
|
||||
jmp done
|
||||
@a_80: cmp #$80
|
||||
bne @a_81
|
||||
lda #$81
|
||||
sta pos
|
||||
jmp done
|
||||
@a_81: cmp #$81
|
||||
bne @a_FF
|
||||
lda #$FF
|
||||
sta pos
|
||||
jmp done
|
||||
@a_FF: cmp #$FF
|
||||
lda #0
|
||||
sta pos
|
||||
jmp next
|
||||
.endmacro
|
||||
run_testcase:
|
||||
increment in_a, increment_x, done
|
||||
increment_x:
|
||||
increment in_x, increment_y, done
|
||||
increment_y:
|
||||
increment in_y, increment_p, done
|
||||
increment_p:
|
||||
increment in_p, ret, done
|
||||
done:
|
||||
jsr testcase_instance
|
||||
jmp run_testcase
|
||||
ret:
|
||||
rts
|
||||
|
||||
; Defines instruction to test
|
||||
|
||||
; Values set_paxyso sets registers to. Set
|
||||
; before calling test_values, which can then
|
||||
; overwrite them if desired.
|
||||
|
||||
; Temporary space for check_paxyso
|
||||
zp_byte out_a
|
||||
zp_byte out_x
|
||||
zp_byte out_s
|
||||
|
||||
; Values to cycle through for registers
|
||||
|
||||
; ; Sets bytes on stack around in_s
|
||||
; .macro set_stack
|
||||
; ldx in_s
|
||||
; inx
|
||||
; inx
|
||||
; ldy #6
|
||||
; : txa
|
||||
; asl
|
||||
; eor #$A5
|
||||
; sta $100,x
|
||||
; dex
|
||||
; dey
|
||||
; bne :-
|
||||
; .endmacro
|
||||
|
||||
; ; Checksums bytes on stack around in_s
|
||||
; .macro check_stack
|
||||
; ldx in_s
|
||||
; inx
|
||||
; inx
|
||||
; ldy #6
|
||||
; : lda $100,x
|
||||
; dex
|
||||
; jsr update_crc
|
||||
; dey
|
||||
; bne :-
|
||||
; .endmacro
|
||||
|
||||
; ; Sets P, A, X, Y, S, and operand
|
||||
; .macro set_paxyso
|
||||
; ldx in_s
|
||||
; txs
|
||||
; lda values,y
|
||||
; sta operand
|
||||
; lda in_p
|
||||
; pha
|
||||
; lda in_a
|
||||
; ldx in_x
|
||||
; ldy in_y
|
||||
; plp
|
||||
; .endmacro
|
||||
|
||||
; ; Checksums P, A, X, Y, S, and operand
|
||||
; .macro check_paxyso
|
||||
; php
|
||||
; sta out_a
|
||||
; pla
|
||||
|
||||
; stx out_x
|
||||
; tsx
|
||||
; stx out_s
|
||||
; ldx saved_s
|
||||
; txs
|
||||
|
||||
; cld
|
||||
; jsr update_crc_fast
|
||||
; lda out_a
|
||||
; jsr update_crc_fast
|
||||
; lda out_x
|
||||
; jsr update_crc_fast
|
||||
; tya
|
||||
; jsr update_crc_fast
|
||||
; lda out_s
|
||||
; jsr update_crc_fast
|
||||
; lda operand
|
||||
; jsr update_crc_fast
|
||||
; .endmacro
|
||||
199
src/test_roms/common/instr_test_end.s
Normal file
199
src/test_roms/common/instr_test_end.s
Normal file
@@ -0,0 +1,199 @@
|
||||
; Offset of current instruction
|
||||
zp_byte instrs_idx
|
||||
|
||||
zp_byte failed_count
|
||||
|
||||
main:
|
||||
; Stack slightly lower than top
|
||||
ldx #$A2
|
||||
txs
|
||||
|
||||
jsr init_crc_fast
|
||||
|
||||
; Test each instruction
|
||||
lda #0
|
||||
@loop: sta instrs_idx
|
||||
tay
|
||||
|
||||
jsr reset_crc
|
||||
lda instrs,y
|
||||
jsr test_instr
|
||||
jsr check_result
|
||||
|
||||
lda instrs_idx
|
||||
clc
|
||||
adc #4
|
||||
cmp #instrs_size
|
||||
bne @loop
|
||||
|
||||
.ifdef BUILD_DEVCART
|
||||
lda #0
|
||||
jmp exit
|
||||
.endif
|
||||
|
||||
lda failed_count
|
||||
jne test_failed
|
||||
jmp tests_passed
|
||||
|
||||
; Check result of test
|
||||
check_result:
|
||||
.ifdef BUILD_DEVCART
|
||||
; Print correct CRC
|
||||
jsr crc_off
|
||||
print_str ".dword $"
|
||||
ldx #0
|
||||
: lda checksum,x
|
||||
jsr print_hex
|
||||
inx
|
||||
cpx #4
|
||||
bne :-
|
||||
jsr print_newline
|
||||
jsr crc_on
|
||||
.else
|
||||
; Verify CRC
|
||||
ldx #3
|
||||
ldy instrs_idx
|
||||
: lda checksum,x
|
||||
cmp correct_checksums,y
|
||||
bne @wrong
|
||||
iny
|
||||
dex
|
||||
bpl :-
|
||||
.endif
|
||||
rts
|
||||
|
||||
; Print failed opcode and name
|
||||
@wrong:
|
||||
ldy instrs_idx
|
||||
lda instrs,y
|
||||
jsr print_a
|
||||
jsr play_byte
|
||||
lda instrs+2,y
|
||||
sta addr
|
||||
lda instrs+3,y
|
||||
sta addr+1
|
||||
jsr print_str_addr
|
||||
jsr print_newline
|
||||
inc failed_count
|
||||
rts
|
||||
|
||||
; Place where instruction is executed
|
||||
instr = $3A0
|
||||
|
||||
; Tests instr A
|
||||
test_instr:
|
||||
sta instr
|
||||
jsr avoid_silent_nsf
|
||||
|
||||
; Copy rest of template
|
||||
ldx #instr_template_size - 1
|
||||
: lda instr_template,x
|
||||
sta instr,x
|
||||
dex
|
||||
bne :-
|
||||
|
||||
; Disable and be sure APU IRQs are clear, since
|
||||
; I flag gets cleared during testing.
|
||||
setb SNDMODE,$C0
|
||||
setb $4010,0
|
||||
nop
|
||||
lda SNDCHN
|
||||
|
||||
; Default stack
|
||||
lda #$90
|
||||
sta in_s
|
||||
|
||||
; Test with different flags
|
||||
lda #$00
|
||||
jsr test_flags
|
||||
lda #$FF
|
||||
jsr test_flags
|
||||
|
||||
rts
|
||||
|
||||
; Position in operand table
|
||||
zp_byte operand_idx
|
||||
|
||||
test_flags:
|
||||
sta in_p
|
||||
|
||||
ldy #values_size-1
|
||||
: sty operand_idx
|
||||
|
||||
lda values,y
|
||||
sta in_a
|
||||
|
||||
lda values+1,y
|
||||
sta in_x
|
||||
|
||||
lda values+2,y
|
||||
sta in_y
|
||||
|
||||
jsr test_values
|
||||
|
||||
ldy operand_idx
|
||||
dey
|
||||
bpl :-
|
||||
|
||||
rts
|
||||
|
||||
.ifndef values2
|
||||
values2 = values
|
||||
values2_size = values_size
|
||||
.endif
|
||||
|
||||
.macro test_normal
|
||||
zp_byte a_idx
|
||||
zp_byte saved_s
|
||||
|
||||
tsx
|
||||
stx saved_s
|
||||
|
||||
set_stack
|
||||
|
||||
ldy #values2_size-1
|
||||
inner: sty a_idx
|
||||
|
||||
lda values2,y
|
||||
sta operand
|
||||
|
||||
set_in
|
||||
|
||||
; For debugging
|
||||
.if 0
|
||||
; P A X Y S O (z,x) (z),y
|
||||
jsr print_p
|
||||
jsr print_a
|
||||
jsr print_x
|
||||
jsr print_y
|
||||
jsr print_s
|
||||
lda operand
|
||||
jsr print_a
|
||||
.ifdef address
|
||||
lda (address,x)
|
||||
jsr print_a
|
||||
lda (address),y
|
||||
jsr print_a
|
||||
.else
|
||||
lda operand,x
|
||||
jsr print_a
|
||||
lda operand,y
|
||||
jsr print_a
|
||||
.endif
|
||||
jsr print_newline
|
||||
.endif
|
||||
|
||||
jmp instr
|
||||
instr_done:
|
||||
|
||||
check_out
|
||||
|
||||
ldy a_idx
|
||||
dey
|
||||
bpl inner
|
||||
|
||||
check_stack
|
||||
|
||||
ldx saved_s
|
||||
txs
|
||||
.endmacro
|
||||
30
src/test_roms/common/joysticks.s
Normal file
30
src/test_roms/common/joysticks.s
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
JOY_RIGHT_MASK = %00000001
|
||||
JOY_LEFT_MASK = %00000010
|
||||
JOY_DOWN_MASK = %00000100
|
||||
JOY_UP_MASK = %00001000
|
||||
JOY_START_MASK = %00010000
|
||||
JOY_SELECT_MASK = %00100000
|
||||
JOY_B_MASK = %01000000
|
||||
JOY_A_MASK = %10000000
|
||||
|
||||
zp_res JOYTEMP
|
||||
; Reads joy_stick 1 into a (and JOYTEMP)
|
||||
read_joy1:
|
||||
; Init joysticks for reading
|
||||
lda #$01
|
||||
sta JOY1
|
||||
lda #$00
|
||||
sta JOY1
|
||||
|
||||
; Read JOY1
|
||||
.repeat 8
|
||||
asl A
|
||||
sta JOYTEMP
|
||||
lda JOY1
|
||||
and #$01
|
||||
ora JOYTEMP
|
||||
.endrepeat
|
||||
sta JOYTEMP
|
||||
rts
|
||||
|
||||
169
src/test_roms/common/macros.inc
Normal file
169
src/test_roms/common/macros.inc
Normal file
@@ -0,0 +1,169 @@
|
||||
; jxx equivalents to bxx
|
||||
.macpack longbranch
|
||||
|
||||
; blt, bge equivalents to bcc, bcs
|
||||
.define blt bcc
|
||||
.define bge bcs
|
||||
.define jge jcs
|
||||
.define jlt jcc
|
||||
|
||||
; Puts data in another segment
|
||||
.macro seg_data seg,data
|
||||
.pushseg
|
||||
.segment seg
|
||||
data
|
||||
.popseg
|
||||
.endmacro
|
||||
|
||||
; Reserves size bytes in zeropage/bss for name.
|
||||
; If size is omitted, reserves one byte.
|
||||
.macro zp_res name,size
|
||||
.ifblank size
|
||||
zp_res name,1
|
||||
.else
|
||||
seg_data "ZEROPAGE",{name: .res size}
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
.macro bss_res name,size
|
||||
.ifblank size
|
||||
bss_res name,1
|
||||
.else
|
||||
seg_data "BSS",{name: .res size}
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
.macro nv_res name,size
|
||||
.ifblank size
|
||||
nv_res name,1
|
||||
.else
|
||||
seg_data "NVRAM",{name: .res size}
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
; Reserves one byte in zeropage for name (very common)
|
||||
.macro zp_byte name
|
||||
seg_data "ZEROPAGE",{name: .res 1}
|
||||
.endmacro
|
||||
|
||||
; Passes constant data to routine in addr
|
||||
; Preserved: A, X, Y
|
||||
.macro jsr_with_addr routine,data
|
||||
.local Addr
|
||||
pha
|
||||
lda #<Addr
|
||||
sta addr
|
||||
lda #>Addr
|
||||
sta addr+1
|
||||
pla
|
||||
jsr routine
|
||||
seg_data "RODATA",{Addr: data}
|
||||
.endmacro
|
||||
|
||||
; Calls routine multiple times, with A having the
|
||||
; value 'start' the first time, 'start+step' the
|
||||
; second time, up to 'end' for the last time.
|
||||
.macro for_loop routine,start,end,step
|
||||
lda #start
|
||||
: pha
|
||||
jsr routine
|
||||
pla
|
||||
clc
|
||||
adc #step
|
||||
cmp #<((end)+(step))
|
||||
bne :-
|
||||
.endmacro
|
||||
|
||||
; Calls routine n times. The value of A in the routine
|
||||
; counts from 0 to n-1.
|
||||
.macro loop_n_times routine,n
|
||||
for_loop routine,0,n-1,+1
|
||||
.endmacro
|
||||
|
||||
; Same as for_loop, except uses 16-bit value in YX.
|
||||
; -256 <= step <= 255
|
||||
.macro for_loop16 routine,start,end,step
|
||||
.if (step) < -256 || (step) > 255
|
||||
.error "Step must be within -256 to 255"
|
||||
.endif
|
||||
ldy #>(start)
|
||||
lda #<(start)
|
||||
: tax
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
jsr routine
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
clc
|
||||
adc #step
|
||||
.if (step) > 0
|
||||
bcc :+
|
||||
iny
|
||||
.else
|
||||
bcs :+
|
||||
dey
|
||||
.endif
|
||||
: cmp #<((end)+(step))
|
||||
bne :--
|
||||
cpy #>((end)+(step))
|
||||
bne :--
|
||||
.endmacro
|
||||
|
||||
; Copies byte from in to out
|
||||
; Preserved: X, Y
|
||||
.macro mov out, in
|
||||
lda in
|
||||
sta out
|
||||
.endmacro
|
||||
|
||||
; Stores byte at addr
|
||||
; Preserved: X, Y
|
||||
.macro setb addr, byte
|
||||
lda #byte
|
||||
sta addr
|
||||
.endmacro
|
||||
|
||||
; Stores word at addr
|
||||
; Preserved: X, Y
|
||||
.macro setw addr, word
|
||||
lda #<(word)
|
||||
sta addr
|
||||
lda #>(word)
|
||||
sta addr+1
|
||||
.endmacro
|
||||
|
||||
; Loads XY with 16-bit immediate or value at address
|
||||
.macro ldxy Arg
|
||||
.if .match( .left( 1, {Arg} ), # )
|
||||
ldy #<(.right( .tcount( {Arg} )-1, {Arg} ))
|
||||
ldx #>(.right( .tcount( {Arg} )-1, {Arg} ))
|
||||
.else
|
||||
ldy (Arg)
|
||||
ldx (Arg)+1
|
||||
.endif
|
||||
.endmacro
|
||||
|
||||
; Increments word at Addr and sets Z flag appropriately
|
||||
; Preserved: A, X, Y
|
||||
.macro incw Addr
|
||||
.local @incw_skip ; doesn't work, so HOW THE HELL DO YOU MAKE A LOCAL LABEL IN A MACRO THAT DOESN"T DISTURB INVOKING CODE< HUH?????? POS
|
||||
inc Addr
|
||||
bne @incw_skip
|
||||
inc Addr+1
|
||||
@incw_skip:
|
||||
.endmacro
|
||||
|
||||
; Increments XY as 16-bit register, in CONSTANT time.
|
||||
; Z flag set based on entire result.
|
||||
; Preserved: A
|
||||
; Time: 7 clocks
|
||||
.macro inxy
|
||||
iny ; 2
|
||||
beq *+4 ; 3
|
||||
; -1
|
||||
bne *+3 ; 3
|
||||
; -1
|
||||
inx ; 2
|
||||
.endmacro
|
||||
37
src/test_roms/common/minimal_ppu.s
Normal file
37
src/test_roms/common/minimal_ppu.s
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
init_ppu_and_wait:
|
||||
; 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
|
||||
lda #$00
|
||||
infinite_loop:
|
||||
jmp infinite_loop
|
||||
|
||||
.macro nmi_start
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
.endmacro
|
||||
.macro nmi_end
|
||||
lda #%00001110
|
||||
sta PPUMASK ; Enable rendering
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
.endmacro
|
||||
55
src/test_roms/common/neshw.inc
Normal file
55
src/test_roms/common/neshw.inc
Normal file
@@ -0,0 +1,55 @@
|
||||
; NES I/O locations and masks
|
||||
|
||||
; Clocks per second
|
||||
.ifndef CLOCK_RATE
|
||||
CLOCK_RATE = 1789773 ; NTSC
|
||||
; CLOCK_RATE = 1662607 ; PAL
|
||||
.endif
|
||||
|
||||
.ifndef BUILD_NSF
|
||||
|
||||
; PPU
|
||||
PPUCTRL = $2000
|
||||
PPUMASK = $2001
|
||||
PPUSTATUS = $2002
|
||||
SPRADDR = $2003
|
||||
SPRDATA = $2004
|
||||
PPUSCROLL = $2005
|
||||
PPUADDR = $2006
|
||||
PPUDATA = $2007
|
||||
SPRDMA = $4014
|
||||
|
||||
PPUCTRL_NMI = $80
|
||||
PPUMASK_BG0 = $0A
|
||||
PPUCTRL_8X8 = $00
|
||||
PPUCTRL_8X16 = $20
|
||||
PPUMASK_SPR = $14
|
||||
PPUMASK_BG0CLIP = $08
|
||||
|
||||
.endif
|
||||
|
||||
; APU
|
||||
SNDCHN = $4015
|
||||
JOY1 = $4016
|
||||
JOY2 = $4017
|
||||
SNDMODE = $4017
|
||||
|
||||
PULSE_CH1_DLCV = $4000
|
||||
PULSE_CH1_SWEEP = $4001
|
||||
PULSE_CH1_TLOW = $4002
|
||||
PULSE_CH1_LCTH = $4003
|
||||
PULSE_CH2_DLCV = $4004
|
||||
PULSE_CH2_SWEEP = $4005
|
||||
PULSE_CH2_TLOW = $4006
|
||||
PULSE_CH2_LCTH = $4007
|
||||
TRIANGLE_LINEAR_C = $4008
|
||||
TRIANGLE_TIMER_LOW = $400A
|
||||
TRIANGLE_LEN_T_HIGH = $400B
|
||||
|
||||
SNDMODE_NOIRQ = $40
|
||||
|
||||
.if CLOCK_RATE = 1789773
|
||||
PPU_FRAMELEN = 29781
|
||||
.elseif CLOCK_RATE = 1662607
|
||||
PPU_FRAMELEN = 33248
|
||||
.endif
|
||||
142
src/test_roms/common/ppu.s
Normal file
142
src/test_roms/common/ppu.s
Normal file
@@ -0,0 +1,142 @@
|
||||
; PPU utilities
|
||||
|
||||
bss_res ppu_not_present
|
||||
|
||||
; Sets PPUADDR to w
|
||||
; Preserved: X, Y
|
||||
.macro set_ppuaddr w
|
||||
bit PPUSTATUS
|
||||
setb PPUADDR,>w
|
||||
setb PPUADDR,<w
|
||||
.endmacro
|
||||
|
||||
|
||||
; Delays by no more than n scanlines
|
||||
.macro delay_scanlines n
|
||||
.if CLOCK_RATE <> 1789773
|
||||
.error "Currently only supports NTSC"
|
||||
.endif
|
||||
delay ((n)*341)/3
|
||||
.endmacro
|
||||
|
||||
|
||||
; Waits for VBL then disables PPU rendering.
|
||||
; Preserved: A, X, Y
|
||||
disable_rendering:
|
||||
pha
|
||||
jsr wait_vbl_optional
|
||||
setb PPUMASK,0
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Fills first nametable with $00
|
||||
; Preserved: Y
|
||||
clear_nametable:
|
||||
ldx #$20
|
||||
bne clear_nametable_
|
||||
|
||||
clear_nametable2:
|
||||
ldx #$24
|
||||
clear_nametable_:
|
||||
lda #0
|
||||
jsr fill_screen_
|
||||
|
||||
; Clear pattern table
|
||||
ldx #64
|
||||
: sta PPUDATA
|
||||
dex
|
||||
bne :-
|
||||
rts
|
||||
|
||||
|
||||
; Fills screen with tile A
|
||||
; Preserved: A, Y
|
||||
fill_screen:
|
||||
ldx #$20
|
||||
bne fill_screen_
|
||||
|
||||
; Same as fill_screen, but fills other nametable
|
||||
fill_screen2:
|
||||
ldx #$24
|
||||
fill_screen_:
|
||||
stx PPUADDR
|
||||
ldx #$00
|
||||
stx PPUADDR
|
||||
ldx #240
|
||||
: sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne :-
|
||||
rts
|
||||
|
||||
|
||||
; Fills palette with $0F
|
||||
; Preserved: Y
|
||||
clear_palette:
|
||||
set_ppuaddr $3F00
|
||||
ldx #$20
|
||||
lda #$0F
|
||||
: sta PPUDATA
|
||||
dex
|
||||
bne :-
|
||||
|
||||
|
||||
; Fills OAM with $FF
|
||||
; Preserved: Y
|
||||
clear_oam:
|
||||
lda #$FF
|
||||
|
||||
; Fills OAM with A
|
||||
; Preserved: A, Y
|
||||
fill_oam:
|
||||
ldx #0
|
||||
stx SPRADDR
|
||||
: sta SPRDATA
|
||||
dex
|
||||
bne :-
|
||||
rts
|
||||
|
||||
|
||||
; Initializes wait_vbl_optional. Must be called before
|
||||
; using it.
|
||||
.align 32
|
||||
init_wait_vbl:
|
||||
; Wait for VBL flag to be set, or ~60000
|
||||
; clocks (2 frames) to pass
|
||||
ldy #24
|
||||
ldx #1
|
||||
bit PPUSTATUS
|
||||
: bit PPUSTATUS
|
||||
bmi @set
|
||||
dex
|
||||
bne :-
|
||||
dey
|
||||
bpl :-
|
||||
@set:
|
||||
; Be sure flag didn't stay set (in case
|
||||
; PPUSTATUS always has high bit set)
|
||||
tya
|
||||
ora PPUSTATUS
|
||||
sta ppu_not_present
|
||||
rts
|
||||
|
||||
|
||||
; Same as wait_vbl, but returns immediately if PPU
|
||||
; isn't working or doesn't support VBL flag
|
||||
; Preserved: A, X, Y
|
||||
.align 16
|
||||
wait_vbl_optional:
|
||||
bit ppu_not_present
|
||||
bmi :++
|
||||
; FALL THROUGH
|
||||
|
||||
; Clears VBL flag then waits for it to be set.
|
||||
; Preserved: A, X, Y
|
||||
wait_vbl:
|
||||
bit PPUSTATUS
|
||||
: bit PPUSTATUS
|
||||
bpl :-
|
||||
: rts
|
||||
235
src/test_roms/common/print.s
Normal file
235
src/test_roms/common/print.s
Normal file
@@ -0,0 +1,235 @@
|
||||
; Prints values in various ways to output,
|
||||
; including numbers and strings.
|
||||
|
||||
newline = 10
|
||||
|
||||
zp_byte print_temp_
|
||||
|
||||
; Prints indicated register to console as two hex
|
||||
; chars and space
|
||||
; Preserved: A, X, Y, flags
|
||||
print_a:
|
||||
php
|
||||
pha
|
||||
print_reg_:
|
||||
jsr print_hex
|
||||
lda #' '
|
||||
jsr print_char_
|
||||
pla
|
||||
plp
|
||||
rts
|
||||
|
||||
print_x:
|
||||
php
|
||||
pha
|
||||
txa
|
||||
jmp print_reg_
|
||||
|
||||
print_y:
|
||||
php
|
||||
pha
|
||||
tya
|
||||
jmp print_reg_
|
||||
|
||||
print_p:
|
||||
php
|
||||
pha
|
||||
php
|
||||
pla
|
||||
jmp print_reg_
|
||||
|
||||
print_s:
|
||||
php
|
||||
pha
|
||||
txa
|
||||
tsx
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
jsr print_x
|
||||
tax
|
||||
pla
|
||||
plp
|
||||
rts
|
||||
|
||||
|
||||
; Prints A as two hex characters, NO space after
|
||||
; Preserved: A, X, Y
|
||||
print_hex:
|
||||
jsr update_crc
|
||||
|
||||
pha
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
jsr @nibble
|
||||
pla
|
||||
|
||||
pha
|
||||
and #$0F
|
||||
jsr @nibble
|
||||
pla
|
||||
rts
|
||||
|
||||
@nibble:
|
||||
cmp #10
|
||||
blt @digit
|
||||
adc #6;+1 since carry is set
|
||||
@digit: adc #'0'
|
||||
jmp print_char_
|
||||
|
||||
|
||||
; Prints character and updates checksum UNLESS
|
||||
; it's a newline.
|
||||
; Preserved: A, X, Y
|
||||
print_char:
|
||||
cmp #newline
|
||||
beq :+
|
||||
jsr update_crc
|
||||
: pha
|
||||
jsr print_char_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Prints space. Does NOT update checksum.
|
||||
; Preserved: A, X, Y
|
||||
print_space:
|
||||
pha
|
||||
lda #' '
|
||||
jsr print_char_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Advances to next line. Does NOT update checksum.
|
||||
; Preserved: A, X, Y
|
||||
print_newline:
|
||||
pha
|
||||
lda #newline
|
||||
jsr print_char_
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Prints string
|
||||
; Preserved: A, X, Y
|
||||
.macro print_str str,str2
|
||||
jsr print_str_
|
||||
.byte str
|
||||
.ifnblank str2
|
||||
.byte str2
|
||||
.endif
|
||||
.byte 0
|
||||
.endmacro
|
||||
|
||||
|
||||
print_str_:
|
||||
sta print_temp_
|
||||
|
||||
pla
|
||||
sta addr
|
||||
pla
|
||||
sta addr+1
|
||||
|
||||
jsr inc_addr
|
||||
jsr print_str_addr
|
||||
|
||||
lda print_temp_
|
||||
jmp (addr)
|
||||
|
||||
|
||||
; Prints string at addr and leaves addr pointing to
|
||||
; byte AFTER zero terminator.
|
||||
; Preserved: A, X, Y
|
||||
print_str_addr:
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
|
||||
ldy #0
|
||||
beq :+ ; always taken
|
||||
@loop: jsr print_char
|
||||
jsr inc_addr
|
||||
: lda (addr),y
|
||||
bne @loop
|
||||
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
; FALL THROUGH
|
||||
|
||||
; Increments 16-bit value in addr.
|
||||
; Preserved: A, X, Y
|
||||
inc_addr:
|
||||
inc addr
|
||||
beq :+
|
||||
rts
|
||||
: inc addr+1
|
||||
rts
|
||||
|
||||
|
||||
; Prints A as 1-3 digit decimal value, NO space after.
|
||||
; Preserved: A, X, Y
|
||||
print_dec:
|
||||
pha
|
||||
sta print_temp_
|
||||
txa
|
||||
pha
|
||||
lda print_temp_
|
||||
|
||||
; Hundreds
|
||||
cmp #10
|
||||
blt @ones
|
||||
cmp #100
|
||||
blt @tens
|
||||
ldx #'0'-1
|
||||
: inx
|
||||
sbc #100
|
||||
bge :-
|
||||
adc #100
|
||||
jsr @digit
|
||||
|
||||
; Tens
|
||||
@tens: sec
|
||||
ldx #'0'-1
|
||||
: inx
|
||||
sbc #10
|
||||
bge :-
|
||||
adc #10
|
||||
jsr @digit
|
||||
|
||||
; Ones
|
||||
@ones: ora #'0'
|
||||
jsr print_char
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rts
|
||||
|
||||
; Print a single digit
|
||||
@digit: pha
|
||||
txa
|
||||
jsr print_char
|
||||
pla
|
||||
rts
|
||||
|
||||
|
||||
; Prints one of two characters based on condition.
|
||||
; SEC; print_cc bcs,'C','-' prints 'C'.
|
||||
; Preserved: A, X, Y, flags
|
||||
.macro print_cc cond,yes,no
|
||||
; Avoids labels since they're not local
|
||||
; to macros in ca65.
|
||||
php
|
||||
pha
|
||||
cond *+6
|
||||
lda #no
|
||||
bne *+4
|
||||
lda #yes
|
||||
jsr print_char
|
||||
pla
|
||||
plp
|
||||
.endmacro
|
||||
64
src/test_roms/common/run_at_reset.s
Normal file
64
src/test_roms/common/run_at_reset.s
Normal file
@@ -0,0 +1,64 @@
|
||||
; Keeps track of number of times reset, and prompts user.
|
||||
|
||||
power_flag_value = $42
|
||||
|
||||
nv_res power_flag_
|
||||
nv_res num_resets_
|
||||
|
||||
; Out: A = number of times NES has been reset since turned on
|
||||
; Preserved: X, Y
|
||||
num_resets:
|
||||
lda power_flag_
|
||||
cmp #power_flag_value
|
||||
bne :+
|
||||
lda num_resets_
|
||||
rts
|
||||
: lda #0
|
||||
rts
|
||||
|
||||
|
||||
; Prompts user to press reset after message disappears,
|
||||
; then hides message, increments reset count, and asks
|
||||
; emulator to reset NES.
|
||||
; Preserved: X, Y
|
||||
prompt_to_reset:
|
||||
print_str {newline,newline,"Press reset AFTER this message",newline,"disappears"}
|
||||
|
||||
; Add "again" if this isn't first requested reset
|
||||
jsr num_resets
|
||||
beq :+
|
||||
print_str ", again"
|
||||
:
|
||||
; Show for a few seconds
|
||||
print_str {newline,newline,newline}
|
||||
jsr console_show
|
||||
delay_msec 1000
|
||||
delay_msec 1000
|
||||
|
||||
jsr inc_reset_count
|
||||
|
||||
; Tell emulator that NES should be reset now
|
||||
lda #$81
|
||||
jsr set_final_result
|
||||
|
||||
jsr console_hide
|
||||
rts
|
||||
|
||||
|
||||
; Increments reset count and marks it as valid
|
||||
; Preserved: X, Y
|
||||
inc_reset_count:
|
||||
jsr num_resets
|
||||
clc
|
||||
adc #1
|
||||
bcc :+
|
||||
lda #$FF ; don't wrap around
|
||||
: sta num_resets_
|
||||
setb power_flag_,power_flag_value
|
||||
rts
|
||||
|
||||
|
||||
; Waits in infinite loop for reset
|
||||
; Preserved: A, X, Y, flags
|
||||
wait_reset:
|
||||
jmp wait_reset
|
||||
26
src/test_roms/common/shell.inc
Normal file
26
src/test_roms/common/shell.inc
Normal file
@@ -0,0 +1,26 @@
|
||||
; Included at beginning of program
|
||||
|
||||
.macro stp
|
||||
.byte $02
|
||||
.endmacro
|
||||
.include "macros.inc"
|
||||
.include "neshw.inc"
|
||||
|
||||
; .ifdef CUSTOM_PREFIX
|
||||
; .include "custom_prefix.s"
|
||||
; .endif
|
||||
|
||||
; ; Devcart
|
||||
; .ifdef BUILD_DEVCART
|
||||
; .include "build_devcart.s"
|
||||
; .endif
|
||||
|
||||
; NES internal RAM
|
||||
; .ifdef BUILD_NOCART
|
||||
; .include "build_nocart.s"
|
||||
; .endif
|
||||
|
||||
; NES ROM (default)
|
||||
.ifndef SHELL_INCLUDED
|
||||
.include "build_rom.s"
|
||||
.endif
|
||||
384
src/test_roms/common/shell.s
Normal file
384
src/test_roms/common/shell.s
Normal file
@@ -0,0 +1,384 @@
|
||||
; Common routines and runtime
|
||||
|
||||
; Detect inclusion loops (otherwise ca65 goes crazy)
|
||||
.ifdef SHELL_INCLUDED
|
||||
.error "shell.s included twice"
|
||||
.end
|
||||
.endif
|
||||
SHELL_INCLUDED = 1
|
||||
|
||||
; Temporary variables that ANY routine might modify, so
|
||||
; only use them between routine calls.
|
||||
temp = <$A
|
||||
temp2 = <$B
|
||||
temp3 = <$C
|
||||
addr = <$E
|
||||
ptr = addr
|
||||
|
||||
; Move code to $E200 ($200 bytes for text output in devcarts
|
||||
; where WRAM is mirrored to $E000)
|
||||
.segment "CODE"
|
||||
.res $2200
|
||||
|
||||
; Put shell code after user code, so user code is in more
|
||||
; consistent environment
|
||||
.segment "CODE2"
|
||||
|
||||
; Any user code which runs off end might end up here,
|
||||
; so catch that mistake.
|
||||
nop ; in case there was three-byte opcode before this
|
||||
nop
|
||||
jmp internal_error
|
||||
|
||||
;**** Common routines ****
|
||||
|
||||
.include "macros.inc"
|
||||
.include "neshw.inc"
|
||||
.include "delay.s"
|
||||
.include "print.s"
|
||||
.include "crc.s"
|
||||
.include "testing.s"
|
||||
|
||||
.ifdef NEED_CONSOLE
|
||||
.include "console.s"
|
||||
.else
|
||||
; Stubs so code doesn't have to care whether
|
||||
; console exists
|
||||
console_init:
|
||||
console_show:
|
||||
console_hide:
|
||||
console_print:
|
||||
console_flush:
|
||||
rts
|
||||
.endif
|
||||
|
||||
.ifndef CUSTOM_PRINT
|
||||
.include "text_out.s"
|
||||
|
||||
print_char_:
|
||||
jsr write_text_out
|
||||
jmp console_print
|
||||
|
||||
stop_capture:
|
||||
rts
|
||||
.endif
|
||||
|
||||
;**** Shell core ****
|
||||
|
||||
.ifndef CUSTOM_RESET
|
||||
reset:
|
||||
sei
|
||||
jmp std_reset
|
||||
.endif
|
||||
|
||||
|
||||
; Sets up hardware then runs main
|
||||
run_shell:
|
||||
sei
|
||||
cld ; unnecessary on NES, but might help on clone
|
||||
ldx #$FF
|
||||
txs
|
||||
jsr init_shell
|
||||
set_test $FF
|
||||
jmp run_main
|
||||
|
||||
|
||||
; Initializes shell
|
||||
init_shell:
|
||||
jsr clear_ram
|
||||
jsr init_wait_vbl ; waits for VBL once here,
|
||||
jsr wait_vbl_optional ; so only need to wait once more
|
||||
; jsr init_text_out
|
||||
jsr init_testing
|
||||
.byte $02
|
||||
jsr init_runtime
|
||||
jsr console_init
|
||||
rts
|
||||
|
||||
|
||||
; Runs main in consistent PPU/APU environment, then exits
|
||||
; with code 0
|
||||
run_main:
|
||||
jsr pre_main
|
||||
jsr main
|
||||
lda #0
|
||||
jmp exit
|
||||
|
||||
|
||||
; Sets up environment for main to run in
|
||||
pre_main:
|
||||
|
||||
.ifndef BUILD_NSF
|
||||
jsr disable_rendering
|
||||
setb PPUCTRL,0
|
||||
jsr clear_palette
|
||||
jsr clear_nametable
|
||||
jsr clear_nametable2
|
||||
jsr clear_oam
|
||||
.endif
|
||||
|
||||
lda #$34
|
||||
pha
|
||||
lda #0
|
||||
tax
|
||||
tay
|
||||
jsr wait_vbl_optional
|
||||
plp
|
||||
sta SNDMODE
|
||||
rts
|
||||
|
||||
|
||||
.ifndef CUSTOM_EXIT
|
||||
exit:
|
||||
.endif
|
||||
|
||||
; Reports result and ends program
|
||||
std_exit:
|
||||
.byte $02
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
ldx #0
|
||||
stx SNDCHN
|
||||
.ifndef BUILD_NSF
|
||||
stx PPUCTRL
|
||||
.endif
|
||||
|
||||
jsr report_result
|
||||
jmp post_exit
|
||||
|
||||
|
||||
; Reports final result code in A
|
||||
report_result:
|
||||
jsr :+
|
||||
jmp play_byte
|
||||
|
||||
: jsr print_newline
|
||||
jsr console_show
|
||||
|
||||
; 0: ""
|
||||
cmp #1
|
||||
bge :+
|
||||
rts
|
||||
:
|
||||
; 1: "Failed"
|
||||
bne :+
|
||||
print_str {"Failed",newline}
|
||||
rts
|
||||
|
||||
; n: "Failed #n"
|
||||
: print_str "Failed #"
|
||||
jsr print_dec
|
||||
jsr print_newline
|
||||
rts
|
||||
|
||||
;**** Other routines ****
|
||||
|
||||
; Reports internal error and exits program
|
||||
internal_error:
|
||||
print_str newline,"Internal error"
|
||||
lda #255
|
||||
jmp exit
|
||||
|
||||
|
||||
.import __NVRAM_LOAD__, __NVRAM_SIZE__
|
||||
|
||||
.macro fill_ram_ Begin, End
|
||||
.local Neg_size
|
||||
Neg_size = (Begin) - (End)
|
||||
ldxy #(Begin) - <Neg_size
|
||||
sty addr
|
||||
stx addr+1
|
||||
ldxy #Neg_size
|
||||
: sta (addr),y
|
||||
iny
|
||||
bne :-
|
||||
inc addr+1
|
||||
inx
|
||||
bne :-
|
||||
.endmacro
|
||||
|
||||
; Clears 0 through ($100+S), $200 through __NVRAM_LOAD__-1, and
|
||||
; __NVRAM_LOAD__+__NVRAM_SIZE__ through $7FF
|
||||
clear_ram:
|
||||
lda #0
|
||||
|
||||
bss_begin = $200
|
||||
|
||||
fill_ram_ bss_begin,__NVRAM_LOAD__
|
||||
fill_ram_ __NVRAM_LOAD__+__NVRAM_SIZE__,$800
|
||||
|
||||
; Zero-page
|
||||
tax
|
||||
: sta 0,x
|
||||
inx
|
||||
bne :-
|
||||
|
||||
; Stack below S
|
||||
tsx
|
||||
inx
|
||||
: dex
|
||||
sta $100,x
|
||||
bne :-
|
||||
|
||||
rts
|
||||
|
||||
|
||||
nv_res unused_nv_var ; to avoid size=0
|
||||
|
||||
; Clears nvram
|
||||
clear_nvram:
|
||||
lda #0
|
||||
fill_ram_ __NVRAM_LOAD__,__NVRAM_LOAD__+__NVRAM_SIZE__
|
||||
rts
|
||||
|
||||
|
||||
; Prints filename and newline, if available, otherwise nothing.
|
||||
; Preserved: A, X, Y
|
||||
print_filename:
|
||||
.ifdef FILENAME_KNOWN
|
||||
pha
|
||||
jsr print_newline
|
||||
setw addr,filename
|
||||
jsr print_str_addr
|
||||
jsr print_newline
|
||||
pla
|
||||
.endif
|
||||
rts
|
||||
|
||||
.pushseg
|
||||
.segment "RODATA"
|
||||
; Filename terminated with zero byte.
|
||||
filename:
|
||||
.ifdef FILENAME_KNOWN
|
||||
.incbin "ram:nes_temp"
|
||||
.endif
|
||||
.byte 0
|
||||
.popseg
|
||||
|
||||
|
||||
;**** ROM-specific ****
|
||||
.ifndef BUILD_NSF
|
||||
|
||||
.include "ppu.s"
|
||||
|
||||
avoid_silent_nsf:
|
||||
play_byte:
|
||||
rts
|
||||
|
||||
; Loads ASCII font into CHR RAM
|
||||
.macro load_ascii_chr
|
||||
bit PPUSTATUS
|
||||
setb PPUADDR,$00
|
||||
setb PPUADDR,$00
|
||||
setb addr,<ascii_chr
|
||||
ldx #>ascii_chr
|
||||
ldy #0
|
||||
@page:
|
||||
stx addr+1
|
||||
: lda (addr),y
|
||||
sta PPUDATA
|
||||
iny
|
||||
bne :-
|
||||
inx
|
||||
cpx #>ascii_chr_end
|
||||
bne @page
|
||||
.endmacro
|
||||
|
||||
; Disables interrupts and loops forever
|
||||
.ifndef CUSTOM_FOREVER
|
||||
forever:
|
||||
sei
|
||||
lda #0
|
||||
sta PPUCTRL
|
||||
: beq :-
|
||||
.res $10,$EA ; room for code to run loader
|
||||
.endif
|
||||
|
||||
|
||||
; Default NMI
|
||||
.ifndef CUSTOM_NMI
|
||||
zp_byte nmi_count
|
||||
|
||||
nmi:
|
||||
inc nmi_count
|
||||
rti
|
||||
|
||||
; Waits for NMI. Must be using NMI handler that increments
|
||||
; nmi_count, with NMI enabled.
|
||||
; Preserved: X, Y
|
||||
wait_nmi:
|
||||
lda nmi_count
|
||||
: cmp nmi_count
|
||||
beq :-
|
||||
rts
|
||||
.endif
|
||||
|
||||
|
||||
; Default IRQ
|
||||
.ifndef CUSTOM_IRQ
|
||||
irq:
|
||||
bit SNDCHN ; clear APU IRQ flag
|
||||
rti
|
||||
.endif
|
||||
|
||||
.endif
|
||||
|
||||
|
||||
; Reports A in binary as high and low tones, with
|
||||
; leading low tone for reference. Omits leading
|
||||
; zeroes. Doesn't hang if no APU is present.
|
||||
; Preserved: A, X, Y
|
||||
play_hex:
|
||||
pha
|
||||
|
||||
; Make low reference beep
|
||||
clc
|
||||
jsr @beep
|
||||
|
||||
; Remove high zero bits
|
||||
sec
|
||||
: rol a
|
||||
bcc :-
|
||||
|
||||
; Play remaining bits
|
||||
beq @zero
|
||||
: jsr @beep
|
||||
asl a
|
||||
bne :-
|
||||
@zero:
|
||||
|
||||
delay_msec 300
|
||||
pla
|
||||
rts
|
||||
|
||||
; Plays low/high beep based on carry
|
||||
; Preserved: A, X, Y
|
||||
@beep:
|
||||
pha
|
||||
|
||||
; Set up square
|
||||
lda #1
|
||||
sta SNDCHN
|
||||
sta $4001
|
||||
sta $4003
|
||||
adc #$FE ; period=$100 if carry, $1FF if none
|
||||
sta $4002
|
||||
|
||||
; Fade volume
|
||||
lda #$0F
|
||||
: ora #$30
|
||||
sta $4000
|
||||
delay_msec 8
|
||||
sec
|
||||
sbc #$31
|
||||
bpl :-
|
||||
|
||||
; Silence
|
||||
sta SNDCHN
|
||||
delay_msec 160
|
||||
|
||||
pla
|
||||
rts
|
||||
36
src/test_roms/common/testing.s
Normal file
36
src/test_roms/common/testing.s
Normal file
@@ -0,0 +1,36 @@
|
||||
; 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
|
||||
|
||||
.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
|
||||
|
||||
.macro patterns_bin name
|
||||
.pushseg
|
||||
.segment "CHARS"
|
||||
.incbin name
|
||||
.popseg
|
||||
.endmacro
|
||||
|
||||
.segment "CODE"
|
||||
|
||||
61
src/test_roms/common/text_out.s
Normal file
61
src/test_roms/common/text_out.s
Normal file
@@ -0,0 +1,61 @@
|
||||
; Text output as expanding zero-terminated string at text_out_base
|
||||
|
||||
; The final exit result byte is written here
|
||||
final_result = $6000
|
||||
|
||||
; Text output is written here as an expanding
|
||||
; zero-terminated string
|
||||
text_out_base = $6004
|
||||
|
||||
bss_res text_out_temp
|
||||
zp_res text_out_addr,2
|
||||
|
||||
init_text_out:
|
||||
ldx #0
|
||||
|
||||
; Put valid data first
|
||||
setb text_out_base,0
|
||||
|
||||
lda #$80
|
||||
jsr set_final_result
|
||||
|
||||
; Now fill in signature that tells emulator there's
|
||||
; useful data there
|
||||
setb text_out_base-3,$DE
|
||||
setb text_out_base-2,$B0
|
||||
setb text_out_base-1,$61
|
||||
|
||||
ldx #>text_out_base
|
||||
stx text_out_addr+1
|
||||
setb text_out_addr,<text_out_base
|
||||
rts
|
||||
|
||||
|
||||
; Sets final result byte in memory
|
||||
set_final_result:
|
||||
sta final_result
|
||||
rts
|
||||
|
||||
|
||||
; Writes character to text output
|
||||
; In: A=Character to write
|
||||
; Preserved: A, X, Y
|
||||
write_text_out:
|
||||
sty text_out_temp
|
||||
|
||||
; Write new terminator FIRST, then new char before it,
|
||||
; in case emulator looks at string in middle of this routine.
|
||||
ldy #1
|
||||
pha
|
||||
lda #0
|
||||
sta (text_out_addr),y
|
||||
dey
|
||||
pla
|
||||
sta (text_out_addr),y
|
||||
|
||||
inc text_out_addr
|
||||
bne :+
|
||||
inc text_out_addr+1
|
||||
:
|
||||
ldy text_out_temp
|
||||
rts
|
||||
17
src/test_roms/cpu_reset_ram.rs
Normal file
17
src/test_roms/cpu_reset_ram.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use super::rom_test;
|
||||
|
||||
// TODO: need to implement soft reset behavior
|
||||
// rom_test!(basic_cpu, "cpu_reset_ram.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.cpu.sp, 0xFD);
|
||||
// // assert_eq!(nes.cpu.a, 0x00);
|
||||
// // assert_eq!(nes.cpu.x, 0x00);
|
||||
// // assert_eq!(nes.cpu.y, 0x00);
|
||||
// });
|
||||
84
src/test_roms/cpu_reset_ram.s.old
Normal file
84
src/test_roms/cpu_reset_ram.s.old
Normal file
@@ -0,0 +1,84 @@
|
||||
; Verifies that reset doesn't alter any RAM.
|
||||
|
||||
CUSTOM_RESET=1
|
||||
.include "shell.inc"
|
||||
.include "run_at_reset.s"
|
||||
|
||||
nv_res bad_addr,2
|
||||
|
||||
reset: sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
ldx #7
|
||||
ldy #0
|
||||
|
||||
; Check first byte, and assume just powered
|
||||
; if not as expected
|
||||
lda <0
|
||||
cmp #$DB
|
||||
jne std_reset
|
||||
iny
|
||||
|
||||
; Check second byte
|
||||
lda <1
|
||||
cmp #$B6
|
||||
bne failed
|
||||
iny
|
||||
|
||||
; Rest of internal memory
|
||||
setb <0,0
|
||||
setb <1,0
|
||||
lda #$6D
|
||||
clc
|
||||
: eor (<0),y
|
||||
bne failed
|
||||
lda (<0),y
|
||||
rol a
|
||||
iny
|
||||
bne :-
|
||||
inc <1
|
||||
dex
|
||||
bpl :-
|
||||
jmp tests_passed
|
||||
|
||||
failed:
|
||||
sty bad_addr
|
||||
txa
|
||||
eor #$07
|
||||
sta bad_addr+1
|
||||
|
||||
jsr init_shell
|
||||
|
||||
print_str "Addr: "
|
||||
lda bad_addr+1
|
||||
jsr print_hex
|
||||
lda bad_addr
|
||||
jsr print_a
|
||||
|
||||
set_test 3,"Reset shouldn't modify RAM"
|
||||
; stp
|
||||
|
||||
main:
|
||||
jsr prompt_to_reset
|
||||
|
||||
; Fill RAM with pattern
|
||||
setb <0,0
|
||||
setb <1,0
|
||||
ldx #8
|
||||
ldy #2
|
||||
lda #$6D
|
||||
clc
|
||||
: sta (<0),y
|
||||
rol a
|
||||
iny
|
||||
bne :-
|
||||
inc <1
|
||||
dex
|
||||
bne :-
|
||||
|
||||
setb <0,$DB
|
||||
setb <1,$B6
|
||||
|
||||
jmp wait_reset
|
||||
63
src/test_roms/crc_check.s
Normal file
63
src/test_roms/crc_check.s
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
.include "shell.inc"
|
||||
.include "crc_fast.s"
|
||||
|
||||
run_crc:
|
||||
pha
|
||||
jsr reset_crc
|
||||
pla
|
||||
jsr update_crc_fast
|
||||
ldy #0
|
||||
lda checksum
|
||||
sta ($0),y
|
||||
iny
|
||||
lda checksum+1
|
||||
sta ($0),y
|
||||
iny
|
||||
lda checksum+2
|
||||
sta ($0),y
|
||||
iny
|
||||
lda checksum+3
|
||||
sta ($0),y
|
||||
rts
|
||||
.macro crc_check crc_val,val
|
||||
lda #.lobyte(crc_val)
|
||||
sta $0
|
||||
lda #.hibyte(crc_val)
|
||||
sta $1
|
||||
lda #val
|
||||
jsr run_crc
|
||||
.endmacro
|
||||
|
||||
main:
|
||||
crc_check crc_1,$00
|
||||
crc_check crc_2,$01
|
||||
crc_check crc_3,$02
|
||||
crc_check crc_4,$7F
|
||||
crc_check crc_5,$80
|
||||
crc_check crc_6,$81
|
||||
crc_check crc_7,$FF
|
||||
lda #$00
|
||||
jmp exit
|
||||
|
||||
.org $20
|
||||
crc_1: .res 4
|
||||
crc_2: .res 4
|
||||
crc_3: .res 4
|
||||
crc_4: .res 4
|
||||
crc_5: .res 4
|
||||
crc_6: .res 4
|
||||
crc_7: .res 4
|
||||
crc_8: .res 4
|
||||
crc_9: .res 4
|
||||
crc_10: .res 4
|
||||
crc_11: .res 4
|
||||
crc_12: .res 4
|
||||
crc_13: .res 4
|
||||
crc_14: .res 4
|
||||
crc_15: .res 4
|
||||
crc_16: .res 4
|
||||
crc_17: .res 4
|
||||
crc_18: .res 4
|
||||
crc_19: .res 4
|
||||
crc_20: .res 4
|
||||
@@ -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
|
||||
@@ -15,7 +11,7 @@ RESET:
|
||||
stx $2000 ; Disable NMI (by writing zero)
|
||||
stx $4010 ; Disable DMC IRQs
|
||||
ldx #$08
|
||||
stx $2001 ; Disable rendering
|
||||
stx $2001 ; Enable rendering
|
||||
|
||||
bit $2002 ; Clear vblank flag by reading ppu status
|
||||
VBLANKWAIT1:
|
||||
@@ -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
|
||||
130
src/test_roms/implied_instrs.s
Normal file
130
src/test_roms/implied_instrs.s
Normal file
@@ -0,0 +1,130 @@
|
||||
|
||||
OPERANDS = 0
|
||||
.include "shell.inc"
|
||||
; .include "instr_test.inc"
|
||||
; .include "macros.inc"
|
||||
|
||||
values:
|
||||
.byte 0,1,2,$40,$7F,$80,$81,$FF
|
||||
values_size = * - values
|
||||
|
||||
zp_res in_a,2
|
||||
zp_res in_x,2
|
||||
zp_res in_y,2
|
||||
zp_res in_p,2
|
||||
zp_res in_s,2
|
||||
zp_res out_a
|
||||
zp_res out_x
|
||||
zp_res out_y
|
||||
zp_res out_p
|
||||
zp_res out_s
|
||||
|
||||
.macro store_ptr zp_ptr, value
|
||||
lda #.lobyte(value)
|
||||
sta zp_ptr
|
||||
lda #.hibyte(value)
|
||||
sta zp_ptr+1
|
||||
.endmacro
|
||||
|
||||
testcase_init:
|
||||
store_ptr in_a, values
|
||||
store_ptr in_x, values
|
||||
store_ptr in_y, values
|
||||
store_ptr in_p, values
|
||||
store_ptr in_s, values
|
||||
rts
|
||||
|
||||
; Calling convention: Will trash stack - anything that needs to be saved must be elsewhere
|
||||
; Will call `test_code` with the requisite
|
||||
zp_res return_addr
|
||||
run_tc:
|
||||
lda (in_s,x)
|
||||
tax
|
||||
txs ; As long as the following code has the same number of pushes & pulls, stack ptr will be correct
|
||||
ldx #0
|
||||
lda (in_p,x)
|
||||
pha
|
||||
lda (in_a,x)
|
||||
pha
|
||||
lda (in_x,x)
|
||||
pha
|
||||
lda (in_y,x)
|
||||
pha
|
||||
; Stack <y, x, a, p>
|
||||
pla
|
||||
tay
|
||||
; Stack <x, a, p>, y correct
|
||||
pla
|
||||
tax
|
||||
; Stack <a, p>, C: x, y
|
||||
pla
|
||||
; Stack <p>, C: x, y
|
||||
plp
|
||||
; Stack <>, C: x, y, p, (also S)
|
||||
jsr test_code
|
||||
php
|
||||
sta out_a
|
||||
pla
|
||||
sta out_p
|
||||
stx out_x
|
||||
sty out_y
|
||||
tsx
|
||||
stx out_s
|
||||
|
||||
jmp (return_addr)
|
||||
|
||||
test_code:
|
||||
rts
|
||||
|
||||
main:
|
||||
jsr testcase_init
|
||||
; $A2,$67,$77,$F8
|
||||
; entry $2A,"ROL A",$CB,$4B,$9A,$4C ; A = op A
|
||||
; entry $0A,"ASL A",$00,$00,$00,$00
|
||||
; entry $0A,"ROR A",$00,$00,$00,$00
|
||||
; entry $4A,"LSR A",$00,$00,$00,$00
|
||||
|
||||
; entry $8A,"TXA",$00,$00,$00,$00 ; AXY = AXY
|
||||
; entry $98,"TYA",$00,$00,$00,$00
|
||||
; entry $AA,"TAX",$00,$00,$00,$00
|
||||
; entry $A8,"TAY",$00,$00,$00,$00
|
||||
|
||||
; entry $E8,"INX",$00,$00,$00,$00 ; XY = op XY
|
||||
; entry $C8,"INY",$00,$00,$00,$00
|
||||
; entry $CA,"DEX",$00,$00,$00,$00
|
||||
; entry $88,"DEY",$00,$00,$00,$00
|
||||
|
||||
; entry $38,"SEC",$00,$00,$00,$00 ; flags = op flags
|
||||
; entry $18,"CLC",$00,$00,$00,$00
|
||||
; entry $F8,"SED",$00,$00,$00,$00
|
||||
; entry $D8,"CLD",$00,$00,$00,$00
|
||||
; entry $78,"SEI",$00,$00,$00,$00
|
||||
; entry $58,"CLI",$00,$00,$00,$00
|
||||
; entry $B8,"CLV",$00,$00,$00,$00
|
||||
|
||||
; entry $EA,"NOP",$00,$00,$00,$00
|
||||
|
||||
lda #$00
|
||||
jmp exit
|
||||
|
||||
|
||||
; .ifndef OFFICIAL_ONLY
|
||||
; entry $1A,"NOP"
|
||||
; entry $3A,"NOP"
|
||||
; entry $5A,"NOP"
|
||||
; entry $7A,"NOP"
|
||||
; entry $DA,"NOP"
|
||||
; entry $FA,"NOP"
|
||||
; .endif
|
||||
; instrs_size = * - instrs
|
||||
|
||||
; instr_template:
|
||||
; nop
|
||||
; jmp instr_done
|
||||
; instr_template_size = * - instr_template
|
||||
|
||||
; .include "instr_test_end.s"
|
||||
|
||||
; test_values:
|
||||
; test_normal
|
||||
; rts
|
||||
335
src/test_roms/input_test.s
Normal file
335
src/test_roms/input_test.s
Normal file
@@ -0,0 +1,335 @@
|
||||
|
||||
; 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"
|
||||
T_EMPTY = 0
|
||||
.repeat 2 ; Empty 0
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
T_TOP_LEFT = 1
|
||||
.repeat 2 ; Top Left 1
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_TOP_RIGHT = 2
|
||||
.repeat 2 ; Top Right 2
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
T_BOTTOM_RIGHT = 3
|
||||
.repeat 2 ; Bottom Right 3
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_BOTTOM_LEFT = 4
|
||||
.repeat 2 ; Bottom Left 4
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_FORWARD = 5
|
||||
.repeat 2 ; FW 5
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %00001110
|
||||
.byte %00011100
|
||||
.byte %00111000
|
||||
.byte %01110000
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_BACKWARD = 6
|
||||
.repeat 2 ; BW 6
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01110000
|
||||
.byte %00111000
|
||||
.byte %00011100
|
||||
.byte %00001110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
|
||||
T_PILL_L = 7
|
||||
.repeat 2 ; Pill L 7
|
||||
.byte %00111111
|
||||
.byte %01111111
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01111111
|
||||
.byte %00111111
|
||||
.endrepeat
|
||||
T_PILL_R = 8
|
||||
.repeat 2 ; Pill R 8
|
||||
.byte %11111100
|
||||
.byte %11111110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %11111110
|
||||
.byte %11111100
|
||||
.endrepeat
|
||||
|
||||
.popseg
|
||||
|
||||
zp_res TEMP
|
||||
|
||||
.macro load_ppu_addr addr
|
||||
lda #.hibyte(addr)
|
||||
sta PPUADDR
|
||||
lda #.lobyte(addr)
|
||||
sta PPUADDR ; Load $2000
|
||||
.endmacro
|
||||
|
||||
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
|
||||
|
||||
; Fill NT0
|
||||
load_ppu_addr $2000
|
||||
ldx #$00
|
||||
ldy #$04
|
||||
lda #$00
|
||||
fill_loop:
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne fill_loop
|
||||
dey
|
||||
bne fill_loop
|
||||
; Set specific tiles
|
||||
load_ppu_addr $2184
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21A4
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E2
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C6
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E6
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $2204
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $2224
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21EA
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
lda #$00
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D2
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $3F00
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$20
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$09
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
lda #%00001110
|
||||
sta PPUMASK
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
.macro shl_a count
|
||||
.scope
|
||||
ldx #count
|
||||
a_l:asl A
|
||||
dex
|
||||
bne a_l
|
||||
.endscope
|
||||
.endmacro
|
||||
|
||||
nmi:
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
lda #$01
|
||||
sta JOY1
|
||||
lda #$00
|
||||
sta JOY1
|
||||
|
||||
load_ppu_addr $23DC ; A
|
||||
lda JOY1
|
||||
and #$01
|
||||
shl_a 6
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $23DD ; B
|
||||
lda JOY1
|
||||
and #$01
|
||||
shl_a 6
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $23DA ; Select
|
||||
lda JOY1
|
||||
and #$01
|
||||
shl_a 6
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $23DB ; Start
|
||||
lda JOY1
|
||||
and #$01
|
||||
shl_a 6
|
||||
sta PPUDATA
|
||||
|
||||
lda JOY1 ; Up
|
||||
and #$01
|
||||
sta TEMP
|
||||
|
||||
load_ppu_addr $23E1 ; Down
|
||||
lda JOY1
|
||||
and #$01
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $23D8 ; Left
|
||||
lda JOY1
|
||||
and #$01
|
||||
shl_a 6
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $23D9 ; Right
|
||||
lda JOY1
|
||||
and #$01
|
||||
shl_a 6
|
||||
ora TEMP
|
||||
sta PPUDATA
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda #%00001110
|
||||
sta PPUMASK ; Enable rendering
|
||||
rti
|
||||
exit:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
68
src/test_roms/instructions.rs
Normal file
68
src/test_roms/instructions.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::NES;
|
||||
|
||||
use super::rom_test;
|
||||
|
||||
enum HexDump<'a> {
|
||||
NES(&'a NES, u16, usize),
|
||||
Lit(&'a [u8]),
|
||||
}
|
||||
|
||||
impl HexDump<'_> {
|
||||
fn get(&self, off: usize) -> Option<u8> {
|
||||
match self {
|
||||
Self::NES(_, _, len) if off >= *len => None,
|
||||
Self::NES(nes, addr, _) => nes.mem().peek_cpu(addr + off as u16),
|
||||
Self::Lit(items) => items.get(off).copied(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HexDump<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut i = 0;
|
||||
while let Some(val) = self.get(i) {
|
||||
if i % 16 == 0 {
|
||||
write!(f, "\n{i:04X}:")?;
|
||||
}
|
||||
write!(f, " {val:02X}")?;
|
||||
i += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn mem_cmp(nes: &NES, addr: u16, vals: &[u8]) {
|
||||
for (i, v) in vals.iter().enumerate() {
|
||||
if nes.mem().peek_cpu(addr + i as u16) != Some(*v) {
|
||||
panic!(
|
||||
"memcmp assertion failed:\nNES:{}\nTest:{}",
|
||||
HexDump::NES(nes, addr, vals.len()),
|
||||
HexDump::Lit(vals)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rom_test!(crc_check, "crc_check.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []");
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
mem_cmp(&nes, 0x20, &[0xFF, 0xFF, 0xFF, 0xFF]);
|
||||
mem_cmp(&nes, 0x24, &[0x69, 0xCF, 0xF8, 0x88]);
|
||||
mem_cmp(&nes, 0x28, &[0xD3, 0x9E, 0xF1, 0x11]);
|
||||
mem_cmp(&nes, 0x2C, &[0x52, 0x93, 0x45, 0x3F]);
|
||||
mem_cmp(&nes, 0x30, &[0xDF, 0x7C, 0x47, 0x12]);
|
||||
mem_cmp(&nes, 0x34, &[0x49, 0x4C, 0x40, 0x65]);
|
||||
mem_cmp(&nes, 0x38, &[0x72, 0x10, 0xFD, 0xD2]);
|
||||
});
|
||||
|
||||
rom_test!(implied_instructions, "implied_instrs.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []");
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
});
|
||||
|
||||
rom_test!(bit, "alu_bit.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction(), "0x8021 HLT :2 []");
|
||||
mem_cmp(&nes, 0x300, &[0xF6, 0xF6]);
|
||||
});
|
||||
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
|
||||
25
src/test_roms/interrupts.rs
Normal file
25
src/test_roms/interrupts.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
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.cpu_cycle(), 86967);
|
||||
assert_eq!(nes.ppu().pixel, 40);
|
||||
assert_eq!(nes.ppu().scanline, 241);
|
||||
assert_eq!(nes.ppu().cycle, 260905);
|
||||
assert_eq!(nes.mem().peek_cpu(0x1FF), Some(0x80));
|
||||
assert_eq!(nes.mem().peek_cpu(0x1FE), Some(0x17));
|
||||
assert_eq!(nes.mem().peek_cpu(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.cpu_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.mem().peek_cpu(0x1FF), Some(0x80));
|
||||
assert_eq!(nes.mem().peek_cpu(0x1FE), Some(0x1B));
|
||||
assert_eq!(nes.mem().peek_cpu(0x1FD), Some(0x26));
|
||||
});
|
||||
@@ -1,15 +1,18 @@
|
||||
use crate::{NES, hex_view::Memory};
|
||||
mod cpu_reset_ram;
|
||||
mod instructions;
|
||||
mod ppu;
|
||||
mod interrupts;
|
||||
|
||||
macro_rules! rom_test {
|
||||
($name:ident, $rom:literal, |$nes:ident| $eval:expr) => {
|
||||
rom_test!($name, $rom, timeout = 10000000, |$nes| $eval);
|
||||
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 = concat!(env!("ROM_DIR"), "/", $rom);
|
||||
println!("{}: {}", stringify!($name), rom_file);
|
||||
let mut $nes = NES::load_nes_file(rom_file).expect("Failed to create nes object");
|
||||
let mut $nes = crate::NES::load_nes_file(rom_file).expect("Failed to create nes object");
|
||||
$nes.reset_and_run_with_timeout($timeout);
|
||||
println!("Final: {:?}", $nes);
|
||||
$eval
|
||||
@@ -23,7 +26,7 @@ macro_rules! rom_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");
|
||||
let mut $nes = crate::NES::load_nes_file(rom_file).expect("Failed to create nes object");
|
||||
$nes.reset_and_run_with_timeout($timeout);
|
||||
println!("Final: {:?}", $nes);
|
||||
$eval
|
||||
@@ -31,39 +34,55 @@ macro_rules! rom_test {
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use rom_test;
|
||||
|
||||
use crate::{Break, NES};
|
||||
|
||||
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.last_instruction(), "0x8001 HLT :2 []");
|
||||
assert_eq!(nes.cpu_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.cpu_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.cpu_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.cpu_cycle(), 25);
|
||||
assert_eq!(nes.cpu.pc, 0x800C);
|
||||
assert_eq!(nes.cpu.sp, 0xFD);
|
||||
|
||||
assert_eq!(nes.cpu.a, 0xAA);
|
||||
assert_eq!(nes.cpu.x, 0xAA);
|
||||
assert_eq!(nes.cpu.y, 0xAA);
|
||||
assert_eq!(nes.cpu_mem().peek(0x0000).unwrap(), 0xAA);
|
||||
assert_eq!(nes.cpu_mem().peek(0x0001).unwrap(), 0xAA);
|
||||
assert_eq!(nes.cpu_mem().peek(0x0002).unwrap(), 0xAA);
|
||||
assert_eq!(nes.mem().peek_cpu(0x0000).unwrap(), 0xAA);
|
||||
assert_eq!(nes.mem().peek_cpu(0x0001).unwrap(), 0xAA);
|
||||
assert_eq!(nes.mem().peek_cpu(0x0002).unwrap(), 0xAA);
|
||||
});
|
||||
|
||||
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.last_instruction(), "0x8017 HLT :2 []");
|
||||
assert_eq!(nes.cpu_cycle(), 40);
|
||||
assert_eq!(nes.cpu.pc, 0x8017);
|
||||
assert_eq!(nes.cpu.sp, 0xFF);
|
||||
|
||||
assert_eq!(nes.cpu.a, 0x00);
|
||||
@@ -72,33 +91,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.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.last_instruction(), "0x801C HLT :2 []");
|
||||
assert_eq!(nes.cpu_cycle(), 27402);
|
||||
assert_eq!(nes.cpu.pc, 0x801C);
|
||||
assert_eq!(nes.ppu.pixel, 29);
|
||||
|
||||
assert_eq!(nes.cpu.sp, 0xFF);
|
||||
@@ -107,11 +102,35 @@ rom_test!(basic_init_3, "basic_init_3.nes", |nes| {
|
||||
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.cpu_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);
|
||||
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.cpu_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);
|
||||
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.last_instruction(), "0x8023 HLT :2 []");
|
||||
assert_eq!(nes.cpu_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);
|
||||
@@ -119,9 +138,70 @@ rom_test!(even_odd, "even_odd.nes", |nes| {
|
||||
assert_eq!(nes.cpu.y, 0x00);
|
||||
});
|
||||
|
||||
fn run_frame(nes: &mut NES) {
|
||||
nes.run_one_clock_cycle(&Break::default());
|
||||
while nes.ppu().scanline != 241 || nes.ppu().pixel != 0 {
|
||||
nes.run_one_clock_cycle(&Break::default());
|
||||
}
|
||||
}
|
||||
|
||||
rom_test!(input_test, "input_test.nes", timeout = 86964*3, |nes| {
|
||||
const A: u16 = 0x23DC; // 0 || 0b01000000
|
||||
const B: u16 = 0x23DD; // 0 || 0b01000000
|
||||
const SELECT: u16 = 0x23DA; // 0 || 0b01000000
|
||||
const START: u16 = 0x23DB; // 0 || 0b01000000
|
||||
const DOWN: u16 = 0x23E1; // 0 || 1
|
||||
const LEFT: u16 = 0x23D8; // 0 || 0b01000000
|
||||
const UP_RIGHT: u16 = 0x23D9; // 0 || 0b01000000 | 1
|
||||
run_frame(&mut nes);
|
||||
assert_eq!(nes.mapped.peek_ppu(A).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(B).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(SELECT).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(START).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(DOWN).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(LEFT).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(UP_RIGHT).unwrap(), 0);
|
||||
nes.controller_1().set_a(true);
|
||||
run_frame(&mut nes);
|
||||
assert_eq!(nes.mapped.peek_ppu(A).unwrap(), 0x40);
|
||||
assert_eq!(nes.mapped.peek_ppu(B).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(SELECT).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(START).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(DOWN).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(LEFT).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(UP_RIGHT).unwrap(), 0);
|
||||
nes.controller_1().set_b(true);
|
||||
run_frame(&mut nes);
|
||||
assert_eq!(nes.mapped.peek_ppu(A).unwrap(), 0x40);
|
||||
assert_eq!(nes.mapped.peek_ppu(B).unwrap(), 0x40);
|
||||
assert_eq!(nes.mapped.peek_ppu(SELECT).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(START).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(DOWN).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(LEFT).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(UP_RIGHT).unwrap(), 0);
|
||||
nes.controller_1().0 = 0xFF;
|
||||
run_frame(&mut nes);
|
||||
assert_eq!(nes.mapped.peek_ppu(A).unwrap(), 0x40);
|
||||
assert_eq!(nes.mapped.peek_ppu(B).unwrap(), 0x40);
|
||||
assert_eq!(nes.mapped.peek_ppu(SELECT).unwrap(), 0x40);
|
||||
assert_eq!(nes.mapped.peek_ppu(START).unwrap(), 0x40);
|
||||
assert_eq!(nes.mapped.peek_ppu(DOWN).unwrap(), 0x01);
|
||||
assert_eq!(nes.mapped.peek_ppu(LEFT).unwrap(), 0x40);
|
||||
assert_eq!(nes.mapped.peek_ppu(UP_RIGHT).unwrap(), 0x41);
|
||||
nes.controller_1().0 = 0;
|
||||
run_frame(&mut nes);
|
||||
assert_eq!(nes.mapped.peek_ppu(A).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(B).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(SELECT).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(START).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(DOWN).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(LEFT).unwrap(), 0);
|
||||
assert_eq!(nes.mapped.peek_ppu(UP_RIGHT).unwrap(), 0);
|
||||
});
|
||||
|
||||
// rom_test!(even_odd, "even_odd.nes", |nes| {
|
||||
// assert_eq!(nes.last_instruction, "0x8023 HLT :2 []");
|
||||
// assert_eq!(nes.cycle, 57182);
|
||||
// assert_eq!(nes.last_instruction(), "0x8023 HLT :2 []");
|
||||
// assert_eq!(nes.cpu_cycle(), 57182);
|
||||
// assert_eq!(nes.cpu.pc, 0x8024);
|
||||
// assert_eq!(nes.ppu.pixel, 25);
|
||||
|
||||
|
||||
36
src/test_roms/nes.cfg
Normal file
36
src/test_roms/nes.cfg
Normal file
@@ -0,0 +1,36 @@
|
||||
# 32K iNES ROM with optional 8K CHR
|
||||
|
||||
MEMORY
|
||||
{
|
||||
ZP: start = $10, size = $E0; # leave $10 free at each end
|
||||
RAM: start = $200, size = $500;
|
||||
|
||||
HEADER: start = 0, size = $10, fill=yes;
|
||||
|
||||
ROM: start = $8000, size = $7E00, fill=yes, fillval=$FF;
|
||||
LOADER: start = $FE00, size = $100, fill=yes, fillval=$FF;
|
||||
FF00: start = $FF00, size = $F4, fill=yes, fillval=$FF;
|
||||
VECTORS:start = $FFF4, size = $C, fill=yes;
|
||||
|
||||
CHARS: start = 0, size = $3000, fill=yes, fillval=$FF;
|
||||
}
|
||||
|
||||
SEGMENTS
|
||||
{
|
||||
ZEROPAGE: load = ZP, type = zp;
|
||||
BSS: load = RAM, type = bss,align=$100;
|
||||
NVRAM: load = RAM, type = bss,define=yes, optional=yes;
|
||||
|
||||
HEADER: load = HEADER, type = ro;
|
||||
CODE: load = ROM, type = ro, align=$100;
|
||||
CODE2: load = ROM, type = ro, align=$100, optional=yes;
|
||||
RODATA: load = ROM, type = ro, align=$100;
|
||||
CHARS_PRG: load = ROM, type = ro, align=$200, optional=yes;
|
||||
CHARS_PRG_ASCII:load = ROM, type = ro, align=$200, optional=yes;
|
||||
LOADER: load = LOADER, type = ro, optional=yes;
|
||||
FF00: load = FF00, type = ro, align=$100, optional=yes;
|
||||
VECTORS: load = VECTORS, type = ro;
|
||||
|
||||
CHARS: load = CHARS, type = ro, align=$100, optional=yes;
|
||||
CHARS_ASCII:load = CHARS, type = ro, align=$200, optional=yes;
|
||||
}
|
||||
BIN
src/test_roms/pat.bin
Normal file
BIN
src/test_roms/pat.bin
Normal file
Binary file not shown.
101
src/test_roms/ppu.rs
Normal file
101
src/test_roms/ppu.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use crate::{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.mem().peek_ppu(0x2000), Ok(1));
|
||||
assert_eq!(nes.mem().peek_ppu(0x2020), Ok(1));
|
||||
assert_eq!(nes.mem().peek_ppu(0x2400), Ok(2));
|
||||
assert_eq!(nes.mem().peek_ppu(0x2401), Ok(2));
|
||||
});
|
||||
|
||||
rom_test!(ppu_palette_shared, "ppu_palette_shared.nes", |nes| {
|
||||
assert_eq!(nes.last_instruction(), "0x8078 HLT :2 []");
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Sets up an image, and scrolls a specific number of pixels over
|
||||
rom_test!(ppu_scrolling, "ppu_fine_x_scrolling.nes", timeout = 86964*4, |nes| {
|
||||
assert_eq!(nes.image().read(0, 0), Color { r: 0xFF, g: 0xFE, b: 0xFF });
|
||||
});
|
||||
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
|
||||
306
src/test_roms/ppu_fine_x_scrolling.s
Normal file
306
src/test_roms/ppu_fine_x_scrolling.s
Normal file
@@ -0,0 +1,306 @@
|
||||
.include "testing.s"
|
||||
; patterns_bin "pat.bin"
|
||||
.macro chr b,s
|
||||
.repeat s
|
||||
.byte b
|
||||
.endrepeat
|
||||
.endmacro
|
||||
.pushseg
|
||||
.segment "CHARS"
|
||||
T_EMPTY = 0
|
||||
.repeat 2 ; Empty 0
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
T_TOP_LEFT = 1
|
||||
.repeat 2 ; Top Left 1
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_TOP_RIGHT = 2
|
||||
.repeat 2 ; Top Right 2
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
T_BOTTOM_RIGHT = 3
|
||||
.repeat 2 ; Bottom Right 3
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_BOTTOM_LEFT = 4
|
||||
.repeat 2 ; Bottom Left 4
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_FORWARD = 5
|
||||
.repeat 2 ; FW 5
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %00001110
|
||||
.byte %00011100
|
||||
.byte %00111000
|
||||
.byte %01110000
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_BACKWARD = 6
|
||||
.repeat 2 ; BW 6
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01110000
|
||||
.byte %00111000
|
||||
.byte %00011100
|
||||
.byte %00001110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
|
||||
T_PILL_L = 7
|
||||
.repeat 2 ; Pill L 7
|
||||
.byte %00111111
|
||||
.byte %01111111
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01111111
|
||||
.byte %00111111
|
||||
.endrepeat
|
||||
T_PILL_R = 8
|
||||
.repeat 2 ; Pill R 8
|
||||
.byte %11111100
|
||||
.byte %11111110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %11111110
|
||||
.byte %11111100
|
||||
.endrepeat
|
||||
|
||||
T_LINE = 9
|
||||
.repeat 2 ; Empty 0
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.endrepeat
|
||||
|
||||
.popseg
|
||||
|
||||
zp_res Y_SCROLL
|
||||
zp_res X_SCROLL
|
||||
zp_res CTRL_BYTE
|
||||
|
||||
.macro load_ppu_addr addr
|
||||
lda #.hibyte(addr)
|
||||
sta PPUADDR
|
||||
lda #.lobyte(addr)
|
||||
sta PPUADDR ; Load $2000
|
||||
.endmacro
|
||||
|
||||
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
|
||||
|
||||
; Fill NT0
|
||||
load_ppu_addr $2000
|
||||
ldx #$00
|
||||
ldy #$04
|
||||
lda #$00
|
||||
fill_loop:
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne fill_loop
|
||||
dey
|
||||
bne fill_loop
|
||||
; Set specific tiles
|
||||
load_ppu_addr $2184
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21A4
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E2
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C6
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E6
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $2204
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $2224
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21EA
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
lda #$00
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D2
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $3F00
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$20
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$09
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $2001
|
||||
lda #T_LINE
|
||||
sta PPUDATA
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
lda #%00001110
|
||||
sta PPUMASK
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
sta CTRL_BYTE
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
nmi:
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
|
||||
lda #$0A
|
||||
; adc X_SCROLL
|
||||
; sta X_SCROLL
|
||||
sta PPUSCROLL
|
||||
; bcc y_scroll
|
||||
; lda #$01
|
||||
; eor CTRL_BYTE
|
||||
; sta CTRL_BYTE
|
||||
; sta PPUCTRL
|
||||
; y_scroll:
|
||||
; clc
|
||||
lda #$00
|
||||
; adc Y_SCROLL
|
||||
; sta Y_SCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda CTRL_BYTE
|
||||
sta PPUCTRL
|
||||
lda #%00001110
|
||||
sta PPUMASK ; Enable rendering
|
||||
rti
|
||||
exit:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
70
src/test_roms/ppu_palette_shared.s
Normal file
70
src/test_roms/ppu_palette_shared.s
Normal file
@@ -0,0 +1,70 @@
|
||||
.include "testing.s"
|
||||
|
||||
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
|
||||
|
||||
lda #$3F
|
||||
sta PPUADDR
|
||||
lda #$00
|
||||
sta PPUADDR
|
||||
lda #$01
|
||||
ldx #$10
|
||||
fill_loop:
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne fill_loop
|
||||
|
||||
lda #$3F
|
||||
sta PPUADDR
|
||||
lda #$10
|
||||
sta PPUADDR
|
||||
|
||||
lda PPUDATA
|
||||
cmp #$01
|
||||
bne fail
|
||||
lda PPUDATA
|
||||
lda PPUDATA
|
||||
lda PPUDATA
|
||||
lda PPUDATA
|
||||
cmp #$01
|
||||
bne fail
|
||||
lda PPUDATA
|
||||
lda PPUDATA
|
||||
lda PPUDATA
|
||||
lda PPUDATA
|
||||
cmp #$01
|
||||
bne fail
|
||||
lda PPUDATA
|
||||
lda PPUDATA
|
||||
lda PPUDATA
|
||||
lda PPUDATA
|
||||
cmp #$01
|
||||
bne fail
|
||||
stp
|
||||
jmp $8000
|
||||
fail:
|
||||
stp
|
||||
|
||||
nmi:
|
||||
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
|
||||
96
src/test_roms/render-updating.s
Normal file
96
src/test_roms/render-updating.s
Normal file
@@ -0,0 +1,96 @@
|
||||
.include "testing.s"
|
||||
|
||||
.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 CUR
|
||||
zp_res POS
|
||||
|
||||
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 #$0A
|
||||
stx $2001 ; Disable rendering
|
||||
|
||||
bit $2002 ; Clear vblank flag by reading ppu status
|
||||
VBLANKWAIT1:
|
||||
bit $2002
|
||||
bpl VBLANKWAIT1
|
||||
VBLANKWAIT2:
|
||||
bit $2002
|
||||
bpl VBLANKWAIT2
|
||||
lda #$1
|
||||
sta CUR
|
||||
lda #$0
|
||||
sta POS
|
||||
lda #$80
|
||||
sta PPUCTRL
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
nmi:
|
||||
lda #$20
|
||||
sta PPUADDR ; MSB
|
||||
lda POS
|
||||
adc #$1
|
||||
sta POS
|
||||
sta PPUADDR
|
||||
bne update
|
||||
clc
|
||||
lda CUR
|
||||
adc #$1
|
||||
sta CUR
|
||||
cmp #$7
|
||||
bne update
|
||||
lda #$0
|
||||
sta CUR
|
||||
update:
|
||||
lda CUR
|
||||
sta PPUDATA
|
||||
|
||||
lda #$0
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda #$80
|
||||
sta PPUCTRL
|
||||
|
||||
rti
|
||||
|
||||
irq:
|
||||
stp
|
||||
306
src/test_roms/scrolling.s
Normal file
306
src/test_roms/scrolling.s
Normal file
@@ -0,0 +1,306 @@
|
||||
.include "testing.s"
|
||||
; patterns_bin "pat.bin"
|
||||
.macro chr b,s
|
||||
.repeat s
|
||||
.byte b
|
||||
.endrepeat
|
||||
.endmacro
|
||||
.pushseg
|
||||
.segment "CHARS"
|
||||
T_EMPTY = 0
|
||||
.repeat 2 ; Empty 0
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
T_TOP_LEFT = 1
|
||||
.repeat 2 ; Top Left 1
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_TOP_RIGHT = 2
|
||||
.repeat 2 ; Top Right 2
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
T_BOTTOM_RIGHT = 3
|
||||
.repeat 2 ; Bottom Right 3
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_BOTTOM_LEFT = 4
|
||||
.repeat 2 ; Bottom Left 4
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_FORWARD = 5
|
||||
.repeat 2 ; FW 5
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %00001110
|
||||
.byte %00011100
|
||||
.byte %00111000
|
||||
.byte %01110000
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_BACKWARD = 6
|
||||
.repeat 2 ; BW 6
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01110000
|
||||
.byte %00111000
|
||||
.byte %00011100
|
||||
.byte %00001110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
|
||||
T_PILL_L = 7
|
||||
.repeat 2 ; Pill L 7
|
||||
.byte %00111111
|
||||
.byte %01111111
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01111111
|
||||
.byte %00111111
|
||||
.endrepeat
|
||||
T_PILL_R = 8
|
||||
.repeat 2 ; Pill R 8
|
||||
.byte %11111100
|
||||
.byte %11111110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %11111110
|
||||
.byte %11111100
|
||||
.endrepeat
|
||||
|
||||
T_LINE = 9
|
||||
.repeat 2 ; Empty 0
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.endrepeat
|
||||
|
||||
.popseg
|
||||
|
||||
zp_res Y_SCROLL
|
||||
zp_res X_SCROLL
|
||||
zp_res CTRL_BYTE
|
||||
|
||||
.macro load_ppu_addr addr
|
||||
lda #.hibyte(addr)
|
||||
sta PPUADDR
|
||||
lda #.lobyte(addr)
|
||||
sta PPUADDR ; Load $2000
|
||||
.endmacro
|
||||
|
||||
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
|
||||
|
||||
; Fill NT0
|
||||
load_ppu_addr $2000
|
||||
ldx #$00
|
||||
ldy #$04
|
||||
lda #$00
|
||||
fill_loop:
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne fill_loop
|
||||
dey
|
||||
bne fill_loop
|
||||
; Set specific tiles
|
||||
load_ppu_addr $2184
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21A4
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E2
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C6
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E6
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $2204
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $2224
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21EA
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
lda #$00
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D2
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $3F00
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$20
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$09
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $2001
|
||||
lda #T_LINE
|
||||
sta PPUDATA
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
lda #%00001110
|
||||
sta PPUMASK
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
sta CTRL_BYTE
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
nmi:
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
|
||||
lda #$0A
|
||||
adc X_SCROLL
|
||||
sta X_SCROLL
|
||||
sta PPUSCROLL
|
||||
bcc y_scroll
|
||||
lda #$01
|
||||
eor CTRL_BYTE
|
||||
sta CTRL_BYTE
|
||||
sta PPUCTRL
|
||||
y_scroll:
|
||||
clc
|
||||
lda #$00
|
||||
adc Y_SCROLL
|
||||
sta Y_SCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda CTRL_BYTE
|
||||
sta PPUCTRL
|
||||
lda #%00001110
|
||||
sta PPUMASK ; Enable rendering
|
||||
rti
|
||||
exit:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
334
src/test_roms/scrolling_colors.s
Normal file
334
src/test_roms/scrolling_colors.s
Normal file
@@ -0,0 +1,334 @@
|
||||
|
||||
.include "testing.s"
|
||||
; patterns_bin "pat.bin"
|
||||
.macro chr b,s
|
||||
.repeat s
|
||||
.byte b
|
||||
.endrepeat
|
||||
.endmacro
|
||||
.pushseg
|
||||
.segment "CHARS"
|
||||
T_EMPTY = 0
|
||||
.repeat 2 ; Empty 0
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
T_TOP_LEFT = 1
|
||||
.repeat 2 ; Top Left 1
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_TOP_RIGHT = 2
|
||||
.repeat 2 ; Top Right 2
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
T_BOTTOM_RIGHT = 3
|
||||
.repeat 2 ; Bottom Right 3
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_BOTTOM_LEFT = 4
|
||||
.repeat 2 ; Bottom Left 4
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_FORWARD = 5
|
||||
.repeat 2 ; FW 5
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %00001110
|
||||
.byte %00011100
|
||||
.byte %00111000
|
||||
.byte %01110000
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_BACKWARD = 6
|
||||
.repeat 2 ; BW 6
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01110000
|
||||
.byte %00111000
|
||||
.byte %00011100
|
||||
.byte %00001110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
|
||||
T_PILL_L = 7
|
||||
.repeat 2 ; Pill L 7
|
||||
.byte %00111111
|
||||
.byte %01111111
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01111111
|
||||
.byte %00111111
|
||||
.endrepeat
|
||||
T_PILL_R = 8
|
||||
.repeat 2 ; Pill R 8
|
||||
.byte %11111100
|
||||
.byte %11111110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %11111110
|
||||
.byte %11111100
|
||||
.endrepeat
|
||||
|
||||
T_LINE = 9
|
||||
.repeat 2 ; Line
|
||||
.byte %00000000
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
.popseg
|
||||
|
||||
zp_res Y_SCROLL
|
||||
zp_res X_SCROLL
|
||||
zp_res CTRL_BYTE
|
||||
|
||||
.macro load_ppu_addr addr
|
||||
lda #.hibyte(addr)
|
||||
sta PPUADDR
|
||||
lda #.lobyte(addr)
|
||||
sta PPUADDR ; Load $2000
|
||||
.endmacro
|
||||
|
||||
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
|
||||
|
||||
; Fill NT0
|
||||
load_ppu_addr $2000
|
||||
ldx #$00
|
||||
ldy #$04
|
||||
lda #$00
|
||||
fill_loop:
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne fill_loop
|
||||
dey
|
||||
bne fill_loop
|
||||
; Set specific tiles
|
||||
load_ppu_addr $2184
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21A4
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E2
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C6
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E6
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $2204
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $2224
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21EA
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
lda #$00
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D2
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $3F00
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$20
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$09
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$1A
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$21
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $2000
|
||||
lda #T_TOP_LEFT
|
||||
ldx #$1F
|
||||
line_loop:
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne line_loop
|
||||
|
||||
load_ppu_addr $23C0
|
||||
ldx #$7
|
||||
line_color_loop:
|
||||
lda #%00000000
|
||||
sta PPUDATA
|
||||
lda #%00001111
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne line_color_loop
|
||||
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
lda #%00001110
|
||||
sta PPUMASK
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
sta CTRL_BYTE
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
nmi:
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
|
||||
lda #$01
|
||||
adc X_SCROLL
|
||||
sta X_SCROLL
|
||||
sta PPUSCROLL
|
||||
bcc y_scroll
|
||||
lda #$01
|
||||
eor CTRL_BYTE
|
||||
sta CTRL_BYTE
|
||||
sta PPUCTRL
|
||||
y_scroll:
|
||||
clc
|
||||
lda #$00
|
||||
adc Y_SCROLL
|
||||
sta Y_SCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda CTRL_BYTE
|
||||
sta PPUCTRL
|
||||
lda #%00001110
|
||||
sta PPUMASK ; Enable rendering
|
||||
rti
|
||||
exit:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
321
src/test_roms/sprites.s
Normal file
321
src/test_roms/sprites.s
Normal file
@@ -0,0 +1,321 @@
|
||||
.include "testing.s"
|
||||
|
||||
.pushseg
|
||||
.segment "CHARS"
|
||||
T_EMPTY = 0
|
||||
.repeat 2 ; Empty 0
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
T_TOP_LEFT = 1
|
||||
.repeat 2 ; Top Left 1
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_TOP_RIGHT = 2
|
||||
.repeat 2 ; Top Right 2
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
T_BOTTOM_RIGHT = 3
|
||||
.repeat 2 ; Bottom Right 3
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_BOTTOM_LEFT = 4
|
||||
.repeat 2 ; Bottom Left 4
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_FORWARD = 5
|
||||
.repeat 2 ; FW 5
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %00001110
|
||||
.byte %00011100
|
||||
.byte %00111000
|
||||
.byte %01110000
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_BACKWARD = 6
|
||||
.repeat 2 ; BW 6
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01110000
|
||||
.byte %00111000
|
||||
.byte %00011100
|
||||
.byte %00001110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
|
||||
T_PILL_L = 7
|
||||
.repeat 2 ; Pill L 7
|
||||
.byte %00111111
|
||||
.byte %01111111
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01111111
|
||||
.byte %00111111
|
||||
.endrepeat
|
||||
T_PILL_R = 8
|
||||
.repeat 2 ; Pill R 8
|
||||
.byte %11111100
|
||||
.byte %11111110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %11111110
|
||||
.byte %11111100
|
||||
.endrepeat
|
||||
|
||||
T_LINE = 9
|
||||
.repeat 2 ; Empty 0
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.byte %00100000
|
||||
.endrepeat
|
||||
|
||||
.popseg
|
||||
|
||||
zp_res Y_SCROLL
|
||||
zp_res X_SCROLL
|
||||
zp_res CTRL_BYTE
|
||||
|
||||
.macro load_ppu_addr addr
|
||||
lda #.hibyte(addr)
|
||||
sta PPUADDR
|
||||
lda #.lobyte(addr)
|
||||
sta PPUADDR ; Load $2000
|
||||
.endmacro
|
||||
|
||||
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
|
||||
|
||||
; Fill NT0
|
||||
load_ppu_addr $2000
|
||||
ldx #$00
|
||||
ldy #$04
|
||||
lda #$00
|
||||
fill_loop:
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne fill_loop
|
||||
dey
|
||||
bne fill_loop
|
||||
; Set specific tiles
|
||||
load_ppu_addr $2184
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21A4
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E2
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C6
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E6
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $2204
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $2224
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21EA
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
lda #$00
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D2
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $3F00
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$20
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$09
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $2001
|
||||
lda #T_LINE
|
||||
sta PPUDATA
|
||||
|
||||
ldx #$00
|
||||
sprite_loop:
|
||||
lda sprite_data,x
|
||||
sta $300,x
|
||||
inx
|
||||
cpx #(sprite_data_end - sprite_data)
|
||||
bne sprite_loop
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
lda #%00001110
|
||||
sta PPUMASK
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
sta CTRL_BYTE
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
nmi:
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
|
||||
lda #$01
|
||||
adc X_SCROLL
|
||||
sta X_SCROLL
|
||||
sta PPUSCROLL
|
||||
bcc y_scroll
|
||||
lda #$01
|
||||
eor CTRL_BYTE
|
||||
sta CTRL_BYTE
|
||||
sta PPUCTRL
|
||||
y_scroll:
|
||||
clc
|
||||
lda #$00
|
||||
adc Y_SCROLL
|
||||
sta Y_SCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda #$00
|
||||
sta SPRADDR
|
||||
lda #$03
|
||||
sta SPRDMA
|
||||
|
||||
lda CTRL_BYTE
|
||||
sta PPUCTRL
|
||||
lda #%00011110
|
||||
sta PPUMASK ; Enable rendering
|
||||
rti
|
||||
exit:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
|
||||
sprite_data:
|
||||
.byte $00
|
||||
.byte T_PILL_R
|
||||
.byte $00
|
||||
.byte $00
|
||||
sprite_data_end:
|
||||
@@ -1,9 +1,9 @@
|
||||
; FINAL = ""
|
||||
|
||||
.inesprg 2 ; 2 banks
|
||||
.ineschr 1 ;
|
||||
.inesmap 0 ; mapper 0 = NROM
|
||||
.inesmir 0 ; background mirroring, horizontal
|
||||
; .inesprg 2 ; 2 banks
|
||||
; .ineschr 1 ;
|
||||
; .inesmap 0 ; mapper 0 = NROM
|
||||
; .inesmir 0 ; background mirroring, horizontal
|
||||
|
||||
.org $8000
|
||||
RESET:
|
||||
|
||||
Reference in New Issue
Block a user