diff --git a/src/core/Scheduler.zig b/src/core/Scheduler.zig index 6282e07..a9273d8 100644 --- a/src/core/Scheduler.zig +++ b/src/core/Scheduler.zig @@ -41,6 +41,10 @@ pub fn reset(self: *@This()) void { self.tick = 0; } +pub inline fn peekTimestamp(self: *const @This()) u64 { + return self.queue.items[0].tick; +} + pub inline fn check(self: *@This()) ?Event { @setRuntimeSafety(false); if (self.tick < self.queue.items[0].tick) return null; diff --git a/src/core/emu.zig b/src/core/emu.zig index c3a9b45..460ecb0 100644 --- a/src/core/emu.zig +++ b/src/core/emu.zig @@ -107,9 +107,18 @@ pub fn runFrame(scheduler: *Scheduler, system: System) void { const frame_end = scheduler.tick + cycles_per_frame; while (scheduler.tick < frame_end) { - system.arm7tdmi.step(); - system.arm946es.step(); - system.arm946es.step(); + 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; @@ -126,6 +135,18 @@ pub fn runFrame(scheduler: *Scheduler, system: System) void { } } +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; @@ -320,8 +341,13 @@ pub fn handleInterrupt(comptime dev: Wram.Device, cpu: if (dev == .nds9) *System 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) + 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; diff --git a/src/core/nds7/io.zig b/src/core/nds7/io.zig index 01e3853..8778a2c 100644 --- a/src/core/nds7/io.zig +++ b/src/core/nds7/io.zig @@ -15,28 +15,26 @@ const log = std.log.scoped(.nds7_io); pub const Io = struct { shr: *SharedCtx.Io, - /// Interrupt Master Enable + /// IME - Interrupt Master Enable /// Read/Write ime: bool = false, - /// Interrupt Enable + /// IE - 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 + /// POSTFLG - Post Boot Flag /// Read/Write - /// - /// Caller must cast the `u8` to either `nds7.PostFlg` or `nds9.PostFlg` postflg: PostFlag = .in_progress, + /// HALTCNT - Low Power Mode Control + /// Read/Write + haltcnt: Haltcnt = .execute, + pub fn init(io: *SharedCtx.Io) @This() { return .{ .shr = io }; } @@ -140,4 +138,11 @@ pub const Vramstat = extern union { raw: u8, }; +const Haltcnt = enum(u2) { + execute = 0, + gba_mode, + halt, + sleep, +}; + const PostFlag = enum(u8) { in_progress = 0, completed }; diff --git a/src/core/nds9/Cp15.zig b/src/core/nds9/Cp15.zig index 194ebf4..9c63f26 100644 --- a/src/core/nds9/Cp15.zig +++ b/src/core/nds9/Cp15.zig @@ -8,6 +8,8 @@ control: u32 = 0x0005_2078, dtcm_size_base: u32 = 0x0300_000A, itcm_size_base: u32 = 0x0000_0020, +wait_for_interrupt: bool = false, + // Protection Unit // cache_bits_data_unified: u32 = 0x0000_0000, // cache_write_bufability: u32 = 0x0000_0000, // For Data Protection Regions @@ -73,7 +75,7 @@ pub fn write(self: *@This(), op1: u3, cn: u4, cm: u4, op2: u3, value: u32) void 0b000_0110_0111_001, => log.err("TODO: write to PU instruction region #{}", .{cm}), - 0b000_0111_0000_100 => log.err("TODO: halt ARM946E-S", .{}), + 0b000_0111_0000_100 => self.wait_for_interrupt = true, // NDS9 Halt 0b000_0111_0101_000 => log.err("TODO: invalidate instruction cache", .{}), 0b000_0111_0110_000 => log.err("TODO: invalidate data cache", .{}), 0b000_0111_1010_100 => log.err("TODO: drain write buffer", .{}),