364 lines
12 KiB
Zig
364 lines
12 KiB
Zig
const std = @import("std");
|
|
|
|
const Header = @import("cartridge.zig").Header;
|
|
const Scheduler = @import("Scheduler.zig");
|
|
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
/// Load a NDS Cartridge
|
|
///
|
|
/// intended to be used immediately after Emulator initialization
|
|
pub fn load(allocator: Allocator, system: System, rom_path: []const u8) ![12]u8 {
|
|
const log = std.log.scoped(.load_rom);
|
|
|
|
const file = try std.fs.cwd().openFile(rom_path, .{});
|
|
defer file.close();
|
|
|
|
const buf = try file.readToEndAlloc(allocator, try file.getEndPos());
|
|
defer allocator.free(buf);
|
|
|
|
var stream = std.io.fixedBufferStream(buf);
|
|
const header = try stream.reader().readStruct(Header);
|
|
|
|
log.info("Title: \"{s}\"", .{std.mem.sliceTo(&header.title, 0)});
|
|
log.info("Game Code: \"{s}\"", .{std.mem.sliceTo(&header.game_code, 0)});
|
|
log.info("Maker Code: \"{s}\"", .{std.mem.sliceTo(&header.maker_code, 0)});
|
|
|
|
// Dealing with the ARM946E-S
|
|
{
|
|
log.debug("ARM9 ROM Offset: 0x{X:0>8}", .{header.arm9_rom_offset});
|
|
log.debug("ARM9 Entry Address: 0x{X:0>8}", .{header.arm9_entry_address});
|
|
log.debug("ARM9 RAM Address: 0x{X:0>8}", .{header.arm9_ram_address});
|
|
log.debug("ARM9 Size: 0x{X:0>8}", .{header.arm9_size});
|
|
|
|
// Copy ARM9 Code into Main Memory
|
|
for (buf[header.arm9_rom_offset..][0..header.arm9_size], 0..) |value, i| {
|
|
const address = header.arm9_ram_address + @as(u32, @intCast(i));
|
|
system.bus9.dbgWrite(u8, address, value);
|
|
}
|
|
|
|
system.arm946es.r[15] = header.arm9_entry_address;
|
|
}
|
|
|
|
// Dealing with the ARM7TDMI
|
|
{
|
|
log.debug("ARM7 ROM Offset: 0x{X:0>8}", .{header.arm7_rom_offset});
|
|
log.debug("ARM7 Entry Address: 0x{X:0>8}", .{header.arm7_entry_address});
|
|
log.debug("ARM7 RAM Address: 0x{X:0>8}", .{header.arm7_ram_address});
|
|
log.debug("ARM7 Size: 0x{X:0>8}", .{header.arm7_size});
|
|
|
|
// Copy ARM7 Code into Main Memory
|
|
for (buf[header.arm7_rom_offset..][0..header.arm7_size], 0..) |value, i| {
|
|
const address = header.arm7_ram_address + @as(u32, @intCast(i));
|
|
system.bus7.dbgWrite(u8, address, value);
|
|
}
|
|
|
|
system.arm7tdmi.r[15] = header.arm7_entry_address;
|
|
}
|
|
|
|
return header.title;
|
|
}
|
|
|
|
/// Load NDS Firmware
|
|
pub fn loadFirm(allocator: Allocator, system: System, firm_path: []const u8) !void {
|
|
const log = std.log.scoped(.load_firm);
|
|
|
|
{ // NDS7 BIOS
|
|
const path = try std.mem.join(allocator, "/", &.{ firm_path, "bios7.bin" });
|
|
defer allocator.free(path);
|
|
|
|
log.debug("bios7 path: {s}", .{path});
|
|
|
|
const file = try std.fs.cwd().openFile(path, .{});
|
|
defer file.close();
|
|
|
|
const buf = try file.readToEndAlloc(allocator, try file.getEndPos());
|
|
defer allocator.free(buf);
|
|
|
|
try system.bus7.bios.load(allocator, buf);
|
|
}
|
|
|
|
{ // NDS9 BIOS
|
|
const path = try std.mem.join(allocator, "/", &.{ firm_path, "bios9.bin" });
|
|
defer allocator.free(path);
|
|
|
|
log.debug("bios9 path: {s}", .{path});
|
|
|
|
const file = try std.fs.cwd().openFile(path, .{});
|
|
defer file.close();
|
|
|
|
const buf = try file.readToEndAlloc(allocator, try file.getEndPos());
|
|
defer allocator.free(buf);
|
|
|
|
try system.bus9.bios.load(allocator, buf);
|
|
}
|
|
}
|
|
|
|
const bus_clock = 33513982; // 33.513982 Hz
|
|
const dot_clock = 5585664; // 5.585664 Hz
|
|
const arm7_clock = bus_clock;
|
|
const arm9_clock = bus_clock * 2;
|
|
|
|
pub fn runFrame(scheduler: *Scheduler, system: System) void {
|
|
const cycles_per_dot = arm9_clock / dot_clock + 1;
|
|
comptime std.debug.assert(cycles_per_dot == 12);
|
|
|
|
const cycles_per_frame = 355 * 263 * cycles_per_dot;
|
|
const frame_end = scheduler.tick + cycles_per_frame;
|
|
|
|
while (scheduler.tick < frame_end) {
|
|
switch (isHalted(system)) {
|
|
.both => scheduler.tick = scheduler.peekTimestamp(),
|
|
inline else => |halt| {
|
|
if (comptime halt != .arm9) {
|
|
system.arm946es.step();
|
|
system.arm946es.step();
|
|
}
|
|
|
|
if (comptime halt != .arm7)
|
|
system.arm7tdmi.step();
|
|
},
|
|
}
|
|
|
|
if (scheduler.check()) |ev| {
|
|
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,
|
|
};
|
|
|
|
scheduler.handle(bus_ptr, ev, late);
|
|
}
|
|
}
|
|
}
|
|
|
|
const Halted = enum { arm7, arm9, both, none };
|
|
|
|
inline fn isHalted(system: System) Halted {
|
|
const ret = [_]Halted{ .none, .arm7, .arm9, .both };
|
|
const nds7_bus: *System.Bus7 = @ptrCast(@alignCast(system.arm7tdmi.bus.ptr));
|
|
|
|
const nds9_halt: u2 = @intFromBool(system.cp15.wait_for_interrupt);
|
|
const nds7_halt: u2 = @intFromBool(nds7_bus.io.haltcnt == .halt);
|
|
|
|
return ret[(nds9_halt << 1) | nds7_halt];
|
|
}
|
|
|
|
// FIXME: Perf win to allocating on the stack instead?
|
|
pub const SharedCtx = struct {
|
|
const MiB = 0x100000;
|
|
const KiB = 0x400;
|
|
|
|
pub const Io = @import("io.zig").Io;
|
|
const Vram = @import("ppu/Vram.zig");
|
|
|
|
io: *Io,
|
|
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(Io);
|
|
io.* = .{};
|
|
|
|
break :blk io;
|
|
},
|
|
.wram = wram,
|
|
.vram = vram,
|
|
.main = try allocator.create([4 * MiB]u8),
|
|
};
|
|
|
|
return ctx;
|
|
}
|
|
|
|
pub fn deinit(self: @This(), allocator: Allocator) void {
|
|
self.wram.deinit(allocator);
|
|
allocator.destroy(self.wram);
|
|
|
|
self.vram.deinit(allocator);
|
|
allocator.destroy(self.vram);
|
|
|
|
allocator.destroy(self.io);
|
|
allocator.destroy(self.main);
|
|
}
|
|
};
|
|
|
|
// Before I implement Bus-wide Fastmem, Let's play with some more limited (read: less useful)
|
|
// fastmem implementations
|
|
|
|
// TODO: move somewhere else ideally
|
|
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");
|
|
const KiB = 0x400;
|
|
|
|
const log = std.log.scoped(.shared_wram);
|
|
|
|
_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([buf_len]u8);
|
|
errdefer allocator.destroy(buf);
|
|
|
|
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,
|
|
};
|
|
}
|
|
|
|
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 update(self: *@This(), wramcnt: io.WramCnt) void {
|
|
const mode = wramcnt.mode.read();
|
|
|
|
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 = i * page_size;
|
|
|
|
switch (mode) {
|
|
0b00 => {
|
|
nds9_ptr.* = self._buf[addr..].ptr;
|
|
nds7_ptr.* = null;
|
|
},
|
|
0b01 => {
|
|
nds9_ptr.* = self._buf[0x4000 + (addr & 0x3FFF) ..].ptr;
|
|
nds7_ptr.* = self._buf[(addr & 0x3FFF)..].ptr;
|
|
},
|
|
0b10 => {
|
|
nds9_ptr.* = self._buf[(addr & 0x3FFF)..].ptr;
|
|
nds7_ptr.* = self._buf[0x4000 + (addr & 0x3FFF) ..].ptr;
|
|
},
|
|
0b11 => {
|
|
nds9_ptr.* = null;
|
|
nds7_ptr.* = self._buf[addr..].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 WRAM space", .{ @tagName(dev), T, 0x0300_0000 + 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 WRAM space", .{ @tagName(dev), T, 0x0300_0000 + address, value });
|
|
}
|
|
};
|
|
|
|
pub inline fn forceAlign(comptime T: type, address: u32) u32 {
|
|
return address & ~@as(u32, @sizeOf(T) - 1);
|
|
}
|
|
|
|
pub const System = struct {
|
|
pub const Bus7 = @import("nds7/Bus.zig");
|
|
pub const Bus9 = @import("nds9/Bus.zig");
|
|
pub const Cp15 = @import("nds9/Cp15.zig");
|
|
|
|
pub const Arm7tdmi = @import("arm32").Arm7tdmi;
|
|
pub const Arm946es = @import("arm32").Arm946es;
|
|
|
|
arm7tdmi: *Arm7tdmi,
|
|
arm946es: *Arm946es,
|
|
|
|
bus7: *Bus7,
|
|
bus9: *Bus9,
|
|
|
|
cp15: *Cp15,
|
|
|
|
pub fn deinit(self: @This(), allocator: Allocator) void {
|
|
self.bus7.deinit(allocator);
|
|
self.bus9.deinit(allocator);
|
|
}
|
|
};
|
|
|
|
// FIXME: Using Wram.Device here is jank. System should probably carry an Enum + some Generic Type Fns
|
|
pub fn handleInterrupt(comptime dev: Wram.Device, cpu: if (dev == .nds9) *System.Arm946es else *System.Arm7tdmi) void {
|
|
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.ie.raw & bus_ptr.io.irq.raw) == 0) return; // ensure there is an irq to handle
|
|
|
|
switch (dev) {
|
|
.nds9 => {
|
|
const cp15: *System.Cp15 = @ptrCast(@alignCast(cpu.cp15.ptr));
|
|
cp15.wait_for_interrupt = false;
|
|
},
|
|
.nds7 => bus_ptr.io.haltcnt = .execute,
|
|
}
|
|
|
|
const ret_addr = cpu.r[15] - if (cpu.cpsr.t.read()) 0 else @as(u32, 4);
|
|
const spsr = cpu.cpsr;
|
|
|
|
cpu.changeMode(.Irq);
|
|
cpu.cpsr.t.unset();
|
|
cpu.cpsr.i.set();
|
|
|
|
cpu.r[14] = ret_addr;
|
|
cpu.spsr.raw = spsr.raw;
|
|
cpu.r[15] = if (dev == .nds9) 0xFFFF_0018 else 0x0000_0018;
|
|
cpu.pipe.reload(cpu);
|
|
}
|