1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
//! The nes module contains the connective tissue of the NES. //! It contains the the code for communication between the CPU //! and the PPU. extern crate emulator_6502; use crate::cartridge::Cartridge; use crate::input::{NesInput, NesInputDevice}; use crate::nes::apu::NesApu; use crate::nes::ppu::NesPpu; use emulator_6502::{Interface6502, MOS6502}; mod apu; mod ppu; /// The dimensions of NES screen in pixels pub const NES_SCREEN_DIMENSIONS: usize = 256 * 240; /// Struct that represents the NES itself pub struct Nes { // NES Components----------------------------------------------------------------------------------------------------------------- /// The cpu of the NES /// /// The actual NES used a 2A03 which combined the cpu and apu functionality, but they are represented separately here cpu: MOS6502, /// The bus of the NES, which holds ownership of the other components bus: Bus, // Additional Tracking Information------------------------------------------------------------------------------------------------ /// The number of cycles that have been executed so far cycle_count: u64, } /// Struct that represents the NES components that are connected to the main bus. /// The primary reasons for this classes existence is to allow for reading and writing by the cpu /// after the NES has been decomposed. struct Bus { /// The cartridge loaded into the NES cartridge: Box<Cartridge>, /// The picture processing unit of the NES ppu: NesPpu, /// The audio processing unit of the NES apu: NesApu, /// The NES' two kilobytes of ram ram: Box<[u8; 0x0800]>, /// The first input device connected to the NES input_device_one: NesInput, /// The second input device connected to the NES input_device_two: NesInput, /// The status of the OAM DMA process. When OAM DMA is activated the value is set to Some(DmaStatus) dma_status: Option<DmaStatus>, } /// Struct that wraps an option to represent if oam dma is in progress and how far along it is. /// If the value is None, no DMA is in progress. /// If the value is Some(n), DMA has been running for n cycles. #[derive(Debug, Clone, Copy)] struct DmaStatus { /// A latch to ensure that DMA waits 1 or 2 cycles before beginning to copy data dma_wait: bool, /// The address that DMA begins to copy from dma_start_address: u16, /// The number of bytes that DMA has copied so far dma_count: u8, /// A buffer for data read from RAM that will be written to OAM on the next cycle dma_buffer: u8, } impl Nes { /// Creates a new NES instance with no connected controllers pub fn new(cartridge: Cartridge) -> Self { let mut bus = Bus { cartridge: Box::new(cartridge), ppu: NesPpu::new(), apu: NesApu::new(), ram: Box::new([0; 0x0800]), input_device_one: NesInput::Disconnected, input_device_two: NesInput::Disconnected, dma_status: None, }; Nes { cpu: MOS6502::new_reset_position(&mut bus), bus, cycle_count: 0, } } /// Executes a single cycle of the NES pub fn cycle(&mut self) { if self.cycle_count % 3 == 0 { //Copy the dma_status so that the bus is not decomposed which would prevent calling methods on it in the match statement let mut dma_status = self.bus.dma_status; // This was created as a personal exercise in pattern matching, but isn't very readable. // I should consider alternatives. match (self.cycle_count, &mut dma_status) { // DMA disabled, CPU cycles every third ppu dot (_, None) => { self.cpu.cycle(&mut self.bus); // DMA status may have been changed, copy it back dma_status = self.bus.dma_status; } // DMA ENABLED ------------------------------------------------------------------------------------------------------------ // DMA can only start on an even clock cycle (c, Some(DmaStatus { dma_wait: wait @ true, .. })) if c % 2 == 1 => { trace!("DMA Initiated on cycle: {}!", self.cycle_count); *wait = false; } // DMA must wait a clock cycle for reads to be resolved (_, Some(DmaStatus { dma_wait: true, .. })) => (), // DMA reads from memory on even clock cycles ( c, Some(DmaStatus { dma_wait: false, dma_start_address, dma_count, dma_buffer, }), ) if c % 2 == 0 => { *dma_buffer = self.bus.read(*dma_start_address + *dma_count as u16); } // And writes to OAM on odd clock cycles ( _, Some(DmaStatus { dma_wait: false, dma_count, dma_buffer, .. }), ) => { self.bus.ppu.oam_dma_write(*dma_count, *dma_buffer); *dma_count = dma_count.wrapping_add(1); // When the count has wrapped around, the DMA is over if *dma_count == 0 { dma_status = None; trace!("DMA ended on cycle: {}!", self.cycle_count); } } } self.bus.dma_status = dma_status; } // PPU cycle runs regardless self.bus.ppu.cycle(&mut self.bus.cartridge, &mut self.cpu); // Check if the Cartridge is triggering an interrupt if self.bus.cartridge.get_pending_interrupt_request() { self.cpu.interrupt_request(); } self.cycle_count += 1; } /// Runs as many cycles as necessary to complete the current frame. /// Returns the frame as an of 32 bit colour ARGB colour values. #[cfg(not(feature = "web-frame-format"))] pub fn frame(&mut self) -> &[u32; NES_SCREEN_DIMENSIONS] { self.complete_frame(); return self.get_screen(); } /// Runs as many cycles as necessary to complete the current frame. /// Returns the frame as an of 32 bit colour ARGB colour values. #[cfg(feature = "web-frame-format")] pub fn frame(&mut self) -> &[u8; NES_SCREEN_DIMENSIONS * 4] { self.complete_frame(); return self.get_screen(); } /// Runs as many cycles as necessary to complete the current frame. fn complete_frame(&mut self) { let current_frame = self.bus.ppu.frame_count; while self.bus.ppu.frame_count == current_frame { self.cycle(); } } /// Updates the state of the input device connected to the first port pub fn update_controller_one(&mut self, input_state: Option<u8>) { match (&mut self.bus.input_device_one, input_state) { (NesInput::Disconnected, None) => {} (NesInput::Connected(_), None) => self.bus.input_device_one = NesInput::Disconnected, (NesInput::Disconnected, Some(state)) => self.bus.input_device_one = NesInput::Connected(NesInputDevice::new(state)), (NesInput::Connected(ref mut device), Some(state)) => device.update_state(state), } } /// Updates the state of the input device connected to the first port pub fn update_controller_two(&mut self, input_state: Option<u8>) { match (&mut self.bus.input_device_two, input_state) { (NesInput::Disconnected, None) => {} (NesInput::Connected(_), None) => self.bus.input_device_two = NesInput::Disconnected, (NesInput::Disconnected, Some(state)) => self.bus.input_device_two = NesInput::Connected(NesInputDevice::new(state)), (NesInput::Connected(ref mut device), Some(state)) => device.update_state(state), } } /// Gets the current state of the screen from the PPU's screen buffer as an array of 32 bit colour values. #[cfg(not(feature = "web-frame-format"))] pub fn get_screen(&mut self) -> &[u32; NES_SCREEN_DIMENSIONS] { self.bus.ppu.get_screen() } /// Gets the current state of the screen from the PPU's screen buffer in web format where each pixel is four bytes in RGBA order for web rendering. #[cfg(feature = "web-frame-format")] pub fn get_screen(&mut self) -> &[u8; NES_SCREEN_DIMENSIONS * 4] { self.bus.ppu.get_screen() } /// Resets the state of the console pub fn reset(&mut self) { self.cycle_count = 0; self.cpu.reset(&mut self.bus); self.bus.reset(); } } impl Bus { /// Resets the state of the console components on the bus fn reset(&mut self) { self.ppu.reset(); // self.apu.reset(); } } impl Interface6502 for Bus { fn read(&mut self, address: u16) -> u8 { match address { 0x0000..=0x1fff => self.ram[usize::from(address) & 0x07ff], // Addresses 0x0800-0x1fff mirror the 2KiB of ram 0x2000..=0x3fff => self.ppu.read(&mut self.cartridge, address), // Mirroring will be done by the ppu 0x4000..=0x4015 => self.apu.read(address), 0x4016 => self.input_device_one.poll(0x00), // Read one bit from the first controller TODO: Open Bus Behaviour 0x4017 => self.input_device_two.poll(0x00), // Read one bit from the second controller 0x4018..=0x401f => 0x00, // Usually disabled on the nes TODO: Decide how to handle these 0x4020..=0xffff => self.cartridge.program_read(address), // Addresses above 0x4020 read from the cartridge } } fn write(&mut self, address: u16, data: u8) { match address { 0x0000..=0x1fff => self.ram[usize::from(address) & 0x07ff] = data, // Addresses 0x0800-0x1fff mirror the 2KiB of ram 0x2000..=0x3fff => self.ppu.write(&mut self.cartridge, address, data), // Mirroring will be done by the ppu 0x4000..=0x4013 => self.apu.write(address, data), 0x4014 => self.dma_status = Some(DmaStatus::new(data)), // Begins the OAM DMA operation at the data page 0x4015 => self.apu.write(address, data), // Write to the APU's sound channel register 0x4016 => { // Set the shift register reload latch on the both controllers self.input_device_one.latch(data); self.input_device_two.latch(data); } 0x4017 => self.apu.write(address, data), // Writing to the second controller address is the APU frame counter control 0x4018..=0x401f => warn!("Write to disabled address 0x{:04X}", address), // Usually disabled on the nes 0x4020..=0xffff => self.cartridge.program_write(address, data), // Addresses above 0x4020 write to the cartridge } } } impl DmaStatus { /// Create a new DmaStatus instance fn new(page: u8) -> Self { DmaStatus { dma_wait: true, dma_start_address: (page as u16) << 8, dma_count: 0, dma_buffer: 0, } } } // TODO: Write DMA tests