From 3a35c5b4288cde0ea17dfcd4c24d902413d6c17e Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Tue, 3 Oct 2023 00:32:32 -0500 Subject: [PATCH] feat: implement VRAM allocation --- src/core/emu.zig | 32 ++-- src/core/nds7/Bus.zig | 9 +- src/core/nds7/io.zig | 7 + src/core/nds9/Bus.zig | 22 +-- src/core/nds9/io.zig | 48 +++++- src/core/ppu.zig | 354 ++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 422 insertions(+), 50 deletions(-) diff --git a/src/core/emu.zig b/src/core/emu.zig index 72421c1..208e806 100644 --- a/src/core/emu.zig +++ b/src/core/emu.zig @@ -94,16 +94,22 @@ pub const SharedContext = struct { const MiB = 0x100000; const KiB = 0x400; + const Vram = @import("ppu.zig").Vram; + io: *SharedIo, main: *[4 * MiB]u8, wram: *Wram, + vram: *Vram, pub fn init(allocator: Allocator) !@This() { const wram = try allocator.create(Wram); errdefer allocator.destroy(wram); - try wram.init(allocator); + const vram = try allocator.create(Vram); + errdefer allocator.destroy(vram); + try vram.init(allocator); + const ctx = .{ .io = blk: { const io = try allocator.create(SharedIo); @@ -112,6 +118,7 @@ pub const SharedContext = struct { break :blk io; }, .wram = wram, + .vram = vram, .main = try allocator.create([4 * MiB]u8), }; @@ -122,6 +129,9 @@ pub const SharedContext = struct { self.wram.deinit(allocator); allocator.destroy(self.wram); + self.vram.deinit(allocator); + allocator.destroy(self.vram); + allocator.destroy(self.io); allocator.destroy(self.main); } @@ -135,6 +145,8 @@ pub const Wram = struct { const page_size = 1 * KiB; // perhaps too big? const addr_space_size = 0x8000; const table_len = addr_space_size / page_size; + const buf_len = 32 * KiB; + const IntFittingRange = std.math.IntFittingRange; const io = @import("io.zig"); @@ -142,13 +154,13 @@ pub const Wram = struct { const log = std.log.scoped(.shared_wram); - _buf: *[32 * KiB]u8, + _buf: *[buf_len]u8, nds9_table: *const [table_len]?[*]u8, nds7_table: *const [table_len]?[*]u8, pub fn init(self: *@This(), allocator: Allocator) !void { - const buf = try allocator.create([32 * KiB]u8); + const buf = try allocator.create([buf_len]u8); errdefer allocator.destroy(buf); const tables = try allocator.alloc(?[*]u8, 2 * table_len); @@ -203,12 +215,13 @@ pub const Wram = struct { 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 page = address >> bits; - const offset = address & (page_size - 1); + const masked_addr = address & (addr_space_size - 1); + const page = masked_addr >> bits; + const offset = masked_addr & (page_size - 1); const table = if (dev == .nds9) self.nds9_table else self.nds7_table; if (table[page]) |some_ptr| { - const ptr: [*]align(1) const T = @ptrCast(@alignCast(some_ptr)); + const ptr: [*]const T = @ptrCast(@alignCast(some_ptr)); return ptr[offset / @sizeOf(T)]; } @@ -219,12 +232,13 @@ pub const Wram = struct { pub fn write(self: *@This(), comptime T: type, comptime dev: Device, address: u32, value: T) void { const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; - const page = address >> bits; - const offset = address & (page_size - 1); + const masked_addr = address & (addr_space_size - 1); + const page = masked_addr >> bits; + const offset = masked_addr & (page_size - 1); const table = if (dev == .nds9) self.nds9_table else self.nds7_table; if (table[page]) |some_ptr| { - const ptr: [*]align(1) T = @ptrCast(@alignCast(some_ptr)); + const ptr: [*]T = @ptrCast(@alignCast(some_ptr)); ptr[offset / @sizeOf(T)] = value; return; diff --git a/src/core/nds7/Bus.zig b/src/core/nds7/Bus.zig index 6a7c4e6..f5f1e92 100644 --- a/src/core/nds7/Bus.zig +++ b/src/core/nds7/Bus.zig @@ -5,6 +5,7 @@ const Scheduler = @import("../Scheduler.zig"); const SharedIo = @import("../io.zig").Io; const SharedContext = @import("../emu.zig").SharedContext; const Wram = @import("../emu.zig").Wram; +const Vram = @import("../ppu.zig").Vram; const forceAlign = @import("../emu.zig").forceAlign; const Allocator = std.mem.Allocator; @@ -19,6 +20,7 @@ scheduler: *Scheduler, main: *[4 * MiB]u8, wram_shr: *Wram, wram: *[64 * KiB]u8, +vram: *Vram, io: io.Io, pub fn init(allocator: Allocator, scheduler: *Scheduler, shared_ctx: SharedContext) !@This() { @@ -29,6 +31,7 @@ pub fn init(allocator: Allocator, scheduler: *Scheduler, shared_ctx: SharedConte return .{ .main = shared_ctx.main, .wram_shr = shared_ctx.wram, + .vram = shared_ctx.vram, .wram = wram, .scheduler = scheduler, .io = io.Io.init(shared_ctx.io), @@ -65,10 +68,11 @@ fn _read(self: *@This(), comptime T: type, comptime mode: Mode, address: u32) T 0x0200_0000...0x02FF_FFFF => readInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count]), 0x0300_0000...0x037F_FFFF => switch (self.io.shared.wramcnt.mode.read()) { 0b00 => readInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count]), - else => self.wram_shr.read(T, .nds7, address & 0x7FFF), + else => self.wram_shr.read(T, .nds7, aligned_addr), }, 0x0380_0000...0x0380_FFFF => readInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count]), 0x0400_0000...0x04FF_FFFF => io.read(self, T, aligned_addr), + 0x0600_0000...0x06FF_FFFF => self.vram.read(T, .nds7, aligned_addr), else => warn("unexpected read: 0x{x:0>8} -> {}", .{ aligned_addr, T }), }; } @@ -97,10 +101,11 @@ fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, v 0x0200_0000...0x02FF_FFFF => writeInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count], value), 0x0300_0000...0x037F_FFFF => switch (self.io.shared.wramcnt.mode.read()) { 0b00 => writeInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], value), - else => self.wram_shr.write(T, .nds7, address & 0x7FFF, value), + else => self.wram_shr.write(T, .nds7, aligned_addr, value), }, 0x0380_0000...0x0380_FFFF => writeInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], value), 0x0400_0000...0x04FF_FFFF => io.write(self, T, aligned_addr, value), + 0x0600_0000...0x06FF_FFFF => self.vram.write(T, .nds7, aligned_addr, value), else => log.warn("unexpected write: 0x{X:}{} -> 0x{X:0>8}", .{ value, T, aligned_addr }), } } diff --git a/src/core/nds7/io.zig b/src/core/nds7/io.zig index f10f366..7d55468 100644 --- a/src/core/nds7/io.zig +++ b/src/core/nds7/io.zig @@ -33,6 +33,7 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T { else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), }, u8 => switch (address) { + 0x0400_0240 => bus.vram.stat().raw, 0x0400_0241 => bus.io.shared.wramcnt.raw, else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), }, @@ -66,3 +67,9 @@ fn warn(comptime format: []const u8, args: anytype) u0 { log.warn(format, args); return 0; } + +pub const Vramstat = extern union { + vramc_enabled: Bit(u8, 0), + vramd_enabled: Bit(u8, 1), + raw: u8, +}; diff --git a/src/core/nds9/Bus.zig b/src/core/nds9/Bus.zig index 51622aa..cfeb86d 100644 --- a/src/core/nds9/Bus.zig +++ b/src/core/nds9/Bus.zig @@ -17,25 +17,19 @@ const log = std.log.scoped(.nds9_bus); main: *[4 * MiB]u8, wram: *Wram, -vram1: *[512 * KiB]u8, // TODO: Rename io: io.Io, ppu: Ppu, scheduler: *Scheduler, pub fn init(allocator: Allocator, scheduler: *Scheduler, shared_ctx: SharedContext) !@This() { - const vram1_mem = try allocator.create([512 * KiB]u8); - errdefer allocator.destroy(vram1_mem); - @memset(vram1_mem, 0); - const dots_per_cycle = 3; // ARM946E-S runs twice as fast as the ARM7TDMI scheduler.push(.{ .nds9 = .draw }, 256 * dots_per_cycle); return .{ .main = shared_ctx.main, .wram = shared_ctx.wram, - .vram1 = vram1_mem, - .ppu = try Ppu.init(allocator), + .ppu = try Ppu.init(allocator, shared_ctx.vram), .scheduler = scheduler, .io = io.Io.init(shared_ctx.io), }; @@ -43,12 +37,10 @@ pub fn init(allocator: Allocator, scheduler: *Scheduler, shared_ctx: SharedConte pub fn deinit(self: *@This(), allocator: Allocator) void { self.ppu.deinit(allocator); - allocator.destroy(self.vram1); } -pub fn reset(self: *@This()) void { - @memset(self.main, 0); - @memset(self.vram1, 0); +pub fn reset(_: *@This()) void { + @panic("TODO: PPU Reset"); } pub fn read(self: *@This(), comptime T: type, address: u32) T { @@ -73,9 +65,9 @@ fn _read(self: *@This(), comptime T: type, comptime mode: Mode, address: u32) T return switch (aligned_addr) { 0x0200_0000...0x02FF_FFFF => readInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count]), - 0x0300_0000...0x03FF_FFFF => self.wram.read(T, .nds9, aligned_addr & 0x7FFF), + 0x0300_0000...0x03FF_FFFF => self.wram.read(T, .nds9, aligned_addr), 0x0400_0000...0x04FF_FFFF => io.read(self, T, aligned_addr), - 0x0600_0000...0x06FF_FFFF => readInt(T, self.vram1[aligned_addr & 0x0007_FFFF ..][0..byte_count]), + 0x0600_0000...0x06FF_FFFF => self.ppu.vram.read(T, .nds9, aligned_addr), else => warn("unexpected read: 0x{x:0>8} -> {}", .{ aligned_addr, T }), }; } @@ -102,9 +94,9 @@ fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, v switch (aligned_addr) { 0x0200_0000...0x02FF_FFFF => writeInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count], value), - 0x0300_0000...0x03FF_FFFF => self.wram.write(T, .nds9, aligned_addr & 0x7FFF, value), + 0x0300_0000...0x03FF_FFFF => self.wram.write(T, .nds9, aligned_addr, value), 0x0400_0000...0x04FF_FFFF => io.write(self, T, aligned_addr, value), - 0x0600_0000...0x06FF_FFFF => writeInt(T, self.vram1[aligned_addr & 0x0007_FFFF ..][0..byte_count], value), + 0x0600_0000...0x06FF_FFFF => self.ppu.vram.write(T, .nds9, aligned_addr, value), else => log.warn("unexpected write: 0x{X:}{} -> 0x{X:0>8}", .{ value, T, aligned_addr }), } } diff --git a/src/core/nds9/io.zig b/src/core/nds9/io.zig index 9e5e8e2..ccf0bac 100644 --- a/src/core/nds9/io.zig +++ b/src/core/nds9/io.zig @@ -74,10 +74,10 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { 0x0400_0188 => bus.io.shared.ipc_fifo.send(.nds9, value) catch |e| std.debug.panic("IPC FIFO Error: {}", .{e}), 0x0400_0240 => { - bus.ppu.io.vramcnt_a.raw = @truncate(value >> 0); // 0x0400_0240 - bus.ppu.io.vramcnt_b.raw = @truncate(value >> 8); // 0x0400_0241 - bus.ppu.io.vramcnt_c.raw = @truncate(value >> 16); // 0x0400_0242 - bus.ppu.io.vramcnt_d.raw = @truncate(value >> 24); // 0x0400_0243 + 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_c.raw = @truncate(value >> 16); // 0x0400_0242 + bus.ppu.vram.io.cnt_d.raw = @truncate(value >> 24); // 0x0400_0243 }, 0x0400_0208 => bus.io.shared.ime = value & 1 == 1, @@ -131,14 +131,46 @@ 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 }), }, u8 => switch (address) { - 0x0400_0240 => bus.ppu.io.vramcnt_a.raw = value, - 0x0400_0241 => bus.ppu.io.vramcnt_b.raw = value, - 0x0400_0242 => bus.ppu.io.vramcnt_c.raw = value, - 0x0400_0243 => bus.ppu.io.vramcnt_d.raw = value, + 0x0400_0240 => { + bus.ppu.vram.io.cnt_a.raw = value; + bus.ppu.vram.update(); + }, + 0x0400_0241 => { + bus.ppu.vram.io.cnt_b.raw = value; + bus.ppu.vram.update(); + }, + 0x0400_0242 => { + bus.ppu.vram.io.cnt_c.raw = value; + bus.ppu.vram.update(); + }, + 0x0400_0243 => { + bus.ppu.vram.io.cnt_d.raw = value; + bus.ppu.vram.update(); + }, + 0x0400_0244 => { + bus.ppu.vram.io.cnt_e.raw = value; + bus.ppu.vram.update(); + }, + 0x0400_0245 => { + bus.ppu.vram.io.cnt_f.raw = value; + bus.ppu.vram.update(); + }, + 0x0400_0246 => { + bus.ppu.vram.io.cnt_g.raw = value; + bus.ppu.vram.update(); + }, 0x0400_0247 => { bus.io.shared.wramcnt.raw = value; bus.wram.update(bus.io.shared.wramcnt); }, + 0x0400_0248 => { + bus.ppu.vram.io.cnt_h.raw = value; + bus.ppu.vram.update(); + }, + 0x0400_0249 => { + bus.ppu.vram.io.cnt_i.raw = value; + bus.ppu.vram.update(); + }, else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), }, diff --git a/src/core/ppu.zig b/src/core/ppu.zig index 4ba61b7..73b5031 100644 --- a/src/core/ppu.zig +++ b/src/core/ppu.zig @@ -6,16 +6,34 @@ const System = @import("emu.zig").System; pub const screen_width = 256; pub const screen_height = 192; +const KiB = 0x400; const cycles_per_dot = 6; pub const Ppu = struct { fb: FrameBuffer, - io: io = .{}, + vram: *Vram, - pub fn init(allocator: Allocator) !@This() { - return .{ .fb = try FrameBuffer.init(allocator) }; + io: Io = .{}, + + const Io = struct { + const nds9 = @import("nds9/io.zig"); + + /// Read / Write + dispcnt_a: nds9.DispcntA = .{ .raw = 0x0000_0000 }, + /// Read / Write + dispstat: nds9.Dispstat = .{ .raw = 0x0000 }, + + /// Read-Only + vcount: nds9.Vcount = .{ .raw = 0x0000 }, + }; + + pub fn init(allocator: Allocator, vram: *Vram) !@This() { + return .{ + .fb = try FrameBuffer.init(allocator), + .vram = vram, + }; } pub fn deinit(self: @This(), allocator: Allocator) void { @@ -158,20 +176,324 @@ inline fn rgba888(bgr555: u16) u32 { // zig fmt: on } -const io = struct { - const nds9_io = @import("nds9/io.zig"); // TODO: rename +pub const Vram = struct { + const page_size = 16 * KiB; // smallest allocation is 16 KiB + const addr_space_size = 0x0100_0000; // 0x0600_0000 -> 0x06FF_FFFF (inclusive) + const table_len = addr_space_size / page_size; + const buf_len = 656 * KiB; - /// Read / Write - dispcnt_a: nds9_io.DispcntA = .{ .raw = 0x0000_0000 }, - /// Read / Write - dispstat: nds9_io.Dispstat = .{ .raw = 0x0000 }, + const IntFittingRange = std.math.IntFittingRange; + const log = std.log.scoped(.vram); - /// Read-Only - vcount: nds9_io.Vcount = .{ .raw = 0x0000 }, + io: Io = .{}, - /// Write-Only - vramcnt_a: nds9_io.Vramcnt.A = .{ .raw = 0x00 }, - vramcnt_b: nds9_io.Vramcnt.A = .{ .raw = 0x00 }, - vramcnt_c: nds9_io.Vramcnt.C = .{ .raw = 0x00 }, - vramcnt_d: nds9_io.Vramcnt.C = .{ .raw = 0x00 }, + _buf: *[buf_len]u8, + nds9_table: *const [table_len]?[*]u8, + nds7_table: *const [table_len]?[*]u8, + + const Io = struct { + const nds9 = @import("nds9/io.zig"); + const nds7 = @import("nds7/io.zig"); + pub const Vramstat = @import("nds7/io.zig").Vramstat; + + stat: nds7.Vramstat = .{ .raw = 0x00 }, + + /// Write-Only (according to melonDS these are readable lol) + cnt_a: nds9.Vramcnt.A = .{ .raw = 0x00 }, + cnt_b: nds9.Vramcnt.A = .{ .raw = 0x00 }, + cnt_c: nds9.Vramcnt.C = .{ .raw = 0x00 }, + cnt_d: nds9.Vramcnt.C = .{ .raw = 0x00 }, + cnt_e: nds9.Vramcnt.E = .{ .raw = 0x00 }, + cnt_f: nds9.Vramcnt.C = .{ .raw = 0x00 }, + cnt_g: nds9.Vramcnt.C = .{ .raw = 0x00 }, + cnt_h: nds9.Vramcnt.H = .{ .raw = 0x00 }, + cnt_i: nds9.Vramcnt.H = .{ .raw = 0x00 }, + }; + + pub fn init(self: *@This(), allocator: Allocator) !void { + const buf = try allocator.create([buf_len]u8); + errdefer allocator.destroy(buf); + @memset(buf, 0); + + const tables = try allocator.alloc(?[*]u8, 2 * table_len); + @memset(tables, null); + + self.* = .{ + .nds9_table = tables[0..table_len], + .nds7_table = tables[table_len .. 2 * table_len], + ._buf = buf, + }; + + // ROMS like redpanda.nds won't write to VRAMCNT before trying to write to VRAM + // therefore we assume some default allocation (in this casee VRAMCNT_A -> VRAMCNT_I are 0x00) + self.update(); + } + + pub fn deinit(self: @This(), allocator: Allocator) void { + allocator.destroy(self._buf); + + const ptr: [*]?[*]const u8 = @ptrCast(@constCast(self.nds9_table)); + allocator.free(ptr[0 .. 2 * table_len]); + } + + pub fn stat(self: *const @This()) Io.Vramstat { + const vram_c: u8 = @intFromBool(self.io.cnt_c.enable.read() and self.io.cnt_c.mst.read() == 2); + const vram_d: u8 = @intFromBool(self.io.cnt_d.enable.read() and self.io.cnt_d.mst.read() == 2); + + return .{ .raw = (vram_d << 1) | vram_c }; + } + + const Range = struct { min: u32, max: u32 }; + const Kind = enum { + a, + b, + c, + d, + e, + f, + g, + h, + i, + + /// In Bytes + inline fn size(self: @This()) u32 { + return switch (self) { + .a => 128 * KiB, + .b => 128 * KiB, + .c => 128 * KiB, + .d => 128 * KiB, + .e => 64 * KiB, + .f => 16 * KiB, + .g => 16 * KiB, + .h => 32 * KiB, + .i => 16 * KiB, + }; + } + }; + + /// max inclusive + fn range(comptime kind: Kind, mst: u3, offset: u2) Range { + const ofs: u32 = offset; + // panic messages are from GBATEK + + return switch (kind) { + .a => switch (mst) { + 0 => .{ .min = 0x0680_0000, .max = 0x0682_0000 }, + 1 => blk: { + const base = 0x0600_0000 + (0x0002_0000 * ofs); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 2 => blk: { + const base = 0x0640_0000 + (0x0002_0000 * (ofs & 0b01)); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 3 => @panic("VRAMCNT_A: Slot OFS(0-3)"), + else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), + }, + .b => switch (mst) { + 0 => .{ .min = 0x0682_0000, .max = 0x0684_0000 }, + 1 => blk: { + const base = 0x0600_0000 + (0x0002_0000 * ofs); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 2 => blk: { + const base = 0x0640_0000 + (0x0002_0000 * (ofs & 0b01)); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 3 => @panic("VRAMCNT_B: Slot OFS(0-3)"), + else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), + }, + .c => switch (mst) { + 0 => .{ .min = 0x0684_0000, .max = 0x0686_0000 }, + 1 => blk: { + const base = 0x0600_0000 + (0x0002_0000 * ofs); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 2 => blk: { + const base = 0x0600_0000 + (0x0002_0000 * (ofs & 0b01)); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 3 => @panic("VRAMCNT_C: Slot OFS(0-3)"), + 4 => .{ .min = 0x0620_0000, .max = 0x0620_0000 + kind.size() }, + else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), + }, + .d => switch (mst) { + 0 => .{ .min = 0x0686_0000, .max = 0x0688_0000 }, + 1 => blk: { + const base = 0x0600_0000 + (0x0002_0000 * ofs); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 2 => blk: { + const base = 0x0600_0000 + (0x0002_0000 * (ofs & 0b01)); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 3 => @panic("VRAMCNT_D: Slot OFS(0-3)"), + 4 => .{ .min = 0x0660_0000, .max = 0x0660_0000 + kind.size() }, + else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), + }, + .e => switch (mst) { + 0 => .{ .min = 0x0688_0000, .max = 0x0689_0000 }, + 1 => .{ .min = 0x0600_0000, .max = 0x0600_0000 + kind.size() }, + 2 => .{ .min = 0x0640_0000, .max = 0x0640_0000 + kind.size() }, + 3 => @panic("VRAMCNT_E: Slots 0-3"), + else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), + }, + .f => switch (mst) { + 0 => .{ .min = 0x0689_0000, .max = 0x0689_4000 }, + 1 => blk: { + const base = 0x0600_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 2 => blk: { + const base = 0x0640_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 3 => @panic("VRAMCNT_F: Slot (OFS.0*1)+(OFS.1*4)"), + 4 => @panic("VRAMCNT_F: Slot 0-1 (OFS=0), Slot 2-3 (OFS=1)"), + 5 => @panic("VRAMCNT_F: Slot 0"), + else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), + }, + .g => switch (mst) { + 0 => .{ .min = 0x0689_4000, .max = 0x0689_8000 }, + 1 => blk: { + const base = 0x0600_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 2 => blk: { + const base = 0x0640_0000 + (0x0000_4000 * (ofs & 0b01)) + (0x0001_0000 * (ofs >> 1)); + break :blk .{ .min = base, .max = base + kind.size() }; + }, + 3 => @panic("VRAMCNT_G: Slot (OFS.0*1)+(OFS.1*4)"), + 4 => @panic("VRAMCNT_G: Slot 0-1 (OFS=0), Slot 2-3 (OFS=1)"), + 5 => @panic("VRAMCNT_G: Slot 0"), + else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), + }, + .h => switch (mst) { + 0 => .{ .min = 0x0689_8000, .max = 0x068A_0000 }, + 1 => .{ .min = 0x0620_0000, .max = 0x0620_0000 + kind.size() }, + 2 => @panic("VRAMCNT_H: Slot 0-3"), + else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), + }, + .i => switch (mst) { + 0 => .{ .min = 0x068A_0000, .max = 0x068A_4000 }, + 1 => .{ .min = 0x0620_8000, .max = 0x0620_8000 + kind.size() }, + 2 => .{ .min = 0x0660_0000, .max = 0x0660_0000 + kind.size() }, + 3 => @panic("Slot 0"), + else => std.debug.panic("Invalid MST for VRAMCNT_{s}", .{[_]u8{std.ascii.toUpper(@tagName(kind)[0])}}), + }, + }; + } + + fn buf_offset(comptime kind: Kind) usize { + // zig fmt: off + return switch (kind) { + .a => 0, // 0x00000 + .b => (128 * KiB) * 1, // 0x20000 (+ 0x20000) + .c => (128 * KiB) * 2, // 0x40000 (+ 0x20000) + .d => (128 * KiB) * 3, // 0x60000 (+ 0x20000) + .e => (128 * KiB) * 4, // 0x80000 (+ 0x20000) + .f => (128 * KiB) * 4 + (64 * KiB), // 0x90000 (+ 0x10000) + .g => (128 * KiB) * 4 + (64 * KiB) + (16 * KiB) * 1, // 0x94000 (+ 0x04000) + .h => (128 * KiB) * 4 + (64 * KiB) + (16 * KiB) * 2, // 0x98000 (+ 0x04000) + .i => (128 * KiB) * 4 + (64 * KiB) + (16 * KiB) * 2 + (32 * KiB) // 0xA0000 (+ 0x08000) + }; + // zig fmt: on + } + + fn CntType(comptime kind: Kind) type { + const io = @import("nds9/io.zig"); + + return switch (kind) { + .a => io.Vramcnt.A, + .b => io.Vramcnt.A, + .c => io.Vramcnt.C, + .d => io.Vramcnt.C, + .e => io.Vramcnt.E, + .f => io.Vramcnt.C, + .g => io.Vramcnt.C, + .h => io.Vramcnt.H, + .i => io.Vramcnt.H, + }; + } + + fn cntValue(self: *const @This(), comptime kind: Kind) CntType(kind) { + return switch (kind) { + .a => self.io.cnt_a, + .b => self.io.cnt_b, + .c => self.io.cnt_c, + .d => self.io.cnt_d, + .e => self.io.cnt_e, + .f => self.io.cnt_f, + .g => self.io.cnt_g, + .h => self.io.cnt_h, + .i => self.io.cnt_i, + }; + } + + pub fn update(self: *@This()) void { + const nds9_tbl = @constCast(self.nds9_table); + const nds7_tbl = @constCast(self.nds7_table); + + for (nds9_tbl, nds7_tbl, 0..) |*nds9_ptr, *nds7_ptr, i| { + const addr = 0x0600_0000 + (i * page_size); + + inline for (std.meta.fields(Kind)) |f| { + const kind = @field(Kind, f.name); + const cnt = cntValue(self, kind); + const ofs = switch (kind) { + .e, .h, .i => 0, + else => cnt.offset.read(), + }; + + const rnge = range(kind, cnt.mst.read(), ofs); + const offset = addr & (kind.size() - 1); + + if (rnge.min <= addr and addr < rnge.max) { + if ((kind == .c or kind == .d) and cnt.mst.read() == 2) { + // Allocate to ARM7 + nds7_ptr.* = self._buf[buf_offset(kind) + offset ..].ptr; + } else { + nds9_ptr.* = self._buf[buf_offset(kind) + offset ..].ptr; + } + } + } + } + } + + // TODO: Rename + 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 masked_addr = address & (addr_space_size - 1); + const page = masked_addr >> bits; + const offset = masked_addr & (page_size - 1); + const table = if (dev == .nds9) self.nds9_table else self.nds7_table; + + if (table[page]) |some_ptr| { + const ptr: [*]const T = @ptrCast(@alignCast(some_ptr)); + + 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 }); + return 0x00; + } + + pub fn write(self: *@This(), comptime T: type, comptime dev: Device, address: u32, value: T) void { + const bits = @typeInfo(IntFittingRange(0, page_size - 1)).Int.bits; + const masked_addr = address & (addr_space_size - 1); + const page = masked_addr >> bits; + const offset = masked_addr & (page_size - 1); + const table = if (dev == .nds9) self.nds9_table else self.nds7_table; + + if (table[page]) |some_ptr| { + const ptr: [*]T = @ptrCast(@alignCast(some_ptr)); + ptr[offset / @sizeOf(T)] = value; + + 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 }); + } };