From 990252107162ff4be37bd3edc0b45d1e0fb04928 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Tue, 17 Jun 2025 18:44:02 +0200 Subject: [PATCH 01/18] frontend: add buttons for stepping, pause and resume --- frontend/src/app.rs | 19 +++++++++++++++++++ frontend/src/emu_state.rs | 30 +++++++++++++++--------------- frontend/src/main.rs | 2 +- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/frontend/src/app.rs b/frontend/src/app.rs index b49ee63..4efc1e8 100644 --- a/frontend/src/app.rs +++ b/frontend/src/app.rs @@ -1,4 +1,5 @@ use crate::emu_state::EmuState; +use crate::emu_state::Message; use aliusnes::cart::Cart; use eframe::{ egui::{self, Color32, ColorImage}, @@ -7,6 +8,7 @@ use eframe::{ pub struct App { emu_state: EmuState, + playing: bool, texture: egui::TextureHandle, } @@ -15,6 +17,7 @@ impl App { cc.egui_ctx.set_visuals(egui::Visuals::dark()); Self { emu_state: EmuState::new(cart), + playing: true, texture: cc.egui_ctx.load_texture( "Framebuffer", egui::ColorImage::new([512, 478], egui::Color32::TRANSPARENT), @@ -27,6 +30,22 @@ impl App { impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::SidePanel::left("my_left_panel").show(ctx, |ui| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| { + if ui.button("Step").clicked() { + self.emu_state.send_message(Message::Step); + } + if ui + .button(if self.playing { "Pause" } else { "Resume" }) + .clicked() + { + if self.playing { + self.emu_state.send_message(Message::Pause); + } else { + self.emu_state.send_message(Message::Play); + } + self.playing = !self.playing; + } + }); ui.label("CPU disasm"); }); egui::CentralPanel::default().show(ctx, |ui| { diff --git a/frontend/src/emu_state.rs b/frontend/src/emu_state.rs index 6501239..8b3aa8b 100644 --- a/frontend/src/emu_state.rs +++ b/frontend/src/emu_state.rs @@ -1,17 +1,14 @@ use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - mpsc, - }, + sync::mpsc::{channel, Receiver, Sender}, thread, }; use aliusnes::{cart::Cart, emu::Emu}; -#[allow(dead_code)] pub enum Message { Pause, - Stop, + Play, + Step, } pub struct Frame { @@ -22,14 +19,14 @@ pub struct Frame { #[allow(dead_code)] pub struct EmuState { - message_tx: mpsc::Sender, + message_tx: Sender, pub frame_rx: rtrb::Consumer, emu_thread: thread::JoinHandle<()>, } impl EmuState { pub fn new(cart: Cart) -> Self { - let (message_tx, message_rx) = mpsc::channel::(); + let (message_tx, message_rx) = channel::(); let (frame_tx, frame_rx) = rtrb::RingBuffer::::new(5); Self { message_tx, @@ -38,23 +35,26 @@ impl EmuState { } } - #[allow(dead_code)] pub fn send_message(&self, msg: Message) { self.message_tx.send(msg).expect("Error on sending message"); } - fn run(cart: Cart, mut frame_tx: rtrb::Producer, message_rx: mpsc::Receiver) { - let paused = AtomicBool::new(false); + fn run(cart: Cart, mut frame_tx: rtrb::Producer, message_rx: Receiver) { + let mut paused = false; let mut emu = Emu::new(cart); - 'main: loop { + loop { for msg in message_rx.try_iter() { match msg { - Message::Pause => paused.store(true, Ordering::Relaxed), - Message::Stop => break 'main, + Message::Pause => paused = true, + Message::Play => paused = false, + Message::Step => { + paused = true; + emu.step(); + } } } - if !paused.load(Ordering::Relaxed) { + if !paused { emu.step(); } diff --git a/frontend/src/main.rs b/frontend/src/main.rs index a30abd9..6a04a1d 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -17,7 +17,7 @@ fn parse_rom(path: &str) -> Option<&Path> { } fn main() { - env::set_var("RUST_BACKTRACE", "1"); + unsafe { env::set_var("RUST_BACKTRACE", "1") }; let args: Vec = env::args().collect(); if args.len() < 2 || args.len() > 3 { From f0199325ea1d69cad696e617f94de464e472ffeb Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Tue, 8 Jul 2025 20:00:37 +0200 Subject: [PATCH 02/18] w65c816: cleanup some code around reading and writing from bus --- aliusnes/src/w65c816/addressing.rs | 20 ++++++---------- aliusnes/src/w65c816/cpu.rs | 37 ++++++++++++++++-------------- aliusnes/src/w65c816/functions.rs | 6 +---- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/aliusnes/src/w65c816/addressing.rs b/aliusnes/src/w65c816/addressing.rs index 3ed62db..cf1b3c1 100644 --- a/aliusnes/src/w65c816/addressing.rs +++ b/aliusnes/src/w65c816/addressing.rs @@ -85,18 +85,16 @@ impl From
for usize { } impl Cpu { - pub fn read_bank0(&mut self, bus: &mut B, offset: u16) -> u16 { - let addr = Address::new(offset, 0); + pub fn read_bank0(&mut self, bus: &mut B, page: u16) -> u16 { u16::from_le_bytes([ - bus.read_and_tick(addr), - bus.read_and_tick(addr.wrapping_offset_add(1)), + bus.read_and_tick(page.into()), + bus.read_and_tick(page.wrapping_add(1).into()), ]) } - pub fn write_bank0(&mut self, bus: &mut B, offset: u16, data: u16) { - let addr = Address::new(offset, 0); - bus.write_and_tick(addr, data.low_byte()); - bus.write_and_tick(addr.wrapping_offset_add(1), data.high_byte()); + pub fn write_bank0(&mut self, bus: &mut B, page: u16, data: u16) { + bus.write_and_tick(page.into(), data.low_byte()); + bus.write_and_tick(page.wrapping_add(1).into(), data.high_byte()); } pub fn get_imm(&mut self, bus: &mut B) -> T { @@ -257,11 +255,7 @@ impl Cpu { | AddressingMode::StackPEI => self.read_from_direct_page(bus, mode).0, _ => { let addr = self.decode_addressing_mode::(bus, *mode); - if T::IS_U16 { - T::from_u16(self.read_16(bus, addr)) - } else { - T::from_u8(bus.read_and_tick(addr)) - } + self.read(bus, addr) } } } diff --git a/aliusnes/src/w65c816/cpu.rs b/aliusnes/src/w65c816/cpu.rs index 2443b03..f18d42a 100644 --- a/aliusnes/src/w65c816/cpu.rs +++ b/aliusnes/src/w65c816/cpu.rs @@ -167,16 +167,25 @@ impl Cpu { self.program_counter = self.read_bank0(bus, interrupt.get_addr(self.emulation_mode)); } - pub fn read_16(&mut self, bus: &mut B, addr: Address) -> u16 { - u16::from_le_bytes([ - bus.read_and_tick(addr), - bus.read_and_tick(addr.wrapping_add(1)), - ]) + pub fn read(&mut self, bus: &mut B, addr: Address) -> T { + if T::IS_U16 { + let value = u16::from_le_bytes([ + bus.read_and_tick(addr), + bus.read_and_tick(addr.wrapping_add(1)), + ]); + T::from_u16(value) + } else { + T::from_u8(bus.read_and_tick(addr)) + } } - pub fn write_16(&mut self, bus: &mut B, addr: Address, data: u16) { - bus.write_and_tick(addr, data.low_byte()); - bus.write_and_tick(addr.wrapping_add(1), data.high_byte()); + pub fn write(&mut self, bus: &mut B, addr: Address, data: T) { + if T::IS_U16 { + bus.write_and_tick(addr, data.as_u16().low_byte()); + bus.write_and_tick(addr.wrapping_add(1), data.as_u16().high_byte()); + } else { + bus.write_and_tick(addr, data.as_u8()); + } } pub fn do_rmw( @@ -200,15 +209,9 @@ impl Cpu { } _ => { let addr = self.decode_addressing_mode::(bus, *mode); - if T::IS_U16 { - let data = self.read_16(bus, addr); - let result = f(self, T::from_u16(data)).as_u16(); - self.write_16(bus, addr, result); - } else { - let data = bus.read_and_tick(addr); - let result = f(self, T::from_u8(data)).as_u8(); - bus.write_and_tick(addr, result); - } + let data = self.read(bus, addr); + let result = f(self, data); + self.write(bus, addr, result); } } } diff --git a/aliusnes/src/w65c816/functions.rs b/aliusnes/src/w65c816/functions.rs index caba837..dd85c7b 100644 --- a/aliusnes/src/w65c816/functions.rs +++ b/aliusnes/src/w65c816/functions.rs @@ -289,11 +289,7 @@ pub(super) fn do_store( } _ => { let addr = cpu.decode_addressing_mode::(bus, mode); - if T::IS_U16 { - cpu.write_16(bus, addr, val.as_u16()); - } else { - bus.write_and_tick(addr, val.as_u8()); - } + cpu.write(bus, addr, val); } } } From 6618af278fcca432bef00ca4a5cadcdc9b7371a6 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Wed, 9 Jul 2025 00:00:56 +0200 Subject: [PATCH 03/18] w65c816: fix jmp instr with address mode AbsoluteIndirectX --- aliusnes/src/w65c816/addressing.rs | 10 +++++++++- aliusnes/src/w65c816/instructions.rs | 17 ++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/aliusnes/src/w65c816/addressing.rs b/aliusnes/src/w65c816/addressing.rs index cf1b3c1..c169422 100644 --- a/aliusnes/src/w65c816/addressing.rs +++ b/aliusnes/src/w65c816/addressing.rs @@ -255,7 +255,15 @@ impl Cpu { | AddressingMode::StackPEI => self.read_from_direct_page(bus, mode).0, _ => { let addr = self.decode_addressing_mode::(bus, *mode); - self.read(bus, addr) + + if let AddressingMode::AbsoluteIndirectX = mode { + T::from_u16(u16::from_le_bytes([ + bus.read_and_tick(addr), + bus.read_and_tick(addr.wrapping_offset_add(1)), + ])) + } else { + self.read(bus, addr) + } } } } diff --git a/aliusnes/src/w65c816/instructions.rs b/aliusnes/src/w65c816/instructions.rs index a0c4a77..eb683fb 100644 --- a/aliusnes/src/w65c816/instructions.rs +++ b/aliusnes/src/w65c816/instructions.rs @@ -301,18 +301,13 @@ impl super::W65C816 { } pub fn jsr(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { - let val = if mode == AddressingMode::AbsoluteIndirectX { - let addr = cpu.decode_addressing_mode::(bus, mode); - u16::from_le_bytes([ - bus.read_and_tick(addr), - bus.read_and_tick(addr.wrapping_offset_add(1)), - ]) - } else { - bus.add_io_cycles(1); - cpu.get_operand(bus, &mode) - }; + let new_pc = cpu.get_operand::(bus, &mode); do_push(cpu, bus, cpu.program_counter.wrapping_sub(1)); - cpu.program_counter = val; + cpu.program_counter = new_pc; + + if let AddressingMode::AbsoluteJMP = mode { + bus.add_io_cycles(1); + } } pub fn lda(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { From 1b2d6ec3f602767d72b3eb2c531bd320ff81568d Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Wed, 9 Jul 2025 00:38:32 +0200 Subject: [PATCH 04/18] add dummy apu communication --- aliusnes/src/bus/system_bus.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/aliusnes/src/bus/system_bus.rs b/aliusnes/src/bus/system_bus.rs index ecda712..a03fbc0 100644 --- a/aliusnes/src/bus/system_bus.rs +++ b/aliusnes/src/bus/system_bus.rs @@ -11,6 +11,7 @@ pub struct SystemBus { math: Math, pub ppu: Ppu, wram: Wram, + dummy_apu: [u8; 4], } impl SystemBus { @@ -24,6 +25,7 @@ impl SystemBus { dma: Dma::new(), math: Math::new(), wram: Wram::new(), + dummy_apu: [0xAA, 0, 0, 0], } } @@ -45,7 +47,18 @@ impl SystemBus { pub fn read_b(&mut self, addr: u16) -> u8 { if let Some(val) = match addr.low_byte() { 0x34..=0x3F => self.ppu.read(addr), - 0x40..=0x43 => todo!("apu area"), + 0x40..=0x43 => { + let ch = ((addr - 0x2140) % 4) as usize; + + let value = self.dummy_apu[ch]; + self.dummy_apu[ch] = match ch { + 0 => 0xAA, + 1 => 0xBB, + _ => 0, + }; + + Some(value) + } 0x80 => self.wram.read(addr), _ => None, } { @@ -107,7 +120,10 @@ impl SystemBus { pub fn write_b(&mut self, addr: u16, data: u8) { match addr.low_byte() { 0x00..=0x33 => self.ppu.write(addr, data), - 0x40..=0x43 => todo!("apu area"), + 0x40..=0x43 => { + let ch = ((addr - 0x2140) % 4) as usize; + self.dummy_apu[ch] = data; + } 0x80..=0x83 => self.wram.write(addr, data), _ => println!("Tried to write at {addr:#0x} val: {data:#04x}"), } From 220aa86f2a007509710c0415ae334584b4ccbaf4 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:16:16 +0200 Subject: [PATCH 05/18] w65c816: added Accumulator addr mode, removed duplicate instructions --- aliusnes/src/w65c816/addressing.rs | 1 + aliusnes/src/w65c816/cpu.rs | 4 ++ aliusnes/src/w65c816/instructions.rs | 66 ---------------------------- aliusnes/src/w65c816/mod.rs | 21 +++++---- 4 files changed, 17 insertions(+), 75 deletions(-) diff --git a/aliusnes/src/w65c816/addressing.rs b/aliusnes/src/w65c816/addressing.rs index c169422..7875dab 100644 --- a/aliusnes/src/w65c816/addressing.rs +++ b/aliusnes/src/w65c816/addressing.rs @@ -3,6 +3,7 @@ use crate::{bus::Bus, utils::int_traits::ManipulateU16}; #[derive(Clone, Copy, Debug, PartialEq)] pub enum AddressingMode { + Accumulator, Implied, Immediate, Relative, diff --git a/aliusnes/src/w65c816/cpu.rs b/aliusnes/src/w65c816/cpu.rs index f18d42a..4481e03 100644 --- a/aliusnes/src/w65c816/cpu.rs +++ b/aliusnes/src/w65c816/cpu.rs @@ -195,6 +195,10 @@ impl Cpu { f: fn(&mut Cpu, T) -> T, ) { match mode { + AddressingMode::Accumulator => { + let result = f(self, T::from_u16(self.accumulator)); + self.set_accumulator(result); + } AddressingMode::Direct | AddressingMode::DirectX | AddressingMode::DirectY diff --git a/aliusnes/src/w65c816/instructions.rs b/aliusnes/src/w65c816/instructions.rs index eb683fb..b29405b 100644 --- a/aliusnes/src/w65c816/instructions.rs +++ b/aliusnes/src/w65c816/instructions.rs @@ -47,17 +47,6 @@ impl super::W65C816 { } } - pub fn asl_a(cpu: &mut Cpu, bus: &mut B, _mode: AddressingMode) { - bus.add_io_cycles(1); - if cpu.status.a_reg_size() { - let result = do_asl(cpu, cpu.accumulator.low_byte()); - cpu.set_accumulator(result); - } else { - let result = do_asl(cpu, cpu.accumulator); - cpu.set_accumulator(result); - } - } - pub fn bcc(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { do_branch(cpu, bus, mode, !cpu.status.carry()); } @@ -179,17 +168,6 @@ impl super::W65C816 { } } - pub fn dec_a(cpu: &mut Cpu, bus: &mut B, _mode: AddressingMode) { - bus.add_io_cycles(1); - if cpu.status.a_reg_size() { - let result = do_dec::(cpu, cpu.accumulator.low_byte()); - cpu.set_accumulator(result); - } else { - let result = do_dec::(cpu, cpu.accumulator); - cpu.set_accumulator(result); - } - } - pub fn dex(cpu: &mut Cpu, bus: &mut B, _mode: AddressingMode) { bus.add_io_cycles(1); if cpu.status.index_regs_size() { @@ -235,17 +213,6 @@ impl super::W65C816 { } } - pub fn inc_a(cpu: &mut Cpu, bus: &mut B, _mode: AddressingMode) { - bus.add_io_cycles(1); - if cpu.status.a_reg_size() { - let result = do_inc::(cpu, cpu.accumulator.low_byte()); - cpu.set_accumulator(result); - } else { - let result = do_inc::(cpu, cpu.accumulator); - cpu.set_accumulator(result); - } - } - pub fn inx(cpu: &mut Cpu, bus: &mut B, _mode: AddressingMode) { bus.add_io_cycles(1); if cpu.status.index_regs_size() { @@ -355,17 +322,6 @@ impl super::W65C816 { } } - pub fn lsr_a(cpu: &mut Cpu, bus: &mut B, _mode: AddressingMode) { - bus.add_io_cycles(1); - if cpu.status.a_reg_size() { - let result = do_lsr::(cpu, cpu.accumulator.low_byte()); - cpu.set_accumulator(result); - } else { - let result = do_lsr::(cpu, cpu.accumulator); - cpu.set_accumulator(result); - } - } - pub fn mvn(cpu: &mut Cpu, bus: &mut B, _mode: AddressingMode) { if cpu.status.index_regs_size() { do_block_move::(cpu, bus, u8::wrapping_add); @@ -538,17 +494,6 @@ impl super::W65C816 { } } - pub fn rol_a(cpu: &mut Cpu, bus: &mut B, _mode: AddressingMode) { - bus.add_io_cycles(1); - if cpu.status.a_reg_size() { - let result = do_rol::(cpu, cpu.accumulator.low_byte()); - cpu.set_accumulator(result); - } else { - let result = do_rol::(cpu, cpu.accumulator); - cpu.set_accumulator(result); - } - } - pub fn ror(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { bus.add_io_cycles(1); if cpu.status.a_reg_size() { @@ -558,17 +503,6 @@ impl super::W65C816 { } } - pub fn ror_a(cpu: &mut Cpu, bus: &mut B, _mode: AddressingMode) { - bus.add_io_cycles(1); - if cpu.status.a_reg_size() { - let result = do_ror::(cpu, cpu.accumulator.low_byte()); - cpu.set_accumulator(result); - } else { - let result = do_ror::(cpu, cpu.accumulator); - cpu.set_accumulator(result); - } - } - pub fn rti(cpu: &mut Cpu, bus: &mut B, _mode: AddressingMode) { bus.add_io_cycles(2); let new_status = do_pull::(cpu, bus); diff --git a/aliusnes/src/w65c816/mod.rs b/aliusnes/src/w65c816/mod.rs index 9727a5d..5bb9618 100644 --- a/aliusnes/src/w65c816/mod.rs +++ b/aliusnes/src/w65c816/mod.rs @@ -78,15 +78,18 @@ impl W65C816 { #[cfg(feature = "log")] log::trace!( - "Instr {} A:{:#06x} X:{:#06x} Y:{:#06x}, PC:{:#06x}, SP:{:#06x}, P:{:#04x} {}", + "{} {:02X} {:02x}:{:04x} A:{:04x} X:{:04x} Y:{:04x}, S:{:04x}, D:{:04x}, DB:{:02x}, P:{:08b}", opcode.meta.mnemonic, + opcode.meta.code, + self.cpu.dbr, + (self.cpu.program_counter - 1), self.cpu.accumulator, self.cpu.index_x, self.cpu.index_y, - (self.cpu.program_counter - 1), self.cpu.stack_pointer, + self.cpu.dpr, + self.cpu.dbr, self.cpu.status.0, - self::cpu::format_status(&self.cpu.status) ); let instr = opcode.function; instr(&mut self.cpu, bus, opcode.meta.mode); @@ -112,7 +115,7 @@ pub const fn opcode_table() -> [OpCode; 256] { OpCode::new(Meta::new(0x07, "ORA", IndirectLong), W65C816::ora), OpCode::new(Meta::new(0x08, "PHP", Implied), W65C816::php), OpCode::new(Meta::new(0x09, "ORA", Immediate), W65C816::ora), - OpCode::new(Meta::new(0x0a, "ASL", Implied), W65C816::asl_a), + OpCode::new(Meta::new(0x0a, "ASL", Accumulator), W65C816::asl), OpCode::new(Meta::new(0x0b, "PHD", Implied), W65C816::phd), OpCode::new(Meta::new(0x0c, "TSB", Absolute), W65C816::tsb), OpCode::new(Meta::new(0x0d, "ORA", Absolute), W65C816::ora), @@ -128,7 +131,7 @@ pub const fn opcode_table() -> [OpCode; 256] { OpCode::new(Meta::new(0x17, "ORA", IndirectLongY), W65C816::ora), OpCode::new(Meta::new(0x18, "CLC", Implied), W65C816::clc), OpCode::new(Meta::new(0x19, "ORA", AbsoluteY), W65C816::ora), - OpCode::new(Meta::new(0x1a, "INC", Implied), W65C816::inc_a), + OpCode::new(Meta::new(0x1a, "INC", Accumulator), W65C816::inc), OpCode::new(Meta::new(0x1b, "TCS", Implied), W65C816::tcs), OpCode::new(Meta::new(0x1c, "TRB", Absolute), W65C816::trb), OpCode::new(Meta::new(0x1d, "ORA", AbsoluteX), W65C816::ora), @@ -144,7 +147,7 @@ pub const fn opcode_table() -> [OpCode; 256] { OpCode::new(Meta::new(0x27, "AND", IndirectLong), W65C816::and), OpCode::new(Meta::new(0x28, "PLP", Implied), W65C816::plp), OpCode::new(Meta::new(0x29, "AND", Immediate), W65C816::and), - OpCode::new(Meta::new(0x2a, "ROL", Implied), W65C816::rol_a), + OpCode::new(Meta::new(0x2a, "ROL", Accumulator), W65C816::rol), OpCode::new(Meta::new(0x2b, "PLD", Implied), W65C816::pld), OpCode::new(Meta::new(0x2c, "BIT", Absolute), W65C816::bit), OpCode::new(Meta::new(0x2d, "AND", Absolute), W65C816::and), @@ -160,7 +163,7 @@ pub const fn opcode_table() -> [OpCode; 256] { OpCode::new(Meta::new(0x37, "AND", IndirectLongY), W65C816::and), OpCode::new(Meta::new(0x38, "SEC", Implied), W65C816::sec), OpCode::new(Meta::new(0x39, "AND", AbsoluteY), W65C816::and), - OpCode::new(Meta::new(0x3a, "DEC", Implied), W65C816::dec_a), + OpCode::new(Meta::new(0x3a, "DEC", Accumulator), W65C816::dec), OpCode::new(Meta::new(0x3b, "TSC", Implied), W65C816::tsc), OpCode::new(Meta::new(0x3c, "BIT", AbsoluteX), W65C816::bit), OpCode::new(Meta::new(0x3d, "AND", AbsoluteX), W65C816::and), @@ -176,7 +179,7 @@ pub const fn opcode_table() -> [OpCode; 256] { OpCode::new(Meta::new(0x47, "EOR", IndirectLong), W65C816::eor), OpCode::new(Meta::new(0x48, "PHA", Implied), W65C816::pha), OpCode::new(Meta::new(0x49, "EOR", Immediate), W65C816::eor), - OpCode::new(Meta::new(0x4a, "LSR", Implied), W65C816::lsr_a), + OpCode::new(Meta::new(0x4a, "LSR", Accumulator), W65C816::lsr), OpCode::new(Meta::new(0x4b, "PHK", Implied), W65C816::phk), OpCode::new(Meta::new(0x4c, "JMP", AbsoluteJMP), W65C816::jmp), OpCode::new(Meta::new(0x4d, "EOR", Absolute), W65C816::eor), @@ -208,7 +211,7 @@ pub const fn opcode_table() -> [OpCode; 256] { OpCode::new(Meta::new(0x67, "ADC", IndirectLong), W65C816::adc), OpCode::new(Meta::new(0x68, "PLA", Implied), W65C816::pla), OpCode::new(Meta::new(0x69, "ADC", Immediate), W65C816::adc), - OpCode::new(Meta::new(0x6a, "ROR", Implied), W65C816::ror_a), + OpCode::new(Meta::new(0x6a, "ROR", Accumulator), W65C816::ror), OpCode::new(Meta::new(0x6b, "RTL", Implied), W65C816::rtl), OpCode::new(Meta::new(0x6c, "JMP", AbsoluteIndirect), W65C816::jmp), OpCode::new(Meta::new(0x6d, "ADC", Absolute), W65C816::adc), From d9bb4e0015ab42a3be2d2f8b53423934edd7db72 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:34:13 +0200 Subject: [PATCH 06/18] w65c816: clean up RelativeLong addressing mode --- aliusnes/src/w65c816/addressing.rs | 23 ++++++-- aliusnes/src/w65c816/instructions.rs | 7 +-- aliusnes/src/w65c816/mod.rs | 3 +- aliusnes/src/w65c816/opcode.rs | 88 ++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 aliusnes/src/w65c816/opcode.rs diff --git a/aliusnes/src/w65c816/addressing.rs b/aliusnes/src/w65c816/addressing.rs index 7875dab..4339201 100644 --- a/aliusnes/src/w65c816/addressing.rs +++ b/aliusnes/src/w65c816/addressing.rs @@ -235,19 +235,34 @@ impl Cpu { let offset = self.read_bank0(bus, indirect); Address::new(offset, self.dbr).wrapping_add(u32::from(self.index_y)) } - _ => unreachable!(), + AddressingMode::Accumulator + | AddressingMode::Implied + | AddressingMode::Immediate + | AddressingMode::Relative + | AddressingMode::RelativeLong + | AddressingMode::Direct + | AddressingMode::DirectX + | AddressingMode::DirectY + | AddressingMode::AbsoluteIndirectLong + | AddressingMode::AbsoluteJMP + | AddressingMode::AbsoluteLongJSL + | AddressingMode::StackRelative + | AddressingMode::StackPEI + | AddressingMode::BlockMove => unreachable!(), } } pub fn get_operand(&mut self, bus: &mut B, mode: &AddressingMode) -> T { match mode { + AddressingMode::RelativeLong => { + bus.add_io_cycles(1); + let offset = self.get_imm::(bus) as i16; + T::from_u16(self.program_counter.wrapping_add(offset as u16)) + } AddressingMode::Immediate - | AddressingMode::Implied | AddressingMode::Relative - | AddressingMode::RelativeLong | AddressingMode::AbsoluteJMP | AddressingMode::AbsoluteLongJSL - | AddressingMode::AbsoluteIndirectLong | AddressingMode::BlockMove => self.get_imm(bus), AddressingMode::Direct | AddressingMode::DirectX diff --git a/aliusnes/src/w65c816/instructions.rs b/aliusnes/src/w65c816/instructions.rs index b29405b..9bfb137 100644 --- a/aliusnes/src/w65c816/instructions.rs +++ b/aliusnes/src/w65c816/instructions.rs @@ -86,9 +86,7 @@ impl super::W65C816 { } pub fn brl(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { - bus.add_io_cycles(1); - let offset = cpu.get_operand::(bus, &mode) as i16; - cpu.program_counter = cpu.program_counter.wrapping_add(offset as u16); + cpu.program_counter = cpu.get_operand::(bus, &mode); } pub fn bvc(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { @@ -367,9 +365,8 @@ impl super::W65C816 { } pub fn per(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { - bus.add_io_cycles(1); let value = cpu.get_operand::(bus, &mode); - do_push(cpu, bus, cpu.program_counter.wrapping_add(value)); + do_push(cpu, bus, value); } pub fn pha(cpu: &mut Cpu, bus: &mut B, _mode: AddressingMode) { diff --git a/aliusnes/src/w65c816/mod.rs b/aliusnes/src/w65c816/mod.rs index 5bb9618..992ddd7 100644 --- a/aliusnes/src/w65c816/mod.rs +++ b/aliusnes/src/w65c816/mod.rs @@ -5,6 +5,7 @@ pub mod addressing; pub mod cpu; mod functions; mod instructions; +mod opcode; mod regsize; #[derive(Clone, Copy)] @@ -203,7 +204,7 @@ pub const fn opcode_table() -> [OpCode; 256] { OpCode::new(Meta::new(0x5f, "EOR", AbsoluteLongX), W65C816::eor), OpCode::new(Meta::new(0x60, "RTS", Implied), W65C816::rts), OpCode::new(Meta::new(0x61, "ADC", IndirectX), W65C816::adc), - OpCode::new(Meta::new(0x62, "PER", Implied), W65C816::per), + OpCode::new(Meta::new(0x62, "PER", RelativeLong), W65C816::per), OpCode::new(Meta::new(0x63, "ADC", StackRelative), W65C816::adc), OpCode::new(Meta::new(0x64, "STZ", Direct), W65C816::stz), OpCode::new(Meta::new(0x65, "ADC", Direct), W65C816::adc), diff --git a/aliusnes/src/w65c816/opcode.rs b/aliusnes/src/w65c816/opcode.rs new file mode 100644 index 0000000..3f67b5a --- /dev/null +++ b/aliusnes/src/w65c816/opcode.rs @@ -0,0 +1,88 @@ +use crate::{ + bus::Bus, + w65c816::addressing::{Address, AddressingMode}, +}; + +impl AddressingMode { + const fn operand_size(&self) -> u8 { + match self { + AddressingMode::Accumulator => 0, + AddressingMode::Implied => 0, + AddressingMode::Immediate => 1, + AddressingMode::Relative => 1, + AddressingMode::RelativeLong => 2, + AddressingMode::Direct => 1, + AddressingMode::DirectX => 1, + AddressingMode::DirectY => 1, + AddressingMode::Indirect => 1, + AddressingMode::IndirectX => 1, + AddressingMode::IndirectY => 1, + AddressingMode::IndirectLong => 1, + AddressingMode::IndirectLongY => 1, + AddressingMode::Absolute => 2, + AddressingMode::AbsoluteX => 2, + AddressingMode::AbsoluteY => 2, + AddressingMode::AbsoluteLong => 3, + AddressingMode::AbsoluteLongX => 3, + AddressingMode::AbsoluteIndirect => 2, + AddressingMode::AbsoluteIndirectLong => 2, + AddressingMode::AbsoluteIndirectX => 2, + AddressingMode::AbsoluteJMP => 2, + AddressingMode::AbsoluteLongJSL => 2, + AddressingMode::StackRelative => 1, + AddressingMode::StackRelIndirectY => 1, + AddressingMode::StackPEI => 1, + AddressingMode::BlockMove => 1, + } + } + + pub fn disasm_operand(&self, bus: &B, addr: Address) -> String { + let operand = match self.operand_size() { + 0 => 0, + 1 => bus.peek_at(addr).unwrap_or_default() as u32, + 2 => u16::from_le_bytes([ + bus.peek_at(addr).unwrap_or_default(), + bus.peek_at(addr.wrapping_offset_add(1)).unwrap_or_default(), + ]) as u32, + 3 => u32::from_le_bytes([ + bus.peek_at(addr).unwrap_or_default(), + bus.peek_at(addr.wrapping_offset_add(1)).unwrap_or_default(), + bus.peek_at(addr.wrapping_offset_add(2)).unwrap_or_default(), + 0, + ]), + _ => unreachable!(), + }; + + match self { + AddressingMode::Accumulator => "".to_string(), + AddressingMode::Implied => "".to_string(), + AddressingMode::Immediate => format!("#${:02X}", operand), + AddressingMode::Relative => format!("${:04X}", operand), + AddressingMode::RelativeLong => format!("${:06X}", operand), + AddressingMode::Direct => format!("${:02X}", operand), + AddressingMode::DirectX => format!("${:02X},X", operand), + AddressingMode::DirectY => format!("${:02X},Y", operand), + AddressingMode::Indirect => format!("(${:02X})", operand), + AddressingMode::IndirectX => format!("(${:02X},X)", operand), + AddressingMode::IndirectY => format!("(${:02X}),Y", operand), + AddressingMode::IndirectLong => format!("[${:02X}]", operand), + AddressingMode::IndirectLongY => format!("[${:02X}],Y", operand), + AddressingMode::Absolute => format!("${:04X}", operand), + AddressingMode::AbsoluteX => format!("${:04X},X", operand), + AddressingMode::AbsoluteY => format!("${:04X},Y", operand), + AddressingMode::AbsoluteLong => format!("${:06X}", operand), + AddressingMode::AbsoluteLongX => format!("${:06X},X", operand), + AddressingMode::AbsoluteIndirect => format!("(${:04X})", operand), + AddressingMode::AbsoluteIndirectLong => todo!(), + AddressingMode::AbsoluteIndirectX => format!("(${:04X},X)", operand), + AddressingMode::AbsoluteJMP => format!("${:04X}", operand), + AddressingMode::AbsoluteLongJSL => format!("${:06X}", operand), + AddressingMode::StackRelative => format!("${:02X},S", operand), + AddressingMode::StackRelIndirectY => format!("(${:02X},S),Y", operand), + AddressingMode::StackPEI => format!("(${:02X})", operand), + AddressingMode::BlockMove => { + format!("${:02X} ${:02X}", operand as u8, (operand >> 8) & 0xFF) + } + } + } +} From a404ec6c00553057d7d782821bd49e2736d42fcb Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Tue, 15 Jul 2025 19:55:41 +0200 Subject: [PATCH 07/18] w65c816: fix rep instruction --- aliusnes/src/w65c816/instructions.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/aliusnes/src/w65c816/instructions.rs b/aliusnes/src/w65c816/instructions.rs index 9bfb137..ca0b55d 100644 --- a/aliusnes/src/w65c816/instructions.rs +++ b/aliusnes/src/w65c816/instructions.rs @@ -479,7 +479,11 @@ impl super::W65C816 { bus.add_io_cycles(1); let mask = cpu.get_operand::(bus, &mode); let src = cpu.status.0; - cpu.status = super::cpu::Status(src & !mask); + cpu.set_status_register(src & !mask); + if cpu.emulation_mode { + cpu.status.set_a_reg_size(true); + cpu.status.set_index_regs_size(true); + } } pub fn rol(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { From 18f7dc8fb5513e8bae365b63f540dcd4b4c8a210 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Tue, 15 Jul 2025 20:06:01 +0200 Subject: [PATCH 08/18] bus: implement peek_at --- aliusnes/src/bus/dma.rs | 8 ++-- aliusnes/src/bus/system_bus.rs | 69 +++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/aliusnes/src/bus/dma.rs b/aliusnes/src/bus/dma.rs index f3f39af..b44f022 100644 --- a/aliusnes/src/bus/dma.rs +++ b/aliusnes/src/bus/dma.rs @@ -71,17 +71,17 @@ impl Dma { 5 => vec![0, 1, 0, 1], _ => unreachable!(), }; - // log::warn!("Channel {index} will transfer {count} Bytes"); + for i in 0..count { let bank = bus.dma.channels[index].a_bank_or_h_table_bank; let offset = bus.dma.channels[index].a_addr_or_h_table_addr; let a_addr = Address::new(offset, bank); let byte = channel.b_addr.wrapping_add(pattern[i % pattern.len()]); - //WRAM to WRAM is invalid + // WRAM to WRAM is invalid if byte == 0x80 - && ((u32::from(a_addr) & 0x00FE_0000) == 0x007E_0000 - || (u32::from(a_addr) & 0x0040_E000) == 0) + && ((u32::from(a_addr) & 0xFE_0000) == 0x7E_0000 + || (u32::from(a_addr) & 0x40_E000) == 0) { continue; } diff --git a/aliusnes/src/bus/system_bus.rs b/aliusnes/src/bus/system_bus.rs index a03fbc0..e796492 100644 --- a/aliusnes/src/bus/system_bus.rs +++ b/aliusnes/src/bus/system_bus.rs @@ -62,42 +62,59 @@ impl SystemBus { 0x80 => self.wram.read(addr), _ => None, } { - self.mdr = val; + val } else { - self.mdr = 0; + self.mdr } - self.mdr + } + + fn peek(&self, addr: Address) -> Option { + let bank = addr.bank; + let page = addr.offset; + + match bank { + 0x00..=0x3F | 0x80..=0xBF => match page.high_byte() { + 0x00..=0x1F => return Some(self.wram.ram[page as usize & 0x1FFF]), + 0x21 => {} + 0x40..=0x43 => {} + _ => {} + }, + 0x7E..=0x7F => return Some(self.wram.ram[u32::from(addr) as usize & 0x1_FFFF]), + _ => {} + } + self.cart.read(bank, page) } pub fn read(&mut self, addr: Address) -> u8 { let bank = addr.bank; let page = addr.offset; + if DMA && (bank & 0x40) == 0 && matches!(page.high_byte(), 0x21 | 0x40 | 0x42 | 0x43) { + self.mdr = 0; + return self.mdr; + } + if let Some(val) = match bank { 0x00..=0x3F | 0x80..=0xBF => match page.high_byte() { 0x00..=0x1F => Some(self.wram.ram[page as usize & 0x1FFF]), - 0x21 => return self.read_b(page), + 0x21 => Some(self.read_b(page)), 0x40..=0x43 => { - if DMA { - Some(0) - } else { - match page { - 0x4210 => Some(self.ppu.read_nmi_flag() | (self.mdr & 0x70)), - 0x4211 => Some(self.ppu.read_irq_flag() | (self.mdr & 0x7F)), - 0x4212 => { - let joypad_autoread_status = false; // todo - Some( - self.ppu.read_hv_status() - | u8::from(joypad_autoread_status) - | (self.mdr & 0x3E), - ) - } - 0x4214..=0x4217 => self.math.read(page), - 0x4300..=0x437F => self.dma.read(page), - _ => { - println!("Tried to read at {page:#0x}"); - None - } + match page { + 0x4210 => Some(self.ppu.read_nmi_flag() | (self.mdr & 0x70)), + 0x4211 => Some(self.ppu.read_irq_flag() | (self.mdr & 0x7F)), + 0x4212 => { + let joypad_autoread_status = false; // todo + Some( + self.ppu.read_hv_status() + | u8::from(joypad_autoread_status) + | (self.mdr & 0x3E), + ) + } + 0x4214..=0x4217 => self.math.read(page), + 0x4300..=0x437F => self.dma.read(page), + _ => { + println!("Tried to read at {page:#0x}"); + None } } } @@ -190,8 +207,8 @@ impl SystemBus { } impl Bus for SystemBus { - fn peek_at(&self, _addr: Address) -> Option { - todo!() + fn peek_at(&self, addr: Address) -> Option { + self.peek(addr) } fn read_and_tick(&mut self, addr: Address) -> u8 { From 9ce712d122ae1a81cf92bd06299431ebebe3b14c Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:38:20 +0200 Subject: [PATCH 09/18] log: trace instructions --- aliusnes/src/emu.rs | 1 + aliusnes/src/w65c816/mod.rs | 40 ++++++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/aliusnes/src/emu.rs b/aliusnes/src/emu.rs index 65b5f63..b4f047d 100644 --- a/aliusnes/src/emu.rs +++ b/aliusnes/src/emu.rs @@ -80,6 +80,7 @@ fn init_log() { log::LevelFilter::Trace, ConfigBuilder::new() .set_time_level(log::LevelFilter::Off) + .set_location_level(log::LevelFilter::Off) .build(), std::fs::File::create("cpu_trace.log").unwrap(), ), diff --git a/aliusnes/src/w65c816/mod.rs b/aliusnes/src/w65c816/mod.rs index 992ddd7..cd9b08e 100644 --- a/aliusnes/src/w65c816/mod.rs +++ b/aliusnes/src/w65c816/mod.rs @@ -75,25 +75,33 @@ impl W65C816 { } let op = self.cpu.get_imm::(bus); + let opcode = &self.instruction_set[op as usize]; + let instr = opcode.function; + let address_mode = opcode.meta.mode; #[cfg(feature = "log")] - log::trace!( - "{} {:02X} {:02x}:{:04x} A:{:04x} X:{:04x} Y:{:04x}, S:{:04x}, D:{:04x}, DB:{:02x}, P:{:08b}", - opcode.meta.mnemonic, - opcode.meta.code, - self.cpu.dbr, - (self.cpu.program_counter - 1), - self.cpu.accumulator, - self.cpu.index_x, - self.cpu.index_y, - self.cpu.stack_pointer, - self.cpu.dpr, - self.cpu.dbr, - self.cpu.status.0, - ); - let instr = opcode.function; - instr(&mut self.cpu, bus, opcode.meta.mode); + { + use crate::w65c816::addressing::Address; + let disasm = address_mode + .disasm_operand(bus, Address::new(self.cpu.program_counter, self.cpu.pbr)); + + log::trace!( + "{} {:<10} {:02x}:{:04x} A:{:04x} X:{:04x} Y:{:04x}, S:{:04x}, D:{:04x}, DB:{:02x}, P:{:08b}", + opcode.meta.mnemonic, + disasm, + self.cpu.dbr, + (self.cpu.program_counter - 1), + self.cpu.accumulator, + self.cpu.index_x, + self.cpu.index_y, + self.cpu.stack_pointer, + self.cpu.dpr, + self.cpu.dbr, + self.cpu.status.0, + ); + } + instr(&mut self.cpu, bus, address_mode); } pub fn peek_opcode(&self, bus: &B) -> Meta { From 4bda74263ea00f25e574276f965cd5e0661c5738 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:41:03 +0200 Subject: [PATCH 10/18] w65c816: cleanup jump instructions --- aliusnes/src/w65c816/addressing.rs | 34 +++++++++++----------------- aliusnes/src/w65c816/cpu.rs | 20 ++++++++++++++++ aliusnes/src/w65c816/functions.rs | 2 +- aliusnes/src/w65c816/instructions.rs | 32 ++++++-------------------- aliusnes/src/w65c816/mod.rs | 8 +++---- aliusnes/src/w65c816/opcode.rs | 6 +---- 6 files changed, 46 insertions(+), 56 deletions(-) diff --git a/aliusnes/src/w65c816/addressing.rs b/aliusnes/src/w65c816/addressing.rs index 4339201..b35069f 100644 --- a/aliusnes/src/w65c816/addressing.rs +++ b/aliusnes/src/w65c816/addressing.rs @@ -1,7 +1,7 @@ use super::{cpu::Cpu, regsize::RegSize}; use crate::{bus::Bus, utils::int_traits::ManipulateU16}; -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug)] pub enum AddressingMode { Accumulator, Implied, @@ -24,8 +24,6 @@ pub enum AddressingMode { AbsoluteIndirect, AbsoluteIndirectLong, AbsoluteIndirectX, - AbsoluteJMP, - AbsoluteLongJSL, StackRelative, StackRelIndirectY, StackPEI, @@ -221,7 +219,10 @@ impl Cpu { AddressingMode::AbsoluteLongX => self .absolute_long_address(bus) .wrapping_add(u32::from(self.index_x)), - AddressingMode::AbsoluteIndirect => Address::new(self.get_imm(bus), 0), + AddressingMode::AbsoluteIndirect => { + let indirect = self.get_imm(bus); + Address::new(self.read_bank0(bus, indirect), 0) + } AddressingMode::AbsoluteIndirectX => { bus.add_io_cycles(1); Address::new( @@ -229,6 +230,10 @@ impl Cpu { self.pbr, ) } + AddressingMode::AbsoluteIndirectLong => { + let indirect = self.get_imm(bus); + self.indirect_long_address(bus, indirect) + } AddressingMode::StackRelIndirectY => { bus.add_io_cycles(1); let indirect = self.stack_relative_address(bus); @@ -243,9 +248,6 @@ impl Cpu { | AddressingMode::Direct | AddressingMode::DirectX | AddressingMode::DirectY - | AddressingMode::AbsoluteIndirectLong - | AddressingMode::AbsoluteJMP - | AddressingMode::AbsoluteLongJSL | AddressingMode::StackRelative | AddressingMode::StackPEI | AddressingMode::BlockMove => unreachable!(), @@ -259,11 +261,9 @@ impl Cpu { let offset = self.get_imm::(bus) as i16; T::from_u16(self.program_counter.wrapping_add(offset as u16)) } - AddressingMode::Immediate - | AddressingMode::Relative - | AddressingMode::AbsoluteJMP - | AddressingMode::AbsoluteLongJSL - | AddressingMode::BlockMove => self.get_imm(bus), + AddressingMode::Immediate | AddressingMode::Relative | AddressingMode::BlockMove => { + self.get_imm(bus) + } AddressingMode::Direct | AddressingMode::DirectX | AddressingMode::DirectY @@ -271,15 +271,7 @@ impl Cpu { | AddressingMode::StackPEI => self.read_from_direct_page(bus, mode).0, _ => { let addr = self.decode_addressing_mode::(bus, *mode); - - if let AddressingMode::AbsoluteIndirectX = mode { - T::from_u16(u16::from_le_bytes([ - bus.read_and_tick(addr), - bus.read_and_tick(addr.wrapping_offset_add(1)), - ])) - } else { - self.read(bus, addr) - } + self.read(bus, addr) } } } diff --git a/aliusnes/src/w65c816/cpu.rs b/aliusnes/src/w65c816/cpu.rs index 4481e03..98cd33d 100644 --- a/aliusnes/src/w65c816/cpu.rs +++ b/aliusnes/src/w65c816/cpu.rs @@ -219,4 +219,24 @@ impl Cpu { } } } + + pub fn do_jmp(&mut self, bus: &mut B, mode: AddressingMode) { + let addr = self.decode_addressing_mode::(bus, mode); + + match mode { + AddressingMode::Absolute | AddressingMode::AbsoluteIndirect => { + self.program_counter = addr.offset; + } + AddressingMode::AbsoluteLong | AddressingMode::AbsoluteIndirectLong => { + self.program_counter = addr.offset; + self.pbr = addr.bank; + } + AddressingMode::AbsoluteIndirectX => { + self.program_counter.set_low_byte(bus.read_and_tick(addr)); + self.program_counter + .set_high_byte(bus.read_and_tick(addr.wrapping_offset_add(1))); + } + _ => unreachable!(), + } + } } diff --git a/aliusnes/src/w65c816/functions.rs b/aliusnes/src/w65c816/functions.rs index dd85c7b..9843bb1 100644 --- a/aliusnes/src/w65c816/functions.rs +++ b/aliusnes/src/w65c816/functions.rs @@ -95,7 +95,7 @@ pub(super) fn do_asl(cpu: &mut Cpu, operand: T) -> T { pub(super) fn do_bit(cpu: &mut Cpu, operand: T, mode: AddressingMode) { let result = T::from_u16(cpu.accumulator) & operand; - if mode == AddressingMode::Immediate { + if let AddressingMode::Immediate = mode { cpu.status.set_zero(result.is_zero()); } else { cpu.status.set_negative(operand.is_negative()); diff --git a/aliusnes/src/w65c816/instructions.rs b/aliusnes/src/w65c816/instructions.rs index ca0b55d..dbc5438 100644 --- a/aliusnes/src/w65c816/instructions.rs +++ b/aliusnes/src/w65c816/instructions.rs @@ -234,43 +234,25 @@ impl super::W65C816 { } pub fn jml(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { - let addr = cpu.get_operand::(bus, &mode); - let pc = cpu.read_bank0(bus, addr); - let pbr = bus.read_and_tick(addr.wrapping_add(2).into()); - cpu.program_counter = pc; - cpu.pbr = pbr; + cpu.do_jmp(bus, mode); } pub fn jmp(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { - match mode { - AddressingMode::AbsoluteLong => { - let new_pc = cpu.get_operand::(bus, &AddressingMode::AbsoluteJMP); - let new_pbr = cpu.get_operand::(bus, &AddressingMode::AbsoluteJMP); - cpu.program_counter = new_pc; - cpu.pbr = new_pbr; - } - _ => { - cpu.program_counter = cpu.get_operand::(bus, &mode); - } - } + cpu.do_jmp(bus, mode); } pub fn jsl(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { bus.add_io_cycles(1); - let new_pc = cpu.get_operand::(bus, &mode); do_push(cpu, bus, cpu.pbr); - let new_pbr = cpu.get_operand::(bus, &mode); - do_push(cpu, bus, cpu.program_counter.wrapping_sub(1)); - cpu.program_counter = new_pc; - cpu.pbr = new_pbr; + do_push(cpu, bus, cpu.program_counter.wrapping_add(2)); + cpu.do_jmp(bus, mode); } pub fn jsr(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { - let new_pc = cpu.get_operand::(bus, &mode); - do_push(cpu, bus, cpu.program_counter.wrapping_sub(1)); - cpu.program_counter = new_pc; + do_push(cpu, bus, cpu.program_counter.wrapping_add(1)); + cpu.do_jmp(bus, mode); - if let AddressingMode::AbsoluteJMP = mode { + if let AddressingMode::Absolute = mode { bus.add_io_cycles(1); } } diff --git a/aliusnes/src/w65c816/mod.rs b/aliusnes/src/w65c816/mod.rs index cd9b08e..2d98144 100644 --- a/aliusnes/src/w65c816/mod.rs +++ b/aliusnes/src/w65c816/mod.rs @@ -146,9 +146,9 @@ pub const fn opcode_table() -> [OpCode; 256] { OpCode::new(Meta::new(0x1d, "ORA", AbsoluteX), W65C816::ora), OpCode::new(Meta::new(0x1e, "ASL", AbsoluteX), W65C816::asl), OpCode::new(Meta::new(0x1f, "ORA", AbsoluteLongX), W65C816::ora), - OpCode::new(Meta::new(0x20, "JSR", AbsoluteJMP), W65C816::jsr), + OpCode::new(Meta::new(0x20, "JSR", Absolute), W65C816::jsr), OpCode::new(Meta::new(0x21, "AND", IndirectX), W65C816::and), - OpCode::new(Meta::new(0x22, "JSL", AbsoluteLongJSL), W65C816::jsl), + OpCode::new(Meta::new(0x22, "JSL", AbsoluteLong), W65C816::jsl), OpCode::new(Meta::new(0x23, "AND", StackRelative), W65C816::and), OpCode::new(Meta::new(0x24, "BIT", Direct), W65C816::bit), OpCode::new(Meta::new(0x25, "AND", Direct), W65C816::and), @@ -190,7 +190,7 @@ pub const fn opcode_table() -> [OpCode; 256] { OpCode::new(Meta::new(0x49, "EOR", Immediate), W65C816::eor), OpCode::new(Meta::new(0x4a, "LSR", Accumulator), W65C816::lsr), OpCode::new(Meta::new(0x4b, "PHK", Implied), W65C816::phk), - OpCode::new(Meta::new(0x4c, "JMP", AbsoluteJMP), W65C816::jmp), + OpCode::new(Meta::new(0x4c, "JMP", Absolute), W65C816::jmp), OpCode::new(Meta::new(0x4d, "EOR", Absolute), W65C816::eor), OpCode::new(Meta::new(0x4e, "LSR", Absolute), W65C816::lsr), OpCode::new(Meta::new(0x4f, "EOR", AbsoluteLong), W65C816::eor), @@ -334,7 +334,7 @@ pub const fn opcode_table() -> [OpCode; 256] { OpCode::new(Meta::new(0xd9, "CMP", AbsoluteY), W65C816::cmp), OpCode::new(Meta::new(0xda, "PHX", Implied), W65C816::phx), OpCode::new(Meta::new(0xdb, "STP", Implied), W65C816::stp), - OpCode::new(Meta::new(0xdc, "JML", AbsoluteJMP), W65C816::jml), + OpCode::new(Meta::new(0xdc, "JML", AbsoluteIndirectLong), W65C816::jml), OpCode::new(Meta::new(0xdd, "CMP", AbsoluteX), W65C816::cmp), OpCode::new(Meta::new(0xde, "DEC", AbsoluteX), W65C816::dec), OpCode::new(Meta::new(0xdf, "CMP", AbsoluteLongX), W65C816::cmp), diff --git a/aliusnes/src/w65c816/opcode.rs b/aliusnes/src/w65c816/opcode.rs index 3f67b5a..e873922 100644 --- a/aliusnes/src/w65c816/opcode.rs +++ b/aliusnes/src/w65c816/opcode.rs @@ -27,8 +27,6 @@ impl AddressingMode { AddressingMode::AbsoluteIndirect => 2, AddressingMode::AbsoluteIndirectLong => 2, AddressingMode::AbsoluteIndirectX => 2, - AddressingMode::AbsoluteJMP => 2, - AddressingMode::AbsoluteLongJSL => 2, AddressingMode::StackRelative => 1, AddressingMode::StackRelIndirectY => 1, AddressingMode::StackPEI => 1, @@ -73,10 +71,8 @@ impl AddressingMode { AddressingMode::AbsoluteLong => format!("${:06X}", operand), AddressingMode::AbsoluteLongX => format!("${:06X},X", operand), AddressingMode::AbsoluteIndirect => format!("(${:04X})", operand), - AddressingMode::AbsoluteIndirectLong => todo!(), + AddressingMode::AbsoluteIndirectLong => format!("[${:04X}]", operand), AddressingMode::AbsoluteIndirectX => format!("(${:04X},X)", operand), - AddressingMode::AbsoluteJMP => format!("${:04X}", operand), - AddressingMode::AbsoluteLongJSL => format!("${:06X}", operand), AddressingMode::StackRelative => format!("${:02X},S", operand), AddressingMode::StackRelIndirectY => format!("(${:02X},S),Y", operand), AddressingMode::StackPEI => format!("(${:02X})", operand), From 6580575d576ddbd34daafce48911931c31f05703 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Thu, 17 Jul 2025 12:32:06 +0200 Subject: [PATCH 11/18] small cleanup --- aliusnes/src/bus/system_bus.rs | 4 ++-- aliusnes/src/ppu/counters.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aliusnes/src/bus/system_bus.rs b/aliusnes/src/bus/system_bus.rs index e796492..2491d1f 100644 --- a/aliusnes/src/bus/system_bus.rs +++ b/aliusnes/src/bus/system_bus.rs @@ -75,8 +75,6 @@ impl SystemBus { match bank { 0x00..=0x3F | 0x80..=0xBF => match page.high_byte() { 0x00..=0x1F => return Some(self.wram.ram[page as usize & 0x1FFF]), - 0x21 => {} - 0x40..=0x43 => {} _ => {} }, 0x7E..=0x7F => return Some(self.wram.ram[u32::from(addr) as usize & 0x1_FFFF]), @@ -111,6 +109,8 @@ impl SystemBus { ) } 0x4214..=0x4217 => self.math.read(page), + // TODO joypad registers + 0x4218..=0x421F => Some(0), 0x4300..=0x437F => self.dma.read(page), _ => { println!("Tried to read at {page:#0x}"); diff --git a/aliusnes/src/ppu/counters.rs b/aliusnes/src/ppu/counters.rs index 9665d66..52988e5 100644 --- a/aliusnes/src/ppu/counters.rs +++ b/aliusnes/src/ppu/counters.rs @@ -272,7 +272,7 @@ impl Ppu { result } - pub fn read_hv_status(&mut self) -> u8 { + pub fn read_hv_status(&self) -> u8 { self.counters.hv_status.0 } From e8f767fc4cd5ba7d5c34e66f17a85377ebf999af Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:09:00 +0200 Subject: [PATCH 12/18] cart: fix LoRom read --- aliusnes/src/cart.rs | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/aliusnes/src/cart.rs b/aliusnes/src/cart.rs index c97fc30..172a6ec 100644 --- a/aliusnes/src/cart.rs +++ b/aliusnes/src/cart.rs @@ -9,11 +9,15 @@ pub struct Cart { pub model: Model, rom: Vec, ram: Vec, + rom_mask: usize, + ram_mask: usize, } impl Cart { pub fn new(header: Header, rom: &[u8], ram: Vec) -> Self { Cart { + rom_mask: rom_mask(rom.len()), + ram_mask: (header.ram_size - 1) as usize, model: header.country.to_model(), header, rom: rom.to_vec(), @@ -23,7 +27,7 @@ impl Cart { pub fn read(&self, bank: u8, addr: u16) -> Option { match self.header.mapper { - Mapper::LoROM => self.read_lo_rom(bank, addr), + Mapper::LoROM => self.read_lo_rom(bank.into(), addr.into()), Mapper::HiROM => todo!(), Mapper::SA1ROM => todo!(), Mapper::SDD1ROM => todo!(), @@ -41,26 +45,18 @@ impl Cart { } } - pub fn read_lo_rom(&self, mut bank: u8, addr: u16) -> Option { + pub fn read_lo_rom(&self, mut bank: usize, addr: usize) -> Option { if ((0x70..0x7E).contains(&bank) || bank >= 0xF0) - && addr < 0x8000 + && (self.rom_mask < 0x200000 || addr < 0x8000) && self.header.chipset.has_ram { - return Some( - self.ram[(u32::from(u16::from(bank & 0xF) << 15) | u32::from(addr)) as usize], - ); + return Some(self.ram[(((bank & 0xF) << 15) | addr) & self.ram_mask]); } bank &= 0x7F; if addr >= 0x8000 || bank >= 0x40 { - return Some( - self.rom[(u32::from(u16::from(bank) << 15) | u32::from(addr & 0x7FFF)) as usize], - ); + return Some(self.rom[((bank << 15) | (addr & 0x7FFF)) & self.rom_mask]); } - println!( - "Attempt to read at 0x{:02x}{:04x}", - (u16::from(bank) << 15), - (addr & 0x7FFF) - ); + println!("Attempt to read at 0x{:02x}{:04x}", bank, (addr & 0x7FFF)); None } @@ -73,3 +69,14 @@ impl Cart { } } } + +fn rom_mask(len: usize) -> usize { + let mut mask = 0x8000; + loop { + if len <= mask { + break; + } + mask *= 2; + } + mask - 1 +} From 4fee8edb799b83a7c44ed9ac1dd362966e6dde1c Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Mon, 4 Aug 2025 18:39:55 +0200 Subject: [PATCH 13/18] add trace feature --- aliusnes/Cargo.toml | 1 + aliusnes/src/w65c816/mod.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/aliusnes/Cargo.toml b/aliusnes/Cargo.toml index 8498de4..d1b02bd 100644 --- a/aliusnes/Cargo.toml +++ b/aliusnes/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] log = ["simplelog"] +trace = ["log"] [dependencies] log = "0.4.21" diff --git a/aliusnes/src/w65c816/mod.rs b/aliusnes/src/w65c816/mod.rs index 2d98144..bf7b3d5 100644 --- a/aliusnes/src/w65c816/mod.rs +++ b/aliusnes/src/w65c816/mod.rs @@ -80,7 +80,7 @@ impl W65C816 { let instr = opcode.function; let address_mode = opcode.meta.mode; - #[cfg(feature = "log")] + #[cfg(feature = "trace")] { use crate::w65c816::addressing::Address; let disasm = address_mode From 0217994da7fa3f93a37ff7f6d189fa2ee9917d22 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:51:24 +0200 Subject: [PATCH 14/18] fix interrupt handling --- aliusnes/src/bus/mod.rs | 4 ++-- aliusnes/src/bus/system_bus.rs | 16 +++++++++------- aliusnes/src/ppu.rs | 2 -- aliusnes/src/ppu/counters.rs | 21 ++++++++++++++++----- aliusnes/src/w65c816/mod.rs | 15 +++++++++------ aliusnes/tests/utils/test_bus.rs | 4 ++-- 6 files changed, 38 insertions(+), 24 deletions(-) diff --git a/aliusnes/src/bus/mod.rs b/aliusnes/src/bus/mod.rs index 54256cf..4220d8b 100644 --- a/aliusnes/src/bus/mod.rs +++ b/aliusnes/src/bus/mod.rs @@ -10,8 +10,8 @@ pub trait Bus { fn read_and_tick(&mut self, addr: Address) -> u8; fn write_and_tick(&mut self, addr: Address, data: u8); fn add_io_cycles(&mut self, cycles: usize); - fn fired_nmi(&self) -> bool; - fn fired_irq(&self) -> bool; + fn fired_nmi(&mut self) -> bool; + fn fired_irq(&mut self) -> bool; } pub trait Access { diff --git a/aliusnes/src/bus/system_bus.rs b/aliusnes/src/bus/system_bus.rs index 2491d1f..c59ddc2 100644 --- a/aliusnes/src/bus/system_bus.rs +++ b/aliusnes/src/bus/system_bus.rs @@ -73,13 +73,15 @@ impl SystemBus { let page = addr.offset; match bank { - 0x00..=0x3F | 0x80..=0xBF => match page.high_byte() { - 0x00..=0x1F => return Some(self.wram.ram[page as usize & 0x1FFF]), - _ => {} - }, + 0x00..=0x3F | 0x80..=0xBF => { + if let 0x00..=0x1F = page.high_byte() { + return Some(self.wram.ram[page as usize & 0x1FFF]); + } + } 0x7E..=0x7F => return Some(self.wram.ram[u32::from(addr) as usize & 0x1_FFFF]), _ => {} } + self.cart.read(bank, page) } @@ -225,11 +227,11 @@ impl Bus for SystemBus { self.cycles += cycles * 6; } - fn fired_nmi(&self) -> bool { - self.ppu.nmi_requested + fn fired_nmi(&mut self) -> bool { + self.ppu.nmi_requested() } - fn fired_irq(&self) -> bool { + fn fired_irq(&mut self) -> bool { self.ppu.is_in_irq() } } diff --git a/aliusnes/src/ppu.rs b/aliusnes/src/ppu.rs index 3ac7e57..104253c 100644 --- a/aliusnes/src/ppu.rs +++ b/aliusnes/src/ppu.rs @@ -63,7 +63,6 @@ pub struct Ppu { pub screen_width: usize, pub screen_height: usize, pub frame_buffer: Box<[[u8; 3]; WIDTH * PAL_HEIGHT]>, - pub nmi_requested: bool, } impl Ppu { @@ -89,7 +88,6 @@ impl Ppu { NTSC_HEIGHT }, frame_buffer: Box::new([[0; 3]; WIDTH * PAL_HEIGHT]), - nmi_requested: false, } } diff --git a/aliusnes/src/ppu/counters.rs b/aliusnes/src/ppu/counters.rs index 52988e5..cc6c875 100644 --- a/aliusnes/src/ppu/counters.rs +++ b/aliusnes/src/ppu/counters.rs @@ -59,6 +59,7 @@ pub struct Counters { stat78: Stat78, hv_status: HvStatus, in_irq: bool, + nmi_requested: bool, pub frame_ready: bool, pub last_scanline: usize, @@ -93,6 +94,7 @@ impl Counters { stat78, hv_status: HvStatus(0), in_irq: false, + nmi_requested: false, frame_ready: false, last_scanline: 0, #[cfg(feature = "log")] @@ -204,6 +206,10 @@ impl Counters { self.hv_status.set_in_vblank(true); self.rdnmi.set_in_nmi(true); self.frame_ready = true; + + if self.nmitimen.nmi_enabled() { + self.nmi_requested = true; + } } } self.check_counters_timer_hit(); @@ -259,9 +265,6 @@ impl Ppu { pub fn write_nmitien(&mut self, val: u8) { let nmitimen = Nmitimen(val); let _joypad_enable = nmitimen.joypad_enable(); // todo when implementing joypad - self.nmi_requested = !self.counters.nmitimen.nmi_enabled() - && nmitimen.nmi_enabled() - && self.counters.rdnmi.in_nmi(); self.counters.nmitimen = nmitimen; self.counters.check_counters_timer_hit(); } @@ -288,7 +291,15 @@ impl Ppu { self.ppu2_mdr } - pub fn is_in_irq(&self) -> bool { - self.counters.in_irq + pub fn nmi_requested(&mut self) -> bool { + let nmi_requested = self.counters.nmi_requested; + self.counters.nmi_requested = false; + nmi_requested + } + + pub fn is_in_irq(&mut self) -> bool { + let in_irq = self.counters.in_irq; + self.counters.in_irq = false; + in_irq } } diff --git a/aliusnes/src/w65c816/mod.rs b/aliusnes/src/w65c816/mod.rs index bf7b3d5..ea66283 100644 --- a/aliusnes/src/w65c816/mod.rs +++ b/aliusnes/src/w65c816/mod.rs @@ -62,13 +62,16 @@ impl W65C816 { if self.cpu.stopped { return; } - if self.cpu.waiting_interrupt { - if bus.fired_nmi() { - self.cpu.waiting_interrupt = false; - self.cpu.handle_interrupt(bus, Vector::Nmi); - } else if !self.cpu.status.irq_disable() && bus.fired_irq() { + + if bus.fired_nmi() { + self.cpu.waiting_interrupt = false; + self.cpu.handle_interrupt(bus, Vector::Nmi); + } else if !self.cpu.status.irq_disable() && bus.fired_irq() { + self.cpu.waiting_interrupt = false; + self.cpu.handle_interrupt(bus, Vector::Irq); + } else if self.cpu.waiting_interrupt { + if self.cpu.status.irq_disable() { self.cpu.waiting_interrupt = false; - self.cpu.handle_interrupt(bus, Vector::Irq); } else { return; } diff --git a/aliusnes/tests/utils/test_bus.rs b/aliusnes/tests/utils/test_bus.rs index 2cd8984..aedea18 100644 --- a/aliusnes/tests/utils/test_bus.rs +++ b/aliusnes/tests/utils/test_bus.rs @@ -45,11 +45,11 @@ impl Bus for TomHarteBus { } } - fn fired_nmi(&self) -> bool { + fn fired_nmi(&mut self) -> bool { false } - fn fired_irq(&self) -> bool { + fn fired_irq(&mut self) -> bool { false } } From 2b1f9a0fa58d9968750ea7784f6a353e21b2739b Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Mon, 18 Aug 2025 18:01:20 +0200 Subject: [PATCH 15/18] add support to hirom carts and mode 6, needed for some blargg test roms --- aliusnes/src/bus/system_bus.rs | 6 +++--- aliusnes/src/cart.rs | 37 +++++++++++++++++++++++++--------- aliusnes/src/cart/header.rs | 12 +++++------ aliusnes/src/ppu/render.rs | 4 ++++ 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/aliusnes/src/bus/system_bus.rs b/aliusnes/src/bus/system_bus.rs index c59ddc2..22f1d49 100644 --- a/aliusnes/src/bus/system_bus.rs +++ b/aliusnes/src/bus/system_bus.rs @@ -82,7 +82,7 @@ impl SystemBus { _ => {} } - self.cart.read(bank, page) + self.cart.read(bank.into(), page.into()) } pub fn read(&mut self, addr: Address) -> u8 { @@ -130,7 +130,7 @@ impl SystemBus { return self.mdr; } - if let Some(val) = self.cart.read(bank, page) { + if let Some(val) = self.cart.read(bank.into(), page.into()) { self.mdr = val; } self.mdr @@ -175,7 +175,7 @@ impl SystemBus { 0x7E..=0x7F => return self.wram.ram[u32::from(addr) as usize & 0x1_FFFF] = data, _ => {} } - self.cart.write(bank, page, data); + self.cart.write(bank.into(), page.into(), data); } pub fn memory_access_cycles(&self, addr: &Address) -> u32 { diff --git a/aliusnes/src/cart.rs b/aliusnes/src/cart.rs index 172a6ec..d08a5ab 100644 --- a/aliusnes/src/cart.rs +++ b/aliusnes/src/cart.rs @@ -25,27 +25,27 @@ impl Cart { } } - pub fn read(&self, bank: u8, addr: u16) -> Option { + pub fn read(&self, bank: usize, addr: usize) -> Option { match self.header.mapper { - Mapper::LoROM => self.read_lo_rom(bank.into(), addr.into()), - Mapper::HiROM => todo!(), + Mapper::LoROM => self.read_lorom(bank, addr), + Mapper::HiROM => self.read_hirom(bank, addr), Mapper::SA1ROM => todo!(), Mapper::SDD1ROM => todo!(), Mapper::ExHiROM => todo!(), } } - pub fn write(&mut self, bank: u8, addr: u16, val: u8) { + pub fn write(&mut self, bank: usize, addr: usize, val: u8) { match self.header.mapper { - Mapper::LoROM => self.write_lo_rom(bank, addr, val), - Mapper::HiROM => todo!(), + Mapper::LoROM => self.write_lorom(bank, addr, val), + Mapper::HiROM => self.write_hirom(bank, addr, val), Mapper::SA1ROM => todo!(), Mapper::SDD1ROM => todo!(), Mapper::ExHiROM => todo!(), } } - pub fn read_lo_rom(&self, mut bank: usize, addr: usize) -> Option { + pub fn read_lorom(&self, mut bank: usize, addr: usize) -> Option { if ((0x70..0x7E).contains(&bank) || bank >= 0xF0) && (self.rom_mask < 0x200000 || addr < 0x8000) && self.header.chipset.has_ram @@ -60,12 +60,31 @@ impl Cart { None } - pub fn write_lo_rom(&mut self, bank: u8, addr: u16, val: u8) { + pub fn write_lorom(&mut self, bank: usize, addr: usize, val: u8) { if ((0x70..0x7E).contains(&bank) || bank >= 0xF0) && addr < 0x8000 && self.header.chipset.has_ram { - self.ram[(u32::from(u16::from(bank & 0xF) << 15) | u32::from(addr)) as usize] = val; + self.ram[(((bank & 0xF) << 15) | addr) & self.ram_mask] = val; + } + } + + pub fn read_hirom(&self, mut bank: usize, addr: usize) -> Option { + bank &= 0x7F; + if bank < 0x40 && (0x6000..0x8000).contains(&addr) && self.header.chipset.has_ram { + return Some(self.ram[(((bank & 0x3F) << 13) | (addr & 0x1FFF)) & (self.ram_mask)]); + } + if addr >= 0x8000 || bank >= 0x40 { + return Some(self.rom[(((bank & 0x3F) << 16) | addr) & (self.rom_mask)]); + } + println!("Attempt to read at 0x{:02x}{:04x}", bank, (addr & 0x7FFF)); + None + } + + pub fn write_hirom(&mut self, mut bank: usize, addr: usize, val: u8) { + bank &= 0x7F; + if bank < 0x40 && (0x6000..0x8000).contains(&addr) && self.header.chipset.has_ram { + self.ram[(((bank & 0x3F) << 13) | (addr & 0x1FFF)) & self.ram_mask] = val; } } } diff --git a/aliusnes/src/cart/header.rs b/aliusnes/src/cart/header.rs index 05fd08e..bea28f0 100644 --- a/aliusnes/src/cart/header.rs +++ b/aliusnes/src/cart/header.rs @@ -94,16 +94,14 @@ impl Header { } pub fn guess_from_rom(rom: &[u8]) -> Option { - let header = rom[..] - .get(0x7FB0..0x8000) - .and_then(|header_bytes| Header::new(header_bytes, Mapper::LoROM)) + rom[..] + .get(0x40_FFB0..0x41_0000) + .and_then(|header_bytes| Header::new(header_bytes, Mapper::ExHiROM)) .or_else(|| { rom[..] - .get(0xFFB0..0x10000) + .get(0xFFB0..0x1_0000) .and_then(|header_bytes| Header::new(header_bytes, Mapper::HiROM)) }) - .or_else(|| Header::new(&rom[0x0040_FFB0..0x0041_0000], Mapper::ExHiROM))?; - - Some(header) + .or_else(|| Header::new(&rom[0x7FB0..0x8000], Mapper::LoROM)) } } diff --git a/aliusnes/src/ppu/render.rs b/aliusnes/src/ppu/render.rs index eb33050..6a4bd11 100644 --- a/aliusnes/src/ppu/render.rs +++ b/aliusnes/src/ppu/render.rs @@ -112,6 +112,10 @@ impl Ppu { self.draw_background::(screen_y, BG2, &mut (*bg_data)[BG2 as usize]); &[S3, H1, S2, H2, S1, L1, S0, L2] } + 6 => { + self.draw_background::(screen_y, BG1, &mut (*bg_data)[BG1 as usize]); + &[S3, H1, S2, S1, L1, S0] + } _ => unimplemented!("mode {}", self.background.bg_mode.bg_mode()), } } From af1a59074299a5c44317c89edb9da977c31d86a2 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:40:54 +0200 Subject: [PATCH 16/18] add a scheduler --- aliusnes/src/bus/dma.rs | 2 +- aliusnes/src/bus/math.rs | 2 +- aliusnes/src/bus/mod.rs | 2 +- aliusnes/src/bus/system_bus.rs | 36 ++++++-------- aliusnes/src/bus/wram.rs | 2 +- aliusnes/src/emu.rs | 50 +++++++++++++++++-- aliusnes/src/lib.rs | 1 + aliusnes/src/ppu.rs | 65 ++++++++++++++++++------ aliusnes/src/ppu/counters.rs | 84 +++++++++---------------------- aliusnes/src/scheduler.rs | 91 ++++++++++++++++++++++++++++++++++ frontend/src/emu_state.rs | 19 +++---- 11 files changed, 236 insertions(+), 118 deletions(-) create mode 100644 aliusnes/src/scheduler.rs diff --git a/aliusnes/src/bus/dma.rs b/aliusnes/src/bus/dma.rs index b44f022..0d4d610 100644 --- a/aliusnes/src/bus/dma.rs +++ b/aliusnes/src/bus/dma.rs @@ -110,7 +110,7 @@ impl Dma { } impl Access for Dma { - fn read(&mut self, addr: u16) -> Option { + fn read(&mut self, addr: u16, _: u64) -> Option { let channel = self.channels[((addr >> 4) & 7) as usize]; match addr & 0xF { 0x0 => Some(channel.parameters.0), diff --git a/aliusnes/src/bus/math.rs b/aliusnes/src/bus/math.rs index 0ad1106..b7b4db1 100644 --- a/aliusnes/src/bus/math.rs +++ b/aliusnes/src/bus/math.rs @@ -40,7 +40,7 @@ impl Math { } impl Access for Math { - fn read(&mut self, addr: u16) -> Option { + fn read(&mut self, addr: u16, _: u64) -> Option { match addr { 0x4214 => Some(self.quotient.low_byte()), 0x4215 => Some(self.quotient.high_byte()), diff --git a/aliusnes/src/bus/mod.rs b/aliusnes/src/bus/mod.rs index 4220d8b..5b3ba3a 100644 --- a/aliusnes/src/bus/mod.rs +++ b/aliusnes/src/bus/mod.rs @@ -15,6 +15,6 @@ pub trait Bus { } pub trait Access { - fn read(&mut self, addr: u16) -> Option; + fn read(&mut self, addr: u16, time: u64) -> Option; fn write(&mut self, addr: u16, data: u8); } diff --git a/aliusnes/src/bus/system_bus.rs b/aliusnes/src/bus/system_bus.rs index 22f1d49..336d0ea 100644 --- a/aliusnes/src/bus/system_bus.rs +++ b/aliusnes/src/bus/system_bus.rs @@ -1,15 +1,18 @@ -use crate::{cart::Cart, ppu::Ppu, utils::int_traits::ManipulateU16, w65c816::addressing::Address}; +use crate::{ + cart::Cart, ppu::Ppu, scheduler::Scheduler, utils::int_traits::ManipulateU16, + w65c816::addressing::Address, +}; use super::{dma::Dma, math::Math, wram::Wram, Access, Bus}; pub struct SystemBus { mdr: u8, fast_rom_enabled: bool, - cycles: usize, cart: Cart, pub dma: Dma, math: Math, pub ppu: Ppu, + pub scheduler: Scheduler, wram: Wram, dummy_apu: [u8; 4], } @@ -19,8 +22,8 @@ impl SystemBus { Self { mdr: 0, fast_rom_enabled: false, - cycles: 0, ppu: Ppu::new(cart.model), + scheduler: Scheduler::new(), cart, dma: Dma::new(), math: Math::new(), @@ -29,24 +32,13 @@ impl SystemBus { } } - pub fn tick(&mut self) { - //todo apu, joypad, hdma - - let ticks = self.cycles; - self.cycles = 0; - - for _ in 0..ticks { - self.ppu.tick(); - } - } - pub fn add_cycles(&mut self, cycles: usize) { - self.cycles += cycles; + self.scheduler.tick(cycles as u64); } pub fn read_b(&mut self, addr: u16) -> u8 { if let Some(val) = match addr.low_byte() { - 0x34..=0x3F => self.ppu.read(addr), + 0x34..=0x3F => self.ppu.read(addr, self.scheduler.cycles), 0x40..=0x43 => { let ch = ((addr - 0x2140) % 4) as usize; @@ -59,7 +51,7 @@ impl SystemBus { Some(value) } - 0x80 => self.wram.read(addr), + 0x80 => self.wram.read(addr, 0), _ => None, } { val @@ -110,10 +102,10 @@ impl SystemBus { | (self.mdr & 0x3E), ) } - 0x4214..=0x4217 => self.math.read(page), + 0x4214..=0x4217 => self.math.read(page, 0), // TODO joypad registers 0x4218..=0x421F => Some(0), - 0x4300..=0x437F => self.dma.read(page), + 0x4300..=0x437F => self.dma.read(page, 0), _ => { println!("Tried to read at {page:#0x}"); None @@ -214,17 +206,17 @@ impl Bus for SystemBus { } fn read_and_tick(&mut self, addr: Address) -> u8 { - self.cycles += self.memory_access_cycles(&addr) as usize; + self.scheduler.tick(self.memory_access_cycles(&addr) as u64); self.read::(addr) } fn write_and_tick(&mut self, addr: Address, data: u8) { - self.cycles += self.memory_access_cycles(&addr) as usize; + self.scheduler.tick(self.memory_access_cycles(&addr) as u64); self.write::(addr, data); } fn add_io_cycles(&mut self, cycles: usize) { - self.cycles += cycles * 6; + self.scheduler.tick((cycles * 6) as u64); } fn fired_nmi(&mut self) -> bool { diff --git a/aliusnes/src/bus/wram.rs b/aliusnes/src/bus/wram.rs index 5a12238..d991c29 100644 --- a/aliusnes/src/bus/wram.rs +++ b/aliusnes/src/bus/wram.rs @@ -38,7 +38,7 @@ impl Wram { } impl Access for Wram { - fn read(&mut self, _addr: u16) -> Option { + fn read(&mut self, _addr: u16, _: u64) -> Option { let data = self.ram[usize::from(self.wm_addr)]; let raw_addr = (u32::from(self.wm_addr) + 1) & 0x1_FFFF; self.wm_addr = Address::from(raw_addr); diff --git a/aliusnes/src/emu.rs b/aliusnes/src/emu.rs index b4f047d..5fabe91 100644 --- a/aliusnes/src/emu.rs +++ b/aliusnes/src/emu.rs @@ -1,6 +1,7 @@ use crate::{ bus::{dma::Dma, system_bus::SystemBus}, cart::Cart, + scheduler::{Event, PpuEvent}, w65c816::W65C816, }; @@ -19,6 +20,9 @@ impl Emu { w65c816: W65C816::new(), }; emu.reset(); + emu.bus + .scheduler + .add_event(Event::Ppu(PpuEvent::NewScanline), 0); emu } @@ -30,24 +34,60 @@ impl Emu { let Emu { ref mut bus, ref mut w65c816, + .. } = self; if bus.dma.enable_channels > 0 { Dma::do_dma(bus); } - w65c816.step(bus); - bus.tick(); + + while let Some((event, time)) = bus.scheduler.pop_event() { + match event { + Event::Ppu(ppu_event) => bus.ppu.handle_event(&mut bus.scheduler, ppu_event, time), + } + } + } + + pub fn run_cpu_until_next_event(&mut self) { + let Emu { + ref mut bus, + ref mut w65c816, + .. + } = self; + + while bus.scheduler.waiting_for_next_event() { + if bus.dma.enable_channels > 0 { + Dma::do_dma(bus); + } + w65c816.step(bus); + } + } + + pub fn run_frame(&mut self) { + while !self.frame_ready() { + self.run_cpu_until_next_event(); + while let Some((event, time)) = self.bus.scheduler.pop_event() { + match event { + Event::Ppu(ppu_event) => { + self.bus + .ppu + .handle_event(&mut self.bus.scheduler, ppu_event, time) + } + } + } + } + self.bus.ppu.frame_ready = false; } pub fn run_for_frames(&mut self, frames: u64) { - while self.bus.ppu.frame_counter() <= frames { - self.step(); + for _ in 0..frames { + self.run_frame(); } } pub fn frame_ready(&self) -> bool { - self.bus.ppu.frame_ready() + self.bus.ppu.frame_ready } pub fn frame_width(&self) -> usize { diff --git a/aliusnes/src/lib.rs b/aliusnes/src/lib.rs index db11ac2..2e5ca0c 100644 --- a/aliusnes/src/lib.rs +++ b/aliusnes/src/lib.rs @@ -9,6 +9,7 @@ pub mod bus; pub mod cart; pub mod emu; mod ppu; +mod scheduler; mod utils; pub mod w65c816; diff --git a/aliusnes/src/ppu.rs b/aliusnes/src/ppu.rs index 104253c..e6d0f54 100644 --- a/aliusnes/src/ppu.rs +++ b/aliusnes/src/ppu.rs @@ -6,7 +6,12 @@ use self::{ oam::{Oam, Objsel}, vram::{VideoPortControl, Vram}, }; -use crate::{bus::Access, cart::info::Model, utils::int_traits::ManipulateU16}; +use crate::{ + bus::Access, + cart::info::Model, + scheduler::{Event, PpuEvent, Scheduler}, + utils::int_traits::ManipulateU16, +}; mod background; mod color; @@ -62,6 +67,7 @@ pub struct Ppu { set_ini: SetIni, pub screen_width: usize, pub screen_height: usize, + pub frame_ready: bool, pub frame_buffer: Box<[[u8; 3]; WIDTH * PAL_HEIGHT]>, } @@ -87,19 +93,50 @@ impl Ppu { } else { NTSC_HEIGHT }, + frame_ready: false, frame_buffer: Box::new([[0; 3]; WIDTH * PAL_HEIGHT]), } } - pub fn tick(&mut self) { - self.counters.update_status( - self.set_ini.overscan_mode(), - self.set_ini.screen_interlacing(), - ); - - if self.counters.in_hdraw() { - self.render_scanline(self.counters.vertical_counter); - self.counters.last_scanline = self.counters.vertical_counter; + pub(crate) fn handle_event(&mut self, scheduler: &mut Scheduler, event: PpuEvent, time: u64) { + // TODO H=6, V=0 reload HDMA registers + // TODO H=10, V=225 reload OAMADD + match event { + PpuEvent::HDraw => { + if self.counters.vertical_counter.wrapping_sub(1) < self.screen_height { + self.render_scanline(self.counters.vertical_counter); + } + scheduler.add_event(Event::Ppu(PpuEvent::HBlankStart), time + 1008); + } + // H = 274 + PpuEvent::HBlankStart => { + self.counters.set_hblank(true); + scheduler.add_event( + Event::Ppu(PpuEvent::NewScanline), + time + (self.counters.cycles_per_scanline - 1096) as u64, + ); + } + // H = 0 + PpuEvent::NewScanline => { + self.counters.vertical_counter += 1; + self.counters.set_hblank(false); + + if self.counters.vertical_counter == self.counters.vblank_end { + self.counters.start_frame( + self.set_ini.overscan_mode(), + self.set_ini.screen_interlacing(), + ); + } + self.counters + .start_scanline(self.set_ini.screen_interlacing()); + + if self.counters.vertical_counter == self.counters.vblank_start { + self.counters.enter_vblank(); + self.frame_ready = true; + } + self.counters.check_counters_timer_hit(time); + scheduler.add_event(Event::Ppu(PpuEvent::HDraw), time + 88); + } } } @@ -124,20 +161,16 @@ impl Ppu { pub fn frame_counter(&self) -> u64 { self.counters.frame_counter } - - pub fn frame_ready(&self) -> bool { - self.counters.frame_ready - } } impl Access for Ppu { - fn read(&mut self, addr: u16) -> Option { + fn read(&mut self, addr: u16, time: u64) -> Option { match addr.low_byte() { 0x34 => Some((self.mode7.do_multiplication()) as u8), 0x35 => Some((self.mode7.do_multiplication() >> 8) as u8), 0x36 => Some((self.mode7.do_multiplication() >> 16) as u8), 0x37 => { - self.counters.software_latch(); + self.counters.software_latch(time); None } 0x38 => { diff --git a/aliusnes/src/ppu/counters.rs b/aliusnes/src/ppu/counters.rs index cc6c875..98261e8 100644 --- a/aliusnes/src/ppu/counters.rs +++ b/aliusnes/src/ppu/counters.rs @@ -6,8 +6,6 @@ use crate::{ utils::int_traits::ManipulateU16, }; -const HBLANK_START: u16 = 274; - bitfield! { pub struct Nmitimen(pub u8) { joypad_enable: bool @ 0, @@ -41,10 +39,9 @@ bitfield! { pub struct Counters { pub vertical_counter: usize, - vblank_start: usize, - vblank_end: usize, - elapsed_cycles: u16, - cycles_per_scanline: u16, + pub vblank_start: usize, + pub vblank_end: usize, + pub cycles_per_scanline: u16, pub frame_counter: u64, ophct_latch: bool, @@ -59,10 +56,8 @@ pub struct Counters { stat78: Stat78, hv_status: HvStatus, in_irq: bool, - nmi_requested: bool, + pub nmi_requested: bool, - pub frame_ready: bool, - pub last_scanline: usize, #[cfg(feature = "log")] vblank_count: f32, #[cfg(feature = "log")] @@ -80,7 +75,6 @@ impl Counters { vertical_counter: 0, vblank_start, vblank_end, - elapsed_cycles: 0, cycles_per_scanline: super::SCANLINE_CYCLES, frame_counter: 0, ophct_latch: false, @@ -95,8 +89,6 @@ impl Counters { hv_status: HvStatus(0), in_irq: false, nmi_requested: false, - frame_ready: false, - last_scanline: 0, #[cfg(feature = "log")] vblank_count: 0.0, #[cfg(feature = "log")] @@ -104,26 +96,31 @@ impl Counters { } } - fn h_dot(&self) -> u16 { - self.elapsed_cycles % (self.cycles_per_scanline / 4) + pub(crate) fn set_hblank(&mut self, in_hblank: bool) { + self.hv_status.set_in_hblank(in_hblank); + } + + pub(crate) fn h_dot(&self, time: u64) -> u16 { + let as_master_cycles = time % (self.cycles_per_scanline as u64); + (as_master_cycles / 4) as u16 } - pub fn software_latch(&mut self) { + pub(crate) fn software_latch(&mut self, time: u64) { if !self.stat78.counter_latch() { self.output_vertical_counter = self.vertical_counter as u16; - self.output_horizontal_counter = self.h_dot(); + self.output_horizontal_counter = self.h_dot(time); } self.stat78.set_counter_latch(true); } - pub fn reset_latches(&mut self) { + pub(crate) fn reset_latches(&mut self) { self.ophct_latch = false; self.opvct_latch = false; self.stat78.set_counter_latch(false); } - fn check_counters_timer_hit(&mut self) { - let h_dot = self.h_dot(); + pub(crate) fn check_counters_timer_hit(&mut self, time: u64) { + let h_dot = self.h_dot(time); self.in_irq = match self.nmitimen.hv_timer_mode() { 0b00 => false, 0b01 => h_dot == self.h_timer_target, @@ -135,15 +132,10 @@ impl Counters { } } - pub fn entered_hblank(&self) -> bool { - self.h_dot() >= HBLANK_START - } - - pub fn start_frame(&mut self, overscan: bool, interlacing: bool) { + pub(crate) fn start_frame(&mut self, overscan: bool, interlacing: bool) { self.frame_counter += 1; self.stat78.set_odd_frame(self.frame_counter & 1 == 1); self.vertical_counter = 0; - self.frame_ready = false; self.rdnmi.set_in_nmi(false); self.hv_status.set_in_vblank(false); self.vblank_start = if overscan { PAL_HEIGHT } else { NTSC_HEIGHT } + 1; @@ -167,7 +159,7 @@ impl Counters { } } - pub fn start_scanline(&mut self, interlacing: bool) { + pub(crate) fn start_scanline(&mut self, interlacing: bool) { if self.vertical_counter == 311 && self.stat78.odd_frame() && self.stat78.is_pal() @@ -185,41 +177,13 @@ impl Counters { } } - pub fn update_status(&mut self, overscan: bool, interlacing: bool) { - self.elapsed_cycles += 1; - - if self.entered_hblank() && !self.hv_status.in_hblank() { - self.hv_status.set_in_hblank(true); - } - - if self.elapsed_cycles >= self.cycles_per_scanline { - self.hv_status.set_in_hblank(false); - self.elapsed_cycles -= self.cycles_per_scanline; - self.vertical_counter += 1; - if self.vertical_counter == self.vblank_end { - self.start_frame(overscan, interlacing); - } - - self.start_scanline(interlacing); - - if self.vertical_counter == self.vblank_start { - self.hv_status.set_in_vblank(true); - self.rdnmi.set_in_nmi(true); - self.frame_ready = true; + pub(crate) fn enter_vblank(&mut self) { + self.hv_status.set_in_vblank(true); + self.rdnmi.set_in_nmi(true); - if self.nmitimen.nmi_enabled() { - self.nmi_requested = true; - } - } + if self.nmitimen.nmi_enabled() { + self.nmi_requested = true; } - self.check_counters_timer_hit(); - } - - pub fn in_hdraw(&self) -> bool { - self.last_scanline != self.vertical_counter - && !self.hv_status.in_hblank() - && !self.hv_status.in_vblank() - && self.vertical_counter > 0 } } @@ -266,7 +230,7 @@ impl Ppu { let nmitimen = Nmitimen(val); let _joypad_enable = nmitimen.joypad_enable(); // todo when implementing joypad self.counters.nmitimen = nmitimen; - self.counters.check_counters_timer_hit(); + // self.counters.check_counters_timer_hit(); } pub fn read_nmi_flag(&mut self) -> u8 { diff --git a/aliusnes/src/scheduler.rs b/aliusnes/src/scheduler.rs new file mode 100644 index 0000000..eaa34a2 --- /dev/null +++ b/aliusnes/src/scheduler.rs @@ -0,0 +1,91 @@ +use std::mem; + +#[derive(Clone, Copy)] +pub enum PpuEvent { + HDraw, + HBlankStart, + NewScanline, +} + +#[derive(Clone, Copy)] +pub enum Event { + Ppu(PpuEvent), +} + +impl Event { + fn index(&self) -> usize { + match self { + Event::Ppu(_) => 1, + } + } +} + +#[derive(Clone, Copy)] +struct PendingEvent { + event: Event, + time: u64, +} + +pub struct Scheduler { + pub cycles: u64, + events: [PendingEvent; 3], + next_event_id: usize, +} + +impl Scheduler { + pub(crate) fn new() -> Self { + Scheduler { + cycles: 0, + events: [PendingEvent { + event: Event::Ppu(PpuEvent::HDraw), + time: u64::MAX, + }; 3], + next_event_id: 0, + } + } + + fn find_next_event(&mut self) { + let mut new_id = 0; + + for idx in 1..3 { + if self.events[idx].time < self.events[new_id].time { + new_id = idx; + } + } + self.next_event_id = new_id; + } + + pub(crate) fn add_event(&mut self, event: Event, time: u64) { + let id = event.index(); + self.events[id].event = event; + self.events[id].time = time; + + if id == self.next_event_id { + self.find_next_event(); + } else if self.events[id].time < self.events[self.next_event_id].time { + self.next_event_id = id; + } + } + + pub(crate) fn waiting_for_next_event(&self) -> bool { + self.cycles < self.events[self.next_event_id].time + } + + pub(crate) fn pop_event(&mut self) -> Option<(Event, u64)> { + if self.waiting_for_next_event() { + return None; + } + + let id = self.next_event_id; + + self.find_next_event(); + + let slot = &mut self.events[id]; + + Some((slot.event, mem::replace(&mut slot.time, u64::MAX))) + } + + pub(crate) fn tick(&mut self, ticks: u64) { + self.cycles += ticks; + } +} diff --git a/frontend/src/emu_state.rs b/frontend/src/emu_state.rs index 8b3aa8b..43dd36e 100644 --- a/frontend/src/emu_state.rs +++ b/frontend/src/emu_state.rs @@ -55,19 +55,16 @@ impl EmuState { } if !paused { - emu.step(); + emu.run_frame(); } + let mut frame = Frame { + width: emu.frame_width(), + height: emu.frame_height(), + buffer: [[0; 3]; 61184], + }; + frame.buffer.copy_from_slice(emu.frame()); - if emu.frame_ready() { - let mut frame = Frame { - width: emu.frame_width(), - height: emu.frame_height(), - buffer: [[0; 3]; 61184], - }; - frame.buffer.copy_from_slice(emu.frame()); - - let _ = frame_tx.push(frame); - } + let _ = frame_tx.push(frame); } } } From 6f0b1a4276d26d1838f86b592c33bbc9dd09d3c7 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Sun, 31 Aug 2025 13:09:35 +0200 Subject: [PATCH 17/18] Ppu: cleanup code --- aliusnes/src/cart/info.rs | 2 +- aliusnes/src/ppu.rs | 37 ++++++++--------- aliusnes/src/ppu/counters.rs | 79 ++++++++++++++++++------------------ 3 files changed, 58 insertions(+), 60 deletions(-) diff --git a/aliusnes/src/cart/info.rs b/aliusnes/src/cart/info.rs index ea1b884..871010e 100644 --- a/aliusnes/src/cart/info.rs +++ b/aliusnes/src/cart/info.rs @@ -23,7 +23,7 @@ pub struct Chipset { pub has_battery: bool, } -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy)] pub enum Model { Ntsc, Pal, diff --git a/aliusnes/src/ppu.rs b/aliusnes/src/ppu.rs index e6d0f54..a442e0e 100644 --- a/aliusnes/src/ppu.rs +++ b/aliusnes/src/ppu.rs @@ -69,13 +69,21 @@ pub struct Ppu { pub screen_height: usize, pub frame_ready: bool, pub frame_buffer: Box<[[u8; 3]; WIDTH * PAL_HEIGHT]>, + + vblank_start: usize, + vblank_end: usize, } impl Ppu { pub fn new(model: Model) -> Self { let stat78 = counters::Stat78(0) .with_ppu2_version(2) - .with_is_pal(model == Model::Pal); + .with_is_pal(matches!(model, Model::Pal)); + let (screen_height, vblank_end) = match model { + Model::Ntsc => (NTSC_HEIGHT, NTSC_SCANLINES), + Model::Pal => (PAL_HEIGHT, PAL_SCANLINES), + }; + Self { background: Background::new(), color: Color::new(), @@ -87,12 +95,10 @@ impl Ppu { ppu2_mdr: 0, ini_display: IniDisplay(0), set_ini: SetIni(0), + vblank_start: screen_height + 1, + vblank_end, screen_width: WIDTH, - screen_height: if model == Model::Pal { - PAL_HEIGHT - } else { - NTSC_HEIGHT - }, + screen_height, frame_ready: false, frame_buffer: Box::new([[0; 3]; WIDTH * PAL_HEIGHT]), } @@ -113,7 +119,7 @@ impl Ppu { self.counters.set_hblank(true); scheduler.add_event( Event::Ppu(PpuEvent::NewScanline), - time + (self.counters.cycles_per_scanline - 1096) as u64, + time + self.counters.hblank_length() as u64, ); } // H = 0 @@ -121,16 +127,13 @@ impl Ppu { self.counters.vertical_counter += 1; self.counters.set_hblank(false); - if self.counters.vertical_counter == self.counters.vblank_end { - self.counters.start_frame( - self.set_ini.overscan_mode(), - self.set_ini.screen_interlacing(), - ); + if self.counters.vertical_counter == self.vblank_end { + self.update_vblank_period(); + self.counters.start_frame(); } - self.counters - .start_scanline(self.set_ini.screen_interlacing()); + self.update_scanline_lenght(); - if self.counters.vertical_counter == self.counters.vblank_start { + if self.counters.vertical_counter == self.vblank_start { self.counters.enter_vblank(); self.frame_ready = true; } @@ -157,10 +160,6 @@ impl Ppu { } self.oam.enabled_on_main_screen = (data >> 4) & 1 != 0; } - - pub fn frame_counter(&self) -> u64 { - self.counters.frame_counter - } } impl Access for Ppu { diff --git a/aliusnes/src/ppu/counters.rs b/aliusnes/src/ppu/counters.rs index 98261e8..991d1ab 100644 --- a/aliusnes/src/ppu/counters.rs +++ b/aliusnes/src/ppu/counters.rs @@ -39,10 +39,7 @@ bitfield! { pub struct Counters { pub vertical_counter: usize, - pub vblank_start: usize, - pub vblank_end: usize, - pub cycles_per_scanline: u16, - pub frame_counter: u64, + cycles_per_scanline: u16, ophct_latch: bool, opvct_latch: bool, @@ -56,7 +53,7 @@ pub struct Counters { stat78: Stat78, hv_status: HvStatus, in_irq: bool, - pub nmi_requested: bool, + nmi_requested: bool, #[cfg(feature = "log")] vblank_count: f32, @@ -66,17 +63,9 @@ pub struct Counters { impl Counters { pub fn new(stat78: Stat78) -> Self { - let (vblank_start, vblank_end) = if stat78.is_pal() { - (PAL_HEIGHT + 1, PAL_SCANLINES) - } else { - (NTSC_HEIGHT + 1, NTSC_SCANLINES) - }; Self { vertical_counter: 0, - vblank_start, - vblank_end, cycles_per_scanline: super::SCANLINE_CYCLES, - frame_counter: 0, ophct_latch: false, opvct_latch: false, output_horizontal_counter: 0, @@ -105,6 +94,10 @@ impl Counters { (as_master_cycles / 4) as u16 } + pub(super) fn hblank_length(&self) -> u16 { + self.cycles_per_scanline - 1096 + } + pub(crate) fn software_latch(&mut self, time: u64) { if !self.stat78.counter_latch() { self.output_vertical_counter = self.vertical_counter as u16; @@ -132,18 +125,11 @@ impl Counters { } } - pub(crate) fn start_frame(&mut self, overscan: bool, interlacing: bool) { - self.frame_counter += 1; - self.stat78.set_odd_frame(self.frame_counter & 1 == 1); + pub(crate) fn start_frame(&mut self) { + self.stat78.set_odd_frame(!self.stat78.odd_frame()); self.vertical_counter = 0; self.rdnmi.set_in_nmi(false); self.hv_status.set_in_vblank(false); - self.vblank_start = if overscan { PAL_HEIGHT } else { NTSC_HEIGHT } + 1; - self.vblank_end = if self.stat78.is_pal() { - PAL_SCANLINES - } else { - NTSC_SCANLINES - } + usize::from(interlacing && !self.stat78.odd_frame()); #[cfg(feature = "log")] { @@ -159,24 +145,6 @@ impl Counters { } } - pub(crate) fn start_scanline(&mut self, interlacing: bool) { - if self.vertical_counter == 311 - && self.stat78.odd_frame() - && self.stat78.is_pal() - && interlacing - { - self.cycles_per_scanline = SCANLINE_CYCLES + 4; - } else if self.vertical_counter == 240 - && self.stat78.odd_frame() - && !self.stat78.is_pal() - && !interlacing - { - self.cycles_per_scanline = SCANLINE_CYCLES - 4; - } else { - self.cycles_per_scanline = SCANLINE_CYCLES; - } - } - pub(crate) fn enter_vblank(&mut self) { self.hv_status.set_in_vblank(true); self.rdnmi.set_in_nmi(true); @@ -266,4 +234,35 @@ impl Ppu { self.counters.in_irq = false; in_irq } + + pub(super) fn update_scanline_lenght(&mut self) { + let is_odd_frame = self.counters.stat78.odd_frame(); + let is_pal = self.counters.stat78.is_pal(); + let interlaced = self.set_ini.screen_interlacing(); + + match ( + self.counters.vertical_counter, + is_odd_frame, + is_pal, + interlaced, + ) { + (311, true, true, true) => self.counters.cycles_per_scanline = SCANLINE_CYCLES + 4, + (240, true, false, false) => self.counters.cycles_per_scanline = SCANLINE_CYCLES - 4, + _ => self.counters.cycles_per_scanline = SCANLINE_CYCLES, + } + } + + pub(super) fn update_vblank_period(&mut self) { + self.vblank_start = if self.set_ini.overscan_mode() { + PAL_HEIGHT + } else { + NTSC_HEIGHT + } + 1; + self.vblank_end = + if self.counters.stat78.is_pal() { + PAL_SCANLINES + } else { + NTSC_SCANLINES + } + usize::from(self.set_ini.screen_interlacing() && !self.counters.stat78.odd_frame()); + } } From eb228cdd620e1583cdf52c3b96ff46f5762b79b1 Mon Sep 17 00:00:00 2001 From: Gae24 <96017547+Gae24@users.noreply.github.com> Date: Sun, 31 Aug 2025 16:35:45 +0200 Subject: [PATCH 18/18] w65c816: remove StackPEI addressing mode --- aliusnes/src/w65c816/addressing.rs | 16 ++-------------- aliusnes/src/w65c816/mod.rs | 2 +- aliusnes/src/w65c816/opcode.rs | 2 -- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/aliusnes/src/w65c816/addressing.rs b/aliusnes/src/w65c816/addressing.rs index b35069f..44cf727 100644 --- a/aliusnes/src/w65c816/addressing.rs +++ b/aliusnes/src/w65c816/addressing.rs @@ -26,7 +26,6 @@ pub enum AddressingMode { AbsoluteIndirectX, StackRelative, StackRelIndirectY, - StackPEI, BlockMove, } @@ -240,17 +239,7 @@ impl Cpu { let offset = self.read_bank0(bus, indirect); Address::new(offset, self.dbr).wrapping_add(u32::from(self.index_y)) } - AddressingMode::Accumulator - | AddressingMode::Implied - | AddressingMode::Immediate - | AddressingMode::Relative - | AddressingMode::RelativeLong - | AddressingMode::Direct - | AddressingMode::DirectX - | AddressingMode::DirectY - | AddressingMode::StackRelative - | AddressingMode::StackPEI - | AddressingMode::BlockMove => unreachable!(), + _ => unreachable!(), } } @@ -267,8 +256,7 @@ impl Cpu { AddressingMode::Direct | AddressingMode::DirectX | AddressingMode::DirectY - | AddressingMode::StackRelative - | AddressingMode::StackPEI => self.read_from_direct_page(bus, mode).0, + | AddressingMode::StackRelative => self.read_from_direct_page(bus, mode).0, _ => { let addr = self.decode_addressing_mode::(bus, *mode); self.read(bus, addr) diff --git a/aliusnes/src/w65c816/mod.rs b/aliusnes/src/w65c816/mod.rs index ea66283..43665a0 100644 --- a/aliusnes/src/w65c816/mod.rs +++ b/aliusnes/src/w65c816/mod.rs @@ -329,7 +329,7 @@ pub const fn opcode_table() -> [OpCode; 256] { OpCode::new(Meta::new(0xd1, "CMP", IndirectY), W65C816::cmp), OpCode::new(Meta::new(0xd2, "CMP", Indirect), W65C816::cmp), OpCode::new(Meta::new(0xd3, "CMP", StackRelIndirectY), W65C816::cmp), - OpCode::new(Meta::new(0xd4, "PEI", StackPEI), W65C816::pei), + OpCode::new(Meta::new(0xd4, "PEI", Direct), W65C816::pei), OpCode::new(Meta::new(0xd5, "CMP", DirectX), W65C816::cmp), OpCode::new(Meta::new(0xd6, "DEC", DirectX), W65C816::dec), OpCode::new(Meta::new(0xd7, "CMP", IndirectLongY), W65C816::cmp), diff --git a/aliusnes/src/w65c816/opcode.rs b/aliusnes/src/w65c816/opcode.rs index e873922..30248bc 100644 --- a/aliusnes/src/w65c816/opcode.rs +++ b/aliusnes/src/w65c816/opcode.rs @@ -29,7 +29,6 @@ impl AddressingMode { AddressingMode::AbsoluteIndirectX => 2, AddressingMode::StackRelative => 1, AddressingMode::StackRelIndirectY => 1, - AddressingMode::StackPEI => 1, AddressingMode::BlockMove => 1, } } @@ -75,7 +74,6 @@ impl AddressingMode { AddressingMode::AbsoluteIndirectX => format!("(${:04X},X)", operand), AddressingMode::StackRelative => format!("${:02X},S", operand), AddressingMode::StackRelIndirectY => format!("(${:02X},S),Y", operand), - AddressingMode::StackPEI => format!("(${:02X})", operand), AddressingMode::BlockMove => { format!("${:02X} ${:02X}", operand as u8, (operand >> 8) & 0xFF) }