diff --git a/build.zig b/build.zig index 2127a05..70921a9 100644 --- a/build.zig +++ b/build.zig @@ -29,6 +29,7 @@ pub fn build(b: *std.Build) void { }); exe.addModule("arm32", arm32.module(b)); + exe.addModule("zba-util", b.dependency("zba-util", .{}).module("zba-util")); exe.addModule("zig-clap", b.dependency("zig-clap", .{}).module("clap")); exe.addAnonymousModule("bitfield", .{ .source_file = .{ .path = "lib/bitfield.zig" } }); // https://github.com/FlorenceOS/ diff --git a/src/core/Scheduler.zig b/src/core/Scheduler.zig index 4f8ccfc..197b68b 100644 --- a/src/core/Scheduler.zig +++ b/src/core/Scheduler.zig @@ -20,6 +20,15 @@ pub fn push(self: *@This(), kind: Event.Kind, offset: u64) void { self.queue.add(.{ .kind = kind, .tick = self.tick + offset }) catch unreachable; } +pub fn remove(self: *@This(), needle: Event.Kind) void { + for (self.queue.items, 0..) |event, i| { + if (!std.meta.eql(event.kind, needle)) continue; + + _ = self.queue.removeIndex(i); // note: invalidates self.queue.items + return; + } +} + pub fn deinit(self: @This()) void { self.queue.deinit(); } @@ -58,6 +67,8 @@ pub fn handle(self: *@This(), bus_ptr: ?*anyopaque, event: Event, late: u64) voi }, .hblank => bus.ppu.onHblankEnd(self, late), .vblank => bus.ppu.onHblankEnd(self, late), + .sqrt => bus.io.sqrt.onSqrtCalc(), + .div => bus.io.div.onDivCalc(), } }, } @@ -68,7 +79,7 @@ pub const Event = struct { kind: Kind, const Kind7 = enum {}; - const Kind9 = enum { draw, hblank, vblank }; + const Kind9 = enum { draw, hblank, vblank, sqrt, div }; pub const Kind = union(enum) { nds7: Kind7, diff --git a/src/core/nds9/io.zig b/src/core/nds9/io.zig index 1421830..e46840d 100644 --- a/src/core/nds9/io.zig +++ b/src/core/nds9/io.zig @@ -7,6 +7,8 @@ const Bus = @import("Bus.zig"); const SharedIo = @import("../io.zig").Io; const masks = @import("../io.zig").masks; +const sext = @import("zba-util").sext; + const log = std.log.scoped(.nds9_io); pub const Io = struct { @@ -19,6 +21,10 @@ pub const Io = struct { // Read Only keyinput: AtomicKeyInput = .{}, + /// DS Maths + div: Divisor = .{}, + sqrt: SquareRootUnit = .{}, + pub fn init(io: *SharedIo) @This() { return .{ .shared = io }; } @@ -31,6 +37,8 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T { 0x0400_0210 => bus.io.shared.ie, 0x0400_0214 => bus.io.shared.irq, + 0x0400_02B4 => @truncate(bus.io.sqrt.result), + 0x0410_0000 => bus.io.shared.ipc_fifo.recv(.arm9), else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), }, @@ -40,6 +48,9 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T { 0x0400_0180 => @truncate(bus.io.shared.ipc_fifo.sync.raw), 0x0400_0184 => @truncate(bus.io.shared.ipc_fifo.cnt.raw), + + 0x0400_02B0 => @truncate(bus.io.sqrt.cnt.raw), + else => warn("unexpected: read(T: {}, addr: 0x{X:0>8}) {} ", .{ T, address, T }), }, u8 => switch (address) { @@ -68,6 +79,15 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { 0x0400_0210 => bus.io.shared.ie = value, 0x0400_0214 => bus.io.shared.irq = value, + 0x0400_02B8 => { + bus.io.sqrt.param = (bus.io.sqrt.param & ~@as(u64, 0xFFFF_FFFF)) | value; + bus.io.sqrt.schedule(bus.scheduler); + }, + 0x0400_02BC => { + bus.io.sqrt.param = (@as(u64, value) << 32) | (bus.io.sqrt.param & @as(u64, 0xFFFF_FFFF)); + bus.io.sqrt.schedule(bus.scheduler); + }, + 0x0400_0304 => bus.io.powcnt.raw = value, else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), @@ -77,6 +97,11 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void { 0x0400_0184 => bus.io.shared.ipc_fifo.cnt.raw = masks.ipcFifoCnt(bus, value), 0x0400_0208 => bus.io.shared.ime = value & 1 == 1, + 0x0400_02B0 => { + bus.io.sqrt.cnt.raw = value; + bus.io.sqrt.schedule(bus.scheduler); + }, + else => log.warn("unexpected: write(T: {}, addr: 0x{X:0>8}, value: 0x{X:0>8})", .{ T, address, value }), }, u8 => switch (address) { @@ -107,6 +132,121 @@ const PowCnt = extern union { raw: u32, }; +/// Divisor +const Divisor = struct { + const Scheduler = @import("../Scheduler.zig"); + + /// DIVCNT - Division Control (R/W) + cnt: Cnt = .{ .raw = 0x0000_0000 }, + /// DIV_NUMER - division numerator (R/W) + numerator: u64 = 0x0000_0000_0000_0000, + /// DIV_DENOM - division denominator (R/W) + denominator: u64 = 0x0000_0000_0000_0000, + + /// DIV_RESULT - division quotient (R) + result: u64 = 0x0000_0000_0000_0000, + /// DIVREM_RESULT - remainder + remainder: u64 = 0x0000_0000_0000_0000, + + const Cnt = extern union { + mode: Bitfield(u32, 0, 2), + div_by_zero: Bit(u32, 14), + busy: Bit(u32, 15), + raw: u32, + }; + + pub fn schedule(self: *@This(), scheduler: *Scheduler) void { + self.cnt.busy.set(); + + const cycle_count = switch (self.cnt.mode.read()) { + 0b00 => 18, + 0b01, 0b10, 0b11 => 34, + }; + + scheduler.remove(.{ .nds9 = .{.div} }); + scheduler.push(.{ .nds9 = .{.div} }, cycle_count); + } + + pub fn onDivCalc(self: *@This()) void { + self.cnt.busy.unset(); + + // const mask: u64 = blk: { + // const value: u64 = @intFromBool(self.cnt.mode.read()); + // break :blk value -| 1; + // }; + + if (self.denominator == 0) { + log.err("TODO: deal with division error ({} / {})", .{ self.numerator, self.denominator }); + return; + } + + switch (self.cnt.mode.read()) { + 0b00 => { + // 32bit / 32bit = 32bit , 32bit + const left = self.numerator & 0xFFFF_FFFFF; + const right = self.denominator & 0xFFFF_FFFF; + + self.result = sext(u64, u32, left / right); + self.remainder = sext(u64, u32, left % right); + }, + 0b01, 0b11 => { + // 64bit / 32bit = 64bit , 32bit + const left = self.numerator; + const right = self.denominator & 0xFFFF_FFFF; + + self.result = left / right; + self.remainder = sext(u64, u32, left % right); + }, + 0b10 => { + // 64bit / 64bit = 64bit , 64bit + const left = self.numerator; + const right = self.denominator; + + self.result = left / right; + self.remainder = left % right; + }, + } + } +}; + +/// Square Root Unit +const SquareRootUnit = struct { + const Scheduler = @import("../Scheduler.zig"); + + /// SQRTCNT - Division Control (R/W) + cnt: Cnt = .{ .raw = 0x0000_0000 }, + + /// SQRT_RESULT - square root result (R) + result: u32 = 0x0000_0000, + /// SQRT_PARAM - square root paramater input (R/W) + param: u64 = 0x0000_0000_0000_0000, + + const Cnt = extern union { + mode: Bit(u32, 0), + busy: Bit(u32, 15), + raw: u32, + }; + + pub fn schedule(self: *@This(), scheduler: *Scheduler) void { + defer self.cnt.busy.set(); + + scheduler.remove(.{ .nds9 = .sqrt }); + scheduler.push(.{ .nds9 = .sqrt }, 13); // always takes 13 cycles + } + + pub fn onSqrtCalc(self: *@This()) void { + defer self.cnt.busy.unset(); + + const mask: u64 = blk: { + const value: u64 = @intFromBool(!self.cnt.mode.read()); + break :blk (value << 32) -% 1; + }; + + self.result = @truncate(std.math.sqrt(self.param & mask)); + std.log.debug("sqrt({}) == {}", .{ self.param & mask, self.result }); + } +}; + pub const DispcntA = extern union { bg_mode: Bitfield(u32, 0, 2),