diff --git a/src/arm7tdmi/cpu.rs b/src/arm7tdmi/cpu.rs index e62c85b..b74eb68 100644 --- a/src/arm7tdmi/cpu.rs +++ b/src/arm7tdmi/cpu.rs @@ -75,7 +75,7 @@ pub struct Core { pub pipeline_arm: PipelineContext, pub pipeline_thumb: PipelineContext, - cycles: usize, + pub cycles: usize, // store the gpr before executing an instruction to show diff in the Display impl gpr_previous: [u32; 15], @@ -183,12 +183,12 @@ impl Core { } pub fn add_cycle(&mut self) { - println!(" total: {}", self.cycles); + // println!(" total: {}", self.cycles); self.cycles += 1; } pub fn add_cycles(&mut self, addr: Addr, bus: &Bus, access: MemoryAccess) { - println!(" total: {}", addr, access, self.cycles); + // println!(" total: {}", addr, access, self.cycles); self.cycles += bus.get_cycles(addr, access); } @@ -393,7 +393,7 @@ impl Core { /// A step that returns only once an instruction was executed. /// Returns the address of PC before executing an instruction, /// and the address of the next instruction to be executed; - pub fn step_debugger(&mut self, bus: &mut Bus) -> CpuResult { + pub fn step_one(&mut self, bus: &mut Bus) -> CpuResult { loop { if let Some(i) = self.step(bus)? { return Ok(i); diff --git a/src/arm7tdmi/mod.rs b/src/arm7tdmi/mod.rs index 99f3ac8..7ed374a 100644 --- a/src/arm7tdmi/mod.rs +++ b/src/arm7tdmi/mod.rs @@ -11,7 +11,7 @@ use thumb::{ThumbDecodeError, ThumbInstruction}; pub mod cpu; pub use cpu::*; pub mod bus; -pub use bus::Bus; +pub use bus::*; pub mod exception; pub mod psr; diff --git a/src/arm7tdmi/psr.rs b/src/arm7tdmi/psr.rs index 5e51d36..3db2133 100644 --- a/src/arm7tdmi/psr.rs +++ b/src/arm7tdmi/psr.rs @@ -143,7 +143,8 @@ impl fmt::Display for RegPSR { }; write!( f, - "{{ mode: {mode}, state: {state}, irq: {irq}, fiq: {fiq}, condition_flags: (N={N} Z={Z} C={C} V={V}) }}", + "{{ [{raw:#010x}] mode: {mode}, state: {state}, irq: {irq}, fiq: {fiq}, condition_flags: (N={N} Z={Z} C={C} V={V}) }}", + raw = self.raw, mode = self.mode(), state = self.state(), irq = disabled_string(self.irq_disabled()), diff --git a/src/bin/main.rs b/src/bin/main.rs index 02cb054..61e2a6c 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,5 +1,3 @@ -use std::io; - #[macro_use] extern crate clap; @@ -7,45 +5,11 @@ use clap::{App, ArgMatches}; extern crate rustboyadvance_ng; -use rustboyadvance_ng::arm7tdmi; -use rustboyadvance_ng::debugger::{Debugger, DebuggerError}; +use rustboyadvance_ng::arm7tdmi::Core; use rustboyadvance_ng::cartridge::Cartridge; -use rustboyadvance_ng::sysbus::SysBus; +use rustboyadvance_ng::debugger::Debugger; use rustboyadvance_ng::util::read_bin_file; - -#[derive(Debug)] -pub enum GBAError { - IO(io::Error), - ArmDecodeError(arm7tdmi::arm::ArmDecodeError), - CpuError(arm7tdmi::CpuError), - DebuggerError(DebuggerError), -} - -pub type GBAResult = Result; - -impl From for GBAError { - fn from(err: io::Error) -> GBAError { - GBAError::IO(err) - } -} - -impl From for GBAError { - fn from(err: arm7tdmi::arm::ArmDecodeError) -> GBAError { - GBAError::ArmDecodeError(err) - } -} - -impl From for GBAError { - fn from(err: arm7tdmi::CpuError) -> GBAError { - GBAError::CpuError(err) - } -} - -impl From for GBAError { - fn from(err: DebuggerError) -> GBAError { - GBAError::DebuggerError(err) - } -} +use rustboyadvance_ng::{GBAResult, GameBoyAdvance}; fn run_debug(matches: &ArgMatches) -> GBAResult<()> { let bios_bin = read_bin_file(matches.value_of("bios").unwrap_or_default())?; @@ -54,11 +18,13 @@ fn run_debug(matches: &ArgMatches) -> GBAResult<()> { let gamepak = Cartridge::new(rom_bin); println!("loaded rom: {:#?}", gamepak.header); - let sysbus = SysBus::new(bios_bin, gamepak); - let mut core = arm7tdmi::cpu::Core::new(); + let mut core = Core::new(); core.reset(); core.set_verbose(true); - let mut debugger = Debugger::new(core, sysbus); + + let gba = GameBoyAdvance::new(core, bios_bin, gamepak); + + let mut debugger = Debugger::new(gba); println!("starting debugger..."); debugger.repl()?; diff --git a/src/debugger/command.rs b/src/debugger/command.rs index f5ebb51..21f98d8 100644 --- a/src/debugger/command.rs +++ b/src/debugger/command.rs @@ -1,6 +1,7 @@ use crate::arm7tdmi::bus::Bus; use crate::arm7tdmi::{Addr, CpuState}; use crate::disass::Disassembler; +use crate::GBAError; use super::{parser::Value, Debugger, DebuggerError, DebuggerResult}; @@ -34,14 +35,14 @@ impl Command { pub fn run(&self, debugger: &mut Debugger) { use Command::*; match *self { - Info => println!("{}", debugger.cpu), + Info => println!("{}", debugger.gba.cpu), Step(count) => { for _ in 0..count { if let Some(bp) = debugger.check_breakpoint() { println!("hit breakpoint #0x{:08x}!", bp); debugger.delete_breakpoint(bp); } else { - match debugger.cpu.step_debugger(&mut debugger.sysbus) { + match debugger.gba.step() { Ok(insn) => { print!( "{}\t{}", @@ -52,19 +53,23 @@ impl Command { .paint(format!("Executed at @0x{:08x}:", insn.get_pc(),)), insn ); - println!("{}", Colour::Purple.dimmed().italic().paint(format!( - "\t\t/// Next instruction at @0x{:08x}", - debugger.cpu.get_next_pc() - ))) + println!( + "{}", + Colour::Purple.dimmed().italic().paint(format!( + "\t\t/// Next instruction at @0x{:08x}", + debugger.gba.cpu.get_next_pc() + )) + ) } - Err(e) => { + Err(GBAError::CpuError(e)) => { println!("{}: {}", "cpu encountered an error".red(), e); - println!("cpu: {:x?}", debugger.cpu) + println!("cpu: {:x?}", debugger.gba.cpu) } + _ => unreachable!(), } } } - println!("{}\n", debugger.cpu); + println!("{}\n", debugger.gba.cpu); } Continue => loop { if let Some(bp) = debugger.check_breakpoint() { @@ -72,7 +77,7 @@ impl Command { debugger.delete_breakpoint(bp); break; } - match debugger.cpu.step_debugger(&mut debugger.sysbus) { + match debugger.gba.step() { Ok(insn) => { println!( "@0x{:08x}:\t{}", @@ -80,22 +85,23 @@ impl Command { Colour::Yellow.italic().paint(format!("{} ", insn)) ); } - Err(e) => { + Err(GBAError::CpuError(e)) => { println!("{}: {}", "cpu encountered an error".red(), e); - println!("cpu: {:x?}", debugger.cpu); + println!("cpu: {:x?}", debugger.gba.cpu); break; } + _ => unreachable!(), }; }, HexDump(addr, nbytes) => { - let bytes = debugger.sysbus.get_bytes(addr); + let bytes = debugger.gba.sysbus.get_bytes(addr); hexdump::hexdump(&bytes[0..nbytes]); } Disass(mode, addr, n) => { use crate::arm7tdmi::arm::ArmInstruction; use crate::arm7tdmi::thumb::ThumbInstruction; - let bytes = debugger.sysbus.get_bytes(addr); + let bytes = debugger.gba.sysbus.get_bytes(addr); match mode { DisassMode::ModeArm => { let disass = Disassembler::::new(addr, bytes); @@ -134,7 +140,7 @@ impl Command { } Reset => { println!("resetting cpu..."); - debugger.cpu.reset(); + debugger.gba.cpu.reset(); println!("cpu is restarted!") } } @@ -159,7 +165,7 @@ impl Debugger { if let Some(Command::Disass(_mode, addr, n)) = &self.previous_command { Ok((*addr + (4 * (*n as u32)), 10)) } else { - Ok((self.cpu.get_next_pc(), 10)) + Ok((self.gba.cpu.get_next_pc(), 10)) } } _ => { @@ -210,7 +216,7 @@ impl Debugger { if let Some(Command::HexDump(addr, n)) = self.previous_command { (addr + (4 * n as u32), 0x100) } else { - (self.cpu.get_reg(15), 0x100) + (self.gba.cpu.get_reg(15), 0x100) } } _ => { @@ -224,7 +230,7 @@ impl Debugger { "d" | "disass" => { let (addr, n) = self.get_disassembler_args(args)?; - let m = match self.cpu.cpsr.state() { + let m = match self.gba.cpu.cpsr.state() { CpuState::ARM => DisassMode::ModeArm, CpuState::THUMB => DisassMode::ModeThumb, }; diff --git a/src/debugger/mod.rs b/src/debugger/mod.rs index 6a7b7cb..959241e 100644 --- a/src/debugger/mod.rs +++ b/src/debugger/mod.rs @@ -3,9 +3,8 @@ use rustyline::Editor; use colored::*; -use super::arm7tdmi; -use super::arm7tdmi::{Addr, Bus}; -use super::sysbus::SysBus; +use super::arm7tdmi::{Addr, Bus, CpuError}; +use super::GameBoyAdvance; mod parser; use parser::{parse_expr, DerefType, Expr, Value}; @@ -16,14 +15,14 @@ use command::Command; #[derive(Debug, PartialEq)] pub enum DebuggerError { ParsingError(String), - CpuError(arm7tdmi::CpuError), + CpuError(CpuError), InvalidCommand(String), InvalidArgument(String), InvalidCommandFormat(String), } -impl From for DebuggerError { - fn from(e: arm7tdmi::CpuError) -> DebuggerError { +impl From for DebuggerError { + fn from(e: CpuError) -> DebuggerError { DebuggerError::CpuError(e) } } @@ -32,18 +31,16 @@ type DebuggerResult = Result; #[derive(Debug)] pub struct Debugger { - pub cpu: arm7tdmi::cpu::Core, - pub sysbus: SysBus, + pub gba: GameBoyAdvance, running: bool, breakpoints: Vec, pub previous_command: Option, } impl Debugger { - pub fn new(cpu: arm7tdmi::cpu::Core, sysbus: SysBus) -> Debugger { + pub fn new(gba: GameBoyAdvance) -> Debugger { Debugger { - cpu: cpu, - sysbus: sysbus, + gba: gba, breakpoints: Vec::new(), running: false, previous_command: None, @@ -51,7 +48,7 @@ impl Debugger { } pub fn check_breakpoint(&self) -> Option { - let next_pc = self.cpu.get_next_pc(); + let next_pc = self.gba.cpu.get_next_pc(); for bp in &self.breakpoints { if *bp == next_pc { return Some(next_pc); @@ -105,7 +102,7 @@ impl Debugger { Value::Num(n) => Ok(*n), Value::Identifier(reg) => { let reg = self.decode_reg(®)?; - Ok(self.cpu.get_reg(reg)) + Ok(self.gba.cpu.get_reg(reg)) } v => Err(DebuggerError::InvalidArgument(format!( "addr: expected a number or register, got {:?}", @@ -120,14 +117,14 @@ impl Debugger { Value::Deref(addr_value, deref_type) => { let addr = self.val_address(&addr_value)?; match deref_type { - DerefType::Word => self.sysbus.read_32(addr), - DerefType::HalfWord => self.sysbus.read_16(addr) as u32, - DerefType::Byte => self.sysbus.read_8(addr) as u32, + DerefType::Word => self.gba.sysbus.read_32(addr), + DerefType::HalfWord => self.gba.sysbus.read_16(addr) as u32, + DerefType::Byte => self.gba.sysbus.read_8(addr) as u32, } } _ => self.val_address(&rvalue)?, }; - self.cpu.set_reg(lvalue, rvalue); + self.gba.cpu.set_reg(lvalue, rvalue); Ok(()) } diff --git a/src/gba.rs b/src/gba.rs new file mode 100644 index 0000000..175153a --- /dev/null +++ b/src/gba.rs @@ -0,0 +1,43 @@ +use super::arm7tdmi::{Core, DecodedInstruction}; +use super::cartridge::Cartridge; +use super::lcd::Lcd; +use super::sysbus::SysBus; +/// Struct containing everything +/// +use super::{EmuIoDev, GBAResult}; + +#[derive(Debug)] +pub struct GameBoyAdvance { + pub cpu: Core, + pub sysbus: SysBus, + + // io devices + lcd: Lcd, + + post_bool_flags: bool, +} + +impl GameBoyAdvance { + pub fn new(cpu: Core, bios_rom: Vec, gamepak: Cartridge) -> GameBoyAdvance { + let sysbus = SysBus::new(bios_rom, gamepak); + + GameBoyAdvance { + cpu: cpu, + sysbus: sysbus, + + lcd: Lcd::new(), + + post_bool_flags: false, + } + } + + pub fn step(&mut self) -> GBAResult { + let previous_cycles = self.cpu.cycles; + let decoded = self.cpu.step_one(&mut self.sysbus)?; + let cycles = self.cpu.cycles - previous_cycles; + + self.lcd.step(cycles, &mut self.sysbus); // drop interrupts at the moment + + Ok(decoded) // return the decoded instruction for the debugger + } +} diff --git a/src/interrupt.rs b/src/interrupt.rs new file mode 100644 index 0000000..18a024f --- /dev/null +++ b/src/interrupt.rs @@ -0,0 +1,20 @@ +#[derive(Debug, Primitive, Copy, Clone, PartialEq)] +#[allow(non_camel_case_types)] +pub enum Interrupt { + LCD_VBlank = 0, + LCD_HBlank = 1, + LCD_VCounterMatch = 2, + Timer0_Overflow = 3, + Timer1_Overflow = 4, + Timer2_Overflow = 5, + Timer3_Overflow = 6, + SerialCommunication = 7, + DMA0 = 8, + DMA1 = 9, + DMA2 = 10, + DMA3 = 11, + Keypad = 12, + GamePak = 13, +} + +pub struct InterruptController; diff --git a/src/ioregs.rs b/src/ioregs.rs new file mode 100644 index 0000000..48c534f --- /dev/null +++ b/src/ioregs.rs @@ -0,0 +1,200 @@ +use std::io; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; + +use crate::arm7tdmi::{Addr, Bus, MemoryAccess}; + +pub mod consts { + use super::*; + + pub const IO_BASE: Addr = 0x0400_0000; + + // LCD I/O Registers + pub const REG_DISPCNT: Addr = IO_BASE + 0x_0000; // 2 R/W LCD Control + pub const REG_DISPSTAT: Addr = IO_BASE + 0x_0004; // 2 R/W General LCD Status (STAT,LYC) + pub const REG_VCOUNT: Addr = IO_BASE + 0x_0006; // 2 R Vertical Counter (LY) + pub const REG_BG0CNT: Addr = IO_BASE + 0x_0008; // 2 R/W BG0 Control + pub const REG_BG1CNT: Addr = IO_BASE + 0x_000A; // 2 R/W BG1 Control + pub const REG_BG2CNT: Addr = IO_BASE + 0x_000C; // 2 R/W BG2 Control + pub const REG_BG3CNT: Addr = IO_BASE + 0x_000E; // 2 R/W BG3 Control + pub const REG_BG0HOFS: Addr = IO_BASE + 0x_0010; // 2 W BG0 X-Offset + pub const REG_BG0VOFS: Addr = IO_BASE + 0x_0012; // 2 W BG0 Y-Offset + pub const REG_BG1HOFS: Addr = IO_BASE + 0x_0014; // 2 W BG1 X-Offset + pub const REG_BG1VOFS: Addr = IO_BASE + 0x_0016; // 2 W BG1 Y-Offset + pub const REG_BG2HOFS: Addr = IO_BASE + 0x_0018; // 2 W BG2 X-Offset + pub const REG_BG2VOFS: Addr = IO_BASE + 0x_001A; // 2 W BG2 Y-Offset + pub const REG_BG3HOFS: Addr = IO_BASE + 0x_001C; // 2 W BG3 X-Offset + pub const REG_BG3VOFS: Addr = IO_BASE + 0x_001E; // 2 W BG3 Y-Offset + pub const REG_BG2PA: Addr = IO_BASE + 0x_0020; // 2 W BG2 Rotation/Scaling Parameter A (dx) + pub const REG_BG2PB: Addr = IO_BASE + 0x_0022; // 2 W BG2 Rotation/Scaling Parameter B (dmx) + pub const REG_BG2PC: Addr = IO_BASE + 0x_0024; // 2 W BG2 Rotation/Scaling Parameter C (dy) + pub const REG_BG2PD: Addr = IO_BASE + 0x_0026; // 2 W BG2 Rotation/Scaling Parameter D (dmy) + pub const REG_BG2X: Addr = IO_BASE + 0x_0028; // 4 W BG2 Reference Point X-Coordinate + pub const REG_BG2Y: Addr = IO_BASE + 0x_002C; // 4 W BG2 Reference Point Y-Coordinate + pub const REG_BG3PA: Addr = IO_BASE + 0x_0030; // 2 W BG3 Rotation/Scaling Parameter A (dx) + pub const REG_BG3PB: Addr = IO_BASE + 0x_0032; // 2 W BG3 Rotation/Scaling Parameter B (dmx) + pub const REG_BG3PC: Addr = IO_BASE + 0x_0034; // 2 W BG3 Rotation/Scaling Parameter C (dy) + pub const REG_BG3PD: Addr = IO_BASE + 0x_0036; // 2 W BG3 Rotation/Scaling Parameter D (dmy) + pub const REG_BG3X: Addr = IO_BASE + 0x_0038; // 4 W BG3 Reference Point X-Coordinate + pub const REG_BG3Y: Addr = IO_BASE + 0x_003C; // 4 W BG3 Reference Point Y-Coordinate + pub const REG_WIN0H: Addr = IO_BASE + 0x_0040; // 2 W Window 0 Horizontal Dimensions + pub const REG_WIN1H: Addr = IO_BASE + 0x_0042; // 2 W Window 1 Horizontal Dimensions + pub const REG_WIN0V: Addr = IO_BASE + 0x_0044; // 2 W Window 0 Vertical Dimensions + pub const REG_WIN1V: Addr = IO_BASE + 0x_0046; // 2 W Window 1 Vertical Dimensions + pub const REG_WININ: Addr = IO_BASE + 0x_0048; // 2 R/W Inside of Window 0 and 1 + pub const REG_WINOUT: Addr = IO_BASE + 0x_004A; // 2 R/W Inside of OBJ Window & Outside of Windows + pub const REG_MOSAIC: Addr = IO_BASE + 0x_004C; // 2 W Mosaic Size + pub const REG_BLDCNT: Addr = IO_BASE + 0x_0050; // 2 R/W Color Special Effects Selection + pub const REG_BLDALPHA: Addr = IO_BASE + 0x_0052; // 2 R/W Alpha Blending Coefficients + pub const REG_BLDY: Addr = IO_BASE + 0x_0054; // 2 W Brightness (Fade-In/Out) Coefficient + // Sound Registers + pub const REG_SOUND1CNT_L: Addr = IO_BASE + 0x_0060; // 2 R/W Channel 1 Sweep register (NR10) + pub const REG_SOUND1CNT_H: Addr = IO_BASE + 0x_0062; // 2 R/W Channel 1 Duty/Length/Envelope (NR11, NR12) + pub const REG_SOUND1CNT_X: Addr = IO_BASE + 0x_0064; // 2 R/W Channel 1 Frequency/Control (NR13, NR14) + pub const REG_SOUND2CNT_L: Addr = IO_BASE + 0x_0068; // 2 R/W Channel 2 Duty/Length/Envelope (NR21, NR22) + pub const REG_SOUND2CNT_H: Addr = IO_BASE + 0x_006C; // 2 R/W Channel 2 Frequency/Control (NR23, NR24) + pub const REG_SOUND3CNT_L: Addr = IO_BASE + 0x_0070; // 2 R/W Channel 3 Stop/Wave RAM select (NR30) + pub const REG_SOUND3CNT_H: Addr = IO_BASE + 0x_0072; // 2 R/W Channel 3 Length/Volume (NR31, NR32) + pub const REG_SOUND3CNT_X: Addr = IO_BASE + 0x_0074; // 2 R/W Channel 3 Frequency/Control (NR33, NR34) + pub const REG_SOUND4CNT_L: Addr = IO_BASE + 0x_0078; // 2 R/W Channel 4 Length/Envelope (NR41, NR42) + pub const REG_SOUND4CNT_H: Addr = IO_BASE + 0x_007C; // 2 R/W Channel 4 Frequency/Control (NR43, NR44) + pub const REG_SOUNDCNT_L: Addr = IO_BASE + 0x_0080; // 2 R/W Control Stereo/Volume/Enable (NR50, NR51) + pub const REG_SOUNDCNT_H: Addr = IO_BASE + 0x_0082; // 2 R/W Control Mixing/DMA Control + pub const REG_SOUNDCNT_X: Addr = IO_BASE + 0x_0084; // 2 R/W Control Sound on/off (NR52) + pub const REG_SOUNDBIAS: Addr = IO_BASE + 0x_0088; // 2 BIOS Sound PWM Control + pub const REG_WAVE_RAM: Addr = IO_BASE + 0x_0090; // Channel 3 Wave Pattern RAM (2 banks!!) + pub const REG_FIFO_A: Addr = IO_BASE + 0x_00A0; // 4 W Channel A FIFO, Data 0-3 + pub const REG_FIFO_B: Addr = IO_BASE + 0x_00A4; // 4 W Channel B FIFO, Data 0-3 + // DMA Transfer Channels + pub const REG_DMA0SAD: Addr = IO_BASE + 0x_00B0; // 4 W DMA 0 Source Address + pub const REG_DMA0DAD: Addr = IO_BASE + 0x_00B4; // 4 W DMA 0 Destination Address + pub const REG_DMA0CNT_L: Addr = IO_BASE + 0x_00B8; // 2 W DMA 0 Word Count + pub const REG_DMA0CNT_H: Addr = IO_BASE + 0x_00BA; // 2 R/W DMA 0 Control + pub const REG_DMA1SAD: Addr = IO_BASE + 0x_00BC; // 4 W DMA 1 Source Address + pub const REG_DMA1DAD: Addr = IO_BASE + 0x_00C0; // 4 W DMA 1 Destination Address + pub const REG_DMA1CNT_L: Addr = IO_BASE + 0x_00C4; // 2 W DMA 1 Word Count + pub const REG_DMA1CNT_H: Addr = IO_BASE + 0x_00C6; // 2 R/W DMA 1 Control + pub const REG_DMA2SAD: Addr = IO_BASE + 0x_00C8; // 4 W DMA 2 Source Address + pub const REG_DMA2DAD: Addr = IO_BASE + 0x_00CC; // 4 W DMA 2 Destination Address + pub const REG_DMA2CNT_L: Addr = IO_BASE + 0x_00D0; // 2 W DMA 2 Word Count + pub const REG_DMA2CNT_H: Addr = IO_BASE + 0x_00D2; // 2 R/W DMA 2 Control + pub const REG_DMA3SAD: Addr = IO_BASE + 0x_00D4; // 4 W DMA 3 Source Address + pub const REG_DMA3DAD: Addr = IO_BASE + 0x_00D8; // 4 W DMA 3 Destination Address + pub const REG_DMA3CNT_L: Addr = IO_BASE + 0x_00DC; // 2 W DMA 3 Word Count + pub const REG_DMA3CNT_H: Addr = IO_BASE + 0x_00DE; // 2 R/W DMA 3 Control + // Timer Registers + pub const REG_TM0CNT_L: Addr = IO_BASE + 0x_0100; // 2 R/W Timer 0 Counter/Reload + pub const REG_TM0CNT_H: Addr = IO_BASE + 0x_0102; // 2 R/W Timer 0 Control + pub const REG_TM1CNT_L: Addr = IO_BASE + 0x_0104; // 2 R/W Timer 1 Counter/Reload + pub const REG_TM1CNT_H: Addr = IO_BASE + 0x_0106; // 2 R/W Timer 1 Control + pub const REG_TM2CNT_L: Addr = IO_BASE + 0x_0108; // 2 R/W Timer 2 Counter/Reload + pub const REG_TM2CNT_H: Addr = IO_BASE + 0x_010A; // 2 R/W Timer 2 Control + pub const REG_TM3CNT_L: Addr = IO_BASE + 0x_010C; // 2 R/W Timer 3 Counter/Reload + pub const REG_TM3CNT_H: Addr = IO_BASE + 0x_010E; // 2 R/W Timer 3 Control + // Serial Communication (1) + pub const REG_SIODATA32: Addr = IO_BASE + 0x_0120; // 4 R/W SIO Data (Normal-32bit Mode; shared with below) + pub const REG_SIOMULTI0: Addr = IO_BASE + 0x_0120; // 2 R/W SIO Data 0 (Parent) (Multi-Player Mode) + pub const REG_SIOMULTI1: Addr = IO_BASE + 0x_0122; // 2 R/W SIO Data 1 (1st Child) (Multi-Player Mode) + pub const REG_SIOMULTI2: Addr = IO_BASE + 0x_0124; // 2 R/W SIO Data 2 (2nd Child) (Multi-Player Mode) + pub const REG_SIOMULTI3: Addr = IO_BASE + 0x_0126; // 2 R/W SIO Data 3 (3rd Child) (Multi-Player Mode) + pub const REG_SIOCNT: Addr = IO_BASE + 0x_0128; // 2 R/W SIO Control Register + pub const REG_SIOMLT_SEND: Addr = IO_BASE + 0x_012A; // 2 R/W SIO Data (Local of MultiPlayer; shared below) + pub const REG_SIODATA8: Addr = IO_BASE + 0x_012A; // 2 R/W SIO Data (Normal-8bit and UART Mode) + // Keypad Input + pub const REG_KEYINPUT: Addr = IO_BASE + 0x_0130; // 2 R Key Status + pub const REG_KEYCNT: Addr = IO_BASE + 0x_0132; // 2 R/W Key Interrupt Control + // Serial Communication (2) + pub const REG_RCNT: Addr = IO_BASE + 0x_0134; // 2 R/W SIO Mode Select/General Purpose Data + pub const REG_IR: Addr = IO_BASE + 0x_0136; // - - Ancient - Infrared Register (Prototypes only) + pub const REG_JOYCNT: Addr = IO_BASE + 0x_0140; // 2 R/W SIO JOY Bus Control + pub const REG_JOY_RECV: Addr = IO_BASE + 0x_0150; // 4 R/W SIO JOY Bus Receive Data + pub const REG_JOY_TRANS: Addr = IO_BASE + 0x_0154; // 4 R/W SIO JOY Bus Transmit Data + pub const REG_JOYSTAT: Addr = IO_BASE + 0x_0158; // 2 R/? SIO JOY Bus Receive Status + // Interrupt, Waitstate, and Power-Down Control + pub const REG_IE: Addr = IO_BASE + 0x_0200; // 2 R/W Interrupt Enable Register + pub const REG_IF: Addr = IO_BASE + 0x_0202; // 2 R/W Interrupt Request Flags / IRQ Acknowledge + pub const REG_WAITCNT: Addr = IO_BASE + 0x_0204; // 2 R/W Game Pak Waitstate Control + pub const REG_IME: Addr = IO_BASE + 0x_0208; // 2 R/W Interrupt Master Enable Register + pub const REG_POSTFLG: Addr = IO_BASE + 0x_0300; // 1 R/W Undocumented - Post Boot Flag + pub const REG_HALTCNT: Addr = IO_BASE + 0x_0301; // 1 W Undocumented - Power Down Control +} + +use consts::*; + +#[derive(Debug)] +pub struct IoRegs { + bytes: Box<[u8]>, +} + +impl Default for IoRegs { + fn default() -> IoRegs { + let mut ioregs = IoRegs { + bytes: vec![0; 4096].into_boxed_slice(), + }; + + // init default values + ioregs.write_reg(REG_DISPCNT, 0x0080); + + ioregs + } +} + +impl IoRegs { + pub fn read_reg(&self, addr: Addr) -> u16 { + let result = self + .get_bytes(addr - IO_BASE) + .read_u16::() + .unwrap(); + result + } + + pub fn write_reg(&mut self, addr: Addr, value: u16) { + self.get_bytes_mut(addr - IO_BASE) + .write_u16::(value) + .unwrap(); + } +} + +impl Bus for IoRegs { + fn read_32(&self, addr: Addr) -> u32 { + self.get_bytes(addr).read_u32::().unwrap() + } + + fn read_16(&self, addr: Addr) -> u16 { + self.read_reg(IO_BASE + addr) + } + + fn read_8(&self, addr: Addr) -> u8 { + self.read_reg(IO_BASE + addr) as u8 + } + + fn write_32(&mut self, addr: Addr, value: u32) -> Result<(), io::Error> { + self.get_bytes_mut(addr).write_u32::(value) + } + + fn write_16(&mut self, addr: Addr, value: u16) -> Result<(), io::Error> { + self.write_reg(IO_BASE + addr, value); + Ok(()) + } + + fn write_8(&mut self, addr: Addr, value: u8) -> Result<(), io::Error> { + let new_value = self.read_reg(IO_BASE + addr) & 0xff00 | (value as u16); + self.write_reg(IO_BASE + addr, new_value); + Ok(()) + } + + /// Return a slice of bytes + fn get_bytes(&self, addr: Addr) -> &[u8] { + &self.bytes[addr as usize..] + } + + /// Return a mutable slice of bytes + fn get_bytes_mut(&mut self, addr: Addr) -> &mut [u8] { + &mut self.bytes[addr as usize..] + } + + /// returns the number of cycles needed for this memory access + fn get_cycles(&self, _addr: Addr, _access: MemoryAccess) -> usize { + 1 + } +} diff --git a/src/lcd.rs b/src/lcd.rs new file mode 100644 index 0000000..2e28b7a --- /dev/null +++ b/src/lcd.rs @@ -0,0 +1,110 @@ +use super::ioregs::consts::*; +use super::*; + +use crate::bit::BitIndex; +use crate::num::FromPrimitive; + +#[derive(Debug, Primitive)] +enum BGMode { + Mode0 = 0, + Mode1 = 1, + Mode2 = 2, + Mode3 = 3, + Mode4 = 4, + Mode5 = 5, +} + +#[derive(Debug)] +pub struct DisplayControl { + bg_mode: BGMode, + display_frame: usize, + hblank_interval_free: bool, + obj_character_vram_mapping: bool, // true - 1 dimentional, false - 2 dimentional + forced_blank: bool, + disp_bg0: bool, + disp_bg1: bool, + disp_bg2: bool, + disp_bg3: bool, + disp_obj: bool, + disp_window0: bool, + disp_window1: bool, + disp_obj_window: bool, +} + +impl From for DisplayControl { + fn from(v: u16) -> Self { + DisplayControl { + bg_mode: BGMode::from_u8(v.bit_range(0..2) as u8).unwrap(), + // bit 3 is unused + display_frame: v.bit(4) as usize, + hblank_interval_free: v.bit(5), + obj_character_vram_mapping: v.bit(6), + forced_blank: v.bit(7), + disp_bg0: v.bit(8), + disp_bg1: v.bit(9), + disp_bg2: v.bit(10), + disp_bg3: v.bit(11), + disp_obj: v.bit(12), + disp_window0: v.bit(13), + disp_window1: v.bit(14), + disp_obj_window: v.bit(15), + } + } +} + +#[derive(Debug)] +pub struct DisplayStatus { + vblank_flag: bool, + hblank_flag: bool, + vcount_flag: bool, + vblank_irq_enable: bool, + hblank_irq_enable: bool, + vcount_irq_enable: bool, + vcount_setting: u8, +} + +impl From for DisplayStatus { + fn from(v: u16) -> Self { + DisplayStatus { + vblank_flag: v.bit(0), + hblank_flag: v.bit(1), + vcount_flag: v.bit(2), + vblank_irq_enable: v.bit(3), + hblank_irq_enable: v.bit(4), + vcount_irq_enable: v.bit(5), + // bits 6-7 are unused in GBA + vcount_setting: v.bit_range(8..15) as u8, + } + } +} + +#[derive(Debug, Default)] +pub struct Lcd { + cycles: usize, + current_scanline: usize, // VCOUNT +} + +impl Lcd { + const DISPLAY_WIDTH: usize = 240; + const DISPLAY_HEIGHT: usize = 160; + + pub fn new() -> Lcd { + Lcd { + ..Default::default() + } + } +} + +impl EmuIoDev for Lcd { + fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> Option { + self.cycles += cycles; + + let mut result = None; + let mut dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT)); + let mut dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT)); + + // TODO + + result + } +} diff --git a/src/lib.rs b/src/lib.rs index ab3dce9..5310dfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,4 +19,43 @@ pub mod cartridge; pub mod debugger; pub mod disass; pub mod sysbus; +pub use sysbus::SysBus; +pub mod interrupt; +pub mod ioregs; +pub use interrupt::Interrupt; +pub mod gba; +pub use gba::GameBoyAdvance; +pub mod lcd; +pub mod palette; pub mod util; + +pub trait EmuIoDev { + fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> Option; +} + +#[derive(Debug)] +pub enum GBAError { + IO(::std::io::Error), + CpuError(arm7tdmi::CpuError), + DebuggerError(debugger::DebuggerError), +} + +pub type GBAResult = Result; + +impl From<::std::io::Error> for GBAError { + fn from(err: ::std::io::Error) -> GBAError { + GBAError::IO(err) + } +} + +impl From for GBAError { + fn from(err: arm7tdmi::CpuError) -> GBAError { + GBAError::CpuError(err) + } +} + +impl From for GBAError { + fn from(err: debugger::DebuggerError) -> GBAError { + GBAError::DebuggerError(err) + } +} diff --git a/src/palette.rs b/src/palette.rs new file mode 100644 index 0000000..351a11e --- /dev/null +++ b/src/palette.rs @@ -0,0 +1,59 @@ +use std::fmt; + +use byteorder::{LittleEndian, ReadBytesExt}; +use std::io::Cursor; + +#[derive(Debug, Copy, Clone)] +pub struct Rgb15 { + pub r: u8, + pub g: u8, + pub b: u8, +} + +impl From for Rgb15 { + fn from(v: u16) -> Rgb15 { + use bit::BitIndex; + Rgb15 { + r: v.bit_range(0..5) as u8, + g: v.bit_range(5..10) as u8, + b: v.bit_range(10..15) as u8, + } + } +} + +impl fmt::Display for Rgb15 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Rgb15({:#x},{:#x},{:#x})", self.r, self.g, self.b) + } +} + +impl Rgb15 { + /// Convert 15-bit high color to a 24-bit true color. + pub fn get_rgb24(&self) -> (u8, u8, u8) { + (self.r << 3, self.g << 3, self.b << 3) + } +} + +pub struct Palette { + pub bg_colors: [Rgb15; 256], + pub fg_colors: [Rgb15; 256], +} + +impl From<&[u8]> for Palette { + fn from(bytes: &[u8]) -> Palette { + let mut rdr = Cursor::new(bytes); + + let mut bg_colors: [Rgb15; 256] = [0.into(); 256]; + for i in 0..256 { + bg_colors[i] = rdr.read_u16::().unwrap().into(); + } + let mut fg_colors: [Rgb15; 256] = [0.into(); 256]; + for i in 0..256 { + fg_colors[i] = rdr.read_u16::().unwrap().into(); + } + Palette { + bg_colors, + fg_colors, + } + } +} diff --git a/src/sysbus.rs b/src/sysbus.rs index dfbef43..dae1193 100644 --- a/src/sysbus.rs +++ b/src/sysbus.rs @@ -1,6 +1,6 @@ use std::io; -use crate::cartridge::Cartridge; +use super::{cartridge::Cartridge, ioregs::IoRegs}; use super::arm7tdmi::bus::{Bus, MemoryAccess, MemoryAccessWidth}; use super::arm7tdmi::Addr; @@ -111,7 +111,7 @@ pub struct SysBus { onboard_work_ram: BoxedMemory, internal_work_ram: BoxedMemory, /// Currently model the IOMem as regular buffer, later make it into something more sophisticated. - ioregs: BoxedMemory, + pub ioregs: IoRegs, palette_ram: BoxedMemory, vram: BoxedMemory, oam: BoxedMemory, @@ -128,7 +128,7 @@ impl SysBus { WaitState::new(3, 3, 6), ), internal_work_ram: BoxedMemory::new(vec![0; INTERNAL_RAM].into_boxed_slice()), - ioregs: BoxedMemory::new(vec![0; 1024].into_boxed_slice()), + ioregs: IoRegs::default(), palette_ram: BoxedMemory::new_with_waitstate( vec![0; PALETTE_RAM_SIZE].into_boxed_slice(), WaitState::new(1, 1, 2),