Compare commits

..

No commits in common. "a590048204dfba58728114eab6947c04b54a81e9" and "dfe94fb9316be87ed57ab2a2c5702b8d0152e2e9" have entirely different histories.

12 changed files with 81 additions and 118 deletions

View File

@ -1,7 +1,6 @@
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");
@ -19,6 +18,7 @@ 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,21 +33,19 @@ iwram: Iwram,
ewram: Ewram,
io: Io,
cpu: ?*Arm7tdmi,
sched: *Scheduler,
pub fn init(alloc: Allocator, sched: *Scheduler, paths: FilePaths) !Self {
pub fn init(alloc: Allocator, sched: *Scheduler, dev: AudioDeviceId, 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(),
.apu = Apu.init(dev),
.iwram = try Iwram.init(alloc),
.ewram = try Ewram.init(alloc),
.dma = DmaControllers.init(),
.tim = Timers.init(sched),
.io = Io.init(),
.cpu = null,
.sched = sched,
};
}
@ -76,29 +74,6 @@ 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);
@ -106,7 +81,7 @@ pub fn read(self: *const Self, comptime T: type, address: u32) T {
return switch (page) {
// General Internal Memory
0x00 => self.readBios(T, address),
0x00 => self.bios.read(T, align_addr),
0x02 => self.ewram.read(T, align_addr),
0x03 => self.iwram.read(T, align_addr),
0x04 => io.read(self, T, align_addr),
@ -130,7 +105,7 @@ pub fn read(self: *const Self, comptime T: type, address: u32) T {
break :blk @as(T, value) * multiplier;
},
else => readOpenBus(self, T, address),
else => undRead("Tried to read {} from 0x{X:0>8}", .{ T, address }),
};
}

View File

@ -24,9 +24,9 @@ pub const Apu = struct {
dma_cnt: io.DmaSoundControl,
cnt: io.SoundControl,
dev: ?AudioDeviceId,
dev: AudioDeviceId,
pub fn init() Self {
pub fn init(dev: AudioDeviceId) Self {
return .{
.ch1 = ToneSweep.init(),
.ch2 = Tone.init(),
@ -40,14 +40,10 @@ pub const Apu = struct {
.cnt = .{ .raw = 0 },
.bias = .{ .raw = 0x0200 },
.dev = null,
.dev = dev,
};
}
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 };
@ -87,7 +83,7 @@ pub const Apu = struct {
},
};
if (self.dev) |dev| _ = SDL.SDL_QueueAudio(dev, &samples, 2);
_ = SDL.SDL_QueueAudio(self.dev, &samples, 2);
}
};

View File

@ -2,9 +2,6 @@ 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,

View File

@ -28,6 +28,7 @@ pub fn init(alloc: Allocator, rom_path: []const u8, save_path: ?[]const u8) !Sel
.backup = try Backup.init(alloc, kind, title, save_path),
};
pak.parseHeader();
log.info("Backup: {}", .{kind});
return pak;
}

View File

@ -27,8 +27,6 @@ pub const Backup = struct {
flash: Flash,
pub fn init(alloc: Allocator, kind: BackupKind, title: [12]u8, path: ?[]const u8) !Self {
log.info("Kind: {}", .{kind});
const buf_size: usize = switch (kind) {
.Sram => 0x8000, // 32K
.Flash => 0x10000, // 64K

View File

@ -5,9 +5,7 @@ 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
@ -61,7 +59,7 @@ pub const Arm7tdmi = struct {
r: [16]u32,
sched: *Scheduler,
bus: Bus,
bus: *Bus,
cpsr: PSR,
spsr: PSR,
@ -79,11 +77,11 @@ pub const Arm7tdmi = struct {
log_buf: [0x100]u8,
binary_log: bool,
pub fn init(alloc: Allocator, sched: *Scheduler, paths: FilePaths) !Self {
var cpu: Arm7tdmi = .{
pub fn init(sched: *Scheduler, bus: *Bus) Self {
return .{
.r = [_]u32{0x00} ** 16,
.sched = sched,
.bus = try Bus.init(alloc, sched, paths),
.bus = bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.banked_fiq = [_]u32{0x00} ** 10,
@ -93,12 +91,6 @@ 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 {
@ -258,13 +250,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);
}
}
}

View File

@ -47,11 +47,13 @@ pub fn halfAndSignedDataTransfer(comptime P: bool, comptime U: bool, comptime I:
},
0b11 => {
// LDRSH
result = if (address & 1 == 1) blk: {
const value = if (address & 1 == 1) blk: {
break :blk sext(8, bus.read(u8, address));
} else blk: {
break :blk sext(16, bus.read(u16, address));
};
result = rotr(u32, value, 8 * (address & 1));
},
0b00 => unreachable, // SWP
}

View File

@ -45,11 +45,13 @@ pub fn format78(comptime op: u2, comptime T: bool) InstrFn {
},
0b11 => {
// LDRSH
cpu.r[rd] = if (address & 1 == 1) blk: {
const value = if (address & 1 == 1) blk: {
break :blk sext(8, bus.read(u8, address));
} else blk: {
break :blk sext(16, bus.read(u16, address));
};
cpu.r[rd] = rotr(u32, value, 8 * (address & 1));
},
}
} else {

View File

@ -32,42 +32,42 @@ const RunKind = enum {
LimitedBusy,
};
pub fn run(kind: RunKind, quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void {
pub fn run(kind: RunKind, quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void {
switch (kind) {
.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),
.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),
}
}
pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void {
const frame_end = sched.tick + cycles_per_frame;
while (sched.tick < frame_end) {
if (cpu.bus.io.haltcnt == .Halt) sched.tick += 1;
if (cpu.bus.io.haltcnt == .Execute) cpu.step();
cpu.bus.handleDMATransfers();
if (bus.io.haltcnt == .Halt) sched.tick += 1;
if (bus.io.haltcnt == .Execute) cpu.step();
bus.handleDMATransfers();
while (sched.tick >= sched.nextTimestamp()) {
sched.handleEvent(cpu);
sched.handleEvent(cpu, bus);
}
}
}
pub fn runUnsync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void {
pub fn runUnsync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void {
log.info("Unsynchronized EmuThread has begun", .{});
while (!quit.load(.Unordered)) runFrame(sched, cpu);
while (!quit.load(.Unordered)) runFrame(sched, cpu, bus);
}
pub fn runSync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void {
pub fn runSync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) 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);
runFrame(sched, cpu, bus);
// 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) void {
}
}
pub fn runUnsyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void {
pub fn runUnsyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void {
log.info("Unsynchronized EmuThread with FPS Tracking has begun", .{});
var fps_timer = Timer.start() catch unreachable;
while (!quit.load(.Unordered)) {
runFrame(sched, cpu);
runFrame(sched, cpu, bus);
fps.add(fps_timer.lap());
}
}
pub fn runSyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void {
pub fn runSyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) 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);
runFrame(sched, cpu, bus);
// 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) void {
pub fn runBusyLoop(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) 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);
runFrame(sched, cpu, bus);
spinLoop(&timer, wake_time);
// Update to the new wake time

View File

@ -92,8 +92,10 @@ pub fn main() anyerror!void {
defer scheduler.deinit();
const paths = .{ .bios = bios_path, .rom = rom_path, .save = save_path };
var cpu = try Arm7tdmi.init(alloc, &scheduler, paths);
cpu.bus.apu.attachAudioDevice(audio_dev);
var bus = try Bus.init(alloc, &scheduler, audio_dev, paths);
defer bus.deinit();
var cpu = Arm7tdmi.init(&scheduler, &bus);
cpu.fastBoot();
const log_file: ?File = if (enable_logging) blk: {
@ -108,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 });
const emu_thread = try Thread.spawn(.{}, emu.run, .{ .LimitedFPS, &quit, &emu_rate, &scheduler, &cpu, &bus });
defer emu_thread.join();
const title = correctTitle(cpu.bus.pak.title);
const title = correctTitle(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});
@ -143,38 +145,36 @@ 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 => 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(),
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(),
else => {},
}
},
SDL.SDL_KEYUP => {
const io = &cpu.bus.io;
const key_code = event.key.keysym.sym;
switch (key_code) {
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(),
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(),
else => {},
}
},
@ -183,7 +183,7 @@ pub fn main() anyerror!void {
}
// FIXME: Is it OK just to copy the Emulator's Frame Buffer to SDL?
const buf_ptr = cpu.bus.ppu.framebuf.ptr;
const buf_ptr = bus.ppu.framebuf.ptr;
_ = SDL.SDL_UpdateTexture(texture, null, buf_ptr, framebuf_pitch);
_ = SDL.SDL_RenderCopy(renderer, texture, null, null);
SDL.SDL_RenderPresent(renderer);

View File

@ -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();

View File

@ -29,7 +29,7 @@ pub const Scheduler = struct {
return self.tick;
}
pub fn handleEvent(self: *Self, cpu: *Arm7tdmi) void {
pub fn handleEvent(self: *Self, cpu: *Arm7tdmi, bus: *Bus) 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
cpu.bus.ppu.drawScanline();
cpu.bus.ppu.handleHDrawEnd(cpu, late);
bus.ppu.drawScanline();
bus.ppu.handleHDrawEnd(cpu, late);
},
.TimerOverflow => |id| {
switch (id) {
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),
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),
}
},
.HBlank => cpu.bus.ppu.handleHBlankEnd(cpu, late), // The end of a HBlank
.VBlank => cpu.bus.ppu.handleHDrawEnd(cpu, late), // The end of a VBlank
.HBlank => bus.ppu.handleHBlankEnd(cpu, late), // The end of a HBlank
.VBlank => bus.ppu.handleHDrawEnd(cpu, late), // The end of a VBlank
}
}
}