feat: upgrade to zig v0.15.1

note: emu crashes for unknown reason
This commit is contained in:
2025-10-12 18:10:12 -05:00
parent 6cacdc7180
commit bd02f625a5
22 changed files with 1083 additions and 5597 deletions

View File

@@ -13,13 +13,13 @@ const TimerTuple = @import("bus/timer.zig").TimerTuple;
const Scheduler = @import("scheduler.zig").Scheduler;
const FilePaths = @import("../util.zig").FilePaths;
const io = @import("bus/io.zig");
const _io = @import("bus/io.zig");
const Allocator = std.mem.Allocator;
const log = std.log.scoped(.Bus);
const createDmaTuple = @import("bus/dma.zig").create;
const createTimerTuple = @import("bus/timer.zig").create;
const rotr = @import("zba-util").rotr;
const rotr = @import("zba_util").rotr;
const timings: [2][0x10]u8 = [_][0x10]u8{
// BIOS, Unused, EWRAM, IWRAM, I/0, PALRAM, VRAM, OAM, ROM0, ROM0, ROM1, ROM1, ROM2, ROM2, SRAM, Unused
@@ -81,7 +81,7 @@ pub fn init(self: *Self, allocator: Allocator, sched: *Scheduler, cpu: *Arm7tdmi
.allocator = allocator,
};
self.fillReadTable(read_table);
self.fillReadTable(@ptrCast(read_table));
// Internal Display Memory behaves differently on 8-bit reads
self.fillWriteTable(u32, write_tables[0]);
@@ -135,12 +135,10 @@ pub fn replaceGamepak(self: *Self, file_path: []const u8) !void {
self.pak = try GamePak.init(self.allocator, self.cpu, file_path, save_path);
const read_ptr: *[table_len]?*const anyopaque = @constCast(self.read_table);
const write_ptrs: [2]*[table_len]?*anyopaque = .{ @constCast(self.write_tables[0]), @constCast(self.write_tables[1]) };
self.fillReadTable(read_ptr);
self.fillWriteTable(u32, write_ptrs[0]);
self.fillWriteTable(u8, write_ptrs[1]);
// SAFETY: TODO: why do we know this is safe?
self.fillReadTable(@ptrCast(@constCast(self.read_table)));
self.fillWriteTable(u32, @constCast(self.write_tables[0]));
self.fillWriteTable(u8, @constCast(self.write_tables[1]));
}
fn fillReadTable(self: *Self, table: *[table_len]?*const anyopaque) void {
@@ -169,7 +167,7 @@ fn fillReadTable(self: *Self, table: *[table_len]?*const anyopaque) void {
}
}
fn fillWriteTable(self: *Self, comptime T: type, table: *[table_len]?*const anyopaque) void {
fn fillWriteTable(self: *Self, comptime T: type, table: *[table_len]?*anyopaque) void {
comptime std.debug.assert(T == u32 or T == u16 or T == u8);
const vramMirror = @import("ppu/Vram.zig").mirror;
@@ -240,11 +238,11 @@ fn fillReadTableExternal(self: *Self, addr: u32) ?*anyopaque {
}
fn readIo(self: *const Self, comptime T: type, address: u32) T {
return io.read(self, T, address) orelse self.openBus(T, address);
return _io.read(self, T, address) orelse self.openBus(T, address);
}
fn openBus(self: *const Self, comptime T: type, address: u32) T {
@setCold(true);
@branchHint(.cold);
const r15 = self.cpu.r[15];
const word = blk: {
@@ -305,7 +303,7 @@ fn openBus(self: *const Self, comptime T: type, address: u32) T {
}
pub fn read(self: *Self, comptime T: type, unaligned_address: u32) T {
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).Int.bits;
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).int.bits;
const page = unaligned_address >> bits;
const offset = unaligned_address & (page_size - 1);
@@ -328,7 +326,7 @@ pub fn read(self: *Self, comptime T: type, unaligned_address: u32) T {
}
pub fn dbgRead(self: *const Self, comptime T: type, unaligned_address: u32) T {
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).Int.bits;
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).int.bits;
const page = unaligned_address >> bits;
const offset = unaligned_address & (page_size - 1);
@@ -348,7 +346,7 @@ pub fn dbgRead(self: *const Self, comptime T: type, unaligned_address: u32) T {
}
fn slowRead(self: *Self, comptime T: type, unaligned_address: u32) T {
@setCold(true);
@branchHint(.cold);
const page: u8 = @truncate(unaligned_address >> 24);
const address = forceAlign(T, unaligned_address);
@@ -419,7 +417,7 @@ fn readBackup(self: *const Self, comptime T: type, unaligned_address: u32) T {
}
pub fn write(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).Int.bits;
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).int.bits;
const page = unaligned_address >> bits;
const offset = unaligned_address & (page_size - 1);
@@ -446,7 +444,7 @@ pub fn write(self: *Self, comptime T: type, unaligned_address: u32, value: T) vo
/// Mostly Identical to `Bus.write`, slowmeme is handled by `Bus.dbgSlowWrite`
pub fn dbgWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).Int.bits;
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).int.bits;
const page = unaligned_address >> bits;
const offset = unaligned_address & (page_size - 1);
@@ -469,7 +467,7 @@ pub fn dbgWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T)
}
fn slowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
@setCold(true);
@branchHint(.cold);
const page: u8 = @truncate(unaligned_address >> 24);
const address = forceAlign(T, unaligned_address);
@@ -479,7 +477,7 @@ fn slowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) vo
0x00 => self.bios.write(T, address, value),
0x02 => unreachable, // completely handled by fastmem
0x03 => unreachable, // completely handled by fastmem
0x04 => io.write(self, T, address, value),
0x04 => _io.write(self, T, address, value),
// Internal Display Memory
0x05 => self.ppu.palette.write(T, address, value),
@@ -494,7 +492,7 @@ fn slowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) vo
}
fn dbgSlowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
@setCold(true);
@branchHint(.cold);
const page: u8 = @truncate(unaligned_address >> 24);
const address = forceAlign(T, unaligned_address);

View File

@@ -1,8 +1,9 @@
const std = @import("std");
const SDL = @import("sdl2");
const io = @import("bus/io.zig");
const util = @import("../util.zig");
const c = @import("../lib.zig").c;
const LinearFifo = @import("../lib/fifo.zig").LinearFifo;
const Arm7tdmi = @import("arm32").Arm7tdmi;
const Bus = @import("Bus.zig");
const Scheduler = @import("scheduler.zig").Scheduler;
@@ -11,7 +12,7 @@ const Tone = @import("apu/Tone.zig");
const Wave = @import("apu/Wave.zig");
const Noise = @import("apu/Noise.zig");
const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
const SoundFifo = LinearFifo(u8, .{ .static = 0x20 });
const getHalf = util.getHalf;
const setHalf = util.setHalf;
@@ -246,7 +247,7 @@ pub const Apu = struct {
sampling_cycle: u2,
stream: *SDL.SDL_AudioStream,
stream: *c.SDL_AudioStream,
sched: *Scheduler,
fs: FrameSequencer,
@@ -271,8 +272,8 @@ pub const Apu = struct {
.bias = .{ .raw = 0x0200 },
.sampling_cycle = 0b00,
.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, 1 << 15, host_format, 2, host_rate).?,
.sched = sched,
.stream = undefined, // FIXME: bad practice
.capacitor = 0,
.fs = FrameSequencer.init(),
@@ -459,21 +460,33 @@ pub const Apu = struct {
if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler();
_ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ ext_left, ext_right }, 2 * @sizeOf(u16));
const ret = c.SDL_PutAudioStreamData(self.stream, &[2]i16{ @bitCast(ext_left ^ 0x8000), @bitCast(ext_right ^ 0x8000) }, 2 * @sizeOf(i16));
if (!ret) @panic("TODO: Failed to put i16s into SDL Audio Queue");
}
fn replaceSDLResampler(self: *Self) void {
@setCold(true);
const sample_rate = Self.sampleRate(self.bias.sampling_cycle.read());
log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), sample_rate });
@branchHint(.cold);
_ = self;
// Sampling Cycle (Sample Rate) changed, Craete a new SDL Audio Resampler
// FIXME: Replace SDL's Audio Resampler with either a custom or more reliable one
const old_stream = self.stream;
defer SDL.SDL_FreeAudioStream(old_stream);
@panic("TODO: Implement Multiple Sample Rates...");
self.sampling_cycle = self.bias.sampling_cycle.read();
self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, @intCast(sample_rate), host_format, 2, host_rate).?;
// const sample_rate = Self.sampleRate(self.bias.sampling_cycle.read());
// log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), sample_rate });
// // Sampling Cycle (Sample Rate) changed, Craete a new SDL Audio Resampler
// // FIXME: Replace SDL's Audio Resampler with either a custom or more reliable one
// const old_stream = self.stream;
// defer c.SDL_DestroyAudioStream(old_stream);
// self.sampling_cycle = self.bias.sampling_cycle.read();
// var desired: c.SDL_AudioSpec = std.mem.zeroes(c.SDL_AudioSpec);
// desired.format = c.SDL_AUDIO_S16;
// desired.channels = 2;
// desired.freq = @intCast(sample_rate);
// const new_stream = c.SDL_OpenAudioDeviceStream(c.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired, null, null) orelse @panic("TODO: Failed to replace SDL Audio Stream");
// self.stream = new_stream;
}
fn interval(self: *const Self) u64 {
@@ -569,7 +582,7 @@ pub fn DmaSound(comptime kind: DmaSoundKind) type {
}
fn enable(self: *Self) void {
@setCold(true);
@branchHint(.cold);
self.enabled = true;
}

View File

@@ -3,7 +3,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const log = std.log.scoped(.Bios);
const rotr = @import("zba-util").rotr;
const rotr = @import("zba_util").rotr;
const forceAlign = @import("../Bus.zig").forceAlign;
/// Size of the BIOS in bytes
@@ -57,7 +57,7 @@ fn _read(self: *const Self, comptime T: type, addr: u32) T {
}
pub fn write(_: *Self, comptime T: type, addr: u32, value: T) void {
@setCold(true);
@branchHint(.cold);
log.debug("Tried to write {} 0x{X:} to 0x{X:0>8} ", .{ T, value, addr });
}

View File

@@ -13,7 +13,7 @@ const setHalf = util.setHalf;
const setQuart = util.setQuart;
const handleInterrupt = @import("../cpu_util.zig").handleInterrupt;
const rotr = @import("zba-util").rotr;
const rotr = @import("zba_util").rotr;
pub fn create() DmaTuple {
return .{ DmaController(0).init(), DmaController(1).init(), DmaController(2).init(), DmaController(3).init() };
@@ -286,17 +286,17 @@ fn DmaController(comptime id: u2) type {
if (self._word_count == 0) {
if (self.cnt.irq.read()) {
switch (id) {
0 => bus_ptr.io.irq.dma0.set(),
1 => bus_ptr.io.irq.dma1.set(),
2 => bus_ptr.io.irq.dma2.set(),
3 => bus_ptr.io.irq.dma3.set(),
0 => bus_ptr.io.irq.dma0.write(true),
1 => bus_ptr.io.irq.dma1.write(true),
2 => bus_ptr.io.irq.dma2.write(true),
3 => bus_ptr.io.irq.dma3.write(true),
}
handleInterrupt(cpu);
}
// If we're not repeating, Fire the IRQs and disable the DMA
if (!self.cnt.repeat.read()) self.cnt.enabled.unset();
if (!self.cnt.repeat.read()) self.cnt.enabled.write(false);
// We want to disable our internal enabled flag regardless of repeat
// because we only want to step A DMA that repeats during it's specific
@@ -338,7 +338,7 @@ fn DmaController(comptime id: u2) type {
// FIXME: Safe to just assume whatever DAD is set to is the FIFO Address?
// self.dad_latch = fifo_addr;
self.cnt.repeat.set();
self.cnt.repeat.write(true);
self._word_count = 4;
self.in_progress = true;
}

View File

@@ -1,5 +1,5 @@
const std = @import("std");
const Bit = @import("bitfield").Bit;
const Bit = @import("bitjuggle").Boolean;
const DateTime = @import("datetime").datetime.Datetime;
const Arm7tdmi = @import("arm32").Arm7tdmi;
@@ -408,7 +408,7 @@ pub const Clock = struct {
// TODO: Confirm that this is the right behaviour
log.debug("Force GamePak IRQ", .{});
bus_ptr.io.irq.game_pak.set();
bus_ptr.io.irq.game_pak.write(true);
handleInterrupt(self.cpu);
}

View File

@@ -5,8 +5,8 @@ const apu = @import("../apu.zig");
const ppu = @import("../ppu.zig");
const util = @import("../../util.zig");
const Bit = @import("bitfield").Bit;
const Bitfield = @import("bitfield").Bitfield;
const Bit = @import("bitjuggle").Boolean;
const Bitfield = @import("bitjuggle").Bitfield;
const Bus = @import("../Bus.zig");
const getHalf = util.getHalf;

View File

@@ -200,10 +200,10 @@ fn Timer(comptime id: u2) type {
if (self.cnt.irq.read()) {
switch (id) {
0 => io.irq.tim0.set(),
1 => io.irq.tim1.set(),
2 => io.irq.tim2.set(),
3 => io.irq.tim3.set(),
0 => io.irq.tim0.write(true),
1 => io.irq.tim1.write(true),
2 => io.irq.tim2.write(true),
3 => io.irq.tim3.write(true),
}
handleInterrupt(cpu);

View File

@@ -1,6 +1,6 @@
const std = @import("std");
const SDL = @import("sdl2");
const config = @import("../config.zig");
const c = @import("../lib.zig").c;
const Scheduler = @import("scheduler.zig").Scheduler;
const Arm7tdmi = @import("arm32").Arm7tdmi;
@@ -18,8 +18,8 @@ pub const Synchro = struct {
// FIXME: This Enum ends up being really LARGE!!!
pub const Message = union(enum) {
rom_path: [std.fs.MAX_PATH_BYTES]u8,
bios_path: [std.fs.MAX_PATH_BYTES]u8,
rom_path: [std.fs.max_path_bytes]u8,
bios_path: [std.fs.max_path_bytes]u8,
restart: void,
};
@@ -160,26 +160,35 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
}
}
fn audioSync(audio_sync: bool, stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void {
comptime std.debug.assert(@import("../platform.zig").sample_format == SDL.AUDIO_U16);
fn audioSync(audio_sync: bool, stream: *c.SDL_AudioStream, is_buffer_full: *bool) void {
// comptime std.debug.assert(@import("../platform.zig").sample_format == SDL.AUDIO_U16);
const sample_size = 2 * @sizeOf(u16);
const max_buf_size: c_int = 0x400;
// Determine whether the APU is busy right at this moment
var still_full: bool = SDL.SDL_AudioStreamAvailable(stream) > sample_size * if (is_buffer_full.*) max_buf_size >> 1 else max_buf_size;
defer is_buffer_full.* = still_full; // Update APU Busy status right before exiting scope
_ = audio_sync;
_ = stream;
_ = is_buffer_full;
// If Busy is false, there's no need to sync here
if (!still_full) return;
_ = sample_size;
_ = max_buf_size;
// TODO: Refactor!!!!
// while (SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1)
// std.atomic.spinLoopHint();
// TODO(paoda): re-enable
while (true) {
still_full = SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1;
if (!audio_sync or !still_full) break;
}
// // Determine whether the APU is busy right at this moment
// var still_full: bool = SDL.SDL_AudioStreamAvailable(stream) > sample_size * if (is_buffer_full.*) max_buf_size >> 1 else max_buf_size;
// defer is_buffer_full.* = still_full; // Update APU Busy status right before exiting scope
// // If Busy is false, there's no need to sync here
// if (!still_full) return;
// // TODO: Refactor!!!!
// // while (SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1)
// // std.atomic.spinLoopHint();
// while (true) {
// still_full = SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1;
// if (!audio_sync or !still_full) break;
// }
}
fn videoSync(timer: *Timer, wake_time: u64) u64 {
@@ -208,7 +217,7 @@ fn sleep(timer: *Timer, wake_time: u64) ?u64 {
const times = sleep_for / step;
for (0..times) |_| {
std.time.sleep(step);
std.Thread.sleep(step);
// Upon wakeup, check to see if this particular sleep was longer than expected
// if so we should exit early, but probably not skip a whole frame period
@@ -279,8 +288,8 @@ pub const EmuThing = struct {
return .{ .cpu = cpu, .scheduler = scheduler };
}
pub fn interface(self: *Self, allocator: Allocator) Interface {
return Interface.init(allocator, self);
pub fn interface(self: *Self) Interface {
return Interface.init(self);
}
pub fn read(self: *const Self, addr: u32) u8 {

View File

@@ -2,8 +2,8 @@ const std = @import("std");
const io = @import("bus/io.zig");
const util = @import("../util.zig");
const Bit = @import("bitfield").Bit;
const Bitfield = @import("bitfield").Bitfield;
const Bit = @import("bitjuggle").Boolean;
const Bitfield = @import("bitjuggle").Bitfield;
const dma = @import("bus/dma.zig");
const Oam = @import("ppu/Oam.zig");
@@ -1007,7 +1007,7 @@ pub const Ppu = struct {
// Transitioning to a Hblank
if (self.dispstat.hblank_irq.read()) {
bus_ptr.io.irq.hblank.set();
bus_ptr.io.irq.hblank.write(true);
handleInterrupt(cpu);
}
@@ -1015,7 +1015,7 @@ pub const Ppu = struct {
if (!self.dispstat.vblank.read())
dma.onBlanking(bus_ptr, .HBlank);
self.dispstat.hblank.set();
self.dispstat.hblank.write(true);
self.sched.push(.HBlank, 68 * 4 -| late);
}
@@ -1027,14 +1027,14 @@ pub const Ppu = struct {
const scanline = (old_scanline + 1) % 228;
self.vcount.scanline.write(scanline);
self.dispstat.hblank.unset();
self.dispstat.hblank.write(false);
// Perform Vc == VcT check
const coincidence = scanline == self.dispstat.vcount_trigger.read();
self.dispstat.coincidence.write(coincidence);
if (coincidence and self.dispstat.vcount_irq.read()) {
bus_ptr.io.irq.coincidence.set();
bus_ptr.io.irq.coincidence.write(true);
handleInterrupt(cpu);
}
@@ -1046,10 +1046,10 @@ pub const Ppu = struct {
if (scanline == 160) {
self.framebuf.swap(); // Swap FrameBuffers
self.dispstat.vblank.set();
self.dispstat.vblank.write(true);
if (self.dispstat.vblank_irq.read()) {
bus_ptr.io.irq.vblank.set();
bus_ptr.io.irq.vblank.write(true);
handleInterrupt(cpu);
}
@@ -1060,7 +1060,7 @@ pub const Ppu = struct {
dma.onBlanking(bus_ptr, .VBlank);
}
if (scanline == 227) self.dispstat.vblank.unset();
if (scanline == 227) self.dispstat.vblank.write(false);
self.sched.push(.VBlank, 240 * 4 -| late);
}
}