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}