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/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; } }