feat: implement VRAM allocation
This commit is contained in:
		@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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 }),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -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 }),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 }),
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										354
									
								
								src/core/ppu.zig
									
									
									
									
									
								
							
							
						
						
									
										354
									
								
								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 });
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user