diff --git a/README.md b/README.md index 0586f8d..8547f41 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ arm7wrestler GBA Fixed | [destoer](https://github.com/destoer) ## Compiling -Most recently built on Zig [v0.11.0-dev.926+266e2e9a3](https://github.com/ziglang/zig/tree/266e2e9a3) +Most recently built on Zig [v0.11.0-dev.987+a1d82352d](https://github.com/ziglang/zig/tree/a1d82352d) ### Dependencies diff --git a/build.zig b/build.zig index 09cf61c..c8f0e87 100644 --- a/build.zig +++ b/build.zig @@ -4,7 +4,7 @@ const Sdk = @import("lib/SDL.zig/Sdk.zig"); pub fn build(b: *std.build.Builder) void { // Minimum Zig Version - const min_ver = std.SemanticVersion.parse("0.11.0-dev.926+266e2e9a3") catch return; // https://github.com/ziglang/zig/commit/266e2e9a3 + const min_ver = std.SemanticVersion.parse("0.11.0-dev.987+a1d82352d") catch return; // https://github.com/ziglang/zig/commit/19056cb68 if (builtin.zig_version.order(min_ver).compare(.lt)) { std.log.err("{s}", .{b.fmt("Zig v{} does not meet the minimum version requirement. (Zig v{})", .{ builtin.zig_version, min_ver })}); std.os.exit(1); diff --git a/src/core/Bus.zig b/src/core/Bus.zig index 0c06f83..864fa4a 100644 --- a/src/core/Bus.zig +++ b/src/core/Bus.zig @@ -60,9 +60,8 @@ allocator: Allocator, pub fn init(self: *Self, allocator: Allocator, sched: *Scheduler, cpu: *Arm7tdmi, paths: FilePaths) !void { const tables = try allocator.alloc(?*anyopaque, 3 * table_len); // Allocate all tables - const read_table: *[table_len]?*const anyopaque = tables[0..table_len]; - const left_write: *[table_len]?*anyopaque = tables[table_len .. 2 * table_len]; - const right_write: *[table_len]?*anyopaque = tables[2 * table_len .. 3 * table_len]; + const read_table = tables[0..table_len]; + const write_tables = .{ tables[table_len .. 2 * table_len], tables[2 * table_len .. 3 * table_len] }; self.* = .{ .pak = try GamePak.init(allocator, cpu, paths.rom, paths.save), @@ -78,18 +77,15 @@ pub fn init(self: *Self, allocator: Allocator, sched: *Scheduler, cpu: *Arm7tdmi .sched = sched, .read_table = read_table, - .write_tables = .{ left_write, right_write }, + .write_tables = write_tables, .allocator = allocator, }; - // read_table, write_tables, and *Self are not restricted to the lifetime - // of this init function so we can initialize our tables here - fillReadTable(self, read_table); + self.fillReadTable(read_table); - // Internal Display Memory behavious unusually on 8-bit reads - // so we have two different tables depending on whether there's an 8-bit read or not - fillWriteTable(u32, self, left_write); - fillWriteTable(u8, self, right_write); + // Internal Display Memory behaves differently on 8-bit reads + self.fillWriteTable(u32, write_tables[0]); + self.fillWriteTable(u8, write_tables[1]); } pub fn deinit(self: *Self) void { @@ -106,50 +102,50 @@ pub fn deinit(self: *Self) void { self.* = undefined; } -fn fillReadTable(bus: *Self, table: *[table_len]?*const anyopaque) void { +fn fillReadTable(self: *Self, table: *[table_len]?*const anyopaque) void { const vramMirror = @import("ppu/Vram.zig").mirror; for (table) |*ptr, i| { - const addr = page_size * i; + const addr = @intCast(u32, page_size * i); ptr.* = switch (addr) { // General Internal Memory 0x0000_0000...0x0000_3FFF => null, // BIOS has it's own checks - 0x0200_0000...0x02FF_FFFF => &bus.ewram.buf[addr & 0x3FFFF], - 0x0300_0000...0x03FF_FFFF => &bus.iwram.buf[addr & 0x7FFF], + 0x0200_0000...0x02FF_FFFF => &self.ewram.buf[addr & 0x3FFFF], + 0x0300_0000...0x03FF_FFFF => &self.iwram.buf[addr & 0x7FFF], 0x0400_0000...0x0400_03FF => null, // I/O // Internal Display Memory - 0x0500_0000...0x05FF_FFFF => &bus.ppu.palette.buf[addr & 0x3FF], - 0x0600_0000...0x06FF_FFFF => &bus.ppu.vram.buf[vramMirror(addr)], - 0x0700_0000...0x07FF_FFFF => &bus.ppu.oam.buf[addr & 0x3FF], + 0x0500_0000...0x05FF_FFFF => &self.ppu.palette.buf[addr & 0x3FF], + 0x0600_0000...0x06FF_FFFF => &self.ppu.vram.buf[vramMirror(addr)], + 0x0700_0000...0x07FF_FFFF => &self.ppu.oam.buf[addr & 0x3FF], // External Memory (Game Pak) - 0x0800_0000...0x0DFF_FFFF => fillTableExternalMemory(bus, addr), + 0x0800_0000...0x0DFF_FFFF => self.fillReadTableExternal(addr), 0x0E00_0000...0x0FFF_FFFF => null, // SRAM else => null, }; } } -fn fillWriteTable(comptime T: type, bus: *Self, table: *[table_len]?*const anyopaque) void { +fn fillWriteTable(self: *Self, comptime T: type, table: *[table_len]?*const anyopaque) void { comptime std.debug.assert(T == u32 or T == u16 or T == u8); const vramMirror = @import("ppu/Vram.zig").mirror; for (table) |*ptr, i| { - const addr = page_size * i; + const addr = @intCast(u32, page_size * i); ptr.* = switch (addr) { // General Internal Memory 0x0000_0000...0x0000_3FFF => null, // BIOS has it's own checks - 0x0200_0000...0x02FF_FFFF => &bus.ewram.buf[addr & 0x3FFFF], - 0x0300_0000...0x03FF_FFFF => &bus.iwram.buf[addr & 0x7FFF], + 0x0200_0000...0x02FF_FFFF => &self.ewram.buf[addr & 0x3FFFF], + 0x0300_0000...0x03FF_FFFF => &self.iwram.buf[addr & 0x7FFF], 0x0400_0000...0x0400_03FF => null, // I/O // Internal Display Memory - 0x0500_0000...0x05FF_FFFF => if (T != u8) &bus.ppu.palette.buf[addr & 0x3FF] else null, - 0x0600_0000...0x06FF_FFFF => if (T != u8) &bus.ppu.vram.buf[vramMirror(addr)] else null, - 0x0700_0000...0x07FF_FFFF => if (T != u8) &bus.ppu.oam.buf[addr & 0x3FF] else null, + 0x0500_0000...0x05FF_FFFF => if (T != u8) &self.ppu.palette.buf[addr & 0x3FF] else null, + 0x0600_0000...0x06FF_FFFF => if (T != u8) &self.ppu.vram.buf[vramMirror(addr)] else null, + 0x0700_0000...0x07FF_FFFF => if (T != u8) &self.ppu.oam.buf[addr & 0x3FF] else null, // External Memory (Game Pak) 0x0800_0000...0x0DFF_FFFF => null, // ROM @@ -159,24 +155,29 @@ fn fillWriteTable(comptime T: type, bus: *Self, table: *[table_len]?*const anyop } } -fn fillTableExternalMemory(bus: *Self, addr: usize) ?*anyopaque { +fn fillReadTableExternal(self: *Self, addr: u32) ?*anyopaque { // see `GamePak.zig` for more information about what conditions need to be true // so that a simple pointer dereference isn't possible + std.debug.assert(addr & @as(u32, page_size - 1) == 0); // addr is guaranteed to be page-aligned + const start_addr = addr; - const end_addr = addr + page_size; + const end_addr = start_addr + page_size; - const gpio_data = start_addr <= 0x0800_00C4 and 0x0800_00C4 < end_addr; - const gpio_direction = start_addr <= 0x0800_00C6 and 0x0800_00C6 < end_addr; - const gpio_control = start_addr <= 0x0800_00C8 and 0x0800_00C8 < end_addr; + { + const data = start_addr <= 0x0800_00C4 and 0x0800_00C4 < end_addr; // GPIO Data + const direction = start_addr <= 0x0800_00C6 and 0x0800_00C6 < end_addr; // GPIO Direction + const control = start_addr <= 0x0800_00C8 and 0x0800_00C8 < end_addr; // GPIO Control - if (bus.pak.gpio.device.kind != .None and (gpio_data or gpio_direction or gpio_control)) { - // We found a GPIO device, and this page a GPIO register. We want to handle this in slowmem - return null; + const has_gpio = data or direction or control; + const gpio_kind = self.pak.gpio.device.kind; + + // There is a GPIO Device, and the current page contains at least one memory-mapped GPIO register + if (gpio_kind != .None and has_gpio) return null; } - if (bus.pak.backup.kind == .Eeprom) { - if (bus.pak.buf.len > 0x100_000) { + if (self.pak.backup.kind == .Eeprom) { + if (self.pak.buf.len > 0x100_000) { // We are using a "large" EEPROM which means that if the below check is true // this page has an address that's reserved for the EEPROM and therefore must // be handled in slowmem @@ -192,9 +193,9 @@ fn fillTableExternalMemory(bus: *Self, addr: usize) ?*anyopaque { // Finally, the GamePak has some unique behaviour for reads past the end of the ROM, // so those will be handled by slowmem as well const masked_addr = addr & 0x1FF_FFFF; - if (masked_addr >= bus.pak.buf.len) return null; + if (masked_addr >= self.pak.buf.len) return null; - return &bus.pak.buf[masked_addr]; + return &self.pak.buf[masked_addr]; } pub fn dbgRead(self: *const Self, comptime T: type, unaligned_address: u32) T { @@ -208,8 +209,7 @@ pub fn dbgRead(self: *const Self, comptime T: type, unaligned_address: u32) T { if (self.read_table[page]) |some_ptr| { // We have a pointer to a page, cast the pointer to it's underlying type const Ptr = [*]const T; - const alignment = @alignOf(std.meta.Child(Ptr)); - const ptr = @ptrCast(Ptr, @alignCast(alignment, some_ptr)); + const ptr = @ptrCast(Ptr, @alignCast(@alignOf(std.meta.Child(Ptr)), some_ptr)); // Note: We don't check array length, since we force align the // lower bits of the address as the GBA would @@ -326,8 +326,7 @@ pub fn read(self: *Self, comptime T: type, unaligned_address: u32) T { if (self.read_table[page]) |some_ptr| { // We have a pointer to a page, cast the pointer to it's underlying type const Ptr = [*]const T; - const alignment = @alignOf(std.meta.Child(Ptr)); - const ptr = @ptrCast(Ptr, @alignCast(alignment, some_ptr)); + const ptr = @ptrCast(Ptr, @alignCast(@alignOf(std.meta.Child(Ptr)), some_ptr)); // Note: We don't check array length, since we force align the // lower bits of the address as the GBA would @@ -394,8 +393,7 @@ pub fn write(self: *Self, comptime T: type, unaligned_address: u32, value: T) vo if (self.write_tables[@boolToInt(T == u8)][page]) |some_ptr| { // We have a pointer to a page, cast the pointer to it's underlying type const Ptr = [*]T; - const alignment = @alignOf(std.meta.Child(Ptr)); - const ptr = @ptrCast(Ptr, @alignCast(alignment, some_ptr)); + const ptr = @ptrCast(Ptr, @alignCast(@alignOf(std.meta.Child(Ptr)), some_ptr)); // Note: We don't check array length, since we force align the // lower bits of the address as the GBA would @@ -408,8 +406,9 @@ pub fn write(self: *Self, comptime T: type, unaligned_address: u32, value: T) vo } } -pub fn slowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void { - // @setCold(true); +fn slowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void { + @setCold(true); + const page = @truncate(u8, unaligned_address >> 24); const address = forceAlign(T, unaligned_address); diff --git a/src/core/cpu/arm/data_processing.zig b/src/core/cpu/arm/data_processing.zig index 7bc3f44..2e92b38 100644 --- a/src/core/cpu/arm/data_processing.zig +++ b/src/core/cpu/arm/data_processing.zig @@ -24,7 +24,7 @@ pub fn dataProcessing(comptime I: bool, comptime S: bool, comptime kind: u4) Ins if (!I and opcode >> 4 & 1 == 1) cpu.r[15] -= 4; var result: u32 = undefined; - var overflow: bool = undefined; + var overflow: u1 = undefined; // Perform Data Processing Logic switch (kind) { @@ -62,7 +62,9 @@ pub fn dataProcessing(comptime I: bool, comptime S: bool, comptime kind: u4) Ins if (rd == 0xF) return undefinedTestBehaviour(cpu); - overflow = @addWithOverflow(u32, op1, op2, &result); + const tmp = @addWithOverflow(op1, op2); + result = tmp[0]; + overflow = tmp[1]; }, 0xC => result = op1 | op2, // ORR 0xD => result = op2, // MOV @@ -110,7 +112,7 @@ pub fn dataProcessing(comptime I: bool, comptime S: bool, comptime kind: u4) Ins // ADD, ADC Flags cpu.cpsr.n.write(result >> 31 & 1 == 1); cpu.cpsr.z.write(result == 0); - cpu.cpsr.c.write(overflow); + cpu.cpsr.c.write(overflow == 0b1); cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); }, 0x6, 0x7 => if (S and rd != 0xF) { @@ -141,7 +143,7 @@ pub fn dataProcessing(comptime I: bool, comptime S: bool, comptime kind: u4) Ins cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); } else if (kind == 0xB) { // CMN specific - cpu.cpsr.c.write(overflow); + cpu.cpsr.c.write(overflow == 0b1); cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); } else { // TST, TEQ specific @@ -162,19 +164,19 @@ pub fn sbc(left: u32, right: u32, old_carry: u1) u32 { return ret; } -pub fn add(overflow: *bool, left: u32, right: u32) u32 { - var ret: u32 = undefined; - overflow.* = @addWithOverflow(u32, left, right, &ret); - return ret; +pub fn add(overflow: *u1, left: u32, right: u32) u32 { + const ret = @addWithOverflow(left, right); + overflow.* = ret[1]; + + return ret[0]; } -pub fn adc(overflow: *bool, left: u32, right: u32, old_carry: u1) u32 { - var ret: u32 = undefined; - const first = @addWithOverflow(u32, left, right, &ret); - const second = @addWithOverflow(u32, ret, old_carry, &ret); +pub fn adc(overflow: *u1, left: u32, right: u32, old_carry: u1) u32 { + const tmp = @addWithOverflow(left, right); + const ret = @addWithOverflow(tmp[0], old_carry); + overflow.* = tmp[1] | ret[1]; - overflow.* = first or second; - return ret; + return ret[0]; } fn undefinedTestBehaviour(cpu: *Arm7tdmi) void { diff --git a/src/core/cpu/thumb/alu.zig b/src/core/cpu/thumb/alu.zig index 778da99..a8038c8 100644 --- a/src/core/cpu/thumb/alu.zig +++ b/src/core/cpu/thumb/alu.zig @@ -21,7 +21,8 @@ pub fn fmt4(comptime op: u4) InstrFn { const op2 = cpu.r[rs]; var result: u32 = undefined; - var overflow: bool = undefined; + var overflow: u1 = undefined; + switch (op) { 0x0 => result = op1 & op2, // AND 0x1 => result = op1 ^ op2, // EOR @@ -34,7 +35,12 @@ pub fn fmt4(comptime op: u4) InstrFn { 0x8 => result = op1 & op2, // TST 0x9 => result = 0 -% op2, // NEG 0xA => result = op1 -% op2, // CMP - 0xB => overflow = @addWithOverflow(u32, op1, op2, &result), // CMN + 0xB => { + // CMN + const tmp = @addWithOverflow(op1, op2); + result = tmp[0]; + overflow = tmp[1]; + }, 0xC => result = op1 | op2, // ORR 0xD => result = @truncate(u32, @as(u64, op2) * @as(u64, op1)), 0xE => result = op1 & ~op2, @@ -71,7 +77,7 @@ pub fn fmt4(comptime op: u4) InstrFn { // ADC, CMN cpu.cpsr.n.write(result >> 31 & 1 == 1); cpu.cpsr.z.write(result == 0); - cpu.cpsr.c.write(overflow); + cpu.cpsr.c.write(overflow == 0b1); cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); }, 0x6 => { diff --git a/src/core/cpu/thumb/data_processing.zig b/src/core/cpu/thumb/data_processing.zig index b9c3337..a8a97e1 100644 --- a/src/core/cpu/thumb/data_processing.zig +++ b/src/core/cpu/thumb/data_processing.zig @@ -64,7 +64,7 @@ pub fn fmt5(comptime op: u2, comptime h1: u1, comptime h2: u1) InstrFn { const op2 = cpu.r[rs]; var result: u32 = undefined; - var overflow: bool = undefined; + var overflow: u1 = undefined; switch (op) { 0b00 => result = add(&overflow, op1, op2), // ADD 0b01 => result = op1 -% op2, // CMP @@ -126,13 +126,13 @@ pub fn fmt2(comptime I: bool, is_sub: bool, rn: u3) InstrFn { cpu.cpsr.v.write(((op1 ^ result) & (~op2 ^ result)) >> 31 & 1 == 1); } else { // ADD - var overflow: bool = undefined; + var overflow: u1 = undefined; const result = add(&overflow, op1, op2); cpu.r[rd] = result; cpu.cpsr.n.write(result >> 31 & 1 == 1); cpu.cpsr.z.write(result == 0); - cpu.cpsr.c.write(overflow); + cpu.cpsr.c.write(overflow == 0b1); cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); } } @@ -145,7 +145,7 @@ pub fn fmt3(comptime op: u2, comptime rd: u3) InstrFn { const op1 = cpu.r[rd]; const op2: u32 = opcode & 0xFF; // Offset - var overflow: bool = undefined; + var overflow: u1 = undefined; const result: u32 = switch (op) { 0b00 => op2, // MOV 0b01 => op1 -% op2, // CMP @@ -169,7 +169,7 @@ pub fn fmt3(comptime op: u2, comptime rd: u3) InstrFn { }, 0b10 => { // ADD - cpu.cpsr.c.write(overflow); + cpu.cpsr.c.write(overflow == 0b1); cpu.cpsr.v.write(((op1 ^ result) & (op2 ^ result)) >> 31 & 1 == 1); }, } diff --git a/src/core/emu.zig b/src/core/emu.zig index d8d2375..cb74345 100644 --- a/src/core/emu.zig +++ b/src/core/emu.zig @@ -94,7 +94,7 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void { if (!cpu.stepDmaTransfer()) { if (cpu.isHalted()) { // Fast-forward to next Event - sched.tick = sched.queue.peek().?.tick; + sched.tick = sched.nextTimestamp(); } else { cpu.step(); } diff --git a/src/core/scheduler.zig b/src/core/scheduler.zig index e87dadc..0c86eda 100644 --- a/src/core/scheduler.zig +++ b/src/core/scheduler.zig @@ -31,61 +31,55 @@ pub const Scheduler = struct { } pub fn handleEvent(self: *Self, cpu: *Arm7tdmi) void { - if (self.queue.removeOrNull()) |event| { - const late = self.tick - event.tick; + const event = self.queue.remove(); + const late = self.tick - event.tick; - switch (event.kind) { - .HeatDeath => { - log.err("u64 overflow. This *actually* should never happen.", .{}); - unreachable; - }, - .Draw => { - // The end of a VDraw - cpu.bus.ppu.drawScanline(); - cpu.bus.ppu.onHdrawEnd(cpu, late); - }, - .TimerOverflow => |id| { - switch (id) { - inline 0...3 => |idx| cpu.bus.tim[idx].onTimerExpire(cpu, late), - } - }, - .ApuChannel => |id| { - switch (id) { - 0 => cpu.bus.apu.ch1.onToneSweepEvent(late), - 1 => cpu.bus.apu.ch2.onToneEvent(late), - 2 => cpu.bus.apu.ch3.onWaveEvent(late), - 3 => cpu.bus.apu.ch4.onNoiseEvent(late), - } - }, - .RealTimeClock => { - const device = &cpu.bus.pak.gpio.device; - if (device.kind != .Rtc or device.ptr == null) return; + switch (event.kind) { + .HeatDeath => { + log.err("u64 overflow. This *actually* should never happen.", .{}); + unreachable; + }, + .Draw => { + // The end of a VDraw + cpu.bus.ppu.drawScanline(); + cpu.bus.ppu.onHdrawEnd(cpu, late); + }, + .TimerOverflow => |id| { + switch (id) { + inline 0...3 => |idx| cpu.bus.tim[idx].onTimerExpire(cpu, late), + } + }, + .ApuChannel => |id| { + switch (id) { + 0 => cpu.bus.apu.ch1.onToneSweepEvent(late), + 1 => cpu.bus.apu.ch2.onToneEvent(late), + 2 => cpu.bus.apu.ch3.onWaveEvent(late), + 3 => cpu.bus.apu.ch4.onNoiseEvent(late), + } + }, + .RealTimeClock => { + const device = &cpu.bus.pak.gpio.device; + if (device.kind != .Rtc or device.ptr == null) return; - const clock = @ptrCast(*Clock, @alignCast(@alignOf(*Clock), device.ptr.?)); - clock.onClockUpdate(late); - }, - .FrameSequencer => cpu.bus.apu.onSequencerTick(late), - .SampleAudio => cpu.bus.apu.sampleAudio(late), - .HBlank => cpu.bus.ppu.onHblankEnd(cpu, late), // The end of a HBlank - .VBlank => cpu.bus.ppu.onHdrawEnd(cpu, late), // The end of a VBlank - } + const clock = @ptrCast(*Clock, @alignCast(@alignOf(*Clock), device.ptr.?)); + clock.onClockUpdate(late); + }, + .FrameSequencer => cpu.bus.apu.onSequencerTick(late), + .SampleAudio => cpu.bus.apu.sampleAudio(late), + .HBlank => cpu.bus.ppu.onHblankEnd(cpu, late), // The end of a HBlank + .VBlank => cpu.bus.ppu.onHdrawEnd(cpu, late), // The end of a VBlank } } /// Removes the **first** scheduled event of type `needle` pub fn removeScheduledEvent(self: *Self, needle: EventKind) void { - var it = self.queue.iterator(); - - var i: usize = 0; - while (it.next()) |event| : (i += 1) { + for (self.queue.items) |event, i| { if (std.meta.eql(event.kind, needle)) { - // This invalidates the iterator + // invalidates the slice we're iterating over _ = self.queue.removeIndex(i); - // Since removing something from the PQ invalidates the iterator, - // this implementation can safely only remove the first instance of - // a Scheduled Event. Exit Early + log.debug("Removed {?}@{}", .{ event.kind, event.tick }); break; } }