Compare commits
	
		
			8 Commits
		
	
	
		
			aa5db57b61
			...
			1f4e01bd32
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1f4e01bd32 | |||
| f9ba5f9b3a | |||
| 764ceff7a8 | |||
| e8fe2a5dec | |||
| b659c829fb | |||
| 0bde97c6cd | |||
| d921255c8e | |||
| aa1db42b0f | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| /.vscode | /.vscode | ||||||
| /bin | /bin | ||||||
| **/zig-cache | /zig-cache | ||||||
| **/zig-out | /zig-out | ||||||
| /docs | /docs | ||||||
| **/*.log | **/*.log | ||||||
| **/*.bin | **/*.bin | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -10,6 +10,3 @@ | |||||||
| [submodule "lib/zig-datetime"] | [submodule "lib/zig-datetime"] | ||||||
| 	path = lib/zig-datetime | 	path = lib/zig-datetime | ||||||
| 	url = https://github.com/frmdstryr/zig-datetime | 	url = https://github.com/frmdstryr/zig-datetime | ||||||
| [submodule "lib/zig-toml"] |  | ||||||
| 	path = lib/zig-toml |  | ||||||
| 	url = https://github.com/aeronavery/zig-toml |  | ||||||
|   | |||||||
							
								
								
									
										29
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,29 +1,14 @@ | |||||||
| # ZBA (working title) | # ZBA (working title) | ||||||
| A Game Boy Advance Emulator written in Zig ⚡! | An in-progress Game Boy Advance Emulator written in Zig ⚡! | ||||||
|  |  | ||||||
| ## Scope |  | ||||||
| I'm hardly the first to write a Game Boy Advance Emulator nor will I be the last. This project isn't going to compete with the GOATs like  |  | ||||||
| [mGBA](https://github.com/mgba-emu) or [NanoBoyAdvance](https://github.com/nba-emu/NanoBoyAdvance). There aren't any interesting |  | ||||||
| ideas either like in [DSHBA](https://github.com/DenSinH/DSHBA).  |  | ||||||
|  |  | ||||||
| This is a simple (read: incomplete) for-fun long-term project. I hope to get "mostly there", which to me means that I'm not missing any major hardware |  | ||||||
| features and the set of possible improvements would be in memory timing or in UI/UX. With respect to that goal, here's what's outstanding:  |  | ||||||
|  |  | ||||||
| ### TODO  |  | ||||||
| - [ ] Affine Sprites |  | ||||||
| - [ ] Windowing (see [this branch](https://git.musuka.dev/paoda/zba/src/branch/window)) |  | ||||||
| - [ ] Audio Resampler (Having issues with SDL2's) |  | ||||||
| - [ ] Immediate Mode GUI |  | ||||||
| - [ ] Refactoring for easy-ish perf boosts |  | ||||||
|  |  | ||||||
| ## Tests  | ## Tests  | ||||||
| - [x] [jsmolka's GBA Test Collection](https://github.com/jsmolka/gba-tests) | - [ ] [jsmolka's GBA Test Collection](https://github.com/jsmolka/gba-tests) | ||||||
|     - [x] `arm.gba` and `thumb.gba` |     - [x] `arm.gba` and `thumb.gba` | ||||||
|     - [x] `flash64.gba`, `flash128.gba`, `none.gba`, and `sram.gba` |     - [x] `flash64.gba`, `flash128.gba`, `none.gba`, and `sram.gba` | ||||||
|     - [x] `hello.gba`, `shades.gba`, and `stripes.gba` |     - [x] `hello.gba`, `shades.gba`, and `stripes.gba` | ||||||
|     - [x] `memory.gba` |     - [x] `memory.gba` | ||||||
|     - [x] `bios.gba` |     - [x] `bios.gba` | ||||||
|     - [x] `nes.gba` |     - [ ] `nes.gba` | ||||||
| - [ ] [DenSinH's GBA ROMs](https://github.com/DenSinH/GBARoms) | - [ ] [DenSinH's GBA ROMs](https://github.com/DenSinH/GBARoms) | ||||||
|     - [x] `eeprom-test` and `flash-test` |     - [x] `eeprom-test` and `flash-test` | ||||||
|     - [x] `midikey2freq` |     - [x] `midikey2freq` | ||||||
| @@ -50,20 +35,18 @@ features and the set of possible improvements would be in memory timing or in UI | |||||||
| * [ARM7TDMI Data Sheet](https://www.dca.fee.unicamp.br/cursos/EA871/references/ARM/ARM7TDMIDataSheet.pdf) | * [ARM7TDMI Data Sheet](https://www.dca.fee.unicamp.br/cursos/EA871/references/ARM/ARM7TDMIDataSheet.pdf) | ||||||
|  |  | ||||||
| ## Compiling | ## Compiling | ||||||
| Most recently built on Zig [0.10.0-dev.4474+b41b35f57](https://github.com/ziglang/zig/tree/b41b35f57) | Most recently built on Zig [0.10.0-dev.3900+ab4b26d8a](https://github.com/ziglang/zig/tree/ab4b26d8a) | ||||||
|  |  | ||||||
| ### Dependencies | ### Dependencies | ||||||
| * [SDL.zig](https://github.com/MasterQ32/SDL.zig) | * [SDL.zig](https://github.com/MasterQ32/SDL.zig) | ||||||
|     * [SDL2](https://www.libsdl.org/download-2.0.php) |     * [SDL2](https://www.libsdl.org/download-2.0.php) | ||||||
| * [zig-clap](https://github.com/Hejsil/zig-clap) | * [zig-clap](https://github.com/Hejsil/zig-clap) | ||||||
| * [known-folders](https://github.com/ziglibs/known-folders) | * [known-folders](https://github.com/ziglibs/known-folders) | ||||||
| * [zig-toml](https://github.com/aeronavery/zig-toml) | * [`bitfields.zig`](https://github.com/FlorenceOS/Florence/blob/aaa5a9e568197ad24780ec9adb421217530d4466/lib/util/bitfields.zig) | ||||||
| * [zig-datetime](https://github.com/frmdstryr/zig-datetime) |  | ||||||
| * [`bitfields.zig`](https://github.com/FlorenceOS/Florence/blob/aaa5a9e568/lib/util/bitfields.zig) |  | ||||||
|  |  | ||||||
| `bitfields.zig` from [FlorenceOS](https://github.com/FlorenceOS) is included under `lib/util/bitfield.zig`. | `bitfields.zig` from [FlorenceOS](https://github.com/FlorenceOS) is included under `lib/util/bitfield.zig`. | ||||||
|  |  | ||||||
| Use `git submodule update --init` from the project root to pull the git submodules `SDL.zig`, `zig-clap`, `known-folders`, `zig-toml` and `zig-datetime` | Use `git submodule update --init` from the project root to pull the git submodules `SDL.zig`, `zig-clap`, and `known-folders` | ||||||
|  |  | ||||||
| Be sure to provide SDL2 using:  | Be sure to provide SDL2 using:  | ||||||
| * Linux: Your distro's package manager | * Linux: Your distro's package manager | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								build.zig
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								build.zig
									
									
									
									
									
								
							| @@ -13,9 +13,6 @@ pub fn build(b: *std.build.Builder) void { | |||||||
|     const mode = b.standardReleaseOptions(); |     const mode = b.standardReleaseOptions(); | ||||||
|  |  | ||||||
|     const exe = b.addExecutable("zba", "src/main.zig"); |     const exe = b.addExecutable("zba", "src/main.zig"); | ||||||
|     exe.setMainPkgPath("."); // Necessary so that src/main.zig can embed example.toml |  | ||||||
|     exe.setTarget(target); |  | ||||||
|  |  | ||||||
|     // Known Folders (%APPDATA%, XDG, etc.) |     // Known Folders (%APPDATA%, XDG, etc.) | ||||||
|     exe.addPackagePath("known_folders", "lib/known-folders/known-folders.zig"); |     exe.addPackagePath("known_folders", "lib/known-folders/known-folders.zig"); | ||||||
|  |  | ||||||
| @@ -29,17 +26,13 @@ pub fn build(b: *std.build.Builder) void { | |||||||
|     // Argument Parsing Library |     // Argument Parsing Library | ||||||
|     exe.addPackagePath("clap", "lib/zig-clap/clap.zig"); |     exe.addPackagePath("clap", "lib/zig-clap/clap.zig"); | ||||||
|  |  | ||||||
|     // TOML Library |  | ||||||
|     exe.addPackagePath("toml", "lib/zig-toml/src/toml.zig"); |  | ||||||
|  |  | ||||||
|     // OpenGL 3.3 Bindings |  | ||||||
|     exe.addPackagePath("gl", "lib/gl.zig"); |  | ||||||
|  |  | ||||||
|     // Zig SDL Bindings: https://github.com/MasterQ32/SDL.zig |     // Zig SDL Bindings: https://github.com/MasterQ32/SDL.zig | ||||||
|     const sdk = Sdk.init(b); |     const sdk = Sdk.init(b); | ||||||
|     sdk.link(exe, .dynamic); |     sdk.link(exe, .dynamic); | ||||||
|  |  | ||||||
|     exe.addPackage(sdk.getNativePackage("sdl2")); |     exe.addPackage(sdk.getNativePackage("sdl2")); | ||||||
|  |  | ||||||
|  |     exe.setTarget(target); | ||||||
|     exe.setBuildMode(mode); |     exe.setBuildMode(mode); | ||||||
|     exe.install(); |     exe.install(); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								example.toml
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								example.toml
									
									
									
									
									
								
							| @@ -1,25 +0,0 @@ | |||||||
| [Host] |  | ||||||
| # Using nearest-neighbour scaling, how many times the native resolution  |  | ||||||
| # of the game bow should the screen be? |  | ||||||
| win_scale = 4 |  | ||||||
| # Enable VSYNC on the UI thread |  | ||||||
| vsync = true |  | ||||||
| # Mute ZBA |  | ||||||
| mute = false |  | ||||||
|  |  | ||||||
| [Guest] |  | ||||||
| # Sync Emulation to Audio  |  | ||||||
| audio_sync = false |  | ||||||
| # Sync Emulation to Video |  | ||||||
| video_sync = false |  | ||||||
| # Force RTC support |  | ||||||
| force_rtc = false |  | ||||||
| # Skip BIOS |  | ||||||
| skip_bios = false |  | ||||||
|  |  | ||||||
| [Debug] |  | ||||||
| # Enable detailed CPU logs |  | ||||||
| cpu_trace = false |  | ||||||
| # When false and builtin.mode == .Debug, ZBA will panic |  | ||||||
| # on unknown I/O reads |  | ||||||
| unhandled_io = true |  | ||||||
							
								
								
									
										5028
									
								
								lib/gl.zig
									
									
									
									
									
								
							
							
						
						
									
										5028
									
								
								lib/gl.zig
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							 Submodule lib/zig-clap updated: e5d09c4b2d...4f4196fc3b
									
								
							 Submodule lib/zig-toml deleted from 5dfa919e03
									
								
							| @@ -1,83 +0,0 @@ | |||||||
| const std = @import("std"); |  | ||||||
| const toml = @import("toml"); |  | ||||||
|  |  | ||||||
| const Allocator = std.mem.Allocator; |  | ||||||
|  |  | ||||||
| const log = std.log.scoped(.Config); |  | ||||||
| var state: Config = .{}; |  | ||||||
|  |  | ||||||
| const Config = struct { |  | ||||||
|     host: Host = .{}, |  | ||||||
|     guest: Guest = .{}, |  | ||||||
|     debug: Debug = .{}, |  | ||||||
|  |  | ||||||
|     /// Settings related to the Computer the Emulator is being run on |  | ||||||
|     const Host = struct { |  | ||||||
|         /// Using Nearest-Neighbor, multiply the resolution of the GBA Window |  | ||||||
|         win_scale: i64 = 3, |  | ||||||
|         /// Enable Vsync |  | ||||||
|         /// |  | ||||||
|         /// Note: This does not affect whether Emulation is synced to 59Hz |  | ||||||
|         vsync: bool = true, |  | ||||||
|         /// Mute ZBA |  | ||||||
|         mute: bool = false, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     // Settings realted to the emulation itself |  | ||||||
|     const Guest = struct { |  | ||||||
|         /// Whether Emulation thread to sync to Audio Callbacks |  | ||||||
|         audio_sync: bool = true, |  | ||||||
|         /// Whether Emulation thread should sync to 59Hz |  | ||||||
|         video_sync: bool = true, |  | ||||||
|         /// Whether RTC I/O should always be enabled |  | ||||||
|         force_rtc: bool = false, |  | ||||||
|         /// Skip BIOS |  | ||||||
|         skip_bios: bool = false, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     /// Settings related to debugging ZBA |  | ||||||
|     const Debug = struct { |  | ||||||
|         /// Enable CPU Trace logs |  | ||||||
|         cpu_trace: bool = false, |  | ||||||
|         /// If false and ZBA is built in debug mode, ZBA will panic on unhandled I/O |  | ||||||
|         unhandled_io: bool = true, |  | ||||||
|     }; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| pub fn config() *const Config { |  | ||||||
|     return &state; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Reads a config file and then loads it into the global state |  | ||||||
| pub fn load(allocator: Allocator, config_path: []const u8) !void { |  | ||||||
|     var config_file = try std.fs.cwd().openFile(config_path, .{}); |  | ||||||
|     defer config_file.close(); |  | ||||||
|  |  | ||||||
|     log.info("loaded from {s}", .{config_path}); |  | ||||||
|  |  | ||||||
|     const contents = try config_file.readToEndAlloc(allocator, try config_file.getEndPos()); |  | ||||||
|     defer allocator.free(contents); |  | ||||||
|  |  | ||||||
|     const table = try toml.parseContents(allocator, contents, null); |  | ||||||
|     defer table.deinit(); |  | ||||||
|  |  | ||||||
|     // TODO: Report unknown config options |  | ||||||
|  |  | ||||||
|     if (table.keys.get("Host")) |host| { |  | ||||||
|         if (host.Table.keys.get("win_scale")) |scale| state.host.win_scale = scale.Integer; |  | ||||||
|         if (host.Table.keys.get("vsync")) |vsync| state.host.vsync = vsync.Boolean; |  | ||||||
|         if (host.Table.keys.get("mute")) |mute| state.host.mute = mute.Boolean; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (table.keys.get("Guest")) |guest| { |  | ||||||
|         if (guest.Table.keys.get("audio_sync")) |sync| state.guest.audio_sync = sync.Boolean; |  | ||||||
|         if (guest.Table.keys.get("video_sync")) |sync| state.guest.video_sync = sync.Boolean; |  | ||||||
|         if (guest.Table.keys.get("force_rtc")) |forced| state.guest.force_rtc = forced.Boolean; |  | ||||||
|         if (guest.Table.keys.get("skip_bios")) |skip| state.guest.skip_bios = skip.Boolean; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (table.keys.get("Debug")) |debug| { |  | ||||||
|         if (debug.Table.keys.get("cpu_trace")) |trace| state.debug.cpu_trace = trace.Boolean; |  | ||||||
|         if (debug.Table.keys.get("unhandled_io")) |unhandled| state.debug.unhandled_io = unhandled.Boolean; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -46,7 +46,7 @@ iwram: Iwram, | |||||||
| ewram: Ewram, | ewram: Ewram, | ||||||
| io: Io, | io: Io, | ||||||
|  |  | ||||||
| cpu: *Arm7tdmi, | cpu: ?*Arm7tdmi, | ||||||
| sched: *Scheduler, | sched: *Scheduler, | ||||||
|  |  | ||||||
| pub fn init(self: *Self, allocator: Allocator, sched: *Scheduler, cpu: *Arm7tdmi, paths: FilePaths) !void { | pub fn init(self: *Self, allocator: Allocator, sched: *Scheduler, cpu: *Arm7tdmi, paths: FilePaths) !void { | ||||||
| @@ -82,9 +82,9 @@ pub fn dbgRead(self: *const Self, comptime T: type, address: u32) T { | |||||||
|         // General Internal Memory |         // General Internal Memory | ||||||
|         0x00 => blk: { |         0x00 => blk: { | ||||||
|             if (address < Bios.size) |             if (address < Bios.size) | ||||||
|                 break :blk self.bios.dbgRead(T, self.cpu.r[15], aligned_addr); |                 break :blk self.bios.dbgRead(T, self.cpu.?.r[15], aligned_addr); | ||||||
|  |  | ||||||
|             break :blk self.openBus(T, address); |             break :blk self.readOpenBus(T, address); | ||||||
|         }, |         }, | ||||||
|         0x02 => self.ewram.read(T, aligned_addr), |         0x02 => self.ewram.read(T, aligned_addr), | ||||||
|         0x03 => self.iwram.read(T, aligned_addr), |         0x03 => self.iwram.read(T, aligned_addr), | ||||||
| @@ -109,63 +109,48 @@ pub fn dbgRead(self: *const Self, comptime T: type, address: u32) T { | |||||||
|  |  | ||||||
|             break :blk @as(T, value) * multiplier; |             break :blk @as(T, value) * multiplier; | ||||||
|         }, |         }, | ||||||
|         else => self.openBus(T, address), |         else => self.readOpenBus(T, address), | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| fn readIo(self: *const Self, comptime T: type, unaligned_address: u32) T { | fn readIo(self: *const Self, comptime T: type, unaligned_address: u32) T { | ||||||
|     const maybe_value = io.read(self, T, forceAlign(T, unaligned_address)); |     const maybe_value = io.read(self, T, forceAlign(T, unaligned_address)); | ||||||
|     return if (maybe_value) |value| value else self.openBus(T, unaligned_address); |     return if (maybe_value) |value| value else self.readOpenBus(T, unaligned_address); | ||||||
| } | } | ||||||
|  |  | ||||||
| fn openBus(self: *const Self, comptime T: type, address: u32) T { | fn readOpenBus(self: *const Self, comptime T: type, address: u32) T { | ||||||
|     const r15 = self.cpu.r[15]; |     const r15 = self.cpu.?.r[15]; | ||||||
|  |  | ||||||
|     const word = blk: { |     const word = blk: { | ||||||
|         // If Arm, get the most recently fetched instruction (PC + 8) |         // If u32 Open Bus, read recently fetched opcode (PC + 8) | ||||||
|         if (!self.cpu.cpsr.t.read()) break :blk self.cpu.pipe.stage[1].?; |         if (!self.cpu.?.cpsr.t.read()) break :blk self.dbgRead(u32, r15 + 4); | ||||||
|  |  | ||||||
|         const page = @truncate(u8, r15 >> 24); |         const page = @truncate(u8, r15 >> 24); | ||||||
|  |  | ||||||
|         // PC + 2 = stage[0] |  | ||||||
|         // PC + 4 = stage[1] |  | ||||||
|         // PC + 6 = Need a Debug Read for this? |  | ||||||
|  |  | ||||||
|         switch (page) { |         switch (page) { | ||||||
|             // EWRAM, PALRAM, VRAM, and Game ROM (16-bit) |             // EWRAM, PALRAM, VRAM, and Game ROM (16-bit) | ||||||
|             0x02, 0x05, 0x06, 0x08...0x0D => { |             0x02, 0x05, 0x06, 0x08...0x0D => { | ||||||
|                 const halfword: u32 = @truncate(u16, self.cpu.pipe.stage[1].?); |                 // (PC + 4) | ||||||
|                 break :blk halfword << 16 | halfword; |                 const halfword = self.dbgRead(u16, r15 + 2); | ||||||
|             }, |  | ||||||
|  |  | ||||||
|  |                 break :blk @as(u32, halfword) << 16 | halfword; | ||||||
|  |             }, | ||||||
|             // BIOS or OAM (32-bit) |             // BIOS or OAM (32-bit) | ||||||
|             0x00, 0x07 => { |             0x00, 0x07 => { | ||||||
|                 // Aligned: (PC + 6) | (PC + 4) |                 // Aligned: (PC + 6) | (PC + 4) | ||||||
|                 // Unaligned: (PC + 4) | (PC + 2) |                 // Unaligned: (PC + 4) | (PC + 2) | ||||||
|                 const aligned = address & 3 == 0b00; |                 const offset: u32 = if (address & 3 == 0b00) 2 else 0; | ||||||
|  |  | ||||||
|                 // TODO: What to do on PC + 6? |                 break :blk @as(u32, self.dbgRead(u16, r15 + 2 + offset)) << 16 | self.dbgRead(u16, r15 + offset); | ||||||
|                 const high: u32 = if (aligned) self.dbgRead(u16, r15 + 4) else @truncate(u16, self.cpu.pipe.stage[1].?); |  | ||||||
|                 const low: u32 = @truncate(u16, self.cpu.pipe.stage[@boolToInt(aligned)].?); |  | ||||||
|  |  | ||||||
|                 break :blk high << 16 | low; |  | ||||||
|             }, |             }, | ||||||
|  |  | ||||||
|             // IWRAM (16-bit but special) |             // IWRAM (16-bit but special) | ||||||
|             0x03 => { |             0x03 => { | ||||||
|                 // Aligned: (PC + 2) | (PC + 4) |                 // Aligned: (PC + 2) | (PC + 4) | ||||||
|                 // Unaligned: (PC + 4) | (PC + 2) |                 // Unaligned: (PC + 4) | (PC + 2) | ||||||
|                 const aligned = address & 3 == 0b00; |                 const offset: u32 = if (address & 3 == 0b00) 2 else 0; | ||||||
|  |  | ||||||
|                 const high: u32 = @truncate(u16, self.cpu.pipe.stage[1 - @boolToInt(aligned)].?); |                 break :blk @as(u32, self.dbgRead(u16, r15 + 2 - offset)) << 16 | self.dbgRead(u16, r15 + offset); | ||||||
|                 const low: u32 = @truncate(u16, self.cpu.pipe.stage[@boolToInt(aligned)].?); |  | ||||||
|  |  | ||||||
|                 break :blk high << 16 | low; |  | ||||||
|             }, |  | ||||||
|             else => { |  | ||||||
|                 log.err("THUMB open bus read from 0x{X:0>2} page @0x{X:0>8}", .{ page, address }); |  | ||||||
|                 @panic("invariant most-likely broken"); |  | ||||||
|             }, |             }, | ||||||
|  |             else => unreachable, | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -182,9 +167,9 @@ pub fn read(self: *Self, comptime T: type, address: u32) T { | |||||||
|         // General Internal Memory |         // General Internal Memory | ||||||
|         0x00 => blk: { |         0x00 => blk: { | ||||||
|             if (address < Bios.size) |             if (address < Bios.size) | ||||||
|                 break :blk self.bios.read(T, self.cpu.r[15], aligned_addr); |                 break :blk self.bios.read(T, self.cpu.?.r[15], aligned_addr); | ||||||
|  |  | ||||||
|             break :blk self.openBus(T, address); |             break :blk self.readOpenBus(T, address); | ||||||
|         }, |         }, | ||||||
|         0x02 => self.ewram.read(T, aligned_addr), |         0x02 => self.ewram.read(T, aligned_addr), | ||||||
|         0x03 => self.iwram.read(T, aligned_addr), |         0x03 => self.iwram.read(T, aligned_addr), | ||||||
| @@ -209,7 +194,7 @@ pub fn read(self: *Self, comptime T: type, address: u32) T { | |||||||
|  |  | ||||||
|             break :blk @as(T, value) * multiplier; |             break :blk @as(T, value) * multiplier; | ||||||
|         }, |         }, | ||||||
|         else => self.openBus(T, address), |         else => self.readOpenBus(T, address), | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1091
									
								
								src/core/apu.zig
									
									
									
									
									
								
							
							
						
						
									
										1091
									
								
								src/core/apu.zig
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,142 +0,0 @@ | |||||||
| const io = @import("../bus/io.zig"); |  | ||||||
| const util = @import("../../util.zig"); |  | ||||||
|  |  | ||||||
| const Scheduler = @import("../scheduler.zig").Scheduler; |  | ||||||
| const FrameSequencer = @import("../apu.zig").FrameSequencer; |  | ||||||
| const Tick = @import("../apu.zig").Apu.Tick; |  | ||||||
| const Envelope = @import("device/Envelope.zig"); |  | ||||||
| const Length = @import("device/Length.zig"); |  | ||||||
| const Lfsr = @import("signal/Lfsr.zig"); |  | ||||||
|  |  | ||||||
| const Self = @This(); |  | ||||||
|  |  | ||||||
| /// Write-only |  | ||||||
| /// NR41 |  | ||||||
| len: u6, |  | ||||||
| /// NR42 |  | ||||||
| envelope: io.Envelope, |  | ||||||
| /// NR43 |  | ||||||
| poly: io.PolyCounter, |  | ||||||
| /// NR44 |  | ||||||
| cnt: io.NoiseControl, |  | ||||||
|  |  | ||||||
| /// Length Functionarlity |  | ||||||
| len_dev: Length, |  | ||||||
|  |  | ||||||
| /// Envelope Functionality |  | ||||||
| env_dev: Envelope, |  | ||||||
|  |  | ||||||
| // Linear Feedback Shift Register |  | ||||||
| lfsr: Lfsr, |  | ||||||
|  |  | ||||||
| enabled: bool, |  | ||||||
| sample: i8, |  | ||||||
|  |  | ||||||
| pub fn init(sched: *Scheduler) Self { |  | ||||||
|     return .{ |  | ||||||
|         .len = 0, |  | ||||||
|         .envelope = .{ .raw = 0 }, |  | ||||||
|         .poly = .{ .raw = 0 }, |  | ||||||
|         .cnt = .{ .raw = 0 }, |  | ||||||
|         .enabled = false, |  | ||||||
|  |  | ||||||
|         .len_dev = Length.create(), |  | ||||||
|         .env_dev = Envelope.create(), |  | ||||||
|         .lfsr = Lfsr.create(sched), |  | ||||||
|  |  | ||||||
|         .sample = 0, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn reset(self: *Self) void { |  | ||||||
|     self.len = 0; |  | ||||||
|     self.envelope.raw = 0; |  | ||||||
|     self.poly.raw = 0; |  | ||||||
|     self.cnt.raw = 0; |  | ||||||
|  |  | ||||||
|     self.sample = 0; |  | ||||||
|     self.enabled = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn tick(self: *Self, comptime kind: Tick) void { |  | ||||||
|     switch (kind) { |  | ||||||
|         .Length => self.len_dev.tick(self.cnt.length_enable.read(), &self.enabled), |  | ||||||
|         .Envelope => self.env_dev.tick(self.envelope), |  | ||||||
|         .Sweep => @compileError("Channel 4 does not implement Sweep"), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR41, NR42 |  | ||||||
| pub fn sound4CntL(self: *const Self) u16 { |  | ||||||
|     return @as(u16, self.envelope.raw) << 8; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR41, NR42 |  | ||||||
| pub fn setSound4CntL(self: *Self, value: u16) void { |  | ||||||
|     self.setNr41(@truncate(u8, value)); |  | ||||||
|     self.setNr42(@truncate(u8, value >> 8)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR41 |  | ||||||
| pub fn setNr41(self: *Self, len: u8) void { |  | ||||||
|     self.len = @truncate(u6, len); |  | ||||||
|     self.len_dev.timer = @as(u7, 64) - @truncate(u6, len); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR42 |  | ||||||
| pub fn setNr42(self: *Self, value: u8) void { |  | ||||||
|     self.envelope.raw = value; |  | ||||||
|     if (!self.isDacEnabled()) self.enabled = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR43, NR44 |  | ||||||
| pub fn sound4CntH(self: *const Self) u16 { |  | ||||||
|     return @as(u16, self.poly.raw & 0x40) << 8 | self.cnt.raw; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR43, NR44 |  | ||||||
| pub fn setSound4CntH(self: *Self, fs: *const FrameSequencer, value: u16) void { |  | ||||||
|     self.poly.raw = @truncate(u8, value); |  | ||||||
|     self.setNr44(fs, @truncate(u8, value >> 8)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR44 |  | ||||||
| pub fn setNr44(self: *Self, fs: *const FrameSequencer, byte: u8) void { |  | ||||||
|     var new: io.NoiseControl = .{ .raw = byte }; |  | ||||||
|  |  | ||||||
|     if (new.trigger.read()) { |  | ||||||
|         self.enabled = true; |  | ||||||
|  |  | ||||||
|         if (self.len_dev.timer == 0) { |  | ||||||
|             self.len_dev.timer = |  | ||||||
|                 if (!fs.isLengthNext() and new.length_enable.read()) 63 else 64; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Update The Frequency Timer |  | ||||||
|         self.lfsr.reload(self.poly); |  | ||||||
|         self.lfsr.shift = 0x7FFF; |  | ||||||
|  |  | ||||||
|         // Update Envelope and Volume |  | ||||||
|         self.env_dev.timer = self.envelope.period.read(); |  | ||||||
|         if (fs.isEnvelopeNext() and self.env_dev.timer != 0b111) self.env_dev.timer += 1; |  | ||||||
|  |  | ||||||
|         self.env_dev.vol = self.envelope.init_vol.read(); |  | ||||||
|  |  | ||||||
|         self.enabled = self.isDacEnabled(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     util.audio.length.ch4.update(self, fs, new); |  | ||||||
|     self.cnt = new; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn onNoiseEvent(self: *Self, late: u64) void { |  | ||||||
|     self.lfsr.onLfsrTimerExpire(self.poly, late); |  | ||||||
|  |  | ||||||
|     self.sample = 0; |  | ||||||
|     if (!self.isDacEnabled()) return; |  | ||||||
|     self.sample = if (self.enabled) self.lfsr.sample() * @as(i8, self.env_dev.vol) else 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn isDacEnabled(self: *const Self) bool { |  | ||||||
|     return self.envelope.raw & 0xF8 != 0x00; |  | ||||||
| } |  | ||||||
| @@ -1,138 +0,0 @@ | |||||||
| const io = @import("../bus/io.zig"); |  | ||||||
| const util = @import("../../util.zig"); |  | ||||||
|  |  | ||||||
| const Scheduler = @import("../scheduler.zig").Scheduler; |  | ||||||
| const FrameSequencer = @import("../apu.zig").FrameSequencer; |  | ||||||
| const Tick = @import("../apu.zig").Apu.Tick; |  | ||||||
| const Length = @import("device/Length.zig"); |  | ||||||
| const Envelope = @import("device/Envelope.zig"); |  | ||||||
| const Square = @import("signal/Square.zig"); |  | ||||||
|  |  | ||||||
| const Self = @This(); |  | ||||||
|  |  | ||||||
| /// NR21 |  | ||||||
| duty: io.Duty, |  | ||||||
| /// NR22 |  | ||||||
| envelope: io.Envelope, |  | ||||||
| /// NR23, NR24 |  | ||||||
| freq: io.Frequency, |  | ||||||
|  |  | ||||||
| /// Length Functionarlity |  | ||||||
| len_dev: Length, |  | ||||||
| /// Envelope Functionality |  | ||||||
| env_dev: Envelope, |  | ||||||
| /// FrequencyTimer Functionality |  | ||||||
| square: Square, |  | ||||||
|  |  | ||||||
| enabled: bool, |  | ||||||
| sample: i8, |  | ||||||
|  |  | ||||||
| pub fn init(sched: *Scheduler) Self { |  | ||||||
|     return .{ |  | ||||||
|         .duty = .{ .raw = 0 }, |  | ||||||
|         .envelope = .{ .raw = 0 }, |  | ||||||
|         .freq = .{ .raw = 0 }, |  | ||||||
|         .enabled = false, |  | ||||||
|  |  | ||||||
|         .square = Square.init(sched), |  | ||||||
|         .len_dev = Length.create(), |  | ||||||
|         .env_dev = Envelope.create(), |  | ||||||
|  |  | ||||||
|         .sample = 0, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn reset(self: *Self) void { |  | ||||||
|     self.duty.raw = 0; |  | ||||||
|     self.envelope.raw = 0; |  | ||||||
|     self.freq.raw = 0; |  | ||||||
|  |  | ||||||
|     self.sample = 0; |  | ||||||
|     self.enabled = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn tick(self: *Self, comptime kind: Tick) void { |  | ||||||
|     switch (kind) { |  | ||||||
|         .Length => self.len_dev.tick(self.freq.length_enable.read(), &self.enabled), |  | ||||||
|         .Envelope => self.env_dev.tick(self.envelope), |  | ||||||
|         .Sweep => @compileError("Channel 2 does not implement Sweep"), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn onToneEvent(self: *Self, late: u64) void { |  | ||||||
|     self.square.onSquareTimerExpire(Self, self.freq, late); |  | ||||||
|  |  | ||||||
|     self.sample = 0; |  | ||||||
|     if (!self.isDacEnabled()) return; |  | ||||||
|     self.sample = if (self.enabled) self.square.sample(self.duty) * @as(i8, self.env_dev.vol) else 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR21, NR22 |  | ||||||
| pub fn sound2CntL(self: *const Self) u16 { |  | ||||||
|     return @as(u16, self.envelope.raw) << 8 | (self.duty.raw & 0xC0); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR21, NR22 |  | ||||||
| pub fn setSound2CntL(self: *Self, value: u16) void { |  | ||||||
|     self.setNr21(@truncate(u8, value)); |  | ||||||
|     self.setNr22(@truncate(u8, value >> 8)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR21 |  | ||||||
| pub fn setNr21(self: *Self, value: u8) void { |  | ||||||
|     self.duty.raw = value; |  | ||||||
|     self.len_dev.timer = @as(u7, 64) - @truncate(u6, value); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR22 |  | ||||||
| pub fn setNr22(self: *Self, value: u8) void { |  | ||||||
|     self.envelope.raw = value; |  | ||||||
|     if (!self.isDacEnabled()) self.enabled = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR23, NR24 |  | ||||||
| pub fn sound2CntH(self: *const Self) u16 { |  | ||||||
|     return self.freq.raw & 0x4000; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR23, NR24 |  | ||||||
| pub fn setSound2CntH(self: *Self, fs: *const FrameSequencer, value: u16) void { |  | ||||||
|     self.setNr23(@truncate(u8, value)); |  | ||||||
|     self.setNr24(fs, @truncate(u8, value >> 8)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR23 |  | ||||||
| pub fn setNr23(self: *Self, byte: u8) void { |  | ||||||
|     self.freq.raw = (self.freq.raw & 0xFF00) | byte; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR24 |  | ||||||
| pub fn setNr24(self: *Self, fs: *const FrameSequencer, byte: u8) void { |  | ||||||
|     var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) }; |  | ||||||
|  |  | ||||||
|     if (new.trigger.read()) { |  | ||||||
|         self.enabled = true; |  | ||||||
|  |  | ||||||
|         if (self.len_dev.timer == 0) { |  | ||||||
|             self.len_dev.timer = |  | ||||||
|                 if (!fs.isLengthNext() and new.length_enable.read()) 63 else 64; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         self.square.reload(Self, self.freq.frequency.read()); |  | ||||||
|  |  | ||||||
|         // Reload Envelope period and timer |  | ||||||
|         self.env_dev.timer = self.envelope.period.read(); |  | ||||||
|         if (fs.isEnvelopeNext() and self.env_dev.timer != 0b111) self.env_dev.timer += 1; |  | ||||||
|  |  | ||||||
|         self.env_dev.vol = self.envelope.init_vol.read(); |  | ||||||
|  |  | ||||||
|         self.enabled = self.isDacEnabled(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     util.audio.length.update(Self, self, fs, new); |  | ||||||
|     self.freq = new; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn isDacEnabled(self: *const Self) bool { |  | ||||||
|     return self.envelope.raw & 0xF8 != 0; |  | ||||||
| } |  | ||||||
| @@ -1,184 +0,0 @@ | |||||||
| const io = @import("../bus/io.zig"); |  | ||||||
| const util = @import("../../util.zig"); |  | ||||||
|  |  | ||||||
| const Scheduler = @import("../scheduler.zig").Scheduler; |  | ||||||
| const FrameSequencer = @import("../apu.zig").FrameSequencer; |  | ||||||
| const Length = @import("device/Length.zig"); |  | ||||||
| const Envelope = @import("device/Envelope.zig"); |  | ||||||
| const Sweep = @import("device/Sweep.zig"); |  | ||||||
| const Square = @import("signal/Square.zig"); |  | ||||||
|  |  | ||||||
| const Tick = @import("../apu.zig").Apu.Tick; |  | ||||||
|  |  | ||||||
| const Self = @This(); |  | ||||||
|  |  | ||||||
| /// NR10 |  | ||||||
| sweep: io.Sweep, |  | ||||||
| /// NR11 |  | ||||||
| duty: io.Duty, |  | ||||||
| /// NR12 |  | ||||||
| envelope: io.Envelope, |  | ||||||
| /// NR13, NR14 |  | ||||||
| freq: io.Frequency, |  | ||||||
|  |  | ||||||
| /// Length Functionality |  | ||||||
| len_dev: Length, |  | ||||||
| /// Sweep Functionality |  | ||||||
| sweep_dev: Sweep, |  | ||||||
| /// Envelope Functionality |  | ||||||
| env_dev: Envelope, |  | ||||||
| /// Frequency Timer Functionality |  | ||||||
| square: Square, |  | ||||||
| enabled: bool, |  | ||||||
|  |  | ||||||
| sample: i8, |  | ||||||
|  |  | ||||||
| pub fn init(sched: *Scheduler) Self { |  | ||||||
|     return .{ |  | ||||||
|         .sweep = .{ .raw = 0 }, |  | ||||||
|         .duty = .{ .raw = 0 }, |  | ||||||
|         .envelope = .{ .raw = 0 }, |  | ||||||
|         .freq = .{ .raw = 0 }, |  | ||||||
|         .sample = 0, |  | ||||||
|         .enabled = false, |  | ||||||
|  |  | ||||||
|         .square = Square.init(sched), |  | ||||||
|         .len_dev = Length.create(), |  | ||||||
|         .sweep_dev = Sweep.create(), |  | ||||||
|         .env_dev = Envelope.create(), |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn reset(self: *Self) void { |  | ||||||
|     self.sweep.raw = 0; |  | ||||||
|     self.sweep_dev.calc_performed = false; |  | ||||||
|  |  | ||||||
|     self.duty.raw = 0; |  | ||||||
|     self.envelope.raw = 0; |  | ||||||
|     self.freq.raw = 0; |  | ||||||
|  |  | ||||||
|     self.sample = 0; |  | ||||||
|     self.enabled = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn tick(self: *Self, comptime kind: Tick) void { |  | ||||||
|     switch (kind) { |  | ||||||
|         .Length => self.len_dev.tick(self.freq.length_enable.read(), &self.enabled), |  | ||||||
|         .Envelope => self.env_dev.tick(self.envelope), |  | ||||||
|         .Sweep => self.sweep_dev.tick(self), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn onToneSweepEvent(self: *Self, late: u64) void { |  | ||||||
|     self.square.onSquareTimerExpire(Self, self.freq, late); |  | ||||||
|  |  | ||||||
|     self.sample = 0; |  | ||||||
|     if (!self.isDacEnabled()) return; |  | ||||||
|     self.sample = if (self.enabled) self.square.sample(self.duty) * @as(i8, self.env_dev.vol) else 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR10, NR11, NR12 |  | ||||||
| pub fn setSound1Cnt(self: *Self, value: u32) void { |  | ||||||
|     self.setSound1CntL(@truncate(u8, value)); |  | ||||||
|     self.setSound1CntH(@truncate(u16, value >> 16)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR10 |  | ||||||
| pub fn sound1CntL(self: *const Self) u8 { |  | ||||||
|     return self.sweep.raw & 0x7F; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR10 |  | ||||||
| pub fn setSound1CntL(self: *Self, value: u8) void { |  | ||||||
|     const new = io.Sweep{ .raw = value }; |  | ||||||
|  |  | ||||||
|     if (self.sweep.direction.read() and !new.direction.read()) { |  | ||||||
|         // Sweep Negate bit has been cleared |  | ||||||
|         // If At least 1 Sweep Calculation has been made since |  | ||||||
|         // the last trigger, the channel is immediately disabled |  | ||||||
|  |  | ||||||
|         if (self.sweep_dev.calc_performed) self.enabled = false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     self.sweep.raw = value; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR11, NR12 |  | ||||||
| pub fn sound1CntH(self: *const Self) u16 { |  | ||||||
|     return @as(u16, self.envelope.raw) << 8 | (self.duty.raw & 0xC0); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR11, NR12 |  | ||||||
| pub fn setSound1CntH(self: *Self, value: u16) void { |  | ||||||
|     self.setNr11(@truncate(u8, value)); |  | ||||||
|     self.setNr12(@truncate(u8, value >> 8)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR11 |  | ||||||
| pub fn setNr11(self: *Self, value: u8) void { |  | ||||||
|     self.duty.raw = value; |  | ||||||
|     self.len_dev.timer = @as(u7, 64) - @truncate(u6, value); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR12 |  | ||||||
| pub fn setNr12(self: *Self, value: u8) void { |  | ||||||
|     self.envelope.raw = value; |  | ||||||
|     if (!self.isDacEnabled()) self.enabled = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR13, NR14 |  | ||||||
| pub fn sound1CntX(self: *const Self) u16 { |  | ||||||
|     return self.freq.raw & 0x4000; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR13, NR14 |  | ||||||
| pub fn setSound1CntX(self: *Self, fs: *const FrameSequencer, value: u16) void { |  | ||||||
|     self.setNr13(@truncate(u8, value)); |  | ||||||
|     self.setNr14(fs, @truncate(u8, value >> 8)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR13 |  | ||||||
| pub fn setNr13(self: *Self, byte: u8) void { |  | ||||||
|     self.freq.raw = (self.freq.raw & 0xFF00) | byte; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR14 |  | ||||||
| pub fn setNr14(self: *Self, fs: *const FrameSequencer, byte: u8) void { |  | ||||||
|     var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) }; |  | ||||||
|  |  | ||||||
|     if (new.trigger.read()) { |  | ||||||
|         self.enabled = true; |  | ||||||
|  |  | ||||||
|         if (self.len_dev.timer == 0) { |  | ||||||
|             self.len_dev.timer = |  | ||||||
|                 if (!fs.isLengthNext() and new.length_enable.read()) 63 else 64; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         self.square.reload(Self, self.freq.frequency.read()); |  | ||||||
|  |  | ||||||
|         // Reload Envelope period and timer |  | ||||||
|         self.env_dev.timer = self.envelope.period.read(); |  | ||||||
|         if (fs.isEnvelopeNext() and self.env_dev.timer != 0b111) self.env_dev.timer += 1; |  | ||||||
|  |  | ||||||
|         self.env_dev.vol = self.envelope.init_vol.read(); |  | ||||||
|  |  | ||||||
|         // Sweep Trigger Behaviour |  | ||||||
|         const sw_period = self.sweep.period.read(); |  | ||||||
|         const sw_shift = self.sweep.shift.read(); |  | ||||||
|  |  | ||||||
|         self.sweep_dev.calc_performed = false; |  | ||||||
|         self.sweep_dev.shadow = self.freq.frequency.read(); |  | ||||||
|         self.sweep_dev.timer = if (sw_period == 0) 8 else sw_period; |  | ||||||
|         self.sweep_dev.enabled = sw_period != 0 or sw_shift != 0; |  | ||||||
|         if (sw_shift != 0) _ = self.sweep_dev.calculate(self.sweep, &self.enabled); |  | ||||||
|  |  | ||||||
|         self.enabled = self.isDacEnabled(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     util.audio.length.update(Self, self, fs, new); |  | ||||||
|     self.freq = new; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn isDacEnabled(self: *const Self) bool { |  | ||||||
|     return self.envelope.raw & 0xF8 != 0; |  | ||||||
| } |  | ||||||
| @@ -1,132 +0,0 @@ | |||||||
| const io = @import("../bus/io.zig"); |  | ||||||
| const util = @import("../../util.zig"); |  | ||||||
|  |  | ||||||
| const Scheduler = @import("../scheduler.zig").Scheduler; |  | ||||||
| const FrameSequencer = @import("../apu.zig").FrameSequencer; |  | ||||||
| const Tick = @import("../apu.zig").Apu.Tick; |  | ||||||
|  |  | ||||||
| const Length = @import("device/Length.zig"); |  | ||||||
| const Wave = @import("signal/Wave.zig"); |  | ||||||
|  |  | ||||||
| const Self = @This(); |  | ||||||
|  |  | ||||||
| /// Write-only |  | ||||||
| /// NR30 |  | ||||||
| select: io.WaveSelect, |  | ||||||
| /// NR31 |  | ||||||
| length: u8, |  | ||||||
| /// NR32 |  | ||||||
| vol: io.WaveVolume, |  | ||||||
| /// NR33, NR34 |  | ||||||
| freq: io.Frequency, |  | ||||||
|  |  | ||||||
| /// Length Functionarlity |  | ||||||
| len_dev: Length, |  | ||||||
| wave_dev: Wave, |  | ||||||
|  |  | ||||||
| enabled: bool, |  | ||||||
| sample: i8, |  | ||||||
|  |  | ||||||
| pub fn init(sched: *Scheduler) Self { |  | ||||||
|     return .{ |  | ||||||
|         .select = .{ .raw = 0 }, |  | ||||||
|         .vol = .{ .raw = 0 }, |  | ||||||
|         .freq = .{ .raw = 0 }, |  | ||||||
|         .length = 0, |  | ||||||
|  |  | ||||||
|         .len_dev = Length.create(), |  | ||||||
|         .wave_dev = Wave.init(sched), |  | ||||||
|         .enabled = false, |  | ||||||
|         .sample = 0, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn reset(self: *Self) void { |  | ||||||
|     self.select.raw = 0; |  | ||||||
|     self.length = 0; |  | ||||||
|     self.vol.raw = 0; |  | ||||||
|     self.freq.raw = 0; |  | ||||||
|  |  | ||||||
|     self.sample = 0; |  | ||||||
|     self.enabled = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn tick(self: *Self, comptime kind: Tick) void { |  | ||||||
|     switch (kind) { |  | ||||||
|         .Length => self.len_dev.tick(self.freq.length_enable.read(), &self.enabled), |  | ||||||
|         .Envelope => @compileError("Channel 3 does not implement Envelope"), |  | ||||||
|         .Sweep => @compileError("Channel 3 does not implement Sweep"), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR30, NR31, NR32 |  | ||||||
| pub fn setSound3Cnt(self: *Self, value: u32) void { |  | ||||||
|     self.setSound3CntL(@truncate(u8, value)); |  | ||||||
|     self.setSound3CntH(@truncate(u16, value >> 16)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR30 |  | ||||||
| pub fn setSound3CntL(self: *Self, value: u8) void { |  | ||||||
|     self.select.raw = value; |  | ||||||
|     if (!self.select.enabled.read()) self.enabled = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR31, NR32 |  | ||||||
| pub fn sound3CntH(self: *const Self) u16 { |  | ||||||
|     return @as(u16, self.length & 0xE0) << 8; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR31, NR32 |  | ||||||
| pub fn setSound3CntH(self: *Self, value: u16) void { |  | ||||||
|     self.setNr31(@truncate(u8, value)); |  | ||||||
|     self.vol.raw = (@truncate(u8, value >> 8)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR31 |  | ||||||
| pub fn setNr31(self: *Self, len: u8) void { |  | ||||||
|     self.length = len; |  | ||||||
|     self.len_dev.timer = 256 - @as(u9, len); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR33, NR34 |  | ||||||
| pub fn setSound3CntX(self: *Self, fs: *const FrameSequencer, value: u16) void { |  | ||||||
|     self.setNr33(@truncate(u8, value)); |  | ||||||
|     self.setNr34(fs, @truncate(u8, value >> 8)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR33 |  | ||||||
| pub fn setNr33(self: *Self, byte: u8) void { |  | ||||||
|     self.freq.raw = (self.freq.raw & 0xFF00) | byte; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NR34 |  | ||||||
| pub fn setNr34(self: *Self, fs: *const FrameSequencer, byte: u8) void { |  | ||||||
|     var new: io.Frequency = .{ .raw = (@as(u16, byte) << 8) | (self.freq.raw & 0xFF) }; |  | ||||||
|  |  | ||||||
|     if (new.trigger.read()) { |  | ||||||
|         self.enabled = true; |  | ||||||
|  |  | ||||||
|         if (self.len_dev.timer == 0) { |  | ||||||
|             self.len_dev.timer = |  | ||||||
|                 if (!fs.isLengthNext() and new.length_enable.read()) 255 else 256; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Update The Frequency Timer |  | ||||||
|         self.wave_dev.reload(self.freq.frequency.read()); |  | ||||||
|         self.wave_dev.offset = 0; |  | ||||||
|  |  | ||||||
|         self.enabled = self.select.enabled.read(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     util.audio.length.update(Self, self, fs, new); |  | ||||||
|     self.freq = new; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn onWaveEvent(self: *Self, late: u64) void { |  | ||||||
|     self.wave_dev.onWaveTimerExpire(self.freq, self.select, late); |  | ||||||
|  |  | ||||||
|     self.sample = 0; |  | ||||||
|     if (!self.select.enabled.read()) return; |  | ||||||
|     // Convert unsigned 4-bit wave sample to signed 8-bit sample |  | ||||||
|     self.sample = (2 * @as(i8, self.wave_dev.sample(self.select)) - 15) >> self.wave_dev.shift(self.vol); |  | ||||||
| } |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| const io = @import("../../bus/io.zig"); |  | ||||||
|  |  | ||||||
| const Self = @This(); |  | ||||||
|  |  | ||||||
| /// Period Timer |  | ||||||
| timer: u3, |  | ||||||
| /// Current Volume |  | ||||||
| vol: u4, |  | ||||||
|  |  | ||||||
| pub fn create() Self { |  | ||||||
|     return .{ .timer = 0, .vol = 0 }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn tick(self: *Self, nrx2: io.Envelope) void { |  | ||||||
|     if (nrx2.period.read() != 0) { |  | ||||||
|         if (self.timer != 0) self.timer -= 1; |  | ||||||
|  |  | ||||||
|         if (self.timer == 0) { |  | ||||||
|             self.timer = nrx2.period.read(); |  | ||||||
|  |  | ||||||
|             if (nrx2.direction.read()) { |  | ||||||
|                 if (self.vol < 0xF) self.vol += 1; |  | ||||||
|             } else { |  | ||||||
|                 if (self.vol > 0x0) self.vol -= 1; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| const Self = @This(); |  | ||||||
|  |  | ||||||
| timer: u9, |  | ||||||
|  |  | ||||||
| pub fn create() Self { |  | ||||||
|     return .{ .timer = 0 }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn tick(self: *Self, enabled: bool, ch_enable: *bool) void { |  | ||||||
|     if (enabled) { |  | ||||||
|         if (self.timer == 0) return; |  | ||||||
|         self.timer -= 1; |  | ||||||
|  |  | ||||||
|         // By returning early if timer == 0, this is only |  | ||||||
|         // true if timer == 0 because of the decrement we just did |  | ||||||
|         if (self.timer == 0) ch_enable.* = false; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,52 +0,0 @@ | |||||||
| const io = @import("../../bus/io.zig"); |  | ||||||
| const ToneSweep = @import("../ToneSweep.zig"); |  | ||||||
|  |  | ||||||
| const Self = @This(); |  | ||||||
|  |  | ||||||
| timer: u8, |  | ||||||
| enabled: bool, |  | ||||||
| shadow: u11, |  | ||||||
|  |  | ||||||
| calc_performed: bool, |  | ||||||
|  |  | ||||||
| pub fn create() Self { |  | ||||||
|     return .{ |  | ||||||
|         .timer = 0, |  | ||||||
|         .enabled = false, |  | ||||||
|         .shadow = 0, |  | ||||||
|         .calc_performed = false, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn tick(self: *Self, ch1: *ToneSweep) void { |  | ||||||
|     if (self.timer != 0) self.timer -= 1; |  | ||||||
|  |  | ||||||
|     if (self.timer == 0) { |  | ||||||
|         const period = ch1.sweep.period.read(); |  | ||||||
|         self.timer = if (period == 0) 8 else period; |  | ||||||
|         if (!self.calc_performed) self.calc_performed = true; |  | ||||||
|  |  | ||||||
|         if (self.enabled and period != 0) { |  | ||||||
|             const new_freq = self.calculate(ch1.sweep, &ch1.enabled); |  | ||||||
|  |  | ||||||
|             if (new_freq <= 0x7FF and ch1.sweep.shift.read() != 0) { |  | ||||||
|                 ch1.freq.frequency.write(@truncate(u11, new_freq)); |  | ||||||
|                 self.shadow = @truncate(u11, new_freq); |  | ||||||
|  |  | ||||||
|                 _ = self.calculate(ch1.sweep, &ch1.enabled); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Calculates the Sweep Frequency |  | ||||||
| pub fn calculate(self: *Self, sweep: io.Sweep, ch_enable: *bool) u12 { |  | ||||||
|     const shadow = @as(u12, self.shadow); |  | ||||||
|     const shadow_shifted = shadow >> sweep.shift.read(); |  | ||||||
|     const decrease = sweep.direction.read(); |  | ||||||
|  |  | ||||||
|     const freq = if (decrease) shadow - shadow_shifted else shadow + shadow_shifted; |  | ||||||
|     if (freq > 0x7FF) ch_enable.* = false; |  | ||||||
|  |  | ||||||
|     return freq; |  | ||||||
| } |  | ||||||
| @@ -1,59 +0,0 @@ | |||||||
| const io = @import("../../bus/io.zig"); |  | ||||||
|  |  | ||||||
| /// Linear Feedback Shift Register |  | ||||||
| const Scheduler = @import("../../scheduler.zig").Scheduler; |  | ||||||
| const FrameSequencer = @import("../../apu.zig").FrameSequencer; |  | ||||||
| const Noise = @import("../Noise.zig"); |  | ||||||
|  |  | ||||||
| const Self = @This(); |  | ||||||
| pub const interval: u64 = (1 << 24) / (1 << 22); |  | ||||||
|  |  | ||||||
| shift: u15, |  | ||||||
| timer: u16, |  | ||||||
|  |  | ||||||
| sched: *Scheduler, |  | ||||||
|  |  | ||||||
| pub fn create(sched: *Scheduler) Self { |  | ||||||
|     return .{ |  | ||||||
|         .shift = 0, |  | ||||||
|         .timer = 0, |  | ||||||
|         .sched = sched, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn sample(self: *const Self) i8 { |  | ||||||
|     return if ((~self.shift & 1) == 1) 1 else -1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Reload LFSR Timer |  | ||||||
| pub fn reload(self: *Self, poly: io.PolyCounter) void { |  | ||||||
|     self.sched.removeScheduledEvent(.{ .ApuChannel = 3 }); |  | ||||||
|  |  | ||||||
|     const div = Self.divisor(poly.div_ratio.read()); |  | ||||||
|     const timer = div << poly.shift.read(); |  | ||||||
|     self.sched.push(.{ .ApuChannel = 3 }, @as(u64, timer) * interval); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Scheduler Event Handler for LFSR Timer Expire |  | ||||||
| /// FIXME: This gets called a lot, clogging up the Scheduler |  | ||||||
| pub fn onLfsrTimerExpire(self: *Self, poly: io.PolyCounter, late: u64) void { |  | ||||||
|     // Obscure: "Using a noise channel clock shift of 14 or 15 |  | ||||||
|     // results in the LFSR receiving no clocks." |  | ||||||
|     if (poly.shift.read() >= 14) return; |  | ||||||
|  |  | ||||||
|     const div = Self.divisor(poly.div_ratio.read()); |  | ||||||
|     const timer = div << poly.shift.read(); |  | ||||||
|  |  | ||||||
|     const tmp = (self.shift & 1) ^ ((self.shift & 2) >> 1); |  | ||||||
|     self.shift = (self.shift >> 1) | (tmp << 14); |  | ||||||
|  |  | ||||||
|     if (poly.width.read()) |  | ||||||
|         self.shift = (self.shift & ~@as(u15, 0x40)) | tmp << 6; |  | ||||||
|  |  | ||||||
|     self.sched.push(.{ .ApuChannel = 3 }, @as(u64, timer) * interval -| late); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn divisor(code: u3) u16 { |  | ||||||
|     if (code == 0) return 8; |  | ||||||
|     return @as(u16, code) << 4; |  | ||||||
| } |  | ||||||
| @@ -1,58 +0,0 @@ | |||||||
| const std = @import("std"); |  | ||||||
| const io = @import("../../bus/io.zig"); |  | ||||||
|  |  | ||||||
| const Scheduler = @import("../../scheduler.zig").Scheduler; |  | ||||||
| const FrameSequencer = @import("../../apu.zig").FrameSequencer; |  | ||||||
| const ToneSweep = @import("../ToneSweep.zig"); |  | ||||||
| const Tone = @import("../Tone.zig"); |  | ||||||
|  |  | ||||||
| const Self = @This(); |  | ||||||
| pub const interval: u64 = (1 << 24) / (1 << 22); |  | ||||||
|  |  | ||||||
| pos: u3, |  | ||||||
| sched: *Scheduler, |  | ||||||
| timer: u16, |  | ||||||
|  |  | ||||||
| pub fn init(sched: *Scheduler) Self { |  | ||||||
|     return .{ |  | ||||||
|         .timer = 0, |  | ||||||
|         .pos = 0, |  | ||||||
|         .sched = sched, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Scheduler Event Handler for Square Synth Timer Expire |  | ||||||
| pub fn onSquareTimerExpire(self: *Self, comptime T: type, nrx34: io.Frequency, late: u64) void { |  | ||||||
|     comptime std.debug.assert(T == ToneSweep or T == Tone); |  | ||||||
|     self.pos +%= 1; |  | ||||||
|  |  | ||||||
|     self.timer = (@as(u16, 2048) - nrx34.frequency.read()) * 4; |  | ||||||
|     self.sched.push(.{ .ApuChannel = if (T == ToneSweep) 0 else 1 }, @as(u64, self.timer) * interval -| late); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Reload Square Wave Timer |  | ||||||
| pub fn reload(self: *Self, comptime T: type, value: u11) void { |  | ||||||
|     comptime std.debug.assert(T == ToneSweep or T == Tone); |  | ||||||
|     const channel = if (T == ToneSweep) 0 else 1; |  | ||||||
|  |  | ||||||
|     self.sched.removeScheduledEvent(.{ .ApuChannel = channel }); |  | ||||||
|  |  | ||||||
|     const tmp = (@as(u16, 2048) - value) * 4; // What Freq Timer should be assuming no weird behaviour |  | ||||||
|     self.timer = (tmp & ~@as(u16, 0x3)) | self.timer & 0x3; // Keep the last two bits from the old timer; |  | ||||||
|  |  | ||||||
|     self.sched.push(.{ .ApuChannel = channel }, @as(u64, self.timer) * interval); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn sample(self: *const Self, nrx1: io.Duty) i8 { |  | ||||||
|     const pattern = nrx1.pattern.read(); |  | ||||||
|  |  | ||||||
|     const i = self.pos ^ 7; // index of 0 should get highest bit |  | ||||||
|     const result = switch (pattern) { |  | ||||||
|         0b00 => @as(u8, 0b00000001) >> i, // 12.5% |  | ||||||
|         0b01 => @as(u8, 0b00000011) >> i, // 25% |  | ||||||
|         0b10 => @as(u8, 0b00001111) >> i, // 50% |  | ||||||
|         0b11 => @as(u8, 0b11111100) >> i, // 75% |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     return if (result & 1 == 1) 1 else -1; |  | ||||||
| } |  | ||||||
| @@ -1,79 +0,0 @@ | |||||||
| const std = @import("std"); |  | ||||||
| const io = @import("../../bus/io.zig"); |  | ||||||
|  |  | ||||||
| const Scheduler = @import("../../scheduler.zig").Scheduler; |  | ||||||
| const FrameSequencer = @import("../../apu.zig").FrameSequencer; |  | ||||||
| const Wave = @import("../Wave.zig"); |  | ||||||
|  |  | ||||||
| const buf_len = 0x20; |  | ||||||
| pub const interval: u64 = (1 << 24) / (1 << 22); |  | ||||||
| const Self = @This(); |  | ||||||
|  |  | ||||||
| buf: [buf_len]u8, |  | ||||||
| timer: u16, |  | ||||||
| offset: u12, |  | ||||||
|  |  | ||||||
| sched: *Scheduler, |  | ||||||
|  |  | ||||||
| pub fn read(self: *const Self, comptime T: type, nr30: io.WaveSelect, addr: u32) T { |  | ||||||
|     // TODO: Handle reads when Channel 3 is disabled |  | ||||||
|     const base = if (!nr30.bank.read()) @as(u32, 0x10) else 0; // Read from the Opposite Bank in Use |  | ||||||
|  |  | ||||||
|     const i = base + addr - 0x0400_0090; |  | ||||||
|     return std.mem.readIntSliceLittle(T, self.buf[i..][0..@sizeOf(T)]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn write(self: *Self, comptime T: type, nr30: io.WaveSelect, addr: u32, value: T) void { |  | ||||||
|     // TODO: Handle writes when Channel 3 is disabled |  | ||||||
|     const base = if (!nr30.bank.read()) @as(u32, 0x10) else 0; // Write to the Opposite Bank in Use |  | ||||||
|  |  | ||||||
|     const i = base + addr - 0x0400_0090; |  | ||||||
|     std.mem.writeIntSliceLittle(T, self.buf[i..][0..@sizeOf(T)], value); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn init(sched: *Scheduler) Self { |  | ||||||
|     return .{ |  | ||||||
|         .buf = [_]u8{0x00} ** buf_len, |  | ||||||
|         .timer = 0, |  | ||||||
|         .offset = 0, |  | ||||||
|         .sched = sched, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Reload internal Wave Timer |  | ||||||
| pub fn reload(self: *Self, value: u11) void { |  | ||||||
|     self.sched.removeScheduledEvent(.{ .ApuChannel = 2 }); |  | ||||||
|  |  | ||||||
|     self.timer = (@as(u16, 2048) - value) * 2; |  | ||||||
|     self.sched.push(.{ .ApuChannel = 2 }, @as(u64, self.timer) * interval); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Scheduler Event Handler |  | ||||||
| pub fn onWaveTimerExpire(self: *Self, nrx34: io.Frequency, nr30: io.WaveSelect, late: u64) void { |  | ||||||
|     if (nr30.dimension.read()) { |  | ||||||
|         self.offset = (self.offset + 1) % 0x40; // 0x20 bytes (both banks), which contain 2 samples each |  | ||||||
|     } else { |  | ||||||
|         self.offset = (self.offset + 1) % 0x20; // 0x10 bytes, which contain 2 samples each |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     self.timer = (@as(u16, 2048) - nrx34.frequency.read()) * 2; |  | ||||||
|     self.sched.push(.{ .ApuChannel = 2 }, @as(u64, self.timer) * interval -| late); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Generate Sample from Wave Synth |  | ||||||
| pub fn sample(self: *const Self, nr30: io.WaveSelect) u4 { |  | ||||||
|     const base = if (nr30.bank.read()) @as(u32, 0x10) else 0; |  | ||||||
|  |  | ||||||
|     const value = self.buf[base + self.offset / 2]; |  | ||||||
|     return if (self.offset & 1 == 0) @truncate(u4, value >> 4) else @truncate(u4, value); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// TODO: Write comment |  | ||||||
| pub fn shift(_: *const Self, nr32: io.WaveVolume) u2 { |  | ||||||
|     return switch (nr32.kind.read()) { |  | ||||||
|         0b00 => 3, // Mute / Zero |  | ||||||
|         0b01 => 0, // 100% Volume |  | ||||||
|         0b10 => 1, // 50% Volume |  | ||||||
|         0b11 => 2, // 25% Volume |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -12,36 +12,6 @@ allocator: Allocator, | |||||||
|  |  | ||||||
| addr_latch: u32, | addr_latch: u32, | ||||||
|  |  | ||||||
| pub fn read(self: *Self, comptime T: type, r15: u32, addr: u32) T { |  | ||||||
|     if (r15 < Self.size) { |  | ||||||
|         self.addr_latch = addr; |  | ||||||
|         return self._read(T, addr); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     log.debug("Rejected read since r15=0x{X:0>8}", .{r15}); |  | ||||||
|     return @truncate(T, self._read(T, self.addr_latch + 8)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn dbgRead(self: *const Self, comptime T: type, r15: u32, addr: u32) T { |  | ||||||
|     if (r15 < Self.size) return self._read(T, addr); |  | ||||||
|     return @truncate(T, self._read(T, self.addr_latch + 8)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Read without the GBA safety checks |  | ||||||
| fn _read(self: *const Self, comptime T: type, addr: u32) T { |  | ||||||
|     const buf = self.buf orelse std.debug.panic("[BIOS] ZBA tried to read {} from 0x{X:0>8} but not BIOS was present", .{ T, addr }); |  | ||||||
|  |  | ||||||
|     return switch (T) { |  | ||||||
|         u32, u16, u8 => std.mem.readIntSliceLittle(T, buf[addr..][0..@sizeOf(T)]), |  | ||||||
|         else => @compileError("BIOS: Unsupported read width"), |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn write(_: *Self, comptime T: type, addr: u32, value: T) void { |  | ||||||
|     @setCold(true); |  | ||||||
|     log.debug("Tried to write {} 0x{X:} to 0x{X:0>8} ", .{ T, value, addr }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn init(allocator: Allocator, maybe_path: ?[]const u8) !Self { | pub fn init(allocator: Allocator, maybe_path: ?[]const u8) !Self { | ||||||
|     const buf: ?[]u8 = if (maybe_path) |path| blk: { |     const buf: ?[]u8 = if (maybe_path) |path| blk: { | ||||||
|         const file = try std.fs.cwd().openFile(path, .{}); |         const file = try std.fs.cwd().openFile(path, .{}); | ||||||
| @@ -61,3 +31,34 @@ pub fn deinit(self: *Self) void { | |||||||
|     if (self.buf) |buf| self.allocator.free(buf); |     if (self.buf) |buf| self.allocator.free(buf); | ||||||
|     self.* = undefined; |     self.* = undefined; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn read(self: *Self, comptime T: type, r15: u32, addr: u32) T { | ||||||
|  |     if (r15 < Self.size) { | ||||||
|  |         self.addr_latch = addr; | ||||||
|  |         return self.uncheckedRead(T, addr); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     log.debug("Rejected read since r15=0x{X:0>8}", .{r15}); | ||||||
|  |     return @truncate(T, self.uncheckedRead(T, self.addr_latch + 8)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn dbgRead(self: *const Self, comptime T: type, r15: u32, addr: u32) T { | ||||||
|  |     if (r15 < Self.size) return self.uncheckedRead(T, addr); | ||||||
|  |     return @truncate(T, self.uncheckedRead(T, self.addr_latch + 8)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn uncheckedRead(self: *const Self, comptime T: type, addr: u32) T { | ||||||
|  |     if (self.buf) |buf| { | ||||||
|  |         return switch (T) { | ||||||
|  |             u32, u16, u8 => std.mem.readIntSliceLittle(T, buf[addr..][0..@sizeOf(T)]), | ||||||
|  |             else => @compileError("BIOS: Unsupported read width"), | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     std.debug.panic("[BIOS] ZBA tried to read {} from 0x{X:0>8} but not BIOS was present", .{ T, addr }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn write(_: *Self, comptime T: type, addr: u32, value: T) void { | ||||||
|  |     @setCold(true); | ||||||
|  |     log.debug("Tried to write {} 0x{X:} to 0x{X:0>8} ", .{ T, value, addr }); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -7,6 +7,21 @@ const Self = @This(); | |||||||
| buf: []u8, | buf: []u8, | ||||||
| allocator: Allocator, | allocator: Allocator, | ||||||
|  |  | ||||||
|  | pub fn init(allocator: Allocator) !Self { | ||||||
|  |     const buf = try allocator.alloc(u8, ewram_size); | ||||||
|  |     std.mem.set(u8, buf, 0); | ||||||
|  |  | ||||||
|  |     return Self{ | ||||||
|  |         .buf = buf, | ||||||
|  |         .allocator = allocator, | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn deinit(self: *Self) void { | ||||||
|  |     self.allocator.free(self.buf); | ||||||
|  |     self.* = undefined; | ||||||
|  | } | ||||||
|  |  | ||||||
| pub fn read(self: *const Self, comptime T: type, address: usize) T { | pub fn read(self: *const Self, comptime T: type, address: usize) T { | ||||||
|     const addr = address & 0x3FFFF; |     const addr = address & 0x3FFFF; | ||||||
|  |  | ||||||
| @@ -24,18 +39,3 @@ pub fn write(self: *const Self, comptime T: type, address: usize, value: T) void | |||||||
|         else => @compileError("EWRAM: Unsupported write width"), |         else => @compileError("EWRAM: Unsupported write width"), | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn init(allocator: Allocator) !Self { |  | ||||||
|     const buf = try allocator.alloc(u8, ewram_size); |  | ||||||
|     std.mem.set(u8, buf, 0); |  | ||||||
|  |  | ||||||
|     return Self{ |  | ||||||
|         .buf = buf, |  | ||||||
|         .allocator = allocator, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn deinit(self: *Self) void { |  | ||||||
|     self.allocator.free(self.buf); |  | ||||||
|     self.* = undefined; |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,6 +1,4 @@ | |||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| const config = @import("../../config.zig"); |  | ||||||
|  |  | ||||||
| const Bit = @import("bitfield").Bit; | const Bit = @import("bitfield").Bit; | ||||||
| const Bitfield = @import("bitfield").Bitfield; | const Bitfield = @import("bitfield").Bitfield; | ||||||
| const DateTime = @import("datetime").datetime.Datetime; | const DateTime = @import("datetime").datetime.Datetime; | ||||||
| @@ -10,6 +8,7 @@ const Backup = @import("backup.zig").Backup; | |||||||
| const Gpio = @import("gpio.zig").Gpio; | const Gpio = @import("gpio.zig").Gpio; | ||||||
| const Allocator = std.mem.Allocator; | const Allocator = std.mem.Allocator; | ||||||
|  |  | ||||||
|  | const force_rtc = @import("../emu.zig").force_rtc; | ||||||
| const log = std.log.scoped(.GamePak); | const log = std.log.scoped(.GamePak); | ||||||
|  |  | ||||||
| const Self = @This(); | const Self = @This(); | ||||||
| @@ -20,11 +19,78 @@ allocator: Allocator, | |||||||
| backup: Backup, | backup: Backup, | ||||||
| gpio: *Gpio, | gpio: *Gpio, | ||||||
|  |  | ||||||
|  | pub fn init(allocator: Allocator, cpu: *Arm7tdmi, rom_path: []const u8, save_path: ?[]const u8) !Self { | ||||||
|  |     const file = try std.fs.cwd().openFile(rom_path, .{}); | ||||||
|  |     defer file.close(); | ||||||
|  |  | ||||||
|  |     const file_buf = try file.readToEndAlloc(allocator, try file.getEndPos()); | ||||||
|  |     const title = file_buf[0xA0..0xAC].*; | ||||||
|  |     const kind = Backup.guessKind(file_buf); | ||||||
|  |     const device = if (force_rtc) .Rtc else guessDevice(file_buf); | ||||||
|  |  | ||||||
|  |     logHeader(file_buf, &title); | ||||||
|  |  | ||||||
|  |     return .{ | ||||||
|  |         .buf = file_buf, | ||||||
|  |         .allocator = allocator, | ||||||
|  |         .title = title, | ||||||
|  |         .backup = try Backup.init(allocator, kind, title, save_path), | ||||||
|  |         .gpio = try Gpio.init(allocator, cpu, device), | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Searches the ROM to see if it can determine whether the ROM it's searching uses | ||||||
|  | /// any GPIO device, like a RTC for example. | ||||||
|  | fn guessDevice(buf: []const u8) Gpio.Device.Kind { | ||||||
|  |     // Try to Guess if ROM uses RTC | ||||||
|  |     const needle = "RTC_V"; // I was told SIIRTC_V, though Pokemen Firered (USA) is a false negative | ||||||
|  |  | ||||||
|  |     var i: usize = 0; | ||||||
|  |     while ((i + needle.len) < buf.len) : (i += 1) { | ||||||
|  |         if (std.mem.eql(u8, needle, buf[i..(i + needle.len)])) return .Rtc; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // TODO: Detect other GPIO devices | ||||||
|  |  | ||||||
|  |     return .None; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn logHeader(buf: []const u8, title: *const [12]u8) void { | ||||||
|  |     const code = buf[0xAC..0xB0]; | ||||||
|  |     const maker = buf[0xB0..0xB2]; | ||||||
|  |     const version = buf[0xBC]; | ||||||
|  |  | ||||||
|  |     log.info("Title: {s}", .{title}); | ||||||
|  |     if (version != 0) log.info("Version: {}", .{version}); | ||||||
|  |     log.info("Game Code: {s}", .{code}); | ||||||
|  |     if (lookupMaker(maker)) |c| log.info("Maker: {s}", .{c}) else log.info("Maker Code: {s}", .{maker}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn lookupMaker(slice: *const [2]u8) ?[]const u8 { | ||||||
|  |     const id = @as(u16, slice[1]) << 8 | @as(u16, slice[0]); | ||||||
|  |     return switch (id) { | ||||||
|  |         0x3130 => "Nintendo", | ||||||
|  |         else => null, | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fn isLarge(self: *const Self) bool { | ||||||
|  |     return self.buf.len > 0x100_0000; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn deinit(self: *Self) void { | ||||||
|  |     self.backup.deinit(); | ||||||
|  |     self.gpio.deinit(self.allocator); | ||||||
|  |     self.allocator.destroy(self.gpio); | ||||||
|  |     self.allocator.free(self.buf); | ||||||
|  |     self.* = undefined; | ||||||
|  | } | ||||||
|  |  | ||||||
| pub fn read(self: *Self, comptime T: type, address: u32) T { | pub fn read(self: *Self, comptime T: type, address: u32) T { | ||||||
|     const addr = address & 0x1FF_FFFF; |     const addr = address & 0x1FF_FFFF; | ||||||
|  |  | ||||||
|     if (self.backup.kind == .Eeprom) { |     if (self.backup.kind == .Eeprom) { | ||||||
|         if (self.buf.len > 0x100_0000) { // Large |         if (self.isLarge()) { | ||||||
|             // Addresses 0x1FF_FF00 to 0x1FF_FFFF are reserved from EEPROM accesses if |             // Addresses 0x1FF_FF00 to 0x1FF_FFFF are reserved from EEPROM accesses if | ||||||
|             // * Backup type is EEPROM |             // * Backup type is EEPROM | ||||||
|             // * Large ROM (Size is greater than 16MB) |             // * Large ROM (Size is greater than 16MB) | ||||||
| @@ -76,19 +142,11 @@ pub fn read(self: *Self, comptime T: type, address: u32) T { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| inline fn get(self: *const Self, i: u32) u8 { |  | ||||||
|     @setRuntimeSafety(false); |  | ||||||
|     if (i < self.buf.len) return self.buf[i]; |  | ||||||
|  |  | ||||||
|     const lhs = i >> 1 & 0xFFFF; |  | ||||||
|     return @truncate(u8, lhs >> 8 * @truncate(u5, i & 1)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn dbgRead(self: *const Self, comptime T: type, address: u32) T { | pub fn dbgRead(self: *const Self, comptime T: type, address: u32) T { | ||||||
|     const addr = address & 0x1FF_FFFF; |     const addr = address & 0x1FF_FFFF; | ||||||
|  |  | ||||||
|     if (self.backup.kind == .Eeprom) { |     if (self.backup.kind == .Eeprom) { | ||||||
|         if (self.buf.len > 0x100_0000) { // Large |         if (self.isLarge()) { | ||||||
|             // Addresses 0x1FF_FF00 to 0x1FF_FFFF are reserved from EEPROM accesses if |             // Addresses 0x1FF_FF00 to 0x1FF_FFFF are reserved from EEPROM accesses if | ||||||
|             // * Backup type is EEPROM |             // * Backup type is EEPROM | ||||||
|             // * Large ROM (Size is greater than 16MB) |             // * Large ROM (Size is greater than 16MB) | ||||||
| @@ -103,35 +161,6 @@ pub fn dbgRead(self: *const Self, comptime T: type, address: u32) T { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (self.gpio.cnt == 1) { |  | ||||||
|         // GPIO Can be read from |  | ||||||
|         // We assume that this will only be true when a ROM actually does want something from GPIO |  | ||||||
|  |  | ||||||
|         switch (T) { |  | ||||||
|             u32 => switch (address) { |  | ||||||
|                 // TODO: Do I even need to implement these? |  | ||||||
|                 0x0800_00C4 => std.debug.panic("Handle 32-bit GPIO Data/Direction Reads", .{}), |  | ||||||
|                 0x0800_00C6 => std.debug.panic("Handle 32-bit GPIO Direction/Control Reads", .{}), |  | ||||||
|                 0x0800_00C8 => std.debug.panic("Handle 32-bit GPIO Control Reads", .{}), |  | ||||||
|                 else => {}, |  | ||||||
|             }, |  | ||||||
|             u16 => switch (address) { |  | ||||||
|                 // FIXME: What do 16-bit GPIO Reads look like? |  | ||||||
|                 0x0800_00C4 => return self.gpio.read(.Data), |  | ||||||
|                 0x0800_00C6 => return self.gpio.read(.Direction), |  | ||||||
|                 0x0800_00C8 => return self.gpio.read(.Control), |  | ||||||
|                 else => {}, |  | ||||||
|             }, |  | ||||||
|             u8 => switch (address) { |  | ||||||
|                 0x0800_00C4 => return self.gpio.read(.Data), |  | ||||||
|                 0x0800_00C6 => return self.gpio.read(.Direction), |  | ||||||
|                 0x0800_00C8 => return self.gpio.read(.Control), |  | ||||||
|                 else => {}, |  | ||||||
|             }, |  | ||||||
|             else => @compileError("GamePak[GPIO]: Unsupported read width"), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return switch (T) { |     return switch (T) { | ||||||
|         u32 => (@as(T, self.get(addr + 3)) << 24) | (@as(T, self.get(addr + 2)) << 16) | (@as(T, self.get(addr + 1)) << 8) | (@as(T, self.get(addr))), |         u32 => (@as(T, self.get(addr + 3)) << 24) | (@as(T, self.get(addr + 2)) << 16) | (@as(T, self.get(addr + 1)) << 8) | (@as(T, self.get(addr))), | ||||||
|         u16 => (@as(T, self.get(addr + 1)) << 8) | @as(T, self.get(addr)), |         u16 => (@as(T, self.get(addr + 1)) << 8) | @as(T, self.get(addr)), | ||||||
| @@ -146,7 +175,7 @@ pub fn write(self: *Self, comptime T: type, word_count: u16, address: u32, value | |||||||
|     if (self.backup.kind == .Eeprom) { |     if (self.backup.kind == .Eeprom) { | ||||||
|         const bit = @truncate(u1, value); |         const bit = @truncate(u1, value); | ||||||
|  |  | ||||||
|         if (self.buf.len > 0x100_0000) { // Large |         if (self.isLarge()) { | ||||||
|             // Addresses 0x1FF_FF00 to 0x1FF_FFFF are reserved from EEPROM accesses if |             // Addresses 0x1FF_FF00 to 0x1FF_FFFF are reserved from EEPROM accesses if | ||||||
|             // * Backup type is EEPROM |             // * Backup type is EEPROM | ||||||
|             // * Large ROM (Size is greater than 16MB) |             // * Large ROM (Size is greater than 16MB) | ||||||
| @@ -184,59 +213,12 @@ pub fn write(self: *Self, comptime T: type, word_count: u16, address: u32, value | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn init(allocator: Allocator, cpu: *Arm7tdmi, rom_path: []const u8, save_path: ?[]const u8) !Self { | fn get(self: *const Self, i: u32) u8 { | ||||||
|     const file = try std.fs.cwd().openFile(rom_path, .{}); |     @setRuntimeSafety(false); | ||||||
|     defer file.close(); |     if (i < self.buf.len) return self.buf[i]; | ||||||
|  |  | ||||||
|     const file_buf = try file.readToEndAlloc(allocator, try file.getEndPos()); |     const lhs = i >> 1 & 0xFFFF; | ||||||
|     const title = file_buf[0xA0..0xAC].*; |     return @truncate(u8, lhs >> 8 * @truncate(u5, i & 1)); | ||||||
|     const kind = Backup.guess(file_buf); |  | ||||||
|     const device = if (config.config().guest.force_rtc) .Rtc else guessDevice(file_buf); |  | ||||||
|  |  | ||||||
|     logHeader(file_buf, &title); |  | ||||||
|  |  | ||||||
|     return .{ |  | ||||||
|         .buf = file_buf, |  | ||||||
|         .allocator = allocator, |  | ||||||
|         .title = title, |  | ||||||
|         .backup = try Backup.init(allocator, kind, title, save_path), |  | ||||||
|         .gpio = try Gpio.init(allocator, cpu, device), |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn deinit(self: *Self) void { |  | ||||||
|     self.backup.deinit(); |  | ||||||
|     self.gpio.deinit(self.allocator); |  | ||||||
|     self.allocator.destroy(self.gpio); |  | ||||||
|     self.allocator.free(self.buf); |  | ||||||
|     self.* = undefined; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Searches the ROM to see if it can determine whether the ROM it's searching uses |  | ||||||
| /// any GPIO device, like a RTC for example. |  | ||||||
| fn guessDevice(buf: []const u8) Gpio.Device.Kind { |  | ||||||
|     // Try to Guess if ROM uses RTC |  | ||||||
|     const needle = "RTC_V"; // I was told SIIRTC_V, though Pokemen Firered (USA) is a false negative |  | ||||||
|  |  | ||||||
|     var i: usize = 0; |  | ||||||
|     while ((i + needle.len) < buf.len) : (i += 1) { |  | ||||||
|         if (std.mem.eql(u8, needle, buf[i..(i + needle.len)])) return .Rtc; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // TODO: Detect other GPIO devices |  | ||||||
|  |  | ||||||
|     return .None; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn logHeader(buf: []const u8, title: *const [12]u8) void { |  | ||||||
|     const code = buf[0xAC..0xB0]; |  | ||||||
|     const maker = buf[0xB0..0xB2]; |  | ||||||
|     const version = buf[0xBC]; |  | ||||||
|  |  | ||||||
|     log.info("Title: {s}", .{title}); |  | ||||||
|     if (version != 0) log.info("Version: {}", .{version}); |  | ||||||
|     log.info("Game Code: {s}", .{code}); |  | ||||||
|     log.info("Maker Code: {s}", .{maker}); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| test "OOB Access" { | test "OOB Access" { | ||||||
|   | |||||||
| @@ -7,6 +7,21 @@ const Self = @This(); | |||||||
| buf: []u8, | buf: []u8, | ||||||
| allocator: Allocator, | allocator: Allocator, | ||||||
|  |  | ||||||
|  | pub fn init(allocator: Allocator) !Self { | ||||||
|  |     const buf = try allocator.alloc(u8, iwram_size); | ||||||
|  |     std.mem.set(u8, buf, 0); | ||||||
|  |  | ||||||
|  |     return Self{ | ||||||
|  |         .buf = buf, | ||||||
|  |         .allocator = allocator, | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn deinit(self: *Self) void { | ||||||
|  |     self.allocator.free(self.buf); | ||||||
|  |     self.* = undefined; | ||||||
|  | } | ||||||
|  |  | ||||||
| pub fn read(self: *const Self, comptime T: type, address: usize) T { | pub fn read(self: *const Self, comptime T: type, address: usize) T { | ||||||
|     const addr = address & 0x7FFF; |     const addr = address & 0x7FFF; | ||||||
|  |  | ||||||
| @@ -24,18 +39,3 @@ pub fn write(self: *const Self, comptime T: type, address: usize, value: T) void | |||||||
|         else => @compileError("IWRAM: Unsupported write width"), |         else => @compileError("IWRAM: Unsupported write width"), | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn init(allocator: Allocator) !Self { |  | ||||||
|     const buf = try allocator.alloc(u8, iwram_size); |  | ||||||
|     std.mem.set(u8, buf, 0); |  | ||||||
|  |  | ||||||
|     return Self{ |  | ||||||
|         .buf = buf, |  | ||||||
|         .allocator = allocator, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn deinit(self: *Self) void { |  | ||||||
|     self.allocator.free(self.buf); |  | ||||||
|     self.* = undefined; |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -2,13 +2,9 @@ const std = @import("std"); | |||||||
| const Allocator = std.mem.Allocator; | const Allocator = std.mem.Allocator; | ||||||
| const log = std.log.scoped(.Backup); | const log = std.log.scoped(.Backup); | ||||||
|  |  | ||||||
| const Eeprom = @import("backup/eeprom.zig").Eeprom; |  | ||||||
| const Flash = @import("backup/Flash.zig"); |  | ||||||
|  |  | ||||||
| const escape = @import("../../util.zig").escape; | const escape = @import("../../util.zig").escape; | ||||||
| const span = @import("../../util.zig").span; | const span = @import("../../util.zig").span; | ||||||
|  |  | ||||||
| const Needle = struct { str: []const u8, kind: Backup.Kind }; |  | ||||||
| const backup_kinds = [6]Needle{ | const backup_kinds = [6]Needle{ | ||||||
|     .{ .str = "EEPROM_V", .kind = .Eeprom }, |     .{ .str = "EEPROM_V", .kind = .Eeprom }, | ||||||
|     .{ .str = "SRAM_V", .kind = .Sram }, |     .{ .str = "SRAM_V", .kind = .Sram }, | ||||||
| @@ -18,8 +14,6 @@ const backup_kinds = [6]Needle{ | |||||||
|     .{ .str = "FLASH1M_V", .kind = .Flash1M }, |     .{ .str = "FLASH1M_V", .kind = .Flash1M }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const SaveError = error{Unsupported}; |  | ||||||
|  |  | ||||||
| pub const Backup = struct { | pub const Backup = struct { | ||||||
|     const Self = @This(); |     const Self = @This(); | ||||||
|  |  | ||||||
| @@ -41,65 +35,6 @@ pub const Backup = struct { | |||||||
|         None, |         None, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     pub fn read(self: *const Self, address: usize) u8 { |  | ||||||
|         const addr = address & 0xFFFF; |  | ||||||
|  |  | ||||||
|         switch (self.kind) { |  | ||||||
|             .Flash => { |  | ||||||
|                 switch (addr) { |  | ||||||
|                     0x0000 => if (self.flash.id_mode) return 0x32, // Panasonic manufacturer ID |  | ||||||
|                     0x0001 => if (self.flash.id_mode) return 0x1B, // Panasonic device ID |  | ||||||
|                     else => {}, |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 return self.flash.read(self.buf, addr); |  | ||||||
|             }, |  | ||||||
|             .Flash1M => { |  | ||||||
|                 switch (addr) { |  | ||||||
|                     0x0000 => if (self.flash.id_mode) return 0x62, // Sanyo manufacturer ID |  | ||||||
|                     0x0001 => if (self.flash.id_mode) return 0x13, // Sanyo device ID |  | ||||||
|                     else => {}, |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 return self.flash.read(self.buf, addr); |  | ||||||
|             }, |  | ||||||
|             .Sram => return self.buf[addr & 0x7FFF], // 32K SRAM chip is mirrored |  | ||||||
|             .None, .Eeprom => return 0xFF, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn write(self: *Self, address: usize, byte: u8) void { |  | ||||||
|         const addr = address & 0xFFFF; |  | ||||||
|  |  | ||||||
|         switch (self.kind) { |  | ||||||
|             .Flash, .Flash1M => { |  | ||||||
|                 if (self.flash.prep_write) return self.flash.write(self.buf, addr, byte); |  | ||||||
|                 if (self.flash.shouldEraseSector(addr, byte)) return self.flash.erase(self.buf, addr); |  | ||||||
|  |  | ||||||
|                 switch (addr) { |  | ||||||
|                     0x0000 => if (self.kind == .Flash1M and self.flash.set_bank) { |  | ||||||
|                         self.flash.bank = @truncate(u1, byte); |  | ||||||
|                     }, |  | ||||||
|                     0x5555 => { |  | ||||||
|                         if (self.flash.state == .Command) { |  | ||||||
|                             self.flash.handleCommand(self.buf, byte); |  | ||||||
|                         } else if (byte == 0xAA and self.flash.state == .Ready) { |  | ||||||
|                             self.flash.state = .Set; |  | ||||||
|                         } else if (byte == 0xF0) { |  | ||||||
|                             self.flash.state = .Ready; |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                     0x2AAA => if (byte == 0x55 and self.flash.state == .Set) { |  | ||||||
|                         self.flash.state = .Command; |  | ||||||
|                     }, |  | ||||||
|                     else => {}, |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             .Sram => self.buf[addr & 0x7FFF] = byte, |  | ||||||
|             .None, .Eeprom => {}, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn init(allocator: Allocator, kind: Kind, title: [12]u8, path: ?[]const u8) !Self { |     pub fn init(allocator: Allocator, kind: Kind, title: [12]u8, path: ?[]const u8) !Self { | ||||||
|         log.info("Kind: {}", .{kind}); |         log.info("Kind: {}", .{kind}); | ||||||
|  |  | ||||||
| @@ -119,22 +54,15 @@ pub const Backup = struct { | |||||||
|             .kind = kind, |             .kind = kind, | ||||||
|             .title = title, |             .title = title, | ||||||
|             .save_path = path, |             .save_path = path, | ||||||
|             .flash = Flash.create(), |             .flash = Flash.init(), | ||||||
|             .eeprom = Eeprom.create(allocator), |             .eeprom = Eeprom.init(allocator), | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         if (backup.save_path) |p| backup.readSave(allocator, p) catch |e| log.err("Failed to load save: {}", .{e}); |         if (backup.save_path) |p| backup.loadSaveFromDisk(allocator, p) catch |e| log.err("Failed to load save: {}", .{e}); | ||||||
|         return backup; |         return backup; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn deinit(self: *Self) void { |     pub fn guessKind(rom: []const u8) Kind { | ||||||
|         if (self.save_path) |path| self.writeSave(self.allocator, path) catch |e| log.err("Failed to write save: {}", .{e}); |  | ||||||
|         self.allocator.free(self.buf); |  | ||||||
|         self.* = undefined; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Guesses the Backup Kind of a GBA ROM |  | ||||||
|     pub fn guess(rom: []const u8) Kind { |  | ||||||
|         for (backup_kinds) |needle| { |         for (backup_kinds) |needle| { | ||||||
|             const needle_len = needle.str.len; |             const needle_len = needle.str.len; | ||||||
|  |  | ||||||
| @@ -147,8 +75,14 @@ pub const Backup = struct { | |||||||
|         return .None; |         return .None; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn readSave(self: *Self, allocator: Allocator, path: []const u8) !void { |     pub fn deinit(self: *Self) void { | ||||||
|         const file_path = try self.savePath(allocator, path); |         if (self.save_path) |path| self.writeSaveToDisk(self.allocator, path) catch |e| log.err("Failed to write save: {}", .{e}); | ||||||
|  |         self.allocator.free(self.buf); | ||||||
|  |         self.* = undefined; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn loadSaveFromDisk(self: *Self, allocator: Allocator, path: []const u8) !void { | ||||||
|  |         const file_path = try self.getSaveFilePath(allocator, path); | ||||||
|         defer allocator.free(file_path); |         defer allocator.free(file_path); | ||||||
|  |  | ||||||
|         // FIXME: Don't rely on this lol |         // FIXME: Don't rely on this lol | ||||||
| @@ -183,26 +117,26 @@ pub const Backup = struct { | |||||||
|                     file_buf.len, |                     file_buf.len, | ||||||
|                 }); |                 }); | ||||||
|             }, |             }, | ||||||
|             .None => return SaveError.Unsupported, |             .None => return SaveError.UnsupportedBackupKind, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn savePath(self: *const Self, allocator: Allocator, path: []const u8) ![]const u8 { |     fn getSaveFilePath(self: *const Self, allocator: Allocator, path: []const u8) ![]const u8 { | ||||||
|         const filename = try self.saveName(allocator); |         const filename = try self.getSaveFilename(allocator); | ||||||
|         defer allocator.free(filename); |         defer allocator.free(filename); | ||||||
|  |  | ||||||
|         return try std.fs.path.join(allocator, &[_][]const u8{ path, filename }); |         return try std.fs.path.join(allocator, &[_][]const u8{ path, filename }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn saveName(self: *const Self, allocator: Allocator) ![]const u8 { |     fn getSaveFilename(self: *const Self, allocator: Allocator) ![]const u8 { | ||||||
|         const title_str = span(&escape(self.title)); |         const title_str = span(&escape(self.title)); | ||||||
|         const name = if (title_str.len != 0) title_str else "untitled"; |         const name = if (title_str.len != 0) title_str else "untitled"; | ||||||
|  |  | ||||||
|         return try std.mem.concat(allocator, u8, &[_][]const u8{ name, ".sav" }); |         return try std.mem.concat(allocator, u8, &[_][]const u8{ name, ".sav" }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn writeSave(self: Self, allocator: Allocator, path: []const u8) !void { |     fn writeSaveToDisk(self: Self, allocator: Allocator, path: []const u8) !void { | ||||||
|         const file_path = try self.savePath(allocator, path); |         const file_path = try self.getSaveFilePath(allocator, path); | ||||||
|         defer allocator.free(file_path); |         defer allocator.free(file_path); | ||||||
|  |  | ||||||
|         switch (self.kind) { |         switch (self.kind) { | ||||||
| @@ -213,7 +147,420 @@ pub const Backup = struct { | |||||||
|                 try file.writeAll(self.buf); |                 try file.writeAll(self.buf); | ||||||
|                 log.info("Wrote Save to {s}", .{file_path}); |                 log.info("Wrote Save to {s}", .{file_path}); | ||||||
|             }, |             }, | ||||||
|             else => return SaveError.Unsupported, |             else => return SaveError.UnsupportedBackupKind, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn read(self: *const Self, address: usize) u8 { | ||||||
|  |         const addr = address & 0xFFFF; | ||||||
|  |  | ||||||
|  |         switch (self.kind) { | ||||||
|  |             .Flash => { | ||||||
|  |                 switch (addr) { | ||||||
|  |                     0x0000 => if (self.flash.id_mode) return 0x32, // Panasonic manufacturer ID | ||||||
|  |                     0x0001 => if (self.flash.id_mode) return 0x1B, // Panasonic device ID | ||||||
|  |                     else => {}, | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return self.flash.read(self.buf, addr); | ||||||
|  |             }, | ||||||
|  |             .Flash1M => { | ||||||
|  |                 switch (addr) { | ||||||
|  |                     0x0000 => if (self.flash.id_mode) return 0x62, // Sanyo manufacturer ID | ||||||
|  |                     0x0001 => if (self.flash.id_mode) return 0x13, // Sanyo device ID | ||||||
|  |                     else => {}, | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return self.flash.read(self.buf, addr); | ||||||
|  |             }, | ||||||
|  |             .Sram => return self.buf[addr & 0x7FFF], // 32K SRAM chip is mirrored | ||||||
|  |             .None, .Eeprom => return 0xFF, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn write(self: *Self, address: usize, byte: u8) void { | ||||||
|  |         const addr = address & 0xFFFF; | ||||||
|  |  | ||||||
|  |         switch (self.kind) { | ||||||
|  |             .Flash, .Flash1M => { | ||||||
|  |                 if (self.flash.prep_write) return self.flash.write(self.buf, addr, byte); | ||||||
|  |                 if (self.flash.shouldEraseSector(addr, byte)) return self.flash.eraseSector(self.buf, addr); | ||||||
|  |  | ||||||
|  |                 switch (addr) { | ||||||
|  |                     0x0000 => if (self.kind == .Flash1M and self.flash.set_bank) { | ||||||
|  |                         self.flash.bank = @truncate(u1, byte); | ||||||
|  |                     }, | ||||||
|  |                     0x5555 => { | ||||||
|  |                         if (self.flash.state == .Command) { | ||||||
|  |                             self.flash.handleCommand(self.buf, byte); | ||||||
|  |                         } else if (byte == 0xAA and self.flash.state == .Ready) { | ||||||
|  |                             self.flash.state = .Set; | ||||||
|  |                         } else if (byte == 0xF0) { | ||||||
|  |                             self.flash.state = .Ready; | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     0x2AAA => if (byte == 0x55 and self.flash.state == .Set) { | ||||||
|  |                         self.flash.state = .Command; | ||||||
|  |                     }, | ||||||
|  |                     else => {}, | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             .Sram => self.buf[addr & 0x7FFF] = byte, | ||||||
|  |             .None, .Eeprom => {}, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | const Needle = struct { | ||||||
|  |     const Self = @This(); | ||||||
|  |  | ||||||
|  |     str: []const u8, | ||||||
|  |     kind: Backup.Kind, | ||||||
|  |  | ||||||
|  |     fn init(str: []const u8, kind: Backup.Kind) Self { | ||||||
|  |         return .{ | ||||||
|  |             .str = str, | ||||||
|  |             .kind = kind, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const SaveError = error{ | ||||||
|  |     UnsupportedBackupKind, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const Flash = struct { | ||||||
|  |     const Self = @This(); | ||||||
|  |  | ||||||
|  |     state: State, | ||||||
|  |  | ||||||
|  |     id_mode: bool, | ||||||
|  |     set_bank: bool, | ||||||
|  |     prep_erase: bool, | ||||||
|  |     prep_write: bool, | ||||||
|  |  | ||||||
|  |     bank: u1, | ||||||
|  |  | ||||||
|  |     const State = enum { | ||||||
|  |         Ready, | ||||||
|  |         Set, | ||||||
|  |         Command, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     fn init() Self { | ||||||
|  |         return .{ | ||||||
|  |             .state = .Ready, | ||||||
|  |             .id_mode = false, | ||||||
|  |             .set_bank = false, | ||||||
|  |             .prep_erase = false, | ||||||
|  |             .prep_write = false, | ||||||
|  |             .bank = 0, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn handleCommand(self: *Self, buf: []u8, byte: u8) void { | ||||||
|  |         switch (byte) { | ||||||
|  |             0x90 => self.id_mode = true, | ||||||
|  |             0xF0 => self.id_mode = false, | ||||||
|  |             0xB0 => self.set_bank = true, | ||||||
|  |             0x80 => self.prep_erase = true, | ||||||
|  |             0x10 => { | ||||||
|  |                 std.mem.set(u8, buf, 0xFF); | ||||||
|  |                 self.prep_erase = false; | ||||||
|  |             }, | ||||||
|  |             0xA0 => self.prep_write = true, | ||||||
|  |             else => std.debug.panic("Unhandled Flash Command: 0x{X:0>2}", .{byte}), | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.state = .Ready; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn shouldEraseSector(self: *const Self, addr: usize, byte: u8) bool { | ||||||
|  |         return self.state == .Command and self.prep_erase and byte == 0x30 and addr & 0xFFF == 0x000; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn write(self: *Self, buf: []u8, idx: usize, byte: u8) void { | ||||||
|  |         buf[self.baseAddress() + idx] = byte; | ||||||
|  |         self.prep_write = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn read(self: *const Self, buf: []u8, idx: usize) u8 { | ||||||
|  |         return buf[self.baseAddress() + idx]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn eraseSector(self: *Self, buf: []u8, idx: usize) void { | ||||||
|  |         const start = self.baseAddress() + (idx & 0xF000); | ||||||
|  |  | ||||||
|  |         std.mem.set(u8, buf[start..][0..0x1000], 0xFF); | ||||||
|  |         self.prep_erase = false; | ||||||
|  |         self.state = .Ready; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline fn baseAddress(self: *const Self) usize { | ||||||
|  |         return if (self.bank == 1) 0x10000 else @as(usize, 0); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const Eeprom = struct { | ||||||
|  |     const Self = @This(); | ||||||
|  |  | ||||||
|  |     addr: u14, | ||||||
|  |  | ||||||
|  |     kind: Kind, | ||||||
|  |     state: State, | ||||||
|  |     writer: Writer, | ||||||
|  |     reader: Reader, | ||||||
|  |  | ||||||
|  |     allocator: Allocator, | ||||||
|  |  | ||||||
|  |     const Kind = enum { | ||||||
|  |         Unknown, | ||||||
|  |         Small, // 512B | ||||||
|  |         Large, // 8KB | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const State = enum { | ||||||
|  |         Ready, | ||||||
|  |         Read, | ||||||
|  |         Write, | ||||||
|  |         WriteTransfer, | ||||||
|  |         RequestEnd, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     fn init(allocator: Allocator) Self { | ||||||
|  |         return .{ | ||||||
|  |             .kind = .Unknown, | ||||||
|  |             .state = .Ready, | ||||||
|  |             .writer = Writer.init(), | ||||||
|  |             .reader = Reader.init(), | ||||||
|  |             .addr = 0, | ||||||
|  |             .allocator = allocator, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn read(self: *Self) u1 { | ||||||
|  |         return self.reader.read(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn dbgRead(self: *const Self) u1 { | ||||||
|  |         return self.reader.dbgRead(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn write(self: *Self, word_count: u16, buf: *[]u8, bit: u1) void { | ||||||
|  |         if (self.guessKind(word_count)) |found| { | ||||||
|  |             log.info("EEPROM Kind: {}", .{found}); | ||||||
|  |             self.kind = found; | ||||||
|  |  | ||||||
|  |             // buf.len will not equal zero when a save file was found and loaded. | ||||||
|  |             // Right now, we assume that the save file is of the correct size which | ||||||
|  |             // isn't necessarily true, since we can't trust anything a user can influence | ||||||
|  |             // TODO: use ?[]u8 instead of a 0-sized slice? | ||||||
|  |             if (buf.len == 0) { | ||||||
|  |                 const len: usize = switch (found) { | ||||||
|  |                     .Small => 0x200, | ||||||
|  |                     .Large => 0x2000, | ||||||
|  |                     else => unreachable, | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 buf.* = self.allocator.alloc(u8, len) catch |e| { | ||||||
|  |                     log.err("Failed to resize EEPROM buf to {} bytes", .{len}); | ||||||
|  |                     std.debug.panic("EEPROM entered irrecoverable state {}", .{e}); | ||||||
|  |                 }; | ||||||
|  |                 std.mem.set(u8, buf.*, 0xFF); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (self.state == .RequestEnd) { | ||||||
|  |             if (bit != 0) log.debug("EEPROM Request did not end in 0u1. TODO: is this ok?", .{}); | ||||||
|  |             self.state = .Ready; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         switch (self.state) { | ||||||
|  |             .Ready => self.writer.requestWrite(bit), | ||||||
|  |             .Read, .Write => self.writer.addressWrite(self.kind, bit), | ||||||
|  |             .WriteTransfer => self.writer.dataWrite(bit), | ||||||
|  |             .RequestEnd => unreachable, // We return early just above this block | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.tick(buf.*); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn guessKind(self: *const Self, word_count: u16) ?Kind { | ||||||
|  |         if (self.kind != .Unknown or self.state != .Read) return null; | ||||||
|  |  | ||||||
|  |         return switch (word_count) { | ||||||
|  |             17 => .Large, | ||||||
|  |             9 => .Small, | ||||||
|  |             else => blk: { | ||||||
|  |                 log.err("Unexpected length of DMA3 Transfer upon initial EEPROM read: {}", .{word_count}); | ||||||
|  |                 break :blk null; | ||||||
|  |             }, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn tick(self: *Self, buf: []u8) void { | ||||||
|  |         switch (self.state) { | ||||||
|  |             .Ready => { | ||||||
|  |                 if (self.writer.len() == 2) { | ||||||
|  |                     const req = @intCast(u2, self.writer.finish()); | ||||||
|  |                     switch (req) { | ||||||
|  |                         0b11 => self.state = .Read, | ||||||
|  |                         0b10 => self.state = .Write, | ||||||
|  |                         else => log.err("Unknown EEPROM Request 0b{b:0>2}", .{req}), | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             .Read => { | ||||||
|  |                 switch (self.kind) { | ||||||
|  |                     .Large => { | ||||||
|  |                         if (self.writer.len() == 14) { | ||||||
|  |                             const addr = @intCast(u10, self.writer.finish()); | ||||||
|  |                             const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]); | ||||||
|  |  | ||||||
|  |                             self.reader.configure(value); | ||||||
|  |                             self.state = .RequestEnd; | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     .Small => { | ||||||
|  |                         if (self.writer.len() == 6) { | ||||||
|  |                             // FIXME: Duplicated code from above | ||||||
|  |                             const addr = @intCast(u6, self.writer.finish()); | ||||||
|  |                             const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]); | ||||||
|  |  | ||||||
|  |                             self.reader.configure(value); | ||||||
|  |                             self.state = .RequestEnd; | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     else => log.err("Unable to calculate EEPROM read address. EEPROM size UNKNOWN", .{}), | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             .Write => { | ||||||
|  |                 switch (self.kind) { | ||||||
|  |                     .Large => { | ||||||
|  |                         if (self.writer.len() == 14) { | ||||||
|  |                             self.addr = @intCast(u10, self.writer.finish()); | ||||||
|  |                             self.state = .WriteTransfer; | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     .Small => { | ||||||
|  |                         if (self.writer.len() == 6) { | ||||||
|  |                             self.addr = @intCast(u6, self.writer.finish()); | ||||||
|  |                             self.state = .WriteTransfer; | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     else => log.err("Unable to calculate EEPROM write address. EEPROM size UNKNOWN", .{}), | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             .WriteTransfer => { | ||||||
|  |                 if (self.writer.len() == 64) { | ||||||
|  |                     std.mem.writeIntSliceLittle(u64, buf[self.addr * 8 ..][0..8], self.writer.finish()); | ||||||
|  |                     self.state = .RequestEnd; | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             .RequestEnd => unreachable, // We return early in write() if state is .RequestEnd | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const Reader = struct { | ||||||
|  |         const This = @This(); | ||||||
|  |  | ||||||
|  |         data: u64, | ||||||
|  |         i: u8, | ||||||
|  |         enabled: bool, | ||||||
|  |  | ||||||
|  |         fn init() This { | ||||||
|  |             return .{ | ||||||
|  |                 .data = 0, | ||||||
|  |                 .i = 0, | ||||||
|  |                 .enabled = false, | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn configure(self: *This, value: u64) void { | ||||||
|  |             self.data = value; | ||||||
|  |             self.i = 0; | ||||||
|  |             self.enabled = true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn read(self: *This) u1 { | ||||||
|  |             if (!self.enabled) return 1; | ||||||
|  |  | ||||||
|  |             const bit = if (self.i < 4) blk: { | ||||||
|  |                 break :blk 0; | ||||||
|  |             } else blk: { | ||||||
|  |                 const idx = @intCast(u6, 63 - (self.i - 4)); | ||||||
|  |                 break :blk @truncate(u1, self.data >> idx); | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             self.i = (self.i + 1) % (64 + 4); | ||||||
|  |             if (self.i == 0) self.enabled = false; | ||||||
|  |  | ||||||
|  |             return bit; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn dbgRead(self: *const This) u1 { | ||||||
|  |             if (!self.enabled) return 1; | ||||||
|  |  | ||||||
|  |             const bit = if (self.i < 4) blk: { | ||||||
|  |                 break :blk 0; | ||||||
|  |             } else blk: { | ||||||
|  |                 const idx = @intCast(u6, 63 - (self.i - 4)); | ||||||
|  |                 break :blk @truncate(u1, self.data >> idx); | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             return bit; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const Writer = struct { | ||||||
|  |         const This = @This(); | ||||||
|  |  | ||||||
|  |         data: u64, | ||||||
|  |         i: u8, | ||||||
|  |  | ||||||
|  |         fn init() This { | ||||||
|  |             return .{ .data = 0, .i = 0 }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn requestWrite(self: *This, bit: u1) void { | ||||||
|  |             const idx = @intCast(u1, 1 - self.i); | ||||||
|  |             self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); | ||||||
|  |             self.i += 1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn addressWrite(self: *This, kind: Eeprom.Kind, bit: u1) void { | ||||||
|  |             if (kind == .Unknown) return; | ||||||
|  |  | ||||||
|  |             const size: u4 = switch (kind) { | ||||||
|  |                 .Large => 13, | ||||||
|  |                 .Small => 5, | ||||||
|  |                 .Unknown => unreachable, | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             const idx = @intCast(u4, size - self.i); | ||||||
|  |             self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); | ||||||
|  |             self.i += 1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn dataWrite(self: *This, bit: u1) void { | ||||||
|  |             const idx = @intCast(u6, 63 - self.i); | ||||||
|  |             self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); | ||||||
|  |             self.i += 1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn len(self: *const This) u8 { | ||||||
|  |             return self.i; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn finish(self: *This) u64 { | ||||||
|  |             defer self.reset(); | ||||||
|  |             return self.data; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn reset(self: *This) void { | ||||||
|  |             self.i = 0; | ||||||
|  |             self.data = 0; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -1,72 +0,0 @@ | |||||||
| const std = @import("std"); |  | ||||||
|  |  | ||||||
| const Self = @This(); |  | ||||||
|  |  | ||||||
| state: State, |  | ||||||
|  |  | ||||||
| id_mode: bool, |  | ||||||
| set_bank: bool, |  | ||||||
| prep_erase: bool, |  | ||||||
| prep_write: bool, |  | ||||||
|  |  | ||||||
| bank: u1, |  | ||||||
|  |  | ||||||
| const State = enum { |  | ||||||
|     Ready, |  | ||||||
|     Set, |  | ||||||
|     Command, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| pub fn read(self: *const Self, buf: []u8, idx: usize) u8 { |  | ||||||
|     return buf[self.address() + idx]; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn write(self: *Self, buf: []u8, idx: usize, byte: u8) void { |  | ||||||
|     buf[self.address() + idx] = byte; |  | ||||||
|     self.prep_write = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn create() Self { |  | ||||||
|     return .{ |  | ||||||
|         .state = .Ready, |  | ||||||
|         .id_mode = false, |  | ||||||
|         .set_bank = false, |  | ||||||
|         .prep_erase = false, |  | ||||||
|         .prep_write = false, |  | ||||||
|         .bank = 0, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn handleCommand(self: *Self, buf: []u8, byte: u8) void { |  | ||||||
|     switch (byte) { |  | ||||||
|         0x90 => self.id_mode = true, |  | ||||||
|         0xF0 => self.id_mode = false, |  | ||||||
|         0xB0 => self.set_bank = true, |  | ||||||
|         0x80 => self.prep_erase = true, |  | ||||||
|         0x10 => { |  | ||||||
|             std.mem.set(u8, buf, 0xFF); |  | ||||||
|             self.prep_erase = false; |  | ||||||
|         }, |  | ||||||
|         0xA0 => self.prep_write = true, |  | ||||||
|         else => std.debug.panic("Unhandled Flash Command: 0x{X:0>2}", .{byte}), |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     self.state = .Ready; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn shouldEraseSector(self: *const Self, addr: usize, byte: u8) bool { |  | ||||||
|     return self.state == .Command and self.prep_erase and byte == 0x30 and addr & 0xFFF == 0x000; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn erase(self: *Self, buf: []u8, sector: usize) void { |  | ||||||
|     const start = self.address() + (sector & 0xF000); |  | ||||||
|  |  | ||||||
|     std.mem.set(u8, buf[start..][0..0x1000], 0xFF); |  | ||||||
|     self.prep_erase = false; |  | ||||||
|     self.state = .Ready; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Base Address |  | ||||||
| inline fn address(self: *const Self) usize { |  | ||||||
|     return if (self.bank == 1) 0x10000 else @as(usize, 0); |  | ||||||
| } |  | ||||||
| @@ -1,269 +0,0 @@ | |||||||
| const std = @import("std"); |  | ||||||
|  |  | ||||||
| const Allocator = std.mem.Allocator; |  | ||||||
|  |  | ||||||
| const log = std.log.scoped(.Eeprom); |  | ||||||
|  |  | ||||||
| pub const Eeprom = struct { |  | ||||||
|     const Self = @This(); |  | ||||||
|  |  | ||||||
|     addr: u14, |  | ||||||
|  |  | ||||||
|     kind: Kind, |  | ||||||
|     state: State, |  | ||||||
|     writer: Writer, |  | ||||||
|     reader: Reader, |  | ||||||
|  |  | ||||||
|     allocator: Allocator, |  | ||||||
|  |  | ||||||
|     const Kind = enum { |  | ||||||
|         Unknown, |  | ||||||
|         Small, // 512B |  | ||||||
|         Large, // 8KB |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     const State = enum { |  | ||||||
|         Ready, |  | ||||||
|         Read, |  | ||||||
|         Write, |  | ||||||
|         WriteTransfer, |  | ||||||
|         RequestEnd, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     pub fn read(self: *Self) u1 { |  | ||||||
|         return self.reader.read(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn dbgRead(self: *const Self) u1 { |  | ||||||
|         return self.reader.dbgRead(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn write(self: *Self, word_count: u16, buf: *[]u8, bit: u1) void { |  | ||||||
|         if (self.guessKind(word_count)) |found| { |  | ||||||
|             log.info("EEPROM Kind: {}", .{found}); |  | ||||||
|             self.kind = found; |  | ||||||
|  |  | ||||||
|             // buf.len will not equal zero when a save file was found and loaded. |  | ||||||
|             // Right now, we assume that the save file is of the correct size which |  | ||||||
|             // isn't necessarily true, since we can't trust anything a user can influence |  | ||||||
|             // TODO: use ?[]u8 instead of a 0-sized slice? |  | ||||||
|             if (buf.len == 0) { |  | ||||||
|                 const len: usize = switch (found) { |  | ||||||
|                     .Small => 0x200, |  | ||||||
|                     .Large => 0x2000, |  | ||||||
|                     else => unreachable, |  | ||||||
|                 }; |  | ||||||
|  |  | ||||||
|                 buf.* = self.allocator.alloc(u8, len) catch |e| { |  | ||||||
|                     log.err("Failed to resize EEPROM buf to {} bytes", .{len}); |  | ||||||
|                     std.debug.panic("EEPROM entered irrecoverable state {}", .{e}); |  | ||||||
|                 }; |  | ||||||
|                 std.mem.set(u8, buf.*, 0xFF); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (self.state == .RequestEnd) { |  | ||||||
|             if (bit != 0) log.debug("EEPROM Request did not end in 0u1. TODO: is this ok?", .{}); |  | ||||||
|             self.state = .Ready; |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         switch (self.state) { |  | ||||||
|             .Ready => self.writer.requestWrite(bit), |  | ||||||
|             .Read, .Write => self.writer.addressWrite(self.kind, bit), |  | ||||||
|             .WriteTransfer => self.writer.dataWrite(bit), |  | ||||||
|             .RequestEnd => unreachable, // We return early just above this block |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         self.tick(buf.*); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn create(allocator: Allocator) Self { |  | ||||||
|         return .{ |  | ||||||
|             .kind = .Unknown, |  | ||||||
|             .state = .Ready, |  | ||||||
|             .writer = Writer.create(), |  | ||||||
|             .reader = Reader.create(), |  | ||||||
|             .addr = 0, |  | ||||||
|             .allocator = allocator, |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn guessKind(self: *const Self, word_count: u16) ?Kind { |  | ||||||
|         if (self.kind != .Unknown or self.state != .Read) return null; |  | ||||||
|  |  | ||||||
|         return switch (word_count) { |  | ||||||
|             17 => .Large, |  | ||||||
|             9 => .Small, |  | ||||||
|             else => blk: { |  | ||||||
|                 log.err("Unexpected length of DMA3 Transfer upon initial EEPROM read: {}", .{word_count}); |  | ||||||
|                 break :blk null; |  | ||||||
|             }, |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn tick(self: *Self, buf: []u8) void { |  | ||||||
|         switch (self.state) { |  | ||||||
|             .Ready => { |  | ||||||
|                 if (self.writer.len() == 2) { |  | ||||||
|                     const req = @intCast(u2, self.writer.finish()); |  | ||||||
|                     switch (req) { |  | ||||||
|                         0b11 => self.state = .Read, |  | ||||||
|                         0b10 => self.state = .Write, |  | ||||||
|                         else => log.err("Unknown EEPROM Request 0b{b:0>2}", .{req}), |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             .Read => { |  | ||||||
|                 switch (self.kind) { |  | ||||||
|                     .Large => { |  | ||||||
|                         if (self.writer.len() == 14) { |  | ||||||
|                             const addr = @intCast(u10, self.writer.finish()); |  | ||||||
|                             const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]); |  | ||||||
|  |  | ||||||
|                             self.reader.configure(value); |  | ||||||
|                             self.state = .RequestEnd; |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                     .Small => { |  | ||||||
|                         if (self.writer.len() == 6) { |  | ||||||
|                             // FIXME: Duplicated code from above |  | ||||||
|                             const addr = @intCast(u6, self.writer.finish()); |  | ||||||
|                             const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]); |  | ||||||
|  |  | ||||||
|                             self.reader.configure(value); |  | ||||||
|                             self.state = .RequestEnd; |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                     else => log.err("Unable to calculate EEPROM read address. EEPROM size UNKNOWN", .{}), |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             .Write => { |  | ||||||
|                 switch (self.kind) { |  | ||||||
|                     .Large => { |  | ||||||
|                         if (self.writer.len() == 14) { |  | ||||||
|                             self.addr = @intCast(u10, self.writer.finish()); |  | ||||||
|                             self.state = .WriteTransfer; |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                     .Small => { |  | ||||||
|                         if (self.writer.len() == 6) { |  | ||||||
|                             self.addr = @intCast(u6, self.writer.finish()); |  | ||||||
|                             self.state = .WriteTransfer; |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                     else => log.err("Unable to calculate EEPROM write address. EEPROM size UNKNOWN", .{}), |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             .WriteTransfer => { |  | ||||||
|                 if (self.writer.len() == 64) { |  | ||||||
|                     std.mem.writeIntSliceLittle(u64, buf[self.addr * 8 ..][0..8], self.writer.finish()); |  | ||||||
|                     self.state = .RequestEnd; |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             .RequestEnd => unreachable, // We return early in write() if state is .RequestEnd |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const Reader = struct { |  | ||||||
|     const Self = @This(); |  | ||||||
|  |  | ||||||
|     data: u64, |  | ||||||
|     i: u8, |  | ||||||
|     enabled: bool, |  | ||||||
|  |  | ||||||
|     fn create() Self { |  | ||||||
|         return .{ |  | ||||||
|             .data = 0, |  | ||||||
|             .i = 0, |  | ||||||
|             .enabled = false, |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn read(self: *Self) u1 { |  | ||||||
|         if (!self.enabled) return 1; |  | ||||||
|  |  | ||||||
|         const bit = if (self.i < 4) blk: { |  | ||||||
|             break :blk 0; |  | ||||||
|         } else blk: { |  | ||||||
|             const idx = @intCast(u6, 63 - (self.i - 4)); |  | ||||||
|             break :blk @truncate(u1, self.data >> idx); |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         self.i = (self.i + 1) % (64 + 4); |  | ||||||
|         if (self.i == 0) self.enabled = false; |  | ||||||
|  |  | ||||||
|         return bit; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn dbgRead(self: *const Self) u1 { |  | ||||||
|         if (!self.enabled) return 1; |  | ||||||
|  |  | ||||||
|         const bit = if (self.i < 4) blk: { |  | ||||||
|             break :blk 0; |  | ||||||
|         } else blk: { |  | ||||||
|             const idx = @intCast(u6, 63 - (self.i - 4)); |  | ||||||
|             break :blk @truncate(u1, self.data >> idx); |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         return bit; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn configure(self: *Self, value: u64) void { |  | ||||||
|         self.data = value; |  | ||||||
|         self.i = 0; |  | ||||||
|         self.enabled = true; |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const Writer = struct { |  | ||||||
|     const Self = @This(); |  | ||||||
|  |  | ||||||
|     data: u64, |  | ||||||
|     i: u8, |  | ||||||
|  |  | ||||||
|     fn create() Self { |  | ||||||
|         return .{ .data = 0, .i = 0 }; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn requestWrite(self: *Self, bit: u1) void { |  | ||||||
|         const idx = @intCast(u1, 1 - self.i); |  | ||||||
|         self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); |  | ||||||
|         self.i += 1; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn addressWrite(self: *Self, kind: Eeprom.Kind, bit: u1) void { |  | ||||||
|         if (kind == .Unknown) return; |  | ||||||
|  |  | ||||||
|         const size: u4 = switch (kind) { |  | ||||||
|             .Large => 13, |  | ||||||
|             .Small => 5, |  | ||||||
|             .Unknown => unreachable, |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         const idx = @intCast(u4, size - self.i); |  | ||||||
|         self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); |  | ||||||
|         self.i += 1; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn dataWrite(self: *Self, bit: u1) void { |  | ||||||
|         const idx = @intCast(u6, 63 - self.i); |  | ||||||
|         self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); |  | ||||||
|         self.i += 1; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn len(self: *const Self) u8 { |  | ||||||
|         return self.i; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn finish(self: *Self) u64 { |  | ||||||
|         defer self.reset(); |  | ||||||
|         return self.data; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn reset(self: *Self) void { |  | ||||||
|         self.i = 0; |  | ||||||
|         self.data = 0; |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| @@ -8,9 +8,6 @@ const Arm7tdmi = @import("../cpu.zig").Arm7tdmi; | |||||||
| pub const DmaTuple = std.meta.Tuple(&[_]type{ DmaController(0), DmaController(1), DmaController(2), DmaController(3) }); | pub const DmaTuple = std.meta.Tuple(&[_]type{ DmaController(0), DmaController(1), DmaController(2), DmaController(3) }); | ||||||
| const log = std.log.scoped(.DmaTransfer); | const log = std.log.scoped(.DmaTransfer); | ||||||
|  |  | ||||||
| const setHi = util.setHi; |  | ||||||
| const setLo = util.setLo; |  | ||||||
|  |  | ||||||
| pub fn create() DmaTuple { | pub fn create() DmaTuple { | ||||||
|     return .{ DmaController(0).init(), DmaController(1).init(), DmaController(2).init(), DmaController(3).init() }; |     return .{ DmaController(0).init(), DmaController(1).init(), DmaController(2).init(), DmaController(3).init() }; | ||||||
| } | } | ||||||
| @@ -43,48 +40,48 @@ pub fn write(comptime T: type, dma: *DmaTuple, addr: u32, value: T) void { | |||||||
|  |  | ||||||
|     switch (T) { |     switch (T) { | ||||||
|         u32 => switch (byte) { |         u32 => switch (byte) { | ||||||
|             0xB0 => dma.*[0].setDmasad(value), |             0xB0 => dma.*[0].setSad(value), | ||||||
|             0xB4 => dma.*[0].setDmadad(value), |             0xB4 => dma.*[0].setDad(value), | ||||||
|             0xB8 => dma.*[0].setDmacnt(value), |             0xB8 => dma.*[0].setCnt(value), | ||||||
|             0xBC => dma.*[1].setDmasad(value), |             0xBC => dma.*[1].setSad(value), | ||||||
|             0xC0 => dma.*[1].setDmadad(value), |             0xC0 => dma.*[1].setDad(value), | ||||||
|             0xC4 => dma.*[1].setDmacnt(value), |             0xC4 => dma.*[1].setCnt(value), | ||||||
|             0xC8 => dma.*[2].setDmasad(value), |             0xC8 => dma.*[2].setSad(value), | ||||||
|             0xCC => dma.*[2].setDmadad(value), |             0xCC => dma.*[2].setDad(value), | ||||||
|             0xD0 => dma.*[2].setDmacnt(value), |             0xD0 => dma.*[2].setCnt(value), | ||||||
|             0xD4 => dma.*[3].setDmasad(value), |             0xD4 => dma.*[3].setSad(value), | ||||||
|             0xD8 => dma.*[3].setDmadad(value), |             0xD8 => dma.*[3].setDad(value), | ||||||
|             0xDC => dma.*[3].setDmacnt(value), |             0xDC => dma.*[3].setCnt(value), | ||||||
|             else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }), |             else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
|         }, |         }, | ||||||
|         u16 => switch (byte) { |         u16 => switch (byte) { | ||||||
|             0xB0 => dma.*[0].setDmasad(setLo(u32, dma.*[0].sad, value)), |             0xB0 => dma.*[0].setSad(setU32L(dma.*[0].sad, value)), | ||||||
|             0xB2 => dma.*[0].setDmasad(setHi(u32, dma.*[0].sad, value)), |             0xB2 => dma.*[0].setSad(setU32H(dma.*[0].sad, value)), | ||||||
|             0xB4 => dma.*[0].setDmadad(setLo(u32, dma.*[0].dad, value)), |             0xB4 => dma.*[0].setDad(setU32L(dma.*[0].dad, value)), | ||||||
|             0xB6 => dma.*[0].setDmadad(setHi(u32, dma.*[0].dad, value)), |             0xB6 => dma.*[0].setDad(setU32H(dma.*[0].dad, value)), | ||||||
|             0xB8 => dma.*[0].setDmacntL(value), |             0xB8 => dma.*[0].setCntL(value), | ||||||
|             0xBA => dma.*[0].setDmacntH(value), |             0xBA => dma.*[0].setCntH(value), | ||||||
|  |  | ||||||
|             0xBC => dma.*[1].setDmasad(setLo(u32, dma.*[1].sad, value)), |             0xBC => dma.*[1].setSad(setU32L(dma.*[1].sad, value)), | ||||||
|             0xBE => dma.*[1].setDmasad(setHi(u32, dma.*[1].sad, value)), |             0xBE => dma.*[1].setSad(setU32H(dma.*[1].sad, value)), | ||||||
|             0xC0 => dma.*[1].setDmadad(setLo(u32, dma.*[1].dad, value)), |             0xC0 => dma.*[1].setDad(setU32L(dma.*[1].dad, value)), | ||||||
|             0xC2 => dma.*[1].setDmadad(setHi(u32, dma.*[1].dad, value)), |             0xC2 => dma.*[1].setDad(setU32H(dma.*[1].dad, value)), | ||||||
|             0xC4 => dma.*[1].setDmacntL(value), |             0xC4 => dma.*[1].setCntL(value), | ||||||
|             0xC6 => dma.*[1].setDmacntH(value), |             0xC6 => dma.*[1].setCntH(value), | ||||||
|  |  | ||||||
|             0xC8 => dma.*[2].setDmasad(setLo(u32, dma.*[2].sad, value)), |             0xC8 => dma.*[2].setSad(setU32L(dma.*[2].sad, value)), | ||||||
|             0xCA => dma.*[2].setDmasad(setHi(u32, dma.*[2].sad, value)), |             0xCA => dma.*[2].setSad(setU32H(dma.*[2].sad, value)), | ||||||
|             0xCC => dma.*[2].setDmadad(setLo(u32, dma.*[2].dad, value)), |             0xCC => dma.*[2].setDad(setU32L(dma.*[2].dad, value)), | ||||||
|             0xCE => dma.*[2].setDmadad(setHi(u32, dma.*[2].dad, value)), |             0xCE => dma.*[2].setDad(setU32H(dma.*[2].dad, value)), | ||||||
|             0xD0 => dma.*[2].setDmacntL(value), |             0xD0 => dma.*[2].setCntL(value), | ||||||
|             0xD2 => dma.*[2].setDmacntH(value), |             0xD2 => dma.*[2].setCntH(value), | ||||||
|  |  | ||||||
|             0xD4 => dma.*[3].setDmasad(setLo(u32, dma.*[3].sad, value)), |             0xD4 => dma.*[3].setSad(setU32L(dma.*[3].sad, value)), | ||||||
|             0xD6 => dma.*[3].setDmasad(setHi(u32, dma.*[3].sad, value)), |             0xD6 => dma.*[3].setSad(setU32H(dma.*[3].sad, value)), | ||||||
|             0xD8 => dma.*[3].setDmadad(setLo(u32, dma.*[3].dad, value)), |             0xD8 => dma.*[3].setDad(setU32L(dma.*[3].dad, value)), | ||||||
|             0xDA => dma.*[3].setDmadad(setHi(u32, dma.*[3].dad, value)), |             0xDA => dma.*[3].setDad(setU32H(dma.*[3].dad, value)), | ||||||
|             0xDC => dma.*[3].setDmacntL(value), |             0xDC => dma.*[3].setCntL(value), | ||||||
|             0xDE => dma.*[3].setDmacntH(value), |             0xDE => dma.*[3].setCntH(value), | ||||||
|             else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }), |             else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
|         }, |         }, | ||||||
|         u8 => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }), |         u8 => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
| @@ -113,12 +110,15 @@ fn DmaController(comptime id: u2) type { | |||||||
|         cnt: DmaControl, |         cnt: DmaControl, | ||||||
|  |  | ||||||
|         /// Internal. Currrent Source Address |         /// Internal. Currrent Source Address | ||||||
|         sad_latch: u32, |         _sad: u32, | ||||||
|         /// Internal. Current Destination Address |         /// Internal. Current Destination Address | ||||||
|         dad_latch: u32, |         _dad: u32, | ||||||
|         /// Internal. Word Count |         /// Internal. Word Count | ||||||
|         _word_count: if (id == 3) u16 else u14, |         _word_count: if (id == 3) u16 else u14, | ||||||
|  |  | ||||||
|  |         // Internal. FIFO Word Count | ||||||
|  |         _fifo_word_count: u8, | ||||||
|  |  | ||||||
|         /// Some DMA Transfers are enabled during Hblank / VBlank and / or |         /// Some DMA Transfers are enabled during Hblank / VBlank and / or | ||||||
|         /// have delays. Thefore bit 15 of DMACNT isn't actually something |         /// have delays. Thefore bit 15 of DMACNT isn't actually something | ||||||
|         /// we can use to control when we do or do not execute a step in a DMA Transfer |         /// we can use to control when we do or do not execute a step in a DMA Transfer | ||||||
| @@ -132,32 +132,33 @@ fn DmaController(comptime id: u2) type { | |||||||
|                 .cnt = .{ .raw = 0x000 }, |                 .cnt = .{ .raw = 0x000 }, | ||||||
|  |  | ||||||
|                 // Internals |                 // Internals | ||||||
|                 .sad_latch = 0, |                 ._sad = 0, | ||||||
|                 .dad_latch = 0, |                 ._dad = 0, | ||||||
|                 ._word_count = 0, |                 ._word_count = 0, | ||||||
|  |                 ._fifo_word_count = 4, | ||||||
|                 .in_progress = false, |                 .in_progress = false, | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         pub fn setDmasad(self: *Self, addr: u32) void { |         pub fn setSad(self: *Self, addr: u32) void { | ||||||
|             self.sad = addr & sad_mask; |             self.sad = addr & sad_mask; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         pub fn setDmadad(self: *Self, addr: u32) void { |         pub fn setDad(self: *Self, addr: u32) void { | ||||||
|             self.dad = addr & dad_mask; |             self.dad = addr & dad_mask; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         pub fn setDmacntL(self: *Self, halfword: u16) void { |         pub fn setCntL(self: *Self, halfword: u16) void { | ||||||
|             self.word_count = @truncate(@TypeOf(self.word_count), halfword); |             self.word_count = @truncate(@TypeOf(self.word_count), halfword); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         pub fn setDmacntH(self: *Self, halfword: u16) void { |         pub fn setCntH(self: *Self, halfword: u16) void { | ||||||
|             const new = DmaControl{ .raw = halfword }; |             const new = DmaControl{ .raw = halfword }; | ||||||
|  |  | ||||||
|             if (!self.cnt.enabled.read() and new.enabled.read()) { |             if (!self.cnt.enabled.read() and new.enabled.read()) { | ||||||
|                 // Reload Internals on Rising Edge. |                 // Reload Internals on Rising Edge. | ||||||
|                 self.sad_latch = self.sad; |                 self._sad = self.sad; | ||||||
|                 self.dad_latch = self.dad; |                 self._dad = self.dad; | ||||||
|                 self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count; |                 self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count; | ||||||
|  |  | ||||||
|                 // Only a Start Timing of 00 has a DMA Transfer immediately begin |                 // Only a Start Timing of 00 has a DMA Transfer immediately begin | ||||||
| @@ -167,15 +168,15 @@ fn DmaController(comptime id: u2) type { | |||||||
|             self.cnt.raw = halfword; |             self.cnt.raw = halfword; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         pub fn setDmacnt(self: *Self, word: u32) void { |         pub fn setCnt(self: *Self, word: u32) void { | ||||||
|             self.setDmacntL(@truncate(u16, word)); |             self.setCntL(@truncate(u16, word)); | ||||||
|             self.setDmacntH(@truncate(u16, word >> 16)); |             self.setCntH(@truncate(u16, word >> 16)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         pub fn step(self: *Self, cpu: *Arm7tdmi) void { |         pub fn step(self: *Self, cpu: *Arm7tdmi) void { | ||||||
|             const is_fifo = (id == 1 or id == 2) and self.cnt.start_timing.read() == 0b11; |             const is_fifo = (id == 1 or id == 2) and self.cnt.start_timing.read() == 0b11; | ||||||
|             const sad_adj = @intToEnum(Adjustment, self.cnt.sad_adj.read()); |             const sad_adj = Self.adjustment(self.cnt.sad_adj.read()); | ||||||
|             const dad_adj = if (is_fifo) .Fixed else @intToEnum(Adjustment, self.cnt.dad_adj.read()); |             const dad_adj = if (is_fifo) .Fixed else Self.adjustment(self.cnt.dad_adj.read()); | ||||||
|  |  | ||||||
|             const transfer_type = is_fifo or self.cnt.transfer_type.read(); |             const transfer_type = is_fifo or self.cnt.transfer_type.read(); | ||||||
|             const offset: u32 = if (transfer_type) @sizeOf(u32) else @sizeOf(u16); |             const offset: u32 = if (transfer_type) @sizeOf(u32) else @sizeOf(u16); | ||||||
| @@ -183,22 +184,22 @@ fn DmaController(comptime id: u2) type { | |||||||
|             const mask = if (transfer_type) ~@as(u32, 3) else ~@as(u32, 1); |             const mask = if (transfer_type) ~@as(u32, 3) else ~@as(u32, 1); | ||||||
|  |  | ||||||
|             if (transfer_type) { |             if (transfer_type) { | ||||||
|                 cpu.bus.write(u32, self.dad_latch & mask, cpu.bus.read(u32, self.sad_latch & mask)); |                 cpu.bus.write(u32, self._dad & mask, cpu.bus.read(u32, self._sad & mask)); | ||||||
|             } else { |             } else { | ||||||
|                 cpu.bus.write(u16, self.dad_latch & mask, cpu.bus.read(u16, self.sad_latch & mask)); |                 cpu.bus.write(u16, self._dad & mask, cpu.bus.read(u16, self._sad & mask)); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             switch (sad_adj) { |             switch (sad_adj) { | ||||||
|                 .Increment => self.sad_latch +%= offset, |                 .Increment => self._sad +%= offset, | ||||||
|                 .Decrement => self.sad_latch -%= offset, |                 .Decrement => self._sad -%= offset, | ||||||
|                 // FIXME: Is just ignoring this ok? |                 // TODO: Is just ignoring this ok? | ||||||
|                 .IncrementReload => log.err("{} is a prohibited adjustment on SAD", .{sad_adj}), |                 .IncrementReload => log.err("{} is a prohibited adjustment on SAD", .{sad_adj}), | ||||||
|                 .Fixed => {}, |                 .Fixed => {}, | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             switch (dad_adj) { |             switch (dad_adj) { | ||||||
|                 .Increment, .IncrementReload => self.dad_latch +%= offset, |                 .Increment, .IncrementReload => self._dad +%= offset, | ||||||
|                 .Decrement => self.dad_latch -%= offset, |                 .Decrement => self._dad -%= offset, | ||||||
|                 .Fixed => {}, |                 .Fixed => {}, | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -226,7 +227,7 @@ fn DmaController(comptime id: u2) type { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         fn poll(self: *Self, comptime kind: DmaKind) void { |         pub fn pollBlanking(self: *Self, comptime kind: DmaKind) void { | ||||||
|             if (self.in_progress) return; // If there's an ongoing DMA Transfer, exit early |             if (self.in_progress) return; // If there's an ongoing DMA Transfer, exit early | ||||||
|  |  | ||||||
|             // No ongoing DMA Transfer, We want to check if we should repeat an existing one |             // No ongoing DMA Transfer, We want to check if we should repeat an existing one | ||||||
| @@ -242,11 +243,11 @@ fn DmaController(comptime id: u2) type { | |||||||
|             // Reload internal DAD latch if we are in IncrementRelaod |             // Reload internal DAD latch if we are in IncrementRelaod | ||||||
|             if (self.in_progress) { |             if (self.in_progress) { | ||||||
|                 self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count; |                 self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count; | ||||||
|                 if (@intToEnum(Adjustment, self.cnt.dad_adj.read()) == .IncrementReload) self.dad_latch = self.dad; |                 if (Self.adjustment(self.cnt.dad_adj.read()) == .IncrementReload) self._dad = self.dad; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         pub fn requestAudio(self: *Self, _: u32) void { |         pub fn requestSoundDma(self: *Self, _: u32) void { | ||||||
|             comptime std.debug.assert(id == 1 or id == 2); |             comptime std.debug.assert(id == 1 or id == 2); | ||||||
|             if (self.in_progress) return; // APU must wait their turn |             if (self.in_progress) return; // APU must wait their turn | ||||||
|  |  | ||||||
| @@ -258,19 +259,23 @@ fn DmaController(comptime id: u2) type { | |||||||
|             // We Assume DMACNT_L is set to 4 |             // We Assume DMACNT_L is set to 4 | ||||||
|  |  | ||||||
|             // FIXME: Safe to just assume whatever DAD is set to is the FIFO Address? |             // FIXME: Safe to just assume whatever DAD is set to is the FIFO Address? | ||||||
|             // self.dad_latch = fifo_addr; |             // self._dad = fifo_addr; | ||||||
|             self.cnt.repeat.set(); |             self.cnt.repeat.set(); | ||||||
|             self._word_count = 4; |             self._word_count = 4; | ||||||
|             self.in_progress = true; |             self.in_progress = true; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         fn adjustment(idx: u2) Adjustment { | ||||||
|  |             return std.meta.intToEnum(Adjustment, idx) catch unreachable; | ||||||
|  |         } | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn onBlanking(bus: *Bus, comptime kind: DmaKind) void { | pub fn onBlanking(bus: *Bus, comptime kind: DmaKind) void { | ||||||
|     bus.dma[0].poll(kind); |     bus.dma[0].pollBlanking(kind); | ||||||
|     bus.dma[1].poll(kind); |     bus.dma[1].pollBlanking(kind); | ||||||
|     bus.dma[2].poll(kind); |     bus.dma[2].pollBlanking(kind); | ||||||
|     bus.dma[3].poll(kind); |     bus.dma[3].pollBlanking(kind); | ||||||
| } | } | ||||||
|  |  | ||||||
| const Adjustment = enum(u2) { | const Adjustment = enum(u2) { | ||||||
| @@ -286,3 +291,11 @@ const DmaKind = enum(u2) { | |||||||
|     VBlank, |     VBlank, | ||||||
|     Special, |     Special, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | fn setU32L(left: u32, right: u16) u32 { | ||||||
|  |     return (left & 0xFFFF_0000) | right; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn setU32H(left: u32, right: u16) u32 { | ||||||
|  |     return (left & 0x0000_FFFF) | (@as(u32, right) << 16); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -68,8 +68,6 @@ pub const Gpio = struct { | |||||||
|         log.info("Device: {}", .{kind}); |         log.info("Device: {}", .{kind}); | ||||||
|  |  | ||||||
|         const self = try allocator.create(Self); |         const self = try allocator.create(Self); | ||||||
|         errdefer allocator.destroy(self); |  | ||||||
|  |  | ||||||
|         self.* = .{ |         self.* = .{ | ||||||
|             .data = 0b0000, |             .data = 0b0000, | ||||||
|             .direction = 0b1111, // TODO: What is GPIO DIrection set to by default? |             .direction = 0b1111, // TODO: What is GPIO DIrection set to by default? | ||||||
| @@ -290,7 +288,7 @@ pub const Clock = struct { | |||||||
|         cpu.sched.push(.RealTimeClock, 1 << 24); // Every Second |         cpu.sched.push(.RealTimeClock, 1 << 24); // Every Second | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn onClockUpdate(self: *Self, late: u64) void { |     pub fn updateTime(self: *Self, late: u64) void { | ||||||
|         self.cpu.sched.push(.RealTimeClock, (1 << 24) -| late); // Reschedule |         self.cpu.sched.push(.RealTimeClock, (1 << 24) -| late); // Reschedule | ||||||
|  |  | ||||||
|         const now = DateTime.now(); |         const now = DateTime.now(); | ||||||
|   | |||||||
| @@ -11,9 +11,6 @@ const Bus = @import("../Bus.zig"); | |||||||
| const DmaController = @import("dma.zig").DmaController; | const DmaController = @import("dma.zig").DmaController; | ||||||
| const Scheduler = @import("../scheduler.zig").Scheduler; | const Scheduler = @import("../scheduler.zig").Scheduler; | ||||||
|  |  | ||||||
| const setHi = util.setLo; |  | ||||||
| const setLo = util.setHi; |  | ||||||
|  |  | ||||||
| const log = std.log.scoped(.@"I/O"); | const log = std.log.scoped(.@"I/O"); | ||||||
|  |  | ||||||
| pub const Io = struct { | pub const Io = struct { | ||||||
| @@ -236,18 +233,18 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | |||||||
|             0x0400_0022 => bus.ppu.aff_bg[0].pb = @bitCast(i16, value), |             0x0400_0022 => bus.ppu.aff_bg[0].pb = @bitCast(i16, value), | ||||||
|             0x0400_0024 => bus.ppu.aff_bg[0].pc = @bitCast(i16, value), |             0x0400_0024 => bus.ppu.aff_bg[0].pc = @bitCast(i16, value), | ||||||
|             0x0400_0026 => bus.ppu.aff_bg[0].pd = @bitCast(i16, value), |             0x0400_0026 => bus.ppu.aff_bg[0].pd = @bitCast(i16, value), | ||||||
|             0x0400_0028 => bus.ppu.aff_bg[0].x = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[0].x), value)), |             0x0400_0028 => bus.ppu.aff_bg[0].x = @bitCast(i32, @bitCast(u32, bus.ppu.aff_bg[0].x) & 0xFFFF_0000 | value), | ||||||
|             0x0400_002A => bus.ppu.aff_bg[0].x = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[0].x), value)), |             0x0400_002A => bus.ppu.aff_bg[0].x = @bitCast(i32, @bitCast(u32, bus.ppu.aff_bg[0].x) & 0x0000_FFFF | (@as(u32, value) << 16)), | ||||||
|             0x0400_002C => bus.ppu.aff_bg[0].y = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[0].y), value)), |             0x0400_002C => bus.ppu.aff_bg[0].y = @bitCast(i32, @bitCast(u32, bus.ppu.aff_bg[0].y) & 0xFFFF_0000 | value), | ||||||
|             0x0400_002E => bus.ppu.aff_bg[0].y = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[0].y), value)), |             0x0400_002E => bus.ppu.aff_bg[0].y = @bitCast(i32, @bitCast(u32, bus.ppu.aff_bg[0].y) & 0x0000_FFFF | (@as(u32, value) << 16)), | ||||||
|             0x0400_0030 => bus.ppu.aff_bg[1].pa = @bitCast(i16, value), |             0x0400_0030 => bus.ppu.aff_bg[1].pa = @bitCast(i16, value), | ||||||
|             0x0400_0032 => bus.ppu.aff_bg[1].pb = @bitCast(i16, value), |             0x0400_0032 => bus.ppu.aff_bg[1].pb = @bitCast(i16, value), | ||||||
|             0x0400_0034 => bus.ppu.aff_bg[1].pc = @bitCast(i16, value), |             0x0400_0034 => bus.ppu.aff_bg[1].pc = @bitCast(i16, value), | ||||||
|             0x0400_0036 => bus.ppu.aff_bg[1].pd = @bitCast(i16, value), |             0x0400_0036 => bus.ppu.aff_bg[1].pd = @bitCast(i16, value), | ||||||
|             0x0400_0038 => bus.ppu.aff_bg[1].x = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[1].x), value)), |             0x0400_0038 => bus.ppu.aff_bg[1].x = @bitCast(i32, @bitCast(u32, bus.ppu.aff_bg[1].x) & 0xFFFF_0000 | value), | ||||||
|             0x0400_003A => bus.ppu.aff_bg[1].x = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[1].x), value)), |             0x0400_003A => bus.ppu.aff_bg[1].x = @bitCast(i32, @bitCast(u32, bus.ppu.aff_bg[1].x) & 0x0000_FFFF | (@as(u32, value) << 16)), | ||||||
|             0x0400_003C => bus.ppu.aff_bg[1].y = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[1].y), value)), |             0x0400_003C => bus.ppu.aff_bg[1].y = @bitCast(i32, @bitCast(u32, bus.ppu.aff_bg[1].y) & 0xFFFF_0000 | value), | ||||||
|             0x0400_003E => bus.ppu.aff_bg[1].y = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[1].y), value)), |             0x0400_003E => bus.ppu.aff_bg[1].y = @bitCast(i32, @bitCast(u32, bus.ppu.aff_bg[1].y) & 0x0000_FFFF | (@as(u32, value) << 16)), | ||||||
|             0x0400_0040 => bus.ppu.win.h[0].raw = value, |             0x0400_0040 => bus.ppu.win.h[0].raw = value, | ||||||
|             0x0400_0042 => bus.ppu.win.h[1].raw = value, |             0x0400_0042 => bus.ppu.win.h[1].raw = value, | ||||||
|             0x0400_0044 => bus.ppu.win.v[0].raw = value, |             0x0400_0044 => bus.ppu.win.v[0].raw = value, | ||||||
| @@ -299,24 +296,24 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | |||||||
|         }, |         }, | ||||||
|         u8 => switch (address) { |         u8 => switch (address) { | ||||||
|             // Display |             // Display | ||||||
|             0x0400_0004 => bus.ppu.dispstat.raw = setLo(u16, bus.ppu.dispstat.raw, value), |             0x0400_0004 => bus.ppu.dispstat.raw = (bus.ppu.dispstat.raw & 0xFF00) | value, | ||||||
|             0x0400_0005 => bus.ppu.dispstat.raw = setHi(u16, bus.ppu.dispstat.raw, value), |             0x0400_0005 => bus.ppu.dispstat.raw = (@as(u16, value) << 8) | (bus.ppu.dispstat.raw & 0xFF), | ||||||
|             0x0400_0008 => bus.ppu.bg[0].cnt.raw = setLo(u16, bus.ppu.bg[0].cnt.raw, value), |             0x0400_0008 => bus.ppu.bg[0].cnt.raw = (bus.ppu.bg[0].cnt.raw & 0xFF00) | value, | ||||||
|             0x0400_0009 => bus.ppu.bg[0].cnt.raw = setHi(u16, bus.ppu.bg[0].cnt.raw, value), |             0x0400_0009 => bus.ppu.bg[0].cnt.raw = (@as(u16, value) << 8) | (bus.ppu.bg[0].cnt.raw & 0xFF), | ||||||
|             0x0400_000A => bus.ppu.bg[1].cnt.raw = setLo(u16, bus.ppu.bg[1].cnt.raw, value), |             0x0400_000A => bus.ppu.bg[1].cnt.raw = (bus.ppu.bg[1].cnt.raw & 0xFF00) | value, | ||||||
|             0x0400_000B => bus.ppu.bg[1].cnt.raw = setHi(u16, bus.ppu.bg[1].cnt.raw, value), |             0x0400_000B => bus.ppu.bg[1].cnt.raw = (@as(u16, value) << 8) | (bus.ppu.bg[1].cnt.raw & 0xFF), | ||||||
|             0x0400_0040 => bus.ppu.win.h[0].raw = setLo(u16, bus.ppu.win.h[0].raw, value), |             0x0400_0040 => bus.ppu.win.h[0].set(.Lo, value), | ||||||
|             0x0400_0041 => bus.ppu.win.h[0].raw = setHi(u16, bus.ppu.win.h[0].raw, value), |             0x0400_0041 => bus.ppu.win.h[0].set(.Hi, value), | ||||||
|             0x0400_0042 => bus.ppu.win.h[1].raw = setLo(u16, bus.ppu.win.h[1].raw, value), |             0x0400_0042 => bus.ppu.win.h[1].set(.Lo, value), | ||||||
|             0x0400_0043 => bus.ppu.win.h[1].raw = setHi(u16, bus.ppu.win.h[1].raw, value), |             0x0400_0043 => bus.ppu.win.h[1].set(.Hi, value), | ||||||
|             0x0400_0044 => bus.ppu.win.v[0].raw = setLo(u16, bus.ppu.win.v[0].raw, value), |             0x0400_0044 => bus.ppu.win.v[0].set(.Lo, value), | ||||||
|             0x0400_0045 => bus.ppu.win.v[0].raw = setHi(u16, bus.ppu.win.v[0].raw, value), |             0x0400_0045 => bus.ppu.win.v[0].set(.Hi, value), | ||||||
|             0x0400_0046 => bus.ppu.win.v[1].raw = setLo(u16, bus.ppu.win.v[1].raw, value), |             0x0400_0046 => bus.ppu.win.v[1].set(.Lo, value), | ||||||
|             0x0400_0047 => bus.ppu.win.v[1].raw = setHi(u16, bus.ppu.win.v[1].raw, value), |             0x0400_0047 => bus.ppu.win.v[1].set(.Hi, value), | ||||||
|             0x0400_0048 => bus.ppu.win.in.raw = setLo(u16, bus.ppu.win.in.raw, value), |             0x0400_0048 => bus.ppu.win.setInL(value), | ||||||
|             0x0400_0049 => bus.ppu.win.in.raw = setHi(u16, bus.ppu.win.in.raw, value), |             0x0400_0049 => bus.ppu.win.setInH(value), | ||||||
|             0x0400_004A => bus.ppu.win.out.raw = setLo(u16, bus.ppu.win.out.raw, value), |             0x0400_004A => bus.ppu.win.setOutL(value), | ||||||
|             0x0400_0054 => bus.ppu.bldy.raw = setLo(u16, bus.ppu.bldy.raw, value), |             0x0400_0054 => bus.ppu.bldy.raw = (bus.ppu.bldy.raw & 0xFF00) | value, | ||||||
|  |  | ||||||
|             // Sound |             // Sound | ||||||
|             0x0400_0060...0x0400_00A7 => apu.write(T, &bus.apu, address, value), |             0x0400_0060...0x0400_00A7 => apu.write(T, &bus.apu, address, value), | ||||||
| @@ -479,6 +476,13 @@ pub const WinH = extern union { | |||||||
|     x2: Bitfield(u16, 0, 8), |     x2: Bitfield(u16, 0, 8), | ||||||
|     x1: Bitfield(u16, 8, 8), |     x1: Bitfield(u16, 8, 8), | ||||||
|     raw: u16, |     raw: u16, | ||||||
|  |  | ||||||
|  |     pub fn set(self: *Self, comptime K: u8WriteKind, value: u8) void { | ||||||
|  |         self.raw = switch (K) { | ||||||
|  |             .Hi => (@as(u16, value) << 8) | self.raw & 0xFF, | ||||||
|  |             .Lo => (self.raw & 0xFF00) | value, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Write-only | /// Write-only | ||||||
|   | |||||||
| @@ -19,20 +19,20 @@ pub fn read(comptime T: type, tim: *const TimerTuple, addr: u32) ?T { | |||||||
|  |  | ||||||
|     return switch (T) { |     return switch (T) { | ||||||
|         u32 => switch (nybble) { |         u32 => switch (nybble) { | ||||||
|             0x0 => @as(T, tim.*[0].cnt.raw) << 16 | tim.*[0].timcntL(), |             0x0 => @as(T, tim.*[0].cnt.raw) << 16 | tim.*[0].getCntL(), | ||||||
|             0x4 => @as(T, tim.*[1].cnt.raw) << 16 | tim.*[1].timcntL(), |             0x4 => @as(T, tim.*[1].cnt.raw) << 16 | tim.*[1].getCntL(), | ||||||
|             0x8 => @as(T, tim.*[2].cnt.raw) << 16 | tim.*[2].timcntL(), |             0x8 => @as(T, tim.*[2].cnt.raw) << 16 | tim.*[2].getCntL(), | ||||||
|             0xC => @as(T, tim.*[3].cnt.raw) << 16 | tim.*[3].timcntL(), |             0xC => @as(T, tim.*[3].cnt.raw) << 16 | tim.*[3].getCntL(), | ||||||
|             else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }), |             else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }), | ||||||
|         }, |         }, | ||||||
|         u16 => switch (nybble) { |         u16 => switch (nybble) { | ||||||
|             0x0 => tim.*[0].timcntL(), |             0x0 => tim.*[0].getCntL(), | ||||||
|             0x2 => tim.*[0].cnt.raw, |             0x2 => tim.*[0].cnt.raw, | ||||||
|             0x4 => tim.*[1].timcntL(), |             0x4 => tim.*[1].getCntL(), | ||||||
|             0x6 => tim.*[1].cnt.raw, |             0x6 => tim.*[1].cnt.raw, | ||||||
|             0x8 => tim.*[2].timcntL(), |             0x8 => tim.*[2].getCntL(), | ||||||
|             0xA => tim.*[2].cnt.raw, |             0xA => tim.*[2].cnt.raw, | ||||||
|             0xC => tim.*[3].timcntL(), |             0xC => tim.*[3].getCntL(), | ||||||
|             0xE => tim.*[3].cnt.raw, |             0xE => tim.*[3].cnt.raw, | ||||||
|             else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }), |             else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }), | ||||||
|         }, |         }, | ||||||
| @@ -46,21 +46,21 @@ pub fn write(comptime T: type, tim: *TimerTuple, addr: u32, value: T) void { | |||||||
|  |  | ||||||
|     return switch (T) { |     return switch (T) { | ||||||
|         u32 => switch (nybble) { |         u32 => switch (nybble) { | ||||||
|             0x0 => tim.*[0].setTimcnt(value), |             0x0 => tim.*[0].setCnt(value), | ||||||
|             0x4 => tim.*[1].setTimcnt(value), |             0x4 => tim.*[1].setCnt(value), | ||||||
|             0x8 => tim.*[2].setTimcnt(value), |             0x8 => tim.*[2].setCnt(value), | ||||||
|             0xC => tim.*[3].setTimcnt(value), |             0xC => tim.*[3].setCnt(value), | ||||||
|             else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }), |             else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
|         }, |         }, | ||||||
|         u16 => switch (nybble) { |         u16 => switch (nybble) { | ||||||
|             0x0 => tim.*[0].setTimcntL(value), |             0x0 => tim.*[0].setCntL(value), | ||||||
|             0x2 => tim.*[0].setTimcntH(value), |             0x2 => tim.*[0].setCntH(value), | ||||||
|             0x4 => tim.*[1].setTimcntL(value), |             0x4 => tim.*[1].setCntL(value), | ||||||
|             0x6 => tim.*[1].setTimcntH(value), |             0x6 => tim.*[1].setCntH(value), | ||||||
|             0x8 => tim.*[2].setTimcntL(value), |             0x8 => tim.*[2].setCntL(value), | ||||||
|             0xA => tim.*[2].setTimcntH(value), |             0xA => tim.*[2].setCntH(value), | ||||||
|             0xC => tim.*[3].setTimcntL(value), |             0xC => tim.*[3].setCntL(value), | ||||||
|             0xE => tim.*[3].setTimcntH(value), |             0xE => tim.*[3].setCntH(value), | ||||||
|             else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }), |             else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
|         }, |         }, | ||||||
|         u8 => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }), |         u8 => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
| @@ -72,13 +72,13 @@ fn Timer(comptime id: u2) type { | |||||||
|     return struct { |     return struct { | ||||||
|         const Self = @This(); |         const Self = @This(); | ||||||
|  |  | ||||||
|         /// Read Only, Internal. Please use self.timcntL() |         /// Read Only, Internal. Please use self.getCntL() | ||||||
|         _counter: u16, |         _counter: u16, | ||||||
|  |  | ||||||
|         /// Write Only, Internal. Please use self.setTimcntL() |         /// Write Only, Internal. Please use self.setCntL() | ||||||
|         _reload: u16, |         _reload: u16, | ||||||
|  |  | ||||||
|         /// Write Only, Internal. Please use self.setTimcntH() |         /// Write Only, Internal. Please use self.setCntH() | ||||||
|         cnt: TimerControl, |         cnt: TimerControl, | ||||||
|  |  | ||||||
|         /// Internal. |         /// Internal. | ||||||
| @@ -97,26 +97,26 @@ fn Timer(comptime id: u2) type { | |||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// TIMCNT_L Getter |         /// TIMCNT_L | ||||||
|         pub fn timcntL(self: *const Self) u16 { |         pub fn getCntL(self: *const Self) u16 { | ||||||
|             if (self.cnt.cascade.read() or !self.cnt.enabled.read()) return self._counter; |             if (self.cnt.cascade.read() or !self.cnt.enabled.read()) return self._counter; | ||||||
|  |  | ||||||
|             return self._counter +% @truncate(u16, (self.sched.now() - self._start_timestamp) / self.frequency()); |             return self._counter +% @truncate(u16, (self.sched.now() - self._start_timestamp) / self.frequency()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// TIMCNT_L Setter |         /// TIMCNT_L | ||||||
|         pub fn setTimcntL(self: *Self, halfword: u16) void { |         pub fn setCntL(self: *Self, halfword: u16) void { | ||||||
|             self._reload = halfword; |             self._reload = halfword; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// TIMCNT_L & TIMCNT_H |         /// TIMCNT_L & TIMCNT_H | ||||||
|         pub fn setTimcnt(self: *Self, word: u32) void { |         pub fn setCnt(self: *Self, word: u32) void { | ||||||
|             self.setTimcntL(@truncate(u16, word)); |             self.setCntL(@truncate(u16, word)); | ||||||
|             self.setTimcntH(@truncate(u16, word >> 16)); |             self.setCntH(@truncate(u16, word >> 16)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// TIMCNT_H |         /// TIMCNT_H | ||||||
|         pub fn setTimcntH(self: *Self, halfword: u16) void { |         pub fn setCntH(self: *Self, halfword: u16) void { | ||||||
|             const new = TimerControl{ .raw = halfword }; |             const new = TimerControl{ .raw = halfword }; | ||||||
|  |  | ||||||
|             // If Timer happens to be enabled, It will either be resheduled or disabled |             // If Timer happens to be enabled, It will either be resheduled or disabled | ||||||
| @@ -132,12 +132,12 @@ fn Timer(comptime id: u2) type { | |||||||
|             if (!self.cnt.enabled.read() and new.enabled.read()) self._counter = self._reload; |             if (!self.cnt.enabled.read() and new.enabled.read()) self._counter = self._reload; | ||||||
|  |  | ||||||
|             // If Timer is enabled and we're not cascading, we need to schedule an overflow event |             // If Timer is enabled and we're not cascading, we need to schedule an overflow event | ||||||
|             if (new.enabled.read() and !new.cascade.read()) self.rescheduleTimerExpire(0); |             if (new.enabled.read() and !new.cascade.read()) self.scheduleOverflow(0); | ||||||
|  |  | ||||||
|             self.cnt.raw = halfword; |             self.cnt.raw = halfword; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         pub fn onTimerExpire(self: *Self, cpu: *Arm7tdmi, late: u64) void { |         pub fn handleOverflow(self: *Self, cpu: *Arm7tdmi, late: u64) void { | ||||||
|             // Fire IRQ if enabled |             // Fire IRQ if enabled | ||||||
|             const io = &cpu.bus.io; |             const io = &cpu.bus.io; | ||||||
|  |  | ||||||
| @@ -154,22 +154,22 @@ fn Timer(comptime id: u2) type { | |||||||
|  |  | ||||||
|             // DMA Sound Things |             // DMA Sound Things | ||||||
|             if (id == 0 or id == 1) { |             if (id == 0 or id == 1) { | ||||||
|                 cpu.bus.apu.onDmaAudioSampleRequest(cpu, id); |                 cpu.bus.apu.handleTimerOverflow(cpu, id); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // Perform Cascade Behaviour |             // Perform Cascade Behaviour | ||||||
|             switch (id) { |             switch (id) { | ||||||
|                 0 => if (cpu.bus.tim[1].cnt.cascade.read()) { |                 0 => if (cpu.bus.tim[1].cnt.cascade.read()) { | ||||||
|                     cpu.bus.tim[1]._counter +%= 1; |                     cpu.bus.tim[1]._counter +%= 1; | ||||||
|                     if (cpu.bus.tim[1]._counter == 0) cpu.bus.tim[1].onTimerExpire(cpu, late); |                     if (cpu.bus.tim[1]._counter == 0) cpu.bus.tim[1].handleOverflow(cpu, late); | ||||||
|                 }, |                 }, | ||||||
|                 1 => if (cpu.bus.tim[2].cnt.cascade.read()) { |                 1 => if (cpu.bus.tim[2].cnt.cascade.read()) { | ||||||
|                     cpu.bus.tim[2]._counter +%= 1; |                     cpu.bus.tim[2]._counter +%= 1; | ||||||
|                     if (cpu.bus.tim[2]._counter == 0) cpu.bus.tim[2].onTimerExpire(cpu, late); |                     if (cpu.bus.tim[2]._counter == 0) cpu.bus.tim[2].handleOverflow(cpu, late); | ||||||
|                 }, |                 }, | ||||||
|                 2 => if (cpu.bus.tim[3].cnt.cascade.read()) { |                 2 => if (cpu.bus.tim[3].cnt.cascade.read()) { | ||||||
|                     cpu.bus.tim[3]._counter +%= 1; |                     cpu.bus.tim[3]._counter +%= 1; | ||||||
|                     if (cpu.bus.tim[3]._counter == 0) cpu.bus.tim[3].onTimerExpire(cpu, late); |                     if (cpu.bus.tim[3]._counter == 0) cpu.bus.tim[3].handleOverflow(cpu, late); | ||||||
|                 }, |                 }, | ||||||
|                 3 => {}, // There is no Timer for TIM3 to "cascade" to, |                 3 => {}, // There is no Timer for TIM3 to "cascade" to, | ||||||
|             } |             } | ||||||
| @@ -177,11 +177,11 @@ fn Timer(comptime id: u2) type { | |||||||
|             // Reschedule Timer if we're not cascading |             // Reschedule Timer if we're not cascading | ||||||
|             if (!self.cnt.cascade.read()) { |             if (!self.cnt.cascade.read()) { | ||||||
|                 self._counter = self._reload; |                 self._counter = self._reload; | ||||||
|                 self.rescheduleTimerExpire(late); |                 self.scheduleOverflow(late); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         fn rescheduleTimerExpire(self: *Self, late: u64) void { |         fn scheduleOverflow(self: *Self, late: u64) void { | ||||||
|             const when = (@as(u64, 0x10000) - self._counter) * self.frequency(); |             const when = (@as(u64, 0x10000) - self._counter) * self.frequency(); | ||||||
|  |  | ||||||
|             self._start_timestamp = self.sched.now(); |             self._start_timestamp = self.sched.now(); | ||||||
|   | |||||||
							
								
								
									
										131
									
								
								src/core/cpu.zig
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								src/core/cpu.zig
									
									
									
									
									
								
							| @@ -236,13 +236,13 @@ pub const thumb = struct { | |||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | const cpu_logging = @import("emu.zig").cpu_logging; | ||||||
| const log = std.log.scoped(.Arm7Tdmi); | const log = std.log.scoped(.Arm7Tdmi); | ||||||
|  |  | ||||||
| pub const Arm7tdmi = struct { | pub const Arm7tdmi = struct { | ||||||
|     const Self = @This(); |     const Self = @This(); | ||||||
|  |  | ||||||
|     r: [16]u32, |     r: [16]u32, | ||||||
|     pipe: Pipeline, |  | ||||||
|     sched: *Scheduler, |     sched: *Scheduler, | ||||||
|     bus: *Bus, |     bus: *Bus, | ||||||
|     cpsr: PSR, |     cpsr: PSR, | ||||||
| @@ -263,7 +263,6 @@ pub const Arm7tdmi = struct { | |||||||
|     pub fn init(sched: *Scheduler, bus: *Bus, log_file: ?std.fs.File) Self { |     pub fn init(sched: *Scheduler, bus: *Bus, log_file: ?std.fs.File) Self { | ||||||
|         return Self{ |         return Self{ | ||||||
|             .r = [_]u32{0x00} ** 16, |             .r = [_]u32{0x00} ** 16, | ||||||
|             .pipe = Pipeline.init(), |  | ||||||
|             .sched = sched, |             .sched = sched, | ||||||
|             .bus = bus, |             .bus = bus, | ||||||
|             .cpsr = .{ .raw = 0x0000_001F }, |             .cpsr = .{ .raw = 0x0000_001F }, | ||||||
| @@ -412,43 +411,29 @@ pub const Arm7tdmi = struct { | |||||||
|         self.cpsr.mode.write(@enumToInt(next)); |         self.cpsr.mode.write(@enumToInt(next)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Advances state so that the BIOS is skipped |  | ||||||
|     /// |  | ||||||
|     /// Note: This accesses the CPU's bus ptr so it only may be called |  | ||||||
|     /// once the Bus has been properly initialized |  | ||||||
|     /// |  | ||||||
|     /// TODO: Make above notice impossible to do in code |  | ||||||
|     pub fn fastBoot(self: *Self) void { |     pub fn fastBoot(self: *Self) void { | ||||||
|         self.r = std.mem.zeroes([16]u32); |         self.r = std.mem.zeroes([16]u32); | ||||||
|  |  | ||||||
|         // self.r[0] = 0x08000000; |         self.r[0] = 0x08000000; | ||||||
|         // self.r[1] = 0x000000EA; |         self.r[1] = 0x000000EA; | ||||||
|         self.r[13] = 0x0300_7F00; |         self.r[13] = 0x0300_7F00; | ||||||
|         self.r[15] = 0x0800_0000; |         self.r[15] = 0x0800_0000; | ||||||
|  |  | ||||||
|         self.banked_r[bankedIdx(.Irq, .R13)] = 0x0300_7FA0; |         self.banked_r[bankedIdx(.Irq, .R13)] = 0x0300_7FA0; | ||||||
|         self.banked_r[bankedIdx(.Supervisor, .R13)] = 0x0300_7FE0; |         self.banked_r[bankedIdx(.Supervisor, .R13)] = 0x0300_7FE0; | ||||||
|  |  | ||||||
|         // self.cpsr.raw = 0x6000001F; |         self.cpsr.raw = 0x6000001F; | ||||||
|         self.cpsr.raw = 0x0000_001F; |  | ||||||
|  |  | ||||||
|         self.bus.bios.addr_latch = 0x0000_00DC + 8; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn step(self: *Self) void { |     pub fn step(self: *Self) void { | ||||||
|         defer { |  | ||||||
|             if (!self.pipe.flushed) self.r[15] += if (self.cpsr.t.read()) 2 else @as(u32, 4); |  | ||||||
|             self.pipe.flushed = false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (self.cpsr.t.read()) { |         if (self.cpsr.t.read()) { | ||||||
|             const opcode = @truncate(u16, self.pipe.step(self, u16) orelse return); |             const opcode = self.fetch(u16); | ||||||
|             if (self.logger) |*trace| trace.mgbaLog(self, opcode); |             if (cpu_logging) self.logger.?.mgbaLog(self, opcode); | ||||||
|  |  | ||||||
|             thumb.lut[thumb.idx(opcode)](self, self.bus, opcode); |             thumb.lut[thumb.idx(opcode)](self, self.bus, opcode); | ||||||
|         } else { |         } else { | ||||||
|             const opcode = self.pipe.step(self, u32) orelse return; |             const opcode = self.fetch(u32); | ||||||
|             if (self.logger) |*trace| trace.mgbaLog(self, opcode); |             if (cpu_logging) self.logger.?.mgbaLog(self, opcode); | ||||||
|  |  | ||||||
|             if (checkCond(self.cpsr, @truncate(u4, opcode >> 28))) { |             if (checkCond(self.cpsr, @truncate(u4, opcode >> 28))) { | ||||||
|                 arm.lut[arm.idx(opcode)](self, self.bus, opcode); |                 arm.lut[arm.idx(opcode)](self, self.bus, opcode); | ||||||
| @@ -488,41 +473,42 @@ pub const Arm7tdmi = struct { | |||||||
|     pub fn handleInterrupt(self: *Self) void { |     pub fn handleInterrupt(self: *Self) void { | ||||||
|         const should_handle = self.bus.io.ie.raw & self.bus.io.irq.raw; |         const should_handle = self.bus.io.ie.raw & self.bus.io.irq.raw; | ||||||
|  |  | ||||||
|         // Return if IME is disabled, CPSR I is set or there is nothing to handle |         if (should_handle != 0) { | ||||||
|         if (!self.bus.io.ime or self.cpsr.i.read() or should_handle == 0) return; |             self.bus.io.haltcnt = .Execute; | ||||||
|  |             // log.debug("An Interrupt was Fired!", .{}); | ||||||
|  |  | ||||||
|         // If Pipeline isn't full, we have a bug |             // Either IME is not true or I in CPSR is true | ||||||
|         std.debug.assert(self.pipe.isFull()); |             // Don't handle interrupts | ||||||
|  |             if (!self.bus.io.ime or self.cpsr.i.read()) return; | ||||||
|  |             // log.debug("An interrupt was Handled!", .{}); | ||||||
|  |  | ||||||
|         // log.debug("Handling Interrupt!", .{}); |             // retAddr.gba says r15 on it's own is off by -04h in both ARM and THUMB mode | ||||||
|         self.bus.io.haltcnt = .Execute; |             const r15 = self.r[15] + 4; | ||||||
|  |             const cpsr = self.cpsr.raw; | ||||||
|  |  | ||||||
|         // FIXME: This seems weird, but retAddr.gba suggests I need to make these changes |             self.changeMode(.Irq); | ||||||
|         const ret_addr = self.r[15] - if (self.cpsr.t.read()) 0 else @as(u32, 4); |             self.cpsr.t.write(false); | ||||||
|         const new_spsr = self.cpsr.raw; |             self.cpsr.i.write(true); | ||||||
|  |  | ||||||
|         self.changeMode(.Irq); |             self.r[14] = r15; | ||||||
|         self.cpsr.t.write(false); |             self.spsr.raw = cpsr; | ||||||
|         self.cpsr.i.write(true); |             self.r[15] = 0x000_0018; | ||||||
|  |         } | ||||||
|         self.r[14] = ret_addr; |  | ||||||
|         self.spsr.raw = new_spsr; |  | ||||||
|         self.r[15] = 0x0000_0018; |  | ||||||
|         self.pipe.reload(self); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     inline fn fetch(self: *Self, comptime T: type, address: u32) T { |     inline fn fetch(self: *Self, comptime T: type) T { | ||||||
|         comptime std.debug.assert(T == u32 or T == u16); // Opcode may be 32-bit (ARM) or 16-bit (THUMB) |         comptime std.debug.assert(T == u32 or T == u16); // Opcode may be 32-bit (ARM) or 16-bit (THUMB) | ||||||
|  |         defer self.r[15] += if (T == u32) 4 else 2; | ||||||
|  |  | ||||||
|         // Bus.read will advance the scheduler. There are different timings for CPU fetches, |         // FIXME: You better hope this is optimized out | ||||||
|         // so we want to undo what Bus.read will apply. We can do this by caching the current tick |  | ||||||
|         // This is very dumb. |  | ||||||
|         // |  | ||||||
|         // FIXME: Please rework this |  | ||||||
|         const tick_cache = self.sched.tick; |         const tick_cache = self.sched.tick; | ||||||
|         defer self.sched.tick = tick_cache + Bus.fetch_timings[@boolToInt(T == u32)][@truncate(u4, address >> 24)]; |         defer self.sched.tick = tick_cache + Bus.fetch_timings[@boolToInt(T == u32)][@truncate(u4, self.r[15] >> 24)]; | ||||||
|  |  | ||||||
|         return self.bus.read(T, address); |         return self.bus.read(T, self.r[15]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn fakePC(self: *const Self) u32 { | ||||||
|  |         return self.r[15] + 4; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn panic(self: *const Self, comptime format: []const u8, args: anytype) noreturn { |     pub fn panic(self: *const Self, comptime format: []const u8, args: anytype) noreturn { | ||||||
| @@ -539,8 +525,6 @@ pub const Arm7tdmi = struct { | |||||||
|         std.debug.print("spsr: 0x{X:0>8} ", .{self.spsr.raw}); |         std.debug.print("spsr: 0x{X:0>8} ", .{self.spsr.raw}); | ||||||
|         prettyPrintPsr(&self.spsr); |         prettyPrintPsr(&self.spsr); | ||||||
|  |  | ||||||
|         std.debug.print("pipeline: {??X:0>8}\n", .{self.pipe.stage}); |  | ||||||
|  |  | ||||||
|         if (self.cpsr.t.read()) { |         if (self.cpsr.t.read()) { | ||||||
|             const opcode = self.bus.dbgRead(u16, self.r[15] - 4); |             const opcode = self.bus.dbgRead(u16, self.r[15] - 4); | ||||||
|             const id = thumb.idx(opcode); |             const id = thumb.idx(opcode); | ||||||
| @@ -604,7 +588,7 @@ pub const Arm7tdmi = struct { | |||||||
|         const r12 = self.r[12]; |         const r12 = self.r[12]; | ||||||
|         const r13 = self.r[13]; |         const r13 = self.r[13]; | ||||||
|         const r14 = self.r[14]; |         const r14 = self.r[14]; | ||||||
|         const r15 = self.r[15] -| if (self.cpsr.t.read()) 2 else @as(u32, 4); |         const r15 = self.r[15]; | ||||||
|  |  | ||||||
|         const c_psr = self.cpsr.raw; |         const c_psr = self.cpsr.raw; | ||||||
|  |  | ||||||
| @@ -612,7 +596,7 @@ pub const Arm7tdmi = struct { | |||||||
|         if (self.cpsr.t.read()) { |         if (self.cpsr.t.read()) { | ||||||
|             if (opcode >> 11 == 0x1E) { |             if (opcode >> 11 == 0x1E) { | ||||||
|                 // Instruction 1 of a BL Opcode, print in ARM mode |                 // Instruction 1 of a BL Opcode, print in ARM mode | ||||||
|                 const other_half = self.bus.debugRead(u16, self.r[15] - 2); |                 const other_half = self.bus.dbgRead(u16, self.r[15]); | ||||||
|                 const bl_opcode = @as(u32, opcode) << 16 | other_half; |                 const bl_opcode = @as(u32, opcode) << 16 | other_half; | ||||||
|  |  | ||||||
|                 log_str = try std.fmt.bufPrint(&buf, arm_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, bl_opcode }); |                 log_str = try std.fmt.bufPrint(&buf, arm_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, bl_opcode }); | ||||||
| @@ -648,49 +632,6 @@ pub fn checkCond(cpsr: PSR, cond: u4) bool { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| const Pipeline = struct { |  | ||||||
|     const Self = @This(); |  | ||||||
|     stage: [2]?u32, |  | ||||||
|     flushed: bool, |  | ||||||
|  |  | ||||||
|     fn init() Self { |  | ||||||
|         return .{ |  | ||||||
|             .stage = [_]?u32{null} ** 2, |  | ||||||
|             .flushed = false, |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn isFull(self: *const Self) bool { |  | ||||||
|         return self.stage[0] != null and self.stage[1] != null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn step(self: *Self, cpu: *Arm7tdmi, comptime T: type) ?u32 { |  | ||||||
|         comptime std.debug.assert(T == u32 or T == u16); |  | ||||||
|  |  | ||||||
|         // FIXME: https://github.com/ziglang/zig/issues/12642 |  | ||||||
|         var opcode = self.stage[0]; |  | ||||||
|  |  | ||||||
|         self.stage[0] = self.stage[1]; |  | ||||||
|         self.stage[1] = cpu.fetch(T, cpu.r[15]); |  | ||||||
|  |  | ||||||
|         return opcode; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn reload(self: *Self, cpu: *Arm7tdmi) void { |  | ||||||
|         if (cpu.cpsr.t.read()) { |  | ||||||
|             self.stage[0] = cpu.fetch(u16, cpu.r[15]); |  | ||||||
|             self.stage[1] = cpu.fetch(u16, cpu.r[15] + 2); |  | ||||||
|             cpu.r[15] += 4; |  | ||||||
|         } else { |  | ||||||
|             self.stage[0] = cpu.fetch(u32, cpu.r[15]); |  | ||||||
|             self.stage[1] = cpu.fetch(u32, cpu.r[15] + 4); |  | ||||||
|             cpu.r[15] += 8; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         self.flushed = true; |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| pub const PSR = extern union { | pub const PSR = extern union { | ||||||
|     mode: Bitfield(u32, 0, 5), |     mode: Bitfield(u32, 0, 5), | ||||||
|     t: Bit(u32, 5), |     t: Bit(u32, 5), | ||||||
|   | |||||||
| @@ -55,10 +55,8 @@ pub fn blockDataTransfer(comptime P: bool, comptime U: bool, comptime S: bool, c | |||||||
|  |  | ||||||
|                 if (L) { |                 if (L) { | ||||||
|                     cpu.r[15] = bus.read(u32, und_addr); |                     cpu.r[15] = bus.read(u32, und_addr); | ||||||
|                     cpu.pipe.reload(cpu); |  | ||||||
|                 } else { |                 } else { | ||||||
|                     // FIXME: Should r15 on write be +12 ahead? |                     bus.write(u32, und_addr, cpu.r[15] + 8); | ||||||
|                     bus.write(u32, und_addr, cpu.r[15] + 4); |  | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 cpu.r[rn] = if (U) cpu.r[rn] + 0x40 else cpu.r[rn] - 0x40; |                 cpu.r[rn] = if (U) cpu.r[rn] + 0x40 else cpu.r[rn] - 0x40; | ||||||
| @@ -88,23 +86,17 @@ pub fn blockDataTransfer(comptime P: bool, comptime U: bool, comptime S: bool, c | |||||||
|                     cpu.setUserModeRegister(i, bus.read(u32, address)); |                     cpu.setUserModeRegister(i, bus.read(u32, address)); | ||||||
|                 } else { |                 } else { | ||||||
|                     const value = bus.read(u32, address); |                     const value = bus.read(u32, address); | ||||||
|  |                     cpu.r[i] = if (i == 0xF) value & 0xFFFF_FFFC else value; | ||||||
|                     cpu.r[i] = value; |                     if (S and i == 0xF) cpu.setCpsr(cpu.spsr.raw); | ||||||
|                     if (i == 0xF) { |  | ||||||
|                         cpu.r[i] &= ~@as(u32, 3); // Align r15 |  | ||||||
|                         cpu.pipe.reload(cpu); |  | ||||||
|  |  | ||||||
|                         if (S) cpu.setCpsr(cpu.spsr.raw); |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 if (S) { |                 if (S) { | ||||||
|                     // Always Transfer User mode Registers |                     // Always Transfer User mode Registers | ||||||
|                     // This happens regardless if r15 is in the list |                     // This happens regardless if r15 is in the list | ||||||
|                     const value = cpu.getUserModeRegister(i); |                     const value = cpu.getUserModeRegister(i); | ||||||
|                     bus.write(u32, address, value + if (i == 0xF) 4 else @as(u32, 0)); // PC is already 8 ahead to make 12 |                     bus.write(u32, address, value + if (i == 0xF) 8 else @as(u32, 0)); // PC is already 4 ahead to make 12 | ||||||
|                 } else { |                 } else { | ||||||
|                     bus.write(u32, address, cpu.r[i] + if (i == 0xF) 4 else @as(u32, 0)); |                     bus.write(u32, address, cpu.r[i] + if (i == 0xF) 8 else @as(u32, 0)); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -9,20 +9,14 @@ const sext = @import("../../../util.zig").sext; | |||||||
| pub fn branch(comptime L: bool) InstrFn { | pub fn branch(comptime L: bool) InstrFn { | ||||||
|     return struct { |     return struct { | ||||||
|         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void { |         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void { | ||||||
|             if (L) cpu.r[14] = cpu.r[15] - 4; |             if (L) cpu.r[14] = cpu.r[15]; | ||||||
|  |             cpu.r[15] = cpu.fakePC() +% (sext(u32, u24, opcode) << 2); | ||||||
|             cpu.r[15] +%= sext(u32, u24, opcode) << 2; |  | ||||||
|             cpu.pipe.reload(cpu); |  | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn branchAndExchange(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void { | pub fn branchAndExchange(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void { | ||||||
|     const rn = opcode & 0xF; |     const rn = opcode & 0xF; | ||||||
|  |     cpu.cpsr.t.write(cpu.r[rn] & 1 == 1); | ||||||
|     const thumb = cpu.r[rn] & 1 == 1; |     cpu.r[15] = cpu.r[rn] & 0xFFFF_FFFE; | ||||||
|     cpu.r[15] = cpu.r[rn] & if (thumb) ~@as(u32, 1) else ~@as(u32, 3); |  | ||||||
|  |  | ||||||
|     cpu.cpsr.t.write(thumb); |  | ||||||
|     cpu.pipe.reload(cpu); |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,10 +2,10 @@ const Bus = @import("../../Bus.zig"); | |||||||
| const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; | const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; | ||||||
| const InstrFn = @import("../../cpu.zig").arm.InstrFn; | const InstrFn = @import("../../cpu.zig").arm.InstrFn; | ||||||
|  |  | ||||||
| const exec = @import("../barrel_shifter.zig").exec; | const rotateRight = @import("../barrel_shifter.zig").rotateRight; | ||||||
| const ror = @import("../barrel_shifter.zig").ror; | const execute = @import("../barrel_shifter.zig").execute; | ||||||
|  |  | ||||||
| pub fn dataProcessing(comptime I: bool, comptime S: bool, comptime kind: u4) InstrFn { | pub fn dataProcessing(comptime I: bool, comptime S: bool, comptime instrKind: u4) InstrFn { | ||||||
|     return struct { |     return struct { | ||||||
|         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void { |         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void { | ||||||
|             const rd = @truncate(u4, opcode >> 12 & 0xF); |             const rd = @truncate(u4, opcode >> 12 & 0xF); | ||||||
| @@ -13,168 +13,269 @@ pub fn dataProcessing(comptime I: bool, comptime S: bool, comptime kind: u4) Ins | |||||||
|             const old_carry = @boolToInt(cpu.cpsr.c.read()); |             const old_carry = @boolToInt(cpu.cpsr.c.read()); | ||||||
|  |  | ||||||
|             // If certain conditions are met, PC is 12 ahead instead of 8 |             // If certain conditions are met, PC is 12 ahead instead of 8 | ||||||
|             // TODO: Why these conditions? |  | ||||||
|             if (!I and opcode >> 4 & 1 == 1) cpu.r[15] += 4; |             if (!I and opcode >> 4 & 1 == 1) cpu.r[15] += 4; | ||||||
|             const op1 = cpu.r[rn]; |  | ||||||
|  |  | ||||||
|             const amount = @truncate(u8, (opcode >> 8 & 0xF) << 1); |             const op1 = if (rn == 0xF) cpu.fakePC() else cpu.r[rn]; | ||||||
|             const op2 = if (I) ror(S, &cpu.cpsr, opcode & 0xFF, amount) else exec(S, cpu, opcode); |  | ||||||
|  |             var op2: u32 = undefined; | ||||||
|  |             if (I) { | ||||||
|  |                 const amount = @truncate(u8, (opcode >> 8 & 0xF) << 1); | ||||||
|  |                 op2 = rotateRight(S, &cpu.cpsr, opcode & 0xFF, amount); | ||||||
|  |             } else { | ||||||
|  |                 op2 = execute(S, cpu, opcode); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             // Undo special condition from above |             // Undo special condition from above | ||||||
|             if (!I and opcode >> 4 & 1 == 1) cpu.r[15] -= 4; |             if (!I and opcode >> 4 & 1 == 1) cpu.r[15] -= 4; | ||||||
|  |  | ||||||
|             var result: u32 = undefined; |             switch (instrKind) { | ||||||
|             var overflow: bool = undefined; |                 0x0 => { | ||||||
|  |                     // AND | ||||||
|             // Perform Data Processing Logic |                     const result = op1 & op2; | ||||||
|             switch (kind) { |                     cpu.r[rd] = result; | ||||||
|                 0x0 => result = op1 & op2, // AND |                     setArmLogicOpFlags(S, cpu, rd, result); | ||||||
|                 0x1 => result = op1 ^ op2, // EOR |                 }, | ||||||
|                 0x2 => result = op1 -% op2, // SUB |                 0x1 => { | ||||||
|                 0x3 => result = op2 -% op1, // RSB |                     // EOR | ||||||
|                 0x4 => result = add(&overflow, op1, op2), // ADD |                     const result = op1 ^ op2; | ||||||
|                 0x5 => result = adc(&overflow, op1, op2, old_carry), // ADC |                     cpu.r[rd] = result; | ||||||
|                 0x6 => result = sbc(op1, op2, old_carry), // SBC |                     setArmLogicOpFlags(S, cpu, rd, result); | ||||||
|                 0x7 => result = sbc(op2, op1, old_carry), // RSC |                 }, | ||||||
|  |                 0x2 => { | ||||||
|  |                     // SUB | ||||||
|  |                     cpu.r[rd] = armSub(S, cpu, rd, op1, op2); | ||||||
|  |                 }, | ||||||
|  |                 0x3 => { | ||||||
|  |                     // RSB | ||||||
|  |                     cpu.r[rd] = armSub(S, cpu, rd, op2, op1); | ||||||
|  |                 }, | ||||||
|  |                 0x4 => { | ||||||
|  |                     // ADD | ||||||
|  |                     cpu.r[rd] = armAdd(S, cpu, rd, op1, op2); | ||||||
|  |                 }, | ||||||
|  |                 0x5 => { | ||||||
|  |                     // ADC | ||||||
|  |                     cpu.r[rd] = armAdc(S, cpu, rd, op1, op2, old_carry); | ||||||
|  |                 }, | ||||||
|  |                 0x6 => { | ||||||
|  |                     // SBC | ||||||
|  |                     cpu.r[rd] = armSbc(S, cpu, rd, op1, op2, old_carry); | ||||||
|  |                 }, | ||||||
|  |                 0x7 => { | ||||||
|  |                     // RSC | ||||||
|  |                     cpu.r[rd] = armSbc(S, cpu, rd, op2, op1, old_carry); | ||||||
|  |                 }, | ||||||
|                 0x8 => { |                 0x8 => { | ||||||
|                     // TST |                     // TST | ||||||
|                     if (rd == 0xF) |                     if (rd == 0xF) { | ||||||
|                         return undefinedTestBehaviour(cpu); |                         undefinedTestBehaviour(cpu); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     result = op1 & op2; |                     const result = op1 & op2; | ||||||
|  |                     setTestOpFlags(S, cpu, opcode, result); | ||||||
|                 }, |                 }, | ||||||
|                 0x9 => { |                 0x9 => { | ||||||
|                     // TEQ |                     // TEQ | ||||||
|                     if (rd == 0xF) |                     if (rd == 0xF) { | ||||||
|                         return undefinedTestBehaviour(cpu); |                         undefinedTestBehaviour(cpu); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     result = op1 ^ op2; |                     const result = op1 ^ op2; | ||||||
|  |                     setTestOpFlags(S, cpu, opcode, result); | ||||||
|                 }, |                 }, | ||||||
|                 0xA => { |                 0xA => { | ||||||
|                     // CMP |                     // CMP | ||||||
|                     if (rd == 0xF) |                     if (rd == 0xF) { | ||||||
|                         return undefinedTestBehaviour(cpu); |                         undefinedTestBehaviour(cpu); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     result = op1 -% op2; |                     cmp(cpu, op1, op2); | ||||||
|                 }, |                 }, | ||||||
|                 0xB => { |                 0xB => { | ||||||
|                     // CMN |                     // CMN | ||||||
|                     if (rd == 0xF) |  | ||||||
|                         return undefinedTestBehaviour(cpu); |  | ||||||
|  |  | ||||||
|                     overflow = @addWithOverflow(u32, op1, op2, &result); |  | ||||||
|                 }, |  | ||||||
|                 0xC => result = op1 | op2, // ORR |  | ||||||
|                 0xD => result = op2, // MOV |  | ||||||
|                 0xE => result = op1 & ~op2, // BIC |  | ||||||
|                 0xF => result = ~op2, // MVN |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Write to Destination Register |  | ||||||
|             switch (kind) { |  | ||||||
|                 0x8, 0x9, 0xA, 0xB => {}, // Test Operations |  | ||||||
|                 else => { |  | ||||||
|                     cpu.r[rd] = result; |  | ||||||
|                     if (rd == 0xF) { |                     if (rd == 0xF) { | ||||||
|                         if (S) cpu.setCpsr(cpu.spsr.raw); |                         undefinedTestBehaviour(cpu); | ||||||
|                         cpu.pipe.reload(cpu); |                         return; | ||||||
|                     } |                     } | ||||||
|                 }, |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Write Flags |  | ||||||
|             switch (kind) { |  | ||||||
|                 0x0, 0x1, 0xC, 0xD, 0xE, 0xF => if (S and rd != 0xF) { |  | ||||||
|                     // Logic Operation Flags |  | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |  | ||||||
|                     cpu.cpsr.z.write(result == 0); |  | ||||||
|                     // C set by Barrel Shifter, V is unaffected |  | ||||||
|  |  | ||||||
|  |                     cmn(cpu, op1, op2); | ||||||
|                 }, |                 }, | ||||||
|                 0x2, 0x3 => if (S and rd != 0xF) { |                 0xC => { | ||||||
|                     // SUB, RSB Flags |                     // ORR | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |                     const result = op1 | op2; | ||||||
|                     cpu.cpsr.z.write(result == 0); |                     cpu.r[rd] = result; | ||||||
|  |                     setArmLogicOpFlags(S, cpu, rd, result); | ||||||
|                     if (kind == 0x2) { |  | ||||||
|                         // SUB specific |  | ||||||
|                         cpu.cpsr.c.write(op2 <= op1); |  | ||||||
|                         cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); |  | ||||||
|                     } else { |  | ||||||
|                         // RSB Specific |  | ||||||
|                         cpu.cpsr.c.write(op1 <= op2); |  | ||||||
|                         cpu.cpsr.v.write(((op2 ^ result) & (~op1 ^ result)) >> 31 & 1 == 1); |  | ||||||
|                     } |  | ||||||
|                 }, |                 }, | ||||||
|                 0x4, 0x5 => if (S and rd != 0xF) { |                 0xD => { | ||||||
|                     // ADD, ADC Flags |                     // MOV | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |                     cpu.r[rd] = op2; | ||||||
|                     cpu.cpsr.z.write(result == 0); |                     setArmLogicOpFlags(S, cpu, rd, op2); | ||||||
|                     cpu.cpsr.c.write(overflow); |  | ||||||
|                     cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); |  | ||||||
|                 }, |                 }, | ||||||
|                 0x6, 0x7 => if (S and rd != 0xF) { |                 0xE => { | ||||||
|                     // SBC, RSC Flags |                     // BIC | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |                     const result = op1 & ~op2; | ||||||
|                     cpu.cpsr.z.write(result == 0); |                     cpu.r[rd] = result; | ||||||
|  |                     setArmLogicOpFlags(S, cpu, rd, result); | ||||||
|                     if (kind == 0x6) { |  | ||||||
|                         // SBC specific |  | ||||||
|                         const subtrahend = @as(u64, op2) -% old_carry +% 1; |  | ||||||
|                         cpu.cpsr.c.write(subtrahend <= op1); |  | ||||||
|                         cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); |  | ||||||
|                     } else { |  | ||||||
|                         // RSC Specific |  | ||||||
|                         const subtrahend = @as(u64, op1) -% old_carry +% 1; |  | ||||||
|                         cpu.cpsr.c.write(subtrahend <= op2); |  | ||||||
|                         cpu.cpsr.v.write(((op2 ^ result) & (~op1 ^ result)) >> 31 & 1 == 1); |  | ||||||
|                     } |  | ||||||
|                 }, |                 }, | ||||||
|                 0x8, 0x9, 0xA, 0xB => { |                 0xF => { | ||||||
|                     // Test Operation Flags |                     // MVN | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |                     const result = ~op2; | ||||||
|                     cpu.cpsr.z.write(result == 0); |                     cpu.r[rd] = result; | ||||||
|  |                     setArmLogicOpFlags(S, cpu, rd, result); | ||||||
|                     if (kind == 0xA) { |  | ||||||
|                         // CMP specific |  | ||||||
|                         cpu.cpsr.c.write(op2 <= op1); |  | ||||||
|                         cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); |  | ||||||
|                     } else if (kind == 0xB) { |  | ||||||
|                         // CMN specific |  | ||||||
|                         cpu.cpsr.c.write(overflow); |  | ||||||
|                         cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); |  | ||||||
|                     } else { |  | ||||||
|                         // TST, TEQ specific |  | ||||||
|                         // Barrel Shifter should always calc CPSR C in TST |  | ||||||
|                         if (!S) _ = exec(true, cpu, opcode); |  | ||||||
|                     } |  | ||||||
|                 }, |                 }, | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn sbc(left: u32, right: u32, old_carry: u1) u32 { | fn armSbc(comptime S: bool, cpu: *Arm7tdmi, rd: u4, left: u32, right: u32, old_carry: u1) u32 { | ||||||
|  |     var result: u32 = undefined; | ||||||
|  |     if (S and rd == 0xF) { | ||||||
|  |         result = sbc(false, cpu, left, right, old_carry); | ||||||
|  |         cpu.setCpsr(cpu.spsr.raw); | ||||||
|  |     } else { | ||||||
|  |         result = sbc(S, cpu, left, right, old_carry); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn sbc(comptime S: bool, cpu: *Arm7tdmi, left: u32, right: u32, old_carry: u1) u32 { | ||||||
|     // TODO: Make your own version (thanks peach.bot) |     // TODO: Make your own version (thanks peach.bot) | ||||||
|     const subtrahend = @as(u64, right) -% old_carry +% 1; |     const subtrahend = @as(u64, right) -% old_carry +% 1; | ||||||
|     const ret = @truncate(u32, left -% subtrahend); |     const result = @truncate(u32, left -% subtrahend); | ||||||
|  |  | ||||||
|     return ret; |     if (S) { | ||||||
|  |         cpu.cpsr.n.write(result >> 31 & 1 == 1); | ||||||
|  |         cpu.cpsr.z.write(result == 0); | ||||||
|  |         cpu.cpsr.c.write(subtrahend <= left); | ||||||
|  |         cpu.cpsr.v.write(((left ^ result) & (~right ^ result)) >> 31 & 1 == 1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn add(overflow: *bool, left: u32, right: u32) u32 { | fn armSub(comptime S: bool, cpu: *Arm7tdmi, rd: u4, left: u32, right: u32) u32 { | ||||||
|     var ret: u32 = undefined; |     var result: u32 = undefined; | ||||||
|     overflow.* = @addWithOverflow(u32, left, right, &ret); |     if (S and rd == 0xF) { | ||||||
|     return ret; |         result = sub(false, cpu, left, right); | ||||||
|  |         cpu.setCpsr(cpu.spsr.raw); | ||||||
|  |     } else { | ||||||
|  |         result = sub(S, cpu, left, right); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn adc(overflow: *bool, left: u32, right: u32, old_carry: u1) u32 { | pub fn sub(comptime S: bool, cpu: *Arm7tdmi, left: u32, right: u32) u32 { | ||||||
|     var ret: u32 = undefined; |     const result = left -% right; | ||||||
|     const first = @addWithOverflow(u32, left, right, &ret); |  | ||||||
|     const second = @addWithOverflow(u32, ret, old_carry, &ret); |  | ||||||
|  |  | ||||||
|     overflow.* = first or second; |     if (S) { | ||||||
|     return ret; |         cpu.cpsr.n.write(result >> 31 & 1 == 1); | ||||||
|  |         cpu.cpsr.z.write(result == 0); | ||||||
|  |         cpu.cpsr.c.write(right <= left); | ||||||
|  |         cpu.cpsr.v.write(((left ^ result) & (~right ^ result)) >> 31 & 1 == 1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn armAdd(comptime S: bool, cpu: *Arm7tdmi, rd: u4, left: u32, right: u32) u32 { | ||||||
|  |     var result: u32 = undefined; | ||||||
|  |     if (S and rd == 0xF) { | ||||||
|  |         result = add(false, cpu, left, right); | ||||||
|  |         cpu.setCpsr(cpu.spsr.raw); | ||||||
|  |     } else { | ||||||
|  |         result = add(S, cpu, left, right); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn add(comptime S: bool, cpu: *Arm7tdmi, left: u32, right: u32) u32 { | ||||||
|  |     var result: u32 = undefined; | ||||||
|  |     const didOverflow = @addWithOverflow(u32, left, right, &result); | ||||||
|  |  | ||||||
|  |     if (S) { | ||||||
|  |         cpu.cpsr.n.write(result >> 31 & 1 == 1); | ||||||
|  |         cpu.cpsr.z.write(result == 0); | ||||||
|  |         cpu.cpsr.c.write(didOverflow); | ||||||
|  |         cpu.cpsr.v.write(((left ^ result) & (right ^ result)) >> 31 & 1 == 1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn armAdc(comptime S: bool, cpu: *Arm7tdmi, rd: u4, left: u32, right: u32, old_carry: u1) u32 { | ||||||
|  |     var result: u32 = undefined; | ||||||
|  |     if (S and rd == 0xF) { | ||||||
|  |         result = adc(false, cpu, left, right, old_carry); | ||||||
|  |         cpu.setCpsr(cpu.spsr.raw); | ||||||
|  |     } else { | ||||||
|  |         result = adc(S, cpu, left, right, old_carry); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn adc(comptime S: bool, cpu: *Arm7tdmi, left: u32, right: u32, old_carry: u1) u32 { | ||||||
|  |     var result: u32 = undefined; | ||||||
|  |     const did = @addWithOverflow(u32, left, right, &result); | ||||||
|  |     const overflow = @addWithOverflow(u32, result, old_carry, &result); | ||||||
|  |  | ||||||
|  |     if (S) { | ||||||
|  |         cpu.cpsr.n.write(result >> 31 & 1 == 1); | ||||||
|  |         cpu.cpsr.z.write(result == 0); | ||||||
|  |         cpu.cpsr.c.write(did or overflow); | ||||||
|  |         cpu.cpsr.v.write(((left ^ result) & (right ^ result)) >> 31 & 1 == 1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn cmp(cpu: *Arm7tdmi, left: u32, right: u32) void { | ||||||
|  |     const result = left -% right; | ||||||
|  |  | ||||||
|  |     cpu.cpsr.n.write(result >> 31 & 1 == 1); | ||||||
|  |     cpu.cpsr.z.write(result == 0); | ||||||
|  |     cpu.cpsr.c.write(right <= left); | ||||||
|  |     cpu.cpsr.v.write(((left ^ result) & (~right ^ result)) >> 31 & 1 == 1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn cmn(cpu: *Arm7tdmi, left: u32, right: u32) void { | ||||||
|  |     var result: u32 = undefined; | ||||||
|  |     const didOverflow = @addWithOverflow(u32, left, right, &result); | ||||||
|  |  | ||||||
|  |     cpu.cpsr.n.write(result >> 31 & 1 == 1); | ||||||
|  |     cpu.cpsr.z.write(result == 0); | ||||||
|  |     cpu.cpsr.c.write(didOverflow); | ||||||
|  |     cpu.cpsr.v.write(((left ^ result) & (right ^ result)) >> 31 & 1 == 1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn setArmLogicOpFlags(comptime S: bool, cpu: *Arm7tdmi, rd: u4, result: u32) void { | ||||||
|  |     if (S and rd == 0xF) { | ||||||
|  |         cpu.setCpsr(cpu.spsr.raw); | ||||||
|  |     } else { | ||||||
|  |         setLogicOpFlags(S, cpu, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn setLogicOpFlags(comptime S: bool, cpu: *Arm7tdmi, result: u32) void { | ||||||
|  |     if (S) { | ||||||
|  |         cpu.cpsr.n.write(result >> 31 & 1 == 1); | ||||||
|  |         cpu.cpsr.z.write(result == 0); | ||||||
|  |         // C set by Barrel Shifter, V is unaffected | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn setTestOpFlags(comptime S: bool, cpu: *Arm7tdmi, opcode: u32, result: u32) void { | ||||||
|  |     cpu.cpsr.n.write(result >> 31 & 1 == 1); | ||||||
|  |     cpu.cpsr.z.write(result == 0); | ||||||
|  |     // Barrel Shifter should always calc CPSR C in TST | ||||||
|  |     if (!S) _ = execute(true, cpu, opcode); | ||||||
| } | } | ||||||
|  |  | ||||||
| fn undefinedTestBehaviour(cpu: *Arm7tdmi) void { | fn undefinedTestBehaviour(cpu: *Arm7tdmi) void { | ||||||
|   | |||||||
| @@ -15,8 +15,20 @@ pub fn halfAndSignedDataTransfer(comptime P: bool, comptime U: bool, comptime I: | |||||||
|             const rm = opcode & 0xF; |             const rm = opcode & 0xF; | ||||||
|             const imm_offset_high = opcode >> 8 & 0xF; |             const imm_offset_high = opcode >> 8 & 0xF; | ||||||
|  |  | ||||||
|             const base = cpu.r[rn] + if (!L and rn == 0xF) 4 else @as(u32, 0); |             var base: u32 = undefined; | ||||||
|             const offset = if (I) imm_offset_high << 4 | rm else cpu.r[rm]; |             if (rn == 0xF) { | ||||||
|  |                 base = cpu.fakePC(); | ||||||
|  |                 if (!L) base += 4; | ||||||
|  |             } else { | ||||||
|  |                 base = cpu.r[rn]; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var offset: u32 = undefined; | ||||||
|  |             if (I) { | ||||||
|  |                 offset = imm_offset_high << 4 | rm; | ||||||
|  |             } else { | ||||||
|  |                 offset = cpu.r[rm]; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             const modified_base = if (U) base +% offset else base -% offset; |             const modified_base = if (U) base +% offset else base -% offset; | ||||||
|             var address = if (P) modified_base else base; |             var address = if (P) modified_base else base; | ||||||
|   | |||||||
| @@ -14,10 +14,15 @@ pub fn singleDataTransfer(comptime I: bool, comptime P: bool, comptime U: bool, | |||||||
|             const rn = opcode >> 16 & 0xF; |             const rn = opcode >> 16 & 0xF; | ||||||
|             const rd = opcode >> 12 & 0xF; |             const rd = opcode >> 12 & 0xF; | ||||||
|  |  | ||||||
|             // rn is r15 and L is not set, the PC is 12 ahead |             var base: u32 = undefined; | ||||||
|             const base = cpu.r[rn] + if (!L and rn == 0xF) 4 else @as(u32, 0); |             if (rn == 0xF) { | ||||||
|  |                 base = cpu.fakePC(); | ||||||
|  |                 if (!L) base += 4; // Offset of 12 | ||||||
|  |             } else { | ||||||
|  |                 base = cpu.r[rn]; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             const offset = if (I) shifter.immediate(false, cpu, opcode) else opcode & 0xFFF; |             const offset = if (I) shifter.immShift(false, cpu, opcode) else opcode & 0xFFF; | ||||||
|  |  | ||||||
|             const modified_base = if (U) base +% offset else base -% offset; |             const modified_base = if (U) base +% offset else base -% offset; | ||||||
|             var address = if (P) modified_base else base; |             var address = if (P) modified_base else base; | ||||||
| @@ -35,26 +40,18 @@ pub fn singleDataTransfer(comptime I: bool, comptime P: bool, comptime U: bool, | |||||||
|             } else { |             } else { | ||||||
|                 if (B) { |                 if (B) { | ||||||
|                     // STRB |                     // STRB | ||||||
|                     const value = cpu.r[rd] + if (rd == 0xF) 4 else @as(u32, 0); // PC is 12 ahead |                     const value = if (rd == 0xF) cpu.r[rd] + 8 else cpu.r[rd]; | ||||||
|                     bus.write(u8, address, @truncate(u8, value)); |                     bus.write(u8, address, @truncate(u8, value)); | ||||||
|                 } else { |                 } else { | ||||||
|                     // STR |                     // STR | ||||||
|                     const value = cpu.r[rd] + if (rd == 0xF) 4 else @as(u32, 0); |                     const value = if (rd == 0xF) cpu.r[rd] + 8 else cpu.r[rd]; | ||||||
|                     bus.write(u32, address, value); |                     bus.write(u32, address, value); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             address = modified_base; |             address = modified_base; | ||||||
|             if (W and P or !P) { |             if (W and P or !P) cpu.r[rn] = address; | ||||||
|                 cpu.r[rn] = address; |             if (L) cpu.r[rd] = result; // This emulates the LDR rd == rn behaviour | ||||||
|                 if (rn == 0xF) cpu.pipe.reload(cpu); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (L) { |  | ||||||
|                 // This emulates the LDR rd == rn behaviour |  | ||||||
|                 cpu.r[rd] = result; |  | ||||||
|                 if (rd == 0xF) cpu.pipe.reload(cpu); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ pub fn armSoftwareInterrupt() InstrFn { | |||||||
|     return struct { |     return struct { | ||||||
|         fn inner(cpu: *Arm7tdmi, _: *Bus, _: u32) void { |         fn inner(cpu: *Arm7tdmi, _: *Bus, _: u32) void { | ||||||
|             // Copy Values from Current Mode |             // Copy Values from Current Mode | ||||||
|             const ret_addr = cpu.r[15] - 4; |             const r15 = cpu.r[15]; | ||||||
|             const cpsr = cpu.cpsr.raw; |             const cpsr = cpu.cpsr.raw; | ||||||
|  |  | ||||||
|             // Switch Mode |             // Switch Mode | ||||||
| @@ -14,10 +14,9 @@ pub fn armSoftwareInterrupt() InstrFn { | |||||||
|             cpu.cpsr.t.write(false); // Force ARM Mode |             cpu.cpsr.t.write(false); // Force ARM Mode | ||||||
|             cpu.cpsr.i.write(true); // Disable normal interrupts |             cpu.cpsr.i.write(true); // Disable normal interrupts | ||||||
|  |  | ||||||
|             cpu.r[14] = ret_addr; // Resume Execution |             cpu.r[14] = r15; // Resume Execution | ||||||
|             cpu.spsr.raw = cpsr; // Previous mode CPSR |             cpu.spsr.raw = cpsr; // Previous mode CPSR | ||||||
|             cpu.r[15] = 0x0000_0008; |             cpu.r[15] = 0x0000_0008; | ||||||
|             cpu.pipe.reload(cpu); |  | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,33 +5,37 @@ const CPSR = @import("../cpu.zig").PSR; | |||||||
|  |  | ||||||
| const rotr = @import("../../util.zig").rotr; | const rotr = @import("../../util.zig").rotr; | ||||||
|  |  | ||||||
| pub fn exec(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 { | pub fn execute(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 { | ||||||
|     var result: u32 = undefined; |     var result: u32 = undefined; | ||||||
|     if (opcode >> 4 & 1 == 1) { |     if (opcode >> 4 & 1 == 1) { | ||||||
|         result = register(S, cpu, opcode); |         result = registerShift(S, cpu, opcode); | ||||||
|     } else { |     } else { | ||||||
|         result = immediate(S, cpu, opcode); |         result = immShift(S, cpu, opcode); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| fn register(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 { | fn registerShift(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 { | ||||||
|     const rs_idx = opcode >> 8 & 0xF; |     const rs_idx = opcode >> 8 & 0xF; | ||||||
|     const rm = cpu.r[opcode & 0xF]; |  | ||||||
|     const rs = @truncate(u8, cpu.r[rs_idx]); |     const rs = @truncate(u8, cpu.r[rs_idx]); | ||||||
|  |  | ||||||
|  |     const rm_idx = opcode & 0xF; | ||||||
|  |     const rm = if (rm_idx == 0xF) cpu.fakePC() else cpu.r[rm_idx]; | ||||||
|  |  | ||||||
|     return switch (@truncate(u2, opcode >> 5)) { |     return switch (@truncate(u2, opcode >> 5)) { | ||||||
|         0b00 => lsl(S, &cpu.cpsr, rm, rs), |         0b00 => logicalLeft(S, &cpu.cpsr, rm, rs), | ||||||
|         0b01 => lsr(S, &cpu.cpsr, rm, rs), |         0b01 => logicalRight(S, &cpu.cpsr, rm, rs), | ||||||
|         0b10 => asr(S, &cpu.cpsr, rm, rs), |         0b10 => arithmeticRight(S, &cpu.cpsr, rm, rs), | ||||||
|         0b11 => ror(S, &cpu.cpsr, rm, rs), |         0b11 => rotateRight(S, &cpu.cpsr, rm, rs), | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn immediate(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 { | pub fn immShift(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 { | ||||||
|     const amount = @truncate(u8, opcode >> 7 & 0x1F); |     const amount = @truncate(u8, opcode >> 7 & 0x1F); | ||||||
|     const rm = cpu.r[opcode & 0xF]; |  | ||||||
|  |     const rm_idx = opcode & 0xF; | ||||||
|  |     const rm = if (rm_idx == 0xF) cpu.fakePC() else cpu.r[rm_idx]; | ||||||
|  |  | ||||||
|     var result: u32 = undefined; |     var result: u32 = undefined; | ||||||
|     if (amount == 0) { |     if (amount == 0) { | ||||||
| @@ -60,17 +64,17 @@ pub fn immediate(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 { | |||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         switch (@truncate(u2, opcode >> 5)) { |         switch (@truncate(u2, opcode >> 5)) { | ||||||
|             0b00 => result = lsl(S, &cpu.cpsr, rm, amount), |             0b00 => result = logicalLeft(S, &cpu.cpsr, rm, amount), | ||||||
|             0b01 => result = lsr(S, &cpu.cpsr, rm, amount), |             0b01 => result = logicalRight(S, &cpu.cpsr, rm, amount), | ||||||
|             0b10 => result = asr(S, &cpu.cpsr, rm, amount), |             0b10 => result = arithmeticRight(S, &cpu.cpsr, rm, amount), | ||||||
|             0b11 => result = ror(S, &cpu.cpsr, rm, amount), |             0b11 => result = rotateRight(S, &cpu.cpsr, rm, amount), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn lsl(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { | pub fn logicalLeft(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { | ||||||
|     const amount = @truncate(u5, total_amount); |     const amount = @truncate(u5, total_amount); | ||||||
|     const bit_count: u8 = @typeInfo(u32).Int.bits; |     const bit_count: u8 = @typeInfo(u32).Int.bits; | ||||||
|  |  | ||||||
| @@ -97,7 +101,7 @@ pub fn lsl(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn lsr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u32) u32 { | pub fn logicalRight(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u32) u32 { | ||||||
|     const amount = @truncate(u5, total_amount); |     const amount = @truncate(u5, total_amount); | ||||||
|     const bit_count: u8 = @typeInfo(u32).Int.bits; |     const bit_count: u8 = @typeInfo(u32).Int.bits; | ||||||
|  |  | ||||||
| @@ -121,7 +125,7 @@ pub fn lsr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u32) u32 { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn asr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { | pub fn arithmeticRight(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { | ||||||
|     const amount = @truncate(u5, total_amount); |     const amount = @truncate(u5, total_amount); | ||||||
|     const bit_count: u8 = @typeInfo(u32).Int.bits; |     const bit_count: u8 = @typeInfo(u32).Int.bits; | ||||||
|  |  | ||||||
| @@ -138,7 +142,7 @@ pub fn asr(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn ror(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { | pub fn rotateRight(comptime S: bool, cpsr: *CPSR, rm: u32, total_amount: u8) u32 { | ||||||
|     const result = rotr(u32, rm, total_amount); |     const result = rotr(u32, rm, total_amount); | ||||||
|  |  | ||||||
|     if (S and total_amount != 0) { |     if (S and total_amount != 0) { | ||||||
|   | |||||||
| @@ -4,11 +4,16 @@ const InstrFn = @import("../../cpu.zig").thumb.InstrFn; | |||||||
|  |  | ||||||
| const adc = @import("../arm/data_processing.zig").adc; | const adc = @import("../arm/data_processing.zig").adc; | ||||||
| const sbc = @import("../arm/data_processing.zig").sbc; | const sbc = @import("../arm/data_processing.zig").sbc; | ||||||
|  | const sub = @import("../arm/data_processing.zig").sub; | ||||||
|  | const cmp = @import("../arm/data_processing.zig").cmp; | ||||||
|  | const cmn = @import("../arm/data_processing.zig").cmn; | ||||||
|  | const setTestOpFlags = @import("../arm/data_processing.zig").setTestOpFlags; | ||||||
|  | const setLogicOpFlags = @import("../arm/data_processing.zig").setLogicOpFlags; | ||||||
|  |  | ||||||
| const lsl = @import("../barrel_shifter.zig").lsl; | const logicalLeft = @import("../barrel_shifter.zig").logicalLeft; | ||||||
| const lsr = @import("../barrel_shifter.zig").lsr; | const logicalRight = @import("../barrel_shifter.zig").logicalRight; | ||||||
| const asr = @import("../barrel_shifter.zig").asr; | const arithmeticRight = @import("../barrel_shifter.zig").arithmeticRight; | ||||||
| const ror = @import("../barrel_shifter.zig").ror; | const rotateRight = @import("../barrel_shifter.zig").rotateRight; | ||||||
|  |  | ||||||
| pub fn fmt4(comptime op: u4) InstrFn { | pub fn fmt4(comptime op: u4) InstrFn { | ||||||
|     return struct { |     return struct { | ||||||
| @@ -17,85 +22,96 @@ pub fn fmt4(comptime op: u4) InstrFn { | |||||||
|             const rd = opcode & 0x7; |             const rd = opcode & 0x7; | ||||||
|             const carry = @boolToInt(cpu.cpsr.c.read()); |             const carry = @boolToInt(cpu.cpsr.c.read()); | ||||||
|  |  | ||||||
|             const op1 = cpu.r[rd]; |  | ||||||
|             const op2 = cpu.r[rs]; |  | ||||||
|  |  | ||||||
|             var result: u32 = undefined; |  | ||||||
|             var overflow: bool = undefined; |  | ||||||
|             switch (op) { |             switch (op) { | ||||||
|                 0x0 => result = op1 & op2, // AND |                 0x0 => { | ||||||
|                 0x1 => result = op1 ^ op2, // EOR |                     // AND | ||||||
|                 0x2 => result = lsl(true, &cpu.cpsr, op1, @truncate(u8, op2)), // LSL |                     const result = cpu.r[rd] & cpu.r[rs]; | ||||||
|                 0x3 => result = lsr(true, &cpu.cpsr, op1, @truncate(u8, op2)), // LSR |                     cpu.r[rd] = result; | ||||||
|                 0x4 => result = asr(true, &cpu.cpsr, op1, @truncate(u8, op2)), // ASR |                     setLogicOpFlags(true, cpu, result); | ||||||
|                 0x5 => result = adc(&overflow, op1, op2, carry), // ADC |  | ||||||
|                 0x6 => result = sbc(op1, op2, carry), // SBC |  | ||||||
|                 0x7 => result = ror(true, &cpu.cpsr, op1, @truncate(u8, op2)), // ROR |  | ||||||
|                 0x8 => result = op1 & op2, // TST |  | ||||||
|                 0x9 => result = 0 -% op2, // NEG |  | ||||||
|                 0xA => result = op1 -% op2, // CMP |  | ||||||
|                 0xB => overflow = @addWithOverflow(u32, op1, op2, &result), // CMN |  | ||||||
|                 0xC => result = op1 | op2, // ORR |  | ||||||
|                 0xD => result = @truncate(u32, @as(u64, op2) * @as(u64, op1)), |  | ||||||
|                 0xE => result = op1 & ~op2, |  | ||||||
|                 0xF => result = ~op2, |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Write to Destination Register |  | ||||||
|             switch (op) { |  | ||||||
|                 0x8, 0xA, 0xB => {}, |  | ||||||
|                 else => cpu.r[rd] = result, |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Write Flags |  | ||||||
|             switch (op) { |  | ||||||
|                 0x0, 0x1, 0x2, 0x3, 0x4, 0x7, 0xC, 0xE, 0xF => { |  | ||||||
|                     // Logic Operations |  | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |  | ||||||
|                     cpu.cpsr.z.write(result == 0); |  | ||||||
|                     // C set by Barrel Shifter, V is unaffected |  | ||||||
|                 }, |                 }, | ||||||
|                 0x8, 0xA => { |                 0x1 => { | ||||||
|                     // Test Flags |                     // EOR | ||||||
|                     // CMN (0xB) is handled with ADC |                     const result = cpu.r[rd] ^ cpu.r[rs]; | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |                     cpu.r[rd] = result; | ||||||
|                     cpu.cpsr.z.write(result == 0); |                     setLogicOpFlags(true, cpu, result); | ||||||
|  |  | ||||||
|                     if (op == 0xA) { |  | ||||||
|                         // CMP specific |  | ||||||
|                         cpu.cpsr.c.write(op2 <= op1); |  | ||||||
|                         cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); |  | ||||||
|                     } |  | ||||||
|                 }, |                 }, | ||||||
|                 0x5, 0xB => { |                 0x2 => { | ||||||
|                     // ADC, CMN |                     // LSL | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |                     const result = logicalLeft(true, &cpu.cpsr, cpu.r[rd], @truncate(u8, cpu.r[rs])); | ||||||
|                     cpu.cpsr.z.write(result == 0); |                     cpu.r[rd] = result; | ||||||
|                     cpu.cpsr.c.write(overflow); |                     setLogicOpFlags(true, cpu, result); | ||||||
|                     cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); |                 }, | ||||||
|  |                 0x3 => { | ||||||
|  |                     // LSR | ||||||
|  |                     const result = logicalRight(true, &cpu.cpsr, cpu.r[rd], @truncate(u8, cpu.r[rs])); | ||||||
|  |                     cpu.r[rd] = result; | ||||||
|  |                     setLogicOpFlags(true, cpu, result); | ||||||
|  |                 }, | ||||||
|  |                 0x4 => { | ||||||
|  |                     // ASR | ||||||
|  |                     const result = arithmeticRight(true, &cpu.cpsr, cpu.r[rd], @truncate(u8, cpu.r[rs])); | ||||||
|  |                     cpu.r[rd] = result; | ||||||
|  |                     setLogicOpFlags(true, cpu, result); | ||||||
|  |                 }, | ||||||
|  |                 0x5 => { | ||||||
|  |                     // ADC | ||||||
|  |                     cpu.r[rd] = adc(true, cpu, cpu.r[rd], cpu.r[rs], carry); | ||||||
|                 }, |                 }, | ||||||
|                 0x6 => { |                 0x6 => { | ||||||
|                     // SBC |                     // SBC | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |                     cpu.r[rd] = sbc(true, cpu, cpu.r[rd], cpu.r[rs], carry); | ||||||
|                     cpu.cpsr.z.write(result == 0); |                 }, | ||||||
|  |                 0x7 => { | ||||||
|                     const subtrahend = @as(u64, op2) -% carry +% 1; |                     // ROR | ||||||
|                     cpu.cpsr.c.write(subtrahend <= op1); |                     const result = rotateRight(true, &cpu.cpsr, cpu.r[rd], @truncate(u8, cpu.r[rs])); | ||||||
|                     cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); |                     cpu.r[rd] = result; | ||||||
|  |                     setLogicOpFlags(true, cpu, result); | ||||||
|  |                 }, | ||||||
|  |                 0x8 => { | ||||||
|  |                     // TST | ||||||
|  |                     const result = cpu.r[rd] & cpu.r[rs]; | ||||||
|  |                     setLogicOpFlags(true, cpu, result); | ||||||
|                 }, |                 }, | ||||||
|                 0x9 => { |                 0x9 => { | ||||||
|                     // NEG |                     // NEG | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |                     cpu.r[rd] = sub(true, cpu, 0, cpu.r[rs]); | ||||||
|                     cpu.cpsr.z.write(result == 0); |                 }, | ||||||
|                     cpu.cpsr.c.write(op2 <= 0); |                 0xA => { | ||||||
|                     cpu.cpsr.v.write(((0 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); |                     // CMP | ||||||
|  |                     cmp(cpu, cpu.r[rd], cpu.r[rs]); | ||||||
|  |                 }, | ||||||
|  |                 0xB => { | ||||||
|  |                     // CMN | ||||||
|  |                     cmn(cpu, cpu.r[rd], cpu.r[rs]); | ||||||
|  |                 }, | ||||||
|  |                 0xC => { | ||||||
|  |                     // ORR | ||||||
|  |                     const result = cpu.r[rd] | cpu.r[rs]; | ||||||
|  |                     cpu.r[rd] = result; | ||||||
|  |                     setLogicOpFlags(true, cpu, result); | ||||||
|                 }, |                 }, | ||||||
|                 0xD => { |                 0xD => { | ||||||
|                     // Multiplication |                     // MUL | ||||||
|  |                     const temp = @as(u64, cpu.r[rs]) * @as(u64, cpu.r[rd]); | ||||||
|  |                     const result = @truncate(u32, temp); | ||||||
|  |                     cpu.r[rd] = result; | ||||||
|  |  | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |                     cpu.cpsr.n.write(result >> 31 & 1 == 1); | ||||||
|                     cpu.cpsr.z.write(result == 0); |                     cpu.cpsr.z.write(result == 0); | ||||||
|                     // V is unaffected, assuming similar behaviour to ARMv4 MUL C is undefined |                     // V is unaffected, assuming similar behaviour to ARMv4 MUL C is undefined | ||||||
|                 }, |                 }, | ||||||
|  |                 0xE => { | ||||||
|  |                     // BIC | ||||||
|  |                     const result = cpu.r[rd] & ~cpu.r[rs]; | ||||||
|  |                     cpu.r[rd] = result; | ||||||
|  |                     setLogicOpFlags(true, cpu, result); | ||||||
|  |                 }, | ||||||
|  |                 0xF => { | ||||||
|  |                     // MVN | ||||||
|  |                     const result = ~cpu.r[rs]; | ||||||
|  |                     cpu.r[rd] = result; | ||||||
|  |                     setLogicOpFlags(true, cpu, result); | ||||||
|  |                 }, | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
|   | |||||||
| @@ -33,8 +33,7 @@ pub fn fmt14(comptime L: bool, comptime R: bool) InstrFn { | |||||||
|             if (R) { |             if (R) { | ||||||
|                 if (L) { |                 if (L) { | ||||||
|                     const value = bus.read(u32, address); |                     const value = bus.read(u32, address); | ||||||
|                     cpu.r[15] = value & ~@as(u32, 1); |                     cpu.r[15] = value & 0xFFFF_FFFE; | ||||||
|                     cpu.pipe.reload(cpu); |  | ||||||
|                 } else { |                 } else { | ||||||
|                     bus.write(u32, address, cpu.r[14]); |                     bus.write(u32, address, cpu.r[14]); | ||||||
|                 } |                 } | ||||||
| @@ -53,13 +52,7 @@ pub fn fmt15(comptime L: bool, comptime rb: u3) InstrFn { | |||||||
|             const end_address = cpu.r[rb] + 4 * countRlist(opcode); |             const end_address = cpu.r[rb] + 4 * countRlist(opcode); | ||||||
|  |  | ||||||
|             if (opcode & 0xFF == 0) { |             if (opcode & 0xFF == 0) { | ||||||
|                 if (L) { |                 if (L) cpu.r[15] = bus.read(u32, address) else bus.write(u32, address, cpu.r[15] + 4); | ||||||
|                     cpu.r[15] = bus.read(u32, address); |  | ||||||
|                     cpu.pipe.reload(cpu); |  | ||||||
|                 } else { |  | ||||||
|                     bus.write(u32, address, cpu.r[15] + 2); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 cpu.r[rb] += 0x40; |                 cpu.r[rb] += 0x40; | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -9,13 +9,16 @@ pub fn fmt16(comptime cond: u4) InstrFn { | |||||||
|     return struct { |     return struct { | ||||||
|         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { |         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { | ||||||
|             // B |             // B | ||||||
|             if (cond == 0xE or cond == 0xF) |             const offset = sext(u32, u8, opcode & 0xFF) << 1; | ||||||
|                 cpu.panic("[CPU/THUMB.16] Undefined conditional branch with condition {}", .{cond}); |  | ||||||
|  |  | ||||||
|             if (!checkCond(cpu.cpsr, cond)) return; |             const should_execute = switch (cond) { | ||||||
|  |                 0xE, 0xF => cpu.panic("[CPU/THUMB.16] Undefined conditional branch with condition {}", .{cond}), | ||||||
|  |                 else => checkCond(cpu.cpsr, cond), | ||||||
|  |             }; | ||||||
|  |  | ||||||
|             cpu.r[15] +%= sext(u32, u8, opcode & 0xFF) << 1; |             if (should_execute) { | ||||||
|             cpu.pipe.reload(cpu); |                 cpu.r[15] = (cpu.r[15] + 2) +% offset; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| } | } | ||||||
| @@ -24,8 +27,8 @@ pub fn fmt18() InstrFn { | |||||||
|     return struct { |     return struct { | ||||||
|         // B but conditional |         // B but conditional | ||||||
|         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { |         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { | ||||||
|             cpu.r[15] +%= sext(u32, u11, opcode & 0x7FF) << 1; |             const offset = sext(u32, u11, opcode & 0x7FF) << 1; | ||||||
|             cpu.pipe.reload(cpu); |             cpu.r[15] = (cpu.r[15] + 2) +% offset; | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| } | } | ||||||
| @@ -38,16 +41,13 @@ pub fn fmt19(comptime is_low: bool) InstrFn { | |||||||
|  |  | ||||||
|             if (is_low) { |             if (is_low) { | ||||||
|                 // Instruction 2 |                 // Instruction 2 | ||||||
|                 const next_opcode = cpu.r[15] - 2; |                 const old_pc = cpu.r[15]; | ||||||
|  |  | ||||||
|                 cpu.r[15] = cpu.r[14] +% (offset << 1); |                 cpu.r[15] = cpu.r[14] +% (offset << 1); | ||||||
|                 cpu.r[14] = next_opcode | 1; |                 cpu.r[14] = old_pc | 1; | ||||||
|  |  | ||||||
|                 cpu.pipe.reload(cpu); |  | ||||||
|             } else { |             } else { | ||||||
|                 // Instruction 1 |                 // Instruction 1 | ||||||
|                 const lr_offset = sext(u32, u11, offset) << 12; |                 cpu.r[14] = (cpu.r[15] + 2) +% (sext(u32, u11, offset) << 12); | ||||||
|                 cpu.r[14] = (cpu.r[15] +% lr_offset) & ~@as(u32, 1); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
|   | |||||||
| @@ -3,12 +3,14 @@ const std = @import("std"); | |||||||
| const Bus = @import("../../Bus.zig"); | const Bus = @import("../../Bus.zig"); | ||||||
| const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; | const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; | ||||||
| const InstrFn = @import("../../cpu.zig").thumb.InstrFn; | const InstrFn = @import("../../cpu.zig").thumb.InstrFn; | ||||||
|  | const shifter = @import("../barrel_shifter.zig"); | ||||||
|  |  | ||||||
| const add = @import("../arm/data_processing.zig").add; | const add = @import("../arm/data_processing.zig").add; | ||||||
|  | const sub = @import("../arm/data_processing.zig").sub; | ||||||
|  | const cmp = @import("../arm/data_processing.zig").cmp; | ||||||
|  | const setLogicOpFlags = @import("../arm/data_processing.zig").setLogicOpFlags; | ||||||
|  |  | ||||||
| const lsl = @import("../barrel_shifter.zig").lsl; | const log = std.log.scoped(.Thumb1); | ||||||
| const lsr = @import("../barrel_shifter.zig").lsr; |  | ||||||
| const asr = @import("../barrel_shifter.zig").asr; |  | ||||||
|  |  | ||||||
| pub fn fmt1(comptime op: u2, comptime offset: u5) InstrFn { | pub fn fmt1(comptime op: u2, comptime offset: u5) InstrFn { | ||||||
|     return struct { |     return struct { | ||||||
| @@ -22,7 +24,7 @@ pub fn fmt1(comptime op: u2, comptime offset: u5) InstrFn { | |||||||
|                     if (offset == 0) { |                     if (offset == 0) { | ||||||
|                         break :blk cpu.r[rs]; |                         break :blk cpu.r[rs]; | ||||||
|                     } else { |                     } else { | ||||||
|                         break :blk lsl(true, &cpu.cpsr, cpu.r[rs], offset); |                         break :blk shifter.logicalLeft(true, &cpu.cpsr, cpu.r[rs], offset); | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 0b01 => blk: { |                 0b01 => blk: { | ||||||
| @@ -31,7 +33,7 @@ pub fn fmt1(comptime op: u2, comptime offset: u5) InstrFn { | |||||||
|                         cpu.cpsr.c.write(cpu.r[rs] >> 31 & 1 == 1); |                         cpu.cpsr.c.write(cpu.r[rs] >> 31 & 1 == 1); | ||||||
|                         break :blk @as(u32, 0); |                         break :blk @as(u32, 0); | ||||||
|                     } else { |                     } else { | ||||||
|                         break :blk lsr(true, &cpu.cpsr, cpu.r[rs], offset); |                         break :blk shifter.logicalRight(true, &cpu.cpsr, cpu.r[rs], offset); | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 0b10 => blk: { |                 0b10 => blk: { | ||||||
| @@ -40,7 +42,7 @@ pub fn fmt1(comptime op: u2, comptime offset: u5) InstrFn { | |||||||
|                         cpu.cpsr.c.write(cpu.r[rs] >> 31 & 1 == 1); |                         cpu.cpsr.c.write(cpu.r[rs] >> 31 & 1 == 1); | ||||||
|                         break :blk @bitCast(u32, @bitCast(i32, cpu.r[rs]) >> 31); |                         break :blk @bitCast(u32, @bitCast(i32, cpu.r[rs]) >> 31); | ||||||
|                     } else { |                     } else { | ||||||
|                         break :blk asr(true, &cpu.cpsr, cpu.r[rs], offset); |                         break :blk shifter.arithmeticRight(true, &cpu.cpsr, cpu.r[rs], offset); | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 else => cpu.panic("[CPU/THUMB.1] 0b{b:0>2} is not a valid op", .{op}), |                 else => cpu.panic("[CPU/THUMB.1] 0b{b:0>2} is not a valid op", .{op}), | ||||||
| @@ -48,10 +50,7 @@ pub fn fmt1(comptime op: u2, comptime offset: u5) InstrFn { | |||||||
|  |  | ||||||
|             // Equivalent to an ARM MOVS |             // Equivalent to an ARM MOVS | ||||||
|             cpu.r[rd] = result; |             cpu.r[rd] = result; | ||||||
|  |             setLogicOpFlags(true, cpu, result); | ||||||
|             // Write Flags |  | ||||||
|             cpu.cpsr.n.write(result >> 31 & 1 == 1); |  | ||||||
|             cpu.cpsr.z.write(result == 0); |  | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| } | } | ||||||
| @@ -59,51 +58,28 @@ pub fn fmt1(comptime op: u2, comptime offset: u5) InstrFn { | |||||||
| pub fn fmt5(comptime op: u2, comptime h1: u1, comptime h2: u1) InstrFn { | pub fn fmt5(comptime op: u2, comptime h1: u1, comptime h2: u1) InstrFn { | ||||||
|     return struct { |     return struct { | ||||||
|         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { |         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { | ||||||
|             const rs = @as(u4, h2) << 3 | (opcode >> 3 & 0x7); |             const src_idx = @as(u4, h2) << 3 | (opcode >> 3 & 0x7); | ||||||
|             const rd = @as(u4, h1) << 3 | (opcode & 0x7); |             const dst_idx = @as(u4, h1) << 3 | (opcode & 0x7); | ||||||
|  |  | ||||||
|             const op1 = cpu.r[rd]; |             const src = if (src_idx == 0xF) (cpu.r[src_idx] + 2) & 0xFFFF_FFFE else cpu.r[src_idx]; | ||||||
|             const op2 = cpu.r[rs]; |             const dst = if (dst_idx == 0xF) (cpu.r[dst_idx] + 2) & 0xFFFF_FFFE else cpu.r[dst_idx]; | ||||||
|  |  | ||||||
|             var result: u32 = undefined; |  | ||||||
|             var overflow: bool = undefined; |  | ||||||
|             switch (op) { |             switch (op) { | ||||||
|                 0b00 => result = add(&overflow, op1, op2), // ADD |                 0b00 => { | ||||||
|                 0b01 => result = op1 -% op2, // CMP |                     // ADD | ||||||
|                 0b10 => result = op2, // MOV |                     const sum = add(false, cpu, dst, src); | ||||||
|                 0b11 => {}, |                     cpu.r[dst_idx] = if (dst_idx == 0xF) sum & 0xFFFF_FFFE else sum; | ||||||
|             } |                 }, | ||||||
|  |                 0b01 => cmp(cpu, dst, src), // CMP | ||||||
|             // Write to Destination Register |                 0b10 => { | ||||||
|             switch (op) { |                     // MOV | ||||||
|                 0b01 => {}, // Test Instruction |                     cpu.r[dst_idx] = if (dst_idx == 0xF) src & 0xFFFF_FFFE else src; | ||||||
|  |                 }, | ||||||
|                 0b11 => { |                 0b11 => { | ||||||
|                     // BX |                     // BX | ||||||
|                     const is_thumb = op2 & 1 == 1; |                     cpu.cpsr.t.write(src & 1 == 1); | ||||||
|                     cpu.r[15] = op2 & ~@as(u32, 1); |                     cpu.r[15] = src & 0xFFFF_FFFE; | ||||||
|  |  | ||||||
|                     cpu.cpsr.t.write(is_thumb); |  | ||||||
|                     cpu.pipe.reload(cpu); |  | ||||||
|                 }, |                 }, | ||||||
|                 else => { |  | ||||||
|                     cpu.r[rd] = result; |  | ||||||
|                     if (rd == 0xF) { |  | ||||||
|                         cpu.r[15] &= ~@as(u32, 1); |  | ||||||
|                         cpu.pipe.reload(cpu); |  | ||||||
|                     } |  | ||||||
|                 }, |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Write Flags |  | ||||||
|             switch (op) { |  | ||||||
|                 0b01 => { |  | ||||||
|                     // CMP |  | ||||||
|                     cpu.cpsr.n.write(result >> 31 & 1 == 1); |  | ||||||
|                     cpu.cpsr.z.write(result == 0); |  | ||||||
|                     cpu.cpsr.c.write(op2 <= op1); |  | ||||||
|                     cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); |  | ||||||
|                 }, |  | ||||||
|                 0b00, 0b10, 0b11 => {}, // MOV and Branch Instruction |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| @@ -114,28 +90,21 @@ pub fn fmt2(comptime I: bool, is_sub: bool, rn: u3) InstrFn { | |||||||
|         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { |         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { | ||||||
|             const rs = opcode >> 3 & 0x7; |             const rs = opcode >> 3 & 0x7; | ||||||
|             const rd = @truncate(u3, opcode); |             const rd = @truncate(u3, opcode); | ||||||
|             const op1 = cpu.r[rs]; |  | ||||||
|             const op2: u32 = if (I) rn else cpu.r[rn]; |  | ||||||
|  |  | ||||||
|             if (is_sub) { |             if (is_sub) { | ||||||
|                 // SUB |                 // SUB | ||||||
|                 const result = op1 -% op2; |                 cpu.r[rd] = if (I) blk: { | ||||||
|                 cpu.r[rd] = result; |                     break :blk sub(true, cpu, cpu.r[rs], rn); | ||||||
|  |                 } else blk: { | ||||||
|                 cpu.cpsr.n.write(result >> 31 & 1 == 1); |                     break :blk sub(true, cpu, cpu.r[rs], cpu.r[rn]); | ||||||
|                 cpu.cpsr.z.write(result == 0); |                 }; | ||||||
|                 cpu.cpsr.c.write(op2 <= op1); |  | ||||||
|                 cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); |  | ||||||
|             } else { |             } else { | ||||||
|                 // ADD |                 // ADD | ||||||
|                 var overflow: bool = undefined; |                 cpu.r[rd] = if (I) blk: { | ||||||
|                 const result = add(&overflow, op1, op2); |                     break :blk add(true, cpu, cpu.r[rs], rn); | ||||||
|                 cpu.r[rd] = result; |                 } else blk: { | ||||||
|  |                     break :blk add(true, cpu, cpu.r[rs], cpu.r[rn]); | ||||||
|                 cpu.cpsr.n.write(result >> 31 & 1 == 1); |                 }; | ||||||
|                 cpu.cpsr.z.write(result == 0); |  | ||||||
|                 cpu.cpsr.c.write(overflow); |  | ||||||
|                 cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| @@ -144,36 +113,17 @@ pub fn fmt2(comptime I: bool, is_sub: bool, rn: u3) InstrFn { | |||||||
| pub fn fmt3(comptime op: u2, comptime rd: u3) InstrFn { | pub fn fmt3(comptime op: u2, comptime rd: u3) InstrFn { | ||||||
|     return struct { |     return struct { | ||||||
|         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { |         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { | ||||||
|             const op1 = cpu.r[rd]; |             const offset = @truncate(u8, opcode); | ||||||
|             const op2: u32 = opcode & 0xFF; // Offset |  | ||||||
|  |  | ||||||
|             var overflow: bool = undefined; |  | ||||||
|             const result: u32 = switch (op) { |  | ||||||
|                 0b00 => op2, // MOV |  | ||||||
|                 0b01 => op1 -% op2, // CMP |  | ||||||
|                 0b10 => add(&overflow, op1, op2), // ADD |  | ||||||
|                 0b11 => op1 -% op2, // SUB |  | ||||||
|             }; |  | ||||||
|  |  | ||||||
|             // Write to Register |  | ||||||
|             if (op != 0b01) cpu.r[rd] = result; |  | ||||||
|  |  | ||||||
|             // Write Flags |  | ||||||
|             cpu.cpsr.n.write(result >> 31 & 1 == 1); |  | ||||||
|             cpu.cpsr.z.write(result == 0); |  | ||||||
|  |  | ||||||
|             switch (op) { |             switch (op) { | ||||||
|                 0b00 => {}, // MOV | C set by Barrel Shifter, V is unaffected |                 0b00 => { | ||||||
|                 0b01, 0b11 => { |                     // MOV | ||||||
|                     // SUB, CMP |                     cpu.r[rd] = offset; | ||||||
|                     cpu.cpsr.c.write(op2 <= op1); |                     setLogicOpFlags(true, cpu, offset); | ||||||
|                     cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); |  | ||||||
|                 }, |  | ||||||
|                 0b10 => { |  | ||||||
|                     // ADD |  | ||||||
|                     cpu.cpsr.c.write(overflow); |  | ||||||
|                     cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); |  | ||||||
|                 }, |                 }, | ||||||
|  |                 0b01 => cmp(cpu, cpu.r[rd], offset), // CMP | ||||||
|  |                 0b10 => cpu.r[rd] = add(true, cpu, cpu.r[rd], offset), // ADD | ||||||
|  |                 0b11 => cpu.r[rd] = sub(true, cpu, cpu.r[rd], offset), // SUB | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| @@ -183,9 +133,10 @@ pub fn fmt12(comptime isSP: bool, comptime rd: u3) InstrFn { | |||||||
|     return struct { |     return struct { | ||||||
|         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { |         fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { | ||||||
|             // ADD |             // ADD | ||||||
|             const left = if (isSP) cpu.r[13] else cpu.r[15] & ~@as(u32, 2); |             const left = if (isSP) cpu.r[13] else (cpu.r[15] + 2) & 0xFFFF_FFFD; | ||||||
|             const right = (opcode & 0xFF) << 2; |             const right = (opcode & 0xFF) << 2; | ||||||
|             cpu.r[rd] = left + right; |             const result = left + right; | ||||||
|  |             cpu.r[rd] = result; | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,9 +12,7 @@ pub fn fmt6(comptime rd: u3) InstrFn { | |||||||
|         fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void { |         fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void { | ||||||
|             // LDR |             // LDR | ||||||
|             const offset = (opcode & 0xFF) << 2; |             const offset = (opcode & 0xFF) << 2; | ||||||
|  |             cpu.r[rd] = bus.read(u32, (cpu.r[15] + 2 & 0xFFFF_FFFD) + offset); | ||||||
|             // Bit 1 of the PC intentionally ignored |  | ||||||
|             cpu.r[rd] = bus.read(u32, (cpu.r[15] & ~@as(u32, 2)) + offset); |  | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ pub fn fmt17() InstrFn { | |||||||
|     return struct { |     return struct { | ||||||
|         fn inner(cpu: *Arm7tdmi, _: *Bus, _: u16) void { |         fn inner(cpu: *Arm7tdmi, _: *Bus, _: u16) void { | ||||||
|             // Copy Values from Current Mode |             // Copy Values from Current Mode | ||||||
|             const ret_addr = cpu.r[15] - 2; |             const r15 = cpu.r[15]; | ||||||
|             const cpsr = cpu.cpsr.raw; |             const cpsr = cpu.cpsr.raw; | ||||||
|  |  | ||||||
|             // Switch Mode |             // Switch Mode | ||||||
| @@ -14,10 +14,9 @@ pub fn fmt17() InstrFn { | |||||||
|             cpu.cpsr.t.write(false); // Force ARM Mode |             cpu.cpsr.t.write(false); // Force ARM Mode | ||||||
|             cpu.cpsr.i.write(true); // Disable normal interrupts |             cpu.cpsr.i.write(true); // Disable normal interrupts | ||||||
|  |  | ||||||
|             cpu.r[14] = ret_addr; // Resume Execution |             cpu.r[14] = r15; // Resume Execution | ||||||
|             cpu.spsr.raw = cpsr; // Previous mode CPSR |             cpu.spsr.raw = cpsr; // Previous mode CPSR | ||||||
|             cpu.r[15] = 0x0000_0008; |             cpu.r[15] = 0x0000_0008; | ||||||
|             cpu.pipe.reload(cpu); |  | ||||||
|         } |         } | ||||||
|     }.inner; |     }.inner; | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										148
									
								
								src/core/emu.zig
									
									
									
									
									
								
							
							
						
						
									
										148
									
								
								src/core/emu.zig
									
									
									
									
									
								
							| @@ -1,6 +1,5 @@ | |||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| const SDL = @import("sdl2"); | const SDL = @import("sdl2"); | ||||||
| const config = @import("../config.zig"); |  | ||||||
|  |  | ||||||
| const Bus = @import("Bus.zig"); | const Bus = @import("Bus.zig"); | ||||||
| const Scheduler = @import("scheduler.zig").Scheduler; | const Scheduler = @import("scheduler.zig").Scheduler; | ||||||
| @@ -13,6 +12,14 @@ const Thread = std.Thread; | |||||||
| const Atomic = std.atomic.Atomic; | const Atomic = std.atomic.Atomic; | ||||||
| const Allocator = std.mem.Allocator; | const Allocator = std.mem.Allocator; | ||||||
|  |  | ||||||
|  | // TODO: Move these to a TOML File | ||||||
|  | const sync_audio = false; // Enable Audio Sync | ||||||
|  | const sync_video: RunKind = .LimitedFPS; // Configure Video Sync | ||||||
|  | pub const win_scale = 4; // 1x, 2x, 3x, etc. Window Scaling | ||||||
|  | pub const cpu_logging = false; // Enable detailed CPU logging | ||||||
|  | pub const allow_unhandled_io = true; // Only relevant in Debug Builds | ||||||
|  | pub const force_rtc = false; | ||||||
|  |  | ||||||
| // 228 Lines which consist of 308 dots (which are 4 cycles long) | // 228 Lines which consist of 308 dots (which are 4 cycles long) | ||||||
| const cycles_per_frame: u64 = 228 * (308 * 4); //280896 | const cycles_per_frame: u64 = 228 * (308 * 4); //280896 | ||||||
| const clock_rate: u64 = 1 << 24; // 16.78MHz | const clock_rate: u64 = 1 << 24; // 16.78MHz | ||||||
| @@ -33,57 +40,18 @@ const RunKind = enum { | |||||||
|     UnlimitedFPS, |     UnlimitedFPS, | ||||||
|     Limited, |     Limited, | ||||||
|     LimitedFPS, |     LimitedFPS, | ||||||
|  |     LimitedBusy, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| pub fn run(quit: *Atomic(bool), scheduler: *Scheduler, cpu: *Arm7tdmi, tracker: *FpsTracker) void { | pub fn run(quit: *Atomic(bool), fps: *FpsTracker, sched: *Scheduler, cpu: *Arm7tdmi) void { | ||||||
|     const audio_sync = config.config().guest.audio_sync; |     if (sync_audio) log.info("Audio sync enabled", .{}); | ||||||
|     if (audio_sync) log.info("Audio sync enabled", .{}); |  | ||||||
|  |  | ||||||
|     if (config.config().guest.video_sync) { |     switch (sync_video) { | ||||||
|         inner(.LimitedFPS, audio_sync, quit, scheduler, cpu, tracker); |         .Unlimited => runUnsynchronized(quit, sched, cpu, null), | ||||||
|     } else { |         .Limited => runSynchronized(quit, sched, cpu, null), | ||||||
|         inner(.UnlimitedFPS, audio_sync, quit, scheduler, cpu, tracker); |         .UnlimitedFPS => runUnsynchronized(quit, sched, cpu, fps), | ||||||
|     } |         .LimitedFPS => runSynchronized(quit, sched, cpu, fps), | ||||||
| } |         .LimitedBusy => runBusyLoop(quit, sched, cpu), | ||||||
|  |  | ||||||
| fn inner(comptime kind: RunKind, audio_sync: bool, quit: *Atomic(bool), scheduler: *Scheduler, cpu: *Arm7tdmi, tracker: ?*FpsTracker) void { |  | ||||||
|     if (kind == .UnlimitedFPS or kind == .LimitedFPS) { |  | ||||||
|         std.debug.assert(tracker != null); |  | ||||||
|         log.info("FPS tracking enabled", .{}); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     switch (kind) { |  | ||||||
|         .Unlimited, .UnlimitedFPS => { |  | ||||||
|             log.info("Emulation w/out video sync", .{}); |  | ||||||
|  |  | ||||||
|             while (!quit.load(.SeqCst)) { |  | ||||||
|                 runFrame(scheduler, cpu); |  | ||||||
|                 audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full); |  | ||||||
|  |  | ||||||
|                 if (kind == .UnlimitedFPS) tracker.?.tick(); |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         .Limited, .LimitedFPS => { |  | ||||||
|             log.info("Emulation w/ video sync", .{}); |  | ||||||
|             var timer = Timer.start() catch @panic("failed to initalize std.timer.Timer"); |  | ||||||
|             var wake_time: u64 = frame_period; |  | ||||||
|  |  | ||||||
|             while (!quit.load(.SeqCst)) { |  | ||||||
|                 runFrame(scheduler, cpu); |  | ||||||
|                 const new_wake_time = videoSync(&timer, wake_time); |  | ||||||
|  |  | ||||||
|                 // Spin to make up the difference of OS scheduler innacuracies |  | ||||||
|                 // If we happen to also be syncing to audio, we choose to spin on |  | ||||||
|                 // the amount of time needed for audio to catch up rather than |  | ||||||
|                 // our expected wake-up time |  | ||||||
|  |  | ||||||
|                 audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full); |  | ||||||
|                 if (!audio_sync) spinLoop(&timer, wake_time); |  | ||||||
|                 wake_time = new_wake_time; |  | ||||||
|  |  | ||||||
|                 if (kind == .LimitedFPS) tracker.?.tick(); |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -104,7 +72,7 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fn audioSync(audio_sync: bool, stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void { | fn syncToAudio(stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void { | ||||||
|     const sample_size = 2 * @sizeOf(u16); |     const sample_size = 2 * @sizeOf(u16); | ||||||
|     const max_buf_size: c_int = 0x400; |     const max_buf_size: c_int = 0x400; | ||||||
|  |  | ||||||
| @@ -117,20 +85,90 @@ fn audioSync(audio_sync: bool, stream: *SDL.SDL_AudioStream, is_buffer_full: *bo | |||||||
|  |  | ||||||
|     while (true) { |     while (true) { | ||||||
|         still_full = SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1; |         still_full = SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1; | ||||||
|         if (!audio_sync or !still_full) break; |         if (!sync_audio or !still_full) break; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fn videoSync(timer: *Timer, wake_time: u64) u64 { | pub fn runUnsynchronized(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, fps: ?*FpsTracker) void { | ||||||
|  |     log.info("Emulation thread w/out video sync", .{}); | ||||||
|  |  | ||||||
|  |     if (fps) |tracker| { | ||||||
|  |         log.info("FPS Tracking Enabled", .{}); | ||||||
|  |  | ||||||
|  |         while (!quit.load(.SeqCst)) { | ||||||
|  |             runFrame(sched, cpu); | ||||||
|  |             syncToAudio(cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full); | ||||||
|  |  | ||||||
|  |             tracker.tick(); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         while (!quit.load(.SeqCst)) { | ||||||
|  |             runFrame(sched, cpu); | ||||||
|  |             syncToAudio(cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn runSynchronized(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, fps: ?*FpsTracker) void { | ||||||
|  |     log.info("Emulation thread w/ video sync", .{}); | ||||||
|  |     var timer = Timer.start() catch std.debug.panic("Failed to initialize std.timer.Timer", .{}); | ||||||
|  |     var wake_time: u64 = frame_period; | ||||||
|  |  | ||||||
|  |     if (fps) |tracker| { | ||||||
|  |         log.info("FPS Tracking Enabled", .{}); | ||||||
|  |  | ||||||
|  |         while (!quit.load(.SeqCst)) { | ||||||
|  |             runFrame(sched, cpu); | ||||||
|  |             const new_wake_time = blockOnVideo(&timer, wake_time); | ||||||
|  |  | ||||||
|  |             // Spin to make up the difference of OS scheduler innacuracies | ||||||
|  |             // If we happen to also be syncing to audio, we choose to spin on | ||||||
|  |             // the amount of time needed for audio to catch up rather than | ||||||
|  |             // our expected wake-up time | ||||||
|  |             syncToAudio(cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full); | ||||||
|  |             if (!sync_audio) spinLoop(&timer, wake_time); | ||||||
|  |             wake_time = new_wake_time; | ||||||
|  |  | ||||||
|  |             tracker.tick(); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         while (!quit.load(.SeqCst)) { | ||||||
|  |             runFrame(sched, cpu); | ||||||
|  |             const new_wake_time = blockOnVideo(&timer, wake_time); | ||||||
|  |  | ||||||
|  |             // see above comment | ||||||
|  |             syncToAudio(cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full); | ||||||
|  |             if (!sync_audio) spinLoop(&timer, wake_time); | ||||||
|  |             wake_time = new_wake_time; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fn blockOnVideo(timer: *Timer, wake_time: u64) u64 { | ||||||
|     // Use the OS scheduler to put the emulation thread to sleep |     // Use the OS scheduler to put the emulation thread to sleep | ||||||
|     const recalculated = sleep(timer, wake_time); |     const maybe_recalc_wake_time = sleep(timer, wake_time); | ||||||
|  |  | ||||||
|     // If sleep() determined we need to adjust our wake up time, do so |     // If sleep() determined we need to adjust our wake up time, do so | ||||||
|     // otherwise predict our next wake up time according to the frame period |     // otherwise predict our next wake up time according to the frame period | ||||||
|     return recalculated orelse wake_time + frame_period; |     return if (maybe_recalc_wake_time) |recalc| recalc else wake_time + frame_period; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn runBusyLoop(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void { | ||||||
|  |     log.info("Emulation thread with video sync using busy loop", .{}); | ||||||
|  |     var timer = Timer.start() catch unreachable; | ||||||
|  |     var wake_time: u64 = frame_period; | ||||||
|  |  | ||||||
|  |     while (!quit.load(.SeqCst)) { | ||||||
|  |         runFrame(sched, cpu); | ||||||
|  |         spinLoop(&timer, wake_time); | ||||||
|  |  | ||||||
|  |         syncToAudio(cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full); | ||||||
|  |  | ||||||
|  |         // Update to the new wake time | ||||||
|  |         wake_time += frame_period; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| // TODO: Better sleep impl? |  | ||||||
| fn sleep(timer: *Timer, wake_time: u64) ?u64 { | fn sleep(timer: *Timer, wake_time: u64) ?u64 { | ||||||
|     // const step = std.time.ns_per_ms * 10; // 10ms |     // const step = std.time.ns_per_ms * 10; // 10ms | ||||||
|     const timestamp = timer.read(); |     const timestamp = timer.read(); | ||||||
|   | |||||||
| @@ -15,6 +15,9 @@ const FrameBuffer = @import("../util.zig").FrameBuffer; | |||||||
| const Allocator = std.mem.Allocator; | const Allocator = std.mem.Allocator; | ||||||
| const log = std.log.scoped(.Ppu); | const log = std.log.scoped(.Ppu); | ||||||
|  |  | ||||||
|  | /// This is used to generate byuu / Talurabi's Color Correction algorithm | ||||||
|  | const COLOUR_LUT = genColourLut(); | ||||||
|  |  | ||||||
| pub const width = 240; | pub const width = 240; | ||||||
| pub const height = 160; | pub const height = 160; | ||||||
| pub const framebuf_pitch = width * @sizeOf(u32); | pub const framebuf_pitch = width * @sizeOf(u32); | ||||||
| @@ -394,7 +397,7 @@ pub const Ppu = struct { | |||||||
|                     const maybe_btm = self.scanline.btm()[i]; |                     const maybe_btm = self.scanline.btm()[i]; | ||||||
|  |  | ||||||
|                     const bgr555 = self.getBgr555(maybe_top, maybe_btm); |                     const bgr555 = self.getBgr555(maybe_top, maybe_btm); | ||||||
|                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555)); |                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], COLOUR_LUT[bgr555 & 0x7FFF]); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 // Reset Current Scanline Pixel Buffer and list of fetched sprites |                 // Reset Current Scanline Pixel Buffer and list of fetched sprites | ||||||
| @@ -421,7 +424,7 @@ pub const Ppu = struct { | |||||||
|                     const maybe_btm = self.scanline.btm()[i]; |                     const maybe_btm = self.scanline.btm()[i]; | ||||||
|  |  | ||||||
|                     const bgr555 = self.getBgr555(maybe_top, maybe_btm); |                     const bgr555 = self.getBgr555(maybe_top, maybe_btm); | ||||||
|                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555)); |                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], COLOUR_LUT[bgr555 & 0x7FFF]); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 // Reset Current Scanline Pixel Buffer and list of fetched sprites |                 // Reset Current Scanline Pixel Buffer and list of fetched sprites | ||||||
| @@ -447,7 +450,7 @@ pub const Ppu = struct { | |||||||
|                     const maybe_btm = self.scanline.btm()[i]; |                     const maybe_btm = self.scanline.btm()[i]; | ||||||
|  |  | ||||||
|                     const bgr555 = self.getBgr555(maybe_top, maybe_btm); |                     const bgr555 = self.getBgr555(maybe_top, maybe_btm); | ||||||
|                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555)); |                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], COLOUR_LUT[bgr555 & 0x7FFF]); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 // Reset Current Scanline Pixel Buffer and list of fetched sprites |                 // Reset Current Scanline Pixel Buffer and list of fetched sprites | ||||||
| @@ -462,7 +465,7 @@ pub const Ppu = struct { | |||||||
|                 var i: usize = 0; |                 var i: usize = 0; | ||||||
|                 while (i < width) : (i += 1) { |                 while (i < width) : (i += 1) { | ||||||
|                     const bgr555 = self.vram.read(u16, vram_base + i * @sizeOf(u16)); |                     const bgr555 = self.vram.read(u16, vram_base + i * @sizeOf(u16)); | ||||||
|                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555)); |                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], COLOUR_LUT[bgr555 & 0x7FFF]); | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             0x4 => { |             0x4 => { | ||||||
| @@ -473,7 +476,7 @@ pub const Ppu = struct { | |||||||
|                 // Render Current Scanline |                 // Render Current Scanline | ||||||
|                 for (self.vram.buf[vram_base .. vram_base + width]) |byte, i| { |                 for (self.vram.buf[vram_base .. vram_base + width]) |byte, i| { | ||||||
|                     const bgr555 = self.palette.read(u16, @as(u16, byte) * @sizeOf(u16)); |                     const bgr555 = self.palette.read(u16, @as(u16, byte) * @sizeOf(u16)); | ||||||
|                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555)); |                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], COLOUR_LUT[bgr555 & 0x7FFF]); | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             0x5 => { |             0x5 => { | ||||||
| @@ -490,7 +493,7 @@ pub const Ppu = struct { | |||||||
|                     const bgr555 = |                     const bgr555 = | ||||||
|                         if (scanline < m5_height and i < m5_width) self.vram.read(u16, vram_base + i * @sizeOf(u16)) else self.palette.backdrop(); |                         if (scanline < m5_height and i < m5_width) self.vram.read(u16, vram_base + i * @sizeOf(u16)) else self.palette.backdrop(); | ||||||
|  |  | ||||||
|                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], rgba888(bgr555)); |                     std.mem.writeIntNative(u32, self.framebuf.get(.Emulator)[fb_base + i * @sizeOf(u32) ..][0..@sizeOf(u32)], COLOUR_LUT[bgr555 & 0x7FFF]); | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             else => std.debug.panic("[PPU] TODO: Implement BG Mode {}", .{bg_mode}), |             else => std.debug.panic("[PPU] TODO: Implement BG Mode {}", .{bg_mode}), | ||||||
| @@ -651,7 +654,7 @@ pub const Ppu = struct { | |||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn onHdrawEnd(self: *Self, cpu: *Arm7tdmi, late: u64) void { |     pub fn handleHDrawEnd(self: *Self, cpu: *Arm7tdmi, late: u64) void { | ||||||
|         // Transitioning to a Hblank |         // Transitioning to a Hblank | ||||||
|         if (self.dispstat.hblank_irq.read()) { |         if (self.dispstat.hblank_irq.read()) { | ||||||
|             cpu.bus.io.irq.hblank.set(); |             cpu.bus.io.irq.hblank.set(); | ||||||
| @@ -667,7 +670,7 @@ pub const Ppu = struct { | |||||||
|         self.sched.push(.HBlank, 68 * 4 -| late); |         self.sched.push(.HBlank, 68 * 4 -| late); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn onHblankEnd(self: *Self, cpu: *Arm7tdmi, late: u64) void { |     pub fn handleHBlankEnd(self: *Self, cpu: *Arm7tdmi, late: u64) void { | ||||||
|         // The End of a Hblank (During Draw or Vblank) |         // The End of a Hblank (During Draw or Vblank) | ||||||
|         const old_scanline = self.vcount.scanline.read(); |         const old_scanline = self.vcount.scanline.read(); | ||||||
|         const scanline = (old_scanline + 1) % 228; |         const scanline = (old_scanline + 1) % 228; | ||||||
| @@ -764,6 +767,18 @@ const Window = struct { | |||||||
|         self.in.raw = @truncate(u16, value); |         self.in.raw = @truncate(u16, value); | ||||||
|         self.out.raw = @truncate(u16, value >> 16); |         self.out.raw = @truncate(u16, value >> 16); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub fn setInL(self: *Self, value: u8) void { | ||||||
|  |         self.in.raw = (self.in.raw & 0xFF00) | value; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn setInH(self: *Self, value: u8) void { | ||||||
|  |         self.in.raw = (self.in.raw & 0x00FF) | (@as(u16, value) << 8); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn setOutL(self: *Self, value: u8) void { | ||||||
|  |         self.out.raw = (self.out.raw & 0xFF00) | value; | ||||||
|  |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const Background = struct { | const Background = struct { | ||||||
| @@ -1019,7 +1034,7 @@ fn spriteDimensions(shape: u2, size: u2) [2]u8 { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| inline fn rgba888(bgr555: u16) u32 { | fn toRgba8888(bgr555: u16) u32 { | ||||||
|     const b = @as(u32, bgr555 >> 10 & 0x1F); |     const b = @as(u32, bgr555 >> 10 & 0x1F); | ||||||
|     const g = @as(u32, bgr555 >> 5 & 0x1F); |     const g = @as(u32, bgr555 >> 5 & 0x1F); | ||||||
|     const r = @as(u32, bgr555 & 0x1F); |     const r = @as(u32, bgr555 & 0x1F); | ||||||
| @@ -1027,6 +1042,39 @@ inline fn rgba888(bgr555: u16) u32 { | |||||||
|     return (r << 3 | r >> 2) << 24 | (g << 3 | g >> 2) << 16 | (b << 3 | b >> 2) << 8 | 0xFF; |     return (r << 3 | r >> 2) << 24 | (g << 3 | g >> 2) << 16 | (b << 3 | b >> 2) << 8 | 0xFF; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn genColourLut() [0x8000]u32 { | ||||||
|  |     return comptime { | ||||||
|  |         @setEvalBranchQuota(0x10001); | ||||||
|  |  | ||||||
|  |         var lut: [0x8000]u32 = undefined; | ||||||
|  |         for (lut) |*px, i| px.* = toRgba8888(i); | ||||||
|  |         return lut; | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // FIXME: The implementation is incorrect and using it in the LUT crashes the compiler (OOM) | ||||||
|  | /// Implementation courtesy of byuu and Talarubi at https://near.sh/articles/video/color-emulation | ||||||
|  | fn toRgba8888Talarubi(bgr555: u16) u32 { | ||||||
|  |     @setRuntimeSafety(false); | ||||||
|  |  | ||||||
|  |     const lcd_gamma: f64 = 4; | ||||||
|  |     const out_gamma: f64 = 2.2; | ||||||
|  |  | ||||||
|  |     const b = @as(u32, bgr555 >> 10 & 0x1F); | ||||||
|  |     const g = @as(u32, bgr555 >> 5 & 0x1F); | ||||||
|  |     const r = @as(u32, bgr555 & 0x1F); | ||||||
|  |  | ||||||
|  |     const lb = std.math.pow(f64, @intToFloat(f64, b << 3 | b >> 2) / 31, lcd_gamma); | ||||||
|  |     const lg = std.math.pow(f64, @intToFloat(f64, g << 3 | g >> 2) / 31, lcd_gamma); | ||||||
|  |     const lr = std.math.pow(f64, @intToFloat(f64, r << 3 | r >> 2) / 31, lcd_gamma); | ||||||
|  |  | ||||||
|  |     const out_b = std.math.pow(f64, (220 * lb + 10 * lg + 50 * lr) / 255, 1 / out_gamma); | ||||||
|  |     const out_g = std.math.pow(f64, (30 * lb + 230 * lg + 10 * lr) / 255, 1 / out_gamma); | ||||||
|  |     const out_r = std.math.pow(f64, (0 * lb + 50 * lg + 255 * lr) / 255, 1 / out_gamma); | ||||||
|  |  | ||||||
|  |     return @floatToInt(u32, out_r) << 24 | @floatToInt(u32, out_g) << 16 | @floatToInt(u32, out_b) << 8 | 0xFF; | ||||||
|  | } | ||||||
|  |  | ||||||
| fn alphaBlend(top: u16, btm: u16, bldalpha: io.BldAlpha) u16 { | fn alphaBlend(top: u16, btm: u16, bldalpha: io.BldAlpha) u16 { | ||||||
|     const eva: u16 = bldalpha.eva.read(); |     const eva: u16 = bldalpha.eva.read(); | ||||||
|     const evb: u16 = bldalpha.evb.read(); |     const evb: u16 = bldalpha.evb.read(); | ||||||
|   | |||||||
| @@ -43,22 +43,22 @@ pub const Scheduler = struct { | |||||||
|                 .Draw => { |                 .Draw => { | ||||||
|                     // The end of a VDraw |                     // The end of a VDraw | ||||||
|                     cpu.bus.ppu.drawScanline(); |                     cpu.bus.ppu.drawScanline(); | ||||||
|                     cpu.bus.ppu.onHdrawEnd(cpu, late); |                     cpu.bus.ppu.handleHDrawEnd(cpu, late); | ||||||
|                 }, |                 }, | ||||||
|                 .TimerOverflow => |id| { |                 .TimerOverflow => |id| { | ||||||
|                     switch (id) { |                     switch (id) { | ||||||
|                         0 => cpu.bus.tim[0].onTimerExpire(cpu, late), |                         0 => cpu.bus.tim[0].handleOverflow(cpu, late), | ||||||
|                         1 => cpu.bus.tim[1].onTimerExpire(cpu, late), |                         1 => cpu.bus.tim[1].handleOverflow(cpu, late), | ||||||
|                         2 => cpu.bus.tim[2].onTimerExpire(cpu, late), |                         2 => cpu.bus.tim[2].handleOverflow(cpu, late), | ||||||
|                         3 => cpu.bus.tim[3].onTimerExpire(cpu, late), |                         3 => cpu.bus.tim[3].handleOverflow(cpu, late), | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 .ApuChannel => |id| { |                 .ApuChannel => |id| { | ||||||
|                     switch (id) { |                     switch (id) { | ||||||
|                         0 => cpu.bus.apu.ch1.onToneSweepEvent(late), |                         0 => cpu.bus.apu.ch1.channelTimerOverflow(late), | ||||||
|                         1 => cpu.bus.apu.ch2.onToneEvent(late), |                         1 => cpu.bus.apu.ch2.channelTimerOverflow(late), | ||||||
|                         2 => cpu.bus.apu.ch3.onWaveEvent(late), |                         2 => cpu.bus.apu.ch3.channelTimerOverflow(late), | ||||||
|                         3 => cpu.bus.apu.ch4.onNoiseEvent(late), |                         3 => cpu.bus.apu.ch4.channelTimerOverflow(late), | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 .RealTimeClock => { |                 .RealTimeClock => { | ||||||
| @@ -66,12 +66,12 @@ pub const Scheduler = struct { | |||||||
|                     if (device.kind != .Rtc or device.ptr == null) return; |                     if (device.kind != .Rtc or device.ptr == null) return; | ||||||
|  |  | ||||||
|                     const clock = @ptrCast(*Clock, @alignCast(@alignOf(*Clock), device.ptr.?)); |                     const clock = @ptrCast(*Clock, @alignCast(@alignOf(*Clock), device.ptr.?)); | ||||||
|                     clock.onClockUpdate(late); |                     clock.updateTime(late); | ||||||
|                 }, |                 }, | ||||||
|                 .FrameSequencer => cpu.bus.apu.onSequencerTick(late), |                 .FrameSequencer => cpu.bus.apu.tickFrameSequencer(late), | ||||||
|                 .SampleAudio => cpu.bus.apu.sampleAudio(late), |                 .SampleAudio => cpu.bus.apu.sampleAudio(late), | ||||||
|                 .HBlank => cpu.bus.ppu.onHblankEnd(cpu, late), // The end of a HBlank |                 .HBlank => cpu.bus.ppu.handleHBlankEnd(cpu, late), // The end of a HBlank | ||||||
|                 .VBlank => cpu.bus.ppu.onHdrawEnd(cpu, late), // The end of a VBlank |                 .VBlank => cpu.bus.ppu.handleHDrawEnd(cpu, late), // The end of a VBlank | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										131
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								src/main.zig
									
									
									
									
									
								
							| @@ -1,10 +1,9 @@ | |||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| const builtin = @import("builtin"); | const builtin = @import("builtin"); | ||||||
|  |  | ||||||
| const known_folders = @import("known_folders"); | const known_folders = @import("known_folders"); | ||||||
| const clap = @import("clap"); | const clap = @import("clap"); | ||||||
|  |  | ||||||
| const config = @import("config.zig"); |  | ||||||
|  |  | ||||||
| const Gui = @import("platform.zig").Gui; | const Gui = @import("platform.zig").Gui; | ||||||
| const Bus = @import("core/Bus.zig"); | const Bus = @import("core/Bus.zig"); | ||||||
| const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi; | const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi; | ||||||
| @@ -15,14 +14,16 @@ const Allocator = std.mem.Allocator; | |||||||
| const log = std.log.scoped(.Cli); | const log = std.log.scoped(.Cli); | ||||||
| const width = @import("core/ppu.zig").width; | const width = @import("core/ppu.zig").width; | ||||||
| const height = @import("core/ppu.zig").height; | const height = @import("core/ppu.zig").height; | ||||||
|  | const cpu_logging = @import("core/emu.zig").cpu_logging; | ||||||
| pub const log_level = if (builtin.mode != .Debug) .info else std.log.default_level; | pub const log_level = if (builtin.mode != .Debug) .info else std.log.default_level; | ||||||
|  |  | ||||||
|  | // TODO: Reimpl Logging | ||||||
|  |  | ||||||
| // CLI Arguments + Help Text | // CLI Arguments + Help Text | ||||||
| const params = clap.parseParamsComptime( | const params = clap.parseParamsComptime( | ||||||
|     \\-h, --help            Display this help and exit. |     \\-h, --help            Display this help and exit. | ||||||
|     \\-s, --skip            Skip BIOS. |  | ||||||
|     \\-b, --bios <str>      Optional path to a GBA BIOS ROM. |     \\-b, --bios <str>      Optional path to a GBA BIOS ROM. | ||||||
|     \\<str>                 Path to the GBA GamePak ROM. |     \\<str>                 Path to the GBA GamePak ROM | ||||||
|     \\ |     \\ | ||||||
| ); | ); | ||||||
|  |  | ||||||
| @@ -30,36 +31,16 @@ pub fn main() anyerror!void { | |||||||
|     // Main Allocator for ZBA |     // Main Allocator for ZBA | ||||||
|     var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |     var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||||||
|     defer std.debug.assert(!gpa.deinit()); |     defer std.debug.assert(!gpa.deinit()); | ||||||
|  |  | ||||||
|     const allocator = gpa.allocator(); |     const allocator = gpa.allocator(); | ||||||
|  |  | ||||||
|     // Determine the Data Directory (stores saves, config file, etc.) |     // Handle CLI Input | ||||||
|     const data_path = blk: { |     const result = try clap.parse(clap.Help, ¶ms, clap.parsers.default, .{}); | ||||||
|         const result = known_folders.getPath(allocator, .data); |  | ||||||
|         const option = result catch |e| exitln("interrupted while attempting to find a data directory: {}", .{e}); |  | ||||||
|         const path = option orelse exitln("no valid data directory could be found", .{}); |  | ||||||
|         ensureDirectoriesExist(path) catch |e| exitln("failed to create directories under \"{s}\": {}", .{ path, e }); |  | ||||||
|  |  | ||||||
|         break :blk path; |  | ||||||
|     }; |  | ||||||
|     defer allocator.free(data_path); |  | ||||||
|  |  | ||||||
|     // Parse CLI |  | ||||||
|     const result = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{}) catch |e| exitln("failed to parse cli: {}", .{e}); |  | ||||||
|     defer result.deinit(); |     defer result.deinit(); | ||||||
|  |  | ||||||
|     // TODO: Move config file to XDG Config directory? |     const paths = try handleArguments(allocator, &result); | ||||||
|     const config_path = configFilePath(allocator, data_path) catch |e| exitln("failed to determine the config file path for ZBA: {}", .{e}); |  | ||||||
|     defer allocator.free(config_path); |  | ||||||
|  |  | ||||||
|     config.load(allocator, config_path) catch |e| exitln("failed to read config file: {}", .{e}); |  | ||||||
|  |  | ||||||
|     const paths = handleArguments(allocator, data_path, &result) catch |e| exitln("failed to handle cli arguments: {}", .{e}); |  | ||||||
|     defer if (paths.save) |path| allocator.free(path); |     defer if (paths.save) |path| allocator.free(path); | ||||||
|  |  | ||||||
|     const log_file = if (config.config().debug.cpu_trace) blk: { |     const log_file: ?std.fs.File = if (cpu_logging) try std.fs.cwd().createFile("zba.log", .{}) else null; | ||||||
|         break :blk std.fs.cwd().createFile("zba.log", .{}) catch |e| exitln("failed to create trace log file: {}", .{e}); |  | ||||||
|     } else null; |  | ||||||
|     defer if (log_file) |file| file.close(); |     defer if (log_file) |file| file.close(); | ||||||
|  |  | ||||||
|     // TODO: Take Emulator Init Code out of main.zig |     // TODO: Take Emulator Init Code out of main.zig | ||||||
| @@ -68,78 +49,54 @@ pub fn main() anyerror!void { | |||||||
|  |  | ||||||
|     var bus: Bus = undefined; |     var bus: Bus = undefined; | ||||||
|     var cpu = Arm7tdmi.init(&scheduler, &bus, log_file); |     var cpu = Arm7tdmi.init(&scheduler, &bus, log_file); | ||||||
|  |     if (paths.bios == null) cpu.fastBoot(); | ||||||
|  |  | ||||||
|     bus.init(allocator, &scheduler, &cpu, paths) catch |e| exitln("failed to init zba bus: {}", .{e}); |     try bus.init(allocator, &scheduler, &cpu, paths); | ||||||
|     defer bus.deinit(); |     defer bus.deinit(); | ||||||
|  |  | ||||||
|     if (config.config().guest.skip_bios or result.args.skip or paths.bios == null) { |  | ||||||
|         cpu.fastBoot(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     var gui = Gui.init(&bus.pak.title, &bus.apu, width, height); |     var gui = Gui.init(&bus.pak.title, &bus.apu, width, height); | ||||||
|     defer gui.deinit(); |     defer gui.deinit(); | ||||||
|  |  | ||||||
|     gui.run(&cpu, &scheduler) catch |e| exitln("failed to run gui thread: {}", .{e}); |     try gui.run(&cpu, &scheduler); | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn handleArguments(allocator: Allocator, data_path: []const u8, result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) !FilePaths { | fn getSavePath(allocator: Allocator) !?[]const u8 { | ||||||
|     const rom_path = romPath(result); |     const save_subpath = "zba" ++ [_]u8{std.fs.path.sep} ++ "save"; | ||||||
|  |  | ||||||
|  |     const maybe_data_path = try known_folders.getPath(allocator, .data); | ||||||
|  |     defer if (maybe_data_path) |path| allocator.free(path); | ||||||
|  |  | ||||||
|  |     const save_path = if (maybe_data_path) |base| try std.fs.path.join(allocator, &[_][]const u8{ base, "zba", "save" }) else null; | ||||||
|  |  | ||||||
|  |     if (save_path) |_| { | ||||||
|  |         // If we've determined what our save path should be, ensure the prereq directories | ||||||
|  |         // are present so that we can successfully write to the path when necessary | ||||||
|  |         const maybe_data_dir = try known_folders.open(allocator, .data, .{}); | ||||||
|  |         if (maybe_data_dir) |data_dir| try data_dir.makePath(save_subpath); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return save_path; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn getRomPath(result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) ![]const u8 { | ||||||
|  |     return switch (result.positionals.len) { | ||||||
|  |         1 => result.positionals[0], | ||||||
|  |         0 => std.debug.panic("ZBA requires a positional path to a GamePak ROM.\n", .{}), | ||||||
|  |         else => std.debug.panic("ZBA received too many arguments.\n", .{}), | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn handleArguments(allocator: Allocator, result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) !FilePaths { | ||||||
|  |     const rom_path = try getRomPath(result); | ||||||
|     log.info("ROM path: {s}", .{rom_path}); |     log.info("ROM path: {s}", .{rom_path}); | ||||||
|  |  | ||||||
|     const bios_path = result.args.bios; |     const bios_path = result.args.bios; | ||||||
|     if (bios_path) |path| log.info("BIOS path: {s}", .{path}) else log.warn("No BIOS provided", .{}); |     if (bios_path) |path| log.info("BIOS path: {s}", .{path}) else log.info("No BIOS provided", .{}); | ||||||
|  |     const save_path = try getSavePath(allocator); | ||||||
|  |     if (save_path) |path| log.info("Save path: {s}", .{path}); | ||||||
|  |  | ||||||
|     const save_path = try std.fs.path.join(allocator, &[_][]const u8{ data_path, "zba", "save" }); |     return FilePaths{ | ||||||
|     log.info("Save path: {s}", .{save_path}); |  | ||||||
|  |  | ||||||
|     return .{ |  | ||||||
|         .rom = rom_path, |         .rom = rom_path, | ||||||
|         .bios = bios_path, |         .bios = bios_path, | ||||||
|         .save = save_path, |         .save = save_path, | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| fn configFilePath(allocator: Allocator, data_path: []const u8) ![]const u8 { |  | ||||||
|     const path = try std.fs.path.join(allocator, &[_][]const u8{ data_path, "zba", "config.toml" }); |  | ||||||
|     errdefer allocator.free(path); |  | ||||||
|  |  | ||||||
|     // We try to create the file exclusively, meaning that we err out if the file already exists. |  | ||||||
|     // All we care about is a file being there so we can just ignore that error in particular and |  | ||||||
|     // continue down the happy pathj |  | ||||||
|     std.fs.accessAbsolute(path, .{}) catch |e| { |  | ||||||
|         if (e != error.FileNotFound) return e; |  | ||||||
|  |  | ||||||
|         const config_file = try std.fs.createFileAbsolute(path, .{}); |  | ||||||
|         defer config_file.close(); |  | ||||||
|  |  | ||||||
|         try config_file.writeAll(@embedFile("../example.toml")); |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     return path; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn ensureDirectoriesExist(data_path: []const u8) !void { |  | ||||||
|     var dir = try std.fs.openDirAbsolute(data_path, .{}); |  | ||||||
|     defer dir.close(); |  | ||||||
|  |  | ||||||
|     // We want to make sure: %APPDATA%/zba and %APPDATA%/zba/save exist |  | ||||||
|     // (~/.local/share/zba/save for linux, ??? for macOS) |  | ||||||
|  |  | ||||||
|     // Will recursively create directories |  | ||||||
|     try dir.makePath("zba" ++ [_]u8{std.fs.path.sep} ++ "save"); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn romPath(result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) []const u8 { |  | ||||||
|     return switch (result.positionals.len) { |  | ||||||
|         1 => result.positionals[0], |  | ||||||
|         0 => exitln("ZBA requires a path to a GamePak ROM", .{}), |  | ||||||
|         else => exitln("ZBA received too many positional arguments.", .{}), |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn exitln(comptime format: []const u8, args: anytype) noreturn { |  | ||||||
|     const stderr = std.io.getStdErr().writer(); |  | ||||||
|     stderr.print(format, args) catch {}; // Just exit already... |  | ||||||
|     stderr.writeByte('\n') catch {}; |  | ||||||
|     std.os.exit(1); |  | ||||||
| } |  | ||||||
|   | |||||||
							
								
								
									
										168
									
								
								src/platform.zig
									
									
									
									
									
								
							
							
						
						
									
										168
									
								
								src/platform.zig
									
									
									
									
									
								
							| @@ -1,8 +1,6 @@ | |||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| const SDL = @import("sdl2"); | const SDL = @import("sdl2"); | ||||||
| const gl = @import("gl"); |  | ||||||
| const emu = @import("core/emu.zig"); | const emu = @import("core/emu.zig"); | ||||||
| const config = @import("config.zig"); |  | ||||||
|  |  | ||||||
| const Apu = @import("core/apu.zig").Apu; | const Apu = @import("core/apu.zig").Apu; | ||||||
| const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi; | const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi; | ||||||
| @@ -12,156 +10,61 @@ const FpsTracker = @import("util.zig").FpsTracker; | |||||||
| const span = @import("util.zig").span; | const span = @import("util.zig").span; | ||||||
|  |  | ||||||
| const pitch = @import("core/ppu.zig").framebuf_pitch; | const pitch = @import("core/ppu.zig").framebuf_pitch; | ||||||
| const gba_width = @import("core/ppu.zig").width; | const scale = @import("core/emu.zig").win_scale; | ||||||
| const gba_height = @import("core/ppu.zig").height; |  | ||||||
|  |  | ||||||
| const default_title: []const u8 = "ZBA"; | const default_title: []const u8 = "ZBA"; | ||||||
|  |  | ||||||
| pub const Gui = struct { | pub const Gui = struct { | ||||||
|     const Self = @This(); |     const Self = @This(); | ||||||
|     const SDL_GLContext = *anyopaque; // SDL.SDL_GLContext is a ?*anyopaque |  | ||||||
|     const log = std.log.scoped(.Gui); |     const log = std.log.scoped(.Gui); | ||||||
|  |  | ||||||
|     // zig fmt: off |  | ||||||
|     const vertices: [32]f32 = [_]f32{ |  | ||||||
|         // Positions        // Colours      // Texture Coords |  | ||||||
|          1.0, -1.0, 0.0,    1.0, 0.0, 0.0,  1.0, 1.0, // Top Right |  | ||||||
|          1.0,  1.0, 0.0,    0.0, 1.0, 0.0,  1.0, 0.0, // Bottom Right |  | ||||||
|         -1.0,  1.0, 0.0,    0.0, 0.0, 1.0,  0.0, 0.0, // Bottom Left |  | ||||||
|         -1.0, -1.0, 0.0,    1.0, 1.0, 0.0,  0.0, 1.0, // Top Left |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     const indices: [6]u32 = [_]u32{ |  | ||||||
|         0, 1, 3, // First Triangle |  | ||||||
|         1, 2, 3, // Second Triangle |  | ||||||
|     }; |  | ||||||
|     // zig fmt: on |  | ||||||
|  |  | ||||||
|     window: *SDL.SDL_Window, |     window: *SDL.SDL_Window, | ||||||
|     ctx: SDL_GLContext, |  | ||||||
|     title: []const u8, |     title: []const u8, | ||||||
|  |     renderer: *SDL.SDL_Renderer, | ||||||
|  |     texture: *SDL.SDL_Texture, | ||||||
|     audio: Audio, |     audio: Audio, | ||||||
|  |  | ||||||
|     program_id: gl.GLuint, |  | ||||||
|  |  | ||||||
|     pub fn init(title: *const [12]u8, apu: *Apu, width: i32, height: i32) Self { |     pub fn init(title: *const [12]u8, apu: *Apu, width: i32, height: i32) Self { | ||||||
|         if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0) panic(); |         const ret = SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_GAMECONTROLLER); | ||||||
|         if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GL_CONTEXT_PROFILE_CORE) < 0) panic(); |         if (ret < 0) panic(); | ||||||
|         if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic(); |  | ||||||
|         if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic(); |  | ||||||
|  |  | ||||||
|         const win_scale = @intCast(c_int, config.config().host.win_scale); |  | ||||||
|  |  | ||||||
|         const window = SDL.SDL_CreateWindow( |         const window = SDL.SDL_CreateWindow( | ||||||
|             default_title.ptr, |             default_title.ptr, | ||||||
|             SDL.SDL_WINDOWPOS_CENTERED, |             SDL.SDL_WINDOWPOS_CENTERED, | ||||||
|             SDL.SDL_WINDOWPOS_CENTERED, |             SDL.SDL_WINDOWPOS_CENTERED, | ||||||
|             @as(c_int, width * win_scale), |             @as(c_int, width * scale), | ||||||
|             @as(c_int, height * win_scale), |             @as(c_int, height * scale), | ||||||
|             SDL.SDL_WINDOW_OPENGL | SDL.SDL_WINDOW_SHOWN, |             SDL.SDL_WINDOW_SHOWN, | ||||||
|         ) orelse panic(); |         ) orelse panic(); | ||||||
|  |  | ||||||
|         const ctx = SDL.SDL_GL_CreateContext(window) orelse panic(); |         const renderer = SDL.SDL_CreateRenderer(window, -1, SDL.SDL_RENDERER_ACCELERATED | SDL.SDL_RENDERER_PRESENTVSYNC) orelse panic(); | ||||||
|         if (SDL.SDL_GL_MakeCurrent(window, ctx) < 0) panic(); |  | ||||||
|  |  | ||||||
|         gl.load(ctx, Self.glGetProcAddress) catch @panic("gl.load failed"); |         const texture = SDL.SDL_CreateTexture( | ||||||
|         if (SDL.SDL_GL_SetSwapInterval(@boolToInt(config.config().host.vsync)) < 0) panic(); |             renderer, | ||||||
|  |             SDL.SDL_PIXELFORMAT_RGBA8888, | ||||||
|         const program_id = compileShaders(); |             SDL.SDL_TEXTUREACCESS_STREAMING, | ||||||
|  |             @as(c_int, width), | ||||||
|  |             @as(c_int, height), | ||||||
|  |         ) orelse panic(); | ||||||
|  |  | ||||||
|         return Self{ |         return Self{ | ||||||
|             .window = window, |             .window = window, | ||||||
|             .title = span(title), |             .title = span(title), | ||||||
|             .ctx = ctx, |             .renderer = renderer, | ||||||
|             .program_id = program_id, |             .texture = texture, | ||||||
|             .audio = Audio.init(apu), |             .audio = Audio.init(apu), | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn compileShaders() gl.GLuint { |  | ||||||
|         // TODO: Panic on Shader Compiler Failure + Error Message |  | ||||||
|         const vert_shader = @embedFile("shader/pixelbuf.vert"); |  | ||||||
|         const frag_shader = @embedFile("shader/pixelbuf.frag"); |  | ||||||
|  |  | ||||||
|         const vs = gl.createShader(gl.VERTEX_SHADER); |  | ||||||
|         defer gl.deleteShader(vs); |  | ||||||
|  |  | ||||||
|         gl.shaderSource(vs, 1, &[_][*c]const u8{vert_shader}, 0); |  | ||||||
|         gl.compileShader(vs); |  | ||||||
|  |  | ||||||
|         const fs = gl.createShader(gl.FRAGMENT_SHADER); |  | ||||||
|         defer gl.deleteShader(fs); |  | ||||||
|  |  | ||||||
|         gl.shaderSource(fs, 1, &[_][*c]const u8{frag_shader}, 0); |  | ||||||
|         gl.compileShader(fs); |  | ||||||
|  |  | ||||||
|         const program = gl.createProgram(); |  | ||||||
|         gl.attachShader(program, vs); |  | ||||||
|         gl.attachShader(program, fs); |  | ||||||
|         gl.linkProgram(program); |  | ||||||
|  |  | ||||||
|         return program; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Returns the VAO ID since it's used in run() |  | ||||||
|     fn generateBuffers() [3]c_uint { |  | ||||||
|         var vao_id: c_uint = undefined; |  | ||||||
|         var vbo_id: c_uint = undefined; |  | ||||||
|         var ebo_id: c_uint = undefined; |  | ||||||
|         gl.genVertexArrays(1, &vao_id); |  | ||||||
|         gl.genBuffers(1, &vbo_id); |  | ||||||
|         gl.genBuffers(1, &ebo_id); |  | ||||||
|  |  | ||||||
|         gl.bindVertexArray(vao_id); |  | ||||||
|  |  | ||||||
|         gl.bindBuffer(gl.ARRAY_BUFFER, vbo_id); |  | ||||||
|         gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(@TypeOf(vertices)), &vertices, gl.STATIC_DRAW); |  | ||||||
|  |  | ||||||
|         gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo_id); |  | ||||||
|         gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, @sizeOf(@TypeOf(indices)), &indices, gl.STATIC_DRAW); |  | ||||||
|  |  | ||||||
|         // Position |  | ||||||
|         gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), @intToPtr(?*anyopaque, 0)); // lmao |  | ||||||
|         gl.enableVertexAttribArray(0); |  | ||||||
|         // Colour |  | ||||||
|         gl.vertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), @intToPtr(?*anyopaque, (3 * @sizeOf(f32)))); |  | ||||||
|         gl.enableVertexAttribArray(1); |  | ||||||
|         // Texture Coord |  | ||||||
|         gl.vertexAttribPointer(2, 2, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), @intToPtr(?*anyopaque, (6 * @sizeOf(f32)))); |  | ||||||
|         gl.enableVertexAttribArray(2); |  | ||||||
|  |  | ||||||
|         return .{ vao_id, vbo_id, ebo_id }; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn generateTexture(buf: []const u8) c_uint { |  | ||||||
|         var tex_id: c_uint = undefined; |  | ||||||
|         gl.genTextures(1, &tex_id); |  | ||||||
|         gl.bindTexture(gl.TEXTURE_2D, tex_id); |  | ||||||
|  |  | ||||||
|         // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); |  | ||||||
|         // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); |  | ||||||
|  |  | ||||||
|         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); |  | ||||||
|         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); |  | ||||||
|  |  | ||||||
|         gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gba_width, gba_height, 0, gl.RGBA, gl.UNSIGNED_INT_8_8_8_8, buf.ptr); |  | ||||||
|         // gl.generateMipmap(gl.TEXTURE_2D); // TODO: Remove? |  | ||||||
|  |  | ||||||
|         return tex_id; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn run(self: *Self, cpu: *Arm7tdmi, scheduler: *Scheduler) !void { |     pub fn run(self: *Self, cpu: *Arm7tdmi, scheduler: *Scheduler) !void { | ||||||
|         var quit = std.atomic.Atomic(bool).init(false); |         var quit = std.atomic.Atomic(bool).init(false); | ||||||
|         var tracker = FpsTracker.init(); |         var frame_rate = FpsTracker.init(); | ||||||
|  |  | ||||||
|         const thread = try std.Thread.spawn(.{}, emu.run, .{ &quit, scheduler, cpu, &tracker }); |         const thread = try std.Thread.spawn(.{}, emu.run, .{ &quit, &frame_rate, scheduler, cpu }); | ||||||
|         defer thread.join(); |         defer thread.join(); | ||||||
|  |  | ||||||
|         var title_buf: [0x100]u8 = [_]u8{0} ** 0x100; |         var title_buf: [0x100]u8 = [_]u8{0} ** 0x100; | ||||||
|  |  | ||||||
|         const vao_id = Self.generateBuffers()[0]; |  | ||||||
|         _ = Self.generateTexture(cpu.bus.ppu.framebuf.get(.Renderer)); |  | ||||||
|  |  | ||||||
|         emu_loop: while (true) { |         emu_loop: while (true) { | ||||||
|             var event: SDL.SDL_Event = undefined; |             var event: SDL.SDL_Event = undefined; | ||||||
|             while (SDL.SDL_PollEvent(&event) != 0) { |             while (SDL.SDL_PollEvent(&event) != 0) { | ||||||
| @@ -220,14 +123,11 @@ pub const Gui = struct { | |||||||
|  |  | ||||||
|             // Emulator has an internal Double Buffer |             // Emulator has an internal Double Buffer | ||||||
|             const framebuf = cpu.bus.ppu.framebuf.get(.Renderer); |             const framebuf = cpu.bus.ppu.framebuf.get(.Renderer); | ||||||
|             gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gba_width, gba_height, gl.RGBA, gl.UNSIGNED_INT_8_8_8_8, framebuf.ptr); |             _ = SDL.SDL_UpdateTexture(self.texture, null, framebuf.ptr, pitch); | ||||||
|  |             _ = SDL.SDL_RenderCopy(self.renderer, self.texture, null, null); | ||||||
|  |             SDL.SDL_RenderPresent(self.renderer); | ||||||
|  |  | ||||||
|             gl.useProgram(self.program_id); |             const dyn_title = std.fmt.bufPrint(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, frame_rate.value() }) catch unreachable; | ||||||
|             gl.bindVertexArray(vao_id); |  | ||||||
|             gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null); |  | ||||||
|             SDL.SDL_GL_SwapWindow(self.window); |  | ||||||
|  |  | ||||||
|             const dyn_title = std.fmt.bufPrint(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, tracker.value() }) catch unreachable; |  | ||||||
|             SDL.SDL_SetWindowTitle(self.window, dyn_title.ptr); |             SDL.SDL_SetWindowTitle(self.window, dyn_title.ptr); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -236,18 +136,12 @@ pub const Gui = struct { | |||||||
|  |  | ||||||
|     pub fn deinit(self: *Self) void { |     pub fn deinit(self: *Self) void { | ||||||
|         self.audio.deinit(); |         self.audio.deinit(); | ||||||
|         // TODO: Buffer deletions |         SDL.SDL_DestroyTexture(self.texture); | ||||||
|         gl.deleteProgram(self.program_id); |         SDL.SDL_DestroyRenderer(self.renderer); | ||||||
|         SDL.SDL_GL_DeleteContext(self.ctx); |  | ||||||
|         SDL.SDL_DestroyWindow(self.window); |         SDL.SDL_DestroyWindow(self.window); | ||||||
|         SDL.SDL_Quit(); |         SDL.SDL_Quit(); | ||||||
|         self.* = undefined; |         self.* = undefined; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn glGetProcAddress(ctx: SDL.SDL_GLContext, proc: [:0]const u8) ?*anyopaque { |  | ||||||
|         _ = ctx; |  | ||||||
|         return SDL.SDL_GL_GetProcAddress(proc.ptr); |  | ||||||
|     } |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const Audio = struct { | const Audio = struct { | ||||||
| @@ -282,16 +176,10 @@ const Audio = struct { | |||||||
|  |  | ||||||
|     export fn callback(userdata: ?*anyopaque, stream: [*c]u8, len: c_int) void { |     export fn callback(userdata: ?*anyopaque, stream: [*c]u8, len: c_int) void { | ||||||
|         const apu = @ptrCast(*Apu, @alignCast(@alignOf(*Apu), userdata)); |         const apu = @ptrCast(*Apu, @alignCast(@alignOf(*Apu), userdata)); | ||||||
|  |         _ = SDL.SDL_AudioStreamGet(apu.stream, stream, len); | ||||||
|         // TODO: Find a better way to mute this |  | ||||||
|         if (!config.config().host.mute) { |  | ||||||
|             _ = SDL.SDL_AudioStreamGet(apu.stream, stream, len); |  | ||||||
|         } else { |  | ||||||
|             // FIXME: I don't think this hack to remove DC Offset is acceptable :thinking: |  | ||||||
|             std.mem.set(u8, stream[0..@intCast(usize, len)], 0x40); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // If we don't write anything, play silence otherwise garbage will be played |         // If we don't write anything, play silence otherwise garbage will be played | ||||||
|  |         // FIXME: I don't think this hack to remove DC Offset is acceptable :thinking: | ||||||
|         // if (written == 0) std.mem.set(u8, stream[0..@intCast(usize, len)], 0x40); |         // if (written == 0) std.mem.set(u8, stream[0..@intCast(usize, len)], 0x40); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,25 +0,0 @@ | |||||||
| #version 330 core |  | ||||||
| out vec4 frag_color; |  | ||||||
|  |  | ||||||
| in vec3 color; |  | ||||||
| in vec2 uv; |  | ||||||
|  |  | ||||||
| uniform sampler2D screen; |  | ||||||
|  |  | ||||||
| void main() { |  | ||||||
| 	// https://near.sh/video/color-emulation |  | ||||||
| 	// Thanks to Talarubi + Near for the Colour Correction |  | ||||||
| 	// Thanks to fleur + mattrb for the Shader Impl |  | ||||||
|  |  | ||||||
| 	vec4 color = texture(screen, uv); |  | ||||||
| 	color.rgb = pow(color.rgb, vec3(4.0)); // LCD Gamma |  | ||||||
|    |  | ||||||
| 	frag_color = vec4( |  | ||||||
| 		pow(vec3( |  | ||||||
| 		  	  0 * color.b +  50 * color.g + 255 * color.r, |  | ||||||
| 	     	 30 * color.b + 230 * color.g +  10 * color.r, |  | ||||||
| 			220 * color.b +  10 * color.g +  50 * color.r |  | ||||||
| 		) / 255, vec3(1.0 / 2.2)), // Out Gamma |  | ||||||
| 	1.0);  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| #version 330 core |  | ||||||
| layout (location = 0) in vec3 pos; |  | ||||||
| layout (location = 1) in vec3 in_color; |  | ||||||
| layout (location = 2) in vec2 in_uv; |  | ||||||
|  |  | ||||||
| out vec3 color; |  | ||||||
| out vec2 uv; |  | ||||||
|  |  | ||||||
| void main() { |  | ||||||
| 	color = in_color; |  | ||||||
| 	uv = in_uv; |  | ||||||
| 	gl_Position = vec4(pos, 1.0); |  | ||||||
| } |  | ||||||
							
								
								
									
										106
									
								
								src/util.zig
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								src/util.zig
									
									
									
									
									
								
							| @@ -1,12 +1,12 @@ | |||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| const builtin = @import("builtin"); | const builtin = @import("builtin"); | ||||||
| const config = @import("config.zig"); |  | ||||||
|  |  | ||||||
| const Log2Int = std.math.Log2Int; | const Log2Int = std.math.Log2Int; | ||||||
| const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi; | const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi; | ||||||
|  |  | ||||||
| const Allocator = std.mem.Allocator; | const Allocator = std.mem.Allocator; | ||||||
|  |  | ||||||
|  | const allow_unhandled_io = @import("core/emu.zig").allow_unhandled_io; | ||||||
|  |  | ||||||
| // Sign-Extend value of type `T` to type `U` | // Sign-Extend value of type `T` to type `U` | ||||||
| pub fn sext(comptime T: type, comptime U: type, value: T) T { | pub fn sext(comptime T: type, comptime U: type, value: T) T { | ||||||
|     // U must have less bits than T |     // U must have less bits than T | ||||||
| @@ -146,10 +146,8 @@ pub const io = struct { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         pub fn undef(comptime T: type, log: anytype, comptime format: []const u8, args: anytype) ?T { |         pub fn undef(comptime T: type, log: anytype, comptime format: []const u8, args: anytype) ?T { | ||||||
|             const unhandled_io = config.config().debug.unhandled_io; |  | ||||||
|  |  | ||||||
|             log.warn(format, args); |             log.warn(format, args); | ||||||
|             if (builtin.mode == .Debug and !unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{}); |             if (builtin.mode == .Debug and !allow_unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{}); | ||||||
|  |  | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
| @@ -157,13 +155,22 @@ pub const io = struct { | |||||||
|  |  | ||||||
|     pub const write = struct { |     pub const write = struct { | ||||||
|         pub fn undef(log: anytype, comptime format: []const u8, args: anytype) void { |         pub fn undef(log: anytype, comptime format: []const u8, args: anytype) void { | ||||||
|             const unhandled_io = config.config().debug.unhandled_io; |  | ||||||
|  |  | ||||||
|             log.warn(format, args); |             log.warn(format, args); | ||||||
|             if (builtin.mode == .Debug and !unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{}); |             if (builtin.mode == .Debug and !allow_unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{}); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
|  | pub fn readUndefined(log: anytype, comptime format: []const u8, args: anytype) u8 { | ||||||
|  |     log.warn(format, args); | ||||||
|  |     if (builtin.mode == .Debug) std.debug.panic("TODO: Implement I/O Register", .{}); | ||||||
|  |  | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn writeUndefined(log: anytype, comptime format: []const u8, args: anytype) void { | ||||||
|  |     log.warn(format, args); | ||||||
|  |     if (builtin.mode == .Debug) std.debug.panic("TODO: Implement I/O Register", .{}); | ||||||
|  | } | ||||||
|  |  | ||||||
| pub const Logger = struct { | pub const Logger = struct { | ||||||
|     const Self = @This(); |     const Self = @This(); | ||||||
| @@ -179,7 +186,6 @@ pub const Logger = struct { | |||||||
|  |  | ||||||
|     pub fn print(self: *Self, comptime format: []const u8, args: anytype) !void { |     pub fn print(self: *Self, comptime format: []const u8, args: anytype) !void { | ||||||
|         try self.buf.writer().print(format, args); |         try self.buf.writer().print(format, args); | ||||||
|         try self.buf.flush(); // FIXME: On panics, whatever is in the buffer isn't written to file |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn mgbaLog(self: *Self, cpu: *const Arm7tdmi, opcode: u32) void { |     pub fn mgbaLog(self: *Self, cpu: *const Arm7tdmi, opcode: u32) void { | ||||||
| @@ -219,92 +225,14 @@ pub const Logger = struct { | |||||||
|             cpu.r[12], |             cpu.r[12], | ||||||
|             cpu.r[13], |             cpu.r[13], | ||||||
|             cpu.r[14], |             cpu.r[14], | ||||||
|             cpu.r[15] - if (cpu.cpsr.t.read()) 2 else @as(u32, 4), |             cpu.r[15], | ||||||
|             cpu.cpsr.raw, |             cpu.cpsr.raw, | ||||||
|             opcode, |             opcode, | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| pub const audio = struct { | // Double Buffering Implementation | ||||||
|     const _io = @import("core/bus/io.zig"); |  | ||||||
|  |  | ||||||
|     const ToneSweep = @import("core/apu/ToneSweep.zig"); |  | ||||||
|     const Tone = @import("core/apu/Tone.zig"); |  | ||||||
|     const Wave = @import("core/apu/Wave.zig"); |  | ||||||
|     const Noise = @import("core/apu/Noise.zig"); |  | ||||||
|  |  | ||||||
|     pub const length = struct { |  | ||||||
|         const FrameSequencer = @import("core/apu.zig").FrameSequencer; |  | ||||||
|  |  | ||||||
|         /// Update State of Ch1, Ch2 and Ch3 length timer |  | ||||||
|         pub fn update(comptime T: type, self: *T, fs: *const FrameSequencer, nrx34: _io.Frequency) void { |  | ||||||
|             comptime std.debug.assert(T == ToneSweep or T == Tone or T == Wave); |  | ||||||
|  |  | ||||||
|             // Write to NRx4 when FS's next step is not one that clocks the length counter |  | ||||||
|             if (!fs.isLengthNext()) { |  | ||||||
|                 // If length_enable was disabled but is now enabled and length timer is not 0 already, |  | ||||||
|                 // decrement the length timer |  | ||||||
|  |  | ||||||
|                 if (!self.freq.length_enable.read() and nrx34.length_enable.read() and self.len_dev.timer != 0) { |  | ||||||
|                     self.len_dev.timer -= 1; |  | ||||||
|  |  | ||||||
|                     // If Length Timer is now 0 and trigger is clear, disable the channel |  | ||||||
|                     if (self.len_dev.timer == 0 and !nrx34.trigger.read()) self.enabled = false; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         pub const ch4 = struct { |  | ||||||
|             /// update state of ch4 length timer |  | ||||||
|             pub fn update(self: *Noise, fs: *const FrameSequencer, nr44: _io.NoiseControl) void { |  | ||||||
|                 // Write to NRx4 when FS's next step is not one that clocks the length counter |  | ||||||
|                 if (!fs.isLengthNext()) { |  | ||||||
|                     // If length_enable was disabled but is now enabled and length timer is not 0 already, |  | ||||||
|                     // decrement the length timer |  | ||||||
|  |  | ||||||
|                     if (!self.cnt.length_enable.read() and nr44.length_enable.read() and self.len_dev.timer != 0) { |  | ||||||
|                         self.len_dev.timer -= 1; |  | ||||||
|  |  | ||||||
|                         // If Length Timer is now 0 and trigger is clear, disable the channel |  | ||||||
|                         if (self.len_dev.timer == 0 and !nr44.trigger.read()) self.enabled = false; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|     }; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /// Sets the high bits of an integer to a value |  | ||||||
| pub inline fn setHi(comptime T: type, left: T, right: HalfInt(T)) T { |  | ||||||
|     return switch (T) { |  | ||||||
|         u32 => (left & 0xFFFF_0000) | right, |  | ||||||
|         u16 => (left & 0xFF00) | right, |  | ||||||
|         u8 => (left & 0xF0) | right, |  | ||||||
|         else => @compileError("unsupported type"), |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// sets the low bits of an integer to a value |  | ||||||
| pub inline fn setLo(comptime T: type, left: T, right: HalfInt(T)) T { |  | ||||||
|     return switch (T) { |  | ||||||
|         u32 => (left & 0x0000_FFFF) | @as(u32, right) << 16, |  | ||||||
|         u16 => (left & 0x00FF) | @as(u16, right) << 8, |  | ||||||
|         u8 => (left & 0x0F) | @as(u8, right) << 4, |  | ||||||
|         else => @compileError("unsupported type"), |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// The Integer type which corresponds to T with exactly half the amount of bits |  | ||||||
| fn HalfInt(comptime T: type) type { |  | ||||||
|     const type_info = @typeInfo(T); |  | ||||||
|     comptime std.debug.assert(type_info == .Int); // Type must be an integer |  | ||||||
|     comptime std.debug.assert(type_info.Int.bits % 2 == 0); // Type must have an even amount of bits |  | ||||||
|  |  | ||||||
|     return std.meta.Int(type_info.Int.signedness, type_info.Int.bits >> 1); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Double Buffering Implementation |  | ||||||
| pub const FrameBuffer = struct { | pub const FrameBuffer = struct { | ||||||
|     const Self = @This(); |     const Self = @This(); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user