Compare commits

..

2 Commits

Author SHA1 Message Date
Rekai Nyangadzayi Musuka 0732fd26aa feat: implement IPCFIFO IRQs
turbo now passes rockwrestler!!!
2023-10-09 13:14:05 -05:00
Rekai Nyangadzayi Musuka 825b6e95ac feat: implement IPCSYNC irqs 2023-10-09 02:52:11 -05:00
7 changed files with 268 additions and 95 deletions

View File

@ -8,13 +8,16 @@ const Allocator = std.mem.Allocator;
/// Load a NDS Cartridge /// Load a NDS Cartridge
/// ///
/// intended to be used immediately after Emulator initialization /// intended to be used immediately after Emulator initialization
pub fn load(allocator: Allocator, system: System, rom_file: std.fs.File) ![12]u8 { pub fn load(allocator: Allocator, system: System, rom_path: []const u8) ![12]u8 {
const log = std.log.scoped(.load_rom); const log = std.log.scoped(.load_rom);
const rom_buf = try rom_file.readToEndAlloc(allocator, try rom_file.getEndPos()); const file = try std.fs.cwd().openFile(rom_path, .{});
defer allocator.free(rom_buf); defer file.close();
var stream = std.io.fixedBufferStream(rom_buf); 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); const header = try stream.reader().readStruct(Header);
log.info("Title: \"{s}\"", .{std.mem.sliceTo(&header.title, 0)}); log.info("Title: \"{s}\"", .{std.mem.sliceTo(&header.title, 0)});
@ -29,7 +32,7 @@ pub fn load(allocator: Allocator, system: System, rom_file: std.fs.File) ![12]u8
log.debug("ARM9 Size: 0x{X:0>8}", .{header.arm9_size}); log.debug("ARM9 Size: 0x{X:0>8}", .{header.arm9_size});
// Copy ARM9 Code into Main Memory // Copy ARM9 Code into Main Memory
for (rom_buf[header.arm9_rom_offset..][0..header.arm9_size], 0..) |value, i| { for (buf[header.arm9_rom_offset..][0..header.arm9_size], 0..) |value, i| {
const address = header.arm9_ram_address + @as(u32, @intCast(i)); const address = header.arm9_ram_address + @as(u32, @intCast(i));
system.bus9.dbgWrite(u8, address, value); system.bus9.dbgWrite(u8, address, value);
} }
@ -45,7 +48,7 @@ pub fn load(allocator: Allocator, system: System, rom_file: std.fs.File) ![12]u8
log.debug("ARM7 Size: 0x{X:0>8}", .{header.arm7_size}); log.debug("ARM7 Size: 0x{X:0>8}", .{header.arm7_size});
// Copy ARM7 Code into Main Memory // Copy ARM7 Code into Main Memory
for (rom_buf[header.arm7_rom_offset..][0..header.arm7_size], 0..) |value, i| { for (buf[header.arm7_rom_offset..][0..header.arm7_size], 0..) |value, i| {
const address = header.arm7_ram_address + @as(u32, @intCast(i)); const address = header.arm7_ram_address + @as(u32, @intCast(i));
system.bus7.dbgWrite(u8, address, value); system.bus7.dbgWrite(u8, address, value);
} }
@ -56,6 +59,41 @@ pub fn load(allocator: Allocator, system: System, rom_file: std.fs.File) ![12]u8
return header.title; 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);
@memcpy(system.bus7.bios[0..buf.len], 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);
@memcpy(system.bus9.bios[0..buf.len], buf);
}
}
const bus_clock = 33513982; // 33.513982 Hz const bus_clock = 33513982; // 33.513982 Hz
const dot_clock = 5585664; // 5.585664 Hz const dot_clock = 5585664; // 5.585664 Hz
const arm7_clock = bus_clock; const arm7_clock = bus_clock;
@ -273,3 +311,27 @@ pub const System = struct {
self.bus9.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
// TODO: Handle HALT
// HALTCNG (NDS7) and CP15 (NDS9)
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);
}

View File

@ -2,42 +2,16 @@ const std = @import("std");
const Bitfield = @import("bitfield").Bitfield; const Bitfield = @import("bitfield").Bitfield;
const Bit = @import("bitfield").Bit; const Bit = @import("bitfield").Bit;
const System = @import("emu.zig").System;
const handleInterrupt = @import("emu.zig").handleInterrupt;
const log = std.log.scoped(.shared_io); const log = std.log.scoped(.shared_io);
// FIXME: This whole thing is bad bad bad bad bad
// I think only the IPC stuff needs to be here, since they talk to each other.
// every other "shared I/O register" is just duplicated on both CPUs. So they shouldn't be here
pub const Io = struct { pub const Io = struct {
/// Interrupt Master Enable
/// Read/Write
ime: bool = false,
/// Interrupt Enable
/// Read/Write
///
/// Caller must cast the `u32` to either `nds7.IntEnable` or `nds9.IntEnable`
ie: u32 = 0x0000_0000,
/// IF - Interrupt Request
/// Read/Write
///
/// Caller must cast the `u32` to either `nds7.IntRequest` or `nds9.IntRequest`
irq: u32 = 0x0000_0000,
/// Inter Process Communication FIFO /// Inter Process Communication FIFO
ipc_fifo: IpcFifo = .{}, ipc: Ipc = .{},
/// Post Boot Flag
/// Read/Write
///
/// Caller must cast the `u8` to either `nds7.PostFlg` or `nds9.PostFlg`
post_flg: u8 = @intFromEnum(nds7.PostFlag.in_progress),
wramcnt: WramCnt = .{ .raw = 0x00 }, wramcnt: WramCnt = .{ .raw = 0x00 },
// TODO: DS Cartridge I/O Ports
}; };
fn warn(comptime format: []const u8, args: anytype) u0 { fn warn(comptime format: []const u8, args: anytype) u0 {
@ -45,13 +19,20 @@ fn warn(comptime format: []const u8, args: anytype) u0 {
return 0; return 0;
} }
const IpcFifo = struct { /// Inter-Process Communication
const Ipc = struct {
const Sync = IpcSync; const Sync = IpcSync;
const Control = IpcFifoCnt; const Control = IpcFifoCnt;
_nds7: Impl = .{}, _nds7: Impl = .{},
_nds9: Impl = .{}, _nds9: Impl = .{},
// we need access to the CPUs to handle IPC IRQs
arm7tdmi: ?*System.Arm7tdmi = null,
arm946es: ?*System.Arm946es = null,
// TODO: DS Cartridge I/O Ports
const Source = enum { nds7, nds9 }; const Source = enum { nds7, nds9 };
const Impl = struct { const Impl = struct {
@ -69,6 +50,11 @@ const IpcFifo = struct {
last_read: ?u32 = null, last_read: ?u32 = null,
}; };
pub fn configure(self: *@This(), system: System) void {
self.arm7tdmi = system.arm7tdmi;
self.arm946es = system.arm946es;
}
/// IPCSYNC /// IPCSYNC
/// Read/Write /// Read/Write
pub fn setIpcSync(self: *@This(), comptime src: Source, value: anytype) void { pub fn setIpcSync(self: *@This(), comptime src: Source, value: anytype) void {
@ -77,28 +63,42 @@ const IpcFifo = struct {
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);
if (value >> 13 & 1 == 1 and self._nds9.sync.recv_irq.read()) {
const bus: *System.Bus9 = @ptrCast(@alignCast(self.arm946es.?.bus.ptr));
bus.io.irq.ipcsync.set();
handleInterrupt(.nds9, self.arm946es.?);
}
if (value >> 3 & 1 == 1) { if (value >> 3 & 1 == 1) {
self._nds7.fifo.reset(); self._nds7.fifo.reset();
self._nds7.cnt.send_fifo_empty.write(true); self._nds7.cnt.send_fifo_empty.set();
self._nds9.cnt.recv_fifo_empty.write(true); self._nds9.cnt.recv_fifo_empty.set();
self._nds7.cnt.send_fifo_full.write(false); self._nds7.cnt.send_fifo_full.unset();
self._nds9.cnt.recv_fifo_full.write(false); self._nds9.cnt.recv_fifo_full.unset();
} }
}, },
.nds9 => { .nds9 => {
self._nds9.sync.raw = masks.ipcFifoSync(self._nds9.sync.raw, value); self._nds9.sync.raw = masks.ipcFifoSync(self._nds9.sync.raw, value);
self._nds7.sync.raw = masks.mask(self._nds7.sync.raw, (self._nds9.sync.raw >> 8) & 0xF, 0xF); self._nds7.sync.raw = masks.mask(self._nds7.sync.raw, (self._nds9.sync.raw >> 8) & 0xF, 0xF);
if (value >> 13 & 1 == 1 and self._nds7.sync.recv_irq.read()) {
const bus: *System.Bus7 = @ptrCast(@alignCast(self.arm7tdmi.?.bus.ptr));
bus.io.irq.ipcsync.set();
handleInterrupt(.nds7, self.arm7tdmi.?);
}
if (value >> 3 & 1 == 1) { if (value >> 3 & 1 == 1) {
self._nds9.fifo.reset(); self._nds9.fifo.reset();
self._nds9.cnt.send_fifo_empty.write(true); self._nds9.cnt.send_fifo_empty.set();
self._nds7.cnt.recv_fifo_empty.write(true); self._nds7.cnt.recv_fifo_empty.set();
self._nds9.cnt.send_fifo_full.write(false); self._nds9.cnt.send_fifo_full.unset();
self._nds7.cnt.recv_fifo_full.write(false); self._nds7.cnt.recv_fifo_full.unset();
} }
}, },
} }
@ -121,23 +121,49 @@ const IpcFifo = struct {
if (!self._nds7.cnt.enable_fifos.read()) return; if (!self._nds7.cnt.enable_fifos.read()) return;
try self._nds7.fifo.push(value); try self._nds7.fifo.push(value);
const not_empty_cache = !self._nds9.cnt.recv_fifo_empty.read();
// update status bits // update status bits
self._nds7.cnt.send_fifo_empty.write(self._nds7.fifo._len() == 0); self._nds7.cnt.send_fifo_empty.write(self._nds7.fifo._len() == 0);
self._nds9.cnt.recv_fifo_empty.write(self._nds7.fifo._len() == 0); self._nds9.cnt.recv_fifo_empty.write(self._nds7.fifo._len() == 0);
self._nds7.cnt.send_fifo_full.write(self._nds7.fifo._len() == 0x10); self._nds7.cnt.send_fifo_full.write(self._nds7.fifo._len() == 0x10);
self._nds9.cnt.recv_fifo_full.write(self._nds7.fifo._len() == 0x10); self._nds9.cnt.recv_fifo_full.write(self._nds7.fifo._len() == 0x10);
const not_empty = !self._nds9.cnt.recv_fifo_empty.read();
if (self._nds9.cnt.recv_fifo_irq_enable.read() and !not_empty_cache and not_empty) {
// NDS7 Send | NDS9 RECV (Handling Not Empty)
const bus: *System.Bus9 = @ptrCast(@alignCast(self.arm946es.?.bus.ptr));
bus.io.irq.ipc_recv_not_empty.set();
handleInterrupt(.nds9, self.arm946es.?);
}
}, },
.nds9 => { .nds9 => {
if (!self._nds9.cnt.enable_fifos.read()) return; if (!self._nds9.cnt.enable_fifos.read()) return;
try self._nds9.fifo.push(value); try self._nds9.fifo.push(value);
const not_empty_cache = !self._nds7.cnt.recv_fifo_empty.read();
// update status bits // update status bits
self._nds9.cnt.send_fifo_empty.write(self._nds9.fifo._len() == 0); self._nds9.cnt.send_fifo_empty.write(self._nds9.fifo._len() == 0);
self._nds7.cnt.recv_fifo_empty.write(self._nds9.fifo._len() == 0); self._nds7.cnt.recv_fifo_empty.write(self._nds9.fifo._len() == 0);
self._nds9.cnt.send_fifo_full.write(self._nds9.fifo._len() == 0x10); self._nds9.cnt.send_fifo_full.write(self._nds9.fifo._len() == 0x10);
self._nds7.cnt.recv_fifo_full.write(self._nds9.fifo._len() == 0x10); self._nds7.cnt.recv_fifo_full.write(self._nds9.fifo._len() == 0x10);
const not_empty = !self._nds7.cnt.recv_fifo_empty.read();
if (self._nds7.cnt.recv_fifo_irq_enable.read() and !not_empty_cache and not_empty) {
// NDS9 Send | NDS7 RECV (Handling Not Empty)
const bus: *System.Bus7 = @ptrCast(@alignCast(self.arm7tdmi.?.bus.ptr));
bus.io.irq.ipc_recv_not_empty.set();
handleInterrupt(.nds7, self.arm7tdmi.?);
}
}, },
} }
} }
@ -158,6 +184,8 @@ const IpcFifo = struct {
break :blk self._nds7.last_read orelse 0x0000_0000; break :blk self._nds7.last_read orelse 0x0000_0000;
}; };
const empty_cache = self._nds9.cnt.send_fifo_empty.read();
// update status bits // update status bits
self._nds7.cnt.recv_fifo_empty.write(self._nds9.fifo._len() == 0); self._nds7.cnt.recv_fifo_empty.write(self._nds9.fifo._len() == 0);
self._nds9.cnt.send_fifo_empty.write(self._nds9.fifo._len() == 0); self._nds9.cnt.send_fifo_empty.write(self._nds9.fifo._len() == 0);
@ -165,6 +193,15 @@ const IpcFifo = struct {
self._nds7.cnt.recv_fifo_full.write(self._nds9.fifo._len() == 0x10); self._nds7.cnt.recv_fifo_full.write(self._nds9.fifo._len() == 0x10);
self._nds9.cnt.send_fifo_full.write(self._nds9.fifo._len() == 0x10); self._nds9.cnt.send_fifo_full.write(self._nds9.fifo._len() == 0x10);
const empty = self._nds9.cnt.send_fifo_empty.read();
if (self._nds9.cnt.send_fifo_irq_enable.read() and (!empty_cache and empty)) {
const bus: *System.Bus9 = @ptrCast(@alignCast(self.arm946es.?.bus.ptr));
bus.io.irq.ipc_send_empty.set();
handleInterrupt(.nds9, self.arm946es.?);
}
return value; return value;
}, },
.nds9 => { .nds9 => {
@ -179,6 +216,8 @@ const IpcFifo = struct {
break :blk self._nds7.last_read orelse 0x0000_0000; break :blk self._nds7.last_read orelse 0x0000_0000;
}; };
const empty_cache = self._nds7.cnt.send_fifo_empty.read();
// update status bits // update status bits
self._nds9.cnt.recv_fifo_empty.write(self._nds7.fifo._len() == 0); self._nds9.cnt.recv_fifo_empty.write(self._nds7.fifo._len() == 0);
self._nds7.cnt.send_fifo_empty.write(self._nds7.fifo._len() == 0); self._nds7.cnt.send_fifo_empty.write(self._nds7.fifo._len() == 0);
@ -186,6 +225,15 @@ const IpcFifo = struct {
self._nds9.cnt.recv_fifo_full.write(self._nds7.fifo._len() == 0x10); self._nds9.cnt.recv_fifo_full.write(self._nds7.fifo._len() == 0x10);
self._nds7.cnt.send_fifo_full.write(self._nds7.fifo._len() == 0x10); self._nds7.cnt.send_fifo_full.write(self._nds7.fifo._len() == 0x10);
const empty = self._nds7.cnt.send_fifo_empty.read();
if (self._nds7.cnt.send_fifo_irq_enable.read() and (!empty_cache and empty)) {
const bus: *System.Bus7 = @ptrCast(@alignCast(self.arm7tdmi.?.bus.ptr));
bus.io.irq.ipc_send_empty.set();
handleInterrupt(.nds7, self.arm7tdmi.?);
}
return value; return value;
}, },
} }
@ -259,7 +307,6 @@ pub const masks = struct {
const err_mask: u32 = 0x4000; // bit 14 const err_mask: u32 = 0x4000; // bit 14
const err_bit = (cnt & err_mask) & ~(value & err_mask); const err_bit = (cnt & err_mask) & ~(value & err_mask);
if (value & 0b1000 != 0) log.err("TODO: handle IPCFIFOCNT.3", .{});
const without_err = (@as(u32, value) & _mask) | (cnt & ~_mask); const without_err = (@as(u32, value) & _mask) | (cnt & ~_mask);
return (without_err & ~err_mask) | err_bit; return (without_err & ~err_mask) | err_bit;
@ -271,23 +318,12 @@ pub const masks = struct {
} }
}; };
pub const nds7 = struct { // FIXME: bitfields depends on NDS9 / NDS7
pub const IntEnable = extern union { pub const IntEnable = extern union {
raw: u32, ipcsync: Bit(u32, 16),
}; ipc_send_empty: Bit(u32, 17),
ipc_recv_not_empty: Bit(u32, 18),
pub const IntRequest = IntEnable; raw: u32,
pub const PostFlag = enum(u8) { in_progress = 0, completed };
};
pub const nds9 = struct {
pub const IntEnable = extern union {
raw: u32,
};
pub const IntRequest = IntEnable;
pub const PostFlag = enum(u8) { in_progress = 0, completed };
}; };
const Fifo = struct { const Fifo = struct {

View File

@ -22,10 +22,16 @@ wram: *[64 * KiB]u8,
vram: *Vram, vram: *Vram,
io: io.Io, io: io.Io,
bios: *[16 * KiB]u8,
pub fn init(allocator: Allocator, scheduler: *Scheduler, ctx: SharedCtx) !@This() { pub fn init(allocator: Allocator, scheduler: *Scheduler, ctx: SharedCtx) !@This() {
const wram = try allocator.create([64 * KiB]u8); const wram = try allocator.create([64 * KiB]u8);
errdefer allocator.destroy(wram);
@memset(wram, 0); @memset(wram, 0);
errdefer allocator.destroy(wram);
const bios = try allocator.create([16 * KiB]u8);
@memset(bios, 0);
errdefer allocator.destroy(bios);
return .{ return .{
.main = ctx.main, .main = ctx.main,
@ -34,11 +40,14 @@ pub fn init(allocator: Allocator, scheduler: *Scheduler, ctx: SharedCtx) !@This(
.wram = wram, .wram = wram,
.scheduler = scheduler, .scheduler = scheduler,
.io = io.Io.init(ctx.io), .io = io.Io.init(ctx.io),
.bios = bios,
}; };
} }
pub fn deinit(self: *@This(), allocator: Allocator) void { pub fn deinit(self: *@This(), allocator: Allocator) void {
allocator.destroy(self.wram); allocator.destroy(self.wram);
allocator.destroy(self.bios);
} }
pub fn reset(_: *@This()) void {} pub fn reset(_: *@This()) void {}
@ -64,12 +73,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 => readInt(T, self.bios[address & 0x3FFF ..][0..byte_count]),
0x0200_0000...0x02FF_FFFF => readInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count]), 0x0200_0000...0x02FF_FFFF => readInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count]),
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 => readInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count]),
else => self.shr_wram.read(T, .nds7, aligned_addr), else => self.shr_wram.read(T, .nds7, aligned_addr),
}, },
0x0380_0000...0x0380_FFFF => readInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count]), 0x0380_0000...0x03FF_FFFF => readInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count]),
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),
else => warn("unexpected read: 0x{x:0>8} -> {}", .{ aligned_addr, T }), else => warn("unexpected read: 0x{x:0>8} -> {}", .{ aligned_addr, T }),
@ -97,6 +107,7 @@ fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, v
} }
switch (aligned_addr) { switch (aligned_addr) {
0x0000_0000...0x01FF_FFFF => log.err("tried to read from NDS7 BIOS: 0x{X:0>8}", .{aligned_addr}),
0x0200_0000...0x02FF_FFFF => writeInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count], value), 0x0200_0000...0x02FF_FFFF => writeInt(T, self.main[aligned_addr & 0x003F_FFFF ..][0..byte_count], value),
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 => writeInt(T, self.wram[aligned_addr & 0x0000_FFFF ..][0..byte_count], value),

View File

@ -7,11 +7,36 @@ const Bus = @import("Bus.zig");
const SharedCtx = @import("../emu.zig").SharedCtx; const SharedCtx = @import("../emu.zig").SharedCtx;
const masks = @import("../io.zig").masks; const masks = @import("../io.zig").masks;
const IntEnable = @import("../io.zig").IntEnable;
const IntRequest = @import("../io.zig").IntEnable;
const log = std.log.scoped(.nds7_io); const log = std.log.scoped(.nds7_io);
pub const Io = struct { pub const Io = struct {
shr: *SharedCtx.Io, shr: *SharedCtx.Io,
/// Interrupt Master Enable
/// Read/Write
ime: bool = false,
/// Interrupt Enable
/// Read/Write
///
/// Caller must cast the `u32` to either `nds7.IntEnable` or `nds9.IntEnable`
ie: IntEnable = .{ .raw = 0x0000_0000 },
/// IF - Interrupt Request
/// Read/Write
///
/// Caller must cast the `u32` to either `nds7.IntRequest` or `nds9.IntRequest`
irq: IntRequest = .{ .raw = 0x0000_0000 },
/// Post Boot Flag
/// Read/Write
///
/// Caller must cast the `u8` to either `nds7.PostFlg` or `nds9.PostFlg`
postflg: PostFlag = .in_progress,
pub fn init(io: *SharedCtx.Io) @This() { pub fn init(io: *SharedCtx.Io) @This() {
return .{ .shr = io }; return .{ .shr = io };
} }
@ -20,21 +45,23 @@ pub const Io = struct {
pub fn read(bus: *const Bus, comptime T: type, address: u32) T { pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
return switch (T) { return switch (T) {
u32 => switch (address) { u32 => switch (address) {
0x0400_0208 => @intFromBool(bus.io.shr.ime), 0x0400_0208 => @intFromBool(bus.io.ime),
0x0400_0210 => bus.io.shr.ie, 0x0400_0210 => bus.io.ie.raw,
0x0400_0214 => bus.io.shr.irq, 0x0400_0214 => bus.io.irq.raw,
0x0410_0000 => bus.io.shr.ipc_fifo.recv(.nds7), 0x0410_0000 => bus.io.shr.ipc.recv(.nds7),
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_0180 => @truncate(bus.io.shr.ipc_fifo._nds7.sync.raw), 0x0400_0180 => @truncate(bus.io.shr.ipc._nds7.sync.raw),
0x0400_0184 => @truncate(bus.io.shr.ipc_fifo._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) {
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,
0x0400_0300 => @intFromEnum(bus.io.postflg),
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 }),
}, },
else => @compileError(T ++ " is an unsupported bus read type"), else => @compileError(T ++ " is an unsupported bus read type"),
@ -44,19 +71,20 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
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_0208 => bus.io.shr.ime = value & 1 == 1, 0x0400_0208 => bus.io.ime = value & 1 == 1,
0x0400_0210 => bus.io.shr.ie = value, 0x0400_0210 => bus.io.ie.raw = value,
0x0400_0214 => bus.io.shr.irq = value, 0x0400_0214 => bus.io.irq.raw &= ~value,
0x0400_0188 => bus.io.shr.ipc_fifo.send(.nds7, value) catch |e| std.debug.panic("FIFO error: {}", .{e}), 0x0400_0188 => bus.io.shr.ipc.send(.nds7, value) catch |e| std.debug.panic("FIFO error: {}", .{e}),
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_0180 => bus.io.shr.ipc_fifo.setIpcSync(.nds7, value), 0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds7, value),
0x0400_0184 => bus.io.shr.ipc_fifo.setIpcFifoCnt(.nds7, value), 0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds7, 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 }),
}, },
u8 => switch (address) { u8 => switch (address) {
0x0400_0208 => bus.io.ime = value & 1 == 1,
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 }),
}, },
else => @compileError(T ++ " is an unsupported bus write type"), else => @compileError(T ++ " is an unsupported bus write type"),
@ -73,3 +101,5 @@ pub const Vramstat = extern union {
vramd_enabled: Bit(u8, 1), vramd_enabled: Bit(u8, 1),
raw: u8, raw: u8,
}; };
const PostFlag = enum(u8) { in_progress = 0, completed };

View File

@ -20,23 +20,32 @@ wram: *Wram,
io: io.Io, io: io.Io,
ppu: Ppu, ppu: Ppu,
bios: *[32 * KiB]u8,
scheduler: *Scheduler, scheduler: *Scheduler,
pub fn init(allocator: Allocator, scheduler: *Scheduler, ctx: SharedCtx) !@This() { pub fn init(allocator: Allocator, scheduler: *Scheduler, ctx: SharedCtx) !@This() {
const dots_per_cycle = 3; // ARM946E-S runs twice as fast as the ARM7TDMI const dots_per_cycle = 3; // ARM946E-S runs twice as fast as the ARM7TDMI
scheduler.push(.{ .nds9 = .draw }, 256 * dots_per_cycle); scheduler.push(.{ .nds9 = .draw }, 256 * dots_per_cycle);
const bios = try allocator.create([32 * KiB]u8);
@memset(bios, 0);
errdefer allocator.destroy(bios);
return .{ return .{
.main = ctx.main, .main = ctx.main,
.wram = ctx.wram, .wram = ctx.wram,
.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),
.bios = bios,
}; };
} }
pub fn deinit(self: *@This(), allocator: Allocator) void { pub fn deinit(self: *@This(), allocator: Allocator) void {
self.ppu.deinit(allocator); self.ppu.deinit(allocator);
allocator.destroy(self.bios);
} }
pub fn reset(_: *@This()) void { pub fn reset(_: *@This()) void {
@ -68,6 +77,7 @@ fn _read(self: *@This(), comptime T: type, comptime mode: Mode, address: u32) T
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),
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 => readInt(T, self.bios[address & 0x0000_7FFF ..][0..byte_count]),
else => warn("unexpected read: 0x{x:0>8} -> {}", .{ aligned_addr, T }), else => warn("unexpected read: 0x{x:0>8} -> {}", .{ aligned_addr, T }),
}; };
} }
@ -97,6 +107,7 @@ fn _write(self: *@This(), comptime T: type, comptime mode: Mode, address: u32, v
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),
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 => log.err("tried to read from NDS9 BIOS: 0x{X:0>8}", .{aligned_addr}),
else => log.warn("unexpected write: 0x{X:}{} -> 0x{X:0>8}", .{ value, T, aligned_addr }), else => log.warn("unexpected write: 0x{X:}{} -> 0x{X:0>8}", .{ value, T, aligned_addr }),
} }
} }

View File

@ -7,6 +7,9 @@ const Bus = @import("Bus.zig");
const SharedCtx = @import("../emu.zig").SharedCtx; const SharedCtx = @import("../emu.zig").SharedCtx;
const masks = @import("../io.zig").masks; const masks = @import("../io.zig").masks;
const IntEnable = @import("../io.zig").IntEnable;
const IntRequest = @import("../io.zig").IntEnable;
const sext = @import("../../util.zig").sext; const sext = @import("../../util.zig").sext;
const log = std.log.scoped(.nds9_io); const log = std.log.scoped(.nds9_io);
@ -14,6 +17,22 @@ const log = std.log.scoped(.nds9_io);
pub const Io = struct { pub const Io = struct {
shr: *SharedCtx.Io, shr: *SharedCtx.Io,
/// Interrupt Master Enable
/// Read/Write
ime: bool = false,
/// Interrupt Enable
/// Read/Write
///
/// Caller must cast the `u32` to either `nds7.IntEnable` or `nds9.IntEnable`
ie: IntEnable = .{ .raw = 0x0000_0000 },
/// IF - Interrupt Request
/// Read/Write
///
/// Caller must cast the `u32` to either `nds7.IntRequest` or `nds9.IntRequest`
irq: IntRequest = .{ .raw = 0x0000_0000 },
/// POWCNT1 - Graphics Power Control /// POWCNT1 - Graphics Power Control
/// Read / Write /// Read / Write
powcnt: PowCnt = .{ .raw = 0x0000_0000 }, powcnt: PowCnt = .{ .raw = 0x0000_0000 },
@ -33,9 +52,9 @@ pub const Io = struct {
pub fn read(bus: *const Bus, comptime T: type, address: u32) T { pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
return switch (T) { return switch (T) {
u32 => switch (address) { u32 => switch (address) {
0x0400_0208 => @intFromBool(bus.io.shr.ime), 0x0400_0208 => @intFromBool(bus.io.ime),
0x0400_0210 => bus.io.shr.ie, 0x0400_0210 => bus.io.ie.raw,
0x0400_0214 => bus.io.shr.irq, 0x0400_0214 => bus.io.irq.raw,
0x0400_02A0 => @truncate(bus.io.div.result), 0x0400_02A0 => @truncate(bus.io.div.result),
0x0400_02A4 => @truncate(bus.io.div.result >> 32), 0x0400_02A4 => @truncate(bus.io.div.result >> 32),
@ -43,15 +62,15 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
0x0400_02AC => @truncate(bus.io.div.remainder >> 32), 0x0400_02AC => @truncate(bus.io.div.remainder >> 32),
0x0400_02B4 => @truncate(bus.io.sqrt.result), 0x0400_02B4 => @truncate(bus.io.sqrt.result),
0x0410_0000 => bus.io.shr.ipc_fifo.recv(.nds9), 0x0410_0000 => bus.io.shr.ipc.recv(.nds9),
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_0004 => bus.ppu.io.dispstat.raw, 0x0400_0004 => bus.ppu.io.dispstat.raw,
0x0400_0130 => bus.io.keyinput.load(.Monotonic), 0x0400_0130 => bus.io.keyinput.load(.Monotonic),
0x0400_0180 => @truncate(bus.io.shr.ipc_fifo._nds9.sync.raw), 0x0400_0180 => @truncate(bus.io.shr.ipc._nds9.sync.raw),
0x0400_0184 => @truncate(bus.io.shr.ipc_fifo._nds9.cnt.raw), 0x0400_0184 => @truncate(bus.io.shr.ipc._nds9.cnt.raw),
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),
@ -69,9 +88,9 @@ 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.io.dispcnt_a.raw = value, 0x0400_0000 => bus.ppu.io.dispcnt_a.raw = value,
0x0400_0180 => bus.io.shr.ipc_fifo.setIpcSync(.nds9, value), 0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds9, value),
0x0400_0184 => bus.io.shr.ipc_fifo.setIpcFifoCnt(.nds9, value), 0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds9, value),
0x0400_0188 => bus.io.shr.ipc_fifo.send(.nds9, value) catch |e| std.debug.panic("IPC FIFO Error: {}", .{e}), 0x0400_0188 => bus.io.shr.ipc.send(.nds9, value) catch |e| std.debug.panic("IPC FIFO Error: {}", .{e}),
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
@ -80,9 +99,9 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
bus.ppu.vram.io.cnt_d.raw = @truncate(value >> 24); // 0x0400_0243 bus.ppu.vram.io.cnt_d.raw = @truncate(value >> 24); // 0x0400_0243
}, },
0x0400_0208 => bus.io.shr.ime = value & 1 == 1, 0x0400_0208 => bus.io.ime = value & 1 == 1,
0x0400_0210 => bus.io.shr.ie = value, 0x0400_0210 => bus.io.ie.raw = value,
0x0400_0214 => bus.io.shr.irq = value, 0x0400_0214 => bus.io.irq.raw &= ~value,
0x0400_0290 => { 0x0400_0290 => {
bus.io.div.numerator = masks.mask(bus.io.div.numerator, value, 0xFFFF_FFFF); bus.io.div.numerator = masks.mask(bus.io.div.numerator, value, 0xFFFF_FFFF);
@ -114,9 +133,9 @@ 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_0180 => bus.io.shr.ipc_fifo.setIpcSync(.nds9, value), 0x0400_0180 => bus.io.shr.ipc.setIpcSync(.nds9, value),
0x0400_0184 => bus.io.shr.ipc_fifo.setIpcFifoCnt(.nds9, value), 0x0400_0184 => bus.io.shr.ipc.setIpcFifoCnt(.nds9, value),
0x0400_0208 => bus.io.shr.ime = value & 1 == 1, 0x0400_0208 => bus.io.ime = value & 1 == 1,
0x0400_0280 => { 0x0400_0280 => {
bus.io.div.cnt.raw = value; bus.io.div.cnt.raw = value;

View File

@ -13,6 +13,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
\\<str> Path to the NDS ROM \\<str> Path to the NDS ROM
\\ \\
); );
@ -31,10 +32,10 @@ pub fn main() !void {
const rom_path = try handlePositional(result); const rom_path = try handlePositional(result);
log.debug("loading rom from: {s}", .{rom_path}); log.debug("loading rom from: {s}", .{rom_path});
const rom_file = try std.fs.cwd().openFile(rom_path, .{}); const firm_path = result.args.firm;
defer rom_file.close(); log.debug("loading firmware from from: {?s}", .{firm_path});
const ctx = try SharedCtx.init(allocator); var ctx = try SharedCtx.init(allocator);
defer ctx.deinit(allocator); defer ctx.deinit(allocator);
var scheduler = try Scheduler.init(allocator); var scheduler = try Scheduler.init(allocator);
@ -56,7 +57,10 @@ pub fn main() !void {
break :blk .{ .arm7tdmi = &arm7tdmi, .arm946es = &arm946es, .bus7 = &bus7, .bus9 = &bus9, .cp15 = &cp15 }; break :blk .{ .arm7tdmi = &arm7tdmi, .arm946es = &arm946es, .bus7 = &bus7, .bus9 = &bus9, .cp15 = &cp15 };
}; };
defer system.deinit(allocator); defer system.deinit(allocator);
const rom_title = try emu.load(allocator, system, rom_file);
ctx.io.ipc.configure(system); // Shared I/O needs access to both CPUs (e.g. IPCSYNC)
const rom_title = try emu.load(allocator, system, rom_path);
if (firm_path) |path| try emu.loadFirm(allocator, system, path);
var ui = try Ui.init(allocator); var ui = try Ui.init(allocator);
defer ui.deinit(allocator); defer ui.deinit(allocator);