From 75bc0ab3327aed94292cd7955498ab4c3477be24 Mon Sep 17 00:00:00 2001 From: Donisha Smith Date: Mon, 20 Jul 2026 09:12:53 -0700 Subject: [PATCH 01/18] Create README.md --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d9a5b8 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Rust Gameboy Advance emulator. From 2460bc0c2a618221a241c4cccb92ddc50d7b158b Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Mon, 20 Jul 2026 22:20:47 -0700 Subject: [PATCH 02/18] First commit; mapping out bus --- .gitignore | 1 + Cargo.lock | 7 ++++++ Cargo.toml | 6 +++++ src/components/memory/bus.rs | 49 ++++++++++++++++++++++++++++++++++++ src/main.rs | 7 ++++++ 5 files changed, 70 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/components/memory/bus.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b4a887b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "gba" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..437fcf9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "gba" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/components/memory/bus.rs b/src/components/memory/bus.rs new file mode 100644 index 0000000..cfcb8e6 --- /dev/null +++ b/src/components/memory/bus.rs @@ -0,0 +1,49 @@ +// https://problemkaputt.de/gbatek.htm#gbamemorymap + +pub struct Bus { + +} + +impl Bus { + pub fn read(&self, address: u32) { + match address { + // General Internal Memory + 0x00000000..=0x00003FFF => {}, // BIOS + 0x00004000..=0x01FFFFFF => {}, // Not Used + 0x02000000..=0x0203FFFF => {}, // On-board WRAM + 0x03000000..=0x03007FFF => {}, // On-chip WRAM + 0x03008000..=0x03FFFFFF => {}, // Not used + 0x04000000..=0x040003FE => self.read_register(address), // I/O register + 0x04000400..=0x04FFFFFF => {}, // Not Used + + // Internal Display Memory + 0x05000000..=0x050003FF => {}, // BG/OBJ Palette RAM + 0x05000400..=0x05FFFFFF => {}, // Not Used + 0x06000000..=0x06017FFF => {}, // VRAM + 0x06018000..=0x06FFFFFF => {}, // Not Used + 0x07000000..=0x070003FF => {}, // OAM + 0x07000400..=0x07FFFFFF => {}, // Not Used + + // External Memory (Game Pak/ROM) + 0x08000000..=0x09FFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 0 + 0x0A000000..=0x0BFFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 1 + 0x0C000000..=0x0DFFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 2 + 0x0E000000..=0x0E00FFFF => {}, // Game Pak SRAM (max 64 KBytes) - 8bit Bus width + 0x0E010000..=0x0FFFFFFF => {}, // Not used + + // Unused Memory Area + 0x10000000..=0xFFFFFFFF => {} // Not used (upper 4bits of address bus unused) + } + + // TODO: Map out all IO registers + // Eventually, each IO component will have its own + // read and write registers function to route to + fn read_register(&self, address: u32) { + + } + + pub fn write(&mut self) {} + + fn write_register() {} +} +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3cf0a4e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,7 @@ +// https://www.copetti.org/writings/consoles/game-boy-advance/ +// https://mgba.io/2015/06/27/cycle-counting-prefetch/ +// https://www.gregorygaines.com/blog/decoding-the-arm7tdmi-instruction-set-game-boy-advance/ + +fn main() { + println!("Hello, world!"); +} From 8421ea15798b51f8af7538ad103f6b92bde57e0c Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Tue, 21 Jul 2026 23:07:40 -0700 Subject: [PATCH 03/18] Add resource --- src/components/memory/bus.rs | 15 ++++++++++++--- src/main.rs | 3 +++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/memory/bus.rs b/src/components/memory/bus.rs index cfcb8e6..2ba1dcc 100644 --- a/src/components/memory/bus.rs +++ b/src/components/memory/bus.rs @@ -5,7 +5,7 @@ pub struct Bus { } impl Bus { - pub fn read(&self, address: u32) { + pub fn read>(&self, address: u32) -> T { match address { // General Internal Memory 0x00000000..=0x00003FFF => {}, // BIOS @@ -38,11 +38,20 @@ impl Bus { // TODO: Map out all IO registers // Eventually, each IO component will have its own // read and write registers function to route to - fn read_register(&self, address: u32) { + fn read_register>(&self, address: u32) -> T { + match address { + 0x4000000 => {}, // LCD Control (DISPCNT), 16 bit register (read + write) + 0x4000002 => {}, // Undocumented 16 bit register (read + write) + 0x4000004 => {}, // Stat & LYC, 16 bit register (read + write) + 0x4000006 => {}, // LY, 16 bit, (VCOUNT), read only + 0x4000008 => {}, // LCD Control + } } - pub fn write(&mut self) {} + pub fn write>(&mut self, address: u32, value: T) { + + } fn write_register() {} } diff --git a/src/main.rs b/src/main.rs index 3cf0a4e..d882dfb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ // https://www.copetti.org/writings/consoles/game-boy-advance/ // https://mgba.io/2015/06/27/cycle-counting-prefetch/ // https://www.gregorygaines.com/blog/decoding-the-arm7tdmi-instruction-set-game-boy-advance/ +// https://ia903206.us.archive.org/34/items/NintendoGbaManualV1.1/Nintendo%20Gba%20Manual%20V1.1.pdf +// https://ww1.microchip.com/downloads/en/DeviceDoc/DDI0029G_7TDMI_R3_trm.pdf + fn main() { println!("Hello, world!"); From 1d94f4b0dabe4706c0458adbba16f6de3b45646c Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Wed, 22 Jul 2026 09:06:37 -0700 Subject: [PATCH 04/18] Minor bus update --- src/components/memory/bus.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/memory/bus.rs b/src/components/memory/bus.rs index 2ba1dcc..8c56389 100644 --- a/src/components/memory/bus.rs +++ b/src/components/memory/bus.rs @@ -45,7 +45,6 @@ impl Bus { 0x4000004 => {}, // Stat & LYC, 16 bit register (read + write) 0x4000006 => {}, // LY, 16 bit, (VCOUNT), read only 0x4000008 => {}, // LCD Control - } } From 0dcb015fd152c9f5e22a5bb2708307d6b801fc67 Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Wed, 22 Jul 2026 23:36:01 -0700 Subject: [PATCH 05/18] Additional memory mapping --- src/components/memory/bus.rs | 148 ++++++++++++++++++++++++++++++++++- src/main.rs | 1 - 2 files changed, 145 insertions(+), 4 deletions(-) diff --git a/src/components/memory/bus.rs b/src/components/memory/bus.rs index 8c56389..94a1584 100644 --- a/src/components/memory/bus.rs +++ b/src/components/memory/bus.rs @@ -33,6 +33,7 @@ impl Bus { // Unused Memory Area 0x10000000..=0xFFFFFFFF => {} // Not used (upper 4bits of address bus unused) + } } // TODO: Map out all IO registers @@ -40,18 +41,159 @@ impl Bus { // read and write registers function to route to fn read_register>(&self, address: u32) -> T { match address { + // LCD I/O Registers 0x4000000 => {}, // LCD Control (DISPCNT), 16 bit register (read + write) 0x4000002 => {}, // Undocumented 16 bit register (read + write) 0x4000004 => {}, // Stat & LYC, 16 bit register (read + write) 0x4000006 => {}, // LY, 16 bit, (VCOUNT), read only - 0x4000008 => {}, // LCD Control + 0x4000008 => {}, // BG0 Control (BG0CNT) 16 bit register (read + write) + 0x400000A => {}, // BG1 Control (BG1CNT) 16 bit register (read + write) + 0x400000C => {}, // BG2 Control (BG2CNT) 16 bit register (read + write) + 0x400000E => {}, // BG3 Control (BG3CNT) 16 bit register (read + write) + 0x4000048 => {}, // Inside of Window 0 and 1 (WININ), 16 bit register (read + write) + 0x400004A => {}, // Inside of OBJ Window & Outside of Windows 2 (WINOUT) (read + write) + 0x400004E => {}, // Not Used + 0x4000050 => {}, // Color Special Effects Selection (BLDCNT), 16 bit register (read + write) + 0x4000052 => {}, // Alpha Blending Coefficients (BLDALPHA), 16 bit register (read + write) + 0x4000056 => {}, // Not Used + + // Sound Registers + 0x4000060 => {}, // Channel 1 Sweep register (NR10) (SOUND1CNT_L), 16 bit register (read + write) + 0x4000062 => {}, // Channel 1 Duty/Length/Envelope (NR11, NR12) (SOUND1CNT_H), 16 bit register (read + write) + 0x4000064 => {}, // Channel 1 Frequency/Control (NR13, NR14) (SOUND1CNT_X), 16 bit register (read + write) + 0x4000066 => {}, // Not Used + 0x4000068 => {}, // Channel 2 Duty/Length/Envelope (NR21, NR22) (SOUND2CNT_L), 16 bit register (read + write) + 0x400006A => {}, // Not Used + 0x400006C => {}, // Channel 2 Frequency/Control (NR23, NR24) (SOUND2CNT_H), 16 bit register (read + write) + 0x400006E => {}, // Not Used + 0x4000070 => {}, // Channel 3 Stop/Wave RAM select (NR30) (SOUND3CNT_L), 16 bit register (read + write) + 0x4000072 => {}, // Channel 3 Length/Volume (NR31, NR32), 16 bit register (read + write) + 0x4000074 => {}, // Channel 3 Frequency/Control (NR33, NR34) (SOUND3CNT_X), 16 bit register (read + write) + 0x4000076 => {}, // Not Used + 0x4000078 => {}, // Channel 4 Length/Envelope (NR41, NR42) (SOUND4CNT_L), 16 bit register (read + write) + 0x400007A => {}, // Not Used + 0x400007C => {}, // Channel 4 Frequency/Control (NR43, NR44) (SOUND4CNT_H), 16 bit register (read + write) + 0x400007E => {}, // Not Used + 0x4000080 => {}, // Control Stereo/Volume/Enable (NR50, NR51) (SOUNDCNT_L), 16 bit register (read + write) + 0x4000082 => {}, // Control Mixing/DMA Control (SOUNDCNT_H), 16 bit register (read + write) + 0x4000084 => {}, // Control Sound on/off (NR52) (SOUNDCNT_X), 16 bit register (read + write) + 0x4000086 => {}, // Not Used + 0x4000088 => {}, // BIOS/Sound PWM Control (SOUNDBIAS), 16 bit register (read + write) + 0x400008A => {}, // Not Used + 0x4000090 => {}, // Channel 3 Wave Pattern RAM (2 banks) (WAVE_RAM) 2x10h in size, (read + write) + 0x40000A8 => {}, // Not Used + + // DMA Transfer Channels + 0x40000BA => {}, // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) } } pub fn write>(&mut self, address: u32, value: T) { + match address { + // General Internal Memory + 0x00004000..=0x01FFFFFF => {}, // Not Used + 0x02000000..=0x0203FFFF => {}, // On-board WRAM + 0x03000000..=0x03007FFF => {}, // On-chip WRAM + 0x03008000..=0x03FFFFFF => {}, // Not used + 0x04000000..=0x040003FE => self.write_register(address, value), // I/O register + 0x04000400..=0x04FFFFFF => {}, // Not Used + + // Internal Display Memory + 0x05000000..=0x050003FF => {}, // BG/OBJ Palette RAM + 0x05000400..=0x05FFFFFF => {}, // Not Used + 0x06000000..=0x06017FFF => {}, // VRAM + 0x06018000..=0x06FFFFFF => {}, // Not Used + 0x07000000..=0x070003FF => {}, // OAM + 0x07000400..=0x07FFFFFF => {}, // Not Used + // External Memory (Game Pak/ROM) + 0x08000000..=0x09FFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 0 + 0x0A000000..=0x0BFFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 1 + 0x0C000000..=0x0DFFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 2 + 0x0E000000..=0x0E00FFFF => {}, // Game Pak SRAM (max 64 KBytes) - 8bit Bus width + 0x0E010000..=0x0FFFFFFF => {}, // Not used + + // Unused Memory Area + 0x10000000..=0xFFFFFFFF => {} // Not used (upper 4bits of address bus unused) + } } - fn write_register() {} + fn write_register>(&mut self, address: u32, value: T) { + match address { + // LCD I/O Registers + 0x4000000 => {}, // LCD Control (DISPCNT), 16 bit register (read + write) + 0x4000002 => {}, // Undocumented 16 bit register (read + write) + 0x4000004 => {}, // Stat & LYC, 16 bit register (read + write) + 0x4000008 => {}, // BG0 Control (BG0CNT) 16 bit register (read + write) + 0x400000A => {}, // BG1 Control (BG1CNT) 16 bit register (read + write) + 0x400000C => {}, // BG2 Control (BG2CNT) 16 bit register (read + write) + 0x400000E => {}, // BG3 Control (BG3CNT) 16 bit register (read + write) + 0x4000010 => {}, // BG0 X-Offset (BG0HOFS) 16 bit register (write only) + 0x4000012 => {}, // BG0 Y-Offset (BG0VOFS) 16 bit register (write only) + 0x4000014 => {}, // BG1 X-Offset (BG1HOFS) 16 bit register (write only) + 0x4000016 => {}, // BG1 Y-Offset (BG1VOFS) 16 bit register (write only) + 0x4000018 => {}, // BG2 X-Offset (BG2HOFS) 16 bit register (write only) + 0x400001A => {}, // BG2 Y-Offset (BG2VOFS) 16 bit register (write only) + 0x400001C => {}, // BG3 X-Offset (BG3HOFS) 16 bit register (write only) + 0x400001E => {}, // BG3 Y-Offset (BG3VOFS) 16 bit register (write only) + 0x4000020 => {}, // BG2 Rotation/Scaling Parameter A (dx) (BG2PA), 16 bit register (write only) + 0x4000022 => {}, // BG2 Rotation/Scaling Parameter B (dmx) (BG2PB), 16 bit register (write only) + 0x4000024 => {}, // BG2 Rotation/Scaling Parameter C (dy) (BG2PC), 16 bit register (write only) + 0x4000026 => {}, // BG2 Rotation/Scaling Parameter D (dmy) (BG2PD), 16 bit register (write only) + 0x4000028 => {}, // BG2 Reference Point X-Coordinate (BG2X), 32 bit register (write only) + 0x400002C => {}, // BG2 Reference Point Y-Coordinate (BG2Y), 32 bit register (write only) + 0x4000030 => {}, // BG3 Rotation/Scaling Parameter A (dx) (BG3PA), 16 bit register (write only) + 0x4000032 => {}, // BG3 Rotation/Scaling Parameter B (dmx) (BG3PB), 16 bit register (write only) + 0x4000034 => {}, // BG3 Rotation/Scaling Parameter C (dy) (BG3PC), 16 bit register (write only) + 0x4000036 => {}, // BG3 Rotation/Scaling Parameter D (dmy) (BG3PD), 16 bit register (write only) + 0x4000038 => {}, // BG3 Reference Point X-Coordinate (BG3X), 32 bit register (write only) + 0x400003C => {}, // BG3 Reference Point Y-Coordinate (BG3Y), 32 bit register (write only) + 0x4000040 => {}, // Window 0 Horizontal Dimensions (WIN0H), 16 bit register (write only) + 0x4000042 => {}, // Window 1 Horizontal Dimensions (WIN1H), 16 bit register (write only) + 0x4000044 => {}, // Window 0 Vertical Dimensions (WIN0V), 16 bit register (write only) + 0x4000046 => {}, // Window 1 Vertical Dimensions (WIN1V), 16 bit register (write only) + 0x4000048 => {}, // Inside of Window 0 and 1 (WININ), 16 bit register (read + write) + 0x400004A => {}, // Inside of OBJ Window & Outside of Windows 2 (WINOUT), 16 bit register (read + write) + 0x400004C => {}, // Mosaic Size (MOSAIC), 16 bit register (write only) + 0x400004E => {}, // Not Used + 0x4000050 => {}, // Color Special Effects Selection (BLDCNT), 16 bit register (read + write) + 0x4000052 => {}, // Alpha Blending Coefficients (BLDALPHA), 16 bit register (read + write) + 0x4000054 => {}, // Brightness (Fade-In/Out) Coefficient (BLDY), 16 bit register (write only) + 0x4000056 => {}, // Not Used + + // Sound Registers + 0x4000060 => {}, // Channel 1 Sweep register (NR10) (SOUND1CNT_L), 16 bit register (read + write) + 0x4000062 => {}, // Channel 1 Duty/Length/Envelope (NR11, NR12) (SOUND1CNT_H), 16 bit register (read + write) + 0x4000064 => {}, // Channel 1 Frequency/Control (NR13, NR14) (SOUND1CNT_X), 16 bit register (read + write) + 0x4000066 => {}, // Not Used + 0x4000068 => {}, // Channel 2 Duty/Length/Envelope (NR21, NR22) (SOUND2CNT_L), 16 bit register (read + write) + 0x400006A => {}, // Not Used + 0x400006C => {}, // Channel 2 Frequency/Control (NR23, NR24) (SOUND2CNT_H), 16 bit register (read + write) + 0x400006E => {}, // Not Used + 0x4000070 => {}, // Channel 3 Stop/Wave RAM select (NR30) (SOUND3CNT_L), 16 bit register (read + write) + 0x4000072 => {}, // Channel 3 Length/Volume (NR31, NR32), 16 bit register (read + write) + 0x4000074 => {}, // Channel 3 Frequency/Control (NR33, NR34) (SOUND3CNT_X), 16 bit register (read + write) + 0x4000076 => {}, // Not Used + 0x4000078 => {}, // Channel 4 Length/Envelope (NR41, NR42) (SOUND4CNT_L), 16 bit register (read + write) + 0x400007A => {}, // Not Used + 0x400007C => {}, // Channel 4 Frequency/Control (NR43, NR44) (SOUND4CNT_H), 16 bit register (read + write) + 0x400007E => {}, // Not Used + 0x4000080 => {}, // Control Stereo/Volume/Enable (NR50, NR51) (SOUNDCNT_L), 16 bit register (read + write) + 0x4000082 => {}, // Control Mixing/DMA Control (SOUNDCNT_H), 16 bit register (read + write) + 0x4000084 => {}, // Control Sound on/off (NR52) (SOUNDCNT_X), 16 bit register (read + write) + 0x4000086 => {}, // Not Used + 0x4000088 => {}, // BIOS/Sound PWM Control (SOUNDBIAS), 16 bit register (read + write) + 0x400008A => {}, // Not Used + 0x4000090 => {}, // Channel 3 Wave Pattern RAM (2 banks) (WAVE_RAM) 2x10h in size, (read + write) + 0x40000A0 => {}, // Channel A FIFO, Data 0-3, (FIFO_A) (write only), 32 bit register + 0x40000A4 => {}, // Channel B FIFO, Data 0-3, (FIFO_B) (write only), 32 bit register + 0x40000A8 => {}, // Not Used + + // DMA Transfer Channels + 0x40000B0 => {}, // DMA 0 Source Address (DMA0SAD), 32 bit register (write only) + 0x40000B4 => {}, // DMA 0 Destination Address (DMA0DAD), 32 bit register (write only) + 0x40000B8 => {}, // DMA 0 Word Count (DMA0CNT_L), 16 bit register (write only) + 0x40000BA => {}, // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) + } + } } -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index d882dfb..dd97a5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ // https://ia903206.us.archive.org/34/items/NintendoGbaManualV1.1/Nintendo%20Gba%20Manual%20V1.1.pdf // https://ww1.microchip.com/downloads/en/DeviceDoc/DDI0029G_7TDMI_R3_trm.pdf - fn main() { println!("Hello, world!"); } From c47dc5b43d894ec228282fa1f221fd90036aba02 Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Wed, 22 Jul 2026 23:36:55 -0700 Subject: [PATCH 06/18] Add reminder --- src/components/memory/bus.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/memory/bus.rs b/src/components/memory/bus.rs index 94a1584..ea6633f 100644 --- a/src/components/memory/bus.rs +++ b/src/components/memory/bus.rs @@ -85,6 +85,7 @@ impl Bus { // DMA Transfer Channels 0x40000BA => {}, // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) + // Start back at 40000C6h } } @@ -194,6 +195,9 @@ impl Bus { 0x40000B4 => {}, // DMA 0 Destination Address (DMA0DAD), 32 bit register (write only) 0x40000B8 => {}, // DMA 0 Word Count (DMA0CNT_L), 16 bit register (write only) 0x40000BA => {}, // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) + 0x40000BC => {}, // DMA 1 Source Address (DMA1SAD), 32 bit register (write only) + 0x40000C0 => {}, // DMA 1 Destination Address (DMA1DAD), 32 bit register (write only) + // Start back at 40000C6h } } } From 0a761723363d27ff3d54fece655df2a3c86d0c63 Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Thu, 23 Jul 2026 13:30:23 -0700 Subject: [PATCH 07/18] Map rest of memory map --- src/components/memory/bus.rs | 118 ++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/src/components/memory/bus.rs b/src/components/memory/bus.rs index ea6633f..995f737 100644 --- a/src/components/memory/bus.rs +++ b/src/components/memory/bus.rs @@ -85,7 +85,61 @@ impl Bus { // DMA Transfer Channels 0x40000BA => {}, // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) - // Start back at 40000C6h + 0x40000C6 => {}, // DMA 1 Control (DMA1CNT_H), 16 bit register (read + write) + 0x40000D2 => {}, // DMA 2 Control (DMA2CNT_H), 16 bit register (read + write) + 0x40000DE => {}, // DMA 3 Control (DMA3CNT_H), 16 bit register (read + write) + 0x40000E0 => {}, // Not Used + + // Timer Registers + 0x4000100 => {}, // Timer 0 Counter/Reload (TM0CNT_L), 16 bit register (read + write) + 0x4000102 => {}, // Timer 0 Control (TM0CNT_H), 16 bit register (read + write) + 0x4000104 => {}, // Timer 1 Counter/Reload (TM1CNT_L), 16 bit register (read + write) + 0x4000106 => {}, // Timer 1 Control (TM1CNT_H), 16 bit register (read + write) + 0x4000108 => {}, // Timer 2 Counter/Reload (TM2CNT_L), 16 bit register (read + write) + 0x400010A => {}, // Timer 2 Control (TM2CNT_H), 16 bit register (read + write) + 0x400010C => {}, // Timer 3 Counter/Reload (TM3CNT_L), 16 bit register (read + write) + 0x400010E => {}, // Timer 3 Control (TM3CNT_H), 16 bit register (read + write) + 0x4000110 => {}, // Not Used + + // Serial Communication (1) + 0x4000120 => {}, // SIO Data (Normal-32bit Mode; shared with SIO Data 0 (Parent) (SIODATA32). SIO Data is a 32 bit register and SIO Data 0 (Parent) (Multi-Player Mode) is a 16 bit register (read + write) (SIOMULTI0) + 0x4000122 => {}, // SIO Data 1 (1st Child) (Multi-Player Mode) (SIOMULTI1), 16 bit register (read + write) + 0x4000124 => {}, // SIO Data 2 (2nd Child) (Multi-Player Mode) (SIOMULTI2), 16 bit register (read + write) + 0x4000126 => {}, // SIO Data 3 (3rd Child) (Multi-Player Mode) (SIOMULTI3), 16 bit register (read + write) + 0x4000128 => {}, // SIO Control Register (SIOCNT), 16 bit register (read + write) + 0x400012A => {}, // SIO Data (Local of MultiPlayer; shared with SIODATA8) (SIOMLT_SEND), 16 bit register (read + write); SIO Data (Normal-8bit and UART Mode) (SIODATA8), 16 bit register (read + write) + 0x400012C => {}, // Not Used + + // Keypad Input + 0x4000130 => {}, // Key Status (KEYINPUT), 16 bit register read only + 0x4000132 => {}, // Key Interrupt Control (KEYCNT), 16 bit register (read + write) + + // Serial Communication (2) + 0x4000134 => {}, // SIO Mode Select/General Purpose Data (RCNT), 16 bit register (read + write) + 0x4000136 => {}, // Ancient - Infrared Register (Prototypes only) (IR) + 0x4000138 => {}, // Not Used + 0x4000140 => {}, // SIO JOY Bus Control (JOYCNT), 16 bit register (read + write) + 0x4000142 => {}, // Not Used + 0x4000150 => {}, // SIO JOY Bus Receive Data (JOY_RECV), 32 bit register (read + write) + 0x4000154 => {}, // SIO JOY Bus Transmit Data (JOY_TRANS), 32 bit register (read + write) + 0x4000158 => {}, // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + maybe write?) + 0x400015A => {}, // Not Used + + //Interrupt, Waitstate, and Power-Down Control + 0x4000200 => {}, // Interrupt Enable Register (IE), 16 bit register (read + write) + 0x4000202 => {}, // Interrupt Request Flags / IRQ Acknowledge (IF), 16 bit register (read + write) + 0x4000204 => {}, // Game Pak Waitstate Control (AITCNT), 16 bit register (read + write) + 0x4000206 => {}, // Not used + 0x4000208 => {}, // Interrupt Master Enable Register (IME), 16 bit register (read + write) + 0x400020A => {}, // Not used + 0x4000300 => {}, // Undocumented - Post Boot Flag (POSTFLG), 8 bit register (read + write) + 0x4000301 => {}, // Undocumented - Power Down Control (HALTCNT), 8 bit register (write only) + 0x4000302 => {}, // Not used + 0x4000410 => {}, // Undocumented - Purpose Unknown / Bug ??? 0FFh + 0x4000411 => {}, // Not used + 0x4000800 => {}, // Undocumented - Internal Memory Control, 32 bit register (read + write) + 0x4000804 => {}, // Not used + address if (address & 0xFF00FFFF) == 0x04000800 => {}, // Mirrors of 4000800h (repeated each 64K), 32 bit (read + write) } } @@ -197,7 +251,67 @@ impl Bus { 0x40000BA => {}, // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) 0x40000BC => {}, // DMA 1 Source Address (DMA1SAD), 32 bit register (write only) 0x40000C0 => {}, // DMA 1 Destination Address (DMA1DAD), 32 bit register (write only) - // Start back at 40000C6h + 0x40000C6 => {}, // DMA 1 Control (DMA1CNT_H), 16 bit register (read + write) + 0x40000C8 => {}, // DMA 2 Source Address (DMA2SAD), 32 bit register (write only) + 0x40000CC => {}, // DMA 2 Destination Address (DMA2DAD), 32 bit register (write only) + 0x40000D0 => {}, // DMA 2 Word Count (DMA2CNT_L), 16 bit register (write only) + 0x40000D2 => {}, // DMA 2 Control (DMA2CNT_H), 16 bit register (read + write) + 0x40000D4 => {}, // DMA 3 Source Address (DMA3SAD), 32 bit register (write only) + 0x40000D8 => {}, // DMA 3 Destination Address (DMA3DAD), 32 bit register (write only) + 0x40000DC => {}, // DMA 3 Word Count (DMA3CNT_L), 16 bit register (write only) + 0x40000DE => {}, // DMA 3 Control (DMA3CNT_H), 16 bit register (read + write) + 0x40000E0 => {}, // Not Used + + // Timer Registers + 0x4000100 => {}, // Timer 0 Counter/Reload (TM0CNT_L), 16 bit register (read + write) + 0x4000102 => {}, // Timer 0 Control (TM0CNT_H), 16 bit register (read + write) + 0x4000104 => {}, // Timer 1 Counter/Reload (TM1CNT_L), 16 bit register (read + write) + 0x4000106 => {}, // Timer 1 Control (TM1CNT_H), 16 bit register (read + write) + 0x4000108 => {}, // Timer 2 Counter/Reload (TM2CNT_L), 16 bit register (read + write) + 0x400010A => {}, // Timer 2 Control (TM2CNT_H), 16 bit register (read + write) + 0x400010C => {}, // Timer 3 Counter/Reload (TM3CNT_L), 16 bit register (read + write) + 0x400010E => {}, // Timer 3 Control (TM3CNT_H), 16 bit register (read + write) + 0x4000110 => {}, // Not Usedrol (TM3CNT_H), 16 bit register (read + write) + 0x4000110 => {} // Not Used + + // Serial Communication (1) + 0x4000120 => {}, // SIO Data (Normal-32bit Mode; shared with SIO Data 0 (Parent) (SIODATA32). SIO Data is a 32 bit register and SIO Data 0 (Parent) (Multi-Player Mode) is a 16 bit register (read + write) (SIOMULTI0) + 0x4000122 => {}, // SIO Data 1 (1st Child) (Multi-Player Mode) (SIOMULTI1), 16 bit register (read + write) + 0x4000124 => {}, // SIO Data 2 (2nd Child) (Multi-Player Mode) (SIOMULTI2), 16 bit register (read + write) + 0x4000126 => {}, // SIO Data 3 (3rd Child) (Multi-Player Mode) (SIOMULTI3), 16 bit register (read + write) + 0x4000128 => {}, // SIO Control Register (SIOCNT), 16 bit register (read + write) + 0x400012A => {}, // SIO Data (Local of MultiPlayer; shared with SIODATA8) (SIOMLT_SEND), 16 bit register (read + write); SIO Data (Normal-8bit and UART Mode) (SIODATA8), 16 bit register (read + write) + 0x400012C => {}, // Not Used + + // Keypad Input + 0x4000132 => {}, // Key Interrupt Control (KEYCNT), 16 bit register (read + write) + + // Serial Communication (2) + 0x4000134 => {}, // SIO Mode Select/General Purpose Data (RCNT), 16 bit register (read + write) + 0x4000136 => {}, // Ancient - Infrared Register (Prototypes only) (IR) + 0x4000138 => {}, // Not Used + 0x4000140 => {}, // SIO JOY Bus Control (JOYCNT), 16 bit register (read + write) + 0x4000142 => {}, // Not Used + 0x4000150 => {}, // SIO JOY Bus Receive Data (JOY_RECV), 32 bit register (read + write) + 0x4000154 => {}, // SIO JOY Bus Transmit Data (JOY_TRANS), 32 bit register (read + write) + 0x4000158 => {}, // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + maybe write?) + 0x400015A => {}, // Not Used + + //Interrupt, Waitstate, and Power-Down Control + 0x4000200 => {}, // Interrupt Enable Register (IE), 16 bit register (read + write) + 0x4000202 => {}, // Interrupt Request Flags / IRQ Acknowledge (IF), 16 bit register (read + write) + 0x4000204 => {}, // Game Pak Waitstate Control (AITCNT), 16 bit register (read + write) + 0x4000206 => {}, // Not used + 0x4000208 => {}, // Interrupt Master Enable Register (IME), 16 bit register (read + write) + 0x400020A => {}, // Not used + 0x4000300 => {}, // Undocumented - Post Boot Flag (POSTFLG), 8 bit register (read + write) + 0x4000301 => {}, // Undocumented - Power Down Control (HALTCNT), 8 bit register (write only) + 0x4000302 => {}, // Not used + 0x4000410 => {}, // Undocumented - Purpose Unknown / Bug ??? 0FFh + 0x4000411 => {}, // Not used + 0x4000800 => {}, // Undocumented - Internal Memory Control, 32 bit register (read + write) + 0x4000804 => {}, // Not used + address if (address & 0xFF00FFFF) == 0x04000800 => {}, // Mirrors of 4000800h (repeated each 64K), 32 bit (read + write) } } } From 35071e7e7d7208c43954a225a493eabb4a5b4398 Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Fri, 24 Jul 2026 09:53:43 -0700 Subject: [PATCH 08/18] Schedule --- src/components/scheduler.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/components/scheduler.rs diff --git a/src/components/scheduler.rs b/src/components/scheduler.rs new file mode 100644 index 0000000..6a737c6 --- /dev/null +++ b/src/components/scheduler.rs @@ -0,0 +1,28 @@ +// https://www.gregorygaines.com/blog/emulator-polling-vs-scheduler-game-loop/ +// https://brilliant.org/wiki/binary-heap/ + +// Lets schedule events instead of polling + +use std::{collections::BinaryHeap, cmp::Reverse}; + +pub enum Event { + Hblank, + Vblank, + TimerOverflow(u8), + ApuSample +} + +pub struct Scheduler { + pub current: u64, + queue: BinaryHeap> +} + +impl Scheduler { + pub fn new() -> Self { + Self {current: 0, queue: BinaryHeap::new()} + } + + pub fn schedule(&mut self, event: Event, cycles: u64) { + self.queue.push(Reverse((self.current + cycles, event))); + } +} \ No newline at end of file From 9986957d3613d4d5f47022e8034d9033777c7a36 Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Fri, 24 Jul 2026 22:23:44 -0700 Subject: [PATCH 09/18] Map out real bus --- src/components/bus.rs | 43 +++++++++++++++++++ src/components/memory/{bus.rs => temp_bus.rs} | 5 ++- 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/components/bus.rs rename src/components/memory/{bus.rs => temp_bus.rs} (99%) diff --git a/src/components/bus.rs b/src/components/bus.rs new file mode 100644 index 0000000..69f734a --- /dev/null +++ b/src/components/bus.rs @@ -0,0 +1,43 @@ +// https://mgba.io/2015/06/27/cycle-counting-prefetch/ +// https://developer.arm.com/documentation/ddi0084/f/memory-interface/bus-cycle-types/sequential-cycles + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum AccessType { + Sequential, // Memory address related to previous address, incremented by + 2 (half word) or +4 (word) + Nonsequential, // Memory address is fetched and has nothing to do with the previous instruction +} + +// Note, there is an addition GBA cycle type: Internal, no memory access, performing a complex internal operation like a multiply, only 1 cycle + +mod sealed { + pub trait Sealed {} + + impl Sealed for u8 {} + + impl Sealed for u16 {} + + impl Sealed for u32 {} +} + + +pub struct Bus { + pub sheduler: Scheduler, +} + +pub trait BusValue: sealed::Sealed + Sized + Copy { + fn read(bus: &mut Bus, address: u32, access_type: AccessType) -> Self; + + fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType); +} + +impl Bus { + pub fn read(&mut self, address: u32, access_type: AccessType) -> T { + T::read(self, address, access) + } + + pub fn write(&mut self, address: u32, value: T, access_type: AccessType) { + T::write(self, address, value, access) + } +} + +// impl BusValue for u16 diff --git a/src/components/memory/bus.rs b/src/components/memory/temp_bus.rs similarity index 99% rename from src/components/memory/bus.rs rename to src/components/memory/temp_bus.rs index 995f737..e92e347 100644 --- a/src/components/memory/bus.rs +++ b/src/components/memory/temp_bus.rs @@ -122,7 +122,7 @@ impl Bus { 0x4000142 => {}, // Not Used 0x4000150 => {}, // SIO JOY Bus Receive Data (JOY_RECV), 32 bit register (read + write) 0x4000154 => {}, // SIO JOY Bus Transmit Data (JOY_TRANS), 32 bit register (read + write) - 0x4000158 => {}, // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + maybe write?) + 0x4000158 => {}, // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + write) 0x400015A => {}, // Not Used //Interrupt, Waitstate, and Power-Down Control @@ -251,6 +251,7 @@ impl Bus { 0x40000BA => {}, // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) 0x40000BC => {}, // DMA 1 Source Address (DMA1SAD), 32 bit register (write only) 0x40000C0 => {}, // DMA 1 Destination Address (DMA1DAD), 32 bit register (write only) + 0x40000C4 => {}, // DMA 1 Word Count (DMA1CNT_L), 16 bit register (read + write) 0x40000C6 => {}, // DMA 1 Control (DMA1CNT_H), 16 bit register (read + write) 0x40000C8 => {}, // DMA 2 Source Address (DMA2SAD), 32 bit register (write only) 0x40000CC => {}, // DMA 2 Destination Address (DMA2DAD), 32 bit register (write only) @@ -294,7 +295,7 @@ impl Bus { 0x4000142 => {}, // Not Used 0x4000150 => {}, // SIO JOY Bus Receive Data (JOY_RECV), 32 bit register (read + write) 0x4000154 => {}, // SIO JOY Bus Transmit Data (JOY_TRANS), 32 bit register (read + write) - 0x4000158 => {}, // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + maybe write?) + 0x4000158 => {}, // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + write) 0x400015A => {}, // Not Used //Interrupt, Waitstate, and Power-Down Control From cf59abf0240728f458b8f9585c78777f568fc748 Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Mon, 27 Jul 2026 20:00:08 -0400 Subject: [PATCH 10/18] Hexdump debug --- .gitignore | 4 +++ src/components/bus.rs | 52 ++++++++++++++++++++++++++----- src/components/memory/temp_bus.rs | 3 ++ src/components/mod.rs | 2 ++ src/components/scheduler.rs | 28 ++++++++++++++--- src/debug.rs | 44 ++++++++++++++++++++++++++ src/lib.rs | 2 ++ src/main.rs | 14 +++++++-- 8 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 src/components/mod.rs create mode 100644 src/debug.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..9ec3221 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ /target +*.txt +*.bin +*.sav +*.gba diff --git a/src/components/bus.rs b/src/components/bus.rs index 69f734a..8d49af9 100644 --- a/src/components/bus.rs +++ b/src/components/bus.rs @@ -1,5 +1,15 @@ // https://mgba.io/2015/06/27/cycle-counting-prefetch/ // https://developer.arm.com/documentation/ddi0084/f/memory-interface/bus-cycle-types/sequential-cycles +// https://corrupt.wiki/systems/gameboy-advance/bizhawk-memory-domains +// https://medium.com/@michelheily/hello-gba-journey-of-making-an-emulator-part-1-8793000e8606 +// https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm#Memory%20Map +// https://www.nesdev.org/wiki/Open_bus_behavior +// https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm#Memory%20Map + +// Just do an afterboot startup + +// https://problemkaputt.de/gbatek.htm#GBAUnpredictableThings +use crate::components::scheduler::Scheduler; #[derive(Clone, Copy, PartialEq, Eq)] pub enum AccessType { @@ -19,25 +29,53 @@ mod sealed { impl Sealed for u32 {} } - -pub struct Bus { - pub sheduler: Scheduler, -} - pub trait BusValue: sealed::Sealed + Sized + Copy { fn read(bus: &mut Bus, address: u32, access_type: AccessType) -> Self; fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType); } +pub struct Bus { + pub scheduler: Scheduler, + iwram: Box<[u8; 0x8000]>, + ewram: Box<[u8; 0x4000]>, + last_read: u32, + last_bios_fetch: u32 // According to medium article, MMBN6 has an email bug due to null pointer dereference in the BIOS + // region [00DCh+8] in bios is 0xE12FFF1E; https://problemkaputt.de/gbatek.htm#GBAUnpredictableThings +} + impl Bus { + pub fn new() -> Self { + Self { + scheduler: Scheduler::new(), + iwram: Box::new([0u8; 0x8000]), + ewram: Box::new([0u8; 0x4000]), + last_read: 0, + last_bios_fetch: 0xE12FFF1E + } + } + pub fn read(&mut self, address: u32, access_type: AccessType) -> T { - T::read(self, address, access) + T::read(self, address, access_type) } pub fn write(&mut self, address: u32, value: T, access_type: AccessType) { - T::write(self, address, value, access) + T::write(self, address, value, access_type) } + + pub fn idle(&mut self, cycles: u64) { + self.scheduler.current += cycles; + } + + pub fn cost(&mut self, address: u32, width: u32, access_type: AccessType) { + self.scheduler.current += 1; // TODO: wait table + } + + fn read_register_16(&mut self, address: u32) -> u16 { + 0 + } + + fn write_register_16(&mut self, address: u32, value: u16) {} } // impl BusValue for u16 diff --git a/src/components/memory/temp_bus.rs b/src/components/memory/temp_bus.rs index e92e347..6b05c3d 100644 --- a/src/components/memory/temp_bus.rs +++ b/src/components/memory/temp_bus.rs @@ -4,6 +4,9 @@ pub struct Bus { } +// unlike gb, gameboy has 16 bit and 32 bit registers, the two 8 bit io registers appear to be undocumented, hence +// why the addresses skip by 2 or 4 + impl Bus { pub fn read>(&self, address: u32) -> T { match address { diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..988e09c --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1,2 @@ +pub mod bus; +pub mod scheduler; diff --git a/src/components/scheduler.rs b/src/components/scheduler.rs index 6a737c6..b849449 100644 --- a/src/components/scheduler.rs +++ b/src/components/scheduler.rs @@ -2,27 +2,45 @@ // https://brilliant.org/wiki/binary-heap/ // Lets schedule events instead of polling +// We can use a match statement in the gba main loop instead of constantly calling +// component functions on each iteration -use std::{collections::BinaryHeap, cmp::Reverse}; +use std::{cmp::Reverse, collections::BinaryHeap}; +#[derive(Eq, Ord, PartialEq, PartialOrd)] pub enum Event { Hblank, Vblank, TimerOverflow(u8), - ApuSample + ApuSample, } pub struct Scheduler { pub current: u64, - queue: BinaryHeap> + queue: BinaryHeap>, } impl Scheduler { pub fn new() -> Self { - Self {current: 0, queue: BinaryHeap::new()} + Self { + current: 0, + queue: BinaryHeap::new(), + } } pub fn schedule(&mut self, event: Event, cycles: u64) { self.queue.push(Reverse((self.current + cycles, event))); } -} \ No newline at end of file + + pub fn next(&self) -> u64 { + self.queue.peek().map_or(u64::MAX, |Reverse((t, _))| *t) + } + + pub fn pop(&mut self) -> Option { + if self.next() <= self.current { + self.queue.pop().map(|Reverse((_, kind))| kind) + } else { + None + } + } +} diff --git a/src/debug.rs b/src/debug.rs new file mode 100644 index 0000000..2a73f3b --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,44 @@ +use std::{error::Error, fmt::Write as _, fs::{read, write}, path::PathBuf}; + +pub enum DumpWidth { + Byte, + HalfWord, + Word +} + + +pub fn read_rom(source: PathBuf) -> Result, std::io::Error>{ + let buffer = read(source)?; + + Ok(buffer) +} + +pub fn hexdump(buffer: &[u8], width: DumpWidth) -> Result<(), Box> { + let mut output = String::new(); + match width { + DumpWidth::Byte => dump_lines::<1>(buffer, &mut output, 16)?, + DumpWidth::HalfWord => dump_lines::<2>(buffer, &mut output, 8)?, + DumpWidth::Word => dump_lines::<4>(buffer, &mut output, 4)?, + } + + Ok(()) +} + +fn dump_lines(buffer: &[u8], output: &mut String, per_row: usize) -> Result<(), Box> { + let (units, _) = buffer.as_chunks::(); + for (i, row) in units.chunks(per_row).enumerate() { + write!(output, "{:#010X}: ", i * per_row * N)?; + for &unit in row { + match N { + 2 => write!(output, "{:04X} ", u16::from_le_bytes([unit[0], unit[1]]))?, + 4 => write!(output, "{:08X} ", u32::from_le_bytes([unit[0], unit[1], unit[2], unit[3]]))?, + _ => { for b in unit { write!(output, "{b:02X} ")?; } } + } + } + writeln!(output)?; + } + + write("hexdump.txt", output)?; + + Ok(()) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ab838f4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod components; +pub mod debug; diff --git a/src/main.rs b/src/main.rs index dd97a5b..4136bdc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,17 @@ // https://www.gregorygaines.com/blog/decoding-the-arm7tdmi-instruction-set-game-boy-advance/ // https://ia903206.us.archive.org/34/items/NintendoGbaManualV1.1/Nintendo%20Gba%20Manual%20V1.1.pdf // https://ww1.microchip.com/downloads/en/DeviceDoc/DDI0029G_7TDMI_R3_trm.pdf +// https://medium.com/@julio.vidaurre/making-a-gba-emulator-fbf91b85979a -fn main() { - println!("Hello, world!"); +use std::{error::Error, path::PathBuf}; + +use gba::{debug::*}; + + +fn main() -> Result<(), Box>{ + let source = PathBuf::from(r"C:\Users\donis\OneDrive\Desktop\Hamtaro - Ham-Ham Heartbreak (USA).gba"); + let buffer = read_rom(source)?; + hexdump(&buffer, DumpWidth::Word)?; + + Ok(()) } From 745f98725044ed8efc3cd0bcc07359374420a707 Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Tue, 28 Jul 2026 22:14:06 -0400 Subject: [PATCH 11/18] Wire up more bus and some skeleton structs + gampak --- src/components/apu/mod.rs | 9 + src/components/bus.rs | 112 ++++++++++- src/components/gamepak/mod.rs | 170 ++++++++++++++++ src/components/joypad.rs | 1 + src/components/memory/temp_bus.rs | 321 ------------------------------ src/components/mod.rs | 6 + src/components/ppu/mod.rs | 17 ++ src/components/timer.rs | 1 + src/components/utils.rs | 3 + src/debug.rs | 32 ++- src/main.rs | 12 +- 11 files changed, 340 insertions(+), 344 deletions(-) create mode 100644 src/components/apu/mod.rs create mode 100644 src/components/gamepak/mod.rs create mode 100644 src/components/joypad.rs delete mode 100644 src/components/memory/temp_bus.rs create mode 100644 src/components/ppu/mod.rs create mode 100644 src/components/timer.rs create mode 100644 src/components/utils.rs diff --git a/src/components/apu/mod.rs b/src/components/apu/mod.rs new file mode 100644 index 0000000..14fbf9d --- /dev/null +++ b/src/components/apu/mod.rs @@ -0,0 +1,9 @@ +// https://gbadev.net/gbadoc/audio/introduction.html + +pub struct APU {} + +impl APU { + pub fn new() -> Self { + Self {} + } +} diff --git a/src/components/bus.rs b/src/components/bus.rs index 8d49af9..4c3c464 100644 --- a/src/components/bus.rs +++ b/src/components/bus.rs @@ -6,10 +6,18 @@ // https://www.nesdev.org/wiki/Open_bus_behavior // https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm#Memory%20Map +// https://blog.asie.pl/2025/09/wonderful-update-september-2025/ + // Just do an afterboot startup // https://problemkaputt.de/gbatek.htm#GBAUnpredictableThings -use crate::components::scheduler::Scheduler; +use crate::components::{ + apu::APU, + gamepak::GamePak, + ppu::PPU, + scheduler::Scheduler, + utils::zero_arr, +}; #[derive(Clone, Copy, PartialEq, Eq)] pub enum AccessType { @@ -37,22 +45,75 @@ pub trait BusValue: sealed::Sealed + Sized + Copy { pub struct Bus { pub scheduler: Scheduler, + bios: Box<[u8; 0x4000]>, iwram: Box<[u8; 0x8000]>, - ewram: Box<[u8; 0x4000]>, + ewram: Box<[u8; 0x40000]>, last_read: u32, - last_bios_fetch: u32 // According to medium article, MMBN6 has an email bug due to null pointer dereference in the BIOS - // region [00DCh+8] in bios is 0xE12FFF1E; https://problemkaputt.de/gbatek.htm#GBAUnpredictableThings + last_bios_fetch: u32, // According to medium article, MMBN6 has an email bug due to null pointer dereference in the BIOS + // region [00DCh+8] in bios is 0xE129F000; https://problemkaputt.de/gbatek.htm#GBAUnpredictableThings + apu: APU, + ppu: PPU, + gamepak: GamePak, } impl Bus { - pub fn new() -> Self { + pub fn new(gamepak: GamePak) -> Self { Self { scheduler: Scheduler::new(), - iwram: Box::new([0u8; 0x8000]), - ewram: Box::new([0u8; 0x4000]), + bios: zero_arr(), + iwram: zero_arr(), + ewram: zero_arr(), last_read: 0, - last_bios_fetch: 0xE12FFF1E + last_bios_fetch: 0xE129F000, + ppu: PPU::new(), + gamepak, + apu: APU::new(), + } + } + + #[inline] + fn ewram_index(address: u32) -> usize { + (address & 0x3FFFF) as usize + } + + #[inline] + fn iwram_index(address: u32) -> usize { + (address & 0x7FFF) as usize + } + + #[inline] + pub fn palette_index(address: u32) -> usize { + (address & 0x3FF) as usize + } + + #[inline] + pub fn oam_index(address: u32) -> usize { + (address & 0x3FF) as usize + } + + #[inline] + pub fn vram_index(address: u32) -> usize { + let index = (address & 0x1FFFE) as usize; + let index = if index >= 0x18000 { + index - 0x8000 + } else { + index + }; + + index + } + + #[inline] + pub fn backup_byte(&self, address: u32) -> u8 { + if self.gamepak.backup_memory.is_empty() { + return 0; } + + let base_address = 0x0E000000; + let mask = (self.gamepak.backup_memory.len() - 1) as u32; + let index = ((address - base_address) & mask) as usize; + + self.gamepak.backup_memory[index] } pub fn read(&mut self, address: u32, access_type: AccessType) -> T { @@ -76,6 +137,39 @@ impl Bus { } fn write_register_16(&mut self, address: u32, value: u16) {} + + fn open(&self) -> u32 { + 0 + } } -// impl BusValue for u16 +impl BusValue for u16 { + fn read(bus: &mut Bus, address: u32, access_type: AccessType) -> Self { + bus.cost(address, 16, access_type); + let address = address & !1; // ensure even + let little_endian = + |arr: &[u8], index: usize| u16::from_le_bytes([arr[index], arr[index + 1]]); + + match address >> 24 { + 0x00 => little_endian(&*bus.bios, (address & 0x3FFE) as usize), + 0x02 => little_endian(&*bus.ewram, Bus::ewram_index(address)), + 0x03 => little_endian(&*bus.iwram, Bus::iwram_index(address)), + 0x04 => bus.read_register_16(address), + 0x05 => little_endian(&*bus.ppu.palette_ram, Bus::palette_index(address)), + 0x06 => little_endian(&*bus.ppu.vram, Bus::vram_index(address)), + 0x07 => little_endian(&*bus.ppu.oam, Bus::oam_index(address)), + 0x08..=0x0D => bus.gamepak.read_rom_region(address) as u16, + 0x0E | 0x0F => { + let byte = bus.backup_byte(address) as u16; + byte | (byte << 8) + } + _ => bus.open() as u16, + } + } + + fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType) {} +} + +// impl BusValue for u8 + +// impl BusValue as u32 diff --git a/src/components/gamepak/mod.rs b/src/components/gamepak/mod.rs new file mode 100644 index 0000000..af63f4a --- /dev/null +++ b/src/components/gamepak/mod.rs @@ -0,0 +1,170 @@ +// https://www.advanscene.com/html/dbstart.php# + +use std::{ + fs::{read, write}, + io::Error, + path::PathBuf, +}; +// SRAM_F + +// https://problemkaputt.de/gbatek-gba-cart-backup-ids.htm +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum BackupType { + Eeprom, + Sram, + SramF, // Apparently, there is an SRAM_F_V variant (e.g. Hamtaro Ham Ham Heartbreak) + Flash, + Flash512, + Flash1M, + None, +} + +impl BackupType { + fn to_vec(self) -> Vec { + match self { + BackupType::Sram | BackupType::SramF => vec![0u8; kilobytes(32)], + BackupType::Flash | BackupType::Flash512 => vec![0u8; kilobytes(64)], + BackupType::Flash1M => vec![0u8; kilobytes(128)], + _ => Vec::::new(), + } + } + + // Just for checking purposes + pub fn to_str(self) -> &'static str { + match self { + BackupType::Eeprom => "EEPROM_V", + BackupType::Sram => "SRAM_V", + BackupType::SramF => "SRAM_F_V", + BackupType::Flash => "FLASH_V", + BackupType::Flash512 => "FLASH512_V", + BackupType::Flash1M => "FLASH1M_V", + BackupType::None => "None", + } + } +} + +fn detect_save_type(rom: &[u8]) -> BackupType { + for (needle, kind) in [ + (b"FLASH1M_V".as_slice(), BackupType::Flash1M), + (b"FLASH512_V".as_slice(), BackupType::Flash512), + (b"FLASH_V".as_slice(), BackupType::Flash), + (b"SRAM_F_V".as_slice(), BackupType::SramF), + (b"SRAM_V".as_slice(), BackupType::Sram), + (b"EEPROM_V".as_slice(), BackupType::Eeprom), + ] { + if rom.windows(needle.len()).any(|x| x == needle) { + return kind; + } + } + + BackupType::None +} + +fn error_message(message: String) -> std::io::Error { + std::io::Error::new(std::io::ErrorKind::InvalidData, message) +} + +fn kilobytes(value: usize) -> usize { + value * 1024 +} + +pub struct GamePak { + pub rom: Vec, + sav_path: PathBuf, + pub backup_memory: Vec, + pub backup_type: BackupType, + pub ram_updated: bool, +} + +impl GamePak { + pub fn load(filename: Option) -> Result { + let Some(rom_path) = filename else { + let file_error_msg = "Issue occured with file selection".to_string(); + + return Err(error_message(file_error_msg)); + }; + + let rom = std::fs::read(&rom_path)?; + + let sav_path = rom_path.with_extension("sav"); + let backup_type = detect_save_type(&rom); + let backup_memory = Self::read_sav(&sav_path, backup_type)?; + + Ok(Self { + rom, + sav_path, + backup_type, + backup_memory, + ram_updated: false, + }) + } + + // Eventually append RTC data to the end + pub fn read_sav(sav_path: &PathBuf, backup_type: BackupType) -> Result, Error> { + let mut backup_memory = backup_type.to_vec(); + + if !sav_path.exists() { + return Ok(backup_memory); + } + + let buffer = read(sav_path)?; + + if backup_type == BackupType::Eeprom { + return Ok(buffer); + } + + let n = buffer.len().min(backup_memory.len()); + backup_memory[..n].copy_from_slice(&buffer[..n]); + + Ok(backup_memory) + } + + pub fn write_sav(&mut self) -> Result<(), Error> { + if self.ram_updated && self.has_backup() { + write(&self.sav_path, &self.backup_memory)?; + } + + self.ram_updated = false; + + Ok(()) + } + + pub fn has_backup(&self) -> bool { + self.backup_type != BackupType::None + } + + // https://densinh.github.io/DenSinH/emulation/2021/02/01/gba-eeprom.html + // https://problemkaputt.de/gbatek.htm#gbacartbackupeeprom + // We will leave this eeprom for later, apparently, the hardware is a + // single wire that writes one bit at a time and the number of bits determined + // the size but only somewhat reliably, this seems to also require DMA, since DMA3 + // can access rom, maybe this should be done closer to the emulator end than trying + // to wire up right now + fn is_eeprom_address(&self, address: u32) -> bool { + if self.backup_type != BackupType::Eeprom { + return false; + } + + if self.rom.len() > 0x1000000 { + address >= 0x0DFFFF00 && address <= 0x0DFFFFFF + } else { + // anywhere in 0x0D000000-0x0DFFFFFF + (address >> 24) == 0x0D + } + } + + pub fn read_rom_region(&self, address: u32) -> u8 { + if self.is_eeprom_address(address) { + //self.eeprom_read_bit() + 0 + } else { + self.rom_byte(address) + } + } + + #[inline] + fn rom_byte(&self, address: u32) -> u8 { + let index = (address & 0x01FFFFFF) as usize; + self.rom.get(index).copied().unwrap_or(0) + } +} diff --git a/src/components/joypad.rs b/src/components/joypad.rs new file mode 100644 index 0000000..5595b8d --- /dev/null +++ b/src/components/joypad.rs @@ -0,0 +1 @@ +pub struct Joypad {} diff --git a/src/components/memory/temp_bus.rs b/src/components/memory/temp_bus.rs deleted file mode 100644 index 6b05c3d..0000000 --- a/src/components/memory/temp_bus.rs +++ /dev/null @@ -1,321 +0,0 @@ -// https://problemkaputt.de/gbatek.htm#gbamemorymap - -pub struct Bus { - -} - -// unlike gb, gameboy has 16 bit and 32 bit registers, the two 8 bit io registers appear to be undocumented, hence -// why the addresses skip by 2 or 4 - -impl Bus { - pub fn read>(&self, address: u32) -> T { - match address { - // General Internal Memory - 0x00000000..=0x00003FFF => {}, // BIOS - 0x00004000..=0x01FFFFFF => {}, // Not Used - 0x02000000..=0x0203FFFF => {}, // On-board WRAM - 0x03000000..=0x03007FFF => {}, // On-chip WRAM - 0x03008000..=0x03FFFFFF => {}, // Not used - 0x04000000..=0x040003FE => self.read_register(address), // I/O register - 0x04000400..=0x04FFFFFF => {}, // Not Used - - // Internal Display Memory - 0x05000000..=0x050003FF => {}, // BG/OBJ Palette RAM - 0x05000400..=0x05FFFFFF => {}, // Not Used - 0x06000000..=0x06017FFF => {}, // VRAM - 0x06018000..=0x06FFFFFF => {}, // Not Used - 0x07000000..=0x070003FF => {}, // OAM - 0x07000400..=0x07FFFFFF => {}, // Not Used - - // External Memory (Game Pak/ROM) - 0x08000000..=0x09FFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 0 - 0x0A000000..=0x0BFFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 1 - 0x0C000000..=0x0DFFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 2 - 0x0E000000..=0x0E00FFFF => {}, // Game Pak SRAM (max 64 KBytes) - 8bit Bus width - 0x0E010000..=0x0FFFFFFF => {}, // Not used - - // Unused Memory Area - 0x10000000..=0xFFFFFFFF => {} // Not used (upper 4bits of address bus unused) - } - } - - // TODO: Map out all IO registers - // Eventually, each IO component will have its own - // read and write registers function to route to - fn read_register>(&self, address: u32) -> T { - match address { - // LCD I/O Registers - 0x4000000 => {}, // LCD Control (DISPCNT), 16 bit register (read + write) - 0x4000002 => {}, // Undocumented 16 bit register (read + write) - 0x4000004 => {}, // Stat & LYC, 16 bit register (read + write) - 0x4000006 => {}, // LY, 16 bit, (VCOUNT), read only - 0x4000008 => {}, // BG0 Control (BG0CNT) 16 bit register (read + write) - 0x400000A => {}, // BG1 Control (BG1CNT) 16 bit register (read + write) - 0x400000C => {}, // BG2 Control (BG2CNT) 16 bit register (read + write) - 0x400000E => {}, // BG3 Control (BG3CNT) 16 bit register (read + write) - 0x4000048 => {}, // Inside of Window 0 and 1 (WININ), 16 bit register (read + write) - 0x400004A => {}, // Inside of OBJ Window & Outside of Windows 2 (WINOUT) (read + write) - 0x400004E => {}, // Not Used - 0x4000050 => {}, // Color Special Effects Selection (BLDCNT), 16 bit register (read + write) - 0x4000052 => {}, // Alpha Blending Coefficients (BLDALPHA), 16 bit register (read + write) - 0x4000056 => {}, // Not Used - - // Sound Registers - 0x4000060 => {}, // Channel 1 Sweep register (NR10) (SOUND1CNT_L), 16 bit register (read + write) - 0x4000062 => {}, // Channel 1 Duty/Length/Envelope (NR11, NR12) (SOUND1CNT_H), 16 bit register (read + write) - 0x4000064 => {}, // Channel 1 Frequency/Control (NR13, NR14) (SOUND1CNT_X), 16 bit register (read + write) - 0x4000066 => {}, // Not Used - 0x4000068 => {}, // Channel 2 Duty/Length/Envelope (NR21, NR22) (SOUND2CNT_L), 16 bit register (read + write) - 0x400006A => {}, // Not Used - 0x400006C => {}, // Channel 2 Frequency/Control (NR23, NR24) (SOUND2CNT_H), 16 bit register (read + write) - 0x400006E => {}, // Not Used - 0x4000070 => {}, // Channel 3 Stop/Wave RAM select (NR30) (SOUND3CNT_L), 16 bit register (read + write) - 0x4000072 => {}, // Channel 3 Length/Volume (NR31, NR32), 16 bit register (read + write) - 0x4000074 => {}, // Channel 3 Frequency/Control (NR33, NR34) (SOUND3CNT_X), 16 bit register (read + write) - 0x4000076 => {}, // Not Used - 0x4000078 => {}, // Channel 4 Length/Envelope (NR41, NR42) (SOUND4CNT_L), 16 bit register (read + write) - 0x400007A => {}, // Not Used - 0x400007C => {}, // Channel 4 Frequency/Control (NR43, NR44) (SOUND4CNT_H), 16 bit register (read + write) - 0x400007E => {}, // Not Used - 0x4000080 => {}, // Control Stereo/Volume/Enable (NR50, NR51) (SOUNDCNT_L), 16 bit register (read + write) - 0x4000082 => {}, // Control Mixing/DMA Control (SOUNDCNT_H), 16 bit register (read + write) - 0x4000084 => {}, // Control Sound on/off (NR52) (SOUNDCNT_X), 16 bit register (read + write) - 0x4000086 => {}, // Not Used - 0x4000088 => {}, // BIOS/Sound PWM Control (SOUNDBIAS), 16 bit register (read + write) - 0x400008A => {}, // Not Used - 0x4000090 => {}, // Channel 3 Wave Pattern RAM (2 banks) (WAVE_RAM) 2x10h in size, (read + write) - 0x40000A8 => {}, // Not Used - - // DMA Transfer Channels - 0x40000BA => {}, // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) - 0x40000C6 => {}, // DMA 1 Control (DMA1CNT_H), 16 bit register (read + write) - 0x40000D2 => {}, // DMA 2 Control (DMA2CNT_H), 16 bit register (read + write) - 0x40000DE => {}, // DMA 3 Control (DMA3CNT_H), 16 bit register (read + write) - 0x40000E0 => {}, // Not Used - - // Timer Registers - 0x4000100 => {}, // Timer 0 Counter/Reload (TM0CNT_L), 16 bit register (read + write) - 0x4000102 => {}, // Timer 0 Control (TM0CNT_H), 16 bit register (read + write) - 0x4000104 => {}, // Timer 1 Counter/Reload (TM1CNT_L), 16 bit register (read + write) - 0x4000106 => {}, // Timer 1 Control (TM1CNT_H), 16 bit register (read + write) - 0x4000108 => {}, // Timer 2 Counter/Reload (TM2CNT_L), 16 bit register (read + write) - 0x400010A => {}, // Timer 2 Control (TM2CNT_H), 16 bit register (read + write) - 0x400010C => {}, // Timer 3 Counter/Reload (TM3CNT_L), 16 bit register (read + write) - 0x400010E => {}, // Timer 3 Control (TM3CNT_H), 16 bit register (read + write) - 0x4000110 => {}, // Not Used - - // Serial Communication (1) - 0x4000120 => {}, // SIO Data (Normal-32bit Mode; shared with SIO Data 0 (Parent) (SIODATA32). SIO Data is a 32 bit register and SIO Data 0 (Parent) (Multi-Player Mode) is a 16 bit register (read + write) (SIOMULTI0) - 0x4000122 => {}, // SIO Data 1 (1st Child) (Multi-Player Mode) (SIOMULTI1), 16 bit register (read + write) - 0x4000124 => {}, // SIO Data 2 (2nd Child) (Multi-Player Mode) (SIOMULTI2), 16 bit register (read + write) - 0x4000126 => {}, // SIO Data 3 (3rd Child) (Multi-Player Mode) (SIOMULTI3), 16 bit register (read + write) - 0x4000128 => {}, // SIO Control Register (SIOCNT), 16 bit register (read + write) - 0x400012A => {}, // SIO Data (Local of MultiPlayer; shared with SIODATA8) (SIOMLT_SEND), 16 bit register (read + write); SIO Data (Normal-8bit and UART Mode) (SIODATA8), 16 bit register (read + write) - 0x400012C => {}, // Not Used - - // Keypad Input - 0x4000130 => {}, // Key Status (KEYINPUT), 16 bit register read only - 0x4000132 => {}, // Key Interrupt Control (KEYCNT), 16 bit register (read + write) - - // Serial Communication (2) - 0x4000134 => {}, // SIO Mode Select/General Purpose Data (RCNT), 16 bit register (read + write) - 0x4000136 => {}, // Ancient - Infrared Register (Prototypes only) (IR) - 0x4000138 => {}, // Not Used - 0x4000140 => {}, // SIO JOY Bus Control (JOYCNT), 16 bit register (read + write) - 0x4000142 => {}, // Not Used - 0x4000150 => {}, // SIO JOY Bus Receive Data (JOY_RECV), 32 bit register (read + write) - 0x4000154 => {}, // SIO JOY Bus Transmit Data (JOY_TRANS), 32 bit register (read + write) - 0x4000158 => {}, // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + write) - 0x400015A => {}, // Not Used - - //Interrupt, Waitstate, and Power-Down Control - 0x4000200 => {}, // Interrupt Enable Register (IE), 16 bit register (read + write) - 0x4000202 => {}, // Interrupt Request Flags / IRQ Acknowledge (IF), 16 bit register (read + write) - 0x4000204 => {}, // Game Pak Waitstate Control (AITCNT), 16 bit register (read + write) - 0x4000206 => {}, // Not used - 0x4000208 => {}, // Interrupt Master Enable Register (IME), 16 bit register (read + write) - 0x400020A => {}, // Not used - 0x4000300 => {}, // Undocumented - Post Boot Flag (POSTFLG), 8 bit register (read + write) - 0x4000301 => {}, // Undocumented - Power Down Control (HALTCNT), 8 bit register (write only) - 0x4000302 => {}, // Not used - 0x4000410 => {}, // Undocumented - Purpose Unknown / Bug ??? 0FFh - 0x4000411 => {}, // Not used - 0x4000800 => {}, // Undocumented - Internal Memory Control, 32 bit register (read + write) - 0x4000804 => {}, // Not used - address if (address & 0xFF00FFFF) == 0x04000800 => {}, // Mirrors of 4000800h (repeated each 64K), 32 bit (read + write) - } - } - - pub fn write>(&mut self, address: u32, value: T) { - match address { - // General Internal Memory - 0x00004000..=0x01FFFFFF => {}, // Not Used - 0x02000000..=0x0203FFFF => {}, // On-board WRAM - 0x03000000..=0x03007FFF => {}, // On-chip WRAM - 0x03008000..=0x03FFFFFF => {}, // Not used - 0x04000000..=0x040003FE => self.write_register(address, value), // I/O register - 0x04000400..=0x04FFFFFF => {}, // Not Used - - // Internal Display Memory - 0x05000000..=0x050003FF => {}, // BG/OBJ Palette RAM - 0x05000400..=0x05FFFFFF => {}, // Not Used - 0x06000000..=0x06017FFF => {}, // VRAM - 0x06018000..=0x06FFFFFF => {}, // Not Used - 0x07000000..=0x070003FF => {}, // OAM - 0x07000400..=0x07FFFFFF => {}, // Not Used - - // External Memory (Game Pak/ROM) - 0x08000000..=0x09FFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 0 - 0x0A000000..=0x0BFFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 1 - 0x0C000000..=0x0DFFFFFF => {}, // Game Pak ROM/FlashROM (max 32MB) - Wait State 2 - 0x0E000000..=0x0E00FFFF => {}, // Game Pak SRAM (max 64 KBytes) - 8bit Bus width - 0x0E010000..=0x0FFFFFFF => {}, // Not used - - // Unused Memory Area - 0x10000000..=0xFFFFFFFF => {} // Not used (upper 4bits of address bus unused) - } - } - - fn write_register>(&mut self, address: u32, value: T) { - match address { - // LCD I/O Registers - 0x4000000 => {}, // LCD Control (DISPCNT), 16 bit register (read + write) - 0x4000002 => {}, // Undocumented 16 bit register (read + write) - 0x4000004 => {}, // Stat & LYC, 16 bit register (read + write) - 0x4000008 => {}, // BG0 Control (BG0CNT) 16 bit register (read + write) - 0x400000A => {}, // BG1 Control (BG1CNT) 16 bit register (read + write) - 0x400000C => {}, // BG2 Control (BG2CNT) 16 bit register (read + write) - 0x400000E => {}, // BG3 Control (BG3CNT) 16 bit register (read + write) - 0x4000010 => {}, // BG0 X-Offset (BG0HOFS) 16 bit register (write only) - 0x4000012 => {}, // BG0 Y-Offset (BG0VOFS) 16 bit register (write only) - 0x4000014 => {}, // BG1 X-Offset (BG1HOFS) 16 bit register (write only) - 0x4000016 => {}, // BG1 Y-Offset (BG1VOFS) 16 bit register (write only) - 0x4000018 => {}, // BG2 X-Offset (BG2HOFS) 16 bit register (write only) - 0x400001A => {}, // BG2 Y-Offset (BG2VOFS) 16 bit register (write only) - 0x400001C => {}, // BG3 X-Offset (BG3HOFS) 16 bit register (write only) - 0x400001E => {}, // BG3 Y-Offset (BG3VOFS) 16 bit register (write only) - 0x4000020 => {}, // BG2 Rotation/Scaling Parameter A (dx) (BG2PA), 16 bit register (write only) - 0x4000022 => {}, // BG2 Rotation/Scaling Parameter B (dmx) (BG2PB), 16 bit register (write only) - 0x4000024 => {}, // BG2 Rotation/Scaling Parameter C (dy) (BG2PC), 16 bit register (write only) - 0x4000026 => {}, // BG2 Rotation/Scaling Parameter D (dmy) (BG2PD), 16 bit register (write only) - 0x4000028 => {}, // BG2 Reference Point X-Coordinate (BG2X), 32 bit register (write only) - 0x400002C => {}, // BG2 Reference Point Y-Coordinate (BG2Y), 32 bit register (write only) - 0x4000030 => {}, // BG3 Rotation/Scaling Parameter A (dx) (BG3PA), 16 bit register (write only) - 0x4000032 => {}, // BG3 Rotation/Scaling Parameter B (dmx) (BG3PB), 16 bit register (write only) - 0x4000034 => {}, // BG3 Rotation/Scaling Parameter C (dy) (BG3PC), 16 bit register (write only) - 0x4000036 => {}, // BG3 Rotation/Scaling Parameter D (dmy) (BG3PD), 16 bit register (write only) - 0x4000038 => {}, // BG3 Reference Point X-Coordinate (BG3X), 32 bit register (write only) - 0x400003C => {}, // BG3 Reference Point Y-Coordinate (BG3Y), 32 bit register (write only) - 0x4000040 => {}, // Window 0 Horizontal Dimensions (WIN0H), 16 bit register (write only) - 0x4000042 => {}, // Window 1 Horizontal Dimensions (WIN1H), 16 bit register (write only) - 0x4000044 => {}, // Window 0 Vertical Dimensions (WIN0V), 16 bit register (write only) - 0x4000046 => {}, // Window 1 Vertical Dimensions (WIN1V), 16 bit register (write only) - 0x4000048 => {}, // Inside of Window 0 and 1 (WININ), 16 bit register (read + write) - 0x400004A => {}, // Inside of OBJ Window & Outside of Windows 2 (WINOUT), 16 bit register (read + write) - 0x400004C => {}, // Mosaic Size (MOSAIC), 16 bit register (write only) - 0x400004E => {}, // Not Used - 0x4000050 => {}, // Color Special Effects Selection (BLDCNT), 16 bit register (read + write) - 0x4000052 => {}, // Alpha Blending Coefficients (BLDALPHA), 16 bit register (read + write) - 0x4000054 => {}, // Brightness (Fade-In/Out) Coefficient (BLDY), 16 bit register (write only) - 0x4000056 => {}, // Not Used - - // Sound Registers - 0x4000060 => {}, // Channel 1 Sweep register (NR10) (SOUND1CNT_L), 16 bit register (read + write) - 0x4000062 => {}, // Channel 1 Duty/Length/Envelope (NR11, NR12) (SOUND1CNT_H), 16 bit register (read + write) - 0x4000064 => {}, // Channel 1 Frequency/Control (NR13, NR14) (SOUND1CNT_X), 16 bit register (read + write) - 0x4000066 => {}, // Not Used - 0x4000068 => {}, // Channel 2 Duty/Length/Envelope (NR21, NR22) (SOUND2CNT_L), 16 bit register (read + write) - 0x400006A => {}, // Not Used - 0x400006C => {}, // Channel 2 Frequency/Control (NR23, NR24) (SOUND2CNT_H), 16 bit register (read + write) - 0x400006E => {}, // Not Used - 0x4000070 => {}, // Channel 3 Stop/Wave RAM select (NR30) (SOUND3CNT_L), 16 bit register (read + write) - 0x4000072 => {}, // Channel 3 Length/Volume (NR31, NR32), 16 bit register (read + write) - 0x4000074 => {}, // Channel 3 Frequency/Control (NR33, NR34) (SOUND3CNT_X), 16 bit register (read + write) - 0x4000076 => {}, // Not Used - 0x4000078 => {}, // Channel 4 Length/Envelope (NR41, NR42) (SOUND4CNT_L), 16 bit register (read + write) - 0x400007A => {}, // Not Used - 0x400007C => {}, // Channel 4 Frequency/Control (NR43, NR44) (SOUND4CNT_H), 16 bit register (read + write) - 0x400007E => {}, // Not Used - 0x4000080 => {}, // Control Stereo/Volume/Enable (NR50, NR51) (SOUNDCNT_L), 16 bit register (read + write) - 0x4000082 => {}, // Control Mixing/DMA Control (SOUNDCNT_H), 16 bit register (read + write) - 0x4000084 => {}, // Control Sound on/off (NR52) (SOUNDCNT_X), 16 bit register (read + write) - 0x4000086 => {}, // Not Used - 0x4000088 => {}, // BIOS/Sound PWM Control (SOUNDBIAS), 16 bit register (read + write) - 0x400008A => {}, // Not Used - 0x4000090 => {}, // Channel 3 Wave Pattern RAM (2 banks) (WAVE_RAM) 2x10h in size, (read + write) - 0x40000A0 => {}, // Channel A FIFO, Data 0-3, (FIFO_A) (write only), 32 bit register - 0x40000A4 => {}, // Channel B FIFO, Data 0-3, (FIFO_B) (write only), 32 bit register - 0x40000A8 => {}, // Not Used - - // DMA Transfer Channels - 0x40000B0 => {}, // DMA 0 Source Address (DMA0SAD), 32 bit register (write only) - 0x40000B4 => {}, // DMA 0 Destination Address (DMA0DAD), 32 bit register (write only) - 0x40000B8 => {}, // DMA 0 Word Count (DMA0CNT_L), 16 bit register (write only) - 0x40000BA => {}, // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) - 0x40000BC => {}, // DMA 1 Source Address (DMA1SAD), 32 bit register (write only) - 0x40000C0 => {}, // DMA 1 Destination Address (DMA1DAD), 32 bit register (write only) - 0x40000C4 => {}, // DMA 1 Word Count (DMA1CNT_L), 16 bit register (read + write) - 0x40000C6 => {}, // DMA 1 Control (DMA1CNT_H), 16 bit register (read + write) - 0x40000C8 => {}, // DMA 2 Source Address (DMA2SAD), 32 bit register (write only) - 0x40000CC => {}, // DMA 2 Destination Address (DMA2DAD), 32 bit register (write only) - 0x40000D0 => {}, // DMA 2 Word Count (DMA2CNT_L), 16 bit register (write only) - 0x40000D2 => {}, // DMA 2 Control (DMA2CNT_H), 16 bit register (read + write) - 0x40000D4 => {}, // DMA 3 Source Address (DMA3SAD), 32 bit register (write only) - 0x40000D8 => {}, // DMA 3 Destination Address (DMA3DAD), 32 bit register (write only) - 0x40000DC => {}, // DMA 3 Word Count (DMA3CNT_L), 16 bit register (write only) - 0x40000DE => {}, // DMA 3 Control (DMA3CNT_H), 16 bit register (read + write) - 0x40000E0 => {}, // Not Used - - // Timer Registers - 0x4000100 => {}, // Timer 0 Counter/Reload (TM0CNT_L), 16 bit register (read + write) - 0x4000102 => {}, // Timer 0 Control (TM0CNT_H), 16 bit register (read + write) - 0x4000104 => {}, // Timer 1 Counter/Reload (TM1CNT_L), 16 bit register (read + write) - 0x4000106 => {}, // Timer 1 Control (TM1CNT_H), 16 bit register (read + write) - 0x4000108 => {}, // Timer 2 Counter/Reload (TM2CNT_L), 16 bit register (read + write) - 0x400010A => {}, // Timer 2 Control (TM2CNT_H), 16 bit register (read + write) - 0x400010C => {}, // Timer 3 Counter/Reload (TM3CNT_L), 16 bit register (read + write) - 0x400010E => {}, // Timer 3 Control (TM3CNT_H), 16 bit register (read + write) - 0x4000110 => {}, // Not Usedrol (TM3CNT_H), 16 bit register (read + write) - 0x4000110 => {} // Not Used - - // Serial Communication (1) - 0x4000120 => {}, // SIO Data (Normal-32bit Mode; shared with SIO Data 0 (Parent) (SIODATA32). SIO Data is a 32 bit register and SIO Data 0 (Parent) (Multi-Player Mode) is a 16 bit register (read + write) (SIOMULTI0) - 0x4000122 => {}, // SIO Data 1 (1st Child) (Multi-Player Mode) (SIOMULTI1), 16 bit register (read + write) - 0x4000124 => {}, // SIO Data 2 (2nd Child) (Multi-Player Mode) (SIOMULTI2), 16 bit register (read + write) - 0x4000126 => {}, // SIO Data 3 (3rd Child) (Multi-Player Mode) (SIOMULTI3), 16 bit register (read + write) - 0x4000128 => {}, // SIO Control Register (SIOCNT), 16 bit register (read + write) - 0x400012A => {}, // SIO Data (Local of MultiPlayer; shared with SIODATA8) (SIOMLT_SEND), 16 bit register (read + write); SIO Data (Normal-8bit and UART Mode) (SIODATA8), 16 bit register (read + write) - 0x400012C => {}, // Not Used - - // Keypad Input - 0x4000132 => {}, // Key Interrupt Control (KEYCNT), 16 bit register (read + write) - - // Serial Communication (2) - 0x4000134 => {}, // SIO Mode Select/General Purpose Data (RCNT), 16 bit register (read + write) - 0x4000136 => {}, // Ancient - Infrared Register (Prototypes only) (IR) - 0x4000138 => {}, // Not Used - 0x4000140 => {}, // SIO JOY Bus Control (JOYCNT), 16 bit register (read + write) - 0x4000142 => {}, // Not Used - 0x4000150 => {}, // SIO JOY Bus Receive Data (JOY_RECV), 32 bit register (read + write) - 0x4000154 => {}, // SIO JOY Bus Transmit Data (JOY_TRANS), 32 bit register (read + write) - 0x4000158 => {}, // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + write) - 0x400015A => {}, // Not Used - - //Interrupt, Waitstate, and Power-Down Control - 0x4000200 => {}, // Interrupt Enable Register (IE), 16 bit register (read + write) - 0x4000202 => {}, // Interrupt Request Flags / IRQ Acknowledge (IF), 16 bit register (read + write) - 0x4000204 => {}, // Game Pak Waitstate Control (AITCNT), 16 bit register (read + write) - 0x4000206 => {}, // Not used - 0x4000208 => {}, // Interrupt Master Enable Register (IME), 16 bit register (read + write) - 0x400020A => {}, // Not used - 0x4000300 => {}, // Undocumented - Post Boot Flag (POSTFLG), 8 bit register (read + write) - 0x4000301 => {}, // Undocumented - Power Down Control (HALTCNT), 8 bit register (write only) - 0x4000302 => {}, // Not used - 0x4000410 => {}, // Undocumented - Purpose Unknown / Bug ??? 0FFh - 0x4000411 => {}, // Not used - 0x4000800 => {}, // Undocumented - Internal Memory Control, 32 bit register (read + write) - 0x4000804 => {}, // Not used - address if (address & 0xFF00FFFF) == 0x04000800 => {}, // Mirrors of 4000800h (repeated each 64K), 32 bit (read + write) - } - } -} diff --git a/src/components/mod.rs b/src/components/mod.rs index 988e09c..99bf98b 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,2 +1,8 @@ +pub mod apu; pub mod bus; +pub mod gamepak; +pub mod joypad; +pub mod ppu; pub mod scheduler; +pub mod timer; +pub mod utils; diff --git a/src/components/ppu/mod.rs b/src/components/ppu/mod.rs new file mode 100644 index 0000000..97f1c74 --- /dev/null +++ b/src/components/ppu/mod.rs @@ -0,0 +1,17 @@ +use crate::components::utils::zero_arr; + +pub struct PPU { + pub vram: Box<[u8; 0x18000]>, + pub palette_ram: Box<[u8; 0x400]>, + pub oam: Box<[u8; 0x400]>, +} + +impl PPU { + pub fn new() -> Self { + Self { + vram: zero_arr(), + palette_ram: zero_arr(), + oam: zero_arr(), + } + } +} diff --git a/src/components/timer.rs b/src/components/timer.rs new file mode 100644 index 0000000..992f0a4 --- /dev/null +++ b/src/components/timer.rs @@ -0,0 +1 @@ +pub struct Timer {} diff --git a/src/components/utils.rs b/src/components/utils.rs new file mode 100644 index 0000000..0c62f1f --- /dev/null +++ b/src/components/utils.rs @@ -0,0 +1,3 @@ +pub fn zero_arr() -> Box<[u8; N]> { + vec![0u8; N].into_boxed_slice().try_into().unwrap() +} diff --git a/src/debug.rs b/src/debug.rs index 2a73f3b..b1c61c4 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,13 +1,17 @@ -use std::{error::Error, fmt::Write as _, fs::{read, write}, path::PathBuf}; +use std::{ + error::Error, + fmt::Write as _, + fs::{read, write}, + path::PathBuf, +}; pub enum DumpWidth { Byte, HalfWord, - Word + Word, } - -pub fn read_rom(source: PathBuf) -> Result, std::io::Error>{ +pub fn read_rom(source: PathBuf) -> Result, std::io::Error> { let buffer = read(source)?; Ok(buffer) @@ -24,15 +28,27 @@ pub fn hexdump(buffer: &[u8], width: DumpWidth) -> Result<(), Box> { Ok(()) } -fn dump_lines(buffer: &[u8], output: &mut String, per_row: usize) -> Result<(), Box> { +fn dump_lines( + buffer: &[u8], + output: &mut String, + per_row: usize, +) -> Result<(), Box> { let (units, _) = buffer.as_chunks::(); for (i, row) in units.chunks(per_row).enumerate() { write!(output, "{:#010X}: ", i * per_row * N)?; for &unit in row { match N { 2 => write!(output, "{:04X} ", u16::from_le_bytes([unit[0], unit[1]]))?, - 4 => write!(output, "{:08X} ", u32::from_le_bytes([unit[0], unit[1], unit[2], unit[3]]))?, - _ => { for b in unit { write!(output, "{b:02X} ")?; } } + 4 => write!( + output, + "{:08X} ", + u32::from_le_bytes([unit[0], unit[1], unit[2], unit[3]]) + )?, + _ => { + for b in unit { + write!(output, "{b:02X} ")?; + } + } } } writeln!(output)?; @@ -41,4 +57,4 @@ fn dump_lines(buffer: &[u8], output: &mut String, per_row: usize write("hexdump.txt", output)?; Ok(()) -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 4136bdc..e18816f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,13 +7,13 @@ use std::{error::Error, path::PathBuf}; -use gba::{debug::*}; +use gba::{components::gamepak::GamePak, debug::*}; +fn main() -> Result<(), Box> { + let file = Some(PathBuf::from(r"")); + let gamepak = GamePak::load(file).unwrap(); + //hexdump(&gamepak.rom, DumpWidth::Byte)?; -fn main() -> Result<(), Box>{ - let source = PathBuf::from(r"C:\Users\donis\OneDrive\Desktop\Hamtaro - Ham-Ham Heartbreak (USA).gba"); - let buffer = read_rom(source)?; - hexdump(&buffer, DumpWidth::Word)?; - + println!("{}", gamepak.backup_type.to_str()); Ok(()) } From bf96a35ccebd8a67f3793b2f082c86cc22f914cf Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Thu, 30 Jul 2026 10:21:08 -0400 Subject: [PATCH 12/18] Update bus --- src/components/bus.rs | 110 ++++++++++++++++++++++++++++++++------ src/components/cpu/mod.rs | 12 +++++ src/components/mod.rs | 1 + 3 files changed, 108 insertions(+), 15 deletions(-) create mode 100644 src/components/cpu/mod.rs diff --git a/src/components/bus.rs b/src/components/bus.rs index 4c3c464..e770b33 100644 --- a/src/components/bus.rs +++ b/src/components/bus.rs @@ -12,11 +12,7 @@ // https://problemkaputt.de/gbatek.htm#GBAUnpredictableThings use crate::components::{ - apu::APU, - gamepak::GamePak, - ppu::PPU, - scheduler::Scheduler, - utils::zero_arr, + apu::APU, gamepak::GamePak, ppu::PPU, scheduler::Scheduler, utils::zero_arr, }; #[derive(Clone, Copy, PartialEq, Eq)] @@ -46,8 +42,8 @@ pub trait BusValue: sealed::Sealed + Sized + Copy { pub struct Bus { pub scheduler: Scheduler, bios: Box<[u8; 0x4000]>, - iwram: Box<[u8; 0x8000]>, ewram: Box<[u8; 0x40000]>, + iwram: Box<[u8; 0x8000]>, last_read: u32, last_bios_fetch: u32, // According to medium article, MMBN6 has an email bug due to null pointer dereference in the BIOS // region [00DCh+8] in bios is 0xE129F000; https://problemkaputt.de/gbatek.htm#GBAUnpredictableThings @@ -61,8 +57,8 @@ impl Bus { Self { scheduler: Scheduler::new(), bios: zero_arr(), - iwram: zero_arr(), ewram: zero_arr(), + iwram: zero_arr(), last_read: 0, last_bios_fetch: 0xE129F000, ppu: PPU::new(), @@ -103,17 +99,35 @@ impl Bus { index } - #[inline] - pub fn backup_byte(&self, address: u32) -> u8 { + fn backup_byte(&self, address: u32) -> Option { if self.gamepak.backup_memory.is_empty() { - return 0; + return None; } let base_address = 0x0E000000; let mask = (self.gamepak.backup_memory.len() - 1) as u32; let index = ((address - base_address) & mask) as usize; - self.gamepak.backup_memory[index] + Some(index) + } + + #[inline] + pub fn read_backup_byte(&self, address: u32) -> u8 { + match self.backup_byte(address) { + Some(index) => self.gamepak.backup_memory[index], + None => 0, + } + } + + #[inline] + pub fn write_backup_byte(&mut self, address: u32, value: u8) { + match self.backup_byte(address) { + Some(index) => { + self.gamepak.backup_memory[index] = value; + self.gamepak.ram_updated = true; + } + None => {} + } } pub fn read(&mut self, address: u32, access_type: AccessType) -> T { @@ -129,7 +143,33 @@ impl Bus { } pub fn cost(&mut self, address: u32, width: u32, access_type: AccessType) { - self.scheduler.current += 1; // TODO: wait table + let region = (address >> 24) as usize; + let cycles = match region { + 0x00 | 0x03 | 0x04 | 0x07 => 1, + 0x02 => { + if width == 32 { + 6 + } else { + 3 + } + } + 0x05 | 0x06 => { + if width == 32 { + 2 + } else { + 1 + } + } + 0x08..=0x0D => self.rom_cost(width, access_type), + 0x0E | 0x0F => 5, + _ => 1, + }; + + self.scheduler.current += cycles as u64; + } + + pub fn rom_cost(&mut self, width: u32, access_type: AccessType) -> usize { + 0 } fn read_register_16(&mut self, address: u32) -> u16 { @@ -158,16 +198,56 @@ impl BusValue for u16 { 0x05 => little_endian(&*bus.ppu.palette_ram, Bus::palette_index(address)), 0x06 => little_endian(&*bus.ppu.vram, Bus::vram_index(address)), 0x07 => little_endian(&*bus.ppu.oam, Bus::oam_index(address)), - 0x08..=0x0D => bus.gamepak.read_rom_region(address) as u16, + 0x08..=0x0D => u16::from_le_bytes([ + bus.gamepak.read_rom_region(address), + bus.gamepak.read_rom_region(address + 1), + ]), 0x0E | 0x0F => { - let byte = bus.backup_byte(address) as u16; + let byte = bus.read_backup_byte(address) as u16; byte | (byte << 8) } _ => bus.open() as u16, } } - fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType) {} + fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType) { + bus.cost(address, 16, access_type); + let bytes = value.to_le_bytes(); + let address = address & !1; // ensure even + + match address >> 24 { + 0x00 => {} // BIOS no write, + 0x02 => { + let index = Bus::ewram_index(address); + bus.ewram[index] = bytes[0]; + bus.ewram[index + 1] = bytes[1]; + } + 0x03 => { + let index = Bus::iwram_index(address); + bus.iwram[index] = bytes[0]; + bus.iwram[index + 1] = bytes[1]; + } + 0x04 => bus.write_register_16(address, value), + 0x05 => { + let index = Bus::palette_index(address); + bus.ppu.palette_ram[index] = bytes[0]; + bus.ppu.palette_ram[index + 1] = bytes[1]; + } + 0x06 => { + let index = Bus::vram_index(address); + bus.ppu.vram[index] = bytes[0]; + bus.ppu.vram[index + 1] = bytes[1]; + } + 0x07 => { + let index = Bus::oam_index(address); + bus.ppu.oam[index] = bytes[0]; + bus.ppu.oam[index + 1] = bytes[1]; + } + 0x08..=0x0D => bus.write_backup_byte(address, bytes[0]), + 0x0E | 0x0F => {} + _ => {} + } + } } // impl BusValue for u8 diff --git a/src/components/cpu/mod.rs b/src/components/cpu/mod.rs new file mode 100644 index 0000000..78a9886 --- /dev/null +++ b/src/components/cpu/mod.rs @@ -0,0 +1,12 @@ +pub struct Arm7tdmi { + r: [u32; 16], + banked_usr: [u32; 7], + banked_fiq: [u32; 7], + banked_irq: [u32; 2], + banked_svc: [u32; 2], + banked_abt: [u32; 2], + banked_und: [u32; 2], + cpsr: u32, + spsr_fiq: u32, + spsr_irq: u32, +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 99bf98b..7789bbe 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,5 +1,6 @@ pub mod apu; pub mod bus; +pub mod cpu; pub mod gamepak; pub mod joypad; pub mod ppu; From b75de949752a0c791bf431ca9697ed67be95a525 Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Thu, 30 Jul 2026 22:18:27 -0400 Subject: [PATCH 13/18] Implement u8 bus value --- Cargo.toml | 3 + src/components/bus.rs | 326 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 323 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 437fcf9..0ad5256 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,6 @@ version = "0.1.0" edition = "2024" [dependencies] + +[profile.dev] +opt-level = 1 \ No newline at end of file diff --git a/src/components/bus.rs b/src/components/bus.rs index e770b33..a8c74ae 100644 --- a/src/components/bus.rs +++ b/src/components/bus.rs @@ -5,6 +5,7 @@ // https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm#Memory%20Map // https://www.nesdev.org/wiki/Open_bus_behavior // https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm#Memory%20Map +// https://www.chibiakumas.com/arm/gba.php // https://blog.asie.pl/2025/09/wonderful-update-september-2025/ @@ -172,17 +173,325 @@ impl Bus { 0 } + // Take regiisters from an early commit and just map them back for now + // GBATEK lists 32-bit registers at TWO halfword addresses (e.g. "40000D4h,0D6h"). + // Both halves are independently addressable, so each gets its own table entry (0x40000D4 = bits 0-15, 0x40000D6 = bits 16-31) fn read_register_16(&mut self, address: u32) -> u16 { - 0 + match address { + // LCD I/O Registers + 0x4000000 => {} // LCD Control (DISPCNT), 16 bit register (read + write) + 0x4000002 => {} // Undocumented 16 bit register (read + write) + 0x4000004 => {} // Stat & LYC, 16 bit register (read + write) + 0x4000006 => {} // LY, 16 bit, (VCOUNT), read only + 0x4000008 => {} // BG0 Control (BG0CNT) 16 bit register (read + write) + 0x400000A => {} // BG1 Control (BG1CNT) 16 bit register (read + write) + 0x400000C => {} // BG2 Control (BG2CNT) 16 bit register (read + write) + 0x400000E => {} // BG3 Control (BG3CNT) 16 bit register (read + write) + 0x4000048 => {} // Inside of Window 0 and 1 (WININ), 16 bit register (read + write) + 0x400004A => {} // Inside of OBJ Window & Outside of Windows 2 (WINOUT) (read + write) + 0x400004E => {} // Not Used + 0x4000050 => {} // Color Special Effects Selection (BLDCNT), 16 bit register (read + write) + 0x4000052 => {} // Alpha Blending Coefficients (BLDALPHA), 16 bit register (read + write) + 0x4000056 => {} // Not Used + + // Sound Registers + 0x4000060 => {} // Channel 1 Sweep register (NR10) (SOUND1CNT_L), 16 bit register (read + write) + 0x4000062 => {} // Channel 1 Duty/Length/Envelope (NR11, NR12) (SOUND1CNT_H), 16 bit register (read + write) + 0x4000064 => {} // Channel 1 Frequency/Control (NR13, NR14) (SOUND1CNT_X), 16 bit register (read + write) + 0x4000066 => {} // Not Used + 0x4000068 => {} // Channel 2 Duty/Length/Envelope (NR21, NR22) (SOUND2CNT_L), 16 bit register (read + write) + 0x400006A => {} // Not Used + 0x400006C => {} // Channel 2 Frequency/Control (NR23, NR24) (SOUND2CNT_H), 16 bit register (read + write) + 0x400006E => {} // Not Used + 0x4000070 => {} // Channel 3 Stop/Wave RAM select (NR30) (SOUND3CNT_L), 16 bit register (read + write) + 0x4000072 => {} // Channel 3 Length/Volume (NR31, NR32), 16 bit register (read + write) + 0x4000074 => {} // Channel 3 Frequency/Control (NR33, NR34) (SOUND3CNT_X), 16 bit register (read + write) + 0x4000076 => {} // Not Used + 0x4000078 => {} // Channel 4 Length/Envelope (NR41, NR42) (SOUND4CNT_L), 16 bit register (read + write) + 0x400007A => {} // Not Used + 0x400007C => {} // Channel 4 Frequency/Control (NR43, NR44) (SOUND4CNT_H), 16 bit register (read + write) + 0x400007E => {} // Not Used + 0x4000080 => {} // Control Stereo/Volume/Enable (NR50, NR51) (SOUNDCNT_L), 16 bit register (read + write) + 0x4000082 => {} // Control Mixing/DMA Control (SOUNDCNT_H), 16 bit register (read + write) + 0x4000084 => {} // Control Sound on/off (NR52) (SOUNDCNT_X), 16 bit register (read + write) + 0x4000086 => {} // Not Used + 0x4000088 => {} // BIOS/Sound PWM Control (SOUNDBIAS), 16 bit register (read + write) + 0x400008A => {} // Not Used + 0x4000090 => {} // Channel 3 Wave Pattern RAM (2 banks) (WAVE_RAM) 2x10h in size, (read + write) + 0x40000A8 => {} // Not Used + + // DMA Transfer Channels + 0x40000BA => {} // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) + 0x40000C6 => {} // DMA 1 Control (DMA1CNT_H), 16 bit register (read + write) + 0x40000D2 => {} // DMA 2 Control (DMA2CNT_H), 16 bit register (read + write) + 0x40000DE => {} // DMA 3 Control (DMA3CNT_H), 16 bit register (read + write) + 0x40000E0 => {} // Not Used + + // Timer Registers + 0x4000100 => {} // Timer 0 Counter/Reload (TM0CNT_L), 16 bit register (read + write) + 0x4000102 => {} // Timer 0 Control (TM0CNT_H), 16 bit register (read + write) + 0x4000104 => {} // Timer 1 Counter/Reload (TM1CNT_L), 16 bit register (read + write) + 0x4000106 => {} // Timer 1 Control (TM1CNT_H), 16 bit register (read + write) + 0x4000108 => {} // Timer 2 Counter/Reload (TM2CNT_L), 16 bit register (read + write) + 0x400010A => {} // Timer 2 Control (TM2CNT_H), 16 bit register (read + write) + 0x400010C => {} // Timer 3 Counter/Reload (TM3CNT_L), 16 bit register (read + write) + 0x400010E => {} // Timer 3 Control (TM3CNT_H), 16 bit register (read + write) + 0x4000110 => {} // Not Used + + // Serial Communication (1) + 0x4000120 => {} // SIO Data (Normal-32bit Mode; shared with SIO Data 0 (Parent) (SIODATA32). SIO Data is a 32 bit register and SIO Data 0 (Parent) (Multi-Player Mode) is a 16 bit register (read + write) (SIOMULTI0) + 0x4000122 => {} // SIO Data 1 (1st Child) (Multi-Player Mode) (SIOMULTI1), 16 bit register (read + write) + 0x4000124 => {} // SIO Data 2 (2nd Child) (Multi-Player Mode) (SIOMULTI2), 16 bit register (read + write) + 0x4000126 => {} // SIO Data 3 (3rd Child) (Multi-Player Mode) (SIOMULTI3), 16 bit register (read + write) + 0x4000128 => {} // SIO Control Register (SIOCNT), 16 bit register (read + write) + 0x400012A => {} // SIO Data (Local of MultiPlayer; shared with SIODATA8) (SIOMLT_SEND), 16 bit register (read + write); SIO Data (Normal-8bit and UART Mode) (SIODATA8), 16 bit register (read + write) + 0x400012C => {} // Not Used + + // Keypad Input + 0x4000130 => {} // Key Status (KEYINPUT), 16 bit register read only + 0x4000132 => {} // Key Interrupt Control (KEYCNT), 16 bit register (read + write) + + // Serial Communication (2) + 0x4000134 => {} // SIO Mode Select/General Purpose Data (RCNT), 16 bit register (read + write) + 0x4000136 => {} // Ancient - Infrared Register (Prototypes only) (IR) + 0x4000138 => {} // Not Used + 0x4000140 => {} // SIO JOY Bus Control (JOYCNT), 16 bit register (read + write) + 0x4000142 => {} // Not Used + 0x4000150 => {} // SIO JOY Bus Receive Data (JOY_RECV), 32 bit register (read + write) + 0x4000154 => {} // SIO JOY Bus Transmit Data (JOY_TRANS), 32 bit register (read + write) + 0x4000158 => {} // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + maybe write?) + 0x400015A => {} // Not Used + + //Interrupt, Waitstate, and Power-Down Control + 0x4000200 => {} // Interrupt Enable Register (IE), 16 bit register (read + write) + 0x4000202 => {} // Interrupt Request Flags / IRQ Acknowledge (IF), 16 bit register (read + write) + 0x4000204 => {} // Game Pak Waitstate Control (AITCNT), 16 bit register (read + write) + 0x4000206 => {} // Not used + 0x4000208 => {} // Interrupt Master Enable Register (IME), 16 bit register (read + write) + 0x400020A => {} // Not used + 0x4000300 => {} // Undocumented - Post Boot Flag (POSTFLG), 8 bit register (read + write) + 0x4000301 => {} // Undocumented - Power Down Control (HALTCNT), 8 bit register (write only) + 0x4000302 => {} // Not used + 0x4000410 => {} // Undocumented - Purpose Unknown / Bug ??? 0FFh + 0x4000411 => {} // Not used + 0x4000800 => {} // Undocumented - Internal Memory Control, 32 bit register (read + write) + 0x4000804 => {} // Not used + address if (address & 0xFF00FFFF) == 0x04000800 => {} // Mirrors of 4000800h (repeated each 64K), 32 bit (read + write) + _ => {} + } + + 0 as u16 } - fn write_register_16(&mut self, address: u32, value: u16) {} + fn write_register_16(&mut self, address: u32, value: u16) { + match address { + // LCD I/O Registers + 0x4000000 => {} // LCD Control (DISPCNT), 16 bit register (read + write) + 0x4000002 => {} // Undocumented 16 bit register (read + write) + 0x4000004 => {} // Stat & LYC, 16 bit register (read + write) + 0x4000008 => {} // BG0 Control (BG0CNT) 16 bit register (read + write) + 0x400000A => {} // BG1 Control (BG1CNT) 16 bit register (read + write) + 0x400000C => {} // BG2 Control (BG2CNT) 16 bit register (read + write) + 0x400000E => {} // BG3 Control (BG3CNT) 16 bit register (read + write) + 0x4000010 => {} // BG0 X-Offset (BG0HOFS) 16 bit register (write only) + 0x4000012 => {} // BG0 Y-Offset (BG0VOFS) 16 bit register (write only) + 0x4000014 => {} // BG1 X-Offset (BG1HOFS) 16 bit register (write only) + 0x4000016 => {} // BG1 Y-Offset (BG1VOFS) 16 bit register (write only) + 0x4000018 => {} // BG2 X-Offset (BG2HOFS) 16 bit register (write only) + 0x400001A => {} // BG2 Y-Offset (BG2VOFS) 16 bit register (write only) + 0x400001C => {} // BG3 X-Offset (BG3HOFS) 16 bit register (write only) + 0x400001E => {} // BG3 Y-Offset (BG3VOFS) 16 bit register (write only) + 0x4000020 => {} // BG2 Rotation/Scaling Parameter A (dx) (BG2PA), 16 bit register (write only) + 0x4000022 => {} // BG2 Rotation/Scaling Parameter B (dmx) (BG2PB), 16 bit register (write only) + 0x4000024 => {} // BG2 Rotation/Scaling Parameter C (dy) (BG2PC), 16 bit register (write only) + 0x4000026 => {} // BG2 Rotation/Scaling Parameter D (dmy) (BG2PD), 16 bit register (write only) + 0x4000028 => {} // BG2 Reference Point X-Coordinate (BG2X), 32 bit register (write only) + 0x400002C => {} // BG2 Reference Point Y-Coordinate (BG2Y), 32 bit register (write only) + 0x4000030 => {} // BG3 Rotation/Scaling Parameter A (dx) (BG3PA), 16 bit register (write only) + 0x4000032 => {} // BG3 Rotation/Scaling Parameter B (dmx) (BG3PB), 16 bit register (write only) + 0x4000034 => {} // BG3 Rotation/Scaling Parameter C (dy) (BG3PC), 16 bit register (write only) + 0x4000036 => {} // BG3 Rotation/Scaling Parameter D (dmy) (BG3PD), 16 bit register (write only) + 0x4000038 => {} // BG3 Reference Point X-Coordinate (BG3X), 32 bit register (write only) + 0x400003C => {} // BG3 Reference Point Y-Coordinate (BG3Y), 32 bit register (write only) + 0x4000040 => {} // Window 0 Horizontal Dimensions (WIN0H), 16 bit register (write only) + 0x4000042 => {} // Window 1 Horizontal Dimensions (WIN1H), 16 bit register (write only) + 0x4000044 => {} // Window 0 Vertical Dimensions (WIN0V), 16 bit register (write only) + 0x4000046 => {} // Window 1 Vertical Dimensions (WIN1V), 16 bit register (write only) + 0x4000048 => {} // Inside of Window 0 and 1 (WININ), 16 bit register (read + write) + 0x400004A => {} // Inside of OBJ Window & Outside of Windows 2 (WINOUT), 16 bit register (read + write) + 0x400004C => {} // Mosaic Size (MOSAIC), 16 bit register (write only) + 0x400004E => {} // Not Used + 0x4000050 => {} // Color Special Effects Selection (BLDCNT), 16 bit register (read + write) + 0x4000052 => {} // Alpha Blending Coefficients (BLDALPHA), 16 bit register (read + write) + 0x4000054 => {} // Brightness (Fade-In/Out) Coefficient (BLDY), 16 bit register (write only) + 0x4000056 => {} // Not Used + + // Sound Registers + 0x4000060 => {} // Channel 1 Sweep register (NR10) (SOUND1CNT_L), 16 bit register (read + write) + 0x4000062 => {} // Channel 1 Duty/Length/Envelope (NR11, NR12) (SOUND1CNT_H), 16 bit register (read + write) + 0x4000064 => {} // Channel 1 Frequency/Control (NR13, NR14) (SOUND1CNT_X), 16 bit register (read + write) + 0x4000066 => {} // Not Used + 0x4000068 => {} // Channel 2 Duty/Length/Envelope (NR21, NR22) (SOUND2CNT_L), 16 bit register (read + write) + 0x400006A => {} // Not Used + 0x400006C => {} // Channel 2 Frequency/Control (NR23, NR24) (SOUND2CNT_H), 16 bit register (read + write) + 0x400006E => {} // Not Used + 0x4000070 => {} // Channel 3 Stop/Wave RAM select (NR30) (SOUND3CNT_L), 16 bit register (read + write) + 0x4000072 => {} // Channel 3 Length/Volume (NR31, NR32), 16 bit register (read + write) + 0x4000074 => {} // Channel 3 Frequency/Control (NR33, NR34) (SOUND3CNT_X), 16 bit register (read + write) + 0x4000076 => {} // Not Used + 0x4000078 => {} // Channel 4 Length/Envelope (NR41, NR42) (SOUND4CNT_L), 16 bit register (read + write) + 0x400007A => {} // Not Used + 0x400007C => {} // Channel 4 Frequency/Control (NR43, NR44) (SOUND4CNT_H), 16 bit register (read + write) + 0x400007E => {} // Not Used + 0x4000080 => {} // Control Stereo/Volume/Enable (NR50, NR51) (SOUNDCNT_L), 16 bit register (read + write) + 0x4000082 => {} // Control Mixing/DMA Control (SOUNDCNT_H), 16 bit register (read + write) + 0x4000084 => {} // Control Sound on/off (NR52) (SOUNDCNT_X), 16 bit register (read + write) + 0x4000086 => {} // Not Used + 0x4000088 => {} // BIOS/Sound PWM Control (SOUNDBIAS), 16 bit register (read + write) + 0x400008A => {} // Not Used + 0x4000090 => {} // Channel 3 Wave Pattern RAM (2 banks) (WAVE_RAM) 2x10h in size, (read + write) + 0x40000A0 => {} // Channel A FIFO, Data 0-3, (FIFO_A) (write only), 32 bit register + 0x40000A4 => {} // Channel B FIFO, Data 0-3, (FIFO_B) (write only), 32 bit register + 0x40000A8 => {} // Not Used + + // DMA Transfer Channels + 0x40000B0 => {} // DMA 0 Source Address (DMA0SAD), 32 bit register (write only) + 0x40000B4 => {} // DMA 0 Destination Address (DMA0DAD), 32 bit register (write only) + 0x40000B8 => {} // DMA 0 Word Count (DMA0CNT_L), 16 bit register (write only) + 0x40000BA => {} // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) + 0x40000BC => {} // DMA 1 Source Address (DMA1SAD), 32 bit register (write only) + 0x40000C0 => {} // DMA 1 Destination Address (DMA1DAD), 32 bit register (write only) + 0x40000C6 => {} // DMA 1 Control (DMA1CNT_H), 16 bit register (read + write) + 0x40000C8 => {} // DMA 2 Source Address (DMA2SAD), 32 bit register (write only) + 0x40000CC => {} // DMA 2 Destination Address (DMA2DAD), 32 bit register (write only) + 0x40000D0 => {} // DMA 2 Word Count (DMA2CNT_L), 16 bit register (write only) + 0x40000D2 => {} // DMA 2 Control (DMA2CNT_H), 16 bit register (read + write) + 0x40000D4 => {} // DMA 3 Source Address (DMA3SAD), 32 bit register (write only) + 0x40000D8 => {} // DMA 3 Destination Address (DMA3DAD), 32 bit register (write only) + 0x40000DC => {} // DMA 3 Word Count (DMA3CNT_L), 16 bit register (write only) + 0x40000DE => {} // DMA 3 Control (DMA3CNT_H), 16 bit register (read + write) + 0x40000E0 => {} // Not Used + + // Timer Registers + 0x4000100 => {} // Timer 0 Counter/Reload (TM0CNT_L), 16 bit register (read + write) + 0x4000102 => {} // Timer 0 Control (TM0CNT_H), 16 bit register (read + write) + 0x4000104 => {} // Timer 1 Counter/Reload (TM1CNT_L), 16 bit register (read + write) + 0x4000106 => {} // Timer 1 Control (TM1CNT_H), 16 bit register (read + write) + 0x4000108 => {} // Timer 2 Counter/Reload (TM2CNT_L), 16 bit register (read + write) + 0x400010A => {} // Timer 2 Control (TM2CNT_H), 16 bit register (read + write) + 0x400010C => {} // Timer 3 Counter/Reload (TM3CNT_L), 16 bit register (read + write) + 0x400010E => {} // Timer 3 Control (TM3CNT_H), 16 bit register (read + write) + 0x4000110 => {} // Not Usedrol (TM3CNT_H), 16 bit register (read + write) + 0x4000112 => {} // Not Used + + // Serial Communication (1) + 0x4000120 => {} // SIO Data (Normal-32bit Mode; shared with SIO Data 0 (Parent) (SIODATA32). SIO Data is a 32 bit register and SIO Data 0 (Parent) (Multi-Player Mode) is a 16 bit register (read + write) (SIOMULTI0) + 0x4000122 => {} // SIO Data 1 (1st Child) (Multi-Player Mode) (SIOMULTI1), 16 bit register (read + write) + 0x4000124 => {} // SIO Data 2 (2nd Child) (Multi-Player Mode) (SIOMULTI2), 16 bit register (read + write) + 0x4000126 => {} // SIO Data 3 (3rd Child) (Multi-Player Mode) (SIOMULTI3), 16 bit register (read + write) + 0x4000128 => {} // SIO Control Register (SIOCNT), 16 bit register (read + write) + 0x400012A => {} // SIO Data (Local of MultiPlayer; shared with SIODATA8) (SIOMLT_SEND), 16 bit register (read + write); SIO Data (Normal-8bit and UART Mode) (SIODATA8), 16 bit register (read + write) + 0x400012C => {} // Not Used + + // Keypad Input + 0x4000132 => {} // Key Interrupt Control (KEYCNT), 16 bit register (read + write) + + // Serial Communication (2) + 0x4000134 => {} // SIO Mode Select/General Purpose Data (RCNT), 16 bit register (read + write) + 0x4000136 => {} // Ancient - Infrared Register (Prototypes only) (IR) + 0x4000138 => {} // Not Used + 0x4000140 => {} // SIO JOY Bus Control (JOYCNT), 16 bit register (read + write) + 0x4000142 => {} // Not Used + 0x4000150 => {} // SIO JOY Bus Receive Data (JOY_RECV), 32 bit register (read + write) + 0x4000154 => {} // SIO JOY Bus Transmit Data (JOY_TRANS), 32 bit register (read + write) + 0x4000158 => {} // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + maybe write?) + 0x400015A => {} // Not Used + + //Interrupt, Waitstate, and Power-Down Control + 0x4000200 => {} // Interrupt Enable Register (IE), 16 bit register (read + write) + 0x4000202 => {} // Interrupt Request Flags / IRQ Acknowledge (IF), 16 bit register (read + write) + 0x4000204 => {} // Game Pak Waitstate Control (AITCNT), 16 bit register (read + write) + 0x4000206 => {} // Not used + 0x4000208 => {} // Interrupt Master Enable Register (IME), 16 bit register (read + write) + 0x400020A => {} // Not used + 0x4000300 => {} // Undocumented - Post Boot Flag (POSTFLG), 8 bit register (read + write) + 0x4000301 => {} // Undocumented - Power Down Control (HALTCNT), 8 bit register (write only) + 0x4000302 => {} // Not used + 0x4000410 => {} // Undocumented - Purpose Unknown / Bug ??? 0FFh + 0x4000411 => {} // Not used + 0x4000800 => {} // Undocumented - Internal Memory Control, 32 bit register (read + write) + 0x4000804 => {} // Not used + address if (address & 0xFF00FFFF) == 0x04000800 => {} // Mirrors of 4000800h (repeated each 64K), 32 bit (read + write) + _ => {} + } + } fn open(&self) -> u32 { 0 } } +impl BusValue for u8 { + fn read(bus: &mut Bus, address: u32, access_type: AccessType) -> Self { + bus.cost(address, 8, access_type); + + match address >> 24 { + 0x00 => bus.bios[(address & 0x3FFE) as usize], + 0x02 => bus.ewram[Bus::ewram_index(address)], + 0x03 => bus.iwram[Bus::iwram_index(address)], + 0x04 => { + let half_word = bus.read_register_16(address & !1); + if address & 1 == 0 { + half_word as u8 + } else { + (half_word >> 8) as u8 + } + } + 0x05 => bus.ppu.palette_ram[Bus::palette_index(address)], + 0x06 => bus.ppu.vram[Bus::vram_index(address)], + 0x07 => bus.ppu.oam[Bus::oam_index(address)], + 0x08..=0x0D => bus.gamepak.read_rom_region(address), + 0x0E | 0x0F => bus.read_backup_byte(address), + _ => bus.open() as u8, + } + } + + fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType) { + bus.cost(address, 8, access_type); + + match address >> 24 { + 0x00 => {} + 0x02 => bus.ewram[Bus::ewram_index(address)] = value, + 0x03 => bus.iwram[Bus::iwram_index(address)] = value, + 0x04 => { + let half_word = bus.read_register_16(address & !1); + let new_half_word = if address & 1 == 0 { + (half_word & 0xFF00) | value as u16 + } else { + (half_word & 0x00FF) | (value as u16) << 8 + }; + + bus.write_register_16(address & !1, new_half_word); + } + 0x05 => { + let index = Bus::palette_index(address) & !1; + bus.ppu.palette_ram[index] = value; + bus.ppu.palette_ram[index + 1] = value; + } + 0x06 => { + // TODO: regquires byte duplication based on MOde, bg/character region + let index = Bus::vram_index(address) & !1; + bus.ppu.vram[index] = value; + bus.ppu.vram[index + 1] = value; + } + 0x07 => {} + 0x08..=0x0D => {} + 0x0E | 0x0F => bus.write_backup_byte(address, value), + _ => {} + } + } +} + impl BusValue for u16 { fn read(bus: &mut Bus, address: u32, access_type: AccessType) -> Self { bus.cost(address, 16, access_type); @@ -243,13 +552,18 @@ impl BusValue for u16 { bus.ppu.oam[index] = bytes[0]; bus.ppu.oam[index + 1] = bytes[1]; } - 0x08..=0x0D => bus.write_backup_byte(address, bytes[0]), - 0x0E | 0x0F => {} + + 0x08..=0x0D => {} + 0x0E | 0x0F => bus.write_backup_byte(address, bytes[0]), _ => {} } } } -// impl BusValue for u8 +impl BusValue for u32 { + fn read(bus: &mut Bus, address: u32, access_type: AccessType) -> Self { + 0 as u32 + } -// impl BusValue as u32 + fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType) {} +} From bf1a6c3f717dfdea76bb53708c2106d9fcefa351 Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Fri, 31 Jul 2026 18:10:09 -0400 Subject: [PATCH 14/18] Implement u32 bus value, also add address bus trait --- src/components/bus.rs | 129 +++++++++++++++++++++++++++++++++++++- src/components/cpu/mod.rs | 26 ++++++-- 2 files changed, 146 insertions(+), 9 deletions(-) diff --git a/src/components/bus.rs b/src/components/bus.rs index a8c74ae..5d56d53 100644 --- a/src/components/bus.rs +++ b/src/components/bus.rs @@ -40,6 +40,17 @@ pub trait BusValue: sealed::Sealed + Sized + Copy { fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType); } +// need to add a generic with trait bound to cpu to create a mock bus for testing +pub trait AddressBus { + fn read_u8(&mut self, address: u32, access: AccessType) -> u8; + fn read_u16(&mut self, address: u32, access: AccessType) -> u16; + fn read_u32(&mut self, address: u32, access: AccessType) -> u32; + fn write_u8(&mut self, address: u32, value: u8, access: AccessType); + fn write_u16(&mut self, address: u32, value: u16, access: AccessType); + fn write_u32(&mut self, address: u32, value: u32, access: AccessType); + fn idle(&mut self, cycles: u64); +} + pub struct Bus { pub scheduler: Scheduler, bios: Box<[u8; 0x4000]>, @@ -513,7 +524,7 @@ impl BusValue for u16 { ]), 0x0E | 0x0F => { let byte = bus.read_backup_byte(address) as u16; - byte | (byte << 8) + (byte << 8) | byte } _ => bus.open() as u16, } @@ -562,8 +573,120 @@ impl BusValue for u16 { impl BusValue for u32 { fn read(bus: &mut Bus, address: u32, access_type: AccessType) -> Self { - 0 as u32 + bus.cost(address, 32, access_type); + let address = address & !3; // every 4th address + + let little_endian = |arr: &[u8], index: usize| { + u32::from_le_bytes([arr[index], arr[index + 1], arr[index + 2], arr[index + 3]]) + }; + + match address >> 24 { + 0x00 => little_endian(&*bus.bios, (address & 0x3FFF) as usize), + 0x02 => little_endian(&*bus.ewram, Bus::ewram_index(address)), + 0x03 => little_endian(&*bus.iwram, Bus::iwram_index(address)), + 0x04 => { + let low_half_word = bus.read_register_16(address); + let high_half_word = bus.read_register_16(address + 2); + + (high_half_word as u32) << 16 | low_half_word as u32 + } + 0x05 => little_endian(&*bus.ppu.palette_ram, Bus::palette_index(address)), + 0x06 => little_endian(&*bus.ppu.vram, Bus::vram_index(address)), + 0x07 => little_endian(&*bus.ppu.oam, Bus::oam_index(address)), + 0x08..=0x0D => u32::from_le_bytes([ + bus.gamepak.read_rom_region(address), + bus.gamepak.read_rom_region(address + 1), + bus.gamepak.read_rom_region(address + 2), + bus.gamepak.read_rom_region(address + 3), + ]), + 0x0E | 0x0F => { + let byte = bus.read_backup_byte(address) as u32; + (byte << 24) | (byte << 16) | (byte << 8) | byte + } + _ => bus.open() as u32, + } } - fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType) {} + fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType) { + bus.cost(address, 32, access_type); + let bytes = value.to_le_bytes(); + let address = address & !3; // every 4th address + + match address >> 24 { + 0x00 => {} // BIOS no write, + 0x02 => { + let index = Bus::ewram_index(address); + bus.ewram[index] = bytes[0]; + bus.ewram[index + 1] = bytes[1]; + bus.ewram[index + 2] = bytes[2]; + bus.ewram[index + 3] = bytes[3]; + } + 0x03 => { + let index = Bus::iwram_index(address); + bus.iwram[index] = bytes[0]; + bus.iwram[index + 1] = bytes[1]; + bus.iwram[index + 2] = bytes[2]; + bus.iwram[index + 3] = bytes[3]; + } + 0x04 => { + bus.write_register_16(address, u16::from_le_bytes([bytes[0], bytes[1]])); + bus.write_register_16(address + 2, u16::from_le_bytes([bytes[2], bytes[3]])); + } + 0x05 => { + let index = Bus::palette_index(address); + bus.ppu.palette_ram[index] = bytes[0]; + bus.ppu.palette_ram[index + 1] = bytes[1]; + bus.ppu.palette_ram[index + 2] = bytes[2]; + bus.ppu.palette_ram[index + 3] = bytes[3]; + } + 0x06 => { + let index = Bus::vram_index(address); + bus.ppu.vram[index] = bytes[0]; + bus.ppu.vram[index + 1] = bytes[1]; + bus.ppu.vram[index + 2] = bytes[2]; + bus.ppu.vram[index + 3] = bytes[3]; + } + 0x07 => { + let index = Bus::oam_index(address); + bus.ppu.oam[index] = bytes[0]; + bus.ppu.oam[index + 1] = bytes[1]; + bus.ppu.oam[index + 2] = bytes[2]; + bus.ppu.oam[index + 3] = bytes[3]; + } + + 0x08..=0x0D => {} + 0x0E | 0x0F => bus.write_backup_byte(address, bytes[0]), + _ => {} + } + } +} + +impl AddressBus for Bus { + fn read_u8(&mut self, address: u32, access: AccessType) -> u8 { + self.read::(address, access) + } + + fn read_u16(&mut self, address: u32, access: AccessType) -> u16 { + self.read::(address, access) + } + + fn read_u32(&mut self, address: u32, access: AccessType) -> u32 { + self.read::(address, access) + } + + fn write_u8(&mut self, address: u32, value: u8, access: AccessType) { + self.write::(address, value, access); + } + + fn write_u16(&mut self, address: u32, value: u16, access: AccessType) { + self.write::(address, value, access); + } + + fn write_u32(&mut self, address: u32, value: u32, access: AccessType) { + self.write::(address, value, access); + } + + fn idle(&mut self, cycles: u64) { + Bus::idle(self, cycles); + } } diff --git a/src/components/cpu/mod.rs b/src/components/cpu/mod.rs index 78a9886..bd0b3bf 100644 --- a/src/components/cpu/mod.rs +++ b/src/components/cpu/mod.rs @@ -1,12 +1,26 @@ +use crate::components::bus::AddressBus; + +// Maybe a mode index helper +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Mode { + User = 0x10, + Fiq = 0x11, + Irq = 0x12, + Supervisor = 0x13, + Abort = 0x17, + Undefined = 0x1B, + System = 0x1F, +} + pub struct Arm7tdmi { r: [u32; 16], banked_usr: [u32; 7], banked_fiq: [u32; 7], - banked_irq: [u32; 2], - banked_svc: [u32; 2], - banked_abt: [u32; 2], - banked_und: [u32; 2], + banked: [[u32; 2]; 5], cpsr: u32, - spsr_fiq: u32, - spsr_irq: u32, + spsr: [u32; 5], +} + +impl Arm7tdmi { + pub fn step(&mut self, bus: &mut A) {} } From ab8545266e9635265deee8c8d7e2a0766f358395 Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Sat, 1 Aug 2026 21:43:30 -0400 Subject: [PATCH 15/18] Update --- src/components/bus.rs | 34 +++++++++++++++++++++++++++ src/components/cpu/mod.rs | 44 +++++++++++++++++++++++++++++------ src/components/gamepak/mod.rs | 4 ++++ 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/components/bus.rs b/src/components/bus.rs index 5d56d53..d08cda1 100644 --- a/src/components/bus.rs +++ b/src/components/bus.rs @@ -690,3 +690,37 @@ impl AddressBus for Bus { Bus::idle(self, cycles); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::components::gamepak::GamePak; + + #[test] + fn test_bus_write_u32() { + let gamepak = GamePak::mock(); + let mut bus = Bus::new(gamepak); + + let address = 0x05000001; + let value = 0b100000001 as u32; + let access_type = AccessType::Sequential; + bus.write::(address, value, access_type); + + assert_eq!(bus.scheduler.current, 2); + assert_eq!(&bus.ppu.palette_ram[..4], [1, 1, 0, 0]); + + let gamepak = GamePak::mock(); + let mut bus = Bus::new(gamepak); + bus.write(address, value, access_type); + + assert_eq!(bus.scheduler.current, 2); + assert_eq!(&bus.ppu.palette_ram[..4], [1, 1, 0, 0]); + + let gamepak = GamePak::mock(); + let mut bus = Bus::new(gamepak); + bus.write_u32(address, value, access_type); + + assert_eq!(bus.scheduler.current, 2); + assert_eq!(&bus.ppu.palette_ram[..4], [1, 1, 0, 0]); + } +} \ No newline at end of file diff --git a/src/components/cpu/mod.rs b/src/components/cpu/mod.rs index bd0b3bf..cc8c90c 100644 --- a/src/components/cpu/mod.rs +++ b/src/components/cpu/mod.rs @@ -1,26 +1,56 @@ use crate::components::bus::AddressBus; // Maybe a mode index helper +// Section 2.7 in https://vision.gel.ulaval.ca/~jflalonde/cours/1001/h19/docs/ARM7TDMI.pdf +// Mode is determined by the bits 0-4 #[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[repr(u16)] pub enum Mode { - User = 0x10, + Usr = 0x10, Fiq = 0x11, Irq = 0x12, - Supervisor = 0x13, - Abort = 0x17, - Undefined = 0x1B, - System = 0x1F, + Svc = 0x13, + Abt = 0x17, + Und = 0x1B, + Sys = 0x1F, } -pub struct Arm7tdmi { +impl Mode { + fn from_cpsr(cpsr: u32) -> Mode { + match cpsr & 0x1F { + 0x10 => Mode::Usr, + 0x11 => Mode::Fiq, + 0x12 => Mode::Irq, + 0x13 => Mode::Svc, + 0x17 => Mode::Abt, + 0x1B => Mode::Und, + 0x1F => Mode::Sys, + _ => Mode::Usr, + } + + } +} + +pub struct Registers { r: [u32; 16], banked_usr: [u32; 7], banked_fiq: [u32; 7], - banked: [[u32; 2]; 5], + banked: [[u32; 2]; 4], cpsr: u32, spsr: [u32; 5], } +impl Registers { + fn new() {} +} + +pub struct Arm7tdmi { + registers: Registers, +} + impl Arm7tdmi { + pub fn step(&mut self, bus: &mut A) {} + + fn execute_arm() {} } diff --git a/src/components/gamepak/mod.rs b/src/components/gamepak/mod.rs index af63f4a..3cc2738 100644 --- a/src/components/gamepak/mod.rs +++ b/src/components/gamepak/mod.rs @@ -167,4 +167,8 @@ impl GamePak { let index = (address & 0x01FFFFFF) as usize; self.rom.get(index).copied().unwrap_or(0) } + + pub fn mock() -> Self { + Self { rom: vec![0u8; kilobytes(32000)], sav_path: PathBuf::from("mock.sav"), backup_memory: vec![0u8; kilobytes(32)], backup_type: BackupType::Sram, ram_updated: false } + } } From 6711f1b3766739c138119354ed5841e56f37b3d8 Mon Sep 17 00:00:00 2001 From: donishadsmith Date: Sun, 2 Aug 2026 20:04:09 -0400 Subject: [PATCH 16/18] Add bank register logic --- src/components/bios.rs | 4 + src/components/bus.rs | 3 +- src/components/cpu/mod.rs | 267 ++++++++++++++++++++++++++++++---- src/components/gamepak/mod.rs | 8 +- src/components/gba.rs | 0 5 files changed, 252 insertions(+), 30 deletions(-) create mode 100644 src/components/bios.rs create mode 100644 src/components/gba.rs diff --git a/src/components/bios.rs b/src/components/bios.rs new file mode 100644 index 0000000..a86f1db --- /dev/null +++ b/src/components/bios.rs @@ -0,0 +1,4 @@ +// BIOS Functions need implementing: https://problemkaputt.de/gbatek-bios-function-summary.htm +// https://github.com/mgba-emu/mgba/blob/master/src/gba/hle-bios.c +// https://github.com/mgba-emu/mgba/blob/master/src/gba/hle-bios.s +// https://github.com/mgba-emu/mgba/blob/master/src/gba/bios.c \ No newline at end of file diff --git a/src/components/bus.rs b/src/components/bus.rs index d08cda1..9a24ef5 100644 --- a/src/components/bus.rs +++ b/src/components/bus.rs @@ -12,6 +12,7 @@ // Just do an afterboot startup // https://problemkaputt.de/gbatek.htm#GBAUnpredictableThings + use crate::components::{ apu::APU, gamepak::GamePak, ppu::PPU, scheduler::Scheduler, utils::zero_arr, }; @@ -723,4 +724,4 @@ mod tests { assert_eq!(bus.scheduler.current, 2); assert_eq!(&bus.ppu.palette_ram[..4], [1, 1, 0, 0]); } -} \ No newline at end of file +} diff --git a/src/components/cpu/mod.rs b/src/components/cpu/mod.rs index cc8c90c..9e297de 100644 --- a/src/components/cpu/mod.rs +++ b/src/components/cpu/mod.rs @@ -1,56 +1,267 @@ -use crate::components::bus::AddressBus; +use crate::components::bus::{AccessType, AddressBus}; +/* +The ARM University Program, ARM Architecture Fundamentals: https://www.youtube.com/watch?v=7LqPJGnBPMM +- Load/store architecture: only load and store can deal with memory, hence processing memory values requires loading +into source register, execution of some instruction, then storing value to some register. No instructions like certain +GBC instructions that can add some value to some value at a specific memory address (ADD A, (HL)) in one instruction. + +- Each processor mode has access to its own stack space and its own private subset of registers (banked) + +13 general purpose registers: + Low registers: r0-r7 + High registers: r8-r12 + +3 special registers + stack pointer (sp) - r13 + link register (lr) - r14 + program counter (pc) - r15 + +- privileged state can manually change mode bits, set autoomatically when a mode change occurs due to exception (e.g., interrupt) + +- Exception Handling Steps: + 1) Save processor status + - Copy cpsr into spsr_ to hold snapshot of current mode processor state + - Stores the return address in lr_ + 2) Change processor status for exception + - Mode field bits + - ARM or Thumb state + - interrupt disable bits (if needed) + - sets pc to vector address + 3) Execute + 4) Return to main + - Restore cpsr from spsr_ + - Restore pc from lr_ + +Instructions +- Each instruction is conditional; hence it can be a Noop if condition flags fail +- By default condition flags are not changed + +Example Instructions: +SUB r0, r1, #5 -> r0 = r1 - 5 +ADD r2, r3, r3, LSL #2 (add with inline shift) -> r2 = r3 + (r3 * 4) +ANDS r4, r4, #0x20 (suffix x means alu condition codes will reflect operation results) -> r4 &= 0x20; +ADDEQ r5, r5, r6 (instruction only executes if the EQ condition is true at the executation stage of pipeline else its noop) -> if (EQ) r5 += r6 +B