tetanes_core/common.rs
1//! Common traits and constants.
2
3use enum_dispatch::enum_dispatch;
4use serde::{Deserialize, Serialize};
5use std::fmt::Write;
6use thiserror::Error;
7
8/// Default directory for save states.
9pub const SAVE_DIR: &str = "save";
10/// Default directory for save RAM.
11pub const SRAM_DIR: &str = "sram";
12
13#[derive(Error, Debug)]
14#[must_use]
15#[error("failed to parse `NesRegion`")]
16pub struct ParseNesRegionError;
17
18#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[must_use]
20pub enum NesRegion {
21 /// Auto-detect region based on ROM headers and a pre-built game database.
22 Auto,
23 /// NTSC, primarily North America.
24 #[default]
25 Ntsc,
26 /// PAL, primarily Japan and Europe.
27 Pal,
28 /// Dendy, primarily Russia.
29 Dendy,
30}
31
32impl NesRegion {
33 pub const fn as_slice() -> &'static [Self] {
34 &[
35 NesRegion::Auto,
36 NesRegion::Ntsc,
37 NesRegion::Pal,
38 NesRegion::Dendy,
39 ]
40 }
41
42 #[must_use]
43 pub const fn is_auto(&self) -> bool {
44 matches!(self, Self::Auto)
45 }
46
47 #[must_use]
48 pub const fn is_ntsc(&self) -> bool {
49 matches!(self, Self::Auto | Self::Ntsc)
50 }
51
52 #[must_use]
53 pub const fn is_pal(&self) -> bool {
54 matches!(self, Self::Pal)
55 }
56
57 #[must_use]
58 pub const fn is_dendy(&self) -> bool {
59 matches!(self, Self::Dendy)
60 }
61
62 #[must_use]
63 pub fn aspect_ratio(&self) -> f32 {
64 // https://www.nesdev.org/wiki/Overscan
65 match self {
66 Self::Auto | Self::Ntsc => 8.0 / 7.0,
67 Self::Pal | Self::Dendy => 18.0 / 13.0,
68 }
69 }
70
71 #[must_use]
72 pub const fn as_str(&self) -> &'static str {
73 match self {
74 Self::Auto => "auto",
75 Self::Ntsc => "ntsc",
76 Self::Pal => "pal",
77 Self::Dendy => "dendy",
78 }
79 }
80}
81
82impl std::fmt::Display for NesRegion {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 let s = match self {
85 Self::Auto => "Auto",
86 Self::Ntsc => "NTSC",
87 Self::Pal => "PAL",
88 Self::Dendy => "Dendy",
89 };
90 write!(f, "{s}")
91 }
92}
93
94impl AsRef<str> for NesRegion {
95 fn as_ref(&self) -> &str {
96 self.as_str()
97 }
98}
99
100impl TryFrom<&str> for NesRegion {
101 type Error = ParseNesRegionError;
102
103 fn try_from(value: &str) -> Result<Self, Self::Error> {
104 match value {
105 "auto" => Ok(Self::Auto),
106 "ntsc" => Ok(Self::Ntsc),
107 "pal" => Ok(Self::Pal),
108 "dendy" => Ok(Self::Dendy),
109 _ => Err(ParseNesRegionError),
110 }
111 }
112}
113
114impl TryFrom<usize> for NesRegion {
115 type Error = ParseNesRegionError;
116
117 fn try_from(value: usize) -> Result<Self, Self::Error> {
118 match value {
119 0 => Ok(Self::Auto),
120 1 => Ok(Self::Ntsc),
121 2 => Ok(Self::Pal),
122 3 => Ok(Self::Dendy),
123 _ => Err(ParseNesRegionError),
124 }
125 }
126}
127
128/// Trait for types that have different behavior depending on NES region.
129// NOTE: enum_dispatch requires absolute paths to types
130#[enum_dispatch(Mapper)]
131pub trait Regional {
132 /// Return the current region.
133 fn region(&self) -> crate::common::NesRegion {
134 crate::common::NesRegion::Ntsc
135 }
136 /// Set the region.
137 fn set_region(&mut self, _region: crate::common::NesRegion) {}
138}
139
140/// Type of reset for types that have different behavior for reset vs power cycling.
141#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
142#[must_use]
143pub enum ResetKind {
144 /// Soft reset generally doesn't zero-out most registers or RAM.
145 Soft,
146 /// Hard reset generally zeros-out most registers and RAM.
147 Hard,
148}
149
150/// Trait for types that can can be reset.
151#[enum_dispatch(Mapper)]
152// NOTE: enum_dispatch requires absolute paths to types
153pub trait Reset {
154 /// Reset the component given the [`ResetKind`].
155 fn reset(&mut self, _kind: crate::common::ResetKind) {}
156}
157
158/// Trait for types that can be clocked.
159#[enum_dispatch(Mapper)]
160pub trait Clock {
161 /// Clock component a single time, returning the number of cycles clocked.
162 fn clock(&mut self) -> u64 {
163 0
164 }
165}
166
167/// Trait for types that can clock to a target cycle.
168pub trait ClockTo {
169 /// Clock component to the given master cycle, returning the number of cycles clocked.
170 fn clock_to(&mut self, _cycle: u64) -> u64 {
171 0
172 }
173}
174
175/// Trait for types that can output `f32` audio samples.
176pub trait Sample {
177 /// Output a single audio sample.
178 fn output(&self) -> f32;
179}
180
181/// Trait for types that can save RAM to disk.
182#[enum_dispatch(Mapper)]
183// NOTE: enum_dispatch requires absolute paths to types
184pub trait Sram {
185 /// Save RAM to a given path.
186 fn save(&self, _path: impl AsRef<std::path::Path>) -> crate::fs::Result<()> {
187 Ok(())
188 }
189
190 /// Load save RAM from a given path.
191 fn load(&mut self, _path: impl AsRef<std::path::Path>) -> crate::fs::Result<()> {
192 Ok(())
193 }
194}
195
196/// Prints a hex dump of a given byte array starting at `addr_offset`.
197#[must_use]
198pub fn hexdump(data: &[u8], addr_offset: usize) -> Vec<String> {
199 use std::cmp;
200
201 let mut addr = 0;
202 let len = data.len();
203 let mut last_line_same = false;
204 let mut output = Vec::new();
205
206 let mut last_line = String::with_capacity(80);
207 while addr <= len {
208 let end = cmp::min(addr + 16, len);
209 let line_data = &data[addr..end];
210 let line_len = line_data.len();
211
212 let mut line = String::with_capacity(80);
213 for byte in line_data.iter() {
214 let _ = write!(line, " {byte:02X}");
215 }
216
217 if line_len % 16 > 0 {
218 let words_left = (16 - line_len) / 2;
219 for _ in 0..3 * words_left {
220 line.push(' ');
221 }
222 }
223
224 if line_len > 0 {
225 line.push_str(" |");
226 for c in line_data {
227 if (*c as char).is_ascii() && !(*c as char).is_control() {
228 let _ = write!(line, "{}", (*c as char));
229 } else {
230 line.push('.');
231 }
232 }
233 line.push('|');
234 }
235 if last_line == line {
236 if !last_line_same {
237 last_line_same = true;
238 output.push("*".to_string());
239 }
240 } else {
241 last_line_same = false;
242 output.push(format!("{:08x} {}", addr + addr_offset, line));
243 }
244 last_line = line;
245
246 addr += 16;
247 }
248 output
249}
250
251#[cfg(test)]
252pub(crate) mod tests {
253 use crate::{
254 action::Action,
255 common::{Regional, Reset, ResetKind},
256 control_deck::{Config, ControlDeck},
257 input::Player,
258 mem::RamState,
259 ppu::Ppu,
260 video::VideoFilter,
261 };
262 use anyhow::Context;
263 use image::{ImageBuffer, Rgba};
264 use serde::{Deserialize, Serialize};
265 use std::{
266 collections::hash_map::DefaultHasher,
267 env,
268 fmt::Write,
269 fs::{self, File},
270 hash::{Hash, Hasher},
271 io::{BufReader, Read},
272 path::{Path, PathBuf},
273 sync::OnceLock,
274 };
275 use tracing::debug;
276
277 pub(crate) const RESULT_DIR: &str = "test_results";
278
279 static PASS_DIR: OnceLock<PathBuf> = OnceLock::new();
280 static FAIL_DIR: OnceLock<PathBuf> = OnceLock::new();
281
282 #[macro_export]
283 macro_rules! test_roms {
284 ($mod:ident, $directory:expr, $( $(#[ignore = $reason:expr])? $test:ident ),* $(,)?) => {
285 mod $mod {$(
286 $(#[ignore = $reason])?
287 #[test]
288 fn $test() -> anyhow::Result<()> {
289 $crate::common::tests::test_rom($directory, stringify!($test))
290 }
291 )*}
292 };
293 }
294
295 // TODO: Instead of a bunch of optional fields, it should be an enum:
296 // enum FrameAction {
297 // DeckAction(DeckAction),
298 // FrameHash(u64),
299 // AudioHash(u64),
300 // }
301 #[derive(Default, Debug, Clone, Serialize, Deserialize)]
302 #[serde(default)]
303 #[must_use]
304 struct TestFrame {
305 number: u32,
306 #[serde(skip_serializing_if = "Option::is_none")]
307 name: Option<String>,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 hash: Option<u64>,
310 #[serde(skip_serializing_if = "Option::is_none")]
311 action: Option<Action>,
312 #[serde(skip_serializing)]
313 audio: bool,
314 }
315
316 #[derive(Debug, Clone, Serialize, Deserialize)]
317 #[must_use]
318 struct RomTest {
319 name: String,
320 #[serde(skip_serializing, default)]
321 audio: bool,
322 frames: Vec<TestFrame>,
323 }
324
325 fn get_rom_tests(directory: &str) -> anyhow::Result<(PathBuf, Vec<RomTest>)> {
326 let file = PathBuf::from(directory)
327 .join("tests")
328 .with_extension("json");
329 let mut content = String::with_capacity(1024);
330 File::open(&file)
331 .and_then(|mut file| file.read_to_string(&mut content))
332 .with_context(|| format!("failed to read rom test data: {file:?}"))?;
333 let tests = serde_json::from_str(&content)
334 .with_context(|| format!("valid rom test data: {file:?}"))?;
335 Ok((file, tests))
336 }
337
338 fn load_control_deck<P: AsRef<Path>>(path: P) -> ControlDeck {
339 let path = path.as_ref();
340 let mut rom = BufReader::new(File::open(path).expect("failed to open path"));
341 let mut deck = ControlDeck::with_config(Config {
342 ram_state: RamState::AllZeros,
343 filter: VideoFilter::Pixellate,
344 ..Default::default()
345 });
346 deck.load_rom(path.to_string_lossy(), &mut rom)
347 .expect("failed to load rom");
348 deck
349 }
350
351 fn on_frame_action(test_frame: &TestFrame, deck: &mut ControlDeck) {
352 if let Some(action) = test_frame.action {
353 debug!("{:?}", action);
354 match action {
355 Action::Reset(kind) => deck.reset(kind),
356 Action::MapperRevision(rev) => deck.set_mapper_revision(rev),
357 Action::SetVideoFilter(filter) => deck.set_filter(filter),
358 Action::SetNesRegion(format) => deck.set_region(format),
359 Action::Joypad((player, button)) => {
360 let joypad = deck.joypad_mut(player);
361 joypad.set_button(button, true);
362 }
363 Action::ToggleZapperConnected => deck.connect_zapper(!deck.zapper_connected()),
364 Action::ZapperAim((x, y)) => deck.aim_zapper(x, y),
365 Action::ZapperTrigger => deck.trigger_zapper(),
366 Action::LoadState
367 | Action::SaveState
368 | Action::SetSaveSlot(_)
369 | Action::ToggleApuChannel(_)
370 | Action::ZapperAimOffscreen
371 | Action::FourPlayer(_) => (),
372 }
373 }
374 }
375
376 fn on_snapshot(
377 test: &str,
378 test_frame: &TestFrame,
379 deck: &mut ControlDeck,
380 count: usize,
381 ) -> anyhow::Result<Option<(u64, u64, u32, PathBuf)>> {
382 match test_frame.hash {
383 Some(expected) => {
384 let mut hasher = DefaultHasher::new();
385 if test_frame.audio {
386 deck.audio_samples()
387 .iter()
388 .for_each(|s| s.to_le_bytes().hash(&mut hasher));
389 } else {
390 deck.frame_buffer().hash(&mut hasher);
391 }
392 let actual = hasher.finish();
393 debug!(
394 "frame: {}, matched: {}",
395 test_frame.number,
396 expected == actual
397 );
398
399 let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
400 let result_dir = if env::var("UPDATE_SNAPSHOT").is_ok() || expected == actual {
401 PASS_DIR.get_or_init(|| {
402 let directory = base_dir.join(PathBuf::from(RESULT_DIR)).join("pass");
403 if let Err(err) = fs::create_dir_all(&directory) {
404 panic!("created pass test results dir: {directory:?}. {err}",);
405 }
406 directory
407 })
408 } else {
409 FAIL_DIR.get_or_init(|| {
410 let directory = base_dir.join(PathBuf::from(RESULT_DIR)).join("fail");
411 if let Err(err) = fs::create_dir_all(&directory) {
412 panic!("created fail test results dir: {directory:?}. {err}",);
413 }
414 directory
415 })
416 };
417 let mut filename = test.to_owned();
418 if let Some(ref name) = test_frame.name {
419 let _ = write!(filename, "_{name}");
420 } else if count > 0 {
421 let _ = write!(filename, "_{}", count + 1);
422 }
423 let screenshot = result_dir
424 .join(PathBuf::from(filename))
425 .with_extension("png");
426
427 ImageBuffer::<Rgba<u8>, &[u8]>::from_raw(
428 Ppu::WIDTH,
429 Ppu::HEIGHT,
430 deck.frame_buffer(),
431 )
432 .expect("valid frame")
433 .save(&screenshot)
434 .with_context(|| format!("failed to save screenshot: {screenshot:?}"))?;
435
436 Ok(Some((expected, actual, test_frame.number, screenshot)))
437 }
438 None => Ok(None),
439 }
440 }
441
442 pub(crate) fn test_rom(directory: &str, test_name: &str) -> anyhow::Result<()> {
443 thread_local! {
444 static INIT_TESTS: OnceLock<bool> = const { OnceLock::new() };
445 }
446
447 let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
448 let initialized = INIT_TESTS.with(|init| {
449 *init.get_or_init(|| {
450 use tracing_subscriber::{
451 filter::Targets, fmt, layer::SubscriberExt, registry, util::SubscriberInitExt,
452 };
453 let _ = registry()
454 .with(
455 env::var("RUST_LOG")
456 .ok()
457 .and_then(|filter| filter.parse::<Targets>().ok())
458 .unwrap_or_default(),
459 )
460 .with(
461 fmt::layer()
462 .compact()
463 .with_line_number(true)
464 .with_thread_ids(true)
465 .with_thread_names(true)
466 .with_writer(std::io::stderr),
467 )
468 .try_init();
469 true
470 })
471 });
472 if initialized {
473 debug!("Initialized tests");
474 }
475
476 let (test_file, mut tests) = get_rom_tests(directory)?;
477 let mut test = tests.iter_mut().find(|test| test.name.eq(test_name));
478 assert!(test.is_some(), "No test found matching {test_name:?}");
479 let test = test.as_mut().expect("definitely has a test");
480
481 let rom = base_dir
482 .join(directory)
483 .join(PathBuf::from(&test.name))
484 .with_extension("nes");
485 assert!(rom.exists(), "No test rom found for {rom:?}");
486
487 let mut deck = load_control_deck(&rom);
488 deck.cpu_mut().bus.apu.skip_mixing = !test.audio;
489
490 let mut results = Vec::new();
491 assert!(!test.frames.is_empty(), "No test frames found for {rom:?}");
492 for test_frame in test.frames.iter() {
493 debug!("{} - {:?}", test_frame.number, deck.joypad_mut(Player::One));
494
495 while deck.frame_number() < test_frame.number {
496 deck.clock_frame().expect("valid frame clock");
497 if deck.frame_number() != test_frame.number && !test_frame.audio {
498 deck.clear_audio_samples();
499 }
500 deck.joypad_mut(Player::One).reset(ResetKind::Soft);
501 deck.joypad_mut(Player::Two).reset(ResetKind::Soft);
502 }
503
504 on_frame_action(test_frame, &mut deck);
505 if let Ok(Some(result)) = on_snapshot(&test.name, test_frame, &mut deck, results.len())
506 {
507 results.push(result);
508 }
509 }
510 let mut update_required = false;
511 for (mut expected, actual, frame_number, screenshot) in results {
512 if env::var("UPDATE_SNAPSHOT").is_ok() && expected != actual {
513 expected = actual;
514 update_required = true;
515 if let Some(frame) = &mut test
516 .frames
517 .iter_mut()
518 .find(|frame| frame.number == frame_number)
519 {
520 frame.hash = Some(actual);
521 }
522 }
523 assert!(
524 expected == actual,
525 "mismatched snapshot for {rom:?} -> {screenshot:?} (expected: {expected}, actual: {actual})",
526 );
527 }
528 if update_required {
529 File::create(&test_file)
530 .context("failed to open rom test file")
531 .and_then(|file| {
532 serde_json::to_writer_pretty(file, &tests)
533 .context("failed to serialize rom data")
534 })
535 .with_context(|| format!("failed to update snapshot: {test_file:?}"))?
536 }
537
538 Ok(())
539 }
540
541 test_roms!(
542 cpu,
543 "test_roms/cpu",
544 branch_backward, // Tests branches jumping backward
545 branch_basics, // Tests branch instructions, including edge cases
546 branch_forward, // Tests branches jumping forward
547 nestest, // Tests all CPU instructions, including illegal opcodes
548 // Verifies ram and registers are set/cleared correctly after reset
549 ram_after_reset,
550 regs_after_reset,
551 // Tests CPU dummy reads
552 dummy_reads,
553 dummy_writes_oam,
554 dummy_writes_ppumem,
555 // Verifies cpu can execute code from any memory location, incl. I/O
556 exec_space_apu,
557 exec_space_ppuio,
558 flag_concurrency,
559 // Tests CPU several instruction combinations
560 instr_abs,
561 instr_abs_xy,
562 instr_basics,
563 instr_branches,
564 instr_brk,
565 instr_imm,
566 instr_imp,
567 instr_ind_x,
568 instr_ind_y,
569 instr_jmp_jsr,
570 instr_misc,
571 instr_rti,
572 instr_rts,
573 instr_special,
574 instr_stack,
575 instr_timing,
576 instr_zp,
577 instr_zp_xy,
578 // Tests IRQ/NMI timings
579 int_branch_delays_irq,
580 int_cli_latency,
581 int_irq_and_dma,
582 int_nmi_and_brk,
583 int_nmi_and_irq,
584 overclock,
585 // Tests cycle stealing behavior of DMC DMA while running sprite DMAs
586 sprdma_and_dmc_dma,
587 sprdma_and_dmc_dma_512,
588 timing_test, // Tests CPU timing
589 );
590 test_roms!(
591 ppu,
592 "test_roms/ppu",
593 _240pee, // TODO: Run each test
594 color, // TODO: Test all color combinations
595 ntsc_torture, // Tests PPU NTSC signal artifacts
596 oam_read, // Tests OAM reading ($2004)
597 oam_stress, // Stresses OAM ($2003) reads and writes ($2004)
598 open_bus, // Tests PPU open bus behavior
599 palette, // Tests simple scanline palette changes
600 palette_ram, // Tests palette RAM access
601 read_buffer, // Thoroughly tests PPU read buffer ($2007)
602 scanline, // Tests scanline rendering
603 spr_hit_alignment, // Tests sprite hit alignment
604 spr_hit_basics, // Tests sprite hit basics
605 spr_hit_corners, // Tests sprite hit corners
606 spr_hit_double_height, // Tests sprite hit in x16 height mode
607 spr_hit_edge_timing, // Tests sprite hit edge timing
608 spr_hit_flip, // Tests sprite hit with sprite flip
609 spr_hit_left_clip, // Tests sprite hit with left edge clipped
610 spr_hit_right_edge, // Tests sprite hit right edge
611 spr_hit_screen_bottom, // Tests sprite hit bottom
612 spr_hit_timing_basics, // Tests sprite hit timing
613 spr_hit_timing_order, // Tests sprite hit order
614 spr_overflow_basics, // Tests sprite overflow basics
615 spr_overflow_details, // Tests more thorough sprite overflow
616 spr_overflow_emulator,
617 spr_overflow_obscure, // Tests obscure sprite overflow cases
618 spr_overflow_timing, // Tests sprite overflow timing
619 sprite_ram, // Tests sprite ram
620 tv, // Tests NTSC color and NTSC/PAL aspect ratio
621 vbl_nmi_basics, // Tests vblank NMI basics
622 vbl_nmi_clear_timing, // Tests vblank NMI clear timing
623 vbl_nmi_control, // Tests vblank NMI control
624 vbl_nmi_disable, // Tests vblank NMI disable
625 vbl_nmi_even_odd_frames, // Tests vblank NMI on even/odd frames
626 #[ignore = "clock is skipped too late relative to enabling BG Failed #3"]
627 vbl_nmi_even_odd_timing, // Tests vblank NMI even/odd frame timing
628 vbl_nmi_frame_basics, // Tests vblank NMI frame basics
629 vbl_nmi_off_timing, // Tests vblank NMI off timing
630 vbl_nmi_on_timing, // Tests vblank NMI on timing
631 vbl_nmi_set_time, // Tests vblank NMI set timing
632 vbl_nmi_suppression, // Tests vblank NMI supression
633 vbl_nmi_timing, // Tests vblank NMI timing
634 vbl_timing, // Tests vblank timing
635 vram_access, // Tests video RAM access
636 );
637 test_roms!(
638 apu,
639 "test_roms/apu",
640 // DMC DMA during $2007 read causes 2-3 extra $2007
641 // reads before real read.
642 //
643 // Number of extra reads depends in CPU-PPU
644 // synchronization at reset.
645 dmc_dma_2007_read,
646 // DMC DMA during $2007 write has no effect.
647 // Output:
648 // 22 11 22 AA 44 55 66 77
649 // 22 11 22 AA 44 55 66 77
650 // 22 11 22 AA 44 55 66 77
651 // 22 11 22 AA 44 55 66 77
652 // 22 11 22 AA 44 55 66 77
653 dmc_dma_2007_write,
654 // DMC DMA during $4016 read causes extra $4016
655 // read.
656 // Output:
657 // 08 08 07 08 08
658 dmc_dma_4016_read,
659 // Double read of $2007 sometimes ignores extra
660 // read, and puts odd things into buffer.
661 //
662 // Output (depends on CPU-PPU synchronization):
663 // 22 33 44 55 66
664 // 22 44 55 66 77 or
665 // 22 33 44 55 66 or
666 // 02 44 55 66 77 or
667 // 32 44 55 66 77 or
668 // 85CFD627 or F018C287 or 440EF923 or E52F41A5
669 dmc_dma_double_2007_read,
670 // Read of $2007 just before write behaves normally.
671 //
672 // Output:
673 // 33 11 22 33 09 55 66 77
674 // 33 11 22 33 09 55 66 77
675 dmc_dma_read_write_2007,
676 // This NES program demonstrates abusing the NTSC NES's sampled sound
677 // playback hardware as a scanline timer to split the screen twice
678 // without needing to use a mapper-generated IRQ.
679 dpcmletterbox,
680 // Blargg's APU tests
681 //
682 // Misc
683 // ----
684 // - The frame IRQ flag is cleared only when $4015 is read or $4017 is
685 // written with bit 6 set ($40 or $c0).
686
687 // - The IRQ handler is invoked at minimum 29833 clocks after writing $00
688 // to $4017 (assuming the frame IRQ flag isn't already set, and nothing
689 // else generates an IRQ during that time).
690
691 // - After reset or power-up, APU acts as if $4017 were written with $00
692 // from 9 to 12 clocks before first instruction begins. It is as if this
693 // occurs (this generates a 10 clock delay):
694
695 // lda #$00
696 // sta $4017 ; 1
697 // lda <0 ; 9 delay
698 // nop
699 // nop
700 // nop
701 // reset:
702 // ...
703
704 // - As shown, the frame irq flag is set three times in a row. Thus when
705 // polling it, always read $4015 an extra time after the flag is found to
706 // be set, to be sure it's clear afterwards,
707
708 // wait: bit $4015 ; V flag reflects frame IRQ flag
709 // bvc wait
710 // bit $4015 ; be sure irq flag is clear
711
712 // or better yet, clear it before polling it:
713
714 // bit $4015 ; clear flag first
715 // wait: bit $4015 ; V flag reflects frame IRQ flag
716 // bvc wait
717 //
718 // See:
719 // <https://github.com/christopherpow/nes-test-roms/tree/master/blargg_apu_2005.07.30>
720 //
721 // Tests basic length counter operation
722 // 1) Passed tests
723 // 2) Problem with length counter load or $4015
724 // 3) Problem with length table, timing, or $4015
725 // 4) Writing $80 to $4017 should clock length immediately
726 // 5) Writing $00 to $4017 shouldn't clock length immediately
727 // 6) Clearing enable bit in $4015 should clear length counter
728 // 7) When disabled via $4015, length shouldn't allow reloading
729 // 8) Halt bit should suspend length clocking
730 len_ctr,
731 // Tests all length table entries.
732 // 1) Passed
733 // 2) Failed. Prints four bytes $II $ee $cc $02 that indicate the length
734 // load value written (ll), the value that the emulator uses ($ee), and the
735 // correct value ($cc).
736 len_table,
737 // Tests basic operation of frame irq flag.
738 // 1) Tests passed
739 // 2) Flag shouldn't be set in $4017 mode $40
740 // 3) Flag shouldn't be set in $4017 mode $80
741 // 4) Flag should be set in $4017 mode $00
742 // 5) Reading flag clears it
743 // 6) Writing $00 or $80 to $4017 doesn't affect flag
744 // 7) Writing $40 or $c0 to $4017 clears flag
745 irq_flag,
746 // Clock Jitter
747 // ------------
748 // Changes to the mode by writing to $4017 only occur on *even* internal
749 // APU clocks; if written on an odd clock, the first step of the mode is
750 // delayed by one clock. At power-up and reset, the APU is randomly in an
751 // odd or even cycle with respect to the first clock of the first
752 // instruction executed by the CPU.
753
754 // ; assume even APU and CPU clocks occur together
755 // lda #$00
756 // sta $4017 ; mode begins in one clock
757 // sta <0 ; delay 3 clocks
758 // sta $4017 ; mode begins immediately
759 //
760 // Tests for APU clock jitter. Also tests basic timing of frame irq flag
761 // since it's needed to determine jitter.
762 // 1) Passed tests
763 // 2) Frame irq is set too soon
764 // 3) Frame irq is set too late
765 // 4) Even jitter not handled properly
766 // 5) Odd jitter not handled properly
767 clock_jitter,
768 // Mode 0 Timing
769 // -------------
770 // -5 lda #$00
771 // -3 sta $4017
772 // 0 (write occurs here)
773 // 1
774 // 2
775 // 3
776 // ...
777 // Step 1
778 // 7459 Clock linear
779 // ...
780 // Step 2
781 // 14915 Clock linear & length
782 // ...
783 // Step 3
784 // 22373 Clock linear
785 // ...
786 // Step 4
787 // 29830 Set frame irq
788 // 29831 Clock linear & length and set frame irq
789 // 29832 Set frame irq
790 // ...
791 // Step 1
792 // 37289 Clock linear
793 // ...
794 // etc.
795 //
796 // Return current jitter in A. Takes an even number of clocks. Tests length
797 // counter timing in mode 0.
798 // 1) Passed tests
799 // 2) First length is clocked too soon
800 // 3) First length is clocked too late
801 // 4) Second length is clocked too soon
802 // 5) Second length is clocked too late
803 // 6) Third length is clocked too soon
804 // 7) Third length is clocked too late
805 len_timing_mode0,
806 // Mode 1 Timing
807 // -------------
808 // -5 lda #$80
809 // -3 sta $4017
810 // 0 (write occurs here)
811 // Step 0
812 // 1 Clock linear & length
813 // 2
814 // ...
815 // Step 1
816 // 7459 Clock linear
817 // ...
818 // Step 2
819 // 14915 Clock linear & length
820 // ...
821 // Step 3
822 // 22373 Clock linear
823 // ...
824 // Step 4
825 // 29829 (do nothing)
826 // ...
827 // Step 0
828 // 37283 Clock linear & length
829 // ...
830 // etc.
831 //
832 // Tests length counter timing in mode 1.
833 // 1) Passed tests
834 // 2) First length is clocked too soon
835 // 3) First length is clocked too late
836 // 4) Second length is clocked too soon
837 // 5) Second length is clocked too late
838 // 6) Third length is clocked too soon
839 // 7) Third length is clocked too late
840 len_timing_mode1,
841 // Frame interrupt flag is set three times in a row 29831 clocks after
842 // writing $4017 with $00.
843 // 1) Success
844 // 2) Flag first set too soon
845 // 3) Flag first set too late
846 // 4) Flag last set too soon
847 // 5) Flag last set too late
848 irq_flag_timing,
849 // IRQ handler is invoked at minimum 29833 clocks after writing $00 to
850 // $4017.
851 // 1) Passed tests
852 // 2) Too soon
853 // 3) Too late
854 // 4) Never occurred
855 irq_timing,
856 // After reset or power-up, APU acts as if $4017 were written with $00 from
857 // 9 to 12 clocks before first instruction begins.
858 // 1) Success
859 // 2) $4015 didn't read back as $00 at power-up
860 // 3) Fourth step occurs too soon
861 // 4) Fourth step occurs too late
862 reset_timing,
863 // Changes to length counter halt occur after clocking length, not before.
864 // 1) Passed tests
865 // 2) Length shouldn't be clocked when halted at 14914
866 // 3) Length should be clocked when halted at 14915
867 // 4) Length should be clocked when unhalted at 14914
868 // 5) Length shouldn't be clocked when unhalted at 14915
869 len_halt_timing,
870 // Write to length counter reload should be ignored when made during length
871 // counter clocking and the length counter is not zero.
872 // 1) Passed tests
873 // 2) Reload just before length clock should work normally
874 // 3) Reload just after length clock should work normally
875 // 4) Reload during length clock when ctr = 0 should work normally
876 // 5) Reload during length clock when ctr > 0 should be ignored
877 len_reload_timing,
878 // Verifies timing of length counter clocks in both modes
879 // 2) First length of mode 0 is too soon
880 // 3) First length of mode 0 is too late
881 // 4) Second length of mode 0 is too soon
882 // 5) Second length of mode 0 is too late
883 // 6) Third length of mode 0 is too soon
884 // 7) Third length of mode 0 is too late
885 // 8) First length of mode 1 is too soon
886 // 9) First length of mode 1 is too late
887 // 10) Second length of mode 1 is too soon
888 // 11) Second length of mode 1 is too late
889 // 12) Third length of mode 1 is too soon
890 // 13) Third length of mode 1 is too late
891 len_timing,
892 // Verifies basic DMC operation
893 // 2) DMC isn't working well enough to test further
894 // 3) Starting DMC should reload length from $4013
895 // 4) Writing $10 to $4015 should restart DMC if previous sample finished
896 // 5) Writing $10 to $4015 should not affect DMC if previous sample is
897 // still playing
898 // 6) Writing $00 to $4015 should stop current sample
899 // 7) Changing $4013 shouldn't affect current sample length
900 // 8) Shouldn't set DMC IRQ flag when flag is disabled
901 // 9) Should set IRQ flag when enabled and sample ends
902 // 10) Reading IRQ flag shouldn't clear it
903 // 11) Writing to $4015 should clear IRQ flag
904 // 12) Disabling IRQ flag should clear it
905 // 13) Looped sample shouldn't end until $00 is written to $4015
906 // 14) Looped sample shouldn't ever set IRQ flag
907 // 15) Clearing loop flag and then setting again shouldn't stop loop
908 // 16) Clearing loop flag should end sample once it reaches end
909 // 17) Looped sample should reload length from $4013 each time it reaches
910 // end
911 // 18) $4013=0 should give 1-byte sample
912 // 19) There should be a one-byte buffer that's filled immediately if empty
913 dmc_basics,
914 // Verifies the DMC's 16 rates
915 dmc_rates,
916 // Reset
917 // See: <https://github.com/christopherpow/nes-test-roms/tree/master/apu_reset>
918 //
919 // At power and reset, $4015 is cleared.
920 // 2) At power, $4015 should be cleared
921 // 3) At reset, $4015 should be cleared
922 reset_4015_cleared,
923 // At power, it is as if $00 were written to $4017,
924 // then a 9-12 clock delay, then execution from address
925 // in reset vector.
926
927 // At reset, same as above, except last value written
928 // to $4017 is written again, rather than $00.
929
930 // The delay from when $00 was written to $4017 is
931 // printed. Delay after NES being powered off for a
932 // minute is usually 9.
933
934 // 2) Frame IRQ flag should be set later after power/reset
935 // 3) Frame IRQ flag should be set sooner after power/reset
936 reset_4017_timing,
937 // At power, $4017 = $00.
938 // At reset, $4017 mode is unchanged, but IRQ inhibit
939 // flag is sometimes cleared.
940
941 // 2) At power, $4017 should be written with $00
942 // 3) At reset, $4017 should should be rewritten with last value written
943 reset_4017_written,
944 // At power and reset, IRQ flag is clear.
945
946 // 2) At power, flag should be clear
947 // 3) At reset, flag should be clear
948 reset_irq_flag_cleared,
949 // At power and reset, length counters are enabled.
950
951 // 2) At power, length counters should be enabled
952 // 3) At reset, length counters should be enabled, triangle unaffected
953 reset_len_ctrs_enabled,
954 // At power and reset, $4017, $4015, and length counters work
955 // immediately.
956
957 // 2) At power, writes should work immediately
958 // 3) At reset, writes should work immediately
959 reset_works_immediately,
960 // 11 tests that verify a number of behaviors with the APU (including the frame counter)
961 //
962 // See: <https://forums.nesdev.org/viewtopic.php?f=3&t=11174>
963 test_1,
964 test_2,
965 test_3,
966 test_4,
967 test_5,
968 test_6,
969 test_7,
970 test_8,
971 test_9,
972 test_10,
973 // PAL APU tests
974 //
975 // See: <https://github.com/christopherpow/nes-test-roms/tree/master/pal_apu_tests>
976 //
977 // Tests basic length counter operation
978 // 1) Passed tests
979 // 2) Problem with length counter load or $4015
980 // 3) Problem with length table, timing, or $4015
981 // 4) Writing $80 to $4017 should clock length immediately
982 // 5) Writing $00 to $4017 shouldn't clock length immediately
983 // 6) Clearing enable bit in $4015 should clear length counter
984 // 7) When disabled via $4015, length shouldn't allow reloading
985 // 8) Halt bit should suspend length clocking
986 pal_len_ctr,
987 // Tests all length table entries.
988 // 1) Passed
989 // 2) Failed. Prints four bytes $II $ee $cc $02 that indicate the length load
990 // value written (ll), the value that the emulator uses ($ee), and the correct
991 // value ($cc).
992 pal_len_table,
993 // Tests basic operation of frame irq flag.
994 // 1) Tests passed
995 // 2) Flag shouldn't be set in $4017 mode $40
996 // 3) Flag shouldn't be set in $4017 mode $80
997 // 4) Flag should be set in $4017 mode $00
998 // 5) Reading flag clears it
999 // 6) Writing $00 or $80 to $4017 doesn't affect flag
1000 // 7) Writing $40 or $c0 to $4017 clears flag
1001 pal_irq_flag,
1002 // Tests for APU clock jitter. Also tests basic timing of frame irq flag since
1003 // it's needed to determine jitter. It's OK if you don't implement jitter, in
1004 // which case you'll get error #5, but you can still run later tests without
1005 // problem.
1006 // 1) Passed tests
1007 // 2) Frame irq is set too soon
1008 // 3) Frame irq is set too late
1009 // 4) Even jitter not handled properly
1010 // 5) Odd jitter not handled properly
1011 pal_clock_jitter,
1012 // Tests length counter timing in mode 0.
1013 // 1) Passed tests
1014 // 2) First length is clocked too soon
1015 // 3) First length is clocked too late
1016 // 4) Second length is clocked too soon
1017 // 5) Second length is clocked too late
1018 // 6) Third length is clocked too soon
1019 // 7) Third length is clocked too late
1020 pal_len_timing_mode0,
1021 // Tests length counter timing in mode 1.
1022 // 1) Passed tests
1023 // 2) First length is clocked too soon
1024 // 3) First length is clocked too late
1025 // 4) Second length is clocked too soon
1026 // 5) Second length is clocked too late
1027 // 6) Third length is clocked too soon
1028 // 7) Third length is clocked too late
1029 pal_len_timing_mode1,
1030 // Frame interrupt flag is set three times in a row 33255 clocks after writing
1031 // $4017 with $00.
1032 // 1) Success
1033 // 2) Flag first set too soon
1034 // 3) Flag first set too late
1035 // 4) Flag last set too soon
1036 // 5) Flag last set too late
1037 pal_irq_flag_timing,
1038 // IRQ handler is invoked at minimum 33257 clocks after writing $00 to $4017.
1039 // 1) Passed tests
1040 // 2) Too soon
1041 // 3) Too late
1042 // 4) Never occurred
1043 pal_irq_timing,
1044 // Changes to length counter halt occur after clocking length, not before.
1045 // 1) Passed tests
1046 // 2) Length shouldn't be clocked when halted at 16628
1047 // 3) Length should be clocked when halted at 16629
1048 // 4) Length should be clocked when unhalted at 16628
1049 // 5) Length shouldn't be clocked when unhalted at 16629
1050 pal_len_halt_timing,
1051 // Write to length counter reload should be ignored when made during length
1052 // counter clocking and the length counter is not zero.
1053 // 1) Passed tests
1054 // 2) Reload just before length clock should work normally
1055 // 3) Reload just after length clock should work normally
1056 // 4) Reload during length clock when ctr = 0 should work normally
1057 // 5) Reload during length clock when ctr > 0 should be ignored
1058 pal_len_reload_timing,
1059 #[ignore = "todo: passes, compare output"]
1060 apu_env,
1061 #[ignore = "todo: passes, check status"]
1062 dmc_buffer_retained,
1063 #[ignore = "todo: passes, compare output"]
1064 dmc_latency,
1065 #[ignore = "todo: passes, compare output"]
1066 dmc_pitch,
1067 #[ignore = "todo: passes, check status"]
1068 dmc_status,
1069 #[ignore = "todo: passes, check status"]
1070 dmc_status_irq,
1071 #[ignore = "todo: passes, compare output"]
1072 lin_ctr,
1073 #[ignore = "todo: passes, compare output"]
1074 noise_pitch,
1075 // Tests pulse behavior when writing to $4003/$4007 (reset duty but not dividers)
1076 #[ignore = "todo: unknown, compare output"]
1077 phase_reset,
1078 #[ignore = "todo: passes, compare output"]
1079 square_pitch,
1080 #[ignore = "todo: passes, compare output"]
1081 sweep_cutoff,
1082 #[ignore = "todo: passes, compare output"]
1083 sweep_sub,
1084 #[ignore = "todo: passes, compare output"]
1085 triangle_pitch,
1086 // This program demonstrates the channel balance among implementations
1087 // of the NES architecture.
1088
1089 // The pattern consists of a set of 12 tones, as close to 1000 Hz as
1090 // the NES allows:
1091 // 1. Channel 1, 1/8 duty
1092 // 2. Channel 1, 1/4 duty
1093 // 3. Channel 1, 1/2 duty
1094 // 4. Channel 1, 3/4 duty
1095 // 5. Channels 1 and 2, 1/8 duty
1096 // 6. Channels 1 and 2, 1/4 duty
1097 // 7. Channels 1 and 2, 1/2 duty
1098 // 8. Channels 1 and 2, 3/4 duty
1099 // 9. Channel 3
1100 // 10. Channel 4, long LFSR period
1101 // 11. Channel 4, short LFSR period
1102 // 12. Channel 5, amplitude 30
1103
1104 // When the user presses A on controller 1, the pattern plays three
1105 // times, with channel 5 held steady at 0, 48, and 96. The high point
1106 // of tone 12 each time is 30 units above the level for that time,
1107 // that is, 30, 78, and 126 respectively.
1108 //
1109 // See: <https://github.com/christopherpow/nes-test-roms/tree/master/volume_tests>
1110 #[ignore = "todo: unknown, compare output"]
1111 volumes,
1112 // Mixer
1113 // The test status is written to $6000. $80 means the test is running, $81
1114 // means the test needs the reset button pressed, but delayed by at least
1115 // 100 msec from now. $00-$7F means the test has completed and given that
1116 // result code.
1117
1118 // To allow an emulator to know when one of these tests is running and the
1119 // data at $6000+ is valid, as opposed to some other NES program, $DE $B0
1120 // $G1 is written to $6001-$6003.
1121 //
1122 // A byte is reported as a series of tones. The code is in binary, with a
1123 // low tone for 0 and a high tone for 1, and with leading zeroes skipped.
1124 // The first tone is always a zero. A final code of 0 means passed, 1 means
1125 // failure, and 2 or higher indicates a specific reason. See the source
1126 // code of the test for more information about the meaning of a test code.
1127 // They are found after the set_test macro. For example, the cause of test
1128 // code 3 would be found in a line containing set_test 3. Examples:
1129
1130 // Tones Binary Decimal Meaning
1131 // - - - - - - - - - - - - - - - - - - - -
1132 // low 0 0 passed
1133 // low high 01 1 failed
1134 // low high low 010 2 error 2
1135 //
1136 // See <https://github.com/christopherpow/nes-test-roms/tree/master/apu_mixer>
1137 #[ignore = "todo: passes, compare $6000 output"]
1138 dmc,
1139 #[ignore = "todo: passes, compare $6000 output"]
1140 noise,
1141 #[ignore = "todo: passes, compare $6000 output"]
1142 square,
1143 #[ignore = "todo: passes, compare $6000 output"]
1144 triangle,
1145 );
1146 test_roms!(
1147 input,
1148 "test_roms/input",
1149 zapper_flip,
1150 zapper_light,
1151 #[ignore = "todo"]
1152 zapper_stream,
1153 #[ignore = "todo"]
1154 zapper_trigger,
1155 );
1156 test_roms!(
1157 m004_txrom,
1158 "test_roms/mapper/m004_txrom",
1159 a12_clocking,
1160 clocking,
1161 details,
1162 rev_b,
1163 scanline_timing,
1164 big_chr_ram,
1165 rev_a,
1166 );
1167 test_roms!(m005_exram, "test_roms/mapper/m005_exrom", exram, basics);
1168}