Compare commits
	
		
			17 Commits
		
	
	
		
			64b1bdbe19
			...
			2afd6dbf9e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2afd6dbf9e | |||
| 422e0d00b8 | |||
| 32e54c67bf | |||
| 0ad017cf43 | |||
| 62db837442 | |||
| 117a95d3a9 | |||
| ed72427c71 | |||
| 7f98f4cc26 | |||
| 51076597e8 | |||
| 14f5682095 | |||
| 334cd432d4 | |||
| b903f2c4a8 | |||
| f52e1ab6b9 | |||
| ea63b04056 | |||
| e0171e5b65 | |||
| 3a1ebfb6e6 | |||
| 16233f3cd8 | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -3,3 +3,6 @@ zig-out/ | |||||||
| bin/ | bin/ | ||||||
| doc/ | doc/ | ||||||
| imgui.ini | imgui.ini | ||||||
|  | .build_config/ | ||||||
|  |  | ||||||
|  | **/*.log | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -4,6 +4,3 @@ | |||||||
| [submodule "lib/zgui"] | [submodule "lib/zgui"] | ||||||
| 	path = lib/zgui | 	path = lib/zgui | ||||||
| 	url = https://git.musuka.dev/paoda/zgui | 	url = https://git.musuka.dev/paoda/zgui | ||||||
| [submodule "lib/arm32"] |  | ||||||
| 	path = lib/arm32 |  | ||||||
| 	url = https://git.musuka.dev/paoda/arm32.git |  | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								build.zig
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								build.zig
									
									
									
									
									
								
							| @@ -1,8 +1,7 @@ | |||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
|  |  | ||||||
| const Sdk = @import("lib/SDL.zig/Sdk.zig"); | const Sdk = @import("lib/SDL.zig/build.zig"); | ||||||
| const zgui = @import("lib/zgui/build.zig"); | const zgui = @import("lib/zgui/build.zig"); | ||||||
| const arm32 = @import("lib/arm32/build.zig"); |  | ||||||
|  |  | ||||||
| // Although this function looks imperative, note that its job is to | // Although this function looks imperative, note that its job is to | ||||||
| // declaratively construct a build graph that will be executed by an external | // declaratively construct a build graph that will be executed by an external | ||||||
| @@ -28,21 +27,23 @@ pub fn build(b: *std.Build) void { | |||||||
|         .optimize = optimize, |         .optimize = optimize, | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     exe.addModule("arm32", arm32.module(b)); |     exe.root_module.addImport("arm32", b.dependency("arm32", .{}).module("arm32")); | ||||||
|     exe.addModule("zig-clap", b.dependency("zig-clap", .{}).module("clap")); |     exe.root_module.addImport("gdbstub", b.dependency("zba-gdbstub", .{}).module("gdbstub")); | ||||||
|  |     exe.root_module.addImport("zig-clap", b.dependency("zig-clap", .{}).module("clap")); | ||||||
|  |  | ||||||
|     exe.addAnonymousModule("bitfield", .{ .source_file = .{ .path = "lib/bitfield.zig" } }); // https://github.com/FlorenceOS/ |     exe.root_module.addAnonymousImport("bitfield", .{ .root_source_file = .{ .path = "lib/bitfield.zig" } }); // https://github.com/FlorenceOS/ | ||||||
|     exe.addAnonymousModule("gl", .{ .source_file = .{ .path = "lib/gl.zig" } }); // https://github.com/MasterQ32/zig-opengl |     exe.root_module.addAnonymousImport("gl", .{ .root_source_file = .{ .path = "lib/gl.zig" } }); // https://github.com/MasterQ32/zig-opengl | ||||||
|  |  | ||||||
|     // https://github.com/MasterQ32/SDL.zig |     // https://github.com/MasterQ32/SDL.zig | ||||||
|     const sdk = Sdk.init(b, null); |     const sdk = Sdk.init(b, null); | ||||||
|     sdk.link(exe, .dynamic); |     sdk.link(exe, .static); | ||||||
|     exe.addModule("sdl2", sdk.getNativeModule()); |     exe.root_module.addImport("sdl2", sdk.getNativeModule()); | ||||||
|  |  | ||||||
|     // https://git.musuka.dev/paoda/zgui |     // https://git.musuka.dev/paoda/zgui | ||||||
|     // .shared option should stay in sync with SDL.zig call above where true == .dynamic, and false == .static |  | ||||||
|     const zgui_pkg = zgui.package(b, target, optimize, .{ .options = .{ .backend = .sdl2_opengl3, .shared = true } }); |     const zgui_pkg = zgui.package(b, target, optimize, .{ .options = .{ .backend = .sdl2_opengl3 } }); | ||||||
|     zgui_pkg.link(exe); |     zgui_pkg.link(exe); | ||||||
|  |     sdk.link(zgui_pkg.zgui_c_cpp, .static); | ||||||
|  |  | ||||||
|     // This declares intent for the executable to be installed into the |     // This declares intent for the executable to be installed into the | ||||||
|     // standard location when the user invokes the "install" step (the default |     // standard location when the user invokes the "install" step (the default | ||||||
|   | |||||||
| @@ -1,15 +1,25 @@ | |||||||
| .{ | .{ | ||||||
|     .name = "turbo", |     .name = "turbo", | ||||||
|     .version = "0.1.0", |     .version = "0.1.0", | ||||||
|  |     .paths = .{ | ||||||
|  |         "lib/bitfield.zig", | ||||||
|  |         "lib/gl.zig", | ||||||
|  |         "src", | ||||||
|  |         "build.zig", | ||||||
|  |         "build.zig.zon", | ||||||
|  |     }, | ||||||
|     .dependencies = .{ |     .dependencies = .{ | ||||||
|         .@"zig-clap" = .{ |         .@"zig-clap" = .{ | ||||||
|             .url = "https://github.com/Hejsil/zig-clap/archive/f49b94700e0761b7514abdca0e4f0e7f3f938a93.tar.gz", |             .url = "https://github.com/Hejsil/zig-clap/archive/4267b0b60ef6f87cccf3ee6ed481e6d0759180c6.tar.gz", | ||||||
|             .hash = "1220f48518ce22882e102255ed3bcdb7aeeb4891f50b2cdd3bd74b5b2e24d3149ba2", |             .hash = "12202fa30d679d821292bcd953458b9e76097a5d16999489125a206db63a53392833", | ||||||
|         }, |         }, | ||||||
|         .@"zba-util" = .{ |         .@"zba-gdbstub" = .{ | ||||||
|             // Necessary to use paoda/arm32 as a git submodule |             .url = "https://git.musuka.dev/paoda/zba-gdbstub/archive/7ae72ed5a892d2fc6cc3f5511e2b96134d928b59.tar.gz", | ||||||
|             .url = "https://git.musuka.dev/paoda/zba-util/archive/322c798e384a0d24cc84ffcfa2e4a3ca807798a0.tar.gz", |             .hash = "1220823e961f369e22b62edc1b4da3742af0a7cb420ae9a52ec33216ff5a8ef270c8", | ||||||
|             .hash = "12209ce0e729460b997706e47a53a32f1842672cd120189e612f4871731780a30ed0", |         }, | ||||||
|  |         .arm32 = .{ | ||||||
|  |             .url = "https://git.musuka.dev/paoda/arm32/archive/6f0e27136072610e6dba97ff8aaf5e2ec86e2c09.tar.gz", | ||||||
|  |             .hash = "122047d0affe12b9e9e9c655a7ba6d51b311f02d688e9f1c9a91394a03103f1c0cd5", | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								dl_sdl2.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								dl_sdl2.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | $SDL2Version = "2.30.0" | ||||||
|  | $ArchiveFile = ".\SDL2-devel-mingw.zip" | ||||||
|  | $Json = @" | ||||||
|  | { | ||||||
|  | 	"x86_64-windows-gnu": { | ||||||
|  | 		"include": "SDL2\\include", | ||||||
|  | 		"libs": "SDL2\\lib", | ||||||
|  | 		"bin": "SDL2\\bin" | ||||||
|  | 	} | ||||||
|  | }	 | ||||||
|  | "@ | ||||||
|  |  | ||||||
|  | New-Item -Force -ItemType Directory -Path .\.build_config | ||||||
|  | Set-Location -Path .build_config -PassThru | ||||||
|  |  | ||||||
|  | if (!(Test-Path -PathType Leaf $ArchiveFile)) { | ||||||
|  | 	Invoke-WebRequest "https://github.com/libsdl-org/SDL/releases/download/release-$SDL2Version/SDL2-devel-$SDL2Version-mingw.zip" -OutFile $ArchiveFile | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Expand-Archive $ArchiveFile | ||||||
|  |  | ||||||
|  | if (Test-Path -PathType Leaf .\SDL2) { | ||||||
|  | 	Remove-Item -Recurse .\SDL2 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | New-Item -Force -ItemType Directory -Path .\SDL2  | ||||||
|  | Get-ChildItem -Path ".\SDL2-devel-mingw\SDL2-$SDL2Version\x86_64-w64-mingw32" | Move-Item -Destination .\SDL2 | ||||||
|  |  | ||||||
|  | New-Item -Force .\sdl.json -Value $Json | ||||||
|  |  | ||||||
|  | Remove-Item -Recurse .\SDL2-devel-mingw | ||||||
|  | Set-Location -Path .. -PassThru | ||||||
 Submodule lib/SDL.zig updated: 80e7409e21...6d42434c4d
									
								
							 Submodule lib/arm32 deleted from dcff3fd588
									
								
							
							
								
								
									
										2
									
								
								lib/zgui
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								lib/zgui
									
									
									
									
									
								
							 Submodule lib/zgui updated: ca27a47224...1fff275f8d
									
								
							
							
								
								
									
										261
									
								
								src/core/emu.zig
									
									
									
									
									
								
							
							
						
						
									
										261
									
								
								src/core/emu.zig
									
									
									
									
									
								
							| @@ -2,9 +2,13 @@ const std = @import("std"); | |||||||
|  |  | ||||||
| const Header = @import("cartridge.zig").Header; | const Header = @import("cartridge.zig").Header; | ||||||
| const Scheduler = @import("Scheduler.zig"); | const Scheduler = @import("Scheduler.zig"); | ||||||
|  | const Ui = @import("../platform.zig").Ui; | ||||||
|  |  | ||||||
| const Allocator = std.mem.Allocator; | const Allocator = std.mem.Allocator; | ||||||
|  |  | ||||||
|  | const dma7 = @import("nds7/dma.zig"); | ||||||
|  | const dma9 = @import("nds9/dma.zig"); | ||||||
|  |  | ||||||
| /// Load a NDS Cartridge | /// Load a NDS Cartridge | ||||||
| /// | /// | ||||||
| /// intended to be used immediately after Emulator initialization | /// intended to be used immediately after Emulator initialization | ||||||
| @@ -110,27 +114,19 @@ pub fn runFrame(scheduler: *Scheduler, system: System) void { | |||||||
|         switch (isHalted(system)) { |         switch (isHalted(system)) { | ||||||
|             .both => scheduler.tick = scheduler.peekTimestamp(), |             .both => scheduler.tick = scheduler.peekTimestamp(), | ||||||
|             inline else => |halt| { |             inline else => |halt| { | ||||||
|                 if (comptime halt != .arm9) { |                 if (!dma9.step(system.arm946es) and comptime halt != .arm9) { | ||||||
|                     system.arm946es.step(); |                     system.arm946es.step(); | ||||||
|                     system.arm946es.step(); |                     system.arm946es.step(); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 if (comptime halt != .arm7) |                 if (!dma7.step(system.arm7tdmi) and comptime halt != .arm7) { | ||||||
|                     system.arm7tdmi.step(); |                     system.arm7tdmi.step(); | ||||||
|  |                 } | ||||||
|             }, |             }, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (scheduler.check()) |ev| { |         if (scheduler.check()) |ev| { | ||||||
|             const late = scheduler.tick - ev.tick; |             const late = scheduler.tick - ev.tick; | ||||||
|  |  | ||||||
|             // this is kinda really jank lol |  | ||||||
|             const bus_ptr: ?*anyopaque = switch (ev.kind) { |  | ||||||
|                 .heat_death => null, |  | ||||||
|                 .nds7 => system.bus7, |  | ||||||
|                 .nds9 => system.bus9, |  | ||||||
|             }; |  | ||||||
|             _ = bus_ptr; |  | ||||||
|  |  | ||||||
|             scheduler.handle(system, ev, late); |             scheduler.handle(system, ev, late); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -270,15 +266,12 @@ pub const Wram = struct { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // TODO: Rename |     pub fn read(self: @This(), comptime T: type, comptime proc: System.Process, address: u32) T { | ||||||
|     const Device = enum { nds9, nds7 }; |  | ||||||
|  |  | ||||||
|     pub fn read(self: @This(), comptime T: type, comptime dev: Device, address: u32) T { |  | ||||||
|         const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; |         const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; | ||||||
|         const masked_addr = address & (addr_space_size - 1); |         const masked_addr = address & (addr_space_size - 1); | ||||||
|         const page = masked_addr >> bits; |         const page = masked_addr >> bits; | ||||||
|         const offset = masked_addr & (page_size - 1); |         const offset = masked_addr & (page_size - 1); | ||||||
|         const table = if (dev == .nds9) self.nds9_table else self.nds7_table; |         const table = if (proc == .nds9) self.nds9_table else self.nds7_table; | ||||||
|  |  | ||||||
|         if (table[page]) |some_ptr| { |         if (table[page]) |some_ptr| { | ||||||
|             const ptr: [*]const T = @ptrCast(@alignCast(some_ptr)); |             const ptr: [*]const T = @ptrCast(@alignCast(some_ptr)); | ||||||
| @@ -286,16 +279,16 @@ pub const Wram = struct { | |||||||
|             return ptr[offset / @sizeOf(T)]; |             return ptr[offset / @sizeOf(T)]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         log.err("{s}: read(T: {}, addr: 0x{X:0>8}) was in un-mapped WRAM space", .{ @tagName(dev), T, 0x0300_0000 + address }); |         log.err("{s}: read(T: {}, addr: 0x{X:0>8}) was in un-mapped WRAM space", .{ @tagName(proc), T, 0x0300_0000 + address }); | ||||||
|         return 0x00; |         return 0x00; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn write(self: *@This(), comptime T: type, comptime dev: Device, address: u32, value: T) void { |     pub fn write(self: *@This(), comptime T: type, comptime proc: System.Process, address: u32, value: T) void { | ||||||
|         const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; |         const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; | ||||||
|         const masked_addr = address & (addr_space_size - 1); |         const masked_addr = address & (addr_space_size - 1); | ||||||
|         const page = masked_addr >> bits; |         const page = masked_addr >> bits; | ||||||
|         const offset = masked_addr & (page_size - 1); |         const offset = masked_addr & (page_size - 1); | ||||||
|         const table = if (dev == .nds9) self.nds9_table else self.nds7_table; |         const table = if (proc == .nds9) self.nds9_table else self.nds7_table; | ||||||
|  |  | ||||||
|         if (table[page]) |some_ptr| { |         if (table[page]) |some_ptr| { | ||||||
|             const ptr: [*]T = @ptrCast(@alignCast(some_ptr)); |             const ptr: [*]T = @ptrCast(@alignCast(some_ptr)); | ||||||
| @@ -304,7 +297,7 @@ pub const Wram = struct { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         log.err("{s}: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8}) was in un-mapped WRAM space", .{ @tagName(dev), T, 0x0300_0000 + address, value }); |         log.err("{s}: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8}) was in un-mapped WRAM space", .{ @tagName(proc), T, 0x0300_0000 + address, value }); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -320,6 +313,8 @@ pub const System = struct { | |||||||
|     pub const Arm7tdmi = @import("arm32").Arm7tdmi; |     pub const Arm7tdmi = @import("arm32").Arm7tdmi; | ||||||
|     pub const Arm946es = @import("arm32").Arm946es; |     pub const Arm946es = @import("arm32").Arm946es; | ||||||
|  |  | ||||||
|  |     pub const Process = enum { nds7, nds9 }; | ||||||
|  |  | ||||||
|     arm7tdmi: *Arm7tdmi, |     arm7tdmi: *Arm7tdmi, | ||||||
|     arm946es: *Arm946es, |     arm946es: *Arm946es, | ||||||
|  |  | ||||||
| @@ -332,17 +327,29 @@ pub const System = struct { | |||||||
|         self.bus7.deinit(allocator); |         self.bus7.deinit(allocator); | ||||||
|         self.bus9.deinit(allocator); |         self.bus9.deinit(allocator); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn Cpu(comptime proc: Process) type { | ||||||
|  |         return switch (proc) { | ||||||
|  |             .nds7 => Arm7tdmi, | ||||||
|  |             .nds9 => Arm946es, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn Bus(comptime proc: Process) type { | ||||||
|  |         return switch (proc) { | ||||||
|  |             .nds7 => Bus7, | ||||||
|  |             .nds9 => Bus9, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // FIXME: Using Wram.Device here is jank. System should probably carry an Enum + some Generic Type Fns | pub fn handleInterrupt(comptime proc: System.Process, cpu: *System.Cpu(proc)) void { | ||||||
| pub fn handleInterrupt(comptime dev: Wram.Device, cpu: if (dev == .nds9) *System.Arm946es else *System.Arm7tdmi) void { |     const bus_ptr: *System.Bus(proc) = @ptrCast(@alignCast(cpu.bus.ptr)); | ||||||
|     const Bus = if (dev == .nds9) System.Bus9 else System.Bus7; |  | ||||||
|     const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr)); |  | ||||||
|  |  | ||||||
|     if (!bus_ptr.io.ime or cpu.cpsr.i.read()) return; // ensure irqs are enabled |     if (!bus_ptr.io.ime or cpu.cpsr.i.read()) return; // ensure irqs are enabled | ||||||
|     if ((bus_ptr.io.ie.raw & bus_ptr.io.irq.raw) == 0) return; // ensure there is an irq to handle |     if ((bus_ptr.io.ie.raw & bus_ptr.io.irq.raw) == 0) return; // ensure there is an irq to handle | ||||||
|  |  | ||||||
|     switch (dev) { |     switch (proc) { | ||||||
|         .nds9 => { |         .nds9 => { | ||||||
|             const cp15: *System.Cp15 = @ptrCast(@alignCast(cpu.cp15.ptr)); |             const cp15: *System.Cp15 = @ptrCast(@alignCast(cpu.cp15.ptr)); | ||||||
|             cp15.wait_for_interrupt = false; |             cp15.wait_for_interrupt = false; | ||||||
| @@ -359,6 +366,208 @@ pub fn handleInterrupt(comptime dev: Wram.Device, cpu: if (dev == .nds9) *System | |||||||
|  |  | ||||||
|     cpu.r[14] = ret_addr; |     cpu.r[14] = ret_addr; | ||||||
|     cpu.spsr.raw = spsr.raw; |     cpu.spsr.raw = spsr.raw; | ||||||
|     cpu.r[15] = if (dev == .nds9) 0xFFFF_0018 else 0x0000_0018; |     cpu.r[15] = if (proc == .nds9) 0xFFFF_0018 else 0x0000_0018; | ||||||
|     cpu.pipe.reload(cpu); |     cpu.pipe.reload(cpu); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn fastBoot(system: System) void { | ||||||
|  |     { | ||||||
|  |         const Bank = System.Arm946es.Bank; | ||||||
|  |  | ||||||
|  |         const cpu = system.arm946es; | ||||||
|  |  | ||||||
|  |         // from advanDS | ||||||
|  |         cpu.spsr = .{ .raw = 0x0000_000DF }; | ||||||
|  |  | ||||||
|  |         @memset(cpu.r[0..12], 0x0000_0000); // r0 -> r11 are zeroed | ||||||
|  |         // TODO: r12, r14, and r15 are set to the entrypoint? | ||||||
|  |         cpu.r[13] = 0x0300_2F7C; // FIXME: Why is there (!) in GBATEK? | ||||||
|  |         cpu.bank.r[Bank.regIdx(.Irq, .R13)] = 0x0300_3F80; | ||||||
|  |         cpu.bank.r[Bank.regIdx(.Supervisor, .R13)] = 0x0300_3FC0; | ||||||
|  |         cpu.bank.spsr[Bank.spsrIdx(.Irq)] = .{ .raw = 0x0000_0000 }; | ||||||
|  |         cpu.bank.spsr[Bank.spsrIdx(.Supervisor)] = .{ .raw = 0x0000_0000 }; | ||||||
|  |     } | ||||||
|  |     { | ||||||
|  |         const Bank = System.Arm7tdmi.Bank; | ||||||
|  |  | ||||||
|  |         const cpu = system.arm7tdmi; | ||||||
|  |  | ||||||
|  |         // from advanDS | ||||||
|  |         cpu.spsr = .{ .raw = 0x0000_000D3 }; | ||||||
|  |  | ||||||
|  |         @memset(cpu.r[0..12], 0x0000_0000); // r0 -> r11 are zeroed | ||||||
|  |         // TODO: r12, r14, and r15 are set to the entrypoint? | ||||||
|  |         cpu.r[13] = 0x0380_FD80; | ||||||
|  |         cpu.bank.r[Bank.regIdx(.Irq, .R13)] = 0x0380_FF80; | ||||||
|  |         cpu.bank.r[Bank.regIdx(.Supervisor, .R13)] = 0x0380_FFC0; | ||||||
|  |         cpu.bank.spsr[Bank.spsrIdx(.Irq)] = .{ .raw = 0x0000_0000 }; | ||||||
|  |         cpu.bank.spsr[Bank.spsrIdx(.Supervisor)] = .{ .raw = 0x0000_0000 }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub const Sync = struct { | ||||||
|  |     const Atomic = std.atomic.Value; | ||||||
|  |  | ||||||
|  |     should_quit: Atomic(bool) = Atomic(bool).init(false), | ||||||
|  |  | ||||||
|  |     pub fn init(self: *Sync) void { | ||||||
|  |         self.* = .{}; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | pub const debug = struct { | ||||||
|  |     const Interface = @import("gdbstub").Emulator; | ||||||
|  |     const Server = @import("gdbstub").Server; | ||||||
|  |     const AtomicBool = std.atomic.Value(bool); | ||||||
|  |     const log = std.log.scoped(.gdbstub); | ||||||
|  |  | ||||||
|  |     const nds7 = struct { | ||||||
|  |         const target: []const u8 = | ||||||
|  |             \\<target version="1.0"> | ||||||
|  |             \\    <architecture>armv4t</architecture> | ||||||
|  |             \\    <feature name="org.gnu.gdb.arm.core"> | ||||||
|  |             \\        <reg name="r0" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r1" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r2" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r3" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r4" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r5" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r6" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r7" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r8" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r9" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r10" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r11" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="r12" bitsize="32" type="uint32"/> | ||||||
|  |             \\        <reg name="sp" bitsize="32" type="data_ptr"/> | ||||||
|  |             \\        <reg name="lr" bitsize="32"/> | ||||||
|  |             \\        <reg name="pc" bitsize="32" type="code_ptr"/> | ||||||
|  |             \\ | ||||||
|  |             \\        <reg name="cpsr" bitsize="32" regnum="25"/> | ||||||
|  |             \\    </feature> | ||||||
|  |             \\</target> | ||||||
|  |         ; | ||||||
|  |  | ||||||
|  |         // Remember that a lot of memory regions are mirrored | ||||||
|  |         const memory_map: []const u8 = | ||||||
|  |             \\ <memory-map version="1.0"> | ||||||
|  |             \\     <memory type="rom" start="0x00000000" length="0x00004000"/> | ||||||
|  |             \\     <memory type="ram" start="0x02000000" length="0x01000000"/> | ||||||
|  |             \\     <memory type="ram" start="0x03000000" length="0x00800000"/> | ||||||
|  |             \\     <memory type="ram" start="0x03800000" length="0x00800000"/> | ||||||
|  |             \\     <memory type="ram" start="0x04000000" length="0x00100010"/> | ||||||
|  |             \\     <memory type="ram" start="0x06000000" length="0x01000000"/> | ||||||
|  |             \\     <memory type="rom" start="0x08000000" length="0x02000000"/> | ||||||
|  |             \\     <memory type="rom" start="0x0A000000" length="0x01000000"/> | ||||||
|  |             \\ </memory-map> | ||||||
|  |         ; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     pub fn Wrapper(comptime proc: System.Process) type { | ||||||
|  |         return struct { | ||||||
|  |             system: System, | ||||||
|  |             scheduler: *Scheduler, | ||||||
|  |  | ||||||
|  |             tick: u64 = 0, | ||||||
|  |  | ||||||
|  |             pub fn init(system: System, scheduler: *Scheduler) @This() { | ||||||
|  |                 return .{ .system = system, .scheduler = scheduler }; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             pub fn interface(self: *@This(), allocator: Allocator) Interface { | ||||||
|  |                 return Interface.init(allocator, self); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             pub fn read(self: *const @This(), addr: u32) u8 { | ||||||
|  |                 const arm = switch (proc) { | ||||||
|  |                     .nds7 => self.system.arm7tdmi, | ||||||
|  |                     .nds9 => self.system.arm946es, | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 return arm.dbgRead(u8, addr); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             pub fn write(self: *@This(), addr: u32, value: u8) void { | ||||||
|  |                 const arm = switch (proc) { | ||||||
|  |                     .nds7 => self.system.arm7tdmi, | ||||||
|  |                     .nds9 => self.system.arm946es, | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 return arm.dbgWrite(u8, addr, value); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             pub fn registers(self: *const @This()) *[16]u32 { | ||||||
|  |                 const arm = switch (proc) { | ||||||
|  |                     .nds7 => self.system.arm7tdmi, | ||||||
|  |                     .nds9 => self.system.arm946es, | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 return &arm.r; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             pub fn cpsr(self: *const @This()) u32 { | ||||||
|  |                 const arm = switch (proc) { | ||||||
|  |                     .nds7 => self.system.arm7tdmi, | ||||||
|  |                     .nds9 => self.system.arm946es, | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 return arm.cpsr.raw; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             pub fn step(self: *@This()) void { | ||||||
|  |                 const scheduler = self.scheduler; | ||||||
|  |                 const system = self.system; | ||||||
|  |  | ||||||
|  |                 var did_step: bool = false; | ||||||
|  |  | ||||||
|  |                 // TODO: keep in lockstep with runFrame | ||||||
|  |                 while (true) { | ||||||
|  |                     if (did_step) break; | ||||||
|  |  | ||||||
|  |                     switch (isHalted(system)) { | ||||||
|  |                         .both => scheduler.tick = scheduler.peekTimestamp(), | ||||||
|  |                         inline else => |halt| { | ||||||
|  |                             if (!dma9.step(system.arm946es) and comptime halt != .arm9) { | ||||||
|  |                                 system.arm946es.step(); | ||||||
|  |  | ||||||
|  |                                 switch (proc) { | ||||||
|  |                                     .nds9 => did_step = true, | ||||||
|  |                                     .nds7 => system.arm946es.step(), | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             if (!dma7.step(system.arm7tdmi) and comptime halt != .arm7) { | ||||||
|  |                                 if (proc == .nds7 or self.tick % 2 == 0) system.arm7tdmi.step(); | ||||||
|  |  | ||||||
|  |                                 if (proc == .nds7) { | ||||||
|  |                                     did_step = true; | ||||||
|  |                                     self.tick += 1; | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (scheduler.check()) |ev| { | ||||||
|  |                         const late = scheduler.tick - ev.tick; | ||||||
|  |                         scheduler.handle(system, ev, late); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn run(allocator: Allocator, ui: *Ui, scheduler: *Scheduler, system: System, sync: *Sync) !void { | ||||||
|  |         var wrapper = Wrapper(.nds9).init(system, scheduler); | ||||||
|  |  | ||||||
|  |         var emu_interface = wrapper.interface(allocator); | ||||||
|  |         defer emu_interface.deinit(); | ||||||
|  |  | ||||||
|  |         var server = try Server.init(emu_interface, .{ .target = nds7.target, .memory_map = nds7.memory_map }); | ||||||
|  |         defer server.deinit(allocator); | ||||||
|  |  | ||||||
|  |         const thread = try std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &sync.should_quit }); | ||||||
|  |         defer thread.join(); | ||||||
|  |  | ||||||
|  |         try ui.debug_run(scheduler, system, sync); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ pub const Io = struct { | |||||||
|     wramcnt: WramCnt = .{ .raw = 0x00 }, |     wramcnt: WramCnt = .{ .raw = 0x00 }, | ||||||
|  |  | ||||||
|     // Read Only |     // Read Only | ||||||
|     keyinput: AtomicKeyInput = .{}, |     input: Input = .{}, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| fn warn(comptime format: []const u8, args: anytype) u0 { | fn warn(comptime format: []const u8, args: anytype) u0 { | ||||||
| @@ -36,8 +36,6 @@ const Ipc = struct { | |||||||
|  |  | ||||||
|     // TODO: DS Cartridge I/O Ports |     // TODO: DS Cartridge I/O Ports | ||||||
|  |  | ||||||
|     const Source = enum { nds7, nds9 }; |  | ||||||
|  |  | ||||||
|     const Impl = struct { |     const Impl = struct { | ||||||
|         /// IPC Synchronize |         /// IPC Synchronize | ||||||
|         /// Read/Write |         /// Read/Write | ||||||
| @@ -60,8 +58,8 @@ const Ipc = struct { | |||||||
|  |  | ||||||
|     /// IPCSYNC |     /// IPCSYNC | ||||||
|     /// Read/Write |     /// Read/Write | ||||||
|     pub fn setIpcSync(self: *@This(), comptime src: Source, value: anytype) void { |     pub fn setIpcSync(self: *@This(), comptime proc: System.Process, value: anytype) void { | ||||||
|         switch (src) { |         switch (proc) { | ||||||
|             .nds7 => { |             .nds7 => { | ||||||
|                 self._nds7.sync.raw = masks.ipcFifoSync(self._nds7.sync.raw, value); |                 self._nds7.sync.raw = masks.ipcFifoSync(self._nds7.sync.raw, value); | ||||||
|                 self._nds9.sync.raw = masks.mask(self._nds9.sync.raw, (self._nds7.sync.raw >> 8) & 0xF, 0xF); |                 self._nds9.sync.raw = masks.mask(self._nds9.sync.raw, (self._nds7.sync.raw >> 8) & 0xF, 0xF); | ||||||
| @@ -109,8 +107,8 @@ const Ipc = struct { | |||||||
|  |  | ||||||
|     /// IPCFIFOCNT |     /// IPCFIFOCNT | ||||||
|     /// Read/Write |     /// Read/Write | ||||||
|     pub fn setIpcFifoCnt(self: *@This(), comptime src: Source, value: anytype) void { |     pub fn setIpcFifoCnt(self: *@This(), comptime proc: System.Process, value: anytype) void { | ||||||
|         switch (src) { |         switch (proc) { | ||||||
|             .nds7 => self._nds7.cnt.raw = masks.ipcFifoCnt(self._nds7.cnt.raw, value), |             .nds7 => self._nds7.cnt.raw = masks.ipcFifoCnt(self._nds7.cnt.raw, value), | ||||||
|             .nds9 => self._nds9.cnt.raw = masks.ipcFifoCnt(self._nds9.cnt.raw, value), |             .nds9 => self._nds9.cnt.raw = masks.ipcFifoCnt(self._nds9.cnt.raw, value), | ||||||
|         } |         } | ||||||
| @@ -118,8 +116,8 @@ const Ipc = struct { | |||||||
|  |  | ||||||
|     /// IPC Send FIFO |     /// IPC Send FIFO | ||||||
|     /// Write-Only |     /// Write-Only | ||||||
|     pub fn send(self: *@This(), comptime src: Source, value: u32) void { |     pub fn send(self: *@This(), comptime proc: System.Process, value: u32) void { | ||||||
|         switch (src) { |         switch (proc) { | ||||||
|             .nds7 => { |             .nds7 => { | ||||||
|                 if (!self._nds7.cnt.enable_fifos.read()) return; |                 if (!self._nds7.cnt.enable_fifos.read()) return; | ||||||
|                 self._nds7.fifo.push(value) catch unreachable; // see early return above |                 self._nds7.fifo.push(value) catch unreachable; // see early return above | ||||||
| @@ -173,8 +171,8 @@ const Ipc = struct { | |||||||
|  |  | ||||||
|     /// IPC Receive FIFO |     /// IPC Receive FIFO | ||||||
|     /// Read-Only |     /// Read-Only | ||||||
|     pub fn recv(self: *@This(), comptime src: Source) u32 { |     pub fn recv(self: *@This(), comptime proc: System.Process) u32 { | ||||||
|         switch (src) { |         switch (proc) { | ||||||
|             .nds7 => { |             .nds7 => { | ||||||
|                 const enabled = self._nds7.cnt.enable_fifos.read(); |                 const enabled = self._nds7.cnt.enable_fifos.read(); | ||||||
|                 const val_opt = if (enabled) self._nds9.fifo.pop() else self._nds9.fifo.peek(); |                 const val_opt = if (enabled) self._nds9.fifo.pop() else self._nds9.fifo.peek(); | ||||||
| @@ -327,6 +325,11 @@ pub const IntEnable = extern union { | |||||||
|     hblank: Bit(u32, 1), |     hblank: Bit(u32, 1), | ||||||
|     coincidence: Bit(u32, 2), |     coincidence: Bit(u32, 2), | ||||||
|  |  | ||||||
|  |     dma0: Bit(u32, 8), | ||||||
|  |     dma1: Bit(u32, 9), | ||||||
|  |     dma2: Bit(u32, 10), | ||||||
|  |     dma3: Bit(u32, 11), | ||||||
|  |  | ||||||
|     ipcsync: Bit(u32, 16), |     ipcsync: Bit(u32, 16), | ||||||
|     ipc_send_empty: Bit(u32, 17), |     ipc_send_empty: Bit(u32, 17), | ||||||
|     ipc_recv_not_empty: Bit(u32, 18), |     ipc_recv_not_empty: Bit(u32, 18), | ||||||
| @@ -410,24 +413,54 @@ pub const KeyInput = extern union { | |||||||
|     raw: u16, |     raw: u16, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const AtomicKeyInput = struct { | pub const ExtKeyIn = extern union { | ||||||
|     const Self = @This(); |     x: Bit(u16, 0), | ||||||
|     const Ordering = std.atomic.Ordering; |     y: Bit(u16, 1), | ||||||
|  |     debug: Bit(u16, 3), | ||||||
|     inner: KeyInput = .{ .raw = 0x03FF }, |     stylus: Bit(u16, 6), | ||||||
|  |     hinge: Bit(u16, 7), | ||||||
|     pub inline fn load(self: *const Self, comptime ordering: Ordering) u16 { |     raw: u16, | ||||||
|         return switch (ordering) { |  | ||||||
|             .AcqRel, .Release => @compileError("not supported for atomic loads"), |  | ||||||
|             else => @atomicLoad(u16, &self.inner.raw, ordering), |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | const Input = struct { | ||||||
|  |     const AtomicOrder = std.builtin.AtomicOrder; | ||||||
|  |     const AtomicRmwOp = std.builtin.AtomicRmwOp; | ||||||
|  |  | ||||||
|  |     inner: u32 = 0x007F_03FF, | ||||||
|  |  | ||||||
|  |     pub inline fn keyinput(self: *const Input) KeyInput { | ||||||
|  |         const value = @atomicLoad(u32, &self.inner, .Monotonic); | ||||||
|  |         return .{ .raw = @truncate(value) }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub inline fn fetchOr(self: *Self, value: u16, comptime ordering: Ordering) void { |     pub inline fn set_keyinput(self: *Input, comptime op: AtomicRmwOp, input: KeyInput) void { | ||||||
|         _ = @atomicRmw(u16, &self.inner.raw, .Or, value, ordering); |         const msked = switch (op) { | ||||||
|  |             .And => 0xFFFF_FFFF & @as(u32, input.raw), | ||||||
|  |             .Or => 0x0000_0000 | @as(u32, input.raw), | ||||||
|  |             else => @compileError("not supported"), | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         _ = @atomicRmw(u32, &self.inner, op, msked, .Monotonic); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub inline fn fetchAnd(self: *Self, value: u16, comptime ordering: Ordering) void { |     pub inline fn extkeyin(self: *const Input) ExtKeyIn { | ||||||
|         _ = @atomicRmw(u16, &self.inner.raw, .And, value, ordering); |         const value = @atomicLoad(u32, &self.inner, .Monotonic); | ||||||
|  |         const shifted: u16 = @truncate(value >> 16); | ||||||
|  |  | ||||||
|  |         return .{ .raw = shifted | 0b00110100 }; // bits 2, 4, 5 are always set | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub inline fn set_extkeyin(self: *Input, comptime op: AtomicRmwOp, input: ExtKeyIn) void { | ||||||
|  |         const msked = switch (op) { | ||||||
|  |             .And => 0xFFFF_FFFF & (@as(u32, ~input.raw) << 16), | ||||||
|  |             .Or => 0x0000_0000 | (@as(u32, input.raw) << 16), | ||||||
|  |             else => @compileError("not supported"), | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         _ = @atomicRmw(u32, &self.inner, op, msked, .Monotonic); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub inline fn set(self: *Input, comptime op: AtomicRmwOp, value: u32) void { | ||||||
|  |         _ = @atomicRmw(u32, &self.inner, op, value, .Monotonic); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -29,7 +29,6 @@ pub fn deinit(self: @This(), allocator: Allocator) void { | |||||||
|  |  | ||||||
| // Note: Parts of 16MiB addrspace that aren't mapped to BIOS are typically undefined | // Note: Parts of 16MiB addrspace that aren't mapped to BIOS are typically undefined | ||||||
| pub fn read(self: *const @This(), comptime T: type, address: u32) T { | pub fn read(self: *const @This(), comptime T: type, address: u32) T { | ||||||
|     const readInt = std.mem.readIntLittle; |  | ||||||
|     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); |     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); | ||||||
|  |  | ||||||
|     // if (address >= len) return 0x0000_0000; // TODO: What is undefined actually? |     // if (address >= len) return 0x0000_0000; // TODO: What is undefined actually? | ||||||
| @@ -39,7 +38,7 @@ pub fn read(self: *const @This(), comptime T: type, address: u32) T { | |||||||
|         @panic("TODO: ability to load in NDS7 BIOS just-in-time"); |         @panic("TODO: ability to load in NDS7 BIOS just-in-time"); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     return readInt(T, ptr[address & (len - 1) ..][0..byte_count]); |     return std.mem.readInt(T, ptr[address & (len - 1) ..][0..byte_count], .little); | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn write(_: *const @This(), comptime T: type, address: u32, value: T) void { | pub fn write(_: *const @This(), comptime T: type, address: u32, value: T) void { | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ const Vram = @import("../ppu/Vram.zig"); | |||||||
| const Bios = @import("Bios.zig"); | const Bios = @import("Bios.zig"); | ||||||
| const forceAlign = @import("../emu.zig").forceAlign; | const forceAlign = @import("../emu.zig").forceAlign; | ||||||
|  |  | ||||||
|  | const Controllers = @import("dma.zig").Controllers; | ||||||
|  |  | ||||||
| const Allocator = std.mem.Allocator; | const Allocator = std.mem.Allocator; | ||||||
|  |  | ||||||
| const Mode = enum { normal, debug }; | const Mode = enum { normal, debug }; | ||||||
| @@ -22,6 +24,8 @@ shr_wram: *Wram, | |||||||
| wram: *[64 * KiB]u8, | wram: *[64 * KiB]u8, | ||||||
| vram: *Vram, | vram: *Vram, | ||||||
|  |  | ||||||
|  | dma: Controllers = .{}, | ||||||
|  |  | ||||||
| io: io.Io, | io: io.Io, | ||||||
| bios: Bios, | bios: Bios, | ||||||
|  |  | ||||||
| @@ -58,8 +62,6 @@ pub fn dbgRead(self: *@This(), comptime T: type, address: u32) T { | |||||||
|  |  | ||||||
| fn _read(self: *@This(), comptime T: type, comptime mode: Mode, address: u32) T { | fn _read(self: *@This(), comptime T: type, comptime mode: Mode, address: u32) T { | ||||||
|     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); |     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); | ||||||
|     const readInt = std.mem.readIntLittle; |  | ||||||
|  |  | ||||||
|     const aligned_addr = forceAlign(T, address); |     const aligned_addr = forceAlign(T, address); | ||||||
|  |  | ||||||
|     switch (mode) { |     switch (mode) { | ||||||
| @@ -69,13 +71,13 @@ fn _read(self: *@This(), comptime T: type, comptime mode: Mode, address: u32) T | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     return switch (aligned_addr) { |     return switch (aligned_addr) { | ||||||
|         0x0000_0000...0x01FF_FFFF => self.bios.read(T, address), |         0x0000_0000...0x01FF_FFFF => self.bios.read(T, aligned_addr), | ||||||
|         0x0200_0000...0x02FF_FFFF => readInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count]), |         0x0200_0000...0x02FF_FFFF => std.mem.readInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count], .little), | ||||||
|         0x0300_0000...0x037F_FFFF => switch (self.io.shr.wramcnt.mode.read()) { |         0x0300_0000...0x037F_FFFF => switch (self.io.shr.wramcnt.mode.read()) { | ||||||
|             0b00 => readInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count]), |             0b00 => std.mem.readInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], .little), | ||||||
|             else => self.shr_wram.read(T, .nds7, aligned_addr), |             else => self.shr_wram.read(T, .nds7, aligned_addr), | ||||||
|         }, |         }, | ||||||
|         0x0380_0000...0x03FF_FFFF => readInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count]), |         0x0380_0000...0x03FF_FFFF => std.mem.readInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], .little), | ||||||
|         0x0400_0000...0x04FF_FFFF => io.read(self, T, aligned_addr), |         0x0400_0000...0x04FF_FFFF => io.read(self, T, aligned_addr), | ||||||
|         0x0600_0000...0x06FF_FFFF => self.vram.read(T, .nds7, aligned_addr), |         0x0600_0000...0x06FF_FFFF => self.vram.read(T, .nds7, aligned_addr), | ||||||
|  |  | ||||||
| @@ -93,7 +95,6 @@ pub fn dbgWrite(self: *@This(), comptime T: type, address: u32, value: T) void { | |||||||
|  |  | ||||||
| fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, value: T) void { | fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, value: T) void { | ||||||
|     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); |     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); | ||||||
|     const writeInt = std.mem.writeIntLittle; |  | ||||||
|  |  | ||||||
|     const aligned_addr = forceAlign(T, address); |     const aligned_addr = forceAlign(T, address); | ||||||
|  |  | ||||||
| @@ -104,13 +105,13 @@ fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, v | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     switch (aligned_addr) { |     switch (aligned_addr) { | ||||||
|         0x0000_0000...0x01FF_FFFF => self.bios.write(T, address, value), |         0x0000_0000...0x01FF_FFFF => self.bios.write(T, aligned_addr, value), | ||||||
|         0x0200_0000...0x02FF_FFFF => writeInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count], value), |         0x0200_0000...0x02FF_FFFF => std.mem.writeInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count], value, .little), | ||||||
|         0x0300_0000...0x037F_FFFF => switch (self.io.shr.wramcnt.mode.read()) { |         0x0300_0000...0x037F_FFFF => switch (self.io.shr.wramcnt.mode.read()) { | ||||||
|             0b00 => writeInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], value), |             0b00 => std.mem.writeInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], value, .little), | ||||||
|             else => self.shr_wram.write(T, .nds7, aligned_addr, value), |             else => self.shr_wram.write(T, .nds7, aligned_addr, value), | ||||||
|         }, |         }, | ||||||
|         0x0380_0000...0x0380_FFFF => writeInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], value), |         0x0380_0000...0x03FF_FFFF => std.mem.writeInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], value, .little), | ||||||
|         0x0400_0000...0x04FF_FFFF => io.write(self, T, aligned_addr, value), |         0x0400_0000...0x04FF_FFFF => io.write(self, T, aligned_addr, value), | ||||||
|         0x0600_0000...0x06FF_FFFF => self.vram.write(T, .nds7, aligned_addr, value), |         0x0600_0000...0x06FF_FFFF => self.vram.write(T, .nds7, aligned_addr, value), | ||||||
|         else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), |         else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||||
|   | |||||||
							
								
								
									
										401
									
								
								src/core/nds7/dma.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										401
									
								
								src/core/nds7/dma.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,401 @@ | |||||||
|  | const std = @import("std"); | ||||||
|  |  | ||||||
|  | const System = @import("../emu.zig").System; | ||||||
|  | const DmaCnt = @import("io.zig").DmaCnt; | ||||||
|  |  | ||||||
|  | const rotr = std.math.rotr; | ||||||
|  | const shift = @import("../../util.zig").shift; | ||||||
|  | const subset = @import("../../util.zig").subset; | ||||||
|  |  | ||||||
|  | const handleInterrupt = @import("../emu.zig").handleInterrupt; | ||||||
|  |  | ||||||
|  | const log = std.log.scoped(.nds7_dma_transfer); | ||||||
|  |  | ||||||
|  | pub const Controllers = struct { | ||||||
|  |     Controller(0) = Controller(0){}, | ||||||
|  |     Controller(1) = Controller(1){}, | ||||||
|  |     Controller(2) = Controller(2){}, | ||||||
|  |     Controller(3) = Controller(3){}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | pub fn read(comptime T: type, dma: *const Controllers, addr: u32) ?T { | ||||||
|  |     const byte_addr: u8 = @truncate(addr); | ||||||
|  |  | ||||||
|  |     return switch (T) { | ||||||
|  |         u32 => switch (byte_addr) { | ||||||
|  |             0xB0, 0xB4 => null, // DMA0SAD, DMA0DAD, | ||||||
|  |             0xB8 => @as(T, dma.*[0].dmacntH()) << 16, // DMA0CNT_L is write-only | ||||||
|  |             0xBC, 0xC0 => null, // DMA1SAD, DMA1DAD | ||||||
|  |             0xC4 => @as(T, dma.*[1].dmacntH()) << 16, // DMA1CNT_L is write-only | ||||||
|  |             0xC8, 0xCC => null, // DMA2SAD, DMA2DAD | ||||||
|  |             0xD0 => @as(T, dma.*[2].dmacntH()) << 16, // DMA2CNT_L is write-only | ||||||
|  |             0xD4, 0xD8 => null, // DMA3SAD, DMA3DAD | ||||||
|  |             0xDC => @as(T, dma.*[3].dmacntH()) << 16, // DMA3CNT_L is write-only | ||||||
|  |             else => warn("unaligned {} read from 0x{X:0>8}", .{ T, addr }), | ||||||
|  |         }, | ||||||
|  |         u16 => switch (byte_addr) { | ||||||
|  |             0xB0, 0xB2, 0xB4, 0xB6 => null, // DMA0SAD, DMA0DAD | ||||||
|  |             0xB8 => 0x0000, // DMA0CNT_L, suite.gba expects 0x0000 instead of 0xDEAD | ||||||
|  |             0xBA => dma.*[0].dmacntH(), | ||||||
|  |  | ||||||
|  |             0xBC, 0xBE, 0xC0, 0xC2 => null, // DMA1SAD, DMA1DAD | ||||||
|  |             0xC4 => 0x0000, // DMA1CNT_L | ||||||
|  |             0xC6 => dma.*[1].dmacntH(), | ||||||
|  |  | ||||||
|  |             0xC8, 0xCA, 0xCC, 0xCE => null, // DMA2SAD, DMA2DAD | ||||||
|  |             0xD0 => 0x0000, // DMA2CNT_L | ||||||
|  |             0xD2 => dma.*[2].dmacntH(), | ||||||
|  |  | ||||||
|  |             0xD4, 0xD6, 0xD8, 0xDA => null, // DMA3SAD, DMA3DAD | ||||||
|  |             0xDC => 0x0000, // DMA3CNT_L | ||||||
|  |             0xDE => dma.*[3].dmacntH(), | ||||||
|  |             else => warn("unaligned {} read from 0x{X:0>8}", .{ T, addr }), | ||||||
|  |         }, | ||||||
|  |         u8 => switch (byte_addr) { | ||||||
|  |             0xB0...0xB7 => null, // DMA0SAD, DMA0DAD | ||||||
|  |             0xB8, 0xB9 => 0x00, // DMA0CNT_L | ||||||
|  |             0xBA, 0xBB => @truncate(dma.*[0].dmacntH() >> shift(u16, byte_addr)), | ||||||
|  |  | ||||||
|  |             0xBC...0xC3 => null, // DMA1SAD, DMA1DAD | ||||||
|  |             0xC4, 0xC5 => 0x00, // DMA1CNT_L | ||||||
|  |             0xC6, 0xC7 => @truncate(dma.*[1].dmacntH() >> shift(u16, byte_addr)), | ||||||
|  |  | ||||||
|  |             0xC8...0xCF => null, // DMA2SAD, DMA2DAD | ||||||
|  |             0xD0, 0xD1 => 0x00, // DMA2CNT_L | ||||||
|  |             0xD2, 0xD3 => @truncate(dma.*[2].dmacntH() >> shift(u16, byte_addr)), | ||||||
|  |  | ||||||
|  |             0xD4...0xDB => null, // DMA3SAD, DMA3DAD | ||||||
|  |             0xDC, 0xDD => 0x00, // DMA3CNT_L | ||||||
|  |             0xDE, 0xDF => @truncate(dma.*[3].dmacntH() >> shift(u16, byte_addr)), | ||||||
|  |             else => warn("unexpected {} read from 0x{X:0>8}", .{ T, addr }), | ||||||
|  |         }, | ||||||
|  |         else => @compileError("DMA: Unsupported read width"), | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn write(comptime T: type, dma: *Controllers, addr: u32, value: T) void { | ||||||
|  |     const byte_addr: u8 = @truncate(addr); | ||||||
|  |  | ||||||
|  |     switch (T) { | ||||||
|  |         u32 => switch (byte_addr) { | ||||||
|  |             0xB0 => dma.*[0].setDmasad(value), | ||||||
|  |             0xB4 => dma.*[0].setDmadad(value), | ||||||
|  |             0xB8 => dma.*[0].setDmacnt(value), | ||||||
|  |  | ||||||
|  |             0xBC => dma.*[1].setDmasad(value), | ||||||
|  |             0xC0 => dma.*[1].setDmadad(value), | ||||||
|  |             0xC4 => dma.*[1].setDmacnt(value), | ||||||
|  |  | ||||||
|  |             0xC8 => dma.*[2].setDmasad(value), | ||||||
|  |             0xCC => dma.*[2].setDmadad(value), | ||||||
|  |             0xD0 => dma.*[2].setDmacnt(value), | ||||||
|  |  | ||||||
|  |             0xD4 => dma.*[3].setDmasad(value), | ||||||
|  |             0xD8 => dma.*[3].setDmadad(value), | ||||||
|  |             0xDC => dma.*[3].setDmacnt(value), | ||||||
|  |             else => log.warn("Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
|  |         }, | ||||||
|  |         u16 => switch (byte_addr) { | ||||||
|  |             0xB0, 0xB2 => dma.*[0].setDmasad(subset(u32, u16, byte_addr, dma.*[0].sad, value)), | ||||||
|  |             0xB4, 0xB6 => dma.*[0].setDmadad(subset(u32, u16, byte_addr, dma.*[0].dad, value)), | ||||||
|  |             0xB8 => dma.*[0].setDmacntL(value), | ||||||
|  |             0xBA => dma.*[0].setDmacntH(value), | ||||||
|  |  | ||||||
|  |             0xBC, 0xBE => dma.*[1].setDmasad(subset(u32, u16, byte_addr, dma.*[1].sad, value)), | ||||||
|  |             0xC0, 0xC2 => dma.*[1].setDmadad(subset(u32, u16, byte_addr, dma.*[1].dad, value)), | ||||||
|  |             0xC4 => dma.*[1].setDmacntL(value), | ||||||
|  |             0xC6 => dma.*[1].setDmacntH(value), | ||||||
|  |  | ||||||
|  |             0xC8, 0xCA => dma.*[2].setDmasad(subset(u32, u16, byte_addr, dma.*[2].sad, value)), | ||||||
|  |             0xCC, 0xCE => dma.*[2].setDmadad(subset(u32, u16, byte_addr, dma.*[2].dad, value)), | ||||||
|  |             0xD0 => dma.*[2].setDmacntL(value), | ||||||
|  |             0xD2 => dma.*[2].setDmacntH(value), | ||||||
|  |  | ||||||
|  |             0xD4, 0xD6 => dma.*[3].setDmasad(subset(u32, u16, byte_addr, dma.*[3].sad, value)), | ||||||
|  |             0xD8, 0xDA => dma.*[3].setDmadad(subset(u32, u16, byte_addr, dma.*[3].dad, value)), | ||||||
|  |             0xDC => dma.*[3].setDmacntL(value), | ||||||
|  |             0xDE => dma.*[3].setDmacntH(value), | ||||||
|  |             else => log.warn("Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
|  |         }, | ||||||
|  |         u8 => switch (byte_addr) { | ||||||
|  |             0xB0, 0xB1, 0xB2, 0xB3 => dma.*[0].setDmasad(subset(u32, u8, byte_addr, dma.*[0].sad, value)), | ||||||
|  |             0xB4, 0xB5, 0xB6, 0xB7 => dma.*[0].setDmadad(subset(u32, u8, byte_addr, dma.*[0].dad, value)), | ||||||
|  |             0xB8, 0xB9 => dma.*[0].setDmacntL(subset(u16, u8, byte_addr, dma.*[0].word_count, value)), | ||||||
|  |             0xBA, 0xBB => dma.*[0].setDmacntH(subset(u16, u8, byte_addr, dma.*[0].cnt.raw, value)), | ||||||
|  |  | ||||||
|  |             0xBC, 0xBD, 0xBE, 0xBF => dma.*[1].setDmasad(subset(u32, u8, byte_addr, dma.*[1].sad, value)), | ||||||
|  |             0xC0, 0xC1, 0xC2, 0xC3 => dma.*[1].setDmadad(subset(u32, u8, byte_addr, dma.*[1].dad, value)), | ||||||
|  |             0xC4, 0xC5 => dma.*[1].setDmacntL(subset(u16, u8, byte_addr, dma.*[1].word_count, value)), | ||||||
|  |             0xC6, 0xC7 => dma.*[1].setDmacntH(subset(u16, u8, byte_addr, dma.*[1].cnt.raw, value)), | ||||||
|  |  | ||||||
|  |             0xC8, 0xC9, 0xCA, 0xCB => dma.*[2].setDmasad(subset(u32, u8, byte_addr, dma.*[2].sad, value)), | ||||||
|  |             0xCC, 0xCD, 0xCE, 0xCF => dma.*[2].setDmadad(subset(u32, u8, byte_addr, dma.*[2].dad, value)), | ||||||
|  |             0xD0, 0xD1 => dma.*[2].setDmacntL(subset(u16, u8, byte_addr, dma.*[2].word_count, value)), | ||||||
|  |             0xD2, 0xD3 => dma.*[2].setDmacntH(subset(u16, u8, byte_addr, dma.*[2].cnt.raw, value)), | ||||||
|  |  | ||||||
|  |             0xD4, 0xD5, 0xD6, 0xD7 => dma.*[3].setDmasad(subset(u32, u8, byte_addr, dma.*[3].sad, value)), | ||||||
|  |             0xD8, 0xD9, 0xDA, 0xDB => dma.*[3].setDmadad(subset(u32, u8, byte_addr, dma.*[3].dad, value)), | ||||||
|  |             0xDC, 0xDD => dma.*[3].setDmacntL(subset(u16, u8, byte_addr, dma.*[3].word_count, value)), | ||||||
|  |             0xDE, 0xDF => dma.*[3].setDmacntH(subset(u16, u8, byte_addr, dma.*[3].cnt.raw, value)), | ||||||
|  |             else => log.warn("Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
|  |         }, | ||||||
|  |         else => @compileError("DMA: Unsupported write width"), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn warn(comptime format: []const u8, args: anytype) u0 { | ||||||
|  |     log.warn(format, args); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Function that creates a DMAController. Determines unique DMA Controller behaiour at compile-time | ||||||
|  | fn Controller(comptime id: u2) type { | ||||||
|  |     return struct { | ||||||
|  |         const Self = @This(); | ||||||
|  |  | ||||||
|  |         const sad_mask: u32 = if (id == 0) 0x07FF_FFFF else 0x0FFF_FFFF; | ||||||
|  |         const dad_mask: u32 = if (id != 3) 0x07FF_FFFF else 0x0FFF_FFFF; | ||||||
|  |         const WordCount = if (id == 3) u16 else u14; | ||||||
|  |  | ||||||
|  |         /// Write-only. The first address in a DMA transfer. (DMASAD) | ||||||
|  |         /// Note: use writeSrc instead of manipulating src_addr directly | ||||||
|  |         sad: u32 = 0x0000_0000, | ||||||
|  |         /// Write-only. The final address in a DMA transffer. (DMADAD) | ||||||
|  |         /// Note: Use writeDst instead of manipulatig dst_addr directly | ||||||
|  |         dad: u32 = 0x0000_0000, | ||||||
|  |         /// Write-only. The Word Count for the DMA Transfer (DMACNT_L) | ||||||
|  |         word_count: WordCount = 0, | ||||||
|  |         /// Read / Write. DMACNT_H | ||||||
|  |         /// Note: Use writeControl instead of manipulating cnt directly. | ||||||
|  |         cnt: DmaCnt = .{ .raw = 0x0000 }, | ||||||
|  |  | ||||||
|  |         /// Internal. The last successfully read value | ||||||
|  |         data_latch: u32 = 0x0000_0000, | ||||||
|  |         /// Internal. Currrent Source Address | ||||||
|  |         sad_latch: u32 = 0x0000_0000, | ||||||
|  |         /// Internal. Current Destination Address | ||||||
|  |         dad_latch: u32 = 0x0000_0000, | ||||||
|  |         /// Internal. Word Count | ||||||
|  |         _word_count: WordCount = 0, | ||||||
|  |  | ||||||
|  |         /// Some DMA Transfers are enabled during Hblank / VBlank and / or | ||||||
|  |         /// 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 | ||||||
|  |         in_progress: bool = false, | ||||||
|  |  | ||||||
|  |         pub fn reset(self: *Self) void { | ||||||
|  |             self.* = Self.init(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn setDmasad(self: *Self, addr: u32) void { | ||||||
|  |             self.sad = addr & sad_mask; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn setDmadad(self: *Self, addr: u32) void { | ||||||
|  |             self.dad = addr & dad_mask; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn setDmacntL(self: *Self, halfword: u16) void { | ||||||
|  |             self.word_count = @truncate(halfword); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn dmacntH(self: *const Self) u16 { | ||||||
|  |             return self.cnt.raw & if (id == 3) 0xFFE0 else 0xF7E0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn setDmacntH(self: *Self, halfword: u16) void { | ||||||
|  |             const new = DmaCnt{ .raw = halfword }; | ||||||
|  |  | ||||||
|  |             if (!self.cnt.enabled.read() and new.enabled.read()) { | ||||||
|  |                 // Reload Internals on Rising Edge. | ||||||
|  |                 self.sad_latch = self.sad; | ||||||
|  |                 self.dad_latch = self.dad; | ||||||
|  |                 self._word_count = if (self.word_count == 0) std.math.maxInt(WordCount) else self.word_count; | ||||||
|  |  | ||||||
|  |                 // Only a Start Timing of 00 has a DMA Transfer immediately begin | ||||||
|  |                 self.in_progress = new.start_timing.read() == 0b00; | ||||||
|  |  | ||||||
|  |                 // this is just for debug purposes | ||||||
|  |                 const start_timing: Kind = @enumFromInt(new.start_timing.read()); | ||||||
|  |  | ||||||
|  |                 switch (start_timing) { | ||||||
|  |                     .immediate, .vblank => {}, | ||||||
|  |                     else => log.err("TODO: Implement DMA({}) {s} mode", .{ id, @tagName(start_timing) }), | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Debug stuff | ||||||
|  |                 { | ||||||
|  |                     const sad_adj: Adjustment = @enumFromInt(new.sad_adj.read()); | ||||||
|  |                     const dad_adj: Adjustment = @enumFromInt(new.dad_adj.read()); | ||||||
|  |                     const byte_count = @as(u32, @sizeOf(u16)) << @intFromBool(new.transfer_type.read()); | ||||||
|  |  | ||||||
|  |                     const sad_final = switch (sad_adj) { | ||||||
|  |                         .Increment, .IncrementReload => self.sad_latch +% self._word_count * byte_count, | ||||||
|  |                         .Decrement => self.sad_latch -% self._word_count * byte_count, | ||||||
|  |                         .Fixed => self.sad_latch, | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|  |                     const dad_final = switch (dad_adj) { | ||||||
|  |                         .Increment, .IncrementReload => self.dad_latch +% self._word_count * byte_count, | ||||||
|  |                         .Decrement => self.dad_latch -% self._word_count * byte_count, | ||||||
|  |                         .Fixed => self.dad_latch, | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|  |                     log.debug("configured {s} transfer from 0x{X:0>8} -> 0x{X:0>8} to 0x{X:0>8} -> 0x{X:0>8} ({} words) for DMA{}", .{ | ||||||
|  |                         @tagName(start_timing), | ||||||
|  |                         self.sad_latch, | ||||||
|  |                         sad_final, | ||||||
|  |                         self.dad_latch, | ||||||
|  |                         dad_final, | ||||||
|  |                         self._word_count, | ||||||
|  |                         id, | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             self.cnt.raw = halfword; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn setDmacnt(self: *Self, word: u32) void { | ||||||
|  |             self.setDmacntL(@truncate(word)); | ||||||
|  |             self.setDmacntH(@truncate(word >> 16)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn step(self: *Self, cpu: *System.Arm7tdmi) void { | ||||||
|  |             const bus_ptr: *System.Bus7 = @ptrCast(@alignCast(cpu.bus.ptr)); | ||||||
|  |  | ||||||
|  |             const is_fifo = (id == 1 or id == 2) and self.cnt.start_timing.read() == 0b11; | ||||||
|  |             const sad_adj: Adjustment = @enumFromInt(self.cnt.sad_adj.read()); | ||||||
|  |             const dad_adj: Adjustment = if (is_fifo) .Fixed else @enumFromInt(self.cnt.dad_adj.read()); | ||||||
|  |  | ||||||
|  |             const transfer_type = is_fifo or self.cnt.transfer_type.read(); | ||||||
|  |             const offset: u32 = if (transfer_type) @sizeOf(u32) else @sizeOf(u16); | ||||||
|  |  | ||||||
|  |             const mask = if (transfer_type) ~@as(u32, 3) else ~@as(u32, 1); | ||||||
|  |             const sad_addr = self.sad_latch & mask; | ||||||
|  |             const dad_addr = self.dad_latch & mask; | ||||||
|  |  | ||||||
|  |             if (transfer_type) { | ||||||
|  |                 if (sad_addr >= 0x0200_0000) self.data_latch = cpu.bus.read(u32, sad_addr); | ||||||
|  |                 cpu.bus.write(u32, dad_addr, self.data_latch); | ||||||
|  |             } else { | ||||||
|  |                 if (sad_addr >= 0x0200_0000) { | ||||||
|  |                     const value: u32 = cpu.bus.read(u16, sad_addr); | ||||||
|  |                     self.data_latch = value << 16 | value; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 cpu.bus.write(u16, dad_addr, @as(u16, @truncate(rotr(u32, self.data_latch, 8 * (dad_addr & 3))))); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             switch (@as(u8, @truncate(sad_addr >> 24))) { | ||||||
|  |                 // according to fleroviux, DMAs with a source address in ROM misbehave | ||||||
|  |                 // the resultant behaviour is that the source address will increment despite what DMAXCNT says | ||||||
|  |                 0x08...0x0D => self.sad_latch +%= offset, // obscure behaviour | ||||||
|  |                 else => switch (sad_adj) { | ||||||
|  |                     .Increment => self.sad_latch +%= offset, | ||||||
|  |                     .Decrement => self.sad_latch -%= offset, | ||||||
|  |                     .IncrementReload => log.err("{} is a prohibited adjustment on SAD", .{sad_adj}), | ||||||
|  |                     .Fixed => {}, | ||||||
|  |                 }, | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             switch (dad_adj) { | ||||||
|  |                 .Increment, .IncrementReload => self.dad_latch +%= offset, | ||||||
|  |                 .Decrement => self.dad_latch -%= offset, | ||||||
|  |                 .Fixed => {}, | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             self._word_count -= 1; | ||||||
|  |  | ||||||
|  |             if (self._word_count == 0) { | ||||||
|  |                 if (self.cnt.irq.read()) { | ||||||
|  |                     switch (id) { | ||||||
|  |                         0 => bus_ptr.io.irq.dma0.set(), | ||||||
|  |                         1 => bus_ptr.io.irq.dma1.set(), | ||||||
|  |                         2 => bus_ptr.io.irq.dma2.set(), | ||||||
|  |                         3 => bus_ptr.io.irq.dma3.set(), | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     handleInterrupt(.nds7, cpu); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // If we're not repeating, Fire the IRQs and disable the DMA | ||||||
|  |                 if (!self.cnt.repeat.read()) self.cnt.enabled.unset(); | ||||||
|  |  | ||||||
|  |                 // We want to disable our internal enabled flag regardless of repeat | ||||||
|  |                 // because we only want to step A DMA that repeats during it's specific | ||||||
|  |                 // timing window | ||||||
|  |                 self.in_progress = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn poll(self: *Self, comptime kind: Kind) void { | ||||||
|  |             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 | ||||||
|  |             // Determined by the repeat bit and whether the DMA is in the right start_timing | ||||||
|  |  | ||||||
|  |             switch (kind) { | ||||||
|  |                 .vblank => self.in_progress = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b01, | ||||||
|  |                 .cartridge_slot, .immediate, .special => {}, | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // If we determined that the repeat bit is set (and now the Hblank / Vblank DMA is now in progress) | ||||||
|  |             // Reload internal word count latch | ||||||
|  |             // Reload internal DAD latch if we are in IncrementRelaod | ||||||
|  |             if (self.in_progress) { | ||||||
|  |                 self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count; | ||||||
|  |                 if (@as(Adjustment, @enumFromInt(self.cnt.dad_adj.read())) == .IncrementReload) self.dad_latch = self.dad; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn requestAudio(self: *Self, _: u32) void { | ||||||
|  |             comptime std.debug.assert(id == 1 or id == 2); | ||||||
|  |             if (self.in_progress) return; // APU must wait their turn | ||||||
|  |  | ||||||
|  |             // DMA May not be configured for handling DMAs | ||||||
|  |             if (self.cnt.start_timing.read() != 0b11) return; | ||||||
|  |  | ||||||
|  |             // We Assume the Repeat Bit is Set | ||||||
|  |             // We Assume that DAD is set to 0x0400_00A0 or 0x0400_00A4 (fifo_addr) | ||||||
|  |             // We Assume DMACNT_L is set to 4 | ||||||
|  |  | ||||||
|  |             // FIXME: Safe to just assume whatever DAD is set to is the FIFO Address? | ||||||
|  |             // self.dad_latch = fifo_addr; | ||||||
|  |             self.cnt.repeat.set(); | ||||||
|  |             self._word_count = 4; | ||||||
|  |             self.in_progress = true; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn onVblank(bus: *System.Bus7) void { | ||||||
|  |     inline for (0..4) |i| bus.dma[i].poll(.vblank); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn step(cpu: *System.Arm7tdmi) bool { | ||||||
|  |     const bus: *System.Bus7 = @ptrCast(@alignCast(cpu.bus.ptr)); | ||||||
|  |  | ||||||
|  |     inline for (0..4) |i| { | ||||||
|  |         if (bus.dma[i].in_progress) { | ||||||
|  |             bus.dma[i].step(cpu); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const Adjustment = enum(u2) { | ||||||
|  |     Increment = 0, | ||||||
|  |     Decrement = 1, | ||||||
|  |     Fixed = 2, | ||||||
|  |     IncrementReload = 3, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const Kind = enum(u2) { | ||||||
|  |     immediate = 0, | ||||||
|  |     vblank, | ||||||
|  |     cartridge_slot, | ||||||
|  |     special, | ||||||
|  | }; | ||||||
| @@ -12,6 +12,8 @@ const masks = @import("../io.zig").masks; | |||||||
| const IntEnable = @import("../io.zig").IntEnable; | const IntEnable = @import("../io.zig").IntEnable; | ||||||
| const IntRequest = @import("../io.zig").IntEnable; | const IntRequest = @import("../io.zig").IntEnable; | ||||||
|  |  | ||||||
|  | const dma = @import("dma.zig"); | ||||||
|  |  | ||||||
| const log = std.log.scoped(.nds7_io); | const log = std.log.scoped(.nds7_io); | ||||||
|  |  | ||||||
| pub const Io = struct { | pub const Io = struct { | ||||||
| @@ -52,10 +54,10 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T { | |||||||
|     return switch (T) { |     return switch (T) { | ||||||
|         u32 => switch (address) { |         u32 => switch (address) { | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DC => warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DC => dma.read(T, &bus.dma, address) orelse 0x000_0000, | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010C => warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010C => warn("TODO(timer): read(T: {}, addr: 0x{X:0>8}) {}", .{ T, address, T }), | ||||||
|  |  | ||||||
|             0x0400_0180 => bus.io.shr.ipc._nds7.sync.raw, |             0x0400_0180 => bus.io.shr.ipc._nds7.sync.raw, | ||||||
|             0x0400_0208 => @intFromBool(bus.io.ime), |             0x0400_0208 => @intFromBool(bus.io.ime), | ||||||
| @@ -68,22 +70,27 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T { | |||||||
|         u16 => switch (address) { |         u16 => switch (address) { | ||||||
|             0x0400_0004 => bus.io.ppu.?.nds7.dispstat.raw, |             0x0400_0004 => bus.io.ppu.?.nds7.dispstat.raw, | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DE => warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DE => dma.read(T, &bus.dma, address) orelse 0x0000, | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010E => warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010E => warn("TODO(timer): read(T: {}, addr: 0x{X:0>8}) {}", .{ T, address, T }), | ||||||
|  |  | ||||||
|  |             0x0400_0130 => bus.io.shr.input.keyinput().raw, | ||||||
|  |             0x0400_0136 => bus.io.shr.input.extkeyin().raw, | ||||||
|  |  | ||||||
|             0x0400_0130 => bus.io.shr.keyinput.load(.Monotonic), |  | ||||||
|             0x0400_0180 => @truncate(bus.io.shr.ipc._nds7.sync.raw), |             0x0400_0180 => @truncate(bus.io.shr.ipc._nds7.sync.raw), | ||||||
|             0x0400_0184 => @truncate(bus.io.shr.ipc._nds7.cnt.raw), |             0x0400_0184 => @truncate(bus.io.shr.ipc._nds7.cnt.raw), | ||||||
|             else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), |             else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), | ||||||
|         }, |         }, | ||||||
|         u8 => switch (address) { |         u8 => switch (address) { | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DF => warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DF => dma.read(T, &bus.dma, address) orelse 0x00, | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010F => warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010F => warn("TODO(timer): read(T: {}, addr: 0x{X:0>8}) {}", .{ T, address, T }), | ||||||
|  |  | ||||||
|  |             // RTC | ||||||
|  |             0x0400_0138 => warn("TODO(rtc): read(T: {}, addr: 0x{X:0>8}) {}", .{ T, address, T }), | ||||||
|  |  | ||||||
|             0x0400_0240 => bus.vram.stat().raw, |             0x0400_0240 => bus.vram.stat().raw, | ||||||
|             0x0400_0241 => bus.io.shr.wramcnt.raw, |             0x0400_0241 => bus.io.shr.wramcnt.raw, | ||||||
| @@ -99,10 +106,10 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | |||||||
|     switch (T) { |     switch (T) { | ||||||
|         u32 => switch (address) { |         u32 => switch (address) { | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DC => log.warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DC => dma.write(T, &bus.dma, address, value), | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010C => log.warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010C => log.warn("TODO(timer): write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||||
|  |  | ||||||
|             0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds7, value), |             0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds7, value), | ||||||
|             0x0400_0208 => bus.io.ime = value & 1 == 1, |             0x0400_0208 => bus.io.ime = value & 1 == 1, | ||||||
| @@ -113,11 +120,13 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | |||||||
|             else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), |             else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||||
|         }, |         }, | ||||||
|         u16 => switch (address) { |         u16 => switch (address) { | ||||||
|  |             0x0400_0004 => bus.io.ppu.?.nds7.dispstat.raw = value, | ||||||
|  |  | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DE => log.warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DE => dma.write(T, &bus.dma, address, value), | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010E => log.warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010E => log.warn("TODO(timer): write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||||
|  |  | ||||||
|             0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds7, value), |             0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds7, value), | ||||||
|             0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds7, value), |             0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds7, value), | ||||||
| @@ -127,12 +136,24 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | |||||||
|         }, |         }, | ||||||
|         u8 => switch (address) { |         u8 => switch (address) { | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DF => log.warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DF => dma.write(T, &bus.dma, address, value), | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010F => log.warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010F => log.warn("TODO(timer): write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||||
|  |  | ||||||
|  |             // RTC | ||||||
|  |             0x0400_0138 => log.warn("TODO(rtc): write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||||
|  |  | ||||||
|             0x0400_0208 => bus.io.ime = value & 1 == 1, |             0x0400_0208 => bus.io.ime = value & 1 == 1, | ||||||
|  |  | ||||||
|  |             0x0400_0301 => switch ((value >> 6) & 0b11) { | ||||||
|  |                 0b00 => bus.io.haltcnt = .execute, | ||||||
|  |                 0b10 => bus.io.haltcnt = .halt, | ||||||
|  |                 else => |val| { | ||||||
|  |                     const tag: Haltcnt = @enumFromInt(val); | ||||||
|  |                     log.err("TODO: Implement {}", .{tag}); | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|             else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>2})", .{ T, address, value }), |             else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>2})", .{ T, address, value }), | ||||||
|         }, |         }, | ||||||
|         else => @compileError(T ++ " is an unsupported bus write type"), |         else => @compileError(T ++ " is an unsupported bus write type"), | ||||||
| @@ -158,3 +179,14 @@ const Haltcnt = enum(u2) { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const PostFlag = enum(u8) { in_progress = 0, completed }; | const PostFlag = enum(u8) { in_progress = 0, completed }; | ||||||
|  |  | ||||||
|  | pub const DmaCnt = extern union { | ||||||
|  |     dad_adj: Bitfield(u16, 5, 2), | ||||||
|  |     sad_adj: Bitfield(u16, 7, 2), | ||||||
|  |     repeat: Bit(u16, 9), | ||||||
|  |     transfer_type: Bit(u16, 10), | ||||||
|  |     start_timing: Bitfield(u16, 12, 2), | ||||||
|  |     irq: Bit(u16, 14), | ||||||
|  |     enabled: Bit(u16, 15), | ||||||
|  |     raw: u16, | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -31,7 +31,6 @@ pub fn deinit(self: @This(), allocator: Allocator) void { | |||||||
|  |  | ||||||
| // Note: Parts of 16MiB addrspace that aren't mapped to BIOS are typically undefined | // Note: Parts of 16MiB addrspace that aren't mapped to BIOS are typically undefined | ||||||
| pub fn read(self: *const @This(), comptime T: type, address: u32) T { | pub fn read(self: *const @This(), comptime T: type, address: u32) T { | ||||||
|     const readInt = std.mem.readIntLittle; |  | ||||||
|     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); |     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); | ||||||
|  |  | ||||||
|     // if (address >= len) return 0x0000_0000; // TODO: What is undefined actually? |     // if (address >= len) return 0x0000_0000; // TODO: What is undefined actually? | ||||||
| @@ -41,7 +40,7 @@ pub fn read(self: *const @This(), comptime T: type, address: u32) T { | |||||||
|         @panic("TODO: ability to load in NDS9 BIOS just-in-time"); |         @panic("TODO: ability to load in NDS9 BIOS just-in-time"); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     return readInt(T, ptr[address & (len - 1) ..][0..byte_count]); |     return std.mem.readInt(T, ptr[address & (len - 1) ..][0..byte_count], .little); | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn write(_: *const @This(), comptime T: type, address: u32, value: T) void { | pub fn write(_: *const @This(), comptime T: type, address: u32, value: T) void { | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ const Wram = @import("../emu.zig").Wram; | |||||||
| const Bios = @import("Bios.zig"); | const Bios = @import("Bios.zig"); | ||||||
| const forceAlign = @import("../emu.zig").forceAlign; | const forceAlign = @import("../emu.zig").forceAlign; | ||||||
|  |  | ||||||
|  | const Controllers = @import("dma.zig").Controllers; | ||||||
|  |  | ||||||
| const Allocator = std.mem.Allocator; | const Allocator = std.mem.Allocator; | ||||||
|  |  | ||||||
| const Mode = enum { normal, debug }; | const Mode = enum { normal, debug }; | ||||||
| @@ -18,8 +20,11 @@ const log = std.log.scoped(.nds9_bus); | |||||||
|  |  | ||||||
| main: *[4 * MiB]u8, | main: *[4 * MiB]u8, | ||||||
| wram: *Wram, | wram: *Wram, | ||||||
|  | makeshift_palram: *[2 * KiB]u8, | ||||||
| scheduler: *Scheduler, | scheduler: *Scheduler, | ||||||
|  |  | ||||||
|  | dma: Controllers = .{}, | ||||||
|  |  | ||||||
| io: io.Io, | io: io.Io, | ||||||
| ppu: Ppu, | ppu: Ppu, | ||||||
| bios: Bios, | bios: Bios, | ||||||
| @@ -31,6 +36,7 @@ pub fn init(allocator: Allocator, scheduler: *Scheduler, ctx: SharedCtx) !@This( | |||||||
|     return .{ |     return .{ | ||||||
|         .main = ctx.main, |         .main = ctx.main, | ||||||
|         .wram = ctx.wram, |         .wram = ctx.wram, | ||||||
|  |         .makeshift_palram = try allocator.create([2 * KiB]u8), | ||||||
|         .ppu = try Ppu.init(allocator, ctx.vram), |         .ppu = try Ppu.init(allocator, ctx.vram), | ||||||
|         .scheduler = scheduler, |         .scheduler = scheduler, | ||||||
|         .io = io.Io.init(ctx.io), |         .io = io.Io.init(ctx.io), | ||||||
| @@ -42,6 +48,8 @@ pub fn init(allocator: Allocator, scheduler: *Scheduler, ctx: SharedCtx) !@This( | |||||||
| pub fn deinit(self: *@This(), allocator: Allocator) void { | pub fn deinit(self: *@This(), allocator: Allocator) void { | ||||||
|     self.ppu.deinit(allocator); |     self.ppu.deinit(allocator); | ||||||
|     self.bios.deinit(allocator); |     self.bios.deinit(allocator); | ||||||
|  |  | ||||||
|  |     allocator.destroy(self.makeshift_palram); | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn reset(_: *@This()) void { | pub fn reset(_: *@This()) void { | ||||||
| @@ -58,8 +66,6 @@ pub fn dbgRead(self: *@This(), comptime T: type, address: u32) T { | |||||||
|  |  | ||||||
| fn _read(self: *@This(), comptime T: type, comptime mode: Mode, address: u32) T { | fn _read(self: *@This(), comptime T: type, comptime mode: Mode, address: u32) T { | ||||||
|     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); |     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); | ||||||
|     const readInt = std.mem.readIntLittle; |  | ||||||
|  |  | ||||||
|     const aligned_addr = forceAlign(T, address); |     const aligned_addr = forceAlign(T, address); | ||||||
|  |  | ||||||
|     switch (mode) { |     switch (mode) { | ||||||
| @@ -69,11 +75,13 @@ fn _read(self: *@This(), comptime T: type, comptime mode: Mode, address: u32) T | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     return switch (aligned_addr) { |     return switch (aligned_addr) { | ||||||
|         0x0200_0000...0x02FF_FFFF => readInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count]), |         0x0200_0000...0x02FF_FFFF => std.mem.readInt(T, self.main[aligned_addr & (4 * MiB - 1) ..][0..byte_count], .little), | ||||||
|         0x0300_0000...0x03FF_FFFF => self.wram.read(T, .nds9, aligned_addr), |         0x0300_0000...0x03FF_FFFF => self.wram.read(T, .nds9, aligned_addr), | ||||||
|         0x0400_0000...0x04FF_FFFF => io.read(self, T, aligned_addr), |         0x0400_0000...0x04FF_FFFF => io.read(self, T, aligned_addr), | ||||||
|  |         0x0500_0000...0x05FF_FFFF => std.mem.readInt(T, self.makeshift_palram[aligned_addr & (2 * KiB - 1) ..][0..@sizeOf(T)], .little), | ||||||
|         0x0600_0000...0x06FF_FFFF => self.ppu.vram.read(T, .nds9, aligned_addr), |         0x0600_0000...0x06FF_FFFF => self.ppu.vram.read(T, .nds9, aligned_addr), | ||||||
|         0xFFFF_0000...0xFFFF_FFFF => self.bios.read(T, address), |         0x0700_0000...0x07FF_FFFF => std.mem.readInt(T, self.ppu.oam.buf[aligned_addr & (2 * KiB - 1) ..][0..byte_count], .little), | ||||||
|  |         0xFFFF_0000...0xFFFF_FFFF => self.bios.read(T, aligned_addr), | ||||||
|         else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), |         else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| @@ -88,8 +96,6 @@ pub fn dbgWrite(self: *@This(), comptime T: type, address: u32, value: T) void { | |||||||
|  |  | ||||||
| fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, value: T) void { | fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, value: T) void { | ||||||
|     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); |     const byte_count = @divExact(@typeInfo(T).Int.bits, 8); | ||||||
|     const writeInt = std.mem.writeIntLittle; |  | ||||||
|  |  | ||||||
|     const aligned_addr = forceAlign(T, address); |     const aligned_addr = forceAlign(T, address); | ||||||
|  |  | ||||||
|     switch (mode) { |     switch (mode) { | ||||||
| @@ -99,11 +105,13 @@ fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, v | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     switch (aligned_addr) { |     switch (aligned_addr) { | ||||||
|         0x0200_0000...0x02FF_FFFF => writeInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count], value), |         0x0200_0000...0x02FF_FFFF => std.mem.writeInt(T, self.main[aligned_addr & (4 * MiB - 1) ..][0..byte_count], value, .little), | ||||||
|         0x0300_0000...0x03FF_FFFF => self.wram.write(T, .nds9, aligned_addr, value), |         0x0300_0000...0x03FF_FFFF => self.wram.write(T, .nds9, aligned_addr, value), | ||||||
|         0x0400_0000...0x04FF_FFFF => io.write(self, T, aligned_addr, value), |         0x0400_0000...0x04FF_FFFF => io.write(self, T, aligned_addr, value), | ||||||
|  |         0x0500_0000...0x05FF_FFFF => std.mem.writeInt(T, self.makeshift_palram[aligned_addr & (2 * KiB - 1) ..][0..@sizeOf(T)], value, .little), | ||||||
|         0x0600_0000...0x06FF_FFFF => self.ppu.vram.write(T, .nds9, aligned_addr, value), |         0x0600_0000...0x06FF_FFFF => self.ppu.vram.write(T, .nds9, aligned_addr, value), | ||||||
|         0xFFFF_0000...0xFFFF_FFFF => self.bios.write(T, address, value), |         0x0700_0000...0x07FF_FFFF => std.mem.writeInt(T, self.ppu.oam.buf[aligned_addr & (2 * KiB - 1) ..][0..@sizeOf(T)], value, .little), | ||||||
|  |         0xFFFF_0000...0xFFFF_FFFF => self.bios.write(T, aligned_addr, value), | ||||||
|         else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), |         else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ const log = std.log.scoped(.cp15); | |||||||
|  |  | ||||||
| const panic_on_unimplemented: bool = false; | const panic_on_unimplemented: bool = false; | ||||||
|  |  | ||||||
| control: u32 = 0x0005_2078, | control: u32 = 0x0001_2078, | ||||||
| dtcm_size_base: u32 = 0x0300_000A, | dtcm_size_base: u32 = 0x0300_000A, | ||||||
| itcm_size_base: u32 = 0x0000_0020, | itcm_size_base: u32 = 0x0000_0020, | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										411
									
								
								src/core/nds9/dma.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										411
									
								
								src/core/nds9/dma.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,411 @@ | |||||||
|  | const std = @import("std"); | ||||||
|  |  | ||||||
|  | const System = @import("../emu.zig").System; | ||||||
|  | const DmaCnt = @import("io.zig").DmaCnt; | ||||||
|  |  | ||||||
|  | const rotr = std.math.rotr; | ||||||
|  | const shift = @import("../../util.zig").shift; | ||||||
|  | const subset = @import("../../util.zig").subset; | ||||||
|  |  | ||||||
|  | const handleInterrupt = @import("../emu.zig").handleInterrupt; | ||||||
|  |  | ||||||
|  | const log = std.log.scoped(.nds9_dma_transfer); | ||||||
|  |  | ||||||
|  | pub const Controllers = struct { | ||||||
|  |     Controller(0) = Controller(0){}, | ||||||
|  |     Controller(1) = Controller(1){}, | ||||||
|  |     Controller(2) = Controller(2){}, | ||||||
|  |     Controller(3) = Controller(3){}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | pub fn read(comptime T: type, dma: *const Controllers, addr: u32) ?T { | ||||||
|  |     const byte_addr: u8 = @truncate(addr); | ||||||
|  |  | ||||||
|  |     return switch (T) { | ||||||
|  |         u32 => switch (byte_addr) { | ||||||
|  |             0xB0, 0xB4 => null, // DMA0SAD, DMA0DAD, | ||||||
|  |             0xB8 => @as(T, dma.*[0].dmacntH()) << 16, // DMA0CNT_L is write-only | ||||||
|  |             0xBC, 0xC0 => null, // DMA1SAD, DMA1DAD | ||||||
|  |             0xC4 => @as(T, dma.*[1].dmacntH()) << 16, // DMA1CNT_L is write-only | ||||||
|  |             0xC8, 0xCC => null, // DMA2SAD, DMA2DAD | ||||||
|  |             0xD0 => @as(T, dma.*[2].dmacntH()) << 16, // DMA2CNT_L is write-only | ||||||
|  |             0xD4, 0xD8 => null, // DMA3SAD, DMA3DAD | ||||||
|  |             0xDC => @as(T, dma.*[3].dmacntH()) << 16, // DMA3CNT_L is write-only | ||||||
|  |             else => warn("unaligned {} read from 0x{X:0>8}", .{ T, addr }), | ||||||
|  |         }, | ||||||
|  |         u16 => switch (byte_addr) { | ||||||
|  |             0xB0, 0xB2, 0xB4, 0xB6 => null, // DMA0SAD, DMA0DAD | ||||||
|  |             0xB8 => 0x0000, // DMA0CNT_L, suite.gba expects 0x0000 instead of 0xDEAD | ||||||
|  |             0xBA => dma.*[0].dmacntH(), | ||||||
|  |  | ||||||
|  |             0xBC, 0xBE, 0xC0, 0xC2 => null, // DMA1SAD, DMA1DAD | ||||||
|  |             0xC4 => 0x0000, // DMA1CNT_L | ||||||
|  |             0xC6 => dma.*[1].dmacntH(), | ||||||
|  |  | ||||||
|  |             0xC8, 0xCA, 0xCC, 0xCE => null, // DMA2SAD, DMA2DAD | ||||||
|  |             0xD0 => 0x0000, // DMA2CNT_L | ||||||
|  |             0xD2 => dma.*[2].dmacntH(), | ||||||
|  |  | ||||||
|  |             0xD4, 0xD6, 0xD8, 0xDA => null, // DMA3SAD, DMA3DAD | ||||||
|  |             0xDC => 0x0000, // DMA3CNT_L | ||||||
|  |             0xDE => dma.*[3].dmacntH(), | ||||||
|  |             else => warn("unaligned {} read from 0x{X:0>8}", .{ T, addr }), | ||||||
|  |         }, | ||||||
|  |         u8 => switch (byte_addr) { | ||||||
|  |             0xB0...0xB7 => null, // DMA0SAD, DMA0DAD | ||||||
|  |             0xB8, 0xB9 => 0x00, // DMA0CNT_L | ||||||
|  |             0xBA, 0xBB => @truncate(dma.*[0].dmacntH() >> shift(u16, byte_addr)), | ||||||
|  |  | ||||||
|  |             0xBC...0xC3 => null, // DMA1SAD, DMA1DAD | ||||||
|  |             0xC4, 0xC5 => 0x00, // DMA1CNT_L | ||||||
|  |             0xC6, 0xC7 => @truncate(dma.*[1].dmacntH() >> shift(u16, byte_addr)), | ||||||
|  |  | ||||||
|  |             0xC8...0xCF => null, // DMA2SAD, DMA2DAD | ||||||
|  |             0xD0, 0xD1 => 0x00, // DMA2CNT_L | ||||||
|  |             0xD2, 0xD3 => @truncate(dma.*[2].dmacntH() >> shift(u16, byte_addr)), | ||||||
|  |  | ||||||
|  |             0xD4...0xDB => null, // DMA3SAD, DMA3DAD | ||||||
|  |             0xDC, 0xDD => 0x00, // DMA3CNT_L | ||||||
|  |             0xDE, 0xDF => @truncate(dma.*[3].dmacntH() >> shift(u16, byte_addr)), | ||||||
|  |             else => warn("unexpected {} read from 0x{X:0>8}", .{ T, addr }), | ||||||
|  |         }, | ||||||
|  |         else => @compileError("DMA: Unsupported read width"), | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn write(comptime T: type, dma: *Controllers, addr: u32, value: T) void { | ||||||
|  |     const byte_addr: u8 = @truncate(addr); | ||||||
|  |  | ||||||
|  |     switch (T) { | ||||||
|  |         u32 => switch (byte_addr) { | ||||||
|  |             0xB0 => dma.*[0].setDmasad(value), | ||||||
|  |             0xB4 => dma.*[0].setDmadad(value), | ||||||
|  |             0xB8 => dma.*[0].setDmacnt(value), | ||||||
|  |  | ||||||
|  |             0xBC => dma.*[1].setDmasad(value), | ||||||
|  |             0xC0 => dma.*[1].setDmadad(value), | ||||||
|  |             0xC4 => dma.*[1].setDmacnt(value), | ||||||
|  |  | ||||||
|  |             0xC8 => dma.*[2].setDmasad(value), | ||||||
|  |             0xCC => dma.*[2].setDmadad(value), | ||||||
|  |             0xD0 => dma.*[2].setDmacnt(value), | ||||||
|  |  | ||||||
|  |             0xD4 => dma.*[3].setDmasad(value), | ||||||
|  |             0xD8 => dma.*[3].setDmadad(value), | ||||||
|  |             0xDC => dma.*[3].setDmacnt(value), | ||||||
|  |             else => log.warn("Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
|  |         }, | ||||||
|  |         u16 => switch (byte_addr) { | ||||||
|  |             0xB0, 0xB2 => dma.*[0].setDmasad(subset(u32, u16, byte_addr, dma.*[0].sad, value)), | ||||||
|  |             0xB4, 0xB6 => dma.*[0].setDmadad(subset(u32, u16, byte_addr, dma.*[0].dad, value)), | ||||||
|  |             0xB8 => dma.*[0].setDmacntL(value), | ||||||
|  |             0xBA => dma.*[0].setDmacntH(value), | ||||||
|  |  | ||||||
|  |             0xBC, 0xBE => dma.*[1].setDmasad(subset(u32, u16, byte_addr, dma.*[1].sad, value)), | ||||||
|  |             0xC0, 0xC2 => dma.*[1].setDmadad(subset(u32, u16, byte_addr, dma.*[1].dad, value)), | ||||||
|  |             0xC4 => dma.*[1].setDmacntL(value), | ||||||
|  |             0xC6 => dma.*[1].setDmacntH(value), | ||||||
|  |  | ||||||
|  |             0xC8, 0xCA => dma.*[2].setDmasad(subset(u32, u16, byte_addr, dma.*[2].sad, value)), | ||||||
|  |             0xCC, 0xCE => dma.*[2].setDmadad(subset(u32, u16, byte_addr, dma.*[2].dad, value)), | ||||||
|  |             0xD0 => dma.*[2].setDmacntL(value), | ||||||
|  |             0xD2 => dma.*[2].setDmacntH(value), | ||||||
|  |  | ||||||
|  |             0xD4, 0xD6 => dma.*[3].setDmasad(subset(u32, u16, byte_addr, dma.*[3].sad, value)), | ||||||
|  |             0xD8, 0xDA => dma.*[3].setDmadad(subset(u32, u16, byte_addr, dma.*[3].dad, value)), | ||||||
|  |             0xDC => dma.*[3].setDmacntL(value), | ||||||
|  |             0xDE => dma.*[3].setDmacntH(value), | ||||||
|  |             else => log.warn("Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
|  |         }, | ||||||
|  |         u8 => switch (byte_addr) { | ||||||
|  |             0xB0, 0xB1, 0xB2, 0xB3 => dma.*[0].setDmasad(subset(u32, u8, byte_addr, dma.*[0].sad, value)), | ||||||
|  |             0xB4, 0xB5, 0xB6, 0xB7 => dma.*[0].setDmadad(subset(u32, u8, byte_addr, dma.*[0].dad, value)), | ||||||
|  |             0xB8, 0xB9 => dma.*[0].setDmacntL(subset(u16, u8, byte_addr, @as(u16, @truncate(dma.*[0].word_count)), value)), // FIXME: How wrong is this? lol | ||||||
|  |             0xBA, 0xBB => dma.*[0].setDmacntH(subset(u16, u8, byte_addr, dma.*[0].cnt.raw, value)), | ||||||
|  |  | ||||||
|  |             0xBC, 0xBD, 0xBE, 0xBF => dma.*[1].setDmasad(subset(u32, u8, byte_addr, dma.*[1].sad, value)), | ||||||
|  |             0xC0, 0xC1, 0xC2, 0xC3 => dma.*[1].setDmadad(subset(u32, u8, byte_addr, dma.*[1].dad, value)), | ||||||
|  |             0xC4, 0xC5 => dma.*[1].setDmacntL(subset(u16, u8, byte_addr, @as(u16, @truncate(dma.*[1].word_count)), value)), | ||||||
|  |             0xC6, 0xC7 => dma.*[1].setDmacntH(subset(u16, u8, byte_addr, dma.*[1].cnt.raw, value)), | ||||||
|  |  | ||||||
|  |             0xC8, 0xC9, 0xCA, 0xCB => dma.*[2].setDmasad(subset(u32, u8, byte_addr, dma.*[2].sad, value)), | ||||||
|  |             0xCC, 0xCD, 0xCE, 0xCF => dma.*[2].setDmadad(subset(u32, u8, byte_addr, dma.*[2].dad, value)), | ||||||
|  |             0xD0, 0xD1 => dma.*[2].setDmacntL(subset(u16, u8, byte_addr, @as(u16, @truncate(dma.*[2].word_count)), value)), | ||||||
|  |             0xD2, 0xD3 => dma.*[2].setDmacntH(subset(u16, u8, byte_addr, dma.*[2].cnt.raw, value)), | ||||||
|  |  | ||||||
|  |             0xD4, 0xD5, 0xD6, 0xD7 => dma.*[3].setDmasad(subset(u32, u8, byte_addr, dma.*[3].sad, value)), | ||||||
|  |             0xD8, 0xD9, 0xDA, 0xDB => dma.*[3].setDmadad(subset(u32, u8, byte_addr, dma.*[3].dad, value)), | ||||||
|  |             0xDC, 0xDD => dma.*[3].setDmacntL(subset(u16, u8, byte_addr, @as(u16, @truncate(dma.*[3].word_count)), value)), | ||||||
|  |             0xDE, 0xDF => dma.*[3].setDmacntH(subset(u16, u8, byte_addr, dma.*[3].cnt.raw, value)), | ||||||
|  |             else => log.warn("Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }), | ||||||
|  |         }, | ||||||
|  |         else => @compileError("DMA: Unsupported write width"), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn warn(comptime format: []const u8, args: anytype) u0 { | ||||||
|  |     log.warn(format, args); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Function that creates a DMAController. Determines unique DMA Controller behaiour at compile-time | ||||||
|  | fn Controller(comptime id: u2) type { | ||||||
|  |     return struct { | ||||||
|  |         const Self = @This(); | ||||||
|  |  | ||||||
|  |         const sad_mask: u32 = 0x0FFF_FFFE; | ||||||
|  |         const dad_mask: u32 = 0x0FFF_FFFE; | ||||||
|  |         const WordCount = u21; | ||||||
|  |  | ||||||
|  |         /// Write-only. The first address in a DMA transfer. (DMASAD) | ||||||
|  |         /// Note: use writeSrc instead of manipulating src_addr directly | ||||||
|  |         sad: u32 = 0x0000_0000, | ||||||
|  |         /// Write-only. The final address in a DMA transffer. (DMADAD) | ||||||
|  |         /// Note: Use writeDst instead of manipulatig dst_addr directly | ||||||
|  |         dad: u32 = 0x0000_0000, | ||||||
|  |         /// Write-only. The Word Count for the DMA Transfer (DMACNT_L) | ||||||
|  |         word_count: WordCount = 0, | ||||||
|  |         /// Read / Write. DMACNT_H | ||||||
|  |         /// Note: Use writeControl instead of manipulating cnt directly. | ||||||
|  |         cnt: DmaCnt = .{ .raw = 0x0000 }, | ||||||
|  |  | ||||||
|  |         /// Internal. The last successfully read value | ||||||
|  |         data_latch: u32 = 0x0000_0000, | ||||||
|  |         /// Internal. Currrent Source Address | ||||||
|  |         sad_latch: u32 = 0x0000_0000, | ||||||
|  |         /// Internal. Current Destination Address | ||||||
|  |         dad_latch: u32 = 0x0000_0000, | ||||||
|  |         /// Internal. Word Count | ||||||
|  |         _word_count: WordCount = 0, | ||||||
|  |  | ||||||
|  |         /// Some DMA Transfers are enabled during Hblank / VBlank and / or | ||||||
|  |         /// 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 | ||||||
|  |         in_progress: bool = false, | ||||||
|  |  | ||||||
|  |         pub fn reset(self: *Self) void { | ||||||
|  |             self.* = Self.init(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn setDmasad(self: *Self, addr: u32) void { | ||||||
|  |             self.sad = addr & sad_mask; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn setDmadad(self: *Self, addr: u32) void { | ||||||
|  |             self.dad = addr & dad_mask; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn setDmacntL(self: *Self, halfword: u16) void { | ||||||
|  |             self.word_count = halfword; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn dmacntH(self: *const Self) u16 { | ||||||
|  |             return self.cnt.raw & if (id == 3) 0xFFE0 else 0xF7E0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn setDmacntH(self: *Self, halfword: u16) void { | ||||||
|  |             const new = DmaCnt{ .raw = halfword }; | ||||||
|  |  | ||||||
|  |             if (!self.cnt.enabled.read() and new.enabled.read()) { | ||||||
|  |                 // Reload Internals on Rising Edge. | ||||||
|  |                 self.sad_latch = self.sad; | ||||||
|  |                 self.dad_latch = self.dad; | ||||||
|  |                 self._word_count = if (self.word_count == 0) std.math.maxInt(WordCount) else self.word_count; | ||||||
|  |  | ||||||
|  |                 // Only a Start Timing of 00 has a DMA Transfer immediately begin | ||||||
|  |                 self.in_progress = new.start_timing.read() == 0b00; | ||||||
|  |  | ||||||
|  |                 // this is just for debug purposes | ||||||
|  |                 const start_timing: Kind = @enumFromInt(new.start_timing.read()); | ||||||
|  |  | ||||||
|  |                 switch (start_timing) { | ||||||
|  |                     .immediate, .vblank, .hblank => {}, | ||||||
|  |                     else => log.err("TODO: Implement DMA({}) {s} mode", .{ id, @tagName(start_timing) }), | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Debug stuff | ||||||
|  |                 { | ||||||
|  |                     const sad_adj: Adjustment = @enumFromInt(new.sad_adj.read()); | ||||||
|  |                     const dad_adj: Adjustment = @enumFromInt(new.dad_adj.read()); | ||||||
|  |                     const byte_count = @as(u32, @sizeOf(u16)) << @intFromBool(new.transfer_type.read()); | ||||||
|  |  | ||||||
|  |                     const sad_final = switch (sad_adj) { | ||||||
|  |                         .Increment, .IncrementReload => self.sad_latch +% self._word_count * byte_count, | ||||||
|  |                         .Decrement => self.sad_latch -% self._word_count * byte_count, | ||||||
|  |                         .Fixed => self.sad_latch, | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|  |                     const dad_final = switch (dad_adj) { | ||||||
|  |                         .Increment, .IncrementReload => self.dad_latch +% self._word_count * byte_count, | ||||||
|  |                         .Decrement => self.dad_latch -% self._word_count * byte_count, | ||||||
|  |                         .Fixed => self.dad_latch, | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|  |                     log.debug("configured {s} transfer from 0x{X:0>8} -> 0x{X:0>8} to 0x{X:0>8} -> 0x{X:0>8} ({} words) for DMA{}", .{ | ||||||
|  |                         @tagName(start_timing), | ||||||
|  |                         self.sad_latch, | ||||||
|  |                         sad_final, | ||||||
|  |                         self.dad_latch, | ||||||
|  |                         dad_final, | ||||||
|  |                         self._word_count, | ||||||
|  |                         id, | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             self.cnt.raw = halfword; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn setDmacnt(self: *Self, word: u32) void { | ||||||
|  |             self.setDmacntL(@truncate(word)); | ||||||
|  |             self.setDmacntH(@truncate(word >> 16)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn step(self: *Self, cpu: *System.Arm946es) void { | ||||||
|  |             const bus_ptr: *System.Bus9 = @ptrCast(@alignCast(cpu.bus.ptr)); | ||||||
|  |  | ||||||
|  |             const is_fifo = (id == 1 or id == 2) and self.cnt.start_timing.read() == 0b11; | ||||||
|  |             const sad_adj: Adjustment = @enumFromInt(self.cnt.sad_adj.read()); | ||||||
|  |             const dad_adj: Adjustment = if (is_fifo) .Fixed else @enumFromInt(self.cnt.dad_adj.read()); | ||||||
|  |  | ||||||
|  |             const transfer_type = is_fifo or self.cnt.transfer_type.read(); | ||||||
|  |             const offset: u32 = if (transfer_type) @sizeOf(u32) else @sizeOf(u16); | ||||||
|  |  | ||||||
|  |             const mask = if (transfer_type) ~@as(u32, 3) else ~@as(u32, 1); | ||||||
|  |             const sad_addr = self.sad_latch & mask; | ||||||
|  |             const dad_addr = self.dad_latch & mask; | ||||||
|  |  | ||||||
|  |             if (transfer_type) { | ||||||
|  |                 if (sad_addr >= 0x0200_0000) self.data_latch = cpu.bus.read(u32, sad_addr); | ||||||
|  |                 cpu.bus.write(u32, dad_addr, self.data_latch); | ||||||
|  |             } else { | ||||||
|  |                 if (sad_addr >= 0x0200_0000) { | ||||||
|  |                     const value: u32 = cpu.bus.read(u16, sad_addr); | ||||||
|  |                     self.data_latch = value << 16 | value; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 cpu.bus.write(u16, dad_addr, @as(u16, @truncate(rotr(u32, self.data_latch, 8 * (dad_addr & 3))))); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             switch (@as(u8, @truncate(sad_addr >> 24))) { | ||||||
|  |                 // according to fleroviux, DMAs with a source address in ROM misbehave | ||||||
|  |                 // the resultant behaviour is that the source address will increment despite what DMAXCNT says | ||||||
|  |                 0x08...0x0D => self.sad_latch +%= offset, // obscure behaviour | ||||||
|  |                 else => switch (sad_adj) { | ||||||
|  |                     .Increment => self.sad_latch +%= offset, | ||||||
|  |                     .Decrement => self.sad_latch -%= offset, | ||||||
|  |                     .IncrementReload => log.err("{} is a prohibited adjustment on SAD", .{sad_adj}), | ||||||
|  |                     .Fixed => {}, | ||||||
|  |                 }, | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             switch (dad_adj) { | ||||||
|  |                 .Increment, .IncrementReload => self.dad_latch +%= offset, | ||||||
|  |                 .Decrement => self.dad_latch -%= offset, | ||||||
|  |                 .Fixed => {}, | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             self._word_count -= 1; | ||||||
|  |  | ||||||
|  |             if (self._word_count == 0) { | ||||||
|  |                 if (self.cnt.irq.read()) { | ||||||
|  |                     switch (id) { | ||||||
|  |                         0 => bus_ptr.io.irq.dma0.set(), | ||||||
|  |                         1 => bus_ptr.io.irq.dma1.set(), | ||||||
|  |                         2 => bus_ptr.io.irq.dma2.set(), | ||||||
|  |                         3 => bus_ptr.io.irq.dma3.set(), | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     handleInterrupt(.nds9, cpu); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // If we're not repeating, Fire the IRQs and disable the DMA | ||||||
|  |                 if (!self.cnt.repeat.read()) self.cnt.enabled.unset(); | ||||||
|  |  | ||||||
|  |                 // We want to disable our internal enabled flag regardless of repeat | ||||||
|  |                 // because we only want to step A DMA that repeats during it's specific | ||||||
|  |                 // timing window | ||||||
|  |                 self.in_progress = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn poll(self: *Self, comptime kind: Kind) void { | ||||||
|  |             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 | ||||||
|  |             // Determined by the repeat bit and whether the DMA is in the right start_timing | ||||||
|  |  | ||||||
|  |             switch (kind) { | ||||||
|  |                 .vblank => self.in_progress = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b01, | ||||||
|  |                 .hblank => self.in_progress = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b10, | ||||||
|  |                 else => {}, | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // If we determined that the repeat bit is set (and now the Hblank / Vblank DMA is now in progress) | ||||||
|  |             // Reload internal word count latch | ||||||
|  |             // Reload internal DAD latch if we are in IncrementRelaod | ||||||
|  |             if (self.in_progress) { | ||||||
|  |                 self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count; | ||||||
|  |                 if (@as(Adjustment, @enumFromInt(self.cnt.dad_adj.read())) == .IncrementReload) self.dad_latch = self.dad; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn requestAudio(self: *Self, _: u32) void { | ||||||
|  |             comptime std.debug.assert(id == 1 or id == 2); | ||||||
|  |             if (self.in_progress) return; // APU must wait their turn | ||||||
|  |  | ||||||
|  |             // DMA May not be configured for handling DMAs | ||||||
|  |             if (self.cnt.start_timing.read() != 0b11) return; | ||||||
|  |  | ||||||
|  |             // We Assume the Repeat Bit is Set | ||||||
|  |             // We Assume that DAD is set to 0x0400_00A0 or 0x0400_00A4 (fifo_addr) | ||||||
|  |             // We Assume DMACNT_L is set to 4 | ||||||
|  |  | ||||||
|  |             // FIXME: Safe to just assume whatever DAD is set to is the FIFO Address? | ||||||
|  |             // self.dad_latch = fifo_addr; | ||||||
|  |             self.cnt.repeat.set(); | ||||||
|  |             self._word_count = 4; | ||||||
|  |             self.in_progress = true; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn onVblank(bus: *System.Bus9) void { | ||||||
|  |     inline for (0..4) |i| bus.dma[i].poll(.vblank); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn onHblank(bus: *System.Bus9) void { | ||||||
|  |     inline for (0..4) |i| bus.dma[i].poll(.hblank); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn step(cpu: *System.Arm946es) bool { | ||||||
|  |     const bus: *System.Bus9 = @ptrCast(@alignCast(cpu.bus.ptr)); | ||||||
|  |  | ||||||
|  |     inline for (0..4) |i| { | ||||||
|  |         if (bus.dma[i].in_progress) { | ||||||
|  |             bus.dma[i].step(cpu); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const Adjustment = enum(u2) { | ||||||
|  |     Increment = 0, | ||||||
|  |     Decrement = 1, | ||||||
|  |     Fixed = 2, | ||||||
|  |     IncrementReload = 3, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const Kind = enum(u3) { | ||||||
|  |     immediate = 0, | ||||||
|  |     vblank, | ||||||
|  |     hblank, | ||||||
|  |  | ||||||
|  |     display_start_sync, | ||||||
|  |     main_mem_display, | ||||||
|  |     cartridge_slot, | ||||||
|  |     pak_slot, | ||||||
|  |     geo_cmd_fifo, | ||||||
|  | }; | ||||||
| @@ -10,12 +10,16 @@ const masks = @import("../io.zig").masks; | |||||||
| const IntEnable = @import("../io.zig").IntEnable; | const IntEnable = @import("../io.zig").IntEnable; | ||||||
| const IntRequest = @import("../io.zig").IntEnable; | const IntRequest = @import("../io.zig").IntEnable; | ||||||
|  |  | ||||||
|  | const dma = @import("dma.zig"); | ||||||
|  |  | ||||||
| const sext = @import("../../util.zig").sext; | const sext = @import("../../util.zig").sext; | ||||||
| const shift = @import("../../util.zig").shift; | const shift = @import("../../util.zig").shift; | ||||||
|  |  | ||||||
| const log = std.log.scoped(.nds9_io); | const log = std.log.scoped(.nds9_io); | ||||||
|  |  | ||||||
| pub const Io = struct { | pub const Io = struct { | ||||||
|  |     const fill_len = 0x10 * @sizeOf(u32); | ||||||
|  |  | ||||||
|     shr: *SharedCtx.Io, |     shr: *SharedCtx.Io, | ||||||
|  |  | ||||||
|     /// Interrupt Master Enable |     /// Interrupt Master Enable | ||||||
| @@ -38,6 +42,9 @@ pub const Io = struct { | |||||||
|     div: Divisor = .{}, |     div: Divisor = .{}, | ||||||
|     sqrt: SquareRootUnit = .{}, |     sqrt: SquareRootUnit = .{}, | ||||||
|  |  | ||||||
|  |     // TODO: move somewhere else? | ||||||
|  |     dma_fill: [fill_len]u8 = [_]u8{0} ** fill_len, | ||||||
|  |  | ||||||
|     pub fn init(io: *SharedCtx.Io) @This() { |     pub fn init(io: *SharedCtx.Io) @This() { | ||||||
|         return .{ .shr = io }; |         return .{ .shr = io }; | ||||||
|     } |     } | ||||||
| @@ -47,36 +54,48 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T { | |||||||
|     return switch (T) { |     return switch (T) { | ||||||
|         u32 => switch (address) { |         u32 => switch (address) { | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DC => warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DC => dma.read(T, &bus.dma, address) orelse 0x0000_0000, | ||||||
|             0x0400_00E0...0x0400_00EC => warn("TODO: impl DMA fill", .{}), |             0x0400_00E0...0x0400_00EC => std.mem.readInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], .little), | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010C => warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010C => warn("TODO(timer): read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), | ||||||
|  |  | ||||||
|             0x0400_0180 => bus.io.shr.ipc._nds9.sync.raw, |             0x0400_0180 => bus.io.shr.ipc._nds9.sync.raw, | ||||||
|             0x0400_0208 => @intFromBool(bus.io.ime), |             0x0400_0208 => @intFromBool(bus.io.ime), | ||||||
|             0x0400_0210 => bus.io.ie.raw, |             0x0400_0210 => bus.io.ie.raw, | ||||||
|             0x0400_0214 => bus.io.irq.raw, |             0x0400_0214 => bus.io.irq.raw, | ||||||
|  |  | ||||||
|  |             // zig fmt: off | ||||||
|  |             0x0400_0240 => @as(u32, bus.ppu.vram.io.cnt_d.raw) << 24 | ||||||
|  |                 | @as(u32, bus.ppu.vram.io.cnt_c.raw) << 16 | ||||||
|  |                 | @as(u32, bus.ppu.vram.io.cnt_b.raw) << 8 | ||||||
|  |                 | bus.ppu.vram.io.cnt_a.raw << 0, | ||||||
|  |             // zig fmt: on | ||||||
|  |  | ||||||
|  |             0x0400_0280 => bus.io.div.cnt.raw, | ||||||
|             0x0400_02A0, 0x0400_02A4 => @truncate(bus.io.div.result >> shift(u64, address)), |             0x0400_02A0, 0x0400_02A4 => @truncate(bus.io.div.result >> shift(u64, address)), | ||||||
|             0x0400_02A8, 0x0400_02AC => @truncate(bus.io.div.remainder >> shift(u64, address)), |             0x0400_02A8, 0x0400_02AC => @truncate(bus.io.div.remainder >> shift(u64, address)), | ||||||
|             0x0400_02B4 => @truncate(bus.io.sqrt.result), |             0x0400_02B4 => @truncate(bus.io.sqrt.result), | ||||||
|  |  | ||||||
|  |             0x0400_1000 => bus.ppu.engines[1].dispcnt.raw, | ||||||
|  |  | ||||||
|             0x0410_0000 => bus.io.shr.ipc.recv(.nds9), |             0x0410_0000 => bus.io.shr.ipc.recv(.nds9), | ||||||
|  |  | ||||||
|             0x0400_4000, 0x0400_4008 => 0x0000_0000, // Lets software know this is NOT a DSi |             0x0400_4000, 0x0400_4008 => 0x0000_0000, // Lets software know this is NOT a DSi | ||||||
|             else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), |             else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), | ||||||
|         }, |         }, | ||||||
|         u16 => switch (address) { |         u16 => switch (address) { | ||||||
|  |             0x0400_0006 => bus.ppu.io.nds9.vcount.raw, | ||||||
|  |  | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DE => warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DE => dma.read(T, &bus.dma, address) orelse 0x0000, | ||||||
|             0x0400_00E0...0x0400_00EE => warn("TODO: impl DMA fill", .{}), |             0x0400_00E0...0x0400_00EE => std.mem.readInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], .little), | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010E => warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010E => warn("TODO(timer): read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), | ||||||
|  |  | ||||||
|             0x0400_0004 => bus.ppu.io.nds9.dispstat.raw, |             0x0400_0004 => bus.ppu.io.nds9.dispstat.raw, | ||||||
|             0x0400_0130 => bus.io.shr.keyinput.load(.Monotonic), |             0x0400_0130 => bus.io.shr.input.keyinput().raw, | ||||||
|  |  | ||||||
|             0x0400_0180 => @truncate(bus.io.shr.ipc._nds9.sync.raw), |             0x0400_0180 => @truncate(bus.io.shr.ipc._nds9.sync.raw), | ||||||
|             0x0400_0184 => @truncate(bus.io.shr.ipc._nds9.cnt.raw), |             0x0400_0184 => @truncate(bus.io.shr.ipc._nds9.cnt.raw), | ||||||
| @@ -84,15 +103,29 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T { | |||||||
|             0x0400_0280 => @truncate(bus.io.div.cnt.raw), |             0x0400_0280 => @truncate(bus.io.div.cnt.raw), | ||||||
|             0x0400_02B0 => @truncate(bus.io.sqrt.cnt.raw), |             0x0400_02B0 => @truncate(bus.io.sqrt.cnt.raw), | ||||||
|  |  | ||||||
|  |             0x0400_1008 => bus.ppu.engines[1].bg[0].cnt.raw, | ||||||
|  |             0x0400_100A => bus.ppu.engines[1].bg[1].cnt.raw, | ||||||
|  |             0x0400_100C => bus.ppu.engines[1].bg[2].cnt.raw, | ||||||
|  |             0x0400_100E => bus.ppu.engines[1].bg[3].cnt.raw, | ||||||
|  |  | ||||||
|  |             0x0400_1010 => bus.ppu.engines[1].bg[0].hofs.raw, | ||||||
|  |             0x0400_1012 => bus.ppu.engines[1].bg[0].vofs.raw, | ||||||
|  |             0x0400_1014 => bus.ppu.engines[1].bg[1].hofs.raw, | ||||||
|  |             0x0400_1016 => bus.ppu.engines[1].bg[1].vofs.raw, | ||||||
|  |             0x0400_1018 => bus.ppu.engines[1].bg[2].hofs.raw, | ||||||
|  |             0x0400_101A => bus.ppu.engines[1].bg[2].vofs.raw, | ||||||
|  |             0x0400_101C => bus.ppu.engines[1].bg[3].hofs.raw, | ||||||
|  |             0x0400_101E => bus.ppu.engines[1].bg[3].vofs.raw, | ||||||
|  |  | ||||||
|             else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), |             else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), | ||||||
|         }, |         }, | ||||||
|         u8 => switch (address) { |         u8 => switch (address) { | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DF => warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DF => dma.read(T, &bus.dma, address) orelse 0x00, | ||||||
|             0x0400_00E0...0x0400_00EF => warn("TODO: impl DMA fill", .{}), |             0x0400_00E0...0x0400_00EF => std.mem.readInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], .little), | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010F => warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010F => warn("TODO(timer): read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), | ||||||
|  |  | ||||||
|             0x0400_0208 => @intFromBool(bus.io.ime), |             0x0400_0208 => @intFromBool(bus.io.ime), | ||||||
|  |  | ||||||
| @@ -108,28 +141,70 @@ const subset = @import("../../util.zig").subset; | |||||||
| pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | ||||||
|     switch (T) { |     switch (T) { | ||||||
|         u32 => switch (address) { |         u32 => switch (address) { | ||||||
|  |             0x0400_0000 => bus.ppu.engines[0].dispcnt.raw = value, | ||||||
|  |             0x0400_0004 => bus.ppu.io.nds9.dispstat.raw = @truncate(value), | ||||||
|  |             0x0400_0008 => { | ||||||
|  |                 bus.ppu.engines[0].bg[0].cnt.raw = @truncate(value >> 0); // 0x0400_0008 | ||||||
|  |                 bus.ppu.engines[0].bg[1].cnt.raw = @truncate(value >> 16); // 0x0400_000A | ||||||
|  |             }, | ||||||
|  |             0x0400_000C => { | ||||||
|  |                 bus.ppu.engines[0].bg[2].cnt.raw = @truncate(value >> 0); // 0x0400_000A | ||||||
|  |                 bus.ppu.engines[0].bg[3].cnt.raw = @truncate(value >> 16); // 0x0400_000C | ||||||
|  |             }, | ||||||
|  |             0x00400_0010 => { | ||||||
|  |                 bus.ppu.engines[0].bg[0].hofs.raw = @truncate(value >> 0); // 0x0400_0010 | ||||||
|  |                 bus.ppu.engines[0].bg[0].vofs.raw = @truncate(value >> 16); // 0x0400_0012 | ||||||
|  |             }, | ||||||
|  |             0x00400_0014 => { | ||||||
|  |                 bus.ppu.engines[0].bg[1].hofs.raw = @truncate(value >> 0); // 0x0400_0014 | ||||||
|  |                 bus.ppu.engines[0].bg[1].vofs.raw = @truncate(value >> 16); // 0x0400_0016 | ||||||
|  |             }, | ||||||
|  |             0x00400_0018 => { | ||||||
|  |                 bus.ppu.engines[0].bg[2].hofs.raw = @truncate(value >> 0); // 0x0400_0018 | ||||||
|  |                 bus.ppu.engines[0].bg[2].vofs.raw = @truncate(value >> 16); // 0x0400_001A | ||||||
|  |             }, | ||||||
|  |             0x00400_001C => { | ||||||
|  |                 bus.ppu.engines[0].bg[3].hofs.raw = @truncate(value >> 0); // 0x0400_001C | ||||||
|  |                 bus.ppu.engines[0].bg[3].vofs.raw = @truncate(value >> 16); // 0x0400_001E | ||||||
|  |             }, | ||||||
|  |  | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DC => log.warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DC => dma.write(T, &bus.dma, address, value), | ||||||
|             0x0400_00E0...0x0400_00EC => log.warn("TODO: impl DMA fill", .{}), |             0x0400_00E0...0x0400_00EC => std.mem.writeInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], value, .little), | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010C => log.warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010C => log.warn("TODO(timer): write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||||
|  |  | ||||||
|             0x0400_0000 => bus.ppu.engines[0].dispcnt.raw = value, |  | ||||||
|             0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds9, value), |             0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds9, value), | ||||||
|             0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds9, value), |             0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds9, value), | ||||||
|             0x0400_0188 => bus.io.shr.ipc.send(.nds9, value), |             0x0400_0188 => bus.io.shr.ipc.send(.nds9, value), | ||||||
|  |  | ||||||
|  |             0x0400_0208 => bus.io.ime = value & 1 == 1, | ||||||
|  |             0x0400_0210 => bus.io.ie.raw = value, | ||||||
|  |             0x0400_0214 => bus.io.irq.raw &= ~value, | ||||||
|  |  | ||||||
|             0x0400_0240 => { |             0x0400_0240 => { | ||||||
|                 bus.ppu.vram.io.cnt_a.raw = @truncate(value >> 0); // 0x0400_0240 |                 bus.ppu.vram.io.cnt_a.raw = @truncate(value >> 0); // 0x0400_0240 | ||||||
|                 bus.ppu.vram.io.cnt_b.raw = @truncate(value >> 8); // 0x0400_0241 |                 bus.ppu.vram.io.cnt_b.raw = @truncate(value >> 8); // 0x0400_0241 | ||||||
|                 bus.ppu.vram.io.cnt_c.raw = @truncate(value >> 16); // 0x0400_0242 |                 bus.ppu.vram.io.cnt_c.raw = @truncate(value >> 16); // 0x0400_0242 | ||||||
|                 bus.ppu.vram.io.cnt_d.raw = @truncate(value >> 24); // 0x0400_0243 |                 bus.ppu.vram.io.cnt_d.raw = @truncate(value >> 24); // 0x0400_0243 | ||||||
|  |  | ||||||
|  |                 bus.ppu.vram.update(); | ||||||
|  |             }, | ||||||
|  |             0x0400_0244 => { | ||||||
|  |                 bus.ppu.vram.io.cnt_e.raw = @truncate(value >> 0); // 0x0400_0244 | ||||||
|  |                 bus.ppu.vram.io.cnt_f.raw = @truncate(value >> 8); // 0x0400_0245 | ||||||
|  |                 bus.ppu.vram.io.cnt_g.raw = @truncate(value >> 16); // 0x0400_0246 | ||||||
|  |                 bus.io.shr.wramcnt.raw = @truncate(value >> 24); // 0x0400_0247 | ||||||
|  |  | ||||||
|  |                 bus.ppu.vram.update(); | ||||||
|  |                 bus.wram.update(bus.io.shr.wramcnt); | ||||||
|             }, |             }, | ||||||
|  |  | ||||||
|             0x0400_0208 => bus.io.ime = value & 1 == 1, |             0x0400_0280 => { | ||||||
|             0x0400_0210 => bus.io.ie.raw = value, |                 bus.io.div.cnt.raw = value; | ||||||
|             0x0400_0214 => bus.io.irq.raw &= ~value, |                 bus.io.div.schedule(bus.scheduler); | ||||||
|  |             }, | ||||||
|  |  | ||||||
|             0x0400_0290, 0x0400_0294 => { |             0x0400_0290, 0x0400_0294 => { | ||||||
|                 bus.io.div.numerator = subset(u64, u32, address, bus.io.div.numerator, value); |                 bus.io.div.numerator = subset(u64, u32, address, bus.io.div.numerator, value); | ||||||
| @@ -141,6 +216,11 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | |||||||
|                 bus.io.div.schedule(bus.scheduler); |                 bus.io.div.schedule(bus.scheduler); | ||||||
|             }, |             }, | ||||||
|  |  | ||||||
|  |             0x0400_02B0 => { | ||||||
|  |                 bus.io.sqrt.cnt.raw = value; | ||||||
|  |                 bus.io.sqrt.schedule(bus.scheduler); | ||||||
|  |             }, | ||||||
|  |  | ||||||
|             0x0400_02B8, 0x0400_02BC => { |             0x0400_02B8, 0x0400_02BC => { | ||||||
|                 bus.io.sqrt.param = subset(u64, u32, address, bus.io.sqrt.param, value); |                 bus.io.sqrt.param = subset(u64, u32, address, bus.io.sqrt.param, value); | ||||||
|                 bus.io.sqrt.schedule(bus.scheduler); |                 bus.io.sqrt.schedule(bus.scheduler); | ||||||
| @@ -150,6 +230,30 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | |||||||
|             0x0400_0304 => bus.ppu.io.powcnt.raw = value, |             0x0400_0304 => bus.ppu.io.powcnt.raw = value, | ||||||
|  |  | ||||||
|             0x0400_1000 => bus.ppu.engines[1].dispcnt.raw = value, |             0x0400_1000 => bus.ppu.engines[1].dispcnt.raw = value, | ||||||
|  |             0x0400_1008 => { | ||||||
|  |                 bus.ppu.engines[1].bg[0].cnt.raw = @truncate(value >> 0); // 0x0400_1008 | ||||||
|  |                 bus.ppu.engines[1].bg[1].cnt.raw = @truncate(value >> 16); // 0x0400_100A | ||||||
|  |             }, | ||||||
|  |             0x0400_100C => { | ||||||
|  |                 bus.ppu.engines[1].bg[2].cnt.raw = @truncate(value >> 0); // 0x0400_100A | ||||||
|  |                 bus.ppu.engines[1].bg[3].cnt.raw = @truncate(value >> 16); // 0x0400_100C | ||||||
|  |             }, | ||||||
|  |             0x00400_1010 => { | ||||||
|  |                 bus.ppu.engines[1].bg[0].hofs.raw = @truncate(value >> 0); // 0x0400_1010 | ||||||
|  |                 bus.ppu.engines[1].bg[0].vofs.raw = @truncate(value >> 16); // 0x0400_1012 | ||||||
|  |             }, | ||||||
|  |             0x00400_1014 => { | ||||||
|  |                 bus.ppu.engines[1].bg[1].hofs.raw = @truncate(value >> 0); // 0x0400_1014 | ||||||
|  |                 bus.ppu.engines[1].bg[1].vofs.raw = @truncate(value >> 16); // 0x0400_1016 | ||||||
|  |             }, | ||||||
|  |             0x00400_1018 => { | ||||||
|  |                 bus.ppu.engines[1].bg[2].hofs.raw = @truncate(value >> 0); // 0x0400_1018 | ||||||
|  |                 bus.ppu.engines[1].bg[2].vofs.raw = @truncate(value >> 16); // 0x0400_101A | ||||||
|  |             }, | ||||||
|  |             0x00400_101C => { | ||||||
|  |                 bus.ppu.engines[1].bg[3].hofs.raw = @truncate(value >> 0); // 0x0400_101C | ||||||
|  |                 bus.ppu.engines[1].bg[3].vofs.raw = @truncate(value >> 16); // 0x0400_101E | ||||||
|  |             }, | ||||||
|  |  | ||||||
|             else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), |             else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||||
|         }, |         }, | ||||||
| @@ -157,11 +261,11 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | |||||||
|             0x0400_0004 => bus.ppu.io.nds9.dispstat.raw = value, |             0x0400_0004 => bus.ppu.io.nds9.dispstat.raw = value, | ||||||
|  |  | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DE => log.warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DE => dma.write(T, &bus.dma, address, value), | ||||||
|             0x0400_00E0...0x0400_00EE => log.warn("TODO: impl DMA fill", .{}), |             0x0400_00E0...0x0400_00EE => std.mem.writeInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], value, .little), | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010E => log.warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010E => log.warn("TODO(timer): write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||||
|  |  | ||||||
|             0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds9, value), |             0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds9, value), | ||||||
|             0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds9, value), |             0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds9, value), | ||||||
| @@ -179,15 +283,29 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { | |||||||
|  |  | ||||||
|             0x0400_0304 => bus.ppu.io.powcnt.raw = value, |             0x0400_0304 => bus.ppu.io.powcnt.raw = value, | ||||||
|  |  | ||||||
|  |             0x0400_1008 => bus.ppu.engines[1].bg[0].cnt.raw = value, | ||||||
|  |             0x0400_100A => bus.ppu.engines[1].bg[1].cnt.raw = value, | ||||||
|  |             0x0400_100C => bus.ppu.engines[1].bg[2].cnt.raw = value, | ||||||
|  |             0x0400_100E => bus.ppu.engines[1].bg[3].cnt.raw = value, | ||||||
|  |  | ||||||
|  |             0x0400_1010 => bus.ppu.engines[1].bg[0].hofs.raw = value, | ||||||
|  |             0x0400_1012 => bus.ppu.engines[1].bg[0].vofs.raw = value, | ||||||
|  |             0x0400_1014 => bus.ppu.engines[1].bg[1].hofs.raw = value, | ||||||
|  |             0x0400_1016 => bus.ppu.engines[1].bg[1].vofs.raw = value, | ||||||
|  |             0x0400_1018 => bus.ppu.engines[1].bg[2].hofs.raw = value, | ||||||
|  |             0x0400_101A => bus.ppu.engines[1].bg[2].vofs.raw = value, | ||||||
|  |             0x0400_101C => bus.ppu.engines[1].bg[3].hofs.raw = value, | ||||||
|  |             0x0400_101E => bus.ppu.engines[1].bg[3].vofs.raw = value, | ||||||
|  |  | ||||||
|             else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>4})", .{ T, address, value }), |             else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>4})", .{ T, address, value }), | ||||||
|         }, |         }, | ||||||
|         u8 => switch (address) { |         u8 => switch (address) { | ||||||
|             // DMA Transfers |             // DMA Transfers | ||||||
|             0x0400_00B0...0x0400_00DF => log.warn("TODO: impl DMA", .{}), |             0x0400_00B0...0x0400_00DF => dma.write(T, &bus.dma, address, value), | ||||||
|             0x0400_00E0...0x0400_00EF => log.warn("TODO: impl DMA fill", .{}), |             0x0400_00E0...0x0400_00EF => std.mem.writeInt(T, bus.io.dma_fill[address & 0xF ..][0..@sizeOf(T)], value, .little), | ||||||
|  |  | ||||||
|             // Timers |             // Timers | ||||||
|             0x0400_0100...0x0400_010F => log.warn("TODO: impl timer", .{}), |             0x0400_0100...0x0400_010F => log.warn("TODO(timer): write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), | ||||||
|  |  | ||||||
|             0x0400_0208 => bus.io.ime = value & 1 == 1, |             0x0400_0208 => bus.io.ime = value & 1 == 1, | ||||||
|  |  | ||||||
| @@ -400,7 +518,7 @@ pub const DispcntA = extern union { | |||||||
|     tile_obj_1d_boundary: Bitfield(u32, 20, 2), |     tile_obj_1d_boundary: Bitfield(u32, 20, 2), | ||||||
|     bitmap_obj_1d_boundary: Bit(u32, 22), |     bitmap_obj_1d_boundary: Bit(u32, 22), | ||||||
|     obj_during_hblank: Bit(u32, 23), |     obj_during_hblank: Bit(u32, 23), | ||||||
|     character_base: Bitfield(u32, 24, 3), |     char_base: Bitfield(u32, 24, 3), | ||||||
|     screen_base: Bitfield(u32, 27, 3), |     screen_base: Bitfield(u32, 27, 3), | ||||||
|     bg_ext_pal_enable: Bit(u32, 30), |     bg_ext_pal_enable: Bit(u32, 30), | ||||||
|     obj_ext_pal_enable: Bit(u32, 31), |     obj_ext_pal_enable: Bit(u32, 31), | ||||||
| @@ -476,3 +594,34 @@ pub const Dispstat = extern union { | |||||||
|     lyc: Bitfield(u16, 7, 9), |     lyc: Bitfield(u16, 7, 9), | ||||||
|     raw: u16, |     raw: u16, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | pub const Bgcnt = extern union { | ||||||
|  |     priority: Bitfield(u16, 0, 2), | ||||||
|  |     char_base: Bitfield(u16, 2, 4), | ||||||
|  |     mosaic_enable: Bit(u16, 6), | ||||||
|  |     colour_mode: Bit(u16, 7), | ||||||
|  |     screen_base: Bitfield(u16, 8, 5), | ||||||
|  |     display_overflow: Bit(u16, 13), | ||||||
|  |     size: Bitfield(u16, 14, 2), | ||||||
|  |     raw: u16, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// Write Only | ||||||
|  | const BackgroundOffset = extern union { | ||||||
|  |     offset: Bitfield(u16, 0, 9), | ||||||
|  |     raw: u16, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | pub const Hofs = BackgroundOffset; | ||||||
|  | pub const Vofs = BackgroundOffset; | ||||||
|  |  | ||||||
|  | pub const DmaCnt = extern union { | ||||||
|  |     dad_adj: Bitfield(u16, 5, 2), | ||||||
|  |     sad_adj: Bitfield(u16, 7, 2), | ||||||
|  |     repeat: Bit(u16, 9), | ||||||
|  |     transfer_type: Bit(u16, 10), | ||||||
|  |     start_timing: Bitfield(u16, 11, 3), | ||||||
|  |     irq: Bit(u16, 14), | ||||||
|  |     enabled: Bit(u16, 15), | ||||||
|  |     raw: u16, | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -5,9 +5,14 @@ const Scheduler = @import("Scheduler.zig"); | |||||||
| const System = @import("emu.zig").System; | const System = @import("emu.zig").System; | ||||||
|  |  | ||||||
| const Vram = @import("ppu/Vram.zig"); | const Vram = @import("ppu/Vram.zig"); | ||||||
|  | const Oam = @import("ppu/Oam.zig"); | ||||||
|  |  | ||||||
| const EngineA = @import("ppu/engine.zig").EngineA; | const EngineA = @import("ppu/engine.zig").EngineA; | ||||||
| const EngineB = @import("ppu/engine.zig").EngineB; | const EngineB = @import("ppu/engine.zig").EngineB; | ||||||
|  |  | ||||||
|  | const dma7 = @import("nds7/dma.zig"); | ||||||
|  | const dma9 = @import("nds9/dma.zig"); | ||||||
|  |  | ||||||
| const handleInterrupt = @import("emu.zig").handleInterrupt; | const handleInterrupt = @import("emu.zig").handleInterrupt; | ||||||
|  |  | ||||||
| pub const screen_width = 256; | pub const screen_width = 256; | ||||||
| @@ -21,7 +26,10 @@ pub const Ppu = struct { | |||||||
|  |  | ||||||
|     vram: *Vram, |     vram: *Vram, | ||||||
|  |  | ||||||
|     engines: struct { EngineA, EngineB } = .{ .{}, .{} }, |     // FIXME: do I need a pointer here? | ||||||
|  |     oam: *Oam, | ||||||
|  |  | ||||||
|  |     engines: struct { EngineA, EngineB }, | ||||||
|  |  | ||||||
|     io: Io = .{}, |     io: Io = .{}, | ||||||
|  |  | ||||||
| @@ -44,20 +52,29 @@ pub const Ppu = struct { | |||||||
|     pub fn init(allocator: Allocator, vram: *Vram) !@This() { |     pub fn init(allocator: Allocator, vram: *Vram) !@This() { | ||||||
|         return .{ |         return .{ | ||||||
|             .fb = try FrameBuffer.init(allocator), |             .fb = try FrameBuffer.init(allocator), | ||||||
|  |             .engines = .{ try EngineA.init(allocator), try EngineB.init(allocator) }, | ||||||
|             .vram = vram, |             .vram = vram, | ||||||
|  |             .oam = blk: { | ||||||
|  |                 var oam = try allocator.create(Oam); | ||||||
|  |                 oam.init(); | ||||||
|  |  | ||||||
|  |                 break :blk oam; | ||||||
|  |             }, | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn deinit(self: @This(), allocator: Allocator) void { |     pub fn deinit(self: @This(), allocator: Allocator) void { | ||||||
|         self.fb.deinit(allocator); |         self.fb.deinit(allocator); | ||||||
|  |         inline for (self.engines) |eng| eng.deinit(allocator); | ||||||
|  |         allocator.destroy(self.oam); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn drawScanline(self: *@This(), bus: *System.Bus9) void { |     pub fn drawScanline(self: *@This(), bus: *System.Bus9) void { | ||||||
|         if (self.io.powcnt.engine2d_a.read()) |         if (self.io.powcnt.engine2d_a.read()) | ||||||
|             self.engines[0].drawScanline(bus, &self.fb, &self.io); |             self.engines[0].drawScanline(bus, &self.fb); | ||||||
|  |  | ||||||
|         if (self.io.powcnt.engine2d_b.read()) |         if (self.io.powcnt.engine2d_b.read()) | ||||||
|             self.engines[1].drawScanline(bus, &self.fb, &self.io); |             self.engines[1].drawScanline(bus, &self.fb); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// HDraw -> HBlank |     /// HDraw -> HBlank | ||||||
| @@ -72,7 +89,9 @@ pub const Ppu = struct { | |||||||
|             handleInterrupt(.nds7, system.arm7tdmi); |             handleInterrupt(.nds7, system.arm7tdmi); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // TODO: Run DMAs on HBlank |         if (!self.io.nds9.dispstat.vblank.read()) { // ensure we aren't in VBlank | ||||||
|  |             dma9.onHblank(system.bus9); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         self.io.nds9.dispstat.hblank.set(); |         self.io.nds9.dispstat.hblank.set(); | ||||||
|         self.io.nds7.dispstat.hblank.set(); |         self.io.nds7.dispstat.hblank.set(); | ||||||
| @@ -159,7 +178,11 @@ pub const Ppu = struct { | |||||||
|             self.io.nds7.dispstat.vblank.set(); |             self.io.nds7.dispstat.vblank.set(); | ||||||
|  |  | ||||||
|             // TODO: Affine BG Latches |             // TODO: Affine BG Latches | ||||||
|             // TODO: VBlank DMA Transfers |  | ||||||
|  |             dma7.onVblank(system.bus7); | ||||||
|  |             dma9.onVblank(system.bus9); | ||||||
|  |  | ||||||
|  |             // TODO: VBlank DMA9 Transfers | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (scanline == 262) { |         if (scanline == 262) { | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								src/core/ppu/Oam.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/core/ppu/Oam.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | const KiB = 0x400; | ||||||
|  |  | ||||||
|  | buf: [2 * KiB]u8, | ||||||
|  |  | ||||||
|  | pub fn init(self: *@This()) void { | ||||||
|  |     @memset(self.buf[0..], 0); | ||||||
|  | } | ||||||
| @@ -1,6 +1,8 @@ | |||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| const KiB = 0x400; | const KiB = 0x400; | ||||||
|  |  | ||||||
|  | const System = @import("../emu.zig").System; | ||||||
|  |  | ||||||
| const Allocator = std.mem.Allocator; | const Allocator = std.mem.Allocator; | ||||||
| const IntFittingRange = std.math.IntFittingRange; | const IntFittingRange = std.math.IntFittingRange; | ||||||
|  |  | ||||||
| @@ -253,15 +255,12 @@ pub fn update(self: *@This()) void { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| // TODO: Rename | pub fn read(self: @This(), comptime T: type, comptime proc: System.Process, address: u32) T { | ||||||
| const Device = enum { nds9, nds7 }; |  | ||||||
|  |  | ||||||
| pub fn read(self: @This(), comptime T: type, comptime dev: Device, address: u32) T { |  | ||||||
|     const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; |     const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; | ||||||
|     const masked_addr = address & (addr_space_size - 1); |     const masked_addr = address & (addr_space_size - 1); | ||||||
|     const page = masked_addr >> bits; |     const page = masked_addr >> bits; | ||||||
|     const offset = masked_addr & (page_size - 1); |     const offset = masked_addr & (page_size - 1); | ||||||
|     const table = if (dev == .nds9) self.nds9_table else self.nds7_table; |     const table = if (proc == .nds9) self.nds9_table else self.nds7_table; | ||||||
|  |  | ||||||
|     if (table[page]) |some_ptr| { |     if (table[page]) |some_ptr| { | ||||||
|         const ptr: [*]const T = @ptrCast(@alignCast(some_ptr)); |         const ptr: [*]const T = @ptrCast(@alignCast(some_ptr)); | ||||||
| @@ -269,16 +268,16 @@ pub fn read(self: @This(), comptime T: type, comptime dev: Device, address: u32) | |||||||
|         return ptr[offset / @sizeOf(T)]; |         return ptr[offset / @sizeOf(T)]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     log.err("{s}: read(T: {}, addr: 0x{X:0>8}) was in un-mapped VRAM space", .{ @tagName(dev), T, address }); |     log.err("{s}: read(T: {}, addr: 0x{X:0>8}) was in un-mapped VRAM space", .{ @tagName(proc), T, address }); | ||||||
|     return 0x00; |     return 0x00; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn write(self: *@This(), comptime T: type, comptime dev: Device, address: u32, value: T) void { | pub fn write(self: *@This(), comptime T: type, comptime proc: System.Process, address: u32, value: T) void { | ||||||
|     const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; |     const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; | ||||||
|     const masked_addr = address & (addr_space_size - 1); |     const masked_addr = address & (addr_space_size - 1); | ||||||
|     const page = masked_addr >> bits; |     const page = masked_addr >> bits; | ||||||
|     const offset = masked_addr & (page_size - 1); |     const offset = masked_addr & (page_size - 1); | ||||||
|     const table = if (dev == .nds9) self.nds9_table else self.nds7_table; |     const table = if (proc == .nds9) self.nds9_table else self.nds7_table; | ||||||
|  |  | ||||||
|     if (table[page]) |some_ptr| { |     if (table[page]) |some_ptr| { | ||||||
|         const ptr: [*]T = @ptrCast(@alignCast(some_ptr)); |         const ptr: [*]T = @ptrCast(@alignCast(some_ptr)); | ||||||
| @@ -287,5 +286,5 @@ pub fn write(self: *@This(), comptime T: type, comptime dev: Device, address: u3 | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     log.err("{s}: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8}) was in un-mapped VRA< space", .{ @tagName(dev), T, address, value }); |     log.err("{s}: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8}) was in un-mapped VRA< space", .{ @tagName(proc), T, address, value }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,14 +11,15 @@ const Ppu = @import("../ppu.zig").Ppu; | |||||||
| const width = @import("../ppu.zig").screen_width; | const width = @import("../ppu.zig").screen_width; | ||||||
| const height = @import("../ppu.zig").screen_height; | const height = @import("../ppu.zig").screen_height; | ||||||
|  |  | ||||||
|  | const KiB = 0x400; | ||||||
|  |  | ||||||
| const EngineKind = enum { a, b }; | const EngineKind = enum { a, b }; | ||||||
|  |  | ||||||
| pub const EngineA = Engine(.a); | pub const EngineA = Engine(.a); | ||||||
| pub const EngineB = Engine(.b); | pub const EngineB = Engine(.b); | ||||||
|  |  | ||||||
| fn Engine(comptime kind: EngineKind) type { | fn Engine(comptime kind: EngineKind) type { | ||||||
|     const log = std.log.scoped(.engine2d); |     const log = std.log.scoped(.engine2d); // TODO: specify between 2D-A and 2D-B | ||||||
|     _ = log; // TODO: specify between 2D-A and 2D-B |  | ||||||
|  |  | ||||||
|     // FIXME: don't commit zig crimes |     // FIXME: don't commit zig crimes | ||||||
|     const Type = struct { |     const Type = struct { | ||||||
| @@ -30,25 +31,76 @@ fn Engine(comptime kind: EngineKind) type { | |||||||
|     return struct { |     return struct { | ||||||
|         dispcnt: Type(DispcntA, DispcntB) = .{ .raw = 0x0000_0000 }, |         dispcnt: Type(DispcntA, DispcntB) = .{ .raw = 0x0000_0000 }, | ||||||
|  |  | ||||||
|         pub fn drawScanline(self: *@This(), bus: *Bus, fb: *FrameBuffer, io: *Ppu.Io) void { |         bg: [4]bg.Text = .{ .{}, .{}, .{}, .{} }, | ||||||
|  |  | ||||||
|  |         // TODO: Rename | ||||||
|  |         scanline: Scanline, | ||||||
|  |  | ||||||
|  |         pub fn init(allocator: std.mem.Allocator) !@This() { | ||||||
|  |             return .{ | ||||||
|  |                 .scanline = try Scanline.init(allocator), | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn deinit(self: @This(), allocator: std.mem.Allocator) void { | ||||||
|  |             self.scanline.deinit(allocator); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn drawScanline(self: *@This(), bus: *Bus, fb: *FrameBuffer) void { | ||||||
|             const disp_mode = self.dispcnt.display_mode.read(); |             const disp_mode = self.dispcnt.display_mode.read(); | ||||||
|  |  | ||||||
|             switch (disp_mode) { |             switch (disp_mode) { | ||||||
|                 0 => { // Display Off |                 0 => { // Display Off | ||||||
|                     const buf = switch (kind) { |                     const buf = switch (kind) { | ||||||
|                         .a => if (io.powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back), |                         .a => if (bus.ppu.io.powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back), | ||||||
|                         .b => if (io.powcnt.display_swap.read()) fb.btm(.back) else fb.top(.back), |                         .b => if (bus.ppu.io.powcnt.display_swap.read()) fb.btm(.back) else fb.top(.back), | ||||||
|                     }; |                     }; | ||||||
|  |  | ||||||
|                     @memset(buf, 0xFF); // set everything to white |                     @memset(buf, 0xFF); // set everything to white | ||||||
|                 }, |                 }, | ||||||
|                 1 => @panic("TODO: standard graphics display (text mode, etc)"), |                 1 => { | ||||||
|  |                     const bg_mode = self.dispcnt.bg_mode.read(); | ||||||
|  |                     const bg_enable = self.dispcnt.bg_enable.read(); | ||||||
|  |                     const scanline: u32 = bus.ppu.io.nds9.vcount.scanline.read(); | ||||||
|  |  | ||||||
|  |                     switch (bg_mode) { | ||||||
|  |                         //  BG0     BG1     BG2     BG3 | ||||||
|  |                         //  Text/3D Text    Text    Text | ||||||
|  |                         0 => { | ||||||
|  |                             // TODO: Fetch Sprites | ||||||
|  |  | ||||||
|  |                             for (0..4) |layer| { | ||||||
|  |                                 // TODO: Draw Sprites | ||||||
|  |  | ||||||
|  |                                 inline for (0..4) |i| { | ||||||
|  |                                     if (layer == self.bg[i].cnt.priority.read() and (bg_enable >> i) & 1 == 1) self.drawBackground(i, bus); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             const buf = switch (kind) { | ||||||
|  |                                 .a => if (bus.ppu.io.powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back), | ||||||
|  |                                 .b => if (bus.ppu.io.powcnt.display_swap.read()) fb.btm(.back) else fb.top(.back), | ||||||
|  |                             }; | ||||||
|  |  | ||||||
|  |                             const scanline_buf = blk: { | ||||||
|  |                                 const rgba_ptr: *[width * height]u32 = @ptrCast(@alignCast(buf)); | ||||||
|  |                                 break :blk rgba_ptr[width * scanline ..][0..width]; | ||||||
|  |                             }; | ||||||
|  |  | ||||||
|  |                             self.renderTextMode(bus.dbgRead(u16, 0x0500_0000), scanline_buf); | ||||||
|  |                         }, | ||||||
|  |                         else => |mode| { | ||||||
|  |                             log.err("TODO: Implement Mode {}", .{mode}); | ||||||
|  |                             @panic("fatal error"); | ||||||
|  |                         }, | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|                 2 => { // VRAM display |                 2 => { // VRAM display | ||||||
|                     if (kind == .b) return; |                     if (kind == .b) return; | ||||||
|                     // TODO: Master Brightness can still affect this mode |                     // TODO: Master Brightness can still affect this mode | ||||||
|  |  | ||||||
|                     const scanline: u32 = io.nds9.vcount.scanline.read(); |                     const scanline: u32 = bus.ppu.io.nds9.vcount.scanline.read(); | ||||||
|                     const buf = if (io.powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back); |                     const buf = if (bus.ppu.io.powcnt.display_swap.read()) fb.top(.back) else fb.btm(.back); | ||||||
|  |  | ||||||
|                     const scanline_buf = blk: { |                     const scanline_buf = blk: { | ||||||
|                         const rgba_ptr: *[width * height]u32 = @ptrCast(@alignCast(buf)); |                         const rgba_ptr: *[width * height]u32 = @ptrCast(@alignCast(buf)); | ||||||
| @@ -69,9 +121,211 @@ fn Engine(comptime kind: EngineKind) type { | |||||||
|                 }, |                 }, | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         fn drawBackground(self: *@This(), comptime layer: u2, bus: *Bus) void { | ||||||
|  |             const screen_base = blk: { | ||||||
|  |                 const bgcnt_off: u32 = self.bg[layer].cnt.screen_base.read(); | ||||||
|  |                 const dispcnt_off: u32 = if (kind == .a) self.dispcnt.screen_base.read() else 0; | ||||||
|  |  | ||||||
|  |                 break :blk (2 * KiB) * bgcnt_off + (64 * KiB) * dispcnt_off; | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             const char_base = blk: { | ||||||
|  |                 const bgcnt_off: u32 = self.bg[layer].cnt.char_base.read(); | ||||||
|  |                 const dispcnt_off: u32 = if (kind == .a) self.dispcnt.char_base.read() else 0; | ||||||
|  |  | ||||||
|  |                 break :blk (16 * KiB) * bgcnt_off + (64 * KiB) * dispcnt_off; | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             const is_8bpp = self.bg[layer].cnt.colour_mode.read(); | ||||||
|  |             const size = self.bg[layer].cnt.size.read(); | ||||||
|  |  | ||||||
|  |             // In 4bpp: 1 byte represents two pixels so the length is (8 x 8) / 2 | ||||||
|  |             // In 8bpp: 1 byte represents one pixel so the length is 8 x 8 | ||||||
|  |             const tile_len: u32 = if (is_8bpp) 0x40 else 0x20; | ||||||
|  |             const tile_row_offset: u32 = if (is_8bpp) 0x8 else 0x4; | ||||||
|  |  | ||||||
|  |             const vofs: u32 = self.bg[layer].vofs.offset.read(); | ||||||
|  |             const hofs: u32 = self.bg[layer].hofs.offset.read(); | ||||||
|  |  | ||||||
|  |             const y: u32 = vofs + bus.ppu.io.nds9.vcount.scanline.read(); | ||||||
|  |  | ||||||
|  |             for (0..width) |idx| { | ||||||
|  |                 const i: u32 = @intCast(idx); | ||||||
|  |                 const x = hofs + i; | ||||||
|  |  | ||||||
|  |                 // TODO: Windowing | ||||||
|  |  | ||||||
|  |                 // Grab the Screen Entry from VRAM | ||||||
|  |                 const entry_addr = screen_base + tilemapOffset(size, x, y); | ||||||
|  |                 const entry: bg.Screen.Entry = @bitCast(bus.read(u16, 0x0600_0000 + entry_addr)); | ||||||
|  |  | ||||||
|  |                 // Calculate the Address of the Tile in the designated Charblock | ||||||
|  |                 // We also take this opportunity to flip tiles if necessary | ||||||
|  |                 const tile_id: u32 = entry.tile_id.read(); | ||||||
|  |  | ||||||
|  |                 // Calculate row and column offsets. Understand that | ||||||
|  |                 // `tile_len`, `tile_row_offset` and `col` are subject to different | ||||||
|  |                 // values depending on whether we are in 4bpp or 8bpp mode. | ||||||
|  |                 const row = @as(u3, @truncate(y)) ^ if (entry.v_flip.read()) 7 else @as(u3, 0); | ||||||
|  |                 const col = @as(u3, @truncate(x)) ^ if (entry.h_flip.read()) 7 else @as(u3, 0); | ||||||
|  |                 const tile_addr = char_base + (tile_id * tile_len) + (row * tile_row_offset) + if (is_8bpp) col else col >> 1; | ||||||
|  |  | ||||||
|  |                 const tile = bus.read(u8, 0x0600_0000 + tile_addr); | ||||||
|  |                 // If we're in 8bpp, then the tile value is an index into the palette, | ||||||
|  |                 // If we're in 4bpp, we have to account for a pal bank value in the Screen entry | ||||||
|  |                 // and then we can index the palette | ||||||
|  |                 const pal_addr: u32 = if (!is_8bpp) get4bppTilePalette(entry.pal_bank.read(), col, tile) else tile; | ||||||
|  |  | ||||||
|  |                 if (pal_addr != 0) { | ||||||
|  |                     self.drawBackgroundPixel(layer, i, bus.read(u16, 0x0500_0000 + pal_addr * 2)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         inline fn get4bppTilePalette(pal_bank: u4, col: u3, tile: u8) u8 { | ||||||
|  |             const nybble_tile = tile >> ((col & 1) << 2) & 0xF; | ||||||
|  |             if (nybble_tile == 0) return 0; | ||||||
|  |  | ||||||
|  |             return (@as(u8, pal_bank) << 4) | nybble_tile; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn renderTextMode(self: *@This(), backdrop: u16, frame_buf: []u32) void { | ||||||
|  |             for (self.scanline.top(), self.scanline.btm(), frame_buf) |maybe_top, maybe_btm, *rgba| { | ||||||
|  |                 _ = maybe_btm; | ||||||
|  |  | ||||||
|  |                 const bgr555 = switch (maybe_top) { | ||||||
|  |                     .set => |px| px, | ||||||
|  |                     else => backdrop, | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 rgba.* = rgba888(bgr555); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             self.scanline.reset(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // TODO: Comment this + get a better understanding | ||||||
|  |         fn tilemapOffset(size: u2, x: u32, y: u32) u32 { | ||||||
|  |             // Current Row: (y % PIXEL_COUNT) / 8 | ||||||
|  |             // Current COlumn: (x % PIXEL_COUNT) / 8 | ||||||
|  |             // Length of 1 row of Screen Entries: 0x40 | ||||||
|  |             // Length of 1 Screen Entry: 0x2 is the size of a screen entry | ||||||
|  |             @setRuntimeSafety(false); | ||||||
|  |  | ||||||
|  |             return switch (size) { | ||||||
|  |                 0 => (x % 256 / 8) * 2 + (y % 256 / 8) * 0x40, // 256 x 256 | ||||||
|  |                 1 => blk: { | ||||||
|  |                     // 512 x 256 | ||||||
|  |                     const offset: u32 = if (x & 0x1FF > 0xFF) 0x800 else 0; | ||||||
|  |                     break :blk offset + (x % 256 / 8) * 2 + (y % 256 / 8) * 0x40; | ||||||
|  |                 }, | ||||||
|  |                 2 => blk: { | ||||||
|  |                     // 256 x 512 | ||||||
|  |                     const offset: u32 = if (y & 0x1FF > 0xFF) 0x800 else 0; | ||||||
|  |                     break :blk offset + (x % 256 / 8) * 2 + (y % 256 / 8) * 0x40; | ||||||
|  |                 }, | ||||||
|  |                 3 => blk: { | ||||||
|  |                     // 512 x 512 | ||||||
|  |                     const offset: u32 = if (x & 0x1FF > 0xFF) 0x800 else 0; | ||||||
|  |                     const offset_2: u32 = if (y & 0x1FF > 0xFF) 0x800 else 0; | ||||||
|  |                     break :blk offset + offset_2 + (x % 256 / 8) * 2 + (y % 512 / 8) * 0x40; | ||||||
|  |                 }, | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         fn drawBackgroundPixel(self: *@This(), comptime layer: u2, i: u32, bgr555: u16) void { | ||||||
|  |             _ = layer; | ||||||
|  |  | ||||||
|  |             self.scanline.top()[i] = Scanline.Pixel.from(.Background, bgr555); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const Scanline = struct { | ||||||
|  |     const Pixel = union(enum) { | ||||||
|  |         // TODO: Rename | ||||||
|  |         const Layer = enum { Background, Sprite }; | ||||||
|  |  | ||||||
|  |         set: u16, | ||||||
|  |         obj_set: u16, | ||||||
|  |         unset: void, | ||||||
|  |         hidden: void, | ||||||
|  |  | ||||||
|  |         fn from(comptime layer: Layer, bgr555: u16) Pixel { | ||||||
|  |             return switch (layer) { | ||||||
|  |                 .Background => .{ .set = bgr555 }, | ||||||
|  |                 .Sprite => .{ .obj_set = bgr555 }, | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn isSet(self: @This()) bool { | ||||||
|  |             return switch (self) { | ||||||
|  |                 .set, .obj_set => true, | ||||||
|  |                 .unset, .hidden => false, | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     layers: [2][]Pixel, | ||||||
|  |     buf: []Pixel, | ||||||
|  |  | ||||||
|  |     fn init(allocator: std.mem.Allocator) !@This() { | ||||||
|  |         const buf = try allocator.alloc(Pixel, width * 2); // Top & Bottom Scanline | ||||||
|  |         @memset(buf, .unset); | ||||||
|  |  | ||||||
|  |         return .{ | ||||||
|  |             // Top & Bototm Layers | ||||||
|  |             .layers = [_][]Pixel{ buf[0..][0..width], buf[width..][0..width] }, | ||||||
|  |             .buf = buf, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn reset(self: *@This()) void { | ||||||
|  |         @memset(self.buf, .unset); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn deinit(self: @This(), allocator: std.mem.Allocator) void { | ||||||
|  |         allocator.free(self.buf); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn top(self: *@This()) []Pixel { | ||||||
|  |         return self.layers[0]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn btm(self: *@This()) []Pixel { | ||||||
|  |         return self.layers[1]; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const bg = struct { | ||||||
|  |     const Text = struct { | ||||||
|  |         const io = @import("../nds9/io.zig"); | ||||||
|  |  | ||||||
|  |         /// Read / Write | ||||||
|  |         cnt: io.Bgcnt = .{ .raw = 0x0000 }, | ||||||
|  |         /// Write Only | ||||||
|  |         hofs: io.Hofs = .{ .raw = 0x0000 }, | ||||||
|  |         /// Write Only | ||||||
|  |         vofs: io.Vofs = .{ .raw = 0x0000 }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const Screen = struct { | ||||||
|  |         const Entry = extern union { | ||||||
|  |             const Bitfield = @import("bitfield").Bitfield; | ||||||
|  |             const Bit = @import("bitfield").Bit; | ||||||
|  |  | ||||||
|  |             tile_id: Bitfield(u16, 0, 10), | ||||||
|  |             h_flip: Bit(u16, 10), | ||||||
|  |             v_flip: Bit(u16, 11), | ||||||
|  |             pal_bank: Bitfield(u16, 12, 4), | ||||||
|  |             raw: u16, | ||||||
|  |         }; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const Affine = @compileError("TODO: Implement Affine Backgrounds"); | ||||||
|  | }; | ||||||
|  |  | ||||||
| inline fn rgba888(bgr555: u16) u32 { | inline fn rgba888(bgr555: u16) u32 { | ||||||
|     const b: u32 = bgr555 >> 10 & 0x1F; |     const b: u32 = bgr555 >> 10 & 0x1F; | ||||||
|     const g: u32 = bgr555 >> 5 & 0x1F; |     const g: u32 = bgr555 >> 5 & 0x1F; | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/main.zig
									
									
									
									
									
								
							| @@ -6,6 +6,7 @@ const emu = @import("core/emu.zig"); | |||||||
| const Ui = @import("platform.zig").Ui; | const Ui = @import("platform.zig").Ui; | ||||||
| const SharedCtx = @import("core/emu.zig").SharedCtx; | const SharedCtx = @import("core/emu.zig").SharedCtx; | ||||||
| const System = @import("core/emu.zig").System; | const System = @import("core/emu.zig").System; | ||||||
|  | const Sync = @import("core/emu.zig").Sync; | ||||||
| const Scheduler = @import("core/Scheduler.zig"); | const Scheduler = @import("core/Scheduler.zig"); | ||||||
|  |  | ||||||
| const Allocator = std.mem.Allocator; | const Allocator = std.mem.Allocator; | ||||||
| @@ -14,6 +15,7 @@ const ClapResult = clap.Result(clap.Help, &cli_params, clap.parsers.default); | |||||||
| const cli_params = clap.parseParamsComptime( | const cli_params = clap.parseParamsComptime( | ||||||
|     \\-h, --help        Display this help and exit. |     \\-h, --help        Display this help and exit. | ||||||
|     \\-f, --firm <str>  Path to NDS Firmware Directory |     \\-f, --firm <str>  Path to NDS Firmware Directory | ||||||
|  |     \\--gdb              Run Turbo in GDB Mode | ||||||
|     \\<str>             Path to the NDS ROM |     \\<str>             Path to the NDS ROM | ||||||
|     \\ |     \\ | ||||||
| ); | ); | ||||||
| @@ -26,7 +28,7 @@ pub fn main() !void { | |||||||
|  |  | ||||||
|     const allocator = gpa.allocator(); |     const allocator = gpa.allocator(); | ||||||
|  |  | ||||||
|     const result = try clap.parse(clap.Help, &cli_params, clap.parsers.default, .{}); |     const result = try clap.parse(clap.Help, &cli_params, clap.parsers.default, .{ .allocator = allocator }); | ||||||
|     defer result.deinit(); |     defer result.deinit(); | ||||||
|  |  | ||||||
|     const rom_path = try handlePositional(result); |     const rom_path = try handlePositional(result); | ||||||
| @@ -65,11 +67,23 @@ pub fn main() !void { | |||||||
|     const rom_title = try emu.load(allocator, system, rom_path); |     const rom_title = try emu.load(allocator, system, rom_path); | ||||||
|     if (firm_path) |path| try emu.loadFirm(allocator, system, path); |     if (firm_path) |path| try emu.loadFirm(allocator, system, path); | ||||||
|  |  | ||||||
|  |     emu.fastBoot(system); | ||||||
|  |  | ||||||
|     var ui = try Ui.init(allocator); |     var ui = try Ui.init(allocator); | ||||||
|     defer ui.deinit(allocator); |     defer ui.deinit(allocator); | ||||||
|  |  | ||||||
|     ui.setTitle(rom_title); |     ui.setTitle(rom_title); | ||||||
|     try ui.run(&scheduler, system); |  | ||||||
|  |     const sync = try allocator.create(Sync); | ||||||
|  |     defer allocator.destroy(sync); | ||||||
|  |  | ||||||
|  |     sync.init(); | ||||||
|  |  | ||||||
|  |     if (result.args.gdb == 0) { | ||||||
|  |         try ui.run(&scheduler, system, sync); | ||||||
|  |     } else { | ||||||
|  |         try emu.debug.run(allocator, &ui, &scheduler, system, sync); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fn handlePositional(result: ClapResult) ![]const u8 { | fn handlePositional(result: ClapResult) ![]const u8 { | ||||||
|   | |||||||
							
								
								
									
										251
									
								
								src/platform.zig
									
									
									
									
									
								
							
							
						
						
									
										251
									
								
								src/platform.zig
									
									
									
									
									
								
							| @@ -8,8 +8,11 @@ const imgui = @import("ui/imgui.zig"); | |||||||
| const emu = @import("core/emu.zig"); | const emu = @import("core/emu.zig"); | ||||||
|  |  | ||||||
| const System = @import("core/emu.zig").System; | const System = @import("core/emu.zig").System; | ||||||
|  | const Sync = @import("core/emu.zig").Sync; | ||||||
| const KeyInput = @import("core/io.zig").KeyInput; | const KeyInput = @import("core/io.zig").KeyInput; | ||||||
|  | const ExtKeyIn = @import("core/io.zig").ExtKeyIn; | ||||||
| const Scheduler = @import("core/Scheduler.zig"); | const Scheduler = @import("core/Scheduler.zig"); | ||||||
|  | const FrameBuffer = @import("core/ppu.zig").FrameBuffer; | ||||||
|  |  | ||||||
| const Allocator = std.mem.Allocator; | const Allocator = std.mem.Allocator; | ||||||
|  |  | ||||||
| @@ -33,7 +36,7 @@ pub const Ui = struct { | |||||||
|     state: imgui.State, |     state: imgui.State, | ||||||
|  |  | ||||||
|     pub fn init(allocator: Allocator) !Self { |     pub fn init(allocator: Allocator) !Self { | ||||||
|         var state = imgui.State{}; |         const state = imgui.State{}; | ||||||
|  |  | ||||||
|         if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0) panic(); |         if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0) panic(); | ||||||
|         if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GL_CONTEXT_PROFILE_CORE) < 0) panic(); |         if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GL_CONTEXT_PROFILE_CORE) < 0) panic(); | ||||||
| @@ -85,108 +88,42 @@ pub const Ui = struct { | |||||||
|         return SDL.SDL_GL_GetProcAddress(proc.ptr); |         return SDL.SDL_GL_GetProcAddress(proc.ptr); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn run(self: *Self, scheduler: *Scheduler, system: System) !void { |     pub fn setTitle(self: *@This(), title: [12]u8) void { | ||||||
|         // TODO: Sort this out please |         self.state.title = title ++ [_:0]u8{}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|         const vao_id = opengl_impl.vao(); |     pub fn run(self: *Self, scheduler: *Scheduler, system: System, sync: *Sync) !void { | ||||||
|         defer gl.deleteVertexArrays(1, &[_]GLuint{vao_id}); |         const id = try opengl_impl.runInit(&system.bus9.ppu.fb); | ||||||
|  |         defer id.deinit(); | ||||||
|         const top_tex = opengl_impl.screenTex(system.bus9.ppu.fb.top(.front)); |  | ||||||
|         const btm_tex = opengl_impl.screenTex(system.bus9.ppu.fb.btm(.front)); |  | ||||||
|         const top_out_tex = opengl_impl.outTex(); |  | ||||||
|         const btm_out_tex = opengl_impl.outTex(); |  | ||||||
|         defer gl.deleteTextures(4, &[_]GLuint{ top_tex, top_out_tex, btm_tex, btm_out_tex }); |  | ||||||
|  |  | ||||||
|         const top_fbo = try opengl_impl.frameBuffer(top_out_tex); |  | ||||||
|         const btm_fbo = try opengl_impl.frameBuffer(btm_out_tex); |  | ||||||
|         defer gl.deleteFramebuffers(2, &[_]GLuint{ top_fbo, btm_fbo }); |  | ||||||
|  |  | ||||||
|         const prog_id = try opengl_impl.program(); |  | ||||||
|         defer gl.deleteProgram(prog_id); |  | ||||||
|  |  | ||||||
|         var event: SDL.SDL_Event = undefined; |         var event: SDL.SDL_Event = undefined; | ||||||
|  |  | ||||||
|         emu_loop: while (true) { |         while (!sync.should_quit.load(.Monotonic)) { | ||||||
|             emu.runFrame(scheduler, system); |             emu.runFrame(scheduler, system); // TODO: run emu in separate thread | ||||||
|  |  | ||||||
|             while (SDL.SDL_PollEvent(&event) != 0) { |             while (SDL.SDL_PollEvent(&event) != 0) { | ||||||
|                 _ = zgui.backend.processEvent(&event); |                 _ = zgui.backend.processEvent(&event); | ||||||
|  |                 handleInput(&event, system, &self.state, sync); | ||||||
|                 switch (event.type) { |  | ||||||
|                     SDL.SDL_QUIT => break :emu_loop, |  | ||||||
|                     SDL.SDL_WINDOWEVENT => { |  | ||||||
|                         if (event.window.event == SDL.SDL_WINDOWEVENT_RESIZED) { |  | ||||||
|                             std.log.debug("window resized to: {}x{}", .{ event.window.data1, event.window.data2 }); |  | ||||||
|  |  | ||||||
|                             self.state.dim.width = @intCast(event.window.data1); |  | ||||||
|                             self.state.dim.height = @intCast(event.window.data2); |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                     SDL.SDL_KEYDOWN => { |  | ||||||
|                         // TODO: Make use of compare_and_xor? |  | ||||||
|                         const key_code = event.key.keysym.sym; |  | ||||||
|                         var keyinput: KeyInput = .{ .raw = 0x0000 }; |  | ||||||
|  |  | ||||||
|                         switch (key_code) { |  | ||||||
|                             SDL.SDLK_UP => keyinput.up.set(), |  | ||||||
|                             SDL.SDLK_DOWN => keyinput.down.set(), |  | ||||||
|                             SDL.SDLK_LEFT => keyinput.left.set(), |  | ||||||
|                             SDL.SDLK_RIGHT => keyinput.right.set(), |  | ||||||
|                             SDL.SDLK_x => keyinput.a.set(), |  | ||||||
|                             SDL.SDLK_z => keyinput.b.set(), |  | ||||||
|                             SDL.SDLK_a => keyinput.shoulder_l.set(), |  | ||||||
|                             SDL.SDLK_s => keyinput.shoulder_r.set(), |  | ||||||
|                             SDL.SDLK_RETURN => keyinput.start.set(), |  | ||||||
|                             SDL.SDLK_RSHIFT => keyinput.select.set(), |  | ||||||
|                             else => {}, |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         system.bus9.io.shr.keyinput.fetchAnd(~keyinput.raw, .Monotonic); |  | ||||||
|                     }, |  | ||||||
|                     SDL.SDL_KEYUP => { |  | ||||||
|                         // TODO: Make use of compare_and_xor? |  | ||||||
|                         const key_code = event.key.keysym.sym; |  | ||||||
|                         var keyinput: KeyInput = .{ .raw = 0x0000 }; |  | ||||||
|  |  | ||||||
|                         switch (key_code) { |  | ||||||
|                             SDL.SDLK_UP => keyinput.up.set(), |  | ||||||
|                             SDL.SDLK_DOWN => keyinput.down.set(), |  | ||||||
|                             SDL.SDLK_LEFT => keyinput.left.set(), |  | ||||||
|                             SDL.SDLK_RIGHT => keyinput.right.set(), |  | ||||||
|                             SDL.SDLK_x => keyinput.a.set(), |  | ||||||
|                             SDL.SDLK_z => keyinput.b.set(), |  | ||||||
|                             SDL.SDLK_a => keyinput.shoulder_l.set(), |  | ||||||
|                             SDL.SDLK_s => keyinput.shoulder_r.set(), |  | ||||||
|                             SDL.SDLK_RETURN => keyinput.start.set(), |  | ||||||
|                             SDL.SDLK_RSHIFT => keyinput.select.set(), |  | ||||||
|                             else => {}, |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         system.bus9.io.shr.keyinput.fetchOr(keyinput.raw, .Monotonic); |  | ||||||
|                     }, |  | ||||||
|                     else => {}, |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             { |             { | ||||||
|                 gl.bindFramebuffer(gl.FRAMEBUFFER, top_fbo); |                 gl.bindFramebuffer(gl.FRAMEBUFFER, id.top_fbo); | ||||||
|                 defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0); |                 defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0); | ||||||
|  |  | ||||||
|                 gl.viewport(0, 0, nds_width, nds_height); |                 gl.viewport(0, 0, nds_width, nds_height); | ||||||
|                 opengl_impl.drawScreen(top_tex, prog_id, vao_id, system.bus9.ppu.fb.top(.front)); |                 opengl_impl.drawScreen(id.top_tex, id.prog_id, id.vao_id, system.bus9.ppu.fb.top(.front)); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             { |             { | ||||||
|                 gl.bindFramebuffer(gl.FRAMEBUFFER, btm_fbo); |                 gl.bindFramebuffer(gl.FRAMEBUFFER, id.btm_fbo); | ||||||
|                 defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0); |                 defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0); | ||||||
|  |  | ||||||
|                 gl.viewport(0, 0, nds_width, nds_height); |                 gl.viewport(0, 0, nds_width, nds_height); | ||||||
|                 opengl_impl.drawScreen(btm_tex, prog_id, vao_id, system.bus9.ppu.fb.btm(.front)); |                 opengl_impl.drawScreen(id.btm_tex, id.prog_id, id.vao_id, system.bus9.ppu.fb.btm(.front)); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             const zgui_redraw = imgui.draw(&self.state, top_out_tex, btm_out_tex, system); |             imgui.draw(&self.state, id.top_out_tex, id.btm_out_tex, system); | ||||||
|  |  | ||||||
|             if (zgui_redraw) { |  | ||||||
|             // Background Colour |             // Background Colour | ||||||
|             const size = zgui.io.getDisplaySize(); |             const size = zgui.io.getDisplaySize(); | ||||||
|             gl.viewport(0, 0, @intFromFloat(size[0]), @intFromFloat(size[1])); |             gl.viewport(0, 0, @intFromFloat(size[0]), @intFromFloat(size[1])); | ||||||
| @@ -194,14 +131,118 @@ pub const Ui = struct { | |||||||
|             gl.clear(gl.COLOR_BUFFER_BIT); |             gl.clear(gl.COLOR_BUFFER_BIT); | ||||||
|  |  | ||||||
|             zgui.backend.draw(); |             zgui.backend.draw(); | ||||||
|             } |  | ||||||
|  |  | ||||||
|             SDL.SDL_GL_SwapWindow(self.window); |             SDL.SDL_GL_SwapWindow(self.window); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn setTitle(self: *@This(), title: [12]u8) void { |     pub fn debug_run(self: *Self, _: *Scheduler, system: System, sync: *Sync) !void { | ||||||
|         self.state.title = title ++ [_:0]u8{}; |         const id = try opengl_impl.runInit(&system.bus9.ppu.fb); | ||||||
|  |         defer id.deinit(); | ||||||
|  |  | ||||||
|  |         var event: SDL.SDL_Event = undefined; | ||||||
|  |  | ||||||
|  |         while (!sync.should_quit.load(.Monotonic)) { | ||||||
|  |             while (SDL.SDL_PollEvent(&event) != 0) { | ||||||
|  |                 _ = zgui.backend.processEvent(&event); | ||||||
|  |                 handleInput(&event, system, &self.state, sync); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             { | ||||||
|  |                 gl.bindFramebuffer(gl.FRAMEBUFFER, id.top_fbo); | ||||||
|  |                 defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0); | ||||||
|  |  | ||||||
|  |                 gl.viewport(0, 0, nds_width, nds_height); | ||||||
|  |                 opengl_impl.drawScreen(id.top_tex, id.prog_id, id.vao_id, system.bus9.ppu.fb.top(.front)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             { | ||||||
|  |                 gl.bindFramebuffer(gl.FRAMEBUFFER, id.btm_fbo); | ||||||
|  |                 defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0); | ||||||
|  |  | ||||||
|  |                 gl.viewport(0, 0, nds_width, nds_height); | ||||||
|  |                 opengl_impl.drawScreen(id.btm_tex, id.prog_id, id.vao_id, system.bus9.ppu.fb.btm(.front)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             imgui.draw(&self.state, id.top_out_tex, id.btm_out_tex, system); | ||||||
|  |  | ||||||
|  |             // Background Colour | ||||||
|  |             const size = zgui.io.getDisplaySize(); | ||||||
|  |             gl.viewport(0, 0, @intFromFloat(size[0]), @intFromFloat(size[1])); | ||||||
|  |             gl.clearColor(0, 0, 0, 1.0); | ||||||
|  |             gl.clear(gl.COLOR_BUFFER_BIT); | ||||||
|  |  | ||||||
|  |             zgui.backend.draw(); | ||||||
|  |             SDL.SDL_GL_SwapWindow(self.window); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn handleInput(event: *SDL.SDL_Event, system: System, state: *imgui.State, sync: *Sync) void { | ||||||
|  |         switch (event.type) { | ||||||
|  |             SDL.SDL_QUIT => sync.should_quit.store(true, .Monotonic), | ||||||
|  |             SDL.SDL_WINDOWEVENT => { | ||||||
|  |                 if (event.window.event == SDL.SDL_WINDOWEVENT_RESIZED) { | ||||||
|  |                     std.log.debug("window resized to: {}x{}", .{ event.window.data1, event.window.data2 }); | ||||||
|  |  | ||||||
|  |                     state.dim.width = @intCast(event.window.data1); | ||||||
|  |                     state.dim.height = @intCast(event.window.data2); | ||||||
|  |  | ||||||
|  |                     zgui.io.setDisplaySize(@floatFromInt(event.window.data1), @floatFromInt(event.window.data2)); | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             SDL.SDL_KEYDOWN => { | ||||||
|  |                 // TODO: Make use of compare_and_xor? | ||||||
|  |                 const key_code = event.key.keysym.sym; | ||||||
|  |  | ||||||
|  |                 var keyinput: KeyInput = .{ .raw = 0x0000 }; | ||||||
|  |                 var extkeyin: ExtKeyIn = .{ .raw = 0x0000 }; | ||||||
|  |  | ||||||
|  |                 switch (key_code) { | ||||||
|  |                     SDL.SDLK_UP => keyinput.up.set(), | ||||||
|  |                     SDL.SDLK_DOWN => keyinput.down.set(), | ||||||
|  |                     SDL.SDLK_LEFT => keyinput.left.set(), | ||||||
|  |                     SDL.SDLK_RIGHT => keyinput.right.set(), | ||||||
|  |                     SDL.SDLK_c => keyinput.a.set(), | ||||||
|  |                     SDL.SDLK_x => keyinput.b.set(), | ||||||
|  |                     SDL.SDLK_d => extkeyin.x.set(), | ||||||
|  |                     SDL.SDLK_s => extkeyin.y.set(), | ||||||
|  |                     SDL.SDLK_a => keyinput.shoulder_l.set(), | ||||||
|  |                     SDL.SDLK_f => keyinput.shoulder_r.set(), | ||||||
|  |                     SDL.SDLK_RETURN => keyinput.start.set(), | ||||||
|  |                     SDL.SDLK_RSHIFT => keyinput.select.set(), | ||||||
|  |                     else => {}, | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 const input = (@as(u32, extkeyin.raw) << 16) | keyinput.raw; | ||||||
|  |                 system.bus9.io.shr.input.set(.And, ~input); | ||||||
|  |             }, | ||||||
|  |             SDL.SDL_KEYUP => { | ||||||
|  |                 // TODO: Make use of compare_and_xor? | ||||||
|  |                 const key_code = event.key.keysym.sym; | ||||||
|  |  | ||||||
|  |                 var keyinput: KeyInput = .{ .raw = 0x0000 }; | ||||||
|  |                 var extkeyin: ExtKeyIn = .{ .raw = 0x0000 }; | ||||||
|  |  | ||||||
|  |                 switch (key_code) { | ||||||
|  |                     SDL.SDLK_UP => keyinput.up.set(), | ||||||
|  |                     SDL.SDLK_DOWN => keyinput.down.set(), | ||||||
|  |                     SDL.SDLK_LEFT => keyinput.left.set(), | ||||||
|  |                     SDL.SDLK_RIGHT => keyinput.right.set(), | ||||||
|  |                     SDL.SDLK_c => keyinput.a.set(), | ||||||
|  |                     SDL.SDLK_x => keyinput.b.set(), | ||||||
|  |                     SDL.SDLK_d => extkeyin.x.set(), | ||||||
|  |                     SDL.SDLK_s => extkeyin.y.set(), | ||||||
|  |                     SDL.SDLK_a => keyinput.shoulder_l.set(), | ||||||
|  |                     SDL.SDLK_f => keyinput.shoulder_r.set(), | ||||||
|  |                     SDL.SDLK_RETURN => keyinput.start.set(), | ||||||
|  |                     SDL.SDLK_RSHIFT => keyinput.select.set(), | ||||||
|  |                     else => {}, | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 const input = (@as(u32, extkeyin.raw) << 16) | keyinput.raw; | ||||||
|  |                 system.bus9.io.shr.input.set(.Or, input); | ||||||
|  |             }, | ||||||
|  |             else => {}, | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -211,6 +252,46 @@ fn panic() noreturn { | |||||||
| } | } | ||||||
|  |  | ||||||
| const opengl_impl = struct { | const opengl_impl = struct { | ||||||
|  |     const Ids = struct { | ||||||
|  |         vao_id: GLuint, | ||||||
|  |  | ||||||
|  |         top_tex: GLuint, | ||||||
|  |         btm_tex: GLuint, | ||||||
|  |         top_out_tex: GLuint, | ||||||
|  |         btm_out_tex: GLuint, | ||||||
|  |  | ||||||
|  |         top_fbo: GLuint, | ||||||
|  |         btm_fbo: GLuint, | ||||||
|  |  | ||||||
|  |         prog_id: GLuint, | ||||||
|  |  | ||||||
|  |         fn deinit(self: Ids) void { | ||||||
|  |             gl.deleteProgram(self.prog_id); | ||||||
|  |             gl.deleteFramebuffers(2, &[_]GLuint{ self.top_fbo, self.btm_fbo }); | ||||||
|  |             gl.deleteTextures(4, &[_]GLuint{ self.top_tex, self.top_out_tex, self.btm_tex, self.btm_out_tex }); | ||||||
|  |             gl.deleteVertexArrays(1, &[_]GLuint{self.vao_id}); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     fn runInit(fb: *const FrameBuffer) !Ids { | ||||||
|  |         const top_out_tex = opengl_impl.outTex(); | ||||||
|  |         const btm_out_tex = opengl_impl.outTex(); | ||||||
|  |  | ||||||
|  |         return .{ | ||||||
|  |             .vao_id = opengl_impl.vao(), | ||||||
|  |  | ||||||
|  |             .top_tex = opengl_impl.screenTex(fb.top(.front)), | ||||||
|  |             .btm_tex = opengl_impl.screenTex(fb.btm(.front)), | ||||||
|  |             .top_out_tex = top_out_tex, | ||||||
|  |             .btm_out_tex = btm_out_tex, | ||||||
|  |  | ||||||
|  |             .top_fbo = try opengl_impl.frameBuffer(top_out_tex), | ||||||
|  |             .btm_fbo = try opengl_impl.frameBuffer(btm_out_tex), | ||||||
|  |  | ||||||
|  |             .prog_id = try opengl_impl.program(), | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fn drawScreen(tex_id: GLuint, prog_id: GLuint, vao_id: GLuint, buf: []const u8) void { |     fn drawScreen(tex_id: GLuint, prog_id: GLuint, vao_id: GLuint, buf: []const u8) void { | ||||||
|         gl.bindTexture(gl.TEXTURE_2D, tex_id); |         gl.bindTexture(gl.TEXTURE_2D, tex_id); | ||||||
|         defer gl.bindTexture(gl.TEXTURE_2D, 0); |         defer gl.bindTexture(gl.TEXTURE_2D, 0); | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ pub const State = struct { | |||||||
|     dim: Dimensions = .{ .width = 1600, .height = 900 }, |     dim: Dimensions = .{ .width = 1600, .height = 900 }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| pub fn draw(state: *const State, top_tex: GLuint, btm_tex: GLuint, system: System) bool { | pub fn draw(state: *const State, top_tex: GLuint, btm_tex: GLuint, system: System) void { | ||||||
|     _ = system; |     _ = system; | ||||||
|  |  | ||||||
|     zgui.backend.newFrame(@floatFromInt(state.dim.width), @floatFromInt(state.dim.height)); |     zgui.backend.newFrame(@floatFromInt(state.dim.width), @floatFromInt(state.dim.height)); | ||||||
| @@ -36,6 +36,4 @@ pub fn draw(state: *const State, top_tex: GLuint, btm_tex: GLuint, system: Syste | |||||||
|         zgui.image(@ptrFromInt(top_tex), .{ .w = w, .h = h }); |         zgui.image(@ptrFromInt(top_tex), .{ .w = w, .h = h }); | ||||||
|         zgui.image(@ptrFromInt(btm_tex), .{ .w = w, .h = h }); |         zgui.image(@ptrFromInt(btm_tex), .{ .w = w, .h = h }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return true; |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user