diff --git a/src/Bus.zig b/src/Bus.zig index 928f5b5..ede86ac 100644 --- a/src/Bus.zig +++ b/src/Bus.zig @@ -1,6 +1,7 @@ const std = @import("std"); const AudioDeviceId = @import("sdl2").SDL_AudioDeviceID; +const Arm7tdmi = @import("cpu.zig").Arm7tdmi; const Bios = @import("bus/Bios.zig"); const Ewram = @import("bus/Ewram.zig"); const GamePak = @import("bus/GamePak.zig"); @@ -18,7 +19,6 @@ const Allocator = std.mem.Allocator; const log = std.log.scoped(.Bus); const rotr = @import("util.zig").rotr; - const Self = @This(); const panic_on_und_bus: bool = false; @@ -33,19 +33,21 @@ iwram: Iwram, ewram: Ewram, io: Io, +cpu: ?*Arm7tdmi, sched: *Scheduler, -pub fn init(alloc: Allocator, sched: *Scheduler, dev: AudioDeviceId, paths: FilePaths) !Self { +pub fn init(alloc: Allocator, sched: *Scheduler, paths: FilePaths) !Self { return Self{ .pak = try GamePak.init(alloc, paths.rom, paths.save), .bios = try Bios.init(alloc, paths.bios), .ppu = try Ppu.init(alloc, sched), - .apu = Apu.init(dev), + .apu = Apu.init(), .iwram = try Iwram.init(alloc), .ewram = try Ewram.init(alloc), .dma = DmaControllers.init(), .tim = Timers.init(sched), .io = Io.init(), + .cpu = null, .sched = sched, }; } @@ -74,6 +76,29 @@ fn isDmaRunning(self: *const Self) bool { self.dma._3.active; } +pub fn debugRead(self: *const Self, comptime T: type, address: u32) T { + const cached = self.sched.tick; + defer self.sched.tick = cached; + + return self.read(T, address); +} + +fn readOpenBus(self: *const Self, comptime T: type, address: u32) T { + if (self.cpu.?.cpsr.t.read()) { + log.err("TODO: {} open bus read in THUMB", .{T}); + return 0; + } + + const word = self.debugRead(u32, self.cpu.?.r[15] + 4); + return @truncate(T, rotr(u32, word, 8 * (address & 3))); +} + +fn readBios(self: *const Self, comptime T: type, address: u32) T { + if (address < Bios.size) return self.bios.read(T, alignAddress(T, address)); + + return self.readOpenBus(T, address); +} + pub fn read(self: *const Self, comptime T: type, address: u32) T { const page = @truncate(u8, address >> 24); const align_addr = alignAddress(T, address); @@ -81,7 +106,7 @@ pub fn read(self: *const Self, comptime T: type, address: u32) T { return switch (page) { // General Internal Memory - 0x00 => self.bios.read(T, align_addr), + 0x00 => self.readBios(T, address), 0x02 => self.ewram.read(T, align_addr), 0x03 => self.iwram.read(T, align_addr), 0x04 => io.read(self, T, align_addr), @@ -105,7 +130,7 @@ pub fn read(self: *const Self, comptime T: type, address: u32) T { break :blk @as(T, value) * multiplier; }, - else => undRead("Tried to read {} from 0x{X:0>8}", .{ T, address }), + else => readOpenBus(self, T, address), }; } diff --git a/src/apu.zig b/src/apu.zig index 9b73d47..43bf872 100644 --- a/src/apu.zig +++ b/src/apu.zig @@ -24,9 +24,9 @@ pub const Apu = struct { dma_cnt: io.DmaSoundControl, cnt: io.SoundControl, - dev: AudioDeviceId, + dev: ?AudioDeviceId, - pub fn init(dev: AudioDeviceId) Self { + pub fn init() Self { return .{ .ch1 = ToneSweep.init(), .ch2 = Tone.init(), @@ -40,10 +40,14 @@ pub const Apu = struct { .cnt = .{ .raw = 0 }, .bias = .{ .raw = 0x0200 }, - .dev = dev, + .dev = null, }; } + pub fn attachAudioDevice(self: *Self, dev: AudioDeviceId) void { + self.dev = dev; + } + pub fn setDmaCnt(self: *Self, value: u16) void { const new: io.DmaSoundControl = .{ .raw = value }; @@ -83,7 +87,7 @@ pub const Apu = struct { }, }; - _ = SDL.SDL_QueueAudio(self.dev, &samples, 2); + if (self.dev) |dev| _ = SDL.SDL_QueueAudio(dev, &samples, 2); } }; diff --git a/src/bus/Bios.zig b/src/bus/Bios.zig index c14c23b..1e7ab5d 100644 --- a/src/bus/Bios.zig +++ b/src/bus/Bios.zig @@ -2,6 +2,9 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const log = std.log.scoped(.Bios); + +/// Size of the BIOS in bytes +pub const size = 0x4000; const Self = @This(); buf: ?[]u8, diff --git a/src/cpu.zig b/src/cpu.zig index 072fdf6..665b840 100644 --- a/src/cpu.zig +++ b/src/cpu.zig @@ -5,7 +5,9 @@ const Bus = @import("Bus.zig"); const Bit = @import("bitfield").Bit; const Bitfield = @import("bitfield").Bitfield; const Scheduler = @import("scheduler.zig").Scheduler; +const FilePaths = @import("util.zig").FilePaths; +const Allocator = std.mem.Allocator; const File = std.fs.File; // ARM Instruction Groups @@ -59,7 +61,7 @@ pub const Arm7tdmi = struct { r: [16]u32, sched: *Scheduler, - bus: *Bus, + bus: Bus, cpsr: PSR, spsr: PSR, @@ -77,11 +79,11 @@ pub const Arm7tdmi = struct { log_buf: [0x100]u8, binary_log: bool, - pub fn init(sched: *Scheduler, bus: *Bus) Self { - return .{ + pub fn init(alloc: Allocator, sched: *Scheduler, paths: FilePaths) !Self { + var cpu: Arm7tdmi = .{ .r = [_]u32{0x00} ** 16, .sched = sched, - .bus = bus, + .bus = try Bus.init(alloc, sched, paths), .cpsr = .{ .raw = 0x0000_001F }, .spsr = .{ .raw = 0x0000_0000 }, .banked_fiq = [_]u32{0x00} ** 10, @@ -91,6 +93,12 @@ pub const Arm7tdmi = struct { .log_buf = undefined, .binary_log = false, }; + cpu.bus.cpu = &cpu; + return cpu; + } + + pub fn deinit(self: Self) void { + self.bus.deinit(); } pub fn useLogger(self: *Self, file: *const File, is_binary: bool) void { @@ -250,13 +258,13 @@ pub const Arm7tdmi = struct { const opcode = self.thumbFetch(); if (enable_logging) if (self.log_file) |file| self.debug_log(file, opcode); - thumb_lut[thumbIdx(opcode)](self, self.bus, opcode); + thumb_lut[thumbIdx(opcode)](self, &self.bus, opcode); } else { const opcode = self.fetch(); if (enable_logging) if (self.log_file) |file| self.debug_log(file, opcode); if (checkCond(self.cpsr, @truncate(u4, opcode >> 28))) { - arm_lut[armIdx(opcode)](self, self.bus, opcode); + arm_lut[armIdx(opcode)](self, &self.bus, opcode); } } } diff --git a/src/emu.zig b/src/emu.zig index 087bed1..2a37730 100644 --- a/src/emu.zig +++ b/src/emu.zig @@ -32,42 +32,42 @@ const RunKind = enum { LimitedBusy, }; -pub fn run(kind: RunKind, quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void { +pub fn run(kind: RunKind, quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void { switch (kind) { - .Unlimited => runUnsync(quit, sched, cpu, bus), - .Limited => runSync(quit, sched, cpu, bus), - .UnlimitedFPS => runUnsyncFps(quit, fps, sched, cpu, bus), - .LimitedFPS => runSyncFps(quit, fps, sched, cpu, bus), - .LimitedBusy => runBusyLoop(quit, sched, cpu, bus), + .Unlimited => runUnsync(quit, sched, cpu), + .Limited => runSync(quit, sched, cpu), + .UnlimitedFPS => runUnsyncFps(quit, fps, sched, cpu), + .LimitedFPS => runSyncFps(quit, fps, sched, cpu), + .LimitedBusy => runBusyLoop(quit, sched, cpu), } } -pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void { +pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void { const frame_end = sched.tick + cycles_per_frame; while (sched.tick < frame_end) { - if (bus.io.haltcnt == .Halt) sched.tick += 1; - if (bus.io.haltcnt == .Execute) cpu.step(); - bus.handleDMATransfers(); + if (cpu.bus.io.haltcnt == .Halt) sched.tick += 1; + if (cpu.bus.io.haltcnt == .Execute) cpu.step(); + cpu.bus.handleDMATransfers(); while (sched.tick >= sched.nextTimestamp()) { - sched.handleEvent(cpu, bus); + sched.handleEvent(cpu); } } } -pub fn runUnsync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void { +pub fn runUnsync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void { log.info("Unsynchronized EmuThread has begun", .{}); - while (!quit.load(.Unordered)) runFrame(sched, cpu, bus); + while (!quit.load(.Unordered)) runFrame(sched, cpu); } -pub fn runSync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void { +pub fn runSync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void { log.info("Synchronized EmuThread has begun", .{}); var timer = Timer.start() catch unreachable; var wake_time: u64 = frame_period; while (!quit.load(.Unordered)) { - runFrame(sched, cpu, bus); + runFrame(sched, cpu); // Put the Thread to Sleep + Backup Spin Loop // This saves on resource usage when frame limiting @@ -78,24 +78,24 @@ pub fn runSync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus } } -pub fn runUnsyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void { +pub fn runUnsyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void { log.info("Unsynchronized EmuThread with FPS Tracking has begun", .{}); var fps_timer = Timer.start() catch unreachable; while (!quit.load(.Unordered)) { - runFrame(sched, cpu, bus); + runFrame(sched, cpu); fps.add(fps_timer.lap()); } } -pub fn runSyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void { +pub fn runSyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void { log.info("Synchronized EmuThread has begun", .{}); var timer = Timer.start() catch unreachable; var fps_timer = Timer.start() catch unreachable; var wake_time: u64 = frame_period; while (!quit.load(.Unordered)) { - runFrame(sched, cpu, bus); + runFrame(sched, cpu); // Put the Thread to Sleep + Backup Spin Loop // This saves on resource usage when frame limiting @@ -109,13 +109,13 @@ pub fn runSyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: } } -pub fn runBusyLoop(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void { +pub fn runBusyLoop(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void { log.info("Run EmuThread with spin-loop sync", .{}); var timer = Timer.start() catch unreachable; var wake_time: u64 = frame_period; while (!quit.load(.Unordered)) { - runFrame(sched, cpu, bus); + runFrame(sched, cpu); spinLoop(&timer, wake_time); // Update to the new wake time diff --git a/src/main.zig b/src/main.zig index 4f97c40..80fe9c6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -92,10 +92,10 @@ pub fn main() anyerror!void { defer scheduler.deinit(); const paths = .{ .bios = bios_path, .rom = rom_path, .save = save_path }; - var bus = try Bus.init(alloc, &scheduler, audio_dev, paths); - defer bus.deinit(); + var cpu = try Arm7tdmi.init(alloc, &scheduler, paths); + defer cpu.deinit(); - var cpu = Arm7tdmi.init(&scheduler, &bus); + cpu.bus.apu.attachAudioDevice(audio_dev); cpu.fastBoot(); const log_file: ?File = if (enable_logging) blk: { @@ -110,10 +110,10 @@ pub fn main() anyerror!void { var emu_rate = FpsAverage.init(); // Create Emulator Thread - const emu_thread = try Thread.spawn(.{}, emu.run, .{ .LimitedFPS, &quit, &emu_rate, &scheduler, &cpu, &bus }); + const emu_thread = try Thread.spawn(.{}, emu.run, .{ .LimitedFPS, &quit, &emu_rate, &scheduler, &cpu }); defer emu_thread.join(); - const title = correctTitle(bus.pak.title); + const title = correctTitle(cpu.bus.pak.title); var title_buf: [0x20]u8 = std.mem.zeroes([0x20]u8); const window_title = try std.fmt.bufPrint(&title_buf, "ZBA | {s}", .{title}); @@ -145,36 +145,38 @@ pub fn main() anyerror!void { switch (event.type) { SDL.SDL_QUIT => break :emu_loop, SDL.SDL_KEYDOWN => { + const io = &cpu.bus.io; const key_code = event.key.keysym.sym; switch (key_code) { - SDL.SDLK_UP => bus.io.keyinput.up.unset(), - SDL.SDLK_DOWN => bus.io.keyinput.down.unset(), - SDL.SDLK_LEFT => bus.io.keyinput.left.unset(), - SDL.SDLK_RIGHT => bus.io.keyinput.right.unset(), - SDL.SDLK_x => bus.io.keyinput.a.unset(), - SDL.SDLK_z => bus.io.keyinput.b.unset(), - SDL.SDLK_a => bus.io.keyinput.shoulder_l.unset(), - SDL.SDLK_s => bus.io.keyinput.shoulder_r.unset(), - SDL.SDLK_RETURN => bus.io.keyinput.start.unset(), - SDL.SDLK_RSHIFT => bus.io.keyinput.select.unset(), + SDL.SDLK_UP => io.keyinput.up.unset(), + SDL.SDLK_DOWN => io.keyinput.down.unset(), + SDL.SDLK_LEFT => io.keyinput.left.unset(), + SDL.SDLK_RIGHT => io.keyinput.right.unset(), + SDL.SDLK_x => io.keyinput.a.unset(), + SDL.SDLK_z => io.keyinput.b.unset(), + SDL.SDLK_a => io.keyinput.shoulder_l.unset(), + SDL.SDLK_s => io.keyinput.shoulder_r.unset(), + SDL.SDLK_RETURN => io.keyinput.start.unset(), + SDL.SDLK_RSHIFT => io.keyinput.select.unset(), else => {}, } }, SDL.SDL_KEYUP => { + const io = &cpu.bus.io; const key_code = event.key.keysym.sym; switch (key_code) { - SDL.SDLK_UP => bus.io.keyinput.up.set(), - SDL.SDLK_DOWN => bus.io.keyinput.down.set(), - SDL.SDLK_LEFT => bus.io.keyinput.left.set(), - SDL.SDLK_RIGHT => bus.io.keyinput.right.set(), - SDL.SDLK_x => bus.io.keyinput.a.set(), - SDL.SDLK_z => bus.io.keyinput.b.set(), - SDL.SDLK_a => bus.io.keyinput.shoulder_l.set(), - SDL.SDLK_s => bus.io.keyinput.shoulder_r.set(), - SDL.SDLK_RETURN => bus.io.keyinput.start.set(), - SDL.SDLK_RSHIFT => bus.io.keyinput.select.set(), + SDL.SDLK_UP => io.keyinput.up.set(), + SDL.SDLK_DOWN => io.keyinput.down.set(), + SDL.SDLK_LEFT => io.keyinput.left.set(), + SDL.SDLK_RIGHT => io.keyinput.right.set(), + SDL.SDLK_x => io.keyinput.a.set(), + SDL.SDLK_z => io.keyinput.b.set(), + SDL.SDLK_a => io.keyinput.shoulder_l.set(), + SDL.SDLK_s => io.keyinput.shoulder_r.set(), + SDL.SDLK_RETURN => io.keyinput.start.set(), + SDL.SDLK_RSHIFT => io.keyinput.select.set(), else => {}, } }, @@ -183,7 +185,7 @@ pub fn main() anyerror!void { } // FIXME: Is it OK just to copy the Emulator's Frame Buffer to SDL? - const buf_ptr = bus.ppu.framebuf.ptr; + const buf_ptr = cpu.bus.ppu.framebuf.ptr; _ = SDL.SDL_UpdateTexture(texture, null, buf_ptr, framebuf_pitch); _ = SDL.SDL_RenderCopy(renderer, texture, null, null); SDL.SDL_RenderPresent(renderer); diff --git a/src/ppu.zig b/src/ppu.zig index 94dabc5..54d1ab6 100644 --- a/src/ppu.zig +++ b/src/ppu.zig @@ -366,7 +366,7 @@ pub const Ppu = struct { } // See if HBlank DMA is present and not enabled - pollBlankingDma(cpu.bus, .HBlank); + pollBlankingDma(&cpu.bus, .HBlank); self.dispstat.hblank.set(); self.sched.push(.HBlank, self.sched.now() + (68 * 4) - late); @@ -403,7 +403,7 @@ pub const Ppu = struct { } // See if Vblank DMA is present and not enabled - pollBlankingDma(cpu.bus, .VBlank); + pollBlankingDma(&cpu.bus, .VBlank); } if (scanline == 227) self.dispstat.vblank.unset(); diff --git a/src/scheduler.zig b/src/scheduler.zig index a586fba..534aebc 100644 --- a/src/scheduler.zig +++ b/src/scheduler.zig @@ -29,7 +29,7 @@ pub const Scheduler = struct { return self.tick; } - pub fn handleEvent(self: *Self, cpu: *Arm7tdmi, bus: *Bus) void { + pub fn handleEvent(self: *Self, cpu: *Arm7tdmi) void { if (self.queue.removeOrNull()) |event| { const late = self.tick - event.tick; @@ -40,19 +40,19 @@ pub const Scheduler = struct { }, .Draw => { // The end of a VDraw - bus.ppu.drawScanline(); - bus.ppu.handleHDrawEnd(cpu, late); + cpu.bus.ppu.drawScanline(); + cpu.bus.ppu.handleHDrawEnd(cpu, late); }, .TimerOverflow => |id| { switch (id) { - 0 => bus.tim._0.handleOverflow(cpu, late), - 1 => bus.tim._1.handleOverflow(cpu, late), - 2 => bus.tim._2.handleOverflow(cpu, late), - 3 => bus.tim._3.handleOverflow(cpu, late), + 0 => cpu.bus.tim._0.handleOverflow(cpu, late), + 1 => cpu.bus.tim._1.handleOverflow(cpu, late), + 2 => cpu.bus.tim._2.handleOverflow(cpu, late), + 3 => cpu.bus.tim._3.handleOverflow(cpu, late), } }, - .HBlank => bus.ppu.handleHBlankEnd(cpu, late), // The end of a HBlank - .VBlank => bus.ppu.handleHDrawEnd(cpu, late), // The end of a VBlank + .HBlank => cpu.bus.ppu.handleHBlankEnd(cpu, late), // The end of a HBlank + .VBlank => cpu.bus.ppu.handleHDrawEnd(cpu, late), // The end of a VBlank } } }