├── .gitignore ├── .gitmodules ├── src ├── lib.rs ├── mapper │ ├── null.rs │ ├── cnrom.rs │ ├── unrom.rs │ ├── mod.rs │ ├── mmc1.rs │ └── mmc3.rs ├── consts.rs ├── util.rs ├── palette.rs ├── rom.rs ├── nes.rs ├── memory.rs ├── context.rs ├── ppu.rs ├── apu.rs └── cpu.rs ├── README.md ├── Cargo.toml ├── LICENSE ├── tests ├── nestest.rs └── nes_test_roms.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "nes-test-roms"] 2 | path = nes-test-roms 3 | url = https://github.com/christopherpow/nes-test-roms 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod apu; 2 | pub mod consts; 3 | pub mod context; 4 | pub mod cpu; 5 | pub mod mapper; 6 | pub mod memory; 7 | pub mod nes; 8 | pub mod palette; 9 | pub mod ppu; 10 | pub mod rom; 11 | pub mod util; 12 | 13 | pub use nes::{Config, Nes}; 14 | pub use rom::Rom; 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sabicom 2 | 3 | A portable NES / Famicom emulation library written in Rust. 4 | 5 | # Features 6 | 7 | * Full NES hardware 8 | 9 | * Mappers 10 | * NROM (0) 11 | * MMC1 (1) 12 | * UxROM (2) 13 | * CNROM (3) 14 | * MMC3 (4) 15 | 16 | # License 17 | 18 | [MIT](LICENSE) 19 | -------------------------------------------------------------------------------- /src/mapper/null.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub struct NullMapper; 5 | 6 | impl NullMapper { 7 | pub fn new(_ctx: &mut impl super::Context) -> Self { 8 | Self 9 | } 10 | } 11 | 12 | impl super::MapperTrait for NullMapper {} 13 | -------------------------------------------------------------------------------- /src/consts.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | pub const PPU_CLOCK_PER_LINE: u64 = 341; 4 | pub const PPU_CLOCK_PER_FRAME: u64 = PPU_CLOCK_PER_LINE * LINES_PER_FRAME as u64; 5 | pub const PPU_CLOCK_PER_CPU_CLOCK: u64 = 3; 6 | 7 | pub const SCREEN_RANGE: Range = 0..240; 8 | pub const VBLANK_LINES: usize = 20; 9 | pub const POST_RENDER_LINE: usize = 240; 10 | pub const PRE_RENDER_LINE: usize = 261; 11 | pub const LINES_PER_FRAME: usize = SCREEN_RANGE.end - SCREEN_RANGE.start + VBLANK_LINES + 1 + 1; 12 | 13 | pub const SCREEN_WIDTH: usize = 256; 14 | pub const SCREEN_HEIGHT: usize = 240; 15 | -------------------------------------------------------------------------------- /src/mapper/cnrom.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub struct Cnrom; 5 | 6 | impl Cnrom { 7 | pub fn new(ctx: &mut impl super::Context) -> Self { 8 | for i in 0..4 { 9 | ctx.map_prg(i, i); 10 | } 11 | for i in 0..8 { 12 | ctx.map_chr(i, i); 13 | } 14 | Self 15 | } 16 | } 17 | 18 | impl super::MapperTrait for Cnrom { 19 | fn write_prg(&mut self, ctx: &mut impl super::Context, _addr: u16, data: u8) { 20 | for i in 0..8 { 21 | ctx.map_chr(i, data as u32 * 8 + i); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | macro_rules! trait_alias { 4 | (pub trait $name:ident = $($traits:tt)+) => { 5 | pub trait $name: $($traits)* {} 6 | impl $name for T {} 7 | }; 8 | } 9 | pub(crate) use trait_alias; 10 | 11 | #[derive(Default, Clone, Debug, Serialize, Deserialize)] 12 | pub struct Input { 13 | pub pad: [Pad; 2], 14 | } 15 | 16 | #[derive(Default, Clone, Debug, Serialize, Deserialize)] 17 | pub struct Pad { 18 | pub up: bool, 19 | pub down: bool, 20 | pub left: bool, 21 | pub right: bool, 22 | pub a: bool, 23 | pub b: bool, 24 | pub start: bool, 25 | pub select: bool, 26 | } 27 | -------------------------------------------------------------------------------- /src/mapper/unrom.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub struct Unrom; 5 | 6 | impl Unrom { 7 | pub fn new(ctx: &mut impl super::Context) -> Self { 8 | let prg_pages = ctx.memory_ctrl().prg_pages() as u32; 9 | ctx.map_prg(0, 0); 10 | ctx.map_prg(1, 1); 11 | ctx.map_prg(2, prg_pages - 2); 12 | ctx.map_prg(3, prg_pages - 1); 13 | Self 14 | } 15 | } 16 | 17 | impl super::MapperTrait for Unrom { 18 | fn write_prg(&mut self, ctx: &mut impl super::Context, _addr: u16, data: u8) { 19 | ctx.map_prg(0, data as u32 * 2); 20 | ctx.map_prg(1, data as u32 * 2 + 1); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sabicom" 3 | version = "0.2.0" 4 | edition = "2021" 5 | authors = ["Hideyuki Tanaka "] 6 | license = "MIT" 7 | description = "NES emulator" 8 | repository = "https://github.com/tanakh/sabicom" 9 | readme = "README.md" 10 | categories = ["emulators"] 11 | keywords = ["emulators", "nes"] 12 | 13 | [dependencies] 14 | meru-interface = "0.3.0" 15 | 16 | ambassador = "0.3.2" 17 | bincode = "1.3.3" 18 | bitvec = "1.0.1" 19 | bytesize = "1.1.0" 20 | chrono = "0.4.22" 21 | crc32fast = "1.3.2" 22 | log = "0.4.17" 23 | schemars = { version = "0.8.10", features = ["schemars_derive"] } 24 | serde = "1.0.144" 25 | thiserror = "1.0.33" 26 | 27 | [dev-dependencies] 28 | anyhow = "1.0.63" 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Hideyuki Tanaka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/palette.rs: -------------------------------------------------------------------------------- 1 | use meru_interface::Color; 2 | 3 | macro_rules! colors { 4 | ($({ $r:expr, $g:expr, $b:expr },) *) => { 5 | [ 6 | $(Color::new($r, $g, $b),)* 7 | ] 8 | }; 9 | } 10 | 11 | pub const NES_PALETTE: [Color; 0x40] = colors! { 12 | {0x75,0x75,0x75}, {0x27,0x1B,0x8F}, {0x00,0x00,0xAB}, {0x47,0x00,0x9F}, 13 | {0x8F,0x00,0x77}, {0xAB,0x00,0x13}, {0xA7,0x00,0x00}, {0x7F,0x0B,0x00}, 14 | {0x43,0x2F,0x00}, {0x00,0x47,0x00}, {0x00,0x51,0x00}, {0x00,0x3F,0x17}, 15 | {0x1B,0x3F,0x5F}, {0x00,0x00,0x00}, {0x05,0x05,0x05}, {0x05,0x05,0x05}, 16 | 17 | {0xBC,0xBC,0xBC}, {0x00,0x73,0xEF}, {0x23,0x3B,0xEF}, {0x83,0x00,0xF3}, 18 | {0xBF,0x00,0xBF}, {0xE7,0x00,0x5B}, {0xDB,0x2B,0x00}, {0xCB,0x4F,0x0F}, 19 | {0x8B,0x73,0x00}, {0x00,0x97,0x00}, {0x00,0xAB,0x00}, {0x00,0x93,0x3B}, 20 | {0x00,0x83,0x8B}, {0x11,0x11,0x11}, {0x09,0x09,0x09}, {0x09,0x09,0x09}, 21 | 22 | {0xFF,0xFF,0xFF}, {0x3F,0xBF,0xFF}, {0x5F,0x97,0xFF}, {0xA7,0x8B,0xFD}, 23 | {0xF7,0x7B,0xFF}, {0xFF,0x77,0xB7}, {0xFF,0x77,0x63}, {0xFF,0x9B,0x3B}, 24 | {0xF3,0xBF,0x3F}, {0x83,0xD3,0x13}, {0x4F,0xDF,0x4B}, {0x58,0xF8,0x98}, 25 | {0x00,0xEB,0xDB}, {0x66,0x66,0x66}, {0x0D,0x0D,0x0D}, {0x0D,0x0D,0x0D}, 26 | 27 | {0xFF,0xFF,0xFF}, {0xAB,0xE7,0xFF}, {0xC7,0xD7,0xFF}, {0xD7,0xCB,0xFF}, 28 | {0xFF,0xC7,0xFF}, {0xFF,0xC7,0xDB}, {0xFF,0xBF,0xB3}, {0xFF,0xDB,0xAB}, 29 | {0xFF,0xE7,0xA3}, {0xE3,0xFF,0xA3}, {0xAB,0xF3,0xBF}, {0xB3,0xFF,0xCF}, 30 | {0x9F,0xFF,0xF3}, {0xDD,0xDD,0xDD}, {0x11,0x11,0x11}, {0x11,0x11,0x11}, 31 | }; 32 | -------------------------------------------------------------------------------- /src/mapper/mod.rs: -------------------------------------------------------------------------------- 1 | mod cnrom; 2 | mod mmc1; 3 | mod mmc3; 4 | mod null; 5 | mod unrom; 6 | 7 | use ambassador::{delegatable_trait, Delegate}; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | use crate::{context, nes::Error, util::trait_alias}; 11 | 12 | trait_alias!(pub trait Context = context::MemoryController + context::Rom + context::Interrupt); 13 | 14 | #[delegatable_trait] 15 | pub trait MapperTrait { 16 | fn read_prg(&self, ctx: &impl Context, addr: u16) -> u8 { 17 | ctx.read_prg(addr) 18 | } 19 | 20 | fn write_prg(&mut self, ctx: &mut impl Context, addr: u16, data: u8) { 21 | ctx.write_prg(addr, data); 22 | } 23 | 24 | fn read_chr(&mut self, ctx: &mut impl Context, addr: u16) -> u8 { 25 | ctx.read_chr(addr) 26 | } 27 | 28 | fn write_chr(&mut self, ctx: &mut impl Context, addr: u16, data: u8) { 29 | ctx.write_chr(addr, data); 30 | } 31 | 32 | fn tick(&mut self, _ctx: &mut impl Context) {} 33 | } 34 | 35 | macro_rules! def_mapper { 36 | ($($id:expr => $constr:ident($ty:ty),)*) => { 37 | #[derive(Delegate, Serialize, Deserialize)] 38 | #[delegate(MapperTrait)] 39 | pub enum Mapper { 40 | $( 41 | $constr($ty), 42 | )* 43 | } 44 | 45 | pub fn create_mapper(ctx: &mut impl Context) -> Result { 46 | let mapper_id = ctx.rom().mapper_id; 47 | Ok(match mapper_id { 48 | $( 49 | $id => Mapper::$constr(<$ty>::new(ctx)), 50 | )* 51 | _ => Err(Error::UnsupportedMapper(mapper_id))?, 52 | }) 53 | } 54 | } 55 | } 56 | 57 | def_mapper! { 58 | 0 => NullMapper(null::NullMapper), 59 | 1 => Mmc1(mmc1::Mmc1), 60 | 2 => Unrom(unrom::Unrom), 61 | 3 => Cnrom(cnrom::Cnrom), 62 | 4 => Mmc3(mmc3::Mmc3), 63 | } 64 | -------------------------------------------------------------------------------- /tests/nestest.rs: -------------------------------------------------------------------------------- 1 | use meru_interface::EmulatorCore; 2 | use sabicom::{context::Cpu, Nes}; 3 | 4 | #[test] 5 | fn test_nestest() -> anyhow::Result<()> { 6 | use std::fmt::Write; 7 | use std::sync::Mutex; 8 | 9 | #[derive(Debug, Default)] 10 | struct NestestLogger(Mutex); 11 | 12 | impl log::Log for NestestLogger { 13 | fn enabled(&self, metadata: &log::Metadata) -> bool { 14 | metadata.target() == "disasm-nestest" && metadata.level() <= log::Level::Trace 15 | } 16 | 17 | fn log(&self, record: &log::Record) { 18 | if self.enabled(record.metadata()) { 19 | writeln!(self.0.lock().unwrap(), "{}", record.args()).unwrap(); 20 | } 21 | } 22 | 23 | fn flush(&self) {} 24 | } 25 | 26 | static LOGGER: NestestLogger = NestestLogger(Mutex::new(String::new())); 27 | 28 | log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::Trace))?; 29 | 30 | let path = "./nes-test-roms/other/nestest.nes"; 31 | let dat = std::fs::read(std::path::Path::new(path))?; 32 | let mut nes = Nes::try_from_file(&dat, None, &Default::default())?; 33 | 34 | // nestest.nes batch mode is start at 0xC000 35 | nes.ctx.cpu_mut().set_pc(0xC000); 36 | 37 | nes.exec_frame(false); 38 | 39 | let my_output = LOGGER.0.lock().unwrap(); 40 | 41 | const REFERENCE_OUTPUT: &str = include_str!("../nes-test-roms/other/nestest.log"); 42 | 43 | let my = my_output.lines().collect::>(); 44 | let ref_ = REFERENCE_OUTPUT.lines().take(8980).collect::>(); 45 | 46 | assert!(my.len() >= ref_.len()); 47 | 48 | for i in 0..ref_.len() { 49 | if ref_[i] != my[i] { 50 | for j in (0..i).rev().take(5).rev() { 51 | println!(" {} | {}", my[j], ref_[j]); 52 | } 53 | println!("> {} | {}", my[i], ref_[i]); 54 | for j in (i + 1..).take(5) { 55 | println!(" {} | {}", my[j], ref_[j]); 56 | } 57 | } 58 | 59 | assert_eq!(ref_[i], my[i]); 60 | } 61 | 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /src/mapper/mmc1.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::rom::Mirroring; 4 | 5 | #[derive(Serialize, Deserialize)] 6 | pub struct Mmc1 { 7 | prg_rom_bank_mode: PrgRomBankMode, 8 | chr_rom_bank_mode: ChrRomBankMode, 9 | buf: u8, 10 | cnt: usize, 11 | } 12 | 13 | #[derive(Serialize, Deserialize)] 14 | enum PrgRomBankMode { 15 | Switch32K, 16 | Switch16KLow, 17 | Switch16KHigh, 18 | } 19 | 20 | #[derive(Serialize, Deserialize)] 21 | enum ChrRomBankMode { 22 | Switch8K, 23 | Switch4K, 24 | } 25 | 26 | impl Mmc1 { 27 | pub fn new(ctx: &mut impl super::Context) -> Self { 28 | let prg_pages = ctx.memory_ctrl().prg_pages(); 29 | ctx.map_prg(0, 0); 30 | ctx.map_prg(1, 1); 31 | ctx.map_prg(2, prg_pages - 2); 32 | ctx.map_prg(3, prg_pages - 1); 33 | 34 | Self { 35 | prg_rom_bank_mode: PrgRomBankMode::Switch16KLow, 36 | chr_rom_bank_mode: ChrRomBankMode::Switch8K, 37 | buf: 0, 38 | cnt: 0, 39 | } 40 | } 41 | } 42 | 43 | impl super::MapperTrait for Mmc1 { 44 | fn write_prg(&mut self, ctx: &mut impl super::Context, addr: u16, data: u8) { 45 | if addr & 0x8000 == 0 { 46 | ctx.write_prg(addr, data); 47 | return; 48 | } 49 | 50 | log::trace!("MMC1: {addr:04X} <- {data:02X}"); 51 | 52 | if data & 0x80 != 0 { 53 | log::trace!("MMC1: Reset"); 54 | self.buf = 0; 55 | self.cnt = 0; 56 | return; 57 | } 58 | 59 | self.buf |= (data & 1) << self.cnt; 60 | self.cnt += 1; 61 | 62 | if self.cnt < 5 { 63 | return; 64 | } 65 | 66 | let cmd = self.buf; 67 | self.buf = 0; 68 | self.cnt = 0; 69 | 70 | let reg_num = (addr >> 13) & 3; 71 | 72 | log::trace!("MMC1: reg[{reg_num}] <- ${cmd:02X} (b{cmd:05b})"); 73 | 74 | match reg_num { 75 | 0 => { 76 | ctx.memory_ctrl_mut().set_mirroring(match cmd & 0x3 { 77 | 0 => Mirroring::OneScreenLow, 78 | 1 => Mirroring::OneScreenHigh, 79 | 2 => Mirroring::Vertical, 80 | 3 => Mirroring::Horizontal, 81 | _ => unreachable!(), 82 | }); 83 | 84 | self.prg_rom_bank_mode = match (cmd >> 2) & 3 { 85 | 0 | 1 => PrgRomBankMode::Switch32K, 86 | 2 => PrgRomBankMode::Switch16KHigh, 87 | 3 => PrgRomBankMode::Switch16KLow, 88 | _ => unreachable!(), 89 | }; 90 | 91 | self.chr_rom_bank_mode = match (cmd >> 4) & 1 { 92 | 0 => ChrRomBankMode::Switch8K, 93 | 1 => ChrRomBankMode::Switch4K, 94 | _ => unreachable!(), 95 | }; 96 | } 97 | 1 => match self.chr_rom_bank_mode { 98 | ChrRomBankMode::Switch8K => { 99 | let page = (cmd >> 1) as u32; 100 | for i in 0..8 { 101 | ctx.map_chr(i, page * 8 + i); 102 | } 103 | } 104 | ChrRomBankMode::Switch4K => { 105 | let page = cmd as u32; 106 | for i in 0..4 { 107 | ctx.map_chr(i, page * 4 + i); 108 | } 109 | } 110 | }, 111 | 2 => match self.chr_rom_bank_mode { 112 | ChrRomBankMode::Switch8K => { 113 | log::info!("MMC1: High CHR page set on 8K CHR mode"); 114 | } 115 | ChrRomBankMode::Switch4K => { 116 | let page = cmd as u32; 117 | for i in 0..4 { 118 | ctx.map_chr(i + 4, page * 4 + i); 119 | } 120 | } 121 | }, 122 | 3 => match self.prg_rom_bank_mode { 123 | PrgRomBankMode::Switch32K => { 124 | let page = (cmd as u32 & 0x0f) >> 1; 125 | for i in 0..4 { 126 | ctx.map_prg(i, page * 4 + i); 127 | } 128 | } 129 | PrgRomBankMode::Switch16KLow => { 130 | let page = cmd as u32 & 0x0f; 131 | let prg_pages = ctx.memory_ctrl().prg_pages(); 132 | for i in 0..2 { 133 | ctx.map_prg(i, page * 2 + i); 134 | } 135 | ctx.map_prg(2, prg_pages - 2); 136 | ctx.map_prg(3, prg_pages - 1); 137 | } 138 | PrgRomBankMode::Switch16KHigh => { 139 | let page = cmd as u32 & 0x0f; 140 | ctx.map_prg(0, 0); 141 | ctx.map_prg(1, 1); 142 | for i in 0..2 { 143 | ctx.map_prg(i + 2, page * 2 + i); 144 | } 145 | } 146 | }, 147 | _ => unreachable!(), 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/mapper/mmc3.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::{ 4 | consts::{LINES_PER_FRAME, PPU_CLOCK_PER_LINE, PRE_RENDER_LINE, SCREEN_RANGE}, 5 | context::IrqSource, 6 | rom::Mirroring, 7 | }; 8 | 9 | use bitvec::prelude::*; 10 | 11 | #[derive(Serialize, Deserialize)] 12 | pub struct Mmc3 { 13 | cmd: u8, 14 | prg_swap: bool, 15 | chr_swap: bool, 16 | prg_bank: [u8; 2], 17 | chr_bank: [u8; 6], 18 | mirroring: Mirroring, 19 | irq_latch: u8, 20 | irq_counter: u8, 21 | irq_reload: bool, 22 | irq_enable: bool, 23 | ppu_cycle: u64, 24 | ppu_line: u64, 25 | ppu_frame: u64, 26 | ppu_bus_addr: u16, 27 | ppu_a12_edge: bool, 28 | } 29 | 30 | impl Mmc3 { 31 | pub fn new(ctx: &mut impl super::Context) -> Self { 32 | let mirroring = ctx.rom().mirroring; 33 | let mut ret = Self { 34 | cmd: 0, 35 | prg_swap: false, 36 | chr_swap: false, 37 | prg_bank: [0, 1], 38 | chr_bank: [0; 6], 39 | mirroring, 40 | irq_latch: 0, 41 | irq_counter: 0, 42 | irq_reload: false, 43 | irq_enable: false, 44 | ppu_cycle: 0, 45 | ppu_line: 0, 46 | ppu_frame: 0, 47 | ppu_bus_addr: 0, 48 | ppu_a12_edge: false, 49 | }; 50 | ret.update(ctx); 51 | ret 52 | } 53 | 54 | fn update(&mut self, ctx: &mut impl super::Context) { 55 | let chr_swap = self.chr_swap as u32 * 4; 56 | for i in 0..2 { 57 | let bank = self.chr_bank[i] as u32; 58 | ctx.map_chr((i * 2) as u32 ^ chr_swap, bank & !1); 59 | ctx.map_chr((i * 2 + 1) as u32 ^ chr_swap, bank | 1); 60 | } 61 | for i in 2..6 { 62 | ctx.map_chr((i + 2) as u32 ^ chr_swap, self.chr_bank[i] as _); 63 | } 64 | 65 | let prg_pages = ctx.memory_ctrl().prg_pages(); 66 | if !self.prg_swap { 67 | ctx.map_prg(0, self.prg_bank[0] as _); 68 | ctx.map_prg(1, self.prg_bank[1] as _); 69 | ctx.map_prg(2, prg_pages - 2); 70 | ctx.map_prg(3, prg_pages - 1); 71 | } else { 72 | ctx.map_prg(0, prg_pages - 2); 73 | ctx.map_prg(1, self.prg_bank[1] as _); 74 | ctx.map_prg(2, self.prg_bank[0] as _); 75 | ctx.map_prg(3, prg_pages - 1); 76 | } 77 | 78 | ctx.memory_ctrl_mut().set_mirroring(self.mirroring); 79 | } 80 | 81 | fn update_ppu_addr(&mut self, addr: u16) { 82 | if addr >= 0x2000 { 83 | return; 84 | } 85 | 86 | if self.ppu_bus_addr & 0x1000 == 0 && addr & 0x1000 != 0 { 87 | self.ppu_a12_edge = true; 88 | } 89 | 90 | self.ppu_bus_addr = addr; 91 | } 92 | } 93 | 94 | impl super::MapperTrait for Mmc3 { 95 | fn write_prg(&mut self, ctx: &mut impl super::Context, addr: u16, data: u8) { 96 | if addr & 0x8000 == 0 { 97 | ctx.write_prg(addr, data); 98 | return; 99 | } 100 | 101 | match addr & 0xE001 { 102 | 0x8000 => { 103 | let v = data.view_bits::(); 104 | self.cmd = v[0..3].load(); 105 | self.prg_swap = v[6]; 106 | self.chr_swap = v[7]; 107 | } 108 | 0x8001 => { 109 | match self.cmd { 110 | 0..=5 => self.chr_bank[self.cmd as usize] = data, 111 | 6..=7 => self.prg_bank[self.cmd as usize - 6] = data, 112 | _ => unreachable!(), 113 | } 114 | self.update(ctx); 115 | } 116 | 117 | 0xA000 => { 118 | if self.mirroring != Mirroring::FourScreen { 119 | self.mirroring = if data & 1 == 0 { 120 | Mirroring::Vertical 121 | } else { 122 | Mirroring::Horizontal 123 | }; 124 | } 125 | self.update(ctx); 126 | } 127 | 0xA001 => { 128 | let v = data.view_bits::(); 129 | log::info!("PRG RAM protect: enable: {}, write protect: {}", v[7], v[6]); 130 | } 131 | 132 | 0xC000 => { 133 | log::trace!( 134 | "MMC3 IRQ latch : {data:3}, PPU frame={}, line={}, pixel={}", 135 | self.ppu_frame, 136 | self.ppu_line, 137 | self.ppu_cycle 138 | ); 139 | self.irq_latch = data 140 | } 141 | 0xC001 => { 142 | log::trace!( 143 | "MMC3 IRQ reload : PPU frame={}, line={}, pixel={}", 144 | self.ppu_frame, 145 | self.ppu_line, 146 | self.ppu_cycle 147 | ); 148 | self.irq_counter = 0; 149 | self.irq_reload = true; 150 | } 151 | 152 | 0xE000 => { 153 | log::trace!( 154 | "MMC3 IRQ disable: PPU frame={}, line={}, pixel={}", 155 | self.ppu_frame, 156 | self.ppu_line, 157 | self.ppu_cycle 158 | ); 159 | self.irq_enable = false; 160 | ctx.set_irq_source(IrqSource::Mapper, false); 161 | } 162 | 0xE001 => { 163 | log::trace!( 164 | "MMC3 IRQ enable : PPU frame={}, line={}, pixel={}", 165 | self.ppu_frame, 166 | self.ppu_line, 167 | self.ppu_cycle 168 | ); 169 | self.irq_enable = true; 170 | } 171 | 172 | _ => unreachable!(), 173 | } 174 | } 175 | 176 | fn read_chr(&mut self, ctx: &mut impl super::Context, addr: u16) -> u8 { 177 | self.update_ppu_addr(addr); 178 | ctx.read_chr(addr) 179 | } 180 | 181 | fn write_chr(&mut self, ctx: &mut impl super::Context, addr: u16, data: u8) { 182 | self.update_ppu_addr(addr); 183 | ctx.write_chr(addr, data); 184 | } 185 | 186 | fn tick(&mut self, ctx: &mut impl super::Context) { 187 | if (self.ppu_line < SCREEN_RANGE.end as u64 || self.ppu_line == PRE_RENDER_LINE as u64) 188 | && self.ppu_cycle == 260 189 | { 190 | if self.ppu_a12_edge { 191 | let tmp = self.irq_counter; 192 | if self.irq_counter == 0 || self.irq_reload { 193 | self.irq_counter = self.irq_latch; 194 | self.irq_reload = false; 195 | } else { 196 | self.irq_counter -= 1; 197 | } 198 | if (tmp > 0 || self.irq_reload) && self.irq_counter == 0 && self.irq_enable { 199 | ctx.set_irq_source(IrqSource::Mapper, true); 200 | } 201 | } 202 | self.ppu_a12_edge = false; 203 | } 204 | 205 | self.ppu_cycle += 1; 206 | if self.ppu_cycle == PPU_CLOCK_PER_LINE { 207 | self.ppu_cycle = 0; 208 | self.ppu_line += 1; 209 | if self.ppu_line == LINES_PER_FRAME as u64 { 210 | self.ppu_line = 0; 211 | self.ppu_frame += 1; 212 | } 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/rom.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | pub struct Rom { 4 | pub format: RomFormat, 5 | pub mapper_id: u16, 6 | pub submapper_id: u8, 7 | pub prg_rom: Vec, 8 | pub chr_rom: Vec, 9 | pub trainer: Option>, 10 | pub prg_ram_size: usize, 11 | pub prg_nvram_size: usize, 12 | pub chr_ram_size: usize, 13 | pub chr_nvram_size: usize, 14 | pub mirroring: Mirroring, 15 | pub console_type: ConsoleType, 16 | pub timing_mode: TimingMode, 17 | pub has_battery: bool, 18 | } 19 | 20 | impl Default for Rom { 21 | fn default() -> Self { 22 | Self { 23 | format: RomFormat::INes, 24 | mapper_id: 0, 25 | submapper_id: 0, 26 | prg_rom: vec![], 27 | chr_rom: vec![], 28 | trainer: None, 29 | prg_ram_size: 0, 30 | prg_nvram_size: 0, 31 | chr_ram_size: 0, 32 | chr_nvram_size: 0, 33 | mirroring: Mirroring::Vertical, 34 | console_type: ConsoleType::Nes, 35 | timing_mode: TimingMode::Ntsc, 36 | has_battery: false, 37 | } 38 | } 39 | } 40 | 41 | pub enum RomFormat { 42 | INes, 43 | Nes20, 44 | } 45 | 46 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 47 | pub enum Mirroring { 48 | OneScreenLow, 49 | OneScreenHigh, 50 | Horizontal, 51 | Vertical, 52 | FourScreen, 53 | } 54 | 55 | #[derive(Debug)] 56 | pub enum ConsoleType { 57 | Nes, 58 | VsSystem { ppu_type: u8, hardware_type: u8 }, 59 | Playchoice10, 60 | ExtendConsoleType { console_type: u8 }, 61 | } 62 | 63 | #[derive(Debug)] 64 | pub enum TimingMode { 65 | Ntsc, 66 | Pal, 67 | MultipleRegion, 68 | Dendy, 69 | } 70 | 71 | #[derive(thiserror::Error, Debug)] 72 | pub enum RomError { 73 | #[error("invalid ROM magic: {0:?}, expected: 'NES\x1a'")] 74 | InvalidMagic([u8; 4]), 75 | #[error("Invalid mirroring: {0}")] 76 | InvalidMirroring(u8), 77 | #[error("ROM data has invalid extra bytes")] 78 | InvalidExtraBytes, 79 | } 80 | 81 | impl Rom { 82 | pub fn from_bytes(dat: &[u8]) -> Result { 83 | let header = &dat[..0x10]; 84 | let mut dat = &dat[0x10..]; 85 | 86 | let magic = &header[0..4]; 87 | if magic != b"NES\x1a" { 88 | Err(RomError::InvalidMagic(magic.try_into().unwrap()))?; 89 | } 90 | 91 | let is_nes2 = header[7] & 0x0C == 0x08; 92 | 93 | let prg_rom_size_in_16kib = if is_nes2 { 94 | header[4] as usize | (header[9] as usize & 0x0f) << 8 95 | } else { 96 | header[4] as usize 97 | }; 98 | 99 | let prg_rom_size = prg_rom_size_in_16kib * 16 * 1024; 100 | 101 | let chr_rom_size_in_8kib = if is_nes2 { 102 | header[5] as usize | (header[9] as usize >> 4) << 8 103 | } else { 104 | header[5] as usize 105 | }; 106 | 107 | let chr_rom_size = chr_rom_size_in_8kib * 8 * 1024; 108 | 109 | let mirroring = match header[6] & 0x09 { 110 | 0 => Mirroring::Horizontal, 111 | 1 => Mirroring::Vertical, 112 | 8 => Mirroring::FourScreen, 113 | _ => Err(RomError::InvalidMirroring(header[6] & 0x09))?, 114 | }; 115 | 116 | let has_battery = header[6] & 0x02 != 0; 117 | let has_trainer = header[6] & 0x04 != 0; 118 | 119 | let mapper_id = if is_nes2 { 120 | header[6] as u16 >> 4 | header[7] as u16 & 0xf0 | (header[8] as u16 & 0xf) << 8 121 | } else { 122 | header[6] as u16 >> 4 | header[7] as u16 & 0xf0 123 | }; 124 | 125 | let submapper_id = if is_nes2 { header[8] >> 4 } else { 0 }; 126 | 127 | let console_type = if is_nes2 { 128 | match header[7] & 3 { 129 | 0 => ConsoleType::Nes, 130 | 1 => ConsoleType::VsSystem { 131 | ppu_type: header[13] & 0x0f, 132 | hardware_type: header[13] >> 4, 133 | }, 134 | 2 => ConsoleType::Playchoice10, 135 | 3 => ConsoleType::ExtendConsoleType { 136 | console_type: header[13] & 0x0f, 137 | }, 138 | _ => unreachable!(), 139 | } 140 | } else { 141 | ConsoleType::Nes 142 | }; 143 | 144 | let prg_ram_size = if is_nes2 { 145 | let shift_count = header[10] & 0xf; 146 | if shift_count == 0 { 147 | 0 148 | } else { 149 | 64 << shift_count 150 | } 151 | } else if header[8] == 0 { 152 | 8 * 1024 153 | } else { 154 | header[8] as usize * 8 * 1024 155 | }; 156 | 157 | let prg_nvram_size = if is_nes2 { 158 | let shift_count = header[10] >> 4; 159 | if shift_count == 0 { 160 | 0 161 | } else { 162 | 64 << shift_count 163 | } 164 | } else { 165 | 0 166 | }; 167 | 168 | let chr_ram_size = if is_nes2 { 169 | let shift_count = header[11] & 0xf; 170 | if shift_count == 0 { 171 | 0 172 | } else { 173 | 64 << shift_count 174 | } 175 | } else if chr_rom_size == 0 { 176 | 8 * 1024 177 | } else { 178 | 0 179 | }; 180 | 181 | let chr_nvram_size = if is_nes2 { 182 | let shift_count = header[11] >> 4; 183 | if shift_count == 0 { 184 | 0 185 | } else { 186 | 64 << shift_count 187 | } 188 | } else { 189 | 0 190 | }; 191 | 192 | let timing_mode = if is_nes2 { 193 | match header[12] & 3 { 194 | 0 => TimingMode::Ntsc, 195 | 1 => TimingMode::Pal, 196 | 2 => TimingMode::MultipleRegion, 197 | 3 => TimingMode::Dendy, 198 | _ => unreachable!(), 199 | } 200 | } else { 201 | match header[10] & 3 { 202 | 0 => TimingMode::Ntsc, 203 | 2 => TimingMode::Pal, 204 | _ => TimingMode::MultipleRegion, 205 | } 206 | }; 207 | 208 | // TODO: 209 | 210 | // 14 Miscellaneous ROMs 211 | // D~7654 3210 212 | // --------- 213 | // .... ..RR 214 | // ++- Number of miscellaneous ROMs present 215 | 216 | // 15 Default Expansion Device 217 | // D~7654 3210 218 | // --------- 219 | // ..DD DDDD 220 | // ++-++++- Default Expansion Device 221 | 222 | let trainer = if has_trainer { 223 | let v = &dat[..512]; 224 | dat = &dat[512..]; 225 | Some(v.to_owned()) 226 | } else { 227 | None 228 | }; 229 | 230 | let prg_rom = dat[..prg_rom_size].to_owned(); 231 | dat = &dat[prg_rom_size..]; 232 | let chr_rom = dat[..chr_rom_size].to_owned(); 233 | dat = &dat[chr_rom_size..]; 234 | 235 | if !dat.is_empty() { 236 | Err(RomError::InvalidExtraBytes)?; 237 | } 238 | 239 | let format = if is_nes2 { 240 | RomFormat::Nes20 241 | } else { 242 | RomFormat::INes 243 | }; 244 | 245 | Ok(Self { 246 | format, 247 | prg_rom, 248 | chr_rom, 249 | trainer, 250 | mapper_id, 251 | submapper_id, 252 | mirroring, 253 | console_type, 254 | timing_mode, 255 | has_battery, 256 | prg_ram_size, 257 | prg_nvram_size, 258 | chr_ram_size, 259 | chr_nvram_size, 260 | }) 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/nes.rs: -------------------------------------------------------------------------------- 1 | use bytesize::ByteSize; 2 | use meru_interface::{CoreInfo, EmulatorCore, KeyConfig}; 3 | use schemars::JsonSchema; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::{ 7 | consts, 8 | context::{self, MemoryController}, 9 | rom::{self, RomError, RomFormat}, 10 | util::{Input, Pad}, 11 | }; 12 | 13 | pub struct Nes { 14 | pub ctx: context::Context, 15 | } 16 | 17 | #[derive(Default, JsonSchema, Serialize, Deserialize)] 18 | pub struct Config {} 19 | 20 | #[derive(thiserror::Error, Debug)] 21 | pub enum Error { 22 | #[error("{0}")] 23 | RomError(#[from] RomError), 24 | #[error("unsupported mapper: {0}")] 25 | UnsupportedMapper(u16), 26 | #[error("{0}")] 27 | DeserializeFailed(#[from] bincode::Error), 28 | #[error("backup ram size mismatch: actual: {0}, expected: {1}")] 29 | BackupSizeMismatch(usize, usize), 30 | } 31 | 32 | const CORE_INFO: CoreInfo = CoreInfo { 33 | system_name: "NES (Sabicom)", 34 | abbrev: "nes", 35 | file_extensions: &["nes"], 36 | }; 37 | 38 | fn default_key_config() -> KeyConfig { 39 | use meru_interface::key_assign::*; 40 | 41 | #[rustfmt::skip] 42 | let keys = vec![ 43 | ("Up", any!(keycode!(Up), pad_button!(0, DPadUp))), 44 | ("Down", any!(keycode!(Down), pad_button!(0, DPadDown))), 45 | ("Left", any!(keycode!(Left), pad_button!(0, DPadLeft))), 46 | ("Right", any!(keycode!(Right), pad_button!(0, DPadRight))), 47 | ("A", any!(keycode!(X), pad_button!(0, East))), 48 | ("B", any!(keycode!(Z), pad_button!(0, South))), 49 | ("Start", any!(keycode!(Return), pad_button!(0, Start))), 50 | ("Select", any!(keycode!(RShift), pad_button!(0, Select))), 51 | ]; 52 | 53 | let empty = vec![ 54 | ("Up", KeyAssign::default()), 55 | ("Down", KeyAssign::default()), 56 | ("Left", KeyAssign::default()), 57 | ("Right", KeyAssign::default()), 58 | ("A", KeyAssign::default()), 59 | ("B", KeyAssign::default()), 60 | ("Start", KeyAssign::default()), 61 | ("Select", KeyAssign::default()), 62 | ]; 63 | 64 | KeyConfig { 65 | controllers: [keys, empty] 66 | .into_iter() 67 | .map(|v| v.into_iter().map(|(k, a)| (k.to_string(), a)).collect()) 68 | .collect(), 69 | } 70 | } 71 | 72 | impl EmulatorCore for Nes { 73 | type Config = Config; 74 | type Error = Error; 75 | 76 | fn core_info() -> &'static meru_interface::CoreInfo { 77 | &CORE_INFO 78 | } 79 | 80 | fn try_from_file( 81 | data: &[u8], 82 | backup: Option<&[u8]>, 83 | _config: &Self::Config, 84 | ) -> Result 85 | where 86 | Self: Sized, 87 | { 88 | use context::Cpu; 89 | let rom = rom::Rom::from_bytes(data)?; 90 | let mut ctx = context::Context::new(rom, backup.map(|r| r.to_vec()))?; 91 | ctx.reset_cpu(); 92 | Ok(Self { ctx }) 93 | } 94 | 95 | fn game_info(&self) -> Vec<(String, String)> { 96 | use context::Rom; 97 | let rom = self.ctx.rom(); 98 | 99 | let to_si = |x| ByteSize(x as _).to_string_as(true); 100 | let yn = |b| if b { "Yes" } else { "No" }; 101 | 102 | let prg_chr_crc32 = { 103 | let mut hasher = crc32fast::Hasher::new(); 104 | hasher.update(&rom.prg_rom); 105 | hasher.update(&rom.chr_rom); 106 | hasher.finalize() 107 | }; 108 | let prg_rom_crc32 = crc32fast::hash(&rom.prg_rom); 109 | let chr_rom_crc32 = crc32fast::hash(&rom.chr_rom); 110 | 111 | let ret = vec![ 112 | ( 113 | "ROM Format", 114 | match &rom.format { 115 | RomFormat::INes => "iNES", 116 | RomFormat::Nes20 => "NES 2.0", 117 | } 118 | .to_string(), 119 | ), 120 | ( 121 | "Mapper ID", 122 | format!("{} ({})", rom.mapper_id, rom.submapper_id), 123 | ), 124 | ("Mirroring", format!("{:?}", rom.mirroring)), 125 | ("Console Type", format!("{:?}", rom.console_type)), 126 | ("Timing Mode", format!("{:?}", rom.timing_mode)), 127 | ("Battery", yn(rom.has_battery).to_string()), 128 | ("Trainer", yn(rom.trainer.is_some()).to_string()), 129 | ("PRG ROM Size", to_si(rom.prg_rom.len())), 130 | ("CHR ROM Size", to_si(rom.chr_rom.len())), 131 | ("PRG RAM Size", to_si(rom.prg_ram_size)), 132 | ("PRG NVRAM Size", to_si(rom.prg_nvram_size)), 133 | ("CHR RAM Size", to_si(rom.chr_ram_size)), 134 | ("CHR NVRAM Size", to_si(rom.chr_nvram_size)), 135 | ("PRG+CHR CRC32", format!("{prg_chr_crc32:08X}")), 136 | ("PRG ROM CRC32", format!("{prg_rom_crc32:08X}")), 137 | ("CHR ROM CRC32", format!("{chr_rom_crc32:08X}")), 138 | ]; 139 | 140 | ret.into_iter().map(|(k, v)| (k.to_string(), v)).collect() 141 | } 142 | 143 | fn set_config(&mut self, _config: &Self::Config) {} 144 | 145 | fn exec_frame(&mut self, render_graphics: bool) { 146 | use context::{Apu, Cpu, Ppu}; 147 | 148 | self.ctx.apu_mut().audio_buffer_mut().samples.clear(); 149 | self.ctx 150 | .ppu_mut() 151 | .frame_buffer_mut() 152 | .resize(consts::SCREEN_WIDTH, consts::SCREEN_HEIGHT); 153 | self.ctx.ppu_mut().set_render_graphics(render_graphics); 154 | 155 | let frame = self.ctx.ppu().frame(); 156 | while frame == self.ctx.ppu().frame() { 157 | self.ctx.tick_cpu(); 158 | } 159 | } 160 | 161 | fn reset(&mut self) { 162 | use context::{Cpu, Rom}; 163 | 164 | let backup = self.backup(); 165 | let mut rom = rom::Rom::default(); 166 | std::mem::swap(&mut rom, self.ctx.rom_mut()); 167 | self.ctx = context::Context::new(rom, backup).unwrap(); 168 | 169 | self.ctx.reset_cpu(); 170 | } 171 | 172 | fn frame_buffer(&self) -> &meru_interface::FrameBuffer { 173 | use context::Ppu; 174 | self.ctx.ppu().frame_buffer() 175 | } 176 | 177 | fn audio_buffer(&self) -> &meru_interface::AudioBuffer { 178 | use context::Apu; 179 | self.ctx.apu().audio_buffer() 180 | } 181 | 182 | fn default_key_config() -> meru_interface::KeyConfig { 183 | default_key_config() 184 | } 185 | 186 | fn set_input(&mut self, input: &meru_interface::InputData) { 187 | let mut pad: [Pad; 2] = Default::default(); 188 | 189 | for i in 0..2 { 190 | let mut pad = &mut pad[i]; 191 | for (key, value) in &input.controllers[i] { 192 | match key.as_str() { 193 | "Up" => pad.up = *value, 194 | "Down" => pad.down = *value, 195 | "Left" => pad.left = *value, 196 | "Right" => pad.right = *value, 197 | "A" => pad.a = *value, 198 | "B" => pad.b = *value, 199 | "Start" => pad.start = *value, 200 | "Select" => pad.select = *value, 201 | _ => (), 202 | } 203 | } 204 | } 205 | 206 | use context::Apu; 207 | self.ctx.apu_mut().set_input(&Input { pad }); 208 | } 209 | 210 | fn backup(&self) -> Option> { 211 | use context::Rom; 212 | if self.ctx.rom().has_battery { 213 | Some(self.ctx.memory_ctrl().prg_ram().to_vec()) 214 | } else { 215 | None 216 | } 217 | } 218 | 219 | fn save_state(&self) -> Vec { 220 | bincode::serialize(&self.ctx).unwrap() 221 | } 222 | 223 | fn load_state(&mut self, data: &[u8]) -> Result<(), Self::Error> { 224 | use context::{Apu, Ppu, Rom}; 225 | let mut ctx: context::Context = bincode::deserialize(data)?; 226 | std::mem::swap(ctx.rom_mut(), self.ctx.rom_mut()); 227 | std::mem::swap( 228 | ctx.ppu_mut().frame_buffer_mut(), 229 | self.ctx.ppu_mut().frame_buffer_mut(), 230 | ); 231 | std::mem::swap( 232 | ctx.apu_mut().audio_buffer_mut(), 233 | self.ctx.apu_mut().audio_buffer_mut(), 234 | ); 235 | self.ctx = ctx; 236 | Ok(()) 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/memory.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::{ 4 | context, 5 | nes::Error, 6 | rom::{Mirroring, Rom}, 7 | util::trait_alias, 8 | }; 9 | 10 | trait_alias!(pub trait Context = context::Mapper + context::Ppu + context::Apu + context::Interrupt + context::Timing); 11 | 12 | #[derive(Serialize, Deserialize)] 13 | pub struct MemoryMap { 14 | ram: Vec, 15 | cpu_stall: u64, 16 | } 17 | 18 | impl Default for MemoryMap { 19 | fn default() -> Self { 20 | Self { 21 | ram: vec![0x00; 2 * 1024], 22 | cpu_stall: 0, 23 | } 24 | } 25 | } 26 | 27 | impl MemoryMap { 28 | pub fn read(&self, ctx: &mut impl Context, addr: u16) -> u8 { 29 | match addr { 30 | 0x0000..=0x1fff => self.ram[(addr & 0x7ff) as usize], 31 | 0x2000..=0x3fff => ctx.read_ppu(addr & 7), 32 | 0x4000..=0x4017 => ctx.read_apu(addr), 33 | 0x4018..=0xffff => ctx.read_prg_mapper(addr), 34 | } 35 | } 36 | 37 | pub fn read_pure(&self, ctx: &impl Context, addr: u16) -> Option { 38 | Some(match addr { 39 | 0x0000..=0x1fff => self.ram[(addr & 0x7ff) as usize], 40 | 0x2000..=0x3fff => None?, 41 | 0x4000..=0x4017 => None?, 42 | 0x4018..=0xffff => ctx.read_prg_mapper(addr), 43 | }) 44 | } 45 | 46 | pub fn write(&mut self, ctx: &mut impl Context, addr: u16, data: u8) { 47 | match addr { 48 | 0x0000..=0x1fff => self.ram[(addr & 0x7ff) as usize] = data, 49 | 0x2000..=0x3fff => ctx.write_ppu(addr & 7, data), 50 | 0x4000..=0x4013 | 0x4015..=0x4017 => ctx.write_apu(addr, data), 51 | 0x4018..=0xffff => ctx.write_prg_mapper(addr, data), 52 | 53 | 0x4014 => { 54 | // OAM DMA 55 | let hi = (data as u16) << 8; 56 | 57 | for lo in 0..0x100 { 58 | let data = self.read(ctx, hi | lo); 59 | self.write(ctx, 0x2004, data); 60 | } 61 | 62 | // FIXME: odd frame stall one more cycle 63 | self.cpu_stall += 513 64 | } 65 | } 66 | } 67 | 68 | pub fn tick(&mut self, ctx: &mut impl Context) { 69 | for _ in 0..3 { 70 | ctx.tick_ppu(); 71 | ctx.tick_mapper(); 72 | } 73 | ctx.tick_apu(); 74 | } 75 | 76 | pub fn cpu_stall(&mut self) -> u64 { 77 | let ret = self.cpu_stall; 78 | self.cpu_stall = 0; 79 | ret 80 | } 81 | } 82 | 83 | #[derive(Serialize, Deserialize)] 84 | pub struct MemoryController { 85 | prg_ram: Vec, 86 | chr_ram: Vec, 87 | 88 | nametable: Vec, 89 | palette: [u8; 0x20], 90 | 91 | rom_page: [usize; 4], 92 | chr_page: [usize; 8], 93 | nametable_page: [usize; 4], 94 | 95 | prg_pages: u32, 96 | chr_pages: u32, 97 | } 98 | 99 | impl MemoryController { 100 | pub fn new(rom: &Rom, backup: Option>) -> Result { 101 | assert!(rom.chr_ram_size == 0 || rom.chr_rom.is_empty()); 102 | 103 | let mirroring = rom.mirroring; 104 | 105 | let prg_ram = if let Some(backup) = backup { 106 | if backup.len() != rom.prg_ram_size { 107 | Err(Error::BackupSizeMismatch(backup.len(), rom.prg_ram_size))? 108 | } 109 | backup 110 | } else { 111 | vec![0x00; rom.prg_ram_size] 112 | }; 113 | let chr_ram = vec![0x00; rom.chr_ram_size]; 114 | 115 | let nametable = vec![0x00; 2 * 1024]; 116 | 117 | #[rustfmt::skip] 118 | let palette = [ 119 | 0x09, 0x01, 0x00, 0x01, 0x00, 0x02, 0x02, 0x0D, 120 | 0x08, 0x10, 0x08, 0x24, 0x00, 0x00, 0x04, 0x2C, 121 | 0x09, 0x01, 0x34, 0x03, 0x00, 0x04, 0x00, 0x14, 122 | 0x08, 0x3A, 0x00, 0x02, 0x00, 0x20, 0x2C, 0x08, 123 | ]; 124 | 125 | let prg_pages = (rom.prg_rom.len() / 0x2000) as u32; 126 | let chr_pages = (rom.chr_rom.len() / 0x0400) as u32; 127 | 128 | let mut ret = Self { 129 | prg_ram, 130 | chr_ram, 131 | nametable, 132 | palette, 133 | rom_page: [0; 4], 134 | chr_page: [0; 8], 135 | nametable_page: [0; 4], 136 | prg_pages, 137 | chr_pages, 138 | }; 139 | 140 | for i in 0..4 { 141 | ret.map_prg(rom, i, i as _); 142 | } 143 | 144 | for i in 0..8 { 145 | ret.map_chr(rom, i, i as _); 146 | } 147 | 148 | ret.set_mirroring(mirroring); 149 | 150 | Ok(ret) 151 | } 152 | 153 | pub fn prg_ram(&self) -> &[u8] { 154 | &self.prg_ram 155 | } 156 | 157 | /// Maps a PRG ROM page to a given 8KB bank 158 | pub fn map_prg(&mut self, rom: &Rom, page: u32, bank8k: u32) { 159 | self.rom_page[page as usize] = (bank8k * 0x2000) as usize % rom.prg_rom.len(); 160 | } 161 | 162 | pub fn prg_pages(&self) -> u32 { 163 | self.prg_pages 164 | } 165 | 166 | pub fn prg_page(&self, page: u32) -> u32 { 167 | (self.rom_page[page as usize] / 0x2000) as u32 168 | } 169 | 170 | /// Maps a CHR ROM page to a given 1KB bank 171 | pub fn map_chr(&mut self, rom: &Rom, page: u32, bank1k: u32) { 172 | if !rom.chr_rom.is_empty() { 173 | self.chr_page[page as usize] = (bank1k * 0x0400) as usize % rom.chr_rom.len(); 174 | } else { 175 | self.chr_page[page as usize] = (bank1k * 0x0400) as usize % rom.chr_ram_size; 176 | } 177 | } 178 | 179 | pub fn chr_pages(&mut self) -> u32 { 180 | self.chr_pages 181 | } 182 | 183 | pub fn map_nametable(&mut self, page: usize, bank: usize) { 184 | self.nametable_page[page] = bank * 0x0400; 185 | } 186 | 187 | pub fn set_mirroring(&mut self, mirroring: Mirroring) { 188 | match mirroring { 189 | Mirroring::OneScreenLow => { 190 | self.map_nametable(0, 0); 191 | self.map_nametable(1, 0); 192 | self.map_nametable(2, 0); 193 | self.map_nametable(3, 0); 194 | } 195 | Mirroring::OneScreenHigh => { 196 | self.map_nametable(0, 1); 197 | self.map_nametable(1, 1); 198 | self.map_nametable(2, 1); 199 | self.map_nametable(3, 1); 200 | } 201 | Mirroring::Horizontal => { 202 | self.map_nametable(0, 0); 203 | self.map_nametable(1, 0); 204 | self.map_nametable(2, 1); 205 | self.map_nametable(3, 1); 206 | } 207 | Mirroring::Vertical => { 208 | self.map_nametable(0, 0); 209 | self.map_nametable(1, 1); 210 | self.map_nametable(2, 0); 211 | self.map_nametable(3, 1); 212 | } 213 | Mirroring::FourScreen => { 214 | todo!() 215 | } 216 | } 217 | } 218 | 219 | pub fn read_prg(&self, rom: &Rom, addr: u16) -> u8 { 220 | match addr { 221 | 0x6000..=0x7fff => { 222 | let addr = addr & 0x1fff; 223 | self.prg_ram[addr as usize] 224 | } 225 | 0x8000..=0xffff => { 226 | let page = (addr & 0x7fff) / 0x2000; 227 | let ix = self.rom_page[page as usize] + (addr & 0x1fff) as usize; 228 | rom.prg_rom[ix] 229 | } 230 | _ => 0, 231 | } 232 | } 233 | 234 | pub fn write_prg(&mut self, _rom: &Rom, addr: u16, data: u8) { 235 | match addr { 236 | 0x6000..=0x7fff => { 237 | let addr = addr & 0x1fff; 238 | self.prg_ram[addr as usize] = data; 239 | } 240 | 0x8000..=0xffff => { 241 | log::warn!("Write to PRG ROM: {addr:04x} = {data:02x}"); 242 | } 243 | _ => (), 244 | } 245 | } 246 | 247 | pub fn read_chr(&self, rom: &Rom, addr: u16) -> u8 { 248 | log::trace!("Read CHR MEM: ${addr:04X}"); 249 | 250 | match addr { 251 | 0x0000..=0x1fff => { 252 | let page = (addr / 0x0400) as usize; 253 | let ix = self.chr_page[page] + (addr & 0x03ff) as usize; 254 | 255 | if !rom.chr_rom.is_empty() { 256 | rom.chr_rom[ix] 257 | } else { 258 | self.chr_ram[ix] 259 | } 260 | } 261 | 0x2000..=0x3eff => { 262 | let page = (addr as usize & 0x0fff) / 0x400; 263 | let ofs = addr as usize & 0x03ff; 264 | let ix = self.nametable_page[page] + ofs; 265 | self.nametable[ix] 266 | } 267 | 0x3f00..=0x3fff => { 268 | let addr = addr & if addr & 3 == 0 { 0x0f } else { 0x1f }; 269 | self.palette[addr as usize] 270 | } 271 | _ => unreachable!(), 272 | } 273 | } 274 | 275 | pub fn write_chr(&mut self, rom: &Rom, addr: u16, data: u8) { 276 | log::trace!("Write CHR MEM: (${addr:04X}) = ${data:02X}"); 277 | 278 | match addr { 279 | 0x0000..=0x1fff => { 280 | let page = (addr / 0x0400) as usize; 281 | let ix = self.chr_page[page] + (addr & 0x03ff) as usize; 282 | 283 | if !rom.chr_rom.is_empty() { 284 | log::warn!("Write to CHR ROM: (${addr:04X}) = ${data:02X}"); 285 | } else { 286 | self.chr_ram[ix] = data; 287 | } 288 | } 289 | 0x2000..=0x3eff => { 290 | let page = (addr as usize & 0x0fff) / 0x400; 291 | let ofs = addr as usize & 0x03ff; 292 | let ix = self.nametable_page[page] + ofs; 293 | self.nametable[ix] = data; 294 | } 295 | 0x3f00..=0x3fff => { 296 | let addr = addr & if addr & 3 == 0 { 0x0f } else { 0x1f }; 297 | self.palette[addr as usize] = data; 298 | } 299 | _ => unreachable!(), 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use ambassador::{delegatable_trait, Delegate}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::{ 5 | apu, cpu, 6 | mapper::{self, create_mapper}, 7 | memory, 8 | nes::Error, 9 | ppu, rom, 10 | }; 11 | 12 | #[delegatable_trait] 13 | pub trait Cpu { 14 | fn cpu(&self) -> &cpu::Cpu; 15 | fn cpu_mut(&mut self) -> &mut cpu::Cpu; 16 | 17 | fn reset_cpu(&mut self); 18 | fn tick_cpu(&mut self); 19 | } 20 | 21 | #[delegatable_trait] 22 | pub trait Bus { 23 | fn read(&mut self, addr: u16) -> u8; 24 | fn read_pure(&self, addr: u16) -> Option; 25 | fn write(&mut self, addr: u16, data: u8); 26 | fn tick_bus(&mut self); 27 | fn cpu_stall(&mut self) -> u64; 28 | } 29 | 30 | #[delegatable_trait] 31 | pub trait Ppu { 32 | fn ppu(&self) -> &ppu::Ppu; 33 | fn ppu_mut(&mut self) -> &mut ppu::Ppu; 34 | 35 | fn read_ppu(&mut self, addr: u16) -> u8; 36 | fn write_ppu(&mut self, addr: u16, data: u8); 37 | fn tick_ppu(&mut self); 38 | } 39 | 40 | #[delegatable_trait] 41 | pub trait Apu { 42 | fn apu(&self) -> &apu::Apu; 43 | fn apu_mut(&mut self) -> &mut apu::Apu; 44 | 45 | fn read_apu(&mut self, addr: u16) -> u8; 46 | fn write_apu(&mut self, addr: u16, data: u8); 47 | fn tick_apu(&mut self); 48 | } 49 | 50 | #[delegatable_trait] 51 | pub trait Mapper { 52 | fn read_prg_mapper(&self, addr: u16) -> u8; 53 | fn write_prg_mapper(&mut self, addr: u16, data: u8); 54 | fn read_chr_mapper(&mut self, addr: u16) -> u8; 55 | fn write_chr_mapper(&mut self, addr: u16, data: u8); 56 | fn tick_mapper(&mut self); 57 | } 58 | 59 | #[delegatable_trait] 60 | pub trait MemoryController { 61 | fn memory_ctrl(&self) -> &memory::MemoryController; 62 | fn memory_ctrl_mut(&mut self) -> &mut memory::MemoryController; 63 | 64 | fn prg_page(&self, page: u32) -> u32; 65 | fn map_prg(&mut self, page: u32, offset8k: u32); 66 | fn read_prg(&self, addr: u16) -> u8; 67 | fn write_prg(&mut self, addr: u16, data: u8); 68 | 69 | fn map_chr(&mut self, page: u32, offset1k: u32); 70 | fn read_chr(&self, addr: u16) -> u8; 71 | fn write_chr(&mut self, addr: u16, data: u8); 72 | } 73 | 74 | #[delegatable_trait] 75 | pub trait Rom { 76 | fn rom(&self) -> &rom::Rom; 77 | fn rom_mut(&mut self) -> &mut rom::Rom; 78 | } 79 | 80 | pub enum IrqSource { 81 | ApuFrame = 0, 82 | ApuDmc = 1, 83 | Mapper = 2, 84 | } 85 | 86 | #[delegatable_trait] 87 | pub trait Interrupt { 88 | fn rst(&mut self) -> bool; 89 | fn nmi(&mut self) -> bool; 90 | fn set_nmi(&mut self, nmi: bool); 91 | fn irq(&mut self) -> bool; 92 | fn irq_source(&self, source: IrqSource) -> bool; 93 | fn set_irq_source(&mut self, source: IrqSource, irq: bool); 94 | } 95 | 96 | #[delegatable_trait] 97 | pub trait Timing { 98 | fn now(&self) -> u64; 99 | fn elapse(&mut self, elapsed: u64); 100 | } 101 | 102 | #[derive(Delegate, Serialize, Deserialize)] 103 | #[delegate(Bus, target = "inner")] 104 | #[delegate(Ppu, target = "inner")] 105 | #[delegate(Apu, target = "inner")] 106 | #[delegate(Mapper, target = "inner")] 107 | #[delegate(MemoryController, target = "inner")] 108 | #[delegate(Rom, target = "inner")] 109 | #[delegate(Interrupt, target = "inner")] 110 | #[delegate(Timing, target = "inner")] 111 | pub struct Context { 112 | cpu: cpu::Cpu, 113 | inner: Inner, 114 | } 115 | 116 | impl Cpu for Context { 117 | fn cpu(&self) -> &cpu::Cpu { 118 | &self.cpu 119 | } 120 | fn cpu_mut(&mut self) -> &mut cpu::Cpu { 121 | &mut self.cpu 122 | } 123 | 124 | fn reset_cpu(&mut self) { 125 | self.cpu.reset(&mut self.inner); 126 | } 127 | fn tick_cpu(&mut self) { 128 | self.cpu.tick(&mut self.inner); 129 | } 130 | } 131 | 132 | #[derive(Delegate, Serialize, Deserialize)] 133 | #[delegate(Ppu, target = "inner")] 134 | #[delegate(Apu, target = "inner")] 135 | #[delegate(Mapper, target = "inner")] 136 | #[delegate(MemoryController, target = "inner")] 137 | #[delegate(Rom, target = "inner")] 138 | #[delegate(Interrupt, target = "inner")] 139 | #[delegate(Timing, target = "inner")] 140 | struct Inner { 141 | mem: memory::MemoryMap, 142 | inner: Inner2, 143 | } 144 | 145 | impl Bus for Inner { 146 | fn read(&mut self, addr: u16) -> u8 { 147 | self.mem.read(&mut self.inner, addr) 148 | } 149 | 150 | fn read_pure(&self, addr: u16) -> Option { 151 | self.mem.read_pure(&self.inner, addr) 152 | } 153 | 154 | fn write(&mut self, addr: u16, data: u8) { 155 | self.mem.write(&mut self.inner, addr, data); 156 | } 157 | 158 | fn tick_bus(&mut self) { 159 | self.mem.tick(&mut self.inner); 160 | } 161 | 162 | fn cpu_stall(&mut self) -> u64 { 163 | self.mem.cpu_stall() 164 | } 165 | } 166 | 167 | #[derive(Delegate, Serialize, Deserialize)] 168 | #[delegate(Mapper, target = "inner")] 169 | #[delegate(MemoryController, target = "inner")] 170 | #[delegate(Rom, target = "inner")] 171 | #[delegate(Interrupt, target = "inner")] 172 | #[delegate(Timing, target = "inner")] 173 | struct Inner2 { 174 | ppu: ppu::Ppu, 175 | apu: apu::Apu, 176 | inner: Inner3, 177 | } 178 | 179 | impl Ppu for Inner2 { 180 | fn ppu(&self) -> &ppu::Ppu { 181 | &self.ppu 182 | } 183 | fn ppu_mut(&mut self) -> &mut ppu::Ppu { 184 | &mut self.ppu 185 | } 186 | fn read_ppu(&mut self, addr: u16) -> u8 { 187 | self.ppu.read(&mut self.inner, addr) 188 | } 189 | fn write_ppu(&mut self, addr: u16, data: u8) { 190 | self.ppu.write(&mut self.inner, addr, data); 191 | } 192 | fn tick_ppu(&mut self) { 193 | self.ppu.tick(&mut self.inner); 194 | } 195 | } 196 | 197 | impl Apu for Inner2 { 198 | fn apu(&self) -> &apu::Apu { 199 | &self.apu 200 | } 201 | fn apu_mut(&mut self) -> &mut apu::Apu { 202 | &mut self.apu 203 | } 204 | fn read_apu(&mut self, addr: u16) -> u8 { 205 | self.apu.read(&mut self.inner, addr) 206 | } 207 | fn write_apu(&mut self, addr: u16, data: u8) { 208 | self.apu.write(&mut self.inner, addr, data); 209 | } 210 | fn tick_apu(&mut self) { 211 | self.apu.tick(&mut self.inner); 212 | } 213 | } 214 | 215 | #[derive(Delegate, Serialize, Deserialize)] 216 | #[delegate(MemoryController, target = "inner")] 217 | #[delegate(Rom, target = "inner")] 218 | #[delegate(Interrupt, target = "inner")] 219 | #[delegate(Timing, target = "inner")] 220 | struct Inner3 { 221 | mapper: mapper::Mapper, 222 | inner: Inner4, 223 | } 224 | 225 | impl Mapper for Inner3 { 226 | fn read_prg_mapper(&self, addr: u16) -> u8 { 227 | use mapper::MapperTrait; 228 | self.mapper.read_prg(&self.inner, addr) 229 | } 230 | fn write_prg_mapper(&mut self, addr: u16, data: u8) { 231 | use mapper::MapperTrait; 232 | self.mapper.write_prg(&mut self.inner, addr, data); 233 | } 234 | fn read_chr_mapper(&mut self, addr: u16) -> u8 { 235 | use mapper::MapperTrait; 236 | self.mapper.read_chr(&mut self.inner, addr) 237 | } 238 | fn write_chr_mapper(&mut self, addr: u16, data: u8) { 239 | use mapper::MapperTrait; 240 | self.mapper.write_chr(&mut self.inner, addr, data); 241 | } 242 | fn tick_mapper(&mut self) { 243 | use mapper::MapperTrait; 244 | self.mapper.tick(&mut self.inner) 245 | } 246 | } 247 | 248 | #[derive(Delegate, Serialize, Deserialize)] 249 | #[delegate(Rom, target = "rom")] 250 | #[delegate(Interrupt, target = "signales")] 251 | struct Inner4 { 252 | mem_ctrl: memory::MemoryController, 253 | #[serde(skip)] 254 | rom: rom::Rom, 255 | signales: Signales, 256 | now: u64, 257 | } 258 | 259 | impl MemoryController for Inner4 { 260 | fn memory_ctrl(&self) -> &memory::MemoryController { 261 | &self.mem_ctrl 262 | } 263 | fn memory_ctrl_mut(&mut self) -> &mut memory::MemoryController { 264 | &mut self.mem_ctrl 265 | } 266 | 267 | fn prg_page(&self, page: u32) -> u32 { 268 | self.mem_ctrl.prg_page(page) 269 | } 270 | fn map_prg(&mut self, page: u32, bank8k: u32) { 271 | self.mem_ctrl.map_prg(&self.rom, page, bank8k); 272 | } 273 | fn read_prg(&self, addr: u16) -> u8 { 274 | self.mem_ctrl.read_prg(&self.rom, addr) 275 | } 276 | fn write_prg(&mut self, addr: u16, data: u8) { 277 | self.mem_ctrl.write_prg(&self.rom, addr, data); 278 | } 279 | 280 | fn map_chr(&mut self, page: u32, bank1k: u32) { 281 | self.mem_ctrl.map_chr(&self.rom, page, bank1k); 282 | } 283 | fn read_chr(&self, addr: u16) -> u8 { 284 | self.mem_ctrl.read_chr(&self.rom, addr) 285 | } 286 | fn write_chr(&mut self, addr: u16, data: u8) { 287 | self.mem_ctrl.write_chr(&self.rom, addr, data); 288 | } 289 | } 290 | 291 | impl Rom for rom::Rom { 292 | fn rom(&self) -> &rom::Rom { 293 | self 294 | } 295 | fn rom_mut(&mut self) -> &mut rom::Rom { 296 | self 297 | } 298 | } 299 | 300 | #[derive(Default, Serialize, Deserialize)] 301 | struct Signales { 302 | rst: bool, 303 | nmi: bool, 304 | irq_source: [bool; 3], 305 | } 306 | 307 | impl Interrupt for Signales { 308 | fn rst(&mut self) -> bool { 309 | self.rst 310 | } 311 | fn nmi(&mut self) -> bool { 312 | self.nmi 313 | } 314 | fn set_nmi(&mut self, nmi: bool) { 315 | self.nmi = nmi; 316 | } 317 | fn irq(&mut self) -> bool { 318 | self.irq_source.iter().any(|r| *r) 319 | } 320 | fn irq_source(&self, source: IrqSource) -> bool { 321 | self.irq_source[source as usize] 322 | } 323 | fn set_irq_source(&mut self, source: IrqSource, irq: bool) { 324 | self.irq_source[source as usize] = irq; 325 | } 326 | } 327 | 328 | impl Timing for Inner4 { 329 | fn now(&self) -> u64 { 330 | self.now 331 | } 332 | fn elapse(&mut self, elapsed: u64) { 333 | self.now += elapsed; 334 | } 335 | } 336 | 337 | impl Context { 338 | pub fn new(rom: rom::Rom, backup: Option>) -> Result { 339 | let cpu = cpu::Cpu::default(); 340 | let mem = memory::MemoryMap::default(); 341 | let ppu = ppu::Ppu::default(); 342 | let apu = apu::Apu::default(); 343 | let mem_ctrl = memory::MemoryController::new(&rom, backup)?; 344 | let signales = Signales::default(); 345 | 346 | let mut inner = Inner4 { 347 | mem_ctrl, 348 | rom, 349 | signales, 350 | now: 0, 351 | }; 352 | 353 | let mapper = create_mapper(&mut inner)?; 354 | 355 | Ok(Context { 356 | cpu, 357 | inner: Inner { 358 | mem, 359 | inner: Inner2 { 360 | ppu, 361 | apu, 362 | inner: Inner3 { mapper, inner }, 363 | }, 364 | }, 365 | }) 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /tests/nes_test_roms.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use meru_interface::EmulatorCore; 3 | use sabicom::{context::Bus, Nes}; 4 | use std::path::Path; 5 | 6 | fn test_rom(path: impl AsRef) -> Result<()> { 7 | // let test_name = path.as_ref().file_stem().unwrap().to_str().unwrap(); 8 | 9 | let dat = std::fs::read(path.as_ref())?; 10 | let mut nes = Nes::try_from_file(&dat, None, &Default::default())?; 11 | 12 | let mut cnt = 0; 13 | let mut starting = true; 14 | 15 | // status code is at 0x6000 16 | // - 0x80: running 17 | // - 0x81: require reset 18 | // - 0x00..=0x7f: exit code (0 for success) 19 | 20 | let exit_code = loop { 21 | assert!(cnt < 3000, "too long time"); 22 | 23 | nes.exec_frame(false); 24 | 25 | let stat = nes.ctx.read(0x6000); 26 | if !starting && stat < 0x80 { 27 | break stat; 28 | } 29 | 30 | if !starting && stat == 0x81 { 31 | todo!("need to reset"); 32 | } 33 | 34 | if starting { 35 | if stat == 0x80 { 36 | starting = false; 37 | } 38 | } else { 39 | assert_eq!(stat, 0x80, "invalid stat = ${:02X}", stat); 40 | cnt += 1; 41 | } 42 | }; 43 | 44 | let tag = (1..=3) 45 | .map(|i| nes.ctx.read(0x6000 + i)) 46 | .collect::>(); 47 | 48 | assert_eq!(tag, [0xDE, 0xB0, 0x61]); 49 | 50 | let mut msg = String::new(); 51 | for i in 0x6004.. { 52 | let c = nes.ctx.read(i); 53 | if c == 0 { 54 | break; 55 | } 56 | msg.push(c as char); 57 | } 58 | 59 | assert_eq!(exit_code, 0x00, "Exit code is not 0: {exit_code}, {msg}",); 60 | assert!(msg.ends_with("\nPassed\n"), "msg: {msg}"); 61 | 62 | Ok(()) 63 | } 64 | 65 | macro_rules! test_rom { 66 | ($title:ident, $path:literal) => { 67 | #[test] 68 | fn $title() -> anyhow::Result<()> { 69 | test_rom(&format!("{}", $path)) 70 | } 71 | }; 72 | } 73 | 74 | macro_rules! test_roms { 75 | ($($title:ident => $path:literal,)*) => { 76 | $( 77 | test_rom!($title, $path); 78 | )* 79 | }; 80 | } 81 | 82 | test_roms! { 83 | instr_test_v3_01_implied => "nes-test-roms/instr_test-v3/rom_singles/01-implied.nes", 84 | instr_test_v3_02_immediate => "nes-test-roms/instr_test-v3/rom_singles/02-immediate.nes", 85 | instr_test_v3_03_zero_page => "nes-test-roms/instr_test-v3/rom_singles/03-zero_page.nes", 86 | instr_test_v3_04_zp_xy => "nes-test-roms/instr_test-v3/rom_singles/04-zp_xy.nes", 87 | instr_test_v3_05_absolute => "nes-test-roms/instr_test-v3/rom_singles/05-absolute.nes", 88 | instr_test_v3_06_abs_xy => "nes-test-roms/instr_test-v3/rom_singles/06-abs_xy.nes", 89 | instr_test_v3_07_ind_x => "nes-test-roms/instr_test-v3/rom_singles/07-ind_x.nes", 90 | instr_test_v3_08_ind_y => "nes-test-roms/instr_test-v3/rom_singles/08-ind_y.nes", 91 | instr_test_v3_09_branches => "nes-test-roms/instr_test-v3/rom_singles/09-branches.nes", 92 | instr_test_v3_10_stack => "nes-test-roms/instr_test-v3/rom_singles/10-stack.nes", 93 | instr_test_v3_11_jmp_jsr => "nes-test-roms/instr_test-v3/rom_singles/11-jmp_jsr.nes", 94 | instr_test_v3_12_rts => "nes-test-roms/instr_test-v3/rom_singles/12-rts.nes", 95 | instr_test_v3_13_rti => "nes-test-roms/instr_test-v3/rom_singles/13-rti.nes", 96 | instr_test_v3_14_brk => "nes-test-roms/instr_test-v3/rom_singles/14-brk.nes", 97 | instr_test_v3_15_special => "nes-test-roms/instr_test-v3/rom_singles/15-special.nes", 98 | 99 | instr_test_v5_01_basics => "instr_test_v5/01-basics.nes", 100 | instr_test_v5_02_implied => "instr_test_v5/02-implied.nes", 101 | instr_test_v5_03_immediate => "instr_test_v5/03-immediate.nes", 102 | instr_test_v5_04_zero_page => "instr_test_v5/04-zero_page.nes", 103 | instr_test_v5_05_zp_xy => "instr_test_v5/05-zp_xy.nes", 104 | instr_test_v5_06_absolute => "instr_test_v5/06-absolute.nes", 105 | instr_test_v5_07_abs_xy => "instr_test_v5/07-abs_xy.nes", 106 | instr_test_v5_08_ind_x => "instr_test_v5/08-ind_x.nes", 107 | instr_test_v5_09_ind_y => "instr_test_v5/09-ind_y.nes", 108 | instr_test_v5_10_branches => "instr_test_v5/10-branches.nes", 109 | instr_test_v5_11_stack => "instr_test_v5/11-stack.nes", 110 | instr_test_v5_12_jmp_jsr => "instr_test_v5/12-jmp_jsr.nes", 111 | instr_test_v5_13_rts => "instr_test_v5/13-rts.nes", 112 | instr_test_v5_14_rti => "instr_test_v5/14-rti.nes", 113 | instr_test_v5_15_brk => "instr_test_v5/15-brk.nes", 114 | instr_test_v5_16_special => "instr_test_v5/16-special.nes", 115 | // instr_test_v5_all_instrs => "instr_test_v5/all_instrs.nes", 116 | // instr_test_v5_official_only => "instr_test_v5/official_only.nes", 117 | 118 | // cpu_dummy_reads => "nes-test-roms/cpu_dummy_reads/cpu_dummy_reads.nes", 119 | cpu_dummy_writes_oam => "nes-test-roms/cpu_dummy_writes/cpu_dummy_writes_oam.nes", 120 | cpu_dummy_writes_ppumem => "nes-test-roms/cpu_dummy_writes/cpu_dummy_writes_ppumem.nes", 121 | 122 | ppu_vbl_nmi_01_vbl_basics => "nes-test-roms/ppu_vbl_nmi/rom_singles/01-vbl_basics.nes", 123 | ppu_vbl_nmi_02_vbl_set_time => "nes-test-roms/ppu_vbl_nmi/rom_singles/02-vbl_set_time.nes", 124 | ppu_vbl_nmi_03_vbl_clear_time => "nes-test-roms/ppu_vbl_nmi/rom_singles/03-vbl_clear_time.nes", 125 | ppu_vbl_nmi_04_nmi_control => "nes-test-roms/ppu_vbl_nmi/rom_singles/04-nmi_control.nes", 126 | ppu_vbl_nmi_05_nmi_timing => "nes-test-roms/ppu_vbl_nmi/rom_singles/05-nmi_timing.nes", 127 | ppu_vbl_nmi_06_supression => "nes-test-roms/ppu_vbl_nmi/rom_singles/06-suppression.nes", 128 | ppu_vbl_nmi_07_nmi_on_timing => "nes-test-roms/ppu_vbl_nmi/rom_singles/07-nmi_on_timing.nes", 129 | ppu_vbl_nmi_08_nmi_off_timing => "nes-test-roms/ppu_vbl_nmi/rom_singles/08-nmi_off_timing.nes", 130 | ppu_vbl_nmi_09_even_odd_frames => "nes-test-roms/ppu_vbl_nmi/rom_singles/09-even_odd_frames.nes", 131 | ppu_vbl_nmi_10_even_odd_timing => "nes-test-roms/ppu_vbl_nmi/rom_singles/10-even_odd_timing.nes", 132 | // "ppu_vbl_nmi/ppu_vbl_nmi.nes", 133 | 134 | // "MMC1_A12/mmc1_a12.nes", 135 | // "PaddleTest3/PaddleTest.nes", 136 | // "apu_mixer/dmc.nes", 137 | // "apu_mixer/noise.nes", 138 | // "apu_mixer/square.nes", 139 | // "apu_mixer/triangle.nes", 140 | // "apu_reset/4015_cleared.nes", 141 | // "apu_reset/4017_timing.nes", 142 | // "apu_reset/4017_written.nes", 143 | // "apu_reset/irq_flag_cleared.nes", 144 | // "apu_reset/len_ctrs_enabled.nes", 145 | // "apu_reset/works_immediately.nes", 146 | // "apu_test/apu_test.nes", 147 | // // "apu_test/rom_singles/1-len_ctr.nes", 148 | // // "apu_test/rom_singles/2-len_table.nes", 149 | // // "apu_test/rom_singles/3-irq_flag.nes", 150 | // // "apu_test/rom_singles/4-jitter.nes", 151 | // // "apu_test/rom_singles/5-len_timing.nes", 152 | // // "apu_test/rom_singles/6-irq_flag_timing.nes", 153 | // // "apu_test/rom_singles/7-dmc_basics.nes", 154 | // // "apu_test/rom_singles/8-dmc_rates.nes", 155 | // "blargg_apu_2005.07.30/01.len_ctr.nes", 156 | // "blargg_apu_2005.07.30/02.len_table.nes", 157 | // "blargg_apu_2005.07.30/03.irq_flag.nes", 158 | // "blargg_apu_2005.07.30/04.clock_jitter.nes", 159 | // "blargg_apu_2005.07.30/05.len_timing_mode0.nes", 160 | // "blargg_apu_2005.07.30/06.len_timing_mode1.nes", 161 | // "blargg_apu_2005.07.30/07.irq_flag_timing.nes", 162 | // "blargg_apu_2005.07.30/08.irq_timing.nes", 163 | // "blargg_apu_2005.07.30/09.reset_timing.nes", 164 | // "blargg_apu_2005.07.30/10.len_halt_timing.nes", 165 | // "blargg_apu_2005.07.30/11.len_reload_timing.nes", 166 | // "blargg_litewall/blargg_litewall-10c.nes", 167 | // "blargg_litewall/blargg_litewall-9.nes", 168 | // "blargg_litewall/litewall2.nes", 169 | // "blargg_litewall/litewall3.nes", 170 | // "blargg_litewall/litewall5.nes", 171 | // "blargg_nes_cpu_test5/cpu.nes", 172 | // "blargg_nes_cpu_test5/official.nes", 173 | // "blargg_ppu_tests_2005.09.15b/palette_ram.nes", 174 | // "blargg_ppu_tests_2005.09.15b/power_up_palette.nes", 175 | // "blargg_ppu_tests_2005.09.15b/sprite_ram.nes", 176 | // "blargg_ppu_tests_2005.09.15b/vbl_clear_time.nes", 177 | // "blargg_ppu_tests_2005.09.15b/vram_access.nes", 178 | // "branch_timing_tests/1.Branch_Basics.nes", 179 | // "branch_timing_tests/2.Backward_Branch.nes", 180 | // "branch_timing_tests/3.Forward_Branch.nes", 181 | // "cpu_exec_space/test_cpu_exec_space_apu.nes", 182 | // "cpu_exec_space/test_cpu_exec_space_ppuio.nes", 183 | // "cpu_interrupts_v2/cpu_interrupts.nes", 184 | // // "cpu_interrupts_v2/rom_singles/1-cli_latency.nes", 185 | // // "cpu_interrupts_v2/rom_singles/2-nmi_and_brk.nes", 186 | // // "cpu_interrupts_v2/rom_singles/3-nmi_and_irq.nes", 187 | // // "cpu_interrupts_v2/rom_singles/4-irq_and_dma.nes", 188 | // // "cpu_interrupts_v2/rom_singles/5-branch_delays_irq.nes", 189 | // "cpu_reset/ram_after_reset.nes", 190 | // "cpu_reset/registers.nes", 191 | // "cpu_timing_test6/cpu_timing_test.nes", 192 | // "dmc_dma_during_read4/dma_2007_read.nes", 193 | // "dmc_dma_during_read4/dma_2007_write.nes", 194 | // "dmc_dma_during_read4/dma_4016_read.nes", 195 | // "dmc_dma_during_read4/double_2007_read.nes", 196 | // "dmc_dma_during_read4/read_write_2007.nes", 197 | // "dmc_tests/buffer_retained.nes", 198 | // "dmc_tests/latency.nes", 199 | // "dmc_tests/status.nes", 200 | // "dmc_tests/status_irq.nes", 201 | // "dpcmletterbox/dpcmletterbox.nes", 202 | // "dpcmletterbox/obj/nes", 203 | // "exram/mmc5exram.nes", 204 | // "full_palette/flowing_palette.nes", 205 | // "full_palette/full_palette.nes", 206 | // "full_palette/full_palette_smooth.nes", 207 | // "instr_misc/instr_misc.nes", 208 | // // "instr_misc/rom_singles/01-abs_x_wrap.nes", 209 | // // "instr_misc/rom_singles/02-branch_wrap.nes", 210 | // // "instr_misc/rom_singles/03-dummy_reads.nes", 211 | // // "instr_misc/rom_singles/04-dummy_reads_apu.nes", 212 | // "instr_test-v3/all_instrs.nes", 213 | // "instr_test-v3/official_only.nes", 214 | // "instr_timing/instr_timing.nes", 215 | // // "instr_timing/rom_singles/1-instr_timing.nes", 216 | // // "instr_timing/rom_singles/2-branch_timing.nes", 217 | // "m22chrbankingtest/0-127.nes", 218 | // "mmc3_irq_tests/1.Clocking.nes", 219 | // "mmc3_irq_tests/2.Details.nes", 220 | // "mmc3_irq_tests/3.A12_clocking.nes", 221 | // "mmc3_irq_tests/4.Scanline_timing.nes", 222 | // "mmc3_irq_tests/5.MMC3_rev_A.nes", 223 | // "mmc3_irq_tests/6.MMC3_rev_B.nes", 224 | // "mmc3_test/1-clocking.nes", 225 | // "mmc3_test/2-details.nes", 226 | // "mmc3_test/3-A12_clocking.nes", 227 | // "mmc3_test/4-scanline_timing.nes", 228 | // "mmc3_test/5-MMC3.nes", 229 | // "mmc3_test/6-MMC6.nes", 230 | // // "mmc3_test_2/rom_singles/1-clocking.nes", 231 | // // "mmc3_test_2/rom_singles/2-details.nes", 232 | // // "mmc3_test_2/rom_singles/3-A12_clocking.nes", 233 | // // "mmc3_test_2/rom_singles/4-scanline_timing.nes", 234 | // // "mmc3_test_2/rom_singles/5-MMC3.nes", 235 | // // "mmc3_test_2/rom_singles/6-MMC3_alt.nes", 236 | // "mmc5test/mmc5test.nes", 237 | // "mmc5test_v2/mmc5test.nes", 238 | // "nes15-1.0.0/nes15-NTSC.nes", 239 | // "nes15-1.0.0/nes15-PAL.nes", 240 | // // "nes_instr_test/rom_singles/01-implied.nes", 241 | // // "nes_instr_test/rom_singles/02-immediate.nes", 242 | // // "nes_instr_test/rom_singles/03-zero_page.nes", 243 | // // "nes_instr_test/rom_singles/04-zp_xy.nes", 244 | // // "nes_instr_test/rom_singles/05-absolute.nes", 245 | // // "nes_instr_test/rom_singles/06-abs_xy.nes", 246 | // // "nes_instr_test/rom_singles/07-ind_x.nes", 247 | // // "nes_instr_test/rom_singles/08-ind_y.nes", 248 | // // "nes_instr_test/rom_singles/09-branches.nes", 249 | // // "nes_instr_test/rom_singles/10-stack.nes", 250 | // // "nes_instr_test/rom_singles/11-special.nes", 251 | // "nmi_sync/demo_ntsc.nes", 252 | // "nmi_sync/demo_pal.nes", 253 | // "nrom368/fail368.nes", 254 | // "nrom368/test1.nes", 255 | // "ny2011/ny2011.nes", 256 | // "oam_read/oam_read.nes", 257 | // "oam_stress/oam_stress.nes", 258 | // "other/2003-test.nes", 259 | // "other/8bitpeoples_-_deadline_console_invitro.nes", 260 | // "other/BladeBuster.nes", 261 | // "other/Duelito.nes", 262 | // "other/PCM.demo.wgraphics.nes", 263 | // "other/SimpleParallaxDemo.nes", 264 | // "other/Streemerz_bundle.nes", 265 | // "other/apocalypse.nes", 266 | // "other/blargg_litewall-2.nes", 267 | // "other/blargg_litewall-9.nes", 268 | // "other/demo jitter.nes", 269 | // "other/demo.nes", 270 | // "other/fceuxd.nes", 271 | // "other/firefly.nes", 272 | // "other/high-hopes.nes", 273 | // "other/logo (E).nes", 274 | // "other/manhole.nes", 275 | // "other/max-300.nes", 276 | // "other/midscanline.nes", 277 | // "other/minipack.nes", 278 | // "other/nescafe.nes", 279 | // "other/nestest.nes", 280 | // "other/nestopia.nes", 281 | // "other/new-game.nes", 282 | // "other/nintendulator.nes", 283 | // "other/oam3.nes", 284 | // "other/oc.nes", 285 | // "other/physics.0.1.nes", 286 | // "other/pulsar.nes", 287 | // "other/quantum_disco_brothers_by_wAMMA.nes", 288 | // "other/rastesam4.nes", 289 | // "other/read2004.nes", 290 | // "other/snow.nes", 291 | // "other/test001.nes", 292 | // "other/test28.nes", 293 | // "other/window2_ntsc.nes", 294 | // "other/window2_pal.nes", 295 | // "other/window_old_ntsc.nes", 296 | // "other/window_old_pal.nes", 297 | // "pal_apu_tests/01.len_ctr.nes", 298 | // "pal_apu_tests/02.len_table.nes", 299 | // "pal_apu_tests/03.irq_flag.nes", 300 | // "pal_apu_tests/04.clock_jitter.nes", 301 | // "pal_apu_tests/05.len_timing_mode0.nes", 302 | // "pal_apu_tests/06.len_timing_mode1.nes", 303 | // "pal_apu_tests/07.irq_flag_timing.nes", 304 | // "pal_apu_tests/08.irq_timing.nes", 305 | // "pal_apu_tests/10.len_halt_timing.nes", 306 | // "pal_apu_tests/11.len_reload_timing.nes", 307 | // "ppu_open_bus/ppu_open_bus.nes", 308 | // "ppu_read_buffer/test_ppu_read_buffer.nes", 309 | // "read_joy3/count_errors.nes", 310 | // "read_joy3/count_errors_fast.nes", 311 | // "read_joy3/test_buttons.nes", 312 | // "read_joy3/thorough_test.nes", 313 | // "scanline-a1/scanline.nes", 314 | // "scanline/scanline.nes", 315 | // "scrolltest/scroll.nes", 316 | // "sprdma_and_dmc_dma/sprdma_and_dmc_dma.nes", 317 | // "sprdma_and_dmc_dma/sprdma_and_dmc_dma_512.nes", 318 | // "sprite_hit_tests_2005.10.05/01.basics.nes", 319 | // "sprite_hit_tests_2005.10.05/02.alignment.nes", 320 | // "sprite_hit_tests_2005.10.05/03.corners.nes", 321 | // "sprite_hit_tests_2005.10.05/04.flip.nes", 322 | // "sprite_hit_tests_2005.10.05/05.left_clip.nes", 323 | // "sprite_hit_tests_2005.10.05/06.right_edge.nes", 324 | // "sprite_hit_tests_2005.10.05/07.screen_bottom.nes", 325 | // "sprite_hit_tests_2005.10.05/08.double_height.nes", 326 | // "sprite_hit_tests_2005.10.05/09.timing_basics.nes", 327 | // "sprite_hit_tests_2005.10.05/10.timing_order.nes", 328 | // "sprite_hit_tests_2005.10.05/11.edge_timing.nes", 329 | // "sprite_overflow_tests/1.Basics.nes", 330 | // "sprite_overflow_tests/2.Details.nes", 331 | // "sprite_overflow_tests/3.Timing.nes", 332 | // "sprite_overflow_tests/4.Obscure.nes", 333 | // "sprite_overflow_tests/5.Emulator.nes", 334 | // "spritecans-2011/obj/nes", 335 | // "spritecans-2011/spritecans.nes", 336 | // "stomper/smwstomp.nes", 337 | // "tutor/tutor.nes", 338 | // "tvpassfail/tv.nes", 339 | // "vaus-test/obj/nes", 340 | // "vaus-test/vaus-test.nes", 341 | // "vbl_nmi_timing/1.frame_basics.nes", 342 | // "vbl_nmi_timing/2.vbl_timing.nes", 343 | // "vbl_nmi_timing/3.even_odd_frames.nes", 344 | // "vbl_nmi_timing/4.vbl_clear_timing.nes", 345 | // "vbl_nmi_timing/5.nmi_suppression.nes", 346 | // "vbl_nmi_timing/6.nmi_disable.nes", 347 | // "vbl_nmi_timing/7.nmi_timing.nes", 348 | // "volume_tests/obj/nes", 349 | // "volume_tests/volumes.nes", 350 | // "window5/colorwin_ntsc.nes", 351 | // "window5/colorwin_pal.nes", 352 | } 353 | -------------------------------------------------------------------------------- /src/ppu.rs: -------------------------------------------------------------------------------- 1 | use bitvec::prelude::*; 2 | use meru_interface::FrameBuffer; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::{consts::*, context, palette::NES_PALETTE, util::trait_alias}; 6 | 7 | trait_alias!(pub trait Context = context::Mapper + context::Interrupt); 8 | 9 | #[derive(Serialize, Deserialize)] 10 | pub struct Ppu { 11 | reg: Register, 12 | oam: Vec, 13 | counter: usize, 14 | line: usize, 15 | frame: u64, 16 | line_buf: Vec, 17 | sprite0_hit: Vec, 18 | 19 | #[serde(skip)] 20 | frame_buffer: FrameBuffer, 21 | render_graphics: bool, 22 | } 23 | 24 | #[derive(Default, Serialize, Deserialize)] 25 | struct Register { 26 | buf: u8, 27 | vram_read_buf: u8, 28 | 29 | nmi_enable: bool, 30 | ppu_master: bool, 31 | sprite_size: bool, 32 | bg_pat_addr: bool, 33 | sprite_pat_addr: bool, 34 | ppu_addr_incr: bool, 35 | 36 | bg_color: u8, 37 | sprite_visible: bool, 38 | bg_visible: bool, 39 | sprite_clip: bool, 40 | bg_clip: bool, 41 | color_display: bool, 42 | 43 | oam_addr: u8, 44 | 45 | toggle: bool, 46 | scroll_x: u8, 47 | tmp_addr: u16, 48 | cur_addr: u16, 49 | 50 | vblank: bool, 51 | sprite0_hit: bool, 52 | sprite_over: bool, 53 | } 54 | 55 | impl Register { 56 | fn new() -> Self { 57 | Self { 58 | ..Default::default() 59 | } 60 | } 61 | } 62 | 63 | impl Default for Ppu { 64 | fn default() -> Self { 65 | Self { 66 | reg: Register::new(), 67 | oam: vec![0x00; 256], 68 | counter: 0, 69 | line: 0, 70 | frame: 0, 71 | line_buf: vec![0x00; SCREEN_WIDTH], 72 | sprite0_hit: vec![false; SCREEN_WIDTH], 73 | frame_buffer: FrameBuffer::new(SCREEN_WIDTH, SCREEN_HEIGHT), 74 | render_graphics: true, 75 | } 76 | } 77 | } 78 | 79 | impl Ppu { 80 | pub fn frame_buffer(&self) -> &FrameBuffer { 81 | &self.frame_buffer 82 | } 83 | 84 | pub fn frame_buffer_mut(&mut self) -> &mut FrameBuffer { 85 | &mut self.frame_buffer 86 | } 87 | 88 | pub fn frame(&self) -> u64 { 89 | self.frame 90 | } 91 | 92 | pub fn set_render_graphics(&mut self, render: bool) { 93 | self.render_graphics = render; 94 | } 95 | 96 | pub fn tick(&mut self, ctx: &mut impl Context) { 97 | // 1 PPU cycle for 1 pixel 98 | 99 | let screen_visible = self.reg.bg_visible || self.reg.sprite_visible; 100 | 101 | if self.counter == 0 { 102 | log::info!("line {} starts", self.line); 103 | 104 | if self.line == SCREEN_RANGE.start && screen_visible { 105 | self.reg.cur_addr = self.reg.tmp_addr; 106 | } 107 | 108 | if SCREEN_RANGE.contains(&self.line) && screen_visible { 109 | self.reg.cur_addr = (self.reg.cur_addr & 0xfbe0) | (self.reg.tmp_addr & 0x041f); 110 | } 111 | 112 | if SCREEN_RANGE.contains(&self.line) { 113 | self.render_line(ctx); 114 | 115 | if screen_visible { 116 | if (self.reg.cur_addr >> 12) & 7 == 7 { 117 | self.reg.cur_addr &= !0x7000; 118 | if ((self.reg.cur_addr >> 5) & 0x1f) == 29 { 119 | self.reg.cur_addr = (self.reg.cur_addr & !0x03e0) ^ 0x800; 120 | } else if (self.reg.cur_addr >> 5) & 0x1f == 0x1f { 121 | self.reg.cur_addr &= !0x03e0; 122 | } else { 123 | self.reg.cur_addr += 0x20; 124 | } 125 | } else { 126 | self.reg.cur_addr += 0x1000; 127 | } 128 | } 129 | } 130 | } 131 | 132 | if (self.line, self.counter) == (POST_RENDER_LINE + 1, 1) { 133 | log::info!("enter vblank"); 134 | self.reg.vblank = true; 135 | } 136 | 137 | if (self.line, self.counter) == (PRE_RENDER_LINE, 1) { 138 | log::info!("leave vblank"); 139 | self.reg.vblank = false; 140 | self.reg.sprite0_hit = false; 141 | } 142 | 143 | if screen_visible 144 | && (self.line < SCREEN_RANGE.end || self.line == PRE_RENDER_LINE) 145 | && self.counter == 256 146 | { 147 | let bg_pat_addr = if self.reg.bg_pat_addr { 0x1000 } else { 0 }; 148 | let spr_pat_addr = if self.reg.sprite_pat_addr { 0x1000 } else { 0 }; 149 | // FIXME: Dummy read for mapper that use CHR Address value 150 | let _ = read_pattern(ctx, bg_pat_addr); 151 | let _ = read_pattern(ctx, spr_pat_addr); 152 | } 153 | 154 | if screen_visible 155 | && SCREEN_RANGE.contains(&self.line) 156 | && self.counter < SCREEN_WIDTH 157 | && self.sprite0_hit[self.counter as usize] 158 | { 159 | self.reg.sprite0_hit = true; 160 | } 161 | 162 | self.counter += 1; 163 | 164 | if self.counter == PPU_CLOCK_PER_LINE as usize { 165 | self.counter = 0; 166 | self.line += 1; 167 | if self.line == LINES_PER_FRAME { 168 | self.line = 0; 169 | self.frame += 1; 170 | } 171 | } 172 | 173 | let nmi = !(self.reg.vblank && self.reg.nmi_enable); 174 | ctx.set_nmi(nmi); 175 | } 176 | 177 | pub fn render_line(&mut self, ctx: &mut impl Context) { 178 | let bg = read_palette(ctx, 0) & 0x3f; 179 | self.line_buf.fill(bg); 180 | self.sprite0_hit.fill(false); 181 | 182 | self.render_bg(ctx); 183 | self.render_spr(ctx); 184 | 185 | if self.reg.bg_clip || self.reg.sprite_clip { 186 | for i in 0..8 { 187 | assert!(!self.sprite0_hit[i]); 188 | } 189 | } 190 | 191 | for x in 0..SCREEN_WIDTH { 192 | *self.frame_buffer.pixel_mut(x, self.line) = 193 | NES_PALETTE[self.line_buf[x] as usize & 0x3f].clone(); 194 | } 195 | } 196 | 197 | pub fn render_bg(&mut self, ctx: &mut impl Context) { 198 | let x_ofs = self.reg.scroll_x as usize; 199 | let y_ofs = (self.reg.cur_addr >> 12) & 7; 200 | let pat_addr = if self.reg.bg_pat_addr { 0x1000 } else { 0x0000 }; 201 | let leftmost = if self.reg.bg_clip { 8 } else { 0 }; 202 | 203 | let _ = read_pattern(ctx, pat_addr); 204 | 205 | if !self.reg.bg_visible { 206 | return; 207 | } 208 | 209 | let mut name_addr = self.reg.cur_addr & 0xfff; 210 | 211 | for i in 0..33 { 212 | let tile = read_nametable(ctx, name_addr) as u16 * 16; 213 | 214 | let b0 = read_pattern(ctx, pat_addr + tile + y_ofs); 215 | let b1 = read_pattern(ctx, pat_addr + tile + 8 + y_ofs); 216 | 217 | let name_addr_v = name_addr.view_bits::(); 218 | let tx = &name_addr_v[0..5]; 219 | let ty = &name_addr_v[5..10]; 220 | 221 | let attr_addr = bits![mut u16, Lsb0; 0; 16]; 222 | attr_addr[10..12].copy_from_bitslice(&name_addr_v[10..12]); 223 | attr_addr[6..10].store(0b1111_u16); 224 | attr_addr[3..6].copy_from_bitslice(&ty[2..5]); 225 | attr_addr[0..3].copy_from_bitslice(&tx[2..5]); 226 | 227 | let aofs = tx[1] as usize * 2 + ty[1] as usize * 4; 228 | let attr = (read_nametable(ctx, attr_addr.load()) >> aofs) & 3; 229 | 230 | for lx in 0..8 { 231 | let x = (i * 8 + lx + 8 - x_ofs) as usize; 232 | if !(x >= 8 + leftmost && x < SCREEN_WIDTH + 8) { 233 | continue; 234 | } 235 | 236 | let b = (b0 >> (7 - lx)) & 1 | ((b1 >> (7 - lx)) & 1) << 1; 237 | if b != 0 { 238 | self.line_buf[x - 8] = 0x40 + read_palette(ctx, attr << 2 | b); 239 | } 240 | } 241 | 242 | if name_addr & 0x1f == 0x1f { 243 | name_addr = (name_addr & !0x1f) ^ 0x400; 244 | } else { 245 | name_addr += 1; 246 | } 247 | } 248 | } 249 | 250 | pub fn render_spr(&mut self, ctx: &mut impl Context) { 251 | if !self.reg.sprite_visible { 252 | return; 253 | } 254 | 255 | let spr_height = if self.reg.sprite_size { 16 } else { 8 }; 256 | let pat_addr = if self.reg.sprite_pat_addr { 0x1000 } else { 0 }; 257 | let leftmost = if self.reg.sprite_clip { 8 } else { 0 }; 258 | 259 | for i in 0..64 { 260 | let r = &self.oam[i * 4..(i + 1) * 4]; 261 | let spr_y = r[0] as usize + 1; 262 | 263 | if i == 0 { 264 | log::trace!("sprite {i}, y = {spr_y}, cur_line: {}", self.line); 265 | } 266 | 267 | if !(spr_y..spr_y + spr_height).contains(&self.line) { 268 | continue; 269 | } 270 | 271 | let tile_index = r[1] as u16; 272 | let spr_x = r[3] as usize; 273 | 274 | log::trace!("sprite {i}, x = {spr_x}, y = {spr_y}, tile = {tile_index}"); 275 | 276 | let attr = r[2].view_bits::(); 277 | let upper = attr[0..2].load::() << 2; 278 | let is_bg = attr[5]; 279 | let h_flip = !attr[6]; 280 | let v_flip = attr[7]; 281 | 282 | let y_ofs = if v_flip { 283 | spr_height - 1 - (self.line - spr_y) 284 | } else { 285 | self.line - spr_y 286 | }; 287 | 288 | let tile_addr = if spr_height == 16 { 289 | (tile_index & !1) * 16 290 | + (tile_index & 1) * 0x1000 291 | + if y_ofs >= 8 { 16 } else { 0 } 292 | + (y_ofs as u16 & 7) 293 | } else { 294 | pat_addr + tile_index * 16 + y_ofs as u16 295 | }; 296 | 297 | let b0 = read_pattern(ctx, tile_addr); 298 | let b1 = read_pattern(ctx, tile_addr + 8); 299 | 300 | for lx in 0..8 { 301 | let x = spr_x + if h_flip { 7 - lx } else { lx }; 302 | if !(x >= leftmost && x < SCREEN_WIDTH) { 303 | continue; 304 | } 305 | 306 | let lo = (b0 >> lx) & 1 | ((b1 >> lx) & 1) << 1; 307 | if lo != 0 && self.line_buf[x] & 0x80 == 0 { 308 | if i == 0 && x < 255 && self.line_buf[x] & 0x40 != 0 { 309 | self.sprite0_hit[x] = true; 310 | } 311 | if !is_bg || self.line_buf[x] & 0x40 == 0 { 312 | self.line_buf[x] = read_palette(ctx, 0x10 | upper | lo); 313 | } 314 | self.line_buf[x] |= 0x80; 315 | } 316 | } 317 | } 318 | } 319 | 320 | pub fn read(&mut self, ctx: &mut impl Context, addr: u16) -> u8 { 321 | let ret = match addr { 322 | 2 => { 323 | // Status 324 | let ret = bits![mut u8, Lsb0; 0; 8]; 325 | ret[0..5].store(self.reg.buf & 0x1f); 326 | ret.set(5, self.reg.sprite_over); 327 | ret.set(6, self.reg.sprite0_hit); 328 | ret.set(7, self.reg.vblank); 329 | 330 | self.reg.vblank = false; 331 | self.reg.toggle = false; 332 | 333 | log::info!(target: "ppureg", "[PPUSTATUS] -> ${ret:02X}"); 334 | 335 | ret.load() 336 | } 337 | 338 | 4 => { 339 | // OAM Data 340 | let ret = self.oam[self.reg.oam_addr as usize]; 341 | let ret = if self.reg.oam_addr & 3 == 2 { 342 | ret & 0xe3 343 | } else { 344 | ret 345 | }; 346 | 347 | log::info!(target: "ppureg", "[OAMDATA] -> ${ret:02X}",); 348 | 349 | ret 350 | } 351 | 352 | 7 => { 353 | // Data 354 | let addr = self.reg.cur_addr & 0x3fff; 355 | 356 | let ret = if addr & 0x3f00 == 0x3f00 { 357 | self.reg.vram_read_buf = ctx.read_chr_mapper(addr & !0x1000); 358 | ctx.read_chr_mapper(addr) 359 | } else { 360 | let ret = self.reg.vram_read_buf; 361 | self.reg.vram_read_buf = ctx.read_chr_mapper(addr); 362 | ret 363 | }; 364 | 365 | let inc_addr = if self.reg.ppu_addr_incr { 32 } else { 1 }; 366 | self.reg.cur_addr = self.reg.cur_addr.wrapping_add(inc_addr); 367 | 368 | log::info!(target: "ppureg", "[PPUDATA], CHR[${addr:04X}] -> ${ret:02X}"); 369 | 370 | ret 371 | } 372 | 373 | _ => { 374 | log::info!("Read from invalid PPU register: [{addr}]"); 375 | self.reg.buf 376 | } 377 | }; 378 | 379 | self.reg.buf = ret; 380 | ret 381 | } 382 | 383 | pub fn write(&mut self, ctx: &mut impl Context, addr: u16, data: u8) { 384 | self.reg.buf = data; 385 | 386 | match addr { 387 | 0 => { 388 | // Controller 389 | let data = data.view_bits::(); 390 | 391 | log::info!( 392 | target: "ppureg::PPUCTRL", 393 | "= b{data:08b}: nmi={nmi}, ppu={ppu}, spr={sprite_size}, bgpat=${bg_pat_addr:04X}, sprpat=${sprite_pat_addr:04X}, addrinc={ppu_addr_incr}, nt_addr=${base_nametable_addr:04X}", 394 | nmi = if data[7] { "t" } else { "f" }, 395 | ppu = if data[6] { "t" } else { "f" }, 396 | sprite_size = if data[5] { "8x16" } else { "8x8" }, 397 | bg_pat_addr = if data[4] { 0x0000 } else { 0x1000 }, 398 | sprite_pat_addr = if data[3] { 0x0000 } else { 0x1000 }, 399 | ppu_addr_incr = if data[2] { 32 } else { 1 }, 400 | base_nametable_addr = 0x2000 + data[0..2].load_le::() * 0x400, 401 | ); 402 | 403 | self.reg.nmi_enable = data[7]; 404 | self.reg.ppu_master = data[6]; 405 | self.reg.sprite_size = data[5]; 406 | self.reg.bg_pat_addr = data[4]; 407 | self.reg.sprite_pat_addr = data[3]; 408 | self.reg.ppu_addr_incr = data[2]; 409 | 410 | self.reg.tmp_addr.view_bits_mut::()[10..12].store(data[0..2].load::()); 411 | } 412 | 413 | 1 => { 414 | // Mask 415 | let data = data.view_bits::(); 416 | 417 | log::info!(target: "ppureg::PPUMASK", "= b{data:08b}: bgcol={r}{g}{b}, spr_vis={sprite_visible}, bg_vis={bg_visible}, spr_clip={sprite_clip}, bg_clip={bg_clip}, greyscale={greyscale}", 418 | r = if data[5] { "R" } else { "-" }, 419 | g = if data[6] { "G" } else { "-" }, 420 | b = if data[7] { "B" } else { "-" }, 421 | sprite_visible = if data[4] { "t" } else { "f" }, 422 | bg_visible = if data[3] { "t" } else { "f" }, 423 | sprite_clip = if data[2] { "t" } else { "f" }, 424 | bg_clip = if data[1] { "t" } else { "f" }, 425 | greyscale = if data[0] { "t" } else { "f" }, 426 | ); 427 | 428 | self.reg.bg_color = data[5..8].load_le(); 429 | self.reg.sprite_visible = data[4]; 430 | self.reg.bg_visible = data[3]; 431 | self.reg.sprite_clip = !data[2]; 432 | self.reg.bg_clip = !data[1]; 433 | self.reg.color_display = data[0]; 434 | } 435 | 2 => { 436 | // Status 437 | log::warn!("Write to $2002 = {data:02X}"); 438 | } 439 | 3 => { 440 | // OAM address 441 | log::info!(target: "ppureg::OAMADDR", "= ${data:02X}"); 442 | 443 | self.reg.oam_addr = data; 444 | } 445 | 4 => { 446 | // OAM data 447 | log::info!(target: "ppureg::OAMDATA", "= ${data:02X}: OAM[${oam_addr:02X}] = ${data:02X}", 448 | oam_addr = self.reg.oam_addr); 449 | 450 | self.oam[self.reg.oam_addr as usize] = data; 451 | self.reg.oam_addr = self.reg.oam_addr.wrapping_add(1); 452 | } 453 | 5 => { 454 | // Scroll 455 | log::info!(target: "ppureg::PPUSCROLL", "= ${data:02X}"); 456 | 457 | let data = data.view_bits::(); 458 | 459 | if !self.reg.toggle { 460 | self.reg.tmp_addr = (self.reg.tmp_addr & 0x7fe0) | data[3..8].load_le::(); 461 | self.reg.scroll_x = data[0..3].load_le(); 462 | } else { 463 | self.reg.tmp_addr = (self.reg.tmp_addr & 0x0c1f) 464 | | data[3..8].load_le::() << 5 465 | | data[0..3].load_le::() << 12; 466 | } 467 | self.reg.toggle = !self.reg.toggle; 468 | } 469 | 6 => { 470 | // Address 471 | log::info!(target: "ppureg::PPUADDR", "= ${data:02X}"); 472 | 473 | let data = data.view_bits::(); 474 | 475 | if !self.reg.toggle { 476 | self.reg.tmp_addr = 477 | (self.reg.tmp_addr & 0x00ff) | data[0..6].load_be::() << 8; 478 | } else { 479 | self.reg.tmp_addr = (self.reg.tmp_addr & 0x7f00) | data.load_be::(); 480 | self.reg.cur_addr = self.reg.tmp_addr; 481 | } 482 | self.reg.toggle = !self.reg.toggle; 483 | } 484 | 7 => { 485 | // Data 486 | let addr = self.reg.cur_addr & 0x3fff; 487 | 488 | log::info!(target: "ppureg::PPUDATA", "= ${data:02X}, CHR[${addr:04X}] <- ${data:02X}"); 489 | 490 | ctx.write_chr_mapper(addr, data); 491 | 492 | let inc_addr = if self.reg.ppu_addr_incr { 32 } else { 1 }; 493 | self.reg.cur_addr = self.reg.cur_addr.wrapping_add(inc_addr); 494 | } 495 | _ => unreachable!(), 496 | } 497 | } 498 | } 499 | 500 | fn read_nametable(ctx: &mut impl Context, addr: u16) -> u8 { 501 | ctx.read_chr_mapper(0x2000 + addr) 502 | } 503 | 504 | fn read_pattern(ctx: &mut impl Context, addr: u16) -> u8 { 505 | ctx.read_chr_mapper(addr) 506 | } 507 | 508 | fn read_palette(ctx: &mut impl Context, index: u8) -> u8 { 509 | ctx.read_chr_mapper(0x3f00 + index as u16) 510 | } 511 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.53" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" 19 | 20 | [[package]] 21 | name = "argopt" 22 | version = "0.2.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "db843b82e5fd13a6faf6deaa27f746f395c3efc09dd77b3a98a122d319c6c6c6" 25 | dependencies = [ 26 | "argopt-impl", 27 | "clap", 28 | ] 29 | 30 | [[package]] 31 | name = "argopt-impl" 32 | version = "0.2.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "f84fc104fd6455fa93bc9cb5ecf3984ebe469f1d8908443ac049d7f001f071fb" 35 | dependencies = [ 36 | "convert_case", 37 | "darling", 38 | "proc-macro2", 39 | "quote", 40 | "syn", 41 | ] 42 | 43 | [[package]] 44 | name = "arrayref" 45 | version = "0.3.6" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 48 | 49 | [[package]] 50 | name = "arrayvec" 51 | version = "0.5.2" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 54 | 55 | [[package]] 56 | name = "atty" 57 | version = "0.2.14" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 60 | dependencies = [ 61 | "hermit-abi", 62 | "libc", 63 | "winapi", 64 | ] 65 | 66 | [[package]] 67 | name = "autocfg" 68 | version = "1.0.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 71 | 72 | [[package]] 73 | name = "base64" 74 | version = "0.13.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 77 | 78 | [[package]] 79 | name = "biquad" 80 | version = "0.4.2" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "820524f5e3e3add696ddf69f79575772e152c0e78e9f0370b56990a7e808ec3e" 83 | dependencies = [ 84 | "libm", 85 | ] 86 | 87 | [[package]] 88 | name = "bitflags" 89 | version = "1.3.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 92 | 93 | [[package]] 94 | name = "bitvec" 95 | version = "1.0.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" 98 | dependencies = [ 99 | "funty", 100 | "radium", 101 | "tap", 102 | "wyz", 103 | ] 104 | 105 | [[package]] 106 | name = "blake2b_simd" 107 | version = "0.5.11" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" 110 | dependencies = [ 111 | "arrayref", 112 | "arrayvec", 113 | "constant_time_eq", 114 | ] 115 | 116 | [[package]] 117 | name = "bstr" 118 | version = "0.2.17" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 121 | dependencies = [ 122 | "lazy_static", 123 | "memchr", 124 | "regex-automata", 125 | "serde", 126 | ] 127 | 128 | [[package]] 129 | name = "byteorder" 130 | version = "1.4.3" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 133 | 134 | [[package]] 135 | name = "bytesize" 136 | version = "1.1.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70" 139 | 140 | [[package]] 141 | name = "cfg-if" 142 | version = "1.0.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 145 | 146 | [[package]] 147 | name = "chrono" 148 | version = "0.4.19" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 151 | dependencies = [ 152 | "libc", 153 | "num-integer", 154 | "num-traits", 155 | "time", 156 | "winapi", 157 | ] 158 | 159 | [[package]] 160 | name = "clap" 161 | version = "3.0.12" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "2afefa54b5c7dd40918dc1e09f213a171ab5937aadccab45e804780b238f9f43" 164 | dependencies = [ 165 | "atty", 166 | "bitflags", 167 | "clap_derive", 168 | "indexmap", 169 | "lazy_static", 170 | "os_str_bytes", 171 | "strsim", 172 | "termcolor", 173 | "textwrap", 174 | ] 175 | 176 | [[package]] 177 | name = "clap_derive" 178 | version = "3.0.12" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "0fd2078197a22f338bd4fbf7d6387eb6f0d6a3c69e6cbc09f5c93e97321fd92a" 181 | dependencies = [ 182 | "heck", 183 | "proc-macro-error", 184 | "proc-macro2", 185 | "quote", 186 | "syn", 187 | ] 188 | 189 | [[package]] 190 | name = "constant_time_eq" 191 | version = "0.1.5" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 194 | 195 | [[package]] 196 | name = "convert_case" 197 | version = "0.5.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" 200 | 201 | [[package]] 202 | name = "crc32fast" 203 | version = "1.3.1" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" 206 | dependencies = [ 207 | "cfg-if", 208 | ] 209 | 210 | [[package]] 211 | name = "crossbeam-utils" 212 | version = "0.8.6" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" 215 | dependencies = [ 216 | "cfg-if", 217 | "lazy_static", 218 | ] 219 | 220 | [[package]] 221 | name = "csv" 222 | version = "1.1.6" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" 225 | dependencies = [ 226 | "bstr", 227 | "csv-core", 228 | "itoa", 229 | "ryu", 230 | "serde", 231 | ] 232 | 233 | [[package]] 234 | name = "csv-core" 235 | version = "0.1.10" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 238 | dependencies = [ 239 | "memchr", 240 | ] 241 | 242 | [[package]] 243 | name = "darling" 244 | version = "0.13.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" 247 | dependencies = [ 248 | "darling_core", 249 | "darling_macro", 250 | ] 251 | 252 | [[package]] 253 | name = "darling_core" 254 | version = "0.13.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" 257 | dependencies = [ 258 | "fnv", 259 | "ident_case", 260 | "proc-macro2", 261 | "quote", 262 | "strsim", 263 | "syn", 264 | ] 265 | 266 | [[package]] 267 | name = "darling_macro" 268 | version = "0.13.1" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" 271 | dependencies = [ 272 | "darling_core", 273 | "quote", 274 | "syn", 275 | ] 276 | 277 | [[package]] 278 | name = "dirs" 279 | version = "1.0.5" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" 282 | dependencies = [ 283 | "libc", 284 | "redox_users", 285 | "winapi", 286 | ] 287 | 288 | [[package]] 289 | name = "encode_unicode" 290 | version = "0.3.6" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 293 | 294 | [[package]] 295 | name = "env_logger" 296 | version = "0.9.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" 299 | dependencies = [ 300 | "atty", 301 | "humantime", 302 | "log", 303 | "regex", 304 | "termcolor", 305 | ] 306 | 307 | [[package]] 308 | name = "fnv" 309 | version = "1.0.7" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 312 | 313 | [[package]] 314 | name = "funty" 315 | version = "2.0.0" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 318 | 319 | [[package]] 320 | name = "getrandom" 321 | version = "0.1.16" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 324 | dependencies = [ 325 | "cfg-if", 326 | "libc", 327 | "wasi 0.9.0+wasi-snapshot-preview1", 328 | ] 329 | 330 | [[package]] 331 | name = "hashbrown" 332 | version = "0.11.2" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 335 | 336 | [[package]] 337 | name = "heck" 338 | version = "0.4.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 341 | 342 | [[package]] 343 | name = "hermit-abi" 344 | version = "0.1.19" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 347 | dependencies = [ 348 | "libc", 349 | ] 350 | 351 | [[package]] 352 | name = "humantime" 353 | version = "2.1.0" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 356 | 357 | [[package]] 358 | name = "ident_case" 359 | version = "1.0.1" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 362 | 363 | [[package]] 364 | name = "indexmap" 365 | version = "1.8.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 368 | dependencies = [ 369 | "autocfg", 370 | "hashbrown", 371 | ] 372 | 373 | [[package]] 374 | name = "itoa" 375 | version = "0.4.8" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 378 | 379 | [[package]] 380 | name = "lazy_static" 381 | version = "1.4.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 384 | 385 | [[package]] 386 | name = "libc" 387 | version = "0.2.113" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9" 390 | 391 | [[package]] 392 | name = "libm" 393 | version = "0.1.4" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" 396 | 397 | [[package]] 398 | name = "log" 399 | version = "0.4.14" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 402 | dependencies = [ 403 | "cfg-if", 404 | ] 405 | 406 | [[package]] 407 | name = "memchr" 408 | version = "2.4.1" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 411 | 412 | [[package]] 413 | name = "num-integer" 414 | version = "0.1.44" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 417 | dependencies = [ 418 | "autocfg", 419 | "num-traits", 420 | ] 421 | 422 | [[package]] 423 | name = "num-traits" 424 | version = "0.2.14" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 427 | dependencies = [ 428 | "autocfg", 429 | ] 430 | 431 | [[package]] 432 | name = "once_cell" 433 | version = "1.9.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" 436 | 437 | [[package]] 438 | name = "os_str_bytes" 439 | version = "6.0.0" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 442 | dependencies = [ 443 | "memchr", 444 | ] 445 | 446 | [[package]] 447 | name = "prettytable-rs" 448 | version = "0.8.0" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e" 451 | dependencies = [ 452 | "atty", 453 | "csv", 454 | "encode_unicode", 455 | "lazy_static", 456 | "term", 457 | "unicode-width", 458 | ] 459 | 460 | [[package]] 461 | name = "proc-macro-error" 462 | version = "1.0.4" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 465 | dependencies = [ 466 | "proc-macro-error-attr", 467 | "proc-macro2", 468 | "quote", 469 | "syn", 470 | "version_check", 471 | ] 472 | 473 | [[package]] 474 | name = "proc-macro-error-attr" 475 | version = "1.0.4" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 478 | dependencies = [ 479 | "proc-macro2", 480 | "quote", 481 | "version_check", 482 | ] 483 | 484 | [[package]] 485 | name = "proc-macro2" 486 | version = "1.0.36" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 489 | dependencies = [ 490 | "unicode-xid", 491 | ] 492 | 493 | [[package]] 494 | name = "quote" 495 | version = "1.0.15" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 498 | dependencies = [ 499 | "proc-macro2", 500 | ] 501 | 502 | [[package]] 503 | name = "radium" 504 | version = "0.7.0" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 507 | 508 | [[package]] 509 | name = "redox_syscall" 510 | version = "0.1.57" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 513 | 514 | [[package]] 515 | name = "redox_users" 516 | version = "0.3.5" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" 519 | dependencies = [ 520 | "getrandom", 521 | "redox_syscall", 522 | "rust-argon2", 523 | ] 524 | 525 | [[package]] 526 | name = "regex" 527 | version = "1.5.4" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 530 | dependencies = [ 531 | "aho-corasick", 532 | "memchr", 533 | "regex-syntax", 534 | ] 535 | 536 | [[package]] 537 | name = "regex-automata" 538 | version = "0.1.10" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 541 | 542 | [[package]] 543 | name = "regex-syntax" 544 | version = "0.6.25" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 547 | 548 | [[package]] 549 | name = "rust-argon2" 550 | version = "0.8.3" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" 553 | dependencies = [ 554 | "base64", 555 | "blake2b_simd", 556 | "constant_time_eq", 557 | "crossbeam-utils", 558 | ] 559 | 560 | [[package]] 561 | name = "ryu" 562 | version = "1.0.9" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 565 | 566 | [[package]] 567 | name = "sabicom" 568 | version = "0.1.0" 569 | dependencies = [ 570 | "anyhow", 571 | "argopt", 572 | "biquad", 573 | "bitvec", 574 | "bytesize", 575 | "chrono", 576 | "crc32fast", 577 | "env_logger", 578 | "log", 579 | "once_cell", 580 | "prettytable-rs", 581 | "sdl2", 582 | ] 583 | 584 | [[package]] 585 | name = "sdl2" 586 | version = "0.35.1" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "f035f8e87735fa3a8437292be49fe6056450f7cbb13c230b4bcd1bdd7279421f" 589 | dependencies = [ 590 | "bitflags", 591 | "lazy_static", 592 | "libc", 593 | "sdl2-sys", 594 | ] 595 | 596 | [[package]] 597 | name = "sdl2-sys" 598 | version = "0.35.1" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "94cb479353c0603785c834e2307440d83d196bf255f204f7f6741358de8d6a2f" 601 | dependencies = [ 602 | "cfg-if", 603 | "libc", 604 | "version-compare", 605 | ] 606 | 607 | [[package]] 608 | name = "serde" 609 | version = "1.0.135" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "2cf9235533494ea2ddcdb794665461814781c53f19d87b76e571a1c35acbad2b" 612 | 613 | [[package]] 614 | name = "strsim" 615 | version = "0.10.0" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 618 | 619 | [[package]] 620 | name = "syn" 621 | version = "1.0.86" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 624 | dependencies = [ 625 | "proc-macro2", 626 | "quote", 627 | "unicode-xid", 628 | ] 629 | 630 | [[package]] 631 | name = "tap" 632 | version = "1.0.1" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 635 | 636 | [[package]] 637 | name = "term" 638 | version = "0.5.2" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" 641 | dependencies = [ 642 | "byteorder", 643 | "dirs", 644 | "winapi", 645 | ] 646 | 647 | [[package]] 648 | name = "termcolor" 649 | version = "1.1.2" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 652 | dependencies = [ 653 | "winapi-util", 654 | ] 655 | 656 | [[package]] 657 | name = "textwrap" 658 | version = "0.14.2" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" 661 | 662 | [[package]] 663 | name = "time" 664 | version = "0.1.44" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 667 | dependencies = [ 668 | "libc", 669 | "wasi 0.10.0+wasi-snapshot-preview1", 670 | "winapi", 671 | ] 672 | 673 | [[package]] 674 | name = "unicode-width" 675 | version = "0.1.9" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 678 | 679 | [[package]] 680 | name = "unicode-xid" 681 | version = "0.2.2" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 684 | 685 | [[package]] 686 | name = "version-compare" 687 | version = "0.1.0" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" 690 | 691 | [[package]] 692 | name = "version_check" 693 | version = "0.9.4" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 696 | 697 | [[package]] 698 | name = "wasi" 699 | version = "0.9.0+wasi-snapshot-preview1" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 702 | 703 | [[package]] 704 | name = "wasi" 705 | version = "0.10.0+wasi-snapshot-preview1" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 708 | 709 | [[package]] 710 | name = "winapi" 711 | version = "0.3.9" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 714 | dependencies = [ 715 | "winapi-i686-pc-windows-gnu", 716 | "winapi-x86_64-pc-windows-gnu", 717 | ] 718 | 719 | [[package]] 720 | name = "winapi-i686-pc-windows-gnu" 721 | version = "0.4.0" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 724 | 725 | [[package]] 726 | name = "winapi-util" 727 | version = "0.1.5" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 730 | dependencies = [ 731 | "winapi", 732 | ] 733 | 734 | [[package]] 735 | name = "winapi-x86_64-pc-windows-gnu" 736 | version = "0.4.0" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 739 | 740 | [[package]] 741 | name = "wyz" 742 | version = "0.5.0" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" 745 | dependencies = [ 746 | "tap", 747 | ] 748 | -------------------------------------------------------------------------------- /src/apu.rs: -------------------------------------------------------------------------------- 1 | use bitvec::prelude::*; 2 | use meru_interface::{AudioBuffer, AudioSample}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::{ 6 | consts::{LINES_PER_FRAME, PPU_CLOCK_PER_CPU_CLOCK, PPU_CLOCK_PER_LINE}, 7 | context::{self, IrqSource}, 8 | util::{trait_alias, Input}, 9 | }; 10 | 11 | trait_alias!(pub trait Context = context::Mapper + context::Interrupt); 12 | 13 | const AUDIO_FREQUENCY: u64 = 48000; 14 | const SAMPLE_PER_FRAME: u64 = AUDIO_FREQUENCY / 60; 15 | const STEP_FRAME: [usize; 5] = [7457, 14913, 22371, 29829, 37281]; 16 | 17 | #[rustfmt::skip] 18 | const LENGTH_TABLE: [u8; 32] = [ 19 | 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 20 | 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30, 21 | ]; 22 | 23 | #[derive(Serialize, Deserialize)] 24 | pub struct Apu { 25 | controller_latch: bool, 26 | expansion_latch: u8, 27 | pad_buf: [u8; 2], 28 | reg: Register, 29 | frame_counter_reset_delay: usize, 30 | frame_counter: usize, 31 | input: Input, 32 | counter: u64, 33 | sampler_counter: u64, 34 | #[serde(skip)] 35 | audio_buffer: AudioBuffer, 36 | } 37 | 38 | #[derive(Default, Serialize, Deserialize)] 39 | struct Register { 40 | pulse: [Pulse; 2], 41 | triangle: Triangle, 42 | noise: Noise, 43 | dmc: Dmc, 44 | 45 | frame_counter_mode: bool, 46 | frame_counter_irq: bool, 47 | } 48 | 49 | impl Register { 50 | fn new() -> Self { 51 | Register { 52 | pulse: std::array::from_fn(Pulse::new), 53 | noise: Noise::new(), 54 | dmc: Dmc::new(), 55 | ..Default::default() 56 | } 57 | } 58 | } 59 | 60 | #[derive(Default, Debug, Serialize, Deserialize)] 61 | struct Pulse { 62 | ch: usize, 63 | enable: bool, 64 | duty: u8, 65 | length_counter_halt: bool, 66 | constant_volume: bool, 67 | volume: u8, 68 | sweep_enabled: bool, 69 | sweep_period: u8, 70 | sweep_negate: bool, 71 | sweep_shift: u8, 72 | sweep_reload: bool, 73 | timer: u16, 74 | length_counter_load: u8, 75 | 76 | sequencer_counter: u16, 77 | length_counter: u8, 78 | envelope_start: bool, 79 | envelope_counter: u8, 80 | decay_level: u8, 81 | sweep_counter: u8, 82 | phase: u8, 83 | } 84 | 85 | impl Pulse { 86 | fn new(ch: usize) -> Self { 87 | Self { 88 | ch, 89 | ..Default::default() 90 | } 91 | } 92 | 93 | fn target_period(&self) -> u16 { 94 | let delta = self.timer >> self.sweep_shift; 95 | if !self.sweep_negate { 96 | self.timer + delta 97 | } else if self.ch == 0 { 98 | self.timer - delta - 1 99 | } else { 100 | self.timer - delta 101 | } 102 | } 103 | 104 | fn sample(&self, correct_bias: bool) -> f32 { 105 | const PULSE_WAVEFORM: [[u8; 8]; 4] = [ 106 | [0, 1, 0, 0, 0, 0, 0, 0], 107 | [0, 1, 1, 0, 0, 0, 0, 0], 108 | [0, 1, 1, 1, 1, 0, 0, 0], 109 | [1, 0, 0, 1, 1, 1, 1, 1], 110 | ]; 111 | 112 | let volume = if self.constant_volume { 113 | self.volume 114 | } else { 115 | self.decay_level 116 | }; 117 | let target_period = self.target_period(); 118 | let sweep_muting = self.sweep_enabled && !(8..=0x7ff).contains(&target_period); 119 | if !(self.length_counter == 0 || sweep_muting || self.timer < 8) { 120 | let bias = if correct_bias { -0.5 } else { 0.0 }; 121 | volume as f32 * (PULSE_WAVEFORM[self.duty as usize][self.phase as usize] as f32 + bias) 122 | } else { 123 | 0.0 124 | } 125 | } 126 | } 127 | 128 | #[derive(Default, Serialize, Deserialize)] 129 | struct Triangle { 130 | enable: bool, 131 | length_counter_halt: bool, 132 | linear_counter_load: u8, 133 | timer: u16, 134 | length_counter_load: u8, 135 | 136 | length_counter: u8, 137 | phase: u8, 138 | linear_counter: u8, 139 | linear_counter_reload: bool, 140 | sequencer_counter: u16, 141 | } 142 | 143 | impl Triangle { 144 | fn sample(&self, correct_bias: bool) -> f32 { 145 | #[rustfmt::skip] 146 | const TRIANGLE_WAVEFORM: [u8; 32] = [ 147 | 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 148 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 149 | ]; 150 | 151 | // mute when timer value is too small because it produces ultrasonic 152 | if self.linear_counter == 0 || self.length_counter == 0 || self.timer <= 2 { 153 | 0.0 154 | } else { 155 | let bias = if correct_bias { -8.0 } else { 0.0 }; 156 | TRIANGLE_WAVEFORM[self.phase as usize] as f32 + bias 157 | } 158 | } 159 | } 160 | 161 | #[derive(Default, Debug, Serialize, Deserialize)] 162 | struct Noise { 163 | enable: bool, 164 | length_counter_halt: bool, 165 | constant_volume: bool, 166 | volume: u8, 167 | noise_mode: bool, 168 | noise_period: u8, 169 | length_counter_load: u8, 170 | 171 | length_counter: u8, 172 | envelope_start: bool, 173 | envelope_counter: u8, 174 | decay_level: u8, 175 | shift_register: u16, 176 | sequencer_counter: u16, 177 | } 178 | 179 | impl Noise { 180 | fn new() -> Noise { 181 | Noise { 182 | shift_register: 1, 183 | ..Default::default() 184 | } 185 | } 186 | 187 | fn sample(&self, correct_bias: bool) -> f32 { 188 | let volume = if self.constant_volume { 189 | self.volume 190 | } else { 191 | self.decay_level 192 | }; 193 | if self.length_counter != 0 { 194 | let b = self.shift_register & 1; 195 | let bias = if correct_bias { -0.5 } else { 0.0 }; 196 | volume as f32 * (b as f32 + bias) 197 | } else { 198 | 0.0 199 | } 200 | } 201 | } 202 | 203 | #[derive(Default, Debug, Serialize, Deserialize)] 204 | struct Dmc { 205 | enable: bool, 206 | irq_enabled: bool, 207 | loop_enabled: bool, 208 | rate_index: u8, 209 | sample_addr: u16, 210 | sample_length: u16, 211 | 212 | shifter_counter: u16, 213 | cur_addr: u16, 214 | length_counter: u16, 215 | shiftreg: u8, 216 | shiftreg_remain: u8, 217 | buffer: Option, 218 | silence: bool, 219 | output_level: u8, 220 | } 221 | 222 | impl Dmc { 223 | fn new() -> Self { 224 | Dmc { 225 | shiftreg_remain: 8, 226 | ..Default::default() 227 | } 228 | } 229 | 230 | fn sample(&self, correct_bias: bool) -> f32 { 231 | let bias = if correct_bias { -128.0 } else { 0.0 }; 232 | self.output_level as f32 + bias 233 | } 234 | } 235 | 236 | impl Default for Apu { 237 | fn default() -> Self { 238 | Self { 239 | controller_latch: false, 240 | expansion_latch: 0, 241 | pad_buf: [0; 2], 242 | reg: Register::new(), 243 | frame_counter_reset_delay: 0, 244 | frame_counter: 0, 245 | counter: 0, 246 | sampler_counter: 0, 247 | input: Input::default(), 248 | audio_buffer: AudioBuffer::new(48000, 2), 249 | } 250 | } 251 | } 252 | 253 | impl Apu { 254 | pub fn audio_buffer(&self) -> &AudioBuffer { 255 | &self.audio_buffer 256 | } 257 | 258 | pub fn audio_buffer_mut(&mut self) -> &mut AudioBuffer { 259 | &mut self.audio_buffer 260 | } 261 | 262 | pub fn tick(&mut self, ctx: &mut impl Context) { 263 | self.frame_counter += 1; 264 | 265 | let mut quarter_frame = false; 266 | let mut half_frame = false; 267 | 268 | if self.frame_counter == STEP_FRAME[0] { 269 | quarter_frame = true; 270 | } 271 | if self.frame_counter == STEP_FRAME[1] { 272 | quarter_frame = true; 273 | half_frame = true; 274 | } 275 | if self.frame_counter == STEP_FRAME[2] { 276 | quarter_frame = true; 277 | } 278 | if !self.reg.frame_counter_mode && self.frame_counter == STEP_FRAME[3] { 279 | quarter_frame = true; 280 | half_frame = true; 281 | 282 | if !self.reg.frame_counter_irq { 283 | // log::info!("APU frame counter IRQ set"); 284 | ctx.set_irq_source(IrqSource::ApuFrame, true); 285 | } 286 | 287 | self.frame_counter = 0; 288 | } 289 | if self.frame_counter == STEP_FRAME[4] { 290 | quarter_frame = true; 291 | half_frame = true; 292 | 293 | self.frame_counter = 0; 294 | } 295 | 296 | if self.frame_counter_reset_delay > 0 { 297 | self.frame_counter_reset_delay -= 1; 298 | if self.frame_counter_reset_delay == 0 { 299 | self.frame_counter = 0; 300 | if self.reg.frame_counter_mode { 301 | quarter_frame = true; 302 | half_frame = true; 303 | } 304 | } 305 | } 306 | 307 | // FIXME: delay clock frame 308 | if quarter_frame { 309 | self.clock_quarter_frame(); 310 | } 311 | if half_frame { 312 | self.clock_half_frame(); 313 | } 314 | 315 | self.counter += 1; 316 | 317 | if self.counter % 2 == 1 { 318 | for ch in 0..2 { 319 | let r = &mut self.reg.pulse[ch]; 320 | if r.sequencer_counter == 0 { 321 | r.sequencer_counter = r.timer; 322 | r.phase = (r.phase + 1) % 8; 323 | } else { 324 | r.sequencer_counter -= 1; 325 | } 326 | } 327 | } 328 | 329 | if self.reg.triangle.linear_counter != 0 && self.reg.triangle.length_counter != 0 { 330 | let r = &mut self.reg.triangle; 331 | if r.sequencer_counter == 0 { 332 | r.sequencer_counter = r.timer; 333 | r.phase = (r.phase + 1) % 32; 334 | } else { 335 | r.sequencer_counter -= 1; 336 | } 337 | } 338 | 339 | if self.counter % 2 == 1 { 340 | const NOISE_PERIOD: [u16; 16] = [ 341 | 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068, 342 | ]; 343 | 344 | let r = &mut self.reg.noise; 345 | if r.sequencer_counter == 0 { 346 | r.sequencer_counter = NOISE_PERIOD[r.noise_period as usize]; 347 | let fb = if !r.noise_mode { 348 | (r.shift_register & 1) ^ ((r.shift_register >> 1) & 1) 349 | } else { 350 | (r.shift_register & 1) ^ ((r.shift_register >> 6) & 1) 351 | }; 352 | r.shift_register = (r.shift_register >> 1) | (fb << 14); 353 | } else { 354 | r.sequencer_counter -= 1; 355 | } 356 | } 357 | 358 | { 359 | const RATE_TABLE: [u16; 16] = [ 360 | 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54, 361 | ]; 362 | 363 | let r = &mut self.reg.dmc; 364 | if r.shifter_counter == 0 { 365 | r.shifter_counter = RATE_TABLE[r.rate_index as usize]; 366 | 367 | if !r.silence { 368 | if r.shiftreg & 1 != 0 { 369 | if r.output_level <= 0x7d { 370 | r.output_level += 2; 371 | } 372 | } else if r.output_level >= 2 { 373 | r.output_level -= 2; 374 | } 375 | r.shiftreg >>= 1; 376 | } 377 | 378 | r.shiftreg_remain -= 1; 379 | if r.shiftreg_remain == 0 { 380 | r.shiftreg_remain = 8; 381 | 382 | if let Some(buf) = r.buffer { 383 | r.shiftreg = buf; 384 | r.buffer = None; 385 | r.silence = false; 386 | } else { 387 | r.silence = true; 388 | } 389 | } 390 | } else { 391 | r.shifter_counter -= 1; 392 | } 393 | 394 | if r.buffer.is_none() && r.length_counter != 0 { 395 | r.buffer = Some(ctx.read_prg_mapper(r.cur_addr)); 396 | 397 | r.cur_addr = r.cur_addr.wrapping_add(1); 398 | if r.cur_addr == 0 { 399 | r.cur_addr = 0x8000; 400 | } 401 | r.length_counter -= 1; 402 | if r.length_counter == 0 { 403 | if r.loop_enabled { 404 | r.cur_addr = r.sample_addr; 405 | r.length_counter = r.sample_length; 406 | } else if r.irq_enabled { 407 | ctx.set_irq_source(IrqSource::ApuDmc, true); 408 | } 409 | } 410 | } 411 | } 412 | 413 | // PPU_CLOCK_PER_LINE * LINES_PER_FRAME <-> 800 * 3 414 | 415 | self.sampler_counter += SAMPLE_PER_FRAME * PPU_CLOCK_PER_CPU_CLOCK; 416 | if self.sampler_counter >= PPU_CLOCK_PER_LINE * LINES_PER_FRAME as u64 { 417 | self.sampler_counter -= PPU_CLOCK_PER_LINE * LINES_PER_FRAME as u64; 418 | let sample = self.sample(); 419 | self.audio_buffer 420 | .samples 421 | .push(AudioSample::new(sample, sample)); 422 | } 423 | } 424 | 425 | pub fn clock_quarter_frame(&mut self) { 426 | for i in 0..2 { 427 | let r = &mut self.reg.pulse[i]; 428 | 429 | if r.envelope_start { 430 | r.envelope_start = false; 431 | r.decay_level = 15; 432 | r.envelope_counter = r.volume; 433 | } else if r.envelope_counter == 0 { 434 | r.envelope_counter = r.volume; 435 | if r.decay_level != 0 { 436 | r.decay_level -= 1; 437 | } else if r.length_counter_halt { 438 | r.decay_level = 15; 439 | } 440 | } else { 441 | r.envelope_counter -= 1; 442 | } 443 | } 444 | 445 | let r = &mut self.reg.triangle; 446 | if r.linear_counter_reload { 447 | r.linear_counter = r.linear_counter_load; 448 | } else if r.linear_counter > 0 { 449 | r.linear_counter -= 1; 450 | } 451 | if !r.length_counter_halt { 452 | r.linear_counter_reload = false; 453 | } 454 | 455 | let r = &mut self.reg.noise; 456 | if r.envelope_start { 457 | r.envelope_start = false; 458 | r.decay_level = 15; 459 | r.envelope_counter = r.volume; 460 | } else if r.volume > 0 { 461 | if r.envelope_counter == 0 { 462 | r.envelope_counter = r.volume; 463 | if r.decay_level != 0 { 464 | r.decay_level -= 1; 465 | } else if r.length_counter_halt { 466 | r.decay_level = 15; 467 | } 468 | } else { 469 | r.envelope_counter -= 1; 470 | } 471 | } 472 | } 473 | 474 | pub fn clock_half_frame(&mut self) { 475 | for ch in 0..2 { 476 | let r = &mut self.reg.pulse[ch]; 477 | let target_period = r.target_period(); 478 | if r.length_counter > 0 && !r.length_counter_halt { 479 | r.length_counter -= 1; 480 | } 481 | 482 | let enabled = r.sweep_enabled && r.sweep_shift != 0; 483 | let muting = !(8..=0x7ff).contains(&target_period); 484 | 485 | if r.sweep_counter == 0 && enabled && !muting { 486 | r.timer = target_period; 487 | } 488 | 489 | if r.sweep_counter == 0 || r.sweep_reload { 490 | r.sweep_counter = r.sweep_period; 491 | r.sweep_reload = false; 492 | } else { 493 | r.sweep_counter -= 1; 494 | } 495 | } 496 | if self.reg.triangle.length_counter > 0 && !self.reg.triangle.length_counter_halt { 497 | self.reg.triangle.length_counter -= 1; 498 | } 499 | if self.reg.noise.length_counter > 0 && !self.reg.noise.length_counter_halt { 500 | self.reg.noise.length_counter -= 1; 501 | } 502 | } 503 | 504 | pub fn sample(&self) -> i16 { 505 | // let pulse = [ 506 | // self.reg.pulse[0].sample(false), 507 | // self.reg.pulse[1].sample(false), 508 | // ]; 509 | // let triangle = self.reg.triangle.sample(false); 510 | // let noise = self.reg.noise.sample(false); 511 | // let dmc = self.reg.dmc.sample(false); 512 | 513 | // let pulse_out = if pulse[0] == 0.0 && pulse[1] == 0.0 { 514 | // 0.0 515 | // } else { 516 | // 95.88 / (8128.0 / (pulse[0] as f64 + pulse[1] as f64) + 100.0) 517 | // }; 518 | 519 | // let tnd_out = if triangle == 0.0 && noise == 0.0 && dmc == 0.0 { 520 | // 0.0 521 | // } else { 522 | // let t = triangle as f64 / 8227.0 + noise as f64 / 12241.0 + dmc as f64 / 22638.0; 523 | // 159.79 / (1.0 / t + 100.0) 524 | // }; 525 | 526 | // // TODO: highpass filter & lowpass filter 527 | // ((pulse_out + tnd_out) * 30000.0).round() as i16 528 | 529 | let pulse = [ 530 | self.reg.pulse[0].sample(true), 531 | self.reg.pulse[1].sample(true), 532 | ]; 533 | let triangle = self.reg.triangle.sample(true); 534 | let noise = self.reg.noise.sample(true); 535 | let dmc = self.reg.dmc.sample(true); 536 | 537 | // Linear approximation 538 | 539 | let pulse_out = 0.00752 * (pulse[0] + pulse[1]); 540 | let tnd_out = 0.00851 * triangle + 0.00494 * noise + 0.00335 * dmc; 541 | let output = pulse_out + tnd_out; 542 | 543 | (output * 32000.0) as i16 544 | } 545 | 546 | pub fn set_input(&mut self, input: &Input) { 547 | self.input = input.clone(); 548 | } 549 | 550 | pub fn read(&mut self, ctx: &mut impl Context, addr: u16) -> u8 { 551 | let ret = match addr { 552 | 0x4015 => { 553 | // Status 554 | let mut ret = 0; 555 | let r = ret.view_bits_mut::(); 556 | r.set(7, ctx.irq_source(IrqSource::ApuDmc)); 557 | r.set(6, ctx.irq_source(IrqSource::ApuFrame)); 558 | r.set(4, self.reg.dmc.length_counter > 0); 559 | r.set(3, self.reg.noise.length_counter > 0); 560 | r.set(2, self.reg.triangle.length_counter > 0); 561 | r.set(1, self.reg.pulse[1].length_counter > 0); 562 | r.set(0, self.reg.pulse[0].length_counter > 0); 563 | 564 | ctx.set_irq_source(IrqSource::ApuFrame, false); 565 | ret 566 | } 567 | 568 | 0x4016 | 0x4017 => { 569 | let ix = (addr - 0x4016) as usize; 570 | 571 | if self.controller_latch { 572 | 0x00 573 | } else { 574 | let ret = self.pad_buf[ix] & 1 != 0; 575 | self.pad_buf[ix] = self.pad_buf[ix] >> 1 | 0x80; 576 | ret as u8 577 | } 578 | } 579 | 580 | _ => { 581 | log::info!("Read APU ${addr:04X}"); 582 | 0 583 | } 584 | }; 585 | log::trace!("Read APU ${addr:04X} = {ret:02X}"); 586 | ret 587 | } 588 | 589 | pub fn write(&mut self, ctx: &mut impl Context, addr: u16, data: u8) { 590 | log::trace!("Write APU ${addr:04X} = ${data:02X}"); 591 | 592 | match addr { 593 | // Pulse 594 | 0x4000 | 0x4004 => { 595 | let ch = (addr - 0x4000) / 4; 596 | let r = &mut self.reg.pulse[ch as usize]; 597 | let v = data.view_bits::(); 598 | r.duty = v[6..8].load(); 599 | r.length_counter_halt = v[5]; 600 | r.constant_volume = v[4]; 601 | r.volume = v[0..4].load(); 602 | 603 | log::trace!( 604 | "Pulse #{ch}: duty={}, inflen={}, constvol={}, vol={}", 605 | r.duty, 606 | r.length_counter_halt, 607 | r.constant_volume, 608 | r.volume 609 | ); 610 | } 611 | 0x4001 | 0x4005 => { 612 | let ch = (addr - 0x4000) / 4; 613 | let r = &mut self.reg.pulse[ch as usize]; 614 | let v = data.view_bits::(); 615 | r.sweep_enabled = v[7]; 616 | r.sweep_period = v[4..6].load(); 617 | r.sweep_negate = v[3]; 618 | r.sweep_shift = v[0..3].load(); 619 | r.sweep_reload = true; 620 | 621 | log::trace!( 622 | "Pulse #{ch}: swenable={}, swperiod={}, swneg={}, swshft={}, swreload={}", 623 | r.sweep_enabled, 624 | r.sweep_period, 625 | r.sweep_negate, 626 | r.sweep_shift, 627 | r.sweep_reload 628 | ); 629 | } 630 | 0x4002 | 0x4006 => { 631 | let ch = (addr - 0x4000) / 4; 632 | let r = &mut self.reg.pulse[ch as usize]; 633 | r.timer.view_bits_mut::()[0..8].store(data); 634 | 635 | log::trace!("Pulse #{ch}: timer_low={}, timer={}", data, r.timer); 636 | } 637 | 0x4003 | 0x4007 => { 638 | let ch = (addr - 0x4000) / 4; 639 | let r = &mut self.reg.pulse[ch as usize]; 640 | let v = data.view_bits::(); 641 | r.timer.view_bits_mut::()[8..].store(v[0..3].load::()); 642 | r.length_counter_load = v[3..8].load(); 643 | 644 | if r.enable { 645 | r.length_counter = LENGTH_TABLE[r.length_counter_load as usize]; 646 | log::trace!("PULSE {ch}: length: {}", r.length_counter); 647 | } 648 | r.envelope_start = true; 649 | r.phase = 0; 650 | 651 | log::trace!( 652 | "Pulse #{ch}: timer_high={}, timer={}, length={}, enabled={}", 653 | v[0..3].load::(), 654 | r.timer, 655 | r.length_counter_load, 656 | r.enable, 657 | ); 658 | } 659 | 660 | // Triangle 661 | 0x4008 => { 662 | let r = &mut self.reg.triangle; 663 | let v = data.view_bits::(); 664 | r.length_counter_halt = v[7]; 665 | r.linear_counter_load = v[0..7].load(); 666 | } 667 | 0x4009 => { 668 | log::warn!("Write APU ${addr:04X} = ${data:02X}"); 669 | } 670 | 0x400A => { 671 | let r = &mut self.reg.triangle; 672 | r.timer.view_bits_mut::()[0..8].store(data); 673 | } 674 | 0x400B => { 675 | let r = &mut self.reg.triangle; 676 | let v = data.view_bits::(); 677 | r.timer.view_bits_mut::()[8..].store(v[0..3].load::()); 678 | r.length_counter_load = v[3..8].load(); 679 | if r.enable { 680 | r.length_counter = LENGTH_TABLE[r.length_counter_load as usize]; 681 | } 682 | r.linear_counter_reload = true; 683 | } 684 | 685 | // Noise 686 | 0x400C => { 687 | let r = &mut self.reg.noise; 688 | let v = data.view_bits::(); 689 | r.length_counter_halt = v[5]; 690 | r.constant_volume = v[4]; 691 | r.volume = v[0..4].load(); 692 | } 693 | 0x400D => { 694 | log::warn!("Write APU ${addr:04X} = ${data:02X}"); 695 | } 696 | 0x400E => { 697 | let r = &mut self.reg.noise; 698 | let v = data.view_bits::(); 699 | r.noise_mode = v[7]; 700 | r.noise_period = v[0..4].load(); 701 | } 702 | 0x400F => { 703 | let r = &mut self.reg.noise; 704 | let v = data.view_bits::(); 705 | r.length_counter_load = v[3..8].load(); 706 | if r.enable { 707 | r.length_counter = LENGTH_TABLE[r.length_counter_load as usize]; 708 | } 709 | r.envelope_start = true; 710 | } 711 | 712 | // DMC 713 | 0x4010 => { 714 | let r = &mut self.reg.dmc; 715 | let v = data.view_bits::(); 716 | r.irq_enabled = v[7]; 717 | r.loop_enabled = v[6]; 718 | r.rate_index = v[0..4].load(); 719 | if !r.irq_enabled { 720 | ctx.set_irq_source(IrqSource::ApuDmc, false); 721 | } 722 | } 723 | 0x4011 => { 724 | let r = &mut self.reg.dmc; 725 | let v = data.view_bits::(); 726 | r.output_level = v[0..7].load(); 727 | } 728 | 0x4012 => { 729 | let r = &mut self.reg.dmc; 730 | r.sample_addr = 0xC000 + data as u16 * 64; 731 | } 732 | 0x4013 => { 733 | let r = &mut self.reg.dmc; 734 | r.sample_length = data as u16 * 16 + 1; 735 | } 736 | 737 | // Status 738 | 0x4015 => { 739 | let v = data.view_bits::(); 740 | self.reg.pulse[0].enable = v[0]; 741 | self.reg.pulse[1].enable = v[1]; 742 | self.reg.triangle.enable = v[2]; 743 | self.reg.noise.enable = v[3]; 744 | self.reg.dmc.enable = v[4]; 745 | 746 | for i in 0..2 { 747 | if !self.reg.pulse[i].enable { 748 | self.reg.pulse[i].length_counter = 0; 749 | } 750 | } 751 | if !self.reg.triangle.enable { 752 | self.reg.triangle.length_counter = 0; 753 | } 754 | if !self.reg.noise.enable { 755 | self.reg.noise.length_counter = 0; 756 | } 757 | 758 | if !self.reg.dmc.enable { 759 | self.reg.dmc.length_counter = 0; 760 | } else if self.reg.dmc.length_counter == 0 { 761 | self.reg.dmc.cur_addr = self.reg.dmc.sample_addr; 762 | self.reg.dmc.length_counter = self.reg.dmc.sample_length; 763 | } 764 | 765 | ctx.set_irq_source(IrqSource::ApuDmc, false); 766 | } 767 | 768 | 0x4016 => { 769 | let v = data.view_bits::(); 770 | self.controller_latch = v[0]; 771 | self.expansion_latch = v[1..3].load_le(); 772 | 773 | if self.controller_latch { 774 | for (i, pad) in self.input.pad.iter().take(2).enumerate() { 775 | let r = self.pad_buf[i].view_bits_mut::(); 776 | r.set(0, pad.a); 777 | r.set(1, pad.b); 778 | r.set(2, pad.select); 779 | r.set(3, pad.start); 780 | r.set(4, pad.up); 781 | r.set(5, pad.down); 782 | r.set(6, pad.left); 783 | r.set(7, pad.right); 784 | } 785 | } 786 | } 787 | 0x4017 => { 788 | let v = data.view_bits::(); 789 | self.reg.frame_counter_mode = v[7]; 790 | self.reg.frame_counter_irq = v[6]; 791 | 792 | if self.reg.frame_counter_irq { 793 | ctx.set_irq_source(IrqSource::ApuFrame, false); 794 | } 795 | 796 | self.frame_counter_reset_delay = 3; 797 | } 798 | 799 | _ => { 800 | log::warn!("Write APU ${addr:04X} = ${data:02X}"); 801 | } 802 | } 803 | } 804 | } 805 | -------------------------------------------------------------------------------- /src/cpu.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::{context, util::trait_alias}; 4 | 5 | trait_alias!(pub trait Context = context::Bus + context::MemoryController + context::Mapper + context::Interrupt + context::Timing); 6 | 7 | #[derive(Default, Serialize, Deserialize)] 8 | pub struct Cpu { 9 | world: u64, 10 | counter: u64, 11 | reg: Register, 12 | nmi_prev: bool, 13 | i_flag_prev: bool, 14 | } 15 | 16 | #[derive(Default, Serialize, Deserialize)] 17 | struct Register { 18 | a: u8, 19 | x: u8, 20 | y: u8, 21 | s: u8, 22 | pc: u16, 23 | flag: Flag, 24 | } 25 | 26 | #[derive(Default, Serialize, Deserialize)] 27 | struct Flag { 28 | c: bool, 29 | z: bool, 30 | i: bool, 31 | d: bool, 32 | v: bool, 33 | n: bool, 34 | } 35 | 36 | impl Flag { 37 | fn set_u8(&mut self, v: u8) { 38 | self.n = (v & 0x80) != 0; 39 | self.v = (v & 0x40) != 0; 40 | self.d = (v & 0x08) != 0; 41 | self.i = (v & 0x04) != 0; 42 | self.z = (v & 0x02) != 0; 43 | self.c = (v & 0x01) != 0; 44 | } 45 | 46 | fn get_u8(&self, b: u8) -> u8 { 47 | let mut v = 0x20; 48 | v |= if self.n { 0x80 } else { 0 }; 49 | v |= if self.v { 0x40 } else { 0 }; 50 | v |= b << 4; 51 | v |= if self.d { 0x08 } else { 0 }; 52 | v |= if self.i { 0x04 } else { 0 }; 53 | v |= if self.z { 0x02 } else { 0 }; 54 | v |= if self.c { 0x01 } else { 0 }; 55 | v 56 | } 57 | 58 | fn set_nz(&mut self, v: u8) { 59 | self.z = v == 0; 60 | self.n = v & 0x80 != 0; 61 | } 62 | } 63 | 64 | #[derive(Debug)] 65 | pub enum Interrupt { 66 | Rst, 67 | Irq, 68 | Nmi, 69 | } 70 | 71 | impl Interrupt { 72 | fn vector_addr(&self) -> u16 { 73 | match self { 74 | Interrupt::Rst => 0xFFFC, 75 | Interrupt::Irq => 0xFFFE, 76 | Interrupt::Nmi => 0xFFFA, 77 | } 78 | } 79 | } 80 | 81 | impl Cpu { 82 | pub fn reset(&mut self, ctx: &mut impl Context) { 83 | self.exec_interrupt(ctx, Interrupt::Rst, false); 84 | } 85 | 86 | pub fn set_pc(&mut self, pc: u16) { 87 | self.reg.pc = pc; 88 | } 89 | 90 | fn exec_interrupt(&mut self, ctx: &mut impl Context, interrupt: Interrupt, brk: bool) { 91 | log::info!("Interrupt: {:?}", interrupt); 92 | 93 | let vector = interrupt.vector_addr(); 94 | 95 | self.push16(ctx, self.reg.pc); 96 | self.push8(ctx, self.reg.flag.get_u8(if brk { 3 } else { 2 })); 97 | self.reg.pc = self.read(ctx, vector) as u16 | (self.read(ctx, vector + 1) as u16) << 8; 98 | self.reg.flag.i = true; 99 | } 100 | 101 | fn read(&mut self, ctx: &mut impl Context, addr: u16) -> u8 { 102 | let ret = ctx.read(addr); 103 | self.tick_bus(ctx); 104 | log::trace!(target: "prgmem", "[${addr:04X}] -> ${ret:02X}"); 105 | ret 106 | } 107 | 108 | fn write(&mut self, ctx: &mut impl Context, addr: u16, data: u8) { 109 | ctx.write(addr, data); 110 | self.tick_bus(ctx); 111 | log::trace!(target: "prgmem", "[${addr:04X}] <- ${data:02X}"); 112 | } 113 | 114 | fn fetch8(&mut self, ctx: &mut impl Context) -> u8 { 115 | let ret = self.read(ctx, self.reg.pc); 116 | self.reg.pc = self.reg.pc.wrapping_add(1); 117 | ret 118 | } 119 | 120 | fn fetch16(&mut self, ctx: &mut impl Context) -> u16 { 121 | let lo = self.fetch8(ctx); 122 | let hi = self.fetch8(ctx); 123 | lo as u16 | (hi as u16) << 8 124 | } 125 | 126 | fn push8(&mut self, ctx: &mut impl Context, data: u8) { 127 | self.write(ctx, 0x100 + self.reg.s as u16, data); 128 | self.reg.s = self.reg.s.wrapping_sub(1); 129 | } 130 | 131 | fn push16(&mut self, ctx: &mut impl Context, data: u16) { 132 | self.push8(ctx, (data >> 8) as u8); 133 | self.push8(ctx, data as u8); 134 | } 135 | 136 | fn pop8(&mut self, ctx: &mut impl Context) -> u8 { 137 | self.reg.s = self.reg.s.wrapping_add(1); 138 | self.read(ctx, 0x100 + self.reg.s as u16) 139 | } 140 | 141 | fn pop16(&mut self, ctx: &mut impl Context) -> u16 { 142 | let lo = self.pop8(ctx) as u16; 143 | let hi = self.pop8(ctx) as u16; 144 | lo | (hi << 8) 145 | } 146 | } 147 | 148 | #[allow(clippy::upper_case_acronyms)] 149 | enum AddrMode { 150 | IMP, // Implicit 151 | ACC, // Accumulator 152 | IMM, // Immediate: #v 153 | ZPG, // Zero Page: d 154 | ABS, // Absolute: a 155 | REL, // Relative: label 156 | IND, // Indirect: (d) 157 | ZPX, // Zero Page indexed: d,X 158 | ZPY, // Zero Page indexed: d,Y 159 | ABX, // Absolute indexed: a,X 160 | ABY, // Absolute indexed: a,Y 161 | INX, // Indirect indexed: (d,X) 162 | INY, // Indirect indexed: (d),Y 163 | UNK, 164 | } 165 | 166 | impl AddrMode { 167 | fn len(&self) -> usize { 168 | use AddrMode::*; 169 | match self { 170 | IMP | ACC => 1, 171 | IMM | ZPG | REL | ZPX | ZPY | INX | INY => 2, 172 | ABS | IND | ABX | ABY => 3, 173 | UNK => 1, 174 | } 175 | } 176 | } 177 | 178 | macro_rules! instructions { 179 | ($cont:ident) => { 180 | $cont! { 181 | 0x00: BRK IMP, 0x01: ORA INX, 0x02: UNK UNK, 0x03:*SLO INX, 182 | 0x04:*NOP ZPG, 0x05: ORA ZPG, 0x06: ASL ZPG, 0x07:*SLO ZPG, 183 | 0x08: PHP IMP, 0x09: ORA IMM, 0x0A: ASL ACC, 0x0B:*AAC IMM, 184 | 0x0C:*NOP ABS, 0x0D: ORA ABS, 0x0E: ASL ABS, 0x0F:*SLO ABS, 185 | 0x10: BPL REL, 0x11: ORA INY, 0x12: UNK UNK, 0x13:*SLO INY, 186 | 0x14:*NOP ZPX, 0x15: ORA ZPX, 0x16: ASL ZPX, 0x17:*SLO ZPX, 187 | 0x18: CLC IMP, 0x19: ORA ABY, 0x1A:*NOP IMP, 0x1B:*SLO ABY, 188 | 0x1C:*NOP ABX, 0x1D: ORA ABX, 0x1E: ASL ABX, 0x1F:*SLO ABX, 189 | 0x20: JSR ABS, 0x21: AND INX, 0x22: UNK UNK, 0x23:*RLA INX, 190 | 0x24: BIT ZPG, 0x25: AND ZPG, 0x26: ROL ZPG, 0x27:*RLA ZPG, 191 | 0x28: PLP IMP, 0x29: AND IMM, 0x2A: ROL ACC, 0x2B:*AAC IMM, 192 | 0x2C: BIT ABS, 0x2D: AND ABS, 0x2E: ROL ABS, 0x2F:*RLA ABS, 193 | 0x30: BMI REL, 0x31: AND INY, 0x32: UNK UNK, 0x33:*RLA INY, 194 | 0x34:*NOP ZPX, 0x35: AND ZPX, 0x36: ROL ZPX, 0x37:*RLA ZPX, 195 | 0x38: SEC IMP, 0x39: AND ABY, 0x3A:*NOP IMP, 0x3B:*RLA ABY, 196 | 0x3C:*NOP ABX, 0x3D: AND ABX, 0x3E: ROL ABX, 0x3F:*RLA ABX, 197 | 0x40: RTI IMP, 0x41: EOR INX, 0x42: UNK UNK, 0x43:*SRE INX, 198 | 0x44:*NOP ZPG, 0x45: EOR ZPG, 0x46: LSR ZPG, 0x47:*SRE ZPG, 199 | 0x48: PHA IMP, 0x49: EOR IMM, 0x4A: LSR ACC, 0x4B:*ASR IMM, 200 | 0x4C: JMP ABS, 0x4D: EOR ABS, 0x4E: LSR ABS, 0x4F:*SRE ABS, 201 | 0x50: BVC REL, 0x51: EOR INY, 0x52: UNK UNK, 0x53:*SRE INY, 202 | 0x54:*NOP ZPX, 0x55: EOR ZPX, 0x56: LSR ZPX, 0x57:*SRE ZPX, 203 | 0x58: CLI IMP, 0x59: EOR ABY, 0x5A:*NOP IMP, 0x5B:*SRE ABY, 204 | 0x5C:*NOP ABX, 0x5D: EOR ABX, 0x5E: LSR ABX, 0x5F:*SRE ABX, 205 | 0x60: RTS IMP, 0x61: ADC INX, 0x62: UNK UNK, 0x63:*RRA INX, 206 | 0x64:*NOP ZPG, 0x65: ADC ZPG, 0x66: ROR ZPG, 0x67:*RRA ZPG, 207 | 0x68: PLA IMP, 0x69: ADC IMM, 0x6A: ROR ACC, 0x6B:*ARR IMM, 208 | 0x6C: JMP IND, 0x6D: ADC ABS, 0x6E: ROR ABS, 0x6F:*RRA ABS, 209 | 0x70: BVS REL, 0x71: ADC INY, 0x72: UNK UNK, 0x73:*RRA INY, 210 | 0x74:*NOP ZPX, 0x75: ADC ZPX, 0x76: ROR ZPX, 0x77:*RRA ZPX, 211 | 0x78: SEI IMP, 0x79: ADC ABY, 0x7A:*NOP IMP, 0x7B:*RRA ABY, 212 | 0x7C:*NOP ABX, 0x7D: ADC ABX, 0x7E: ROR ABX, 0x7F:*RRA ABX, 213 | 0x80:*NOP IMM, 0x81: STA INX, 0x82:*NOP IMM, 0x83:*SAX INX, 214 | 0x84: STY ZPG, 0x85: STA ZPG, 0x86: STX ZPG, 0x87:*SAX ZPG, 215 | 0x88: DEY IMP, 0x89:*NOP IMM, 0x8A: TXA IMP, 0x8B: UNK UNK, 216 | 0x8C: STY ABS, 0x8D: STA ABS, 0x8E: STX ABS, 0x8F:*SAX ABS, 217 | 0x90: BCC REL, 0x91: STA INY, 0x92: UNK UNK, 0x93: UNK UNK, 218 | 0x94: STY ZPX, 0x95: STA ZPX, 0x96: STX ZPY, 0x97:*SAX ZPY, 219 | 0x98: TYA IMP, 0x99: STA ABY, 0x9A: TXS IMP, 0x9B: UNK UNK, 220 | 0x9C:*SYA ABX, 0x9D: STA ABX, 0x9E:*SXA ABY, 0x9F: UNK UNK, 221 | 0xA0: LDY IMM, 0xA1: LDA INX, 0xA2: LDX IMM, 0xA3:*LAX INX, 222 | 0xA4: LDY ZPG, 0xA5: LDA ZPG, 0xA6: LDX ZPG, 0xA7:*LAX ZPG, 223 | 0xA8: TAY IMP, 0xA9: LDA IMM, 0xAA: TAX IMP, 0xAB:*ATX IMM, 224 | 0xAC: LDY ABS, 0xAD: LDA ABS, 0xAE: LDX ABS, 0xAF:*LAX ABS, 225 | 0xB0: BCS REL, 0xB1: LDA INY, 0xB2: UNK UNK, 0xB3:*LAX INY, 226 | 0xB4: LDY ZPX, 0xB5: LDA ZPX, 0xB6: LDX ZPY, 0xB7:*LAX ZPY, 227 | 0xB8: CLV IMP, 0xB9: LDA ABY, 0xBA: TSX IMP, 0xBB: UNK UNK, 228 | 0xBC: LDY ABX, 0xBD: LDA ABX, 0xBE: LDX ABY, 0xBF:*LAX ABY, 229 | 0xC0: CPY IMM, 0xC1: CMP INX, 0xC2:*NOP IMM, 0xC3:*DCP INX, 230 | 0xC4: CPY ZPG, 0xC5: CMP ZPG, 0xC6: DEC ZPG, 0xC7:*DCP ZPG, 231 | 0xC8: INY IMP, 0xC9: CMP IMM, 0xCA: DEX IMP, 0xCB:*AXS IMM, 232 | 0xCC: CPY ABS, 0xCD: CMP ABS, 0xCE: DEC ABS, 0xCF:*DCP ABS, 233 | 0xD0: BNE REL, 0xD1: CMP INY, 0xD2: UNK UNK, 0xD3:*DCP INY, 234 | 0xD4:*NOP ZPX, 0xD5: CMP ZPX, 0xD6: DEC ZPX, 0xD7:*DCP ZPX, 235 | 0xD8: CLD IMP, 0xD9: CMP ABY, 0xDA:*NOP IMP, 0xDB:*DCP ABY, 236 | 0xDC:*NOP ABX, 0xDD: CMP ABX, 0xDE: DEC ABX, 0xDF:*DCP ABX, 237 | 0xE0: CPX IMM, 0xE1: SBC INX, 0xE2:*NOP IMM, 0xE3:*ISB INX, 238 | 0xE4: CPX ZPG, 0xE5: SBC ZPG, 0xE6: INC ZPG, 0xE7:*ISB ZPG, 239 | 0xE8: INX IMP, 0xE9: SBC IMM, 0xEA: NOP IMP, 0xEB:*SBC IMM, 240 | 0xEC: CPX ABS, 0xED: SBC ABS, 0xEE: INC ABS, 0xEF:*ISB ABS, 241 | 0xF0: BEQ REL, 0xF1: SBC INY, 0xF2: UNK UNK, 0xF3:*ISB INY, 242 | 0xF4:*NOP ZPX, 0xF5: SBC ZPX, 0xF6: INC ZPX, 0xF7:*ISB ZPX, 243 | 0xF8: SED IMP, 0xF9: SBC ABY, 0xFA:*NOP IMP, 0xFB:*ISB ABY, 244 | 0xFC:*NOP ABX, 0xFD: SBC ABX, 0xFE: INC ABX, 0xFF:*ISB ABX, 245 | } 246 | }; 247 | } 248 | 249 | impl Cpu { 250 | pub fn tick(&mut self, ctx: &mut impl Context) { 251 | let stall = ctx.cpu_stall(); 252 | for _ in 0..stall { 253 | self.tick_bus(ctx); 254 | } 255 | 256 | self.world += 1; 257 | 258 | while self.counter < self.world { 259 | let nmi_cur = ctx.nmi(); 260 | let nmi_prev = self.nmi_prev; 261 | self.nmi_prev = nmi_cur; 262 | 263 | let irq_prev = ctx.irq(); 264 | self.i_flag_prev = self.reg.flag.i; 265 | 266 | self.exec_one(ctx); 267 | 268 | if nmi_prev && !nmi_cur { 269 | self.exec_interrupt(ctx, Interrupt::Nmi, false); 270 | continue; 271 | } 272 | 273 | if !self.i_flag_prev && irq_prev { 274 | self.exec_interrupt(ctx, Interrupt::Irq, false); 275 | continue; 276 | } 277 | } 278 | } 279 | 280 | fn tick_bus(&mut self, ctx: &mut impl Context) { 281 | self.counter += 1; 282 | ctx.tick_bus(); 283 | } 284 | 285 | fn exec_one(&mut self, ctx: &mut impl Context) { 286 | if log::log_enabled!(log::Level::Trace) { 287 | self.trace(ctx); 288 | } 289 | 290 | let opaddr = self.reg.pc; 291 | let opc = self.fetch8(ctx); 292 | 293 | macro_rules! gen_code { 294 | ($($opc:literal: $a:tt $b:ident $($c:ident)?, )*) => {{ 295 | match opc { 296 | $( $opc => exec!($a $b $($c)*), )* 297 | } 298 | }}; 299 | } 300 | 301 | macro_rules! is_read { 302 | (STA) => { 303 | false 304 | }; 305 | (LSR) => { 306 | false 307 | }; 308 | (ASL) => { 309 | false 310 | }; 311 | (ROR) => { 312 | false 313 | }; 314 | (ROL) => { 315 | false 316 | }; 317 | (INC) => { 318 | false 319 | }; 320 | (DEC) => { 321 | false 322 | }; 323 | ($mne:ident) => { 324 | true 325 | }; 326 | } 327 | 328 | macro_rules! exec { 329 | (*$mne:ident $mode:ident) => { 330 | exec!($mne $mode) 331 | }; 332 | ($mne:ident IMP) => {{ 333 | let _ = self.read(ctx, self.reg.pc); 334 | exec_op!($mne) 335 | }}; 336 | ($mne:ident ACC) => {{ 337 | let _ = self.read(ctx, self.reg.pc); 338 | exec_op!($mne, ACC) 339 | }}; 340 | 341 | 342 | ($mne:ident $mode:ident) => {{ 343 | #[allow(unused_variables)] 344 | let read = is_read!($mne); 345 | #[allow(unused_variables)] 346 | let addr = effaddr!($mode, read); 347 | exec_op!($mne, addr) 348 | }}; 349 | } 350 | 351 | macro_rules! effaddr { 352 | (IMM, $read:ident) => {{ 353 | let ret = self.reg.pc; 354 | self.reg.pc = self.reg.pc.wrapping_add(1); 355 | ret 356 | }}; 357 | (ABS, $read:ident) => {{ 358 | self.fetch16(ctx) 359 | }}; 360 | (ABX, $read:ident) => { 361 | effaddr!(abs_ix, x, $read) 362 | }; 363 | (ABY, $read:ident) => { 364 | effaddr!(abs_ix, y, $read) 365 | }; 366 | (abs_ix, $reg:ident, $read:ident) => {{ 367 | let addr = self.fetch16(ctx); 368 | let tmp = (addr & 0xff) + self.reg.$reg as u16; 369 | if !$read || tmp >= 0x100 { 370 | let _ = self.read(ctx, addr & 0xff00 | tmp & 0xff); 371 | } 372 | addr.wrapping_add(self.reg.$reg as u16) 373 | }}; 374 | (IND, $read:ident) => {{ 375 | let lo = self.fetch16(ctx); 376 | let hi = (lo & 0xff00) | (lo as u8).wrapping_add(1) as u16; 377 | self.read(ctx, lo) as u16 | (self.read(ctx, hi) as u16) << 8 378 | }}; 379 | (ZPG, $read:ident) => {{ 380 | self.fetch8(ctx) as u16 381 | }}; 382 | (ZPX, $read:ident) => {{ 383 | let addr = self.fetch8(ctx); 384 | self.read(ctx, addr as u16); 385 | addr.wrapping_add(self.reg.x) as u16 386 | }}; 387 | (ZPY, $read:ident) => {{ 388 | let addr = self.fetch8(ctx); 389 | self.read(ctx, addr as u16); 390 | addr.wrapping_add(self.reg.y) as u16 391 | }}; 392 | (INX, $read:ident) => {{ 393 | let a = self.fetch8(ctx); 394 | let _ = self.read(ctx, a as u16); 395 | let a = a.wrapping_add(self.reg.x); 396 | let lo = self.read(ctx, a as u16); 397 | let hi = self.read(ctx, a.wrapping_add(1) as u16); 398 | lo as u16 | (hi as u16) << 8 399 | }}; 400 | (INY, $read:ident) => {{ 401 | let a = self.fetch8(ctx); 402 | let lo = self.read(ctx, a as u16) as u16; 403 | let hi = self.read(ctx, a.wrapping_add(1) as u16) as u16; 404 | let addr = (lo | hi << 8); 405 | let tmp = lo + self.reg.y as u16; 406 | if !$read || tmp >= 0x100 { 407 | let _ = self.read(ctx, hi << 8 | tmp & 0xff); 408 | } 409 | addr.wrapping_add(self.reg.y as u16) 410 | }}; 411 | (REL, $read:ident) => {{ 412 | let rel = self.fetch8(ctx) as i8; 413 | self.reg.pc.wrapping_add(rel as u16) 414 | }}; 415 | (UNK, $read:ident) => {{}}; 416 | } 417 | 418 | macro_rules! exec_op { 419 | (ADC, $addr:ident) => {{ 420 | let a = self.reg.a as u16; 421 | let b = self.read(ctx, $addr) as u16; 422 | let c = self.reg.flag.c as u16; 423 | let r = a.wrapping_add(b).wrapping_add(c); 424 | self.reg.flag.c = r > 0xff; 425 | self.reg.flag.v = (a ^ r) & (b ^ r) & 0x80 != 0; 426 | self.reg.a = r as u8; 427 | self.reg.flag.set_nz(self.reg.a); 428 | }}; 429 | (SBC, $addr:ident) => {{ 430 | let a = self.reg.a as u16; 431 | let b = self.read(ctx, $addr) as u16; 432 | let c = self.reg.flag.c as u16; 433 | let r = a.wrapping_sub(b).wrapping_sub(1 - c); 434 | self.reg.flag.c = r <= 0xff; 435 | self.reg.flag.v = (a ^ b) & (a ^ r) & 0x80 != 0; 436 | self.reg.a = r as u8; 437 | self.reg.flag.set_nz(self.reg.a); 438 | }}; 439 | (AND, $addr:ident) => {{ 440 | self.reg.a &= self.read(ctx, $addr); 441 | self.reg.flag.set_nz(self.reg.a); 442 | }}; 443 | (ORA, $addr:ident) => {{ 444 | self.reg.a |= self.read(ctx, $addr); 445 | self.reg.flag.set_nz(self.reg.a); 446 | }}; 447 | (EOR, $addr:ident) => {{ 448 | self.reg.a ^= self.read(ctx, $addr); 449 | self.reg.flag.set_nz(self.reg.a); 450 | }}; 451 | (BIT, $addr:ident) => {{ 452 | let r = self.read(ctx, $addr); 453 | self.reg.flag.v = r & 0x40 != 0; 454 | self.reg.flag.n = r & 0x80 != 0; 455 | self.reg.flag.z = (self.reg.a & r) == 0; 456 | }}; 457 | 458 | (cmp, $reg:ident, $addr:ident) => {{ 459 | let a = self.reg.$reg as u16; 460 | let b = self.read(ctx, $addr) as u16; 461 | let r = a.wrapping_sub(b); 462 | self.reg.flag.c = r <= 0xff; 463 | self.reg.flag.set_nz(r as u8); 464 | }}; 465 | (CMP, $addr:ident) => {{ 466 | exec_op!(cmp, a, $addr); 467 | }}; 468 | (CPX, $addr:ident) => {{ 469 | exec_op!(cmp, x, $addr); 470 | }}; 471 | (CPY, $addr:ident) => {{ 472 | exec_op!(cmp, y, $addr); 473 | }}; 474 | 475 | (ld, $reg:ident, $addr:ident) => {{ 476 | self.reg.$reg = self.read(ctx, $addr); 477 | self.reg.flag.set_nz(self.reg.$reg); 478 | }}; 479 | (LDA, $addr:ident) => {{ 480 | exec_op!(ld, a, $addr) 481 | }}; 482 | (LDX, $addr:ident) => {{ 483 | exec_op!(ld, x, $addr) 484 | }}; 485 | (LDY, $addr:ident) => {{ 486 | exec_op!(ld, y, $addr) 487 | }}; 488 | 489 | (st, $reg:ident, $addr:ident) => {{ 490 | self.write(ctx, $addr, self.reg.$reg); 491 | }}; 492 | (STA, $addr:ident) => {{ 493 | exec_op!(st, a, $addr) 494 | }}; 495 | (STX, $addr:ident) => {{ 496 | exec_op!(st, x, $addr) 497 | }}; 498 | (STY, $addr:ident) => {{ 499 | exec_op!(st, y, $addr) 500 | }}; 501 | 502 | (mov, s, $src:ident) => {{ 503 | self.reg.s = self.reg.$src; 504 | }}; 505 | (mov, $dest:ident, $src:ident) => {{ 506 | self.reg.$dest = self.reg.$src; 507 | self.reg.flag.set_nz(self.reg.$dest); 508 | }}; 509 | (TAX) => {{ 510 | exec_op!(mov, x, a); 511 | }}; 512 | (TAY) => {{ 513 | exec_op!(mov, y, a); 514 | }}; 515 | (TXA) => {{ 516 | exec_op!(mov, a, x); 517 | }}; 518 | (TYA) => {{ 519 | exec_op!(mov, a, y); 520 | }}; 521 | (TSX) => {{ 522 | exec_op!(mov, x, s); 523 | }}; 524 | (TXS) => {{ 525 | exec_op!(mov, s, x); 526 | }}; 527 | 528 | (rmw, $op:ident, $addr:ident) => {{ 529 | let mut a = self.read(ctx, $addr); 530 | self.write(ctx, $addr, a); 531 | exec_op!($op, a); 532 | self.write(ctx, $addr, a); 533 | }}; 534 | 535 | (asl, $var:expr) => {{ 536 | self.reg.flag.c = $var & 0x80 != 0; 537 | $var <<= 1; 538 | self.reg.flag.set_nz($var); 539 | }}; 540 | (lsr, $var:expr) => {{ 541 | self.reg.flag.c = $var & 1 != 0; 542 | $var >>= 1; 543 | self.reg.flag.set_nz($var); 544 | }}; 545 | (rol, $var:expr) => {{ 546 | let t = $var; 547 | $var = (t << 1) | self.reg.flag.c as u8; 548 | self.reg.flag.c = t & 0x80 != 0; 549 | self.reg.flag.set_nz($var); 550 | }}; 551 | (ror, $var:expr) => {{ 552 | let t = $var; 553 | $var = (t >> 1) | (self.reg.flag.c as u8) << 7; 554 | self.reg.flag.c = t & 1 != 0; 555 | self.reg.flag.set_nz($var); 556 | }}; 557 | (inc, $var:expr) => {{ 558 | $var = $var.wrapping_add(1); 559 | self.reg.flag.set_nz($var); 560 | }}; 561 | (dec, $var:expr) => {{ 562 | $var = $var.wrapping_sub(1); 563 | self.reg.flag.set_nz($var); 564 | }}; 565 | 566 | (ASL, ACC) => {{ 567 | exec_op!(asl, self.reg.a); 568 | }}; 569 | (ASL, $addr:ident) => {{ 570 | exec_op!(rmw, asl, $addr); 571 | }}; 572 | (LSR, ACC) => {{ 573 | exec_op!(lsr, self.reg.a); 574 | }}; 575 | (LSR, $addr:ident) => {{ 576 | exec_op!(rmw, lsr, $addr); 577 | }}; 578 | (ROL, ACC) => {{ 579 | exec_op!(rol, self.reg.a); 580 | }}; 581 | (ROL, $addr:ident) => {{ 582 | exec_op!(rmw, rol, $addr); 583 | }}; 584 | (ROR, ACC) => {{ 585 | exec_op!(ror, self.reg.a); 586 | }}; 587 | (ROR, $addr:ident) => {{ 588 | exec_op!(rmw, ror, $addr); 589 | }}; 590 | (INX) => {{ 591 | exec_op!(inc, self.reg.x); 592 | }}; 593 | (INY) => {{ 594 | exec_op!(inc, self.reg.y); 595 | }}; 596 | (INC, $addr:ident) => {{ 597 | exec_op!(rmw, inc, $addr); 598 | }}; 599 | (DEX) => {{ 600 | exec_op!(dec, self.reg.x); 601 | }}; 602 | (DEY) => {{ 603 | exec_op!(dec, self.reg.y); 604 | }}; 605 | (DEC, $addr:ident) => {{ 606 | exec_op!(rmw, dec, $addr); 607 | }}; 608 | 609 | (JMP, $addr:ident) => {{ 610 | self.reg.pc = $addr; 611 | }}; 612 | (JSR, $addr:ident) => {{ 613 | let _ = self.read(ctx, self.reg.s as u16 | 0x100); 614 | self.push16(ctx, self.reg.pc.wrapping_sub(1)); 615 | self.reg.pc = $addr; 616 | }}; 617 | (RTS) => {{ 618 | let _ = self.read(ctx, self.reg.s as u16 | 0x100); 619 | let pc = self.pop16(ctx); 620 | let _ = self.read(ctx, pc); 621 | self.reg.pc = pc.wrapping_add(1); 622 | }}; 623 | (RTI) => {{ 624 | let _ = self.read(ctx, self.reg.s as u16 | 0x100); 625 | let p = self.pop8(ctx); 626 | self.reg.flag.set_u8(p); 627 | // Flag set by RTI affects interrupts 628 | self.i_flag_prev = self.reg.flag.i; 629 | self.reg.pc = self.pop16(ctx); 630 | }}; 631 | 632 | (bra, $cond:ident, $val:expr, $addr:ident) => {{ 633 | if self.reg.flag.$cond == $val { 634 | let _ = self.read(ctx, self.reg.pc); 635 | if self.reg.pc & 0xff00 != $addr & 0xff00 { 636 | self.read(ctx, self.reg.pc & 0xff00 | $addr & 0xff); 637 | } 638 | self.reg.pc = $addr; 639 | } 640 | }}; 641 | (BCC, $addr:ident) => {{ 642 | exec_op!(bra, c, false, $addr) 643 | }}; 644 | (BCS, $addr:ident) => {{ 645 | exec_op!(bra, c, true, $addr) 646 | }}; 647 | (BNE, $addr:ident) => {{ 648 | exec_op!(bra, z, false, $addr) 649 | }}; 650 | (BEQ, $addr:ident) => {{ 651 | exec_op!(bra, z, true, $addr) 652 | }}; 653 | (BPL, $addr:ident) => {{ 654 | exec_op!(bra, n, false, $addr) 655 | }}; 656 | (BMI, $addr:ident) => {{ 657 | exec_op!(bra, n, true, $addr) 658 | }}; 659 | (BVC, $addr:ident) => {{ 660 | exec_op!(bra, v, false, $addr) 661 | }}; 662 | (BVS, $addr:ident) => {{ 663 | exec_op!(bra, v, true, $addr) 664 | }}; 665 | 666 | (SEC) => {{ 667 | self.reg.flag.c = true; 668 | }}; 669 | (SED) => {{ 670 | self.reg.flag.d = true; 671 | }}; 672 | (SEI) => {{ 673 | self.reg.flag.i = true; 674 | }}; 675 | (CLC) => {{ 676 | self.reg.flag.c = false; 677 | }}; 678 | (CLD) => {{ 679 | self.reg.flag.d = false; 680 | }}; 681 | (CLI) => {{ 682 | self.reg.flag.i = false; 683 | }}; 684 | (CLV) => {{ 685 | self.reg.flag.v = false; 686 | }}; 687 | 688 | (PHA) => {{ 689 | self.push8(ctx, self.reg.a); 690 | }}; 691 | (PHP) => {{ 692 | self.push8(ctx, self.reg.flag.get_u8(3)); 693 | }}; 694 | (PLA) => {{ 695 | let _ = self.read(ctx, self.reg.s as u16 | 0x100); 696 | self.reg.a = self.pop8(ctx); 697 | self.reg.flag.set_nz(self.reg.a); 698 | }}; 699 | (PLP) => {{ 700 | let _ = self.read(ctx, self.reg.s as u16 | 0x100); 701 | let p = self.pop8(ctx); 702 | self.reg.flag.set_u8(p); 703 | }}; 704 | 705 | (BRK) => {{ 706 | self.reg.pc = self.reg.pc.wrapping_add(1); 707 | self.exec_interrupt(ctx, Interrupt::Irq, true); 708 | // Interrupt after BRK did not happen 709 | self.i_flag_prev = self.reg.flag.i; 710 | }}; 711 | 712 | (NOP) => {{}}; 713 | 714 | // Undocumented 715 | (NOP, $addr:ident) => {{ 716 | let _ = self.read(ctx, $addr); 717 | }}; 718 | 719 | (LAX, $addr:ident) => {{ 720 | self.reg.a = self.read(ctx, $addr); 721 | self.reg.x = self.reg.a; 722 | self.reg.flag.set_nz(self.reg.a); 723 | }}; 724 | (SAX, $addr:ident) => {{ 725 | self.write(ctx, $addr, self.reg.a & self.reg.x); 726 | }}; 727 | (DCP, $addr:ident) => {{ 728 | let b = self.read(ctx, $addr); 729 | self.write(ctx, $addr, b); 730 | let b = b.wrapping_sub(1); 731 | self.write(ctx, $addr, b); 732 | let r = (self.reg.a as u16).wrapping_sub(b as u16); 733 | self.reg.flag.c = r <= 0xff; 734 | self.reg.flag.set_nz(r as u8); 735 | }}; 736 | (ISB, $addr:ident) => {{ 737 | let b = self.read(ctx, $addr); 738 | self.write(ctx, $addr, b); 739 | let b = b.wrapping_add(1); 740 | self.write(ctx, $addr, b); 741 | let a = self.reg.a as u16; 742 | let b = b as u16; 743 | let c = self.reg.flag.c as u16; 744 | let r = a.wrapping_sub(b).wrapping_sub(1 - c); 745 | self.reg.flag.c = r <= 0xff; 746 | self.reg.flag.v = (a ^ b) & (a ^ r) & 0x80 != 0; 747 | self.reg.a = r as u8; 748 | self.reg.flag.set_nz(self.reg.a); 749 | }}; 750 | (SLO, $addr:ident) => {{ 751 | let b = self.read(ctx, $addr); 752 | self.write(ctx, $addr, b); 753 | self.reg.flag.c = b >> 7 != 0; 754 | let b = b << 1; 755 | self.reg.a |= b; 756 | self.write(ctx, $addr, b); 757 | self.reg.flag.set_nz(self.reg.a); 758 | }}; 759 | (RLA, $addr:ident) => {{ 760 | let b = self.read(ctx, $addr); 761 | self.write(ctx, $addr, b); 762 | let c = self.reg.flag.c; 763 | self.reg.flag.c = b >> 7 != 0; 764 | let b = (b << 1) | c as u8; 765 | self.write(ctx, $addr, b); 766 | self.reg.a &= b; 767 | self.reg.flag.set_nz(self.reg.a); 768 | }}; 769 | (SRE, $addr:ident) => {{ 770 | let b = self.read(ctx, $addr); 771 | self.write(ctx, $addr, b); 772 | self.reg.flag.c = b & 1 != 0; 773 | let b = b >> 1; 774 | self.write(ctx, $addr, b); 775 | self.reg.a ^= b; 776 | self.reg.flag.set_nz(self.reg.a); 777 | }}; 778 | (RRA, $addr:ident) => {{ 779 | let b = self.read(ctx, $addr); 780 | self.write(ctx, $addr, b); 781 | let c = self.reg.flag.c as u8; 782 | self.reg.flag.c = b & 1 != 0; 783 | let b = (b >> 1) | (c << 7); 784 | self.write(ctx, $addr, b); 785 | let a = self.reg.a as u16; 786 | let b = b as u16; 787 | let c = self.reg.flag.c as u16; 788 | let r = a.wrapping_add(b).wrapping_add(c); 789 | self.reg.flag.c = r > 0xff; 790 | self.reg.flag.v = (a ^ r) & (b ^ r) & 0x80 != 0; 791 | self.reg.a = r as u8; 792 | self.reg.flag.set_nz(self.reg.a); 793 | }}; 794 | (AAC, $addr:ident) => {{ 795 | self.reg.a &= self.read(ctx, $addr); 796 | self.reg.flag.set_nz(self.reg.a); 797 | self.reg.flag.c = self.reg.flag.n; 798 | }}; 799 | (ASR, $addr:ident) => {{ 800 | self.reg.a &= self.read(ctx, $addr); 801 | self.reg.flag.c = self.reg.a & 1 != 0; 802 | self.reg.a >>= 1; 803 | self.reg.flag.set_nz(self.reg.a); 804 | }}; 805 | (ARR, $addr:ident) => {{ 806 | self.reg.a &= self.read(ctx, $addr); 807 | self.reg.a = (self.reg.a >> 1) | (self.reg.flag.c as u8) << 7; 808 | self.reg.flag.set_nz(self.reg.a); 809 | self.reg.flag.c = (self.reg.a >> 6) & 1 != 0; 810 | self.reg.flag.v = ((self.reg.a >> 5) & 1 != 0) != self.reg.flag.c; 811 | }}; 812 | (ATX, $addr:ident) => {{ 813 | self.reg.a = self.read(ctx, $addr); 814 | self.reg.x = self.reg.a; 815 | self.reg.flag.set_nz(self.reg.a); 816 | }}; 817 | (AXS, $addr:ident) => {{ 818 | let t = 819 | ((self.reg.x & self.reg.a) as u16).wrapping_sub(self.read(ctx, $addr) as u16); 820 | self.reg.x = t as u8; 821 | self.reg.flag.set_nz(self.reg.x); 822 | self.reg.flag.c = t <= 0xff; 823 | }}; 824 | (SYA, $addr:ident) => {{ 825 | let t = self.reg.y & (($addr >> 8) + 1) as u8; 826 | if self.reg.x as u16 + self.read(ctx, opaddr.wrapping_add(1)) as u16 <= 0xff { 827 | self.write(ctx, $addr, t); 828 | } 829 | }}; 830 | (SXA, $addr:ident) => {{ 831 | let t = self.reg.x & (($addr >> 8) + 1) as u8; 832 | if self.reg.y as u16 + self.read(ctx, opaddr.wrapping_add(1)) as u16 <= 0xff { 833 | self.write(ctx, $addr, t); 834 | } 835 | }}; 836 | 837 | (UNK, $addr:ident) => {{ 838 | log::warn!("invalid opcode: ${opc:02X}"); 839 | }}; 840 | } 841 | 842 | instructions!(gen_code); 843 | } 844 | 845 | fn trace(&self, ctx: &impl Context) { 846 | use crate::consts::{LINES_PER_FRAME, PPU_CLOCK_PER_LINE}; 847 | 848 | let pc = self.reg.pc; 849 | let opc = ctx.read_pure(pc).unwrap_or(0); 850 | let opr = ctx.read_pure(pc + 1).unwrap_or(0) as u16 851 | | (ctx.read_pure(pc + 2).unwrap_or(0) as u16) << 8; 852 | 853 | let ppu_cycle = self.counter * 3; 854 | let line = ppu_cycle / PPU_CLOCK_PER_LINE % LINES_PER_FRAME as u64; 855 | let col = ppu_cycle % PPU_CLOCK_PER_LINE; 856 | 857 | let asm = disasm(pc, opc, opr); 858 | let prg_page = if pc & 0x8000 != 0 { 859 | format!("{:02X}", ctx.prg_page(((pc & !0x8000) / 0x2000) as _)) 860 | } else { 861 | " ".to_string() 862 | }; 863 | 864 | log::trace!(target: "disasm", 865 | "{prg_page}:{pc:04X}: {asm:13} | A:{a:02X} X:{x:02X} Y:{y:02X} S:{s:02X} P:{n}{v}{d}{i}{z}{c} PPU:{line:3},{col:3}", 866 | pc = self.reg.pc, 867 | a = self.reg.a, 868 | x = self.reg.x, 869 | y = self.reg.y, 870 | s = self.reg.s, 871 | n = if self.reg.flag.n { 'N' } else { '-' }, 872 | v = if self.reg.flag.v { 'V' } else { '-' }, 873 | d = if self.reg.flag.d { 'D' } else { '-' }, 874 | i = if self.reg.flag.i { 'I' } else { '-' }, 875 | z = if self.reg.flag.z { 'Z' } else { '-' }, 876 | c = if self.reg.flag.c { 'C' } else { '-' }, 877 | ); 878 | 879 | let bytes = match INSTR_TABLE[opc as usize].1.len() { 880 | 1 => format!("{opc:02X}"), 881 | 2 => format!("{opc:02X} {:02X}", opr & 0xff), 882 | 3 => format!("{opc:02X} {:02X} {:02X}", opr & 0xff, opr >> 8), 883 | _ => unreachable!(), 884 | }; 885 | 886 | let read = |addr: u16| { 887 | if !(0x2000..0x8000).contains(&addr) { 888 | format!("{:02X}", ctx.read_pure(addr).unwrap_or(0)) 889 | } else { 890 | "??".to_string() 891 | } 892 | }; 893 | 894 | let ctx = match &INSTR_TABLE[opc as usize].1 { 895 | AddrMode::ZPG => format!(" = {}", read(opr & 0xff)), 896 | AddrMode::ABS => { 897 | if !matches!(INSTR_TABLE[opc as usize].0, "JMP" | "JSR") { 898 | format!(" = {}", read(opr)) 899 | } else { 900 | "".to_string() 901 | } 902 | } 903 | AddrMode::IND => format!( 904 | " = {}{}", 905 | read((opr & 0xff00) | (opr as u8).wrapping_add(1) as u16), 906 | read(opr) 907 | ), 908 | AddrMode::ZPX => { 909 | let addr = (opr as u8).wrapping_add(self.reg.x); 910 | format!(" @ {addr:02X} = {}", read(addr as u16)) 911 | } 912 | AddrMode::ZPY => { 913 | let addr = (opr as u8).wrapping_add(self.reg.y); 914 | format!(" @ {addr:02X} = {}", read(addr as u16)) 915 | } 916 | AddrMode::ABX => { 917 | let addr = opr.wrapping_add(self.reg.x as u16); 918 | format!(" @ {addr:04X} = {}", read(addr as u16)) 919 | } 920 | AddrMode::ABY => { 921 | let addr = opr.wrapping_add(self.reg.y as u16); 922 | format!(" @ {addr:04X} = {}", read(addr as u16)) 923 | } 924 | AddrMode::INX => { 925 | let addr = (opr as u8).wrapping_add(self.reg.x); 926 | let ind = ctx.read_pure(addr as u16).unwrap_or(0) as u16 927 | | (ctx.read_pure(addr.wrapping_add(1) as u16).unwrap_or(0) as u16) << 8; 928 | format!(" @ {addr:02X} = {ind:04X} = {}", read(ind)) 929 | } 930 | AddrMode::INY => { 931 | let ind = ctx.read_pure((opr as u8) as u16).unwrap_or(0) as u16 932 | | (ctx 933 | .read_pure((opr as u8).wrapping_add(1) as u16) 934 | .unwrap_or(0) as u16) 935 | << 8; 936 | let addr = ind.wrapping_add(self.reg.y as u16); 937 | format!(" = {ind:04X} @ {addr:04X} = {}", read(addr)) 938 | } 939 | 940 | AddrMode::IMP | AddrMode::ACC | AddrMode::IMM | AddrMode::REL | AddrMode::UNK => { 941 | "".to_string() 942 | } 943 | }; 944 | 945 | let asm = format!("{}{}", asm, ctx); 946 | 947 | log::trace!(target: "disasnt", 948 | "{pc:04X} {bytes:8} {asm:32} \ 949 | A:{a:02X} X:{x:02X} Y:{y:02X} P:{p:02X} SP:{s:02X} \ 950 | PPU:{line:3},{col:3} CYC:{cyc}", 951 | pc = self.reg.pc, 952 | a = self.reg.a, 953 | x = self.reg.x, 954 | y = self.reg.y, 955 | s = self.reg.s, 956 | p = self.reg.flag.get_u8(2), 957 | cyc = self.counter, 958 | ); 959 | } 960 | } 961 | 962 | macro_rules! instr_table { 963 | ($($opc:literal: $a:tt $b:ident $($c:ident)?, )*) => {{ 964 | [$( 965 | instr_entry!($a $b $($c)*), 966 | )*] 967 | }}; 968 | } 969 | 970 | macro_rules! instr_entry { 971 | (*$mne:ident $mode:ident) => {{ 972 | (stringify!($mne), AddrMode::$mode, false) 973 | }}; 974 | ($mne:ident $mode:ident) => {{ 975 | (stringify!($mne), AddrMode::$mode, true) 976 | }}; 977 | } 978 | 979 | const INSTR_TABLE: [(&str, AddrMode, bool); 256] = instructions!(instr_table); 980 | 981 | fn disasm(pc: u16, opc: u8, opr: u16) -> String { 982 | let opc = opc as usize; 983 | let (mne, addr_mode, official) = &INSTR_TABLE[opc]; 984 | let u = if *official { ' ' } else { '*' }; 985 | 986 | match addr_mode { 987 | AddrMode::IMP => format!("{u}{mne}"), 988 | AddrMode::IMM => format!("{u}{mne} #${:02X}", opr & 0xff), 989 | AddrMode::ACC => format!("{u}{mne} A"), 990 | AddrMode::ABS => format!("{u}{mne} ${opr:04X}"), 991 | AddrMode::ABX => format!("{u}{mne} ${opr:04X},X"), 992 | AddrMode::ABY => format!("{u}{mne} ${opr:04X},Y"), 993 | AddrMode::IND => format!("{u}{mne} (${opr:04X})"), 994 | AddrMode::ZPG => format!("{u}{mne} ${:02X}", opr & 0xff), 995 | AddrMode::ZPX => format!("{u}{mne} ${:02X},X", opr & 0xff), 996 | AddrMode::ZPY => format!("{u}{mne} ${:02X},Y", opr & 0xff), 997 | AddrMode::INX => format!("{u}{mne} (${:02X},X)", opr & 0xff), 998 | AddrMode::INY => format!("{u}{mne} (${:02X}),Y", opr & 0xff), 999 | AddrMode::REL => { 1000 | let addr = pc.wrapping_add((opr & 0xff) as i8 as u16).wrapping_add(2); 1001 | format!("{u}{mne} ${:04X}", addr) 1002 | } 1003 | AddrMode::UNK => format!("{u}{mne} ???"), 1004 | } 1005 | } 1006 | --------------------------------------------------------------------------------