Compare commits

...

9 Commits

35 changed files with 819 additions and 731 deletions

193
src/Gui.zig Normal file
View File

@ -0,0 +1,193 @@
const std = @import("std");
const SDL = @import("sdl2");
const Self = @This();
const Apu = @import("core/apu.zig").Apu;
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
const Scheduler = @import("core/scheduler.zig").Scheduler;
const FpsTracker = @import("core/util.zig").FpsTracker;
const pitch = @import("core/ppu.zig").framebuf_pitch;
const emu = @import("core/emu.zig");
const asString = @import("core/util.zig").asString;
const log = std.log.scoped(.GUI);
const scale = 4;
const default_title: []const u8 = "ZBA";
window: *SDL.SDL_Window,
base_title: [12]u8,
renderer: *SDL.SDL_Renderer,
texture: *SDL.SDL_Texture,
audio: ?Audio,
pub fn init(title: [12]u8, width: i32, height: i32) Self {
const ret = SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_GAMECONTROLLER);
if (ret < 0) panic();
const window = SDL.SDL_CreateWindow(
default_title.ptr,
SDL.SDL_WINDOWPOS_CENTERED,
SDL.SDL_WINDOWPOS_CENTERED,
@as(c_int, width * scale),
@as(c_int, height * scale),
SDL.SDL_WINDOW_SHOWN,
) orelse panic();
const renderer = SDL.SDL_CreateRenderer(window, -1, SDL.SDL_RENDERER_ACCELERATED | SDL.SDL_RENDERER_PRESENTVSYNC) orelse panic();
const texture = SDL.SDL_CreateTexture(
renderer,
SDL.SDL_PIXELFORMAT_RGBA8888,
SDL.SDL_TEXTUREACCESS_STREAMING,
@as(c_int, width),
@as(c_int, height),
) orelse panic();
return Self{
.window = window,
.base_title = title,
.renderer = renderer,
.texture = texture,
.audio = null,
};
}
pub fn run(self: *Self, arm7tdmi: *Arm7tdmi, scheduler: *Scheduler) !void {
var quit = std.atomic.Atomic(bool).init(false);
var frame_rate = FpsTracker.init();
const thread = try std.Thread.spawn(.{}, emu.run, .{ &quit, &frame_rate, scheduler, arm7tdmi });
defer thread.join();
var title_buf: [0x100]u8 = [_]u8{0} ** 0x100;
emu_loop: while (true) {
var event: SDL.SDL_Event = undefined;
while (SDL.SDL_PollEvent(&event) != 0) {
switch (event.type) {
SDL.SDL_QUIT => break :emu_loop,
SDL.SDL_KEYDOWN => {
const io = &arm7tdmi.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(),
else => {},
}
},
SDL.SDL_KEYUP => {
const io = &arm7tdmi.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_i => log.err("Sample Count: {}", .{@intCast(u32, SDL.SDL_AudioStreamAvailable(arm7tdmi.bus.apu.stream)) / (2 * @sizeOf(u16))}),
SDL.SDLK_j => log.err("Scheduler Capacity: {} | Scheduler Event Count: {}", .{ scheduler.queue.capacity(), scheduler.queue.count() }),
SDL.SDLK_k => {
// Dump IWRAM to file
log.info("PC: 0x{X:0>8}", .{arm7tdmi.r[15]});
log.info("LR: 0x{X:0>8}", .{arm7tdmi.r[14]});
// const iwram_file = try std.fs.cwd().createFile("iwram.bin", .{});
// defer iwram_file.close();
// try iwram_file.writeAll(cpu.bus.iwram.buf);
},
else => {},
}
},
else => {},
}
}
// Emulator has an internal Double Buffer
const framebuf = arm7tdmi.bus.ppu.framebuf.get(.Renderer);
_ = SDL.SDL_UpdateTexture(self.texture, null, framebuf.ptr, pitch);
_ = SDL.SDL_RenderCopy(self.renderer, self.texture, null, null);
SDL.SDL_RenderPresent(self.renderer);
const title = std.fmt.bufPrint(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.base_title, frame_rate.value() }) catch unreachable;
SDL.SDL_SetWindowTitle(self.window, title.ptr);
}
quit.store(true, .SeqCst); // Terminate Emulator Thread
}
pub fn initAudio(self: *Self, apu: *Apu) void {
self.audio = Audio.init(apu);
self.audio.?.play();
}
pub fn deinit(self: Self) void {
if (self.audio) |aud| aud.deinit();
SDL.SDL_DestroyTexture(self.texture);
SDL.SDL_DestroyRenderer(self.renderer);
SDL.SDL_DestroyWindow(self.window);
SDL.SDL_Quit();
}
const Audio = struct {
const This = @This();
const sample_rate = @import("core/apu.zig").host_sample_rate;
device: SDL.SDL_AudioDeviceID,
fn init(apu: *Apu) This {
var have: SDL.SDL_AudioSpec = undefined;
var want: SDL.SDL_AudioSpec = std.mem.zeroes(SDL.SDL_AudioSpec);
want.freq = sample_rate;
want.format = SDL.AUDIO_U16;
want.channels = 2;
want.samples = 0x100;
want.callback = This.callback;
want.userdata = apu;
const device = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0);
if (device == 0) panic();
return .{
.device = device,
};
}
fn deinit(this: This) void {
SDL.SDL_CloseAudioDevice(this.device);
}
pub fn play(this: *This) void {
SDL.SDL_PauseAudioDevice(this.device, 0);
}
export fn callback(userdata: ?*anyopaque, stream: [*c]u8, len: c_int) void {
const apu = @ptrCast(*Apu, @alignCast(@alignOf(*Apu), userdata));
const written = SDL.SDL_AudioStreamGet(apu.stream, stream, len);
// If we don't write anything, play silence otherwise garbage will be played
// FIXME: I don't think this hack to remove DC Offset is acceptable :thinking:
if (written == 0) std.mem.set(u8, stream[0..@intCast(usize, len)], 0x40);
}
};
fn panic() noreturn {
const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error";
@panic(std.mem.sliceTo(str, 0));
}

View File

@ -6,62 +6,236 @@ const Bit = @import("bitfield").Bit;
const Bitfield = @import("bitfield").Bitfield; const Bitfield = @import("bitfield").Bitfield;
const Scheduler = @import("scheduler.zig").Scheduler; const Scheduler = @import("scheduler.zig").Scheduler;
const FilePaths = @import("util.zig").FilePaths; const FilePaths = @import("util.zig").FilePaths;
const Logger = @import("util.zig").Logger;
const Allocator = std.mem.Allocator;
const File = std.fs.File; const File = std.fs.File;
// ARM Instruction Groups // ARM Instructions
const dataProcessing = @import("cpu/arm/data_processing.zig").dataProcessing; pub const arm = struct {
const psrTransfer = @import("cpu/arm/psr_transfer.zig").psrTransfer; pub const InstrFn = fn (*Arm7tdmi, *Bus, u32) void;
const singleDataTransfer = @import("cpu/arm/single_data_transfer.zig").singleDataTransfer; const lut: [0x1000]InstrFn = populate();
const halfAndSignedDataTransfer = @import("cpu/arm/half_signed_data_transfer.zig").halfAndSignedDataTransfer;
const blockDataTransfer = @import("cpu/arm/block_data_transfer.zig").blockDataTransfer;
const branch = @import("cpu/arm/branch.zig").branch;
const branchAndExchange = @import("cpu/arm/branch.zig").branchAndExchange;
const armSoftwareInterrupt = @import("cpu/arm/software_interrupt.zig").armSoftwareInterrupt;
const singleDataSwap = @import("cpu/arm/single_data_swap.zig").singleDataSwap;
const multiply = @import("cpu/arm/multiply.zig").multiply; const processing = @import("cpu/arm/data_processing.zig").dataProcessing;
const multiplyLong = @import("cpu/arm/multiply.zig").multiplyLong; const psrTransfer = @import("cpu/arm/psr_transfer.zig").psrTransfer;
const transfer = @import("cpu/arm/single_data_transfer.zig").singleDataTransfer;
const halfSignedTransfer = @import("cpu/arm/half_signed_data_transfer.zig").halfAndSignedDataTransfer;
const blockTransfer = @import("cpu/arm/block_data_transfer.zig").blockDataTransfer;
const branch = @import("cpu/arm/branch.zig").branch;
const branchExchange = @import("cpu/arm/branch.zig").branchAndExchange;
const swi = @import("cpu/arm/software_interrupt.zig").armSoftwareInterrupt;
const swap = @import("cpu/arm/single_data_swap.zig").singleDataSwap;
// THUMB Instruction Groups const multiply = @import("cpu/arm/multiply.zig").multiply;
const format1 = @import("cpu/thumb/data_processing.zig").format1; const multiplyLong = @import("cpu/arm/multiply.zig").multiplyLong;
const format2 = @import("cpu/thumb/data_processing.zig").format2;
const format3 = @import("cpu/thumb/data_processing.zig").format3;
const format12 = @import("cpu/thumb/data_processing.zig").format12;
const format13 = @import("cpu/thumb/data_processing.zig").format13;
const format4 = @import("cpu/thumb/alu.zig").format4; // Undefined ARM Instruction handler
const format5 = @import("cpu/thumb/processing_branch.zig").format5; fn und(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
const id = armIdx(opcode);
cpu.panic("[CPU/Decode] ID: 0x{X:0>3} 0x{X:0>8} is an illegal opcode", .{ id, opcode });
}
const format6 = @import("cpu/thumb/data_transfer.zig").format6; fn populate() [0x1000]InstrFn {
const format78 = @import("cpu/thumb/data_transfer.zig").format78; return comptime {
const format9 = @import("cpu/thumb/data_transfer.zig").format9; @setEvalBranchQuota(0xE000);
const format10 = @import("cpu/thumb/data_transfer.zig").format10; var ret = [_]InstrFn{und} ** 0x1000;
const format11 = @import("cpu/thumb/data_transfer.zig").format11;
const format14 = @import("cpu/thumb/block_data_transfer.zig").format14;
const format15 = @import("cpu/thumb/block_data_transfer.zig").format15;
const format16 = @import("cpu/thumb/branch.zig").format16; var i: usize = 0;
const format18 = @import("cpu/thumb/branch.zig").format18; while (i < ret.len) : (i += 1) {
const format19 = @import("cpu/thumb/branch.zig").format19; ret[i] = switch (@as(u2, i >> 10)) {
0b00 => if (i == 0x121) blk: {
break :blk branchExchange;
} else if (i & 0xFCF == 0x009) blk: {
const A = i >> 5 & 1 == 1;
const S = i >> 4 & 1 == 1;
break :blk multiply(A, S);
} else if (i & 0xFBF == 0x109) blk: {
const B = i >> 6 & 1 == 1;
break :blk swap(B);
} else if (i & 0xF8F == 0x089) blk: {
const U = i >> 6 & 1 == 1;
const A = i >> 5 & 1 == 1;
const S = i >> 4 & 1 == 1;
break :blk multiplyLong(U, A, S);
} else if (i & 0xE49 == 0x009 or i & 0xE49 == 0x049) blk: {
const P = i >> 8 & 1 == 1;
const U = i >> 7 & 1 == 1;
const I = i >> 6 & 1 == 1;
const W = i >> 5 & 1 == 1;
const L = i >> 4 & 1 == 1;
break :blk halfSignedTransfer(P, U, I, W, L);
} else if (i & 0xD90 == 0x100) blk: {
const I = i >> 9 & 1 == 1;
const R = i >> 6 & 1 == 1;
const kind = i >> 4 & 0x3;
break :blk psrTransfer(I, R, kind);
} else blk: {
const I = i >> 9 & 1 == 1;
const S = i >> 4 & 1 == 1;
const instrKind = i >> 5 & 0xF;
break :blk processing(I, S, instrKind);
},
0b01 => if (i >> 9 & 1 == 1 and i & 1 == 1) und else blk: {
const I = i >> 9 & 1 == 1;
const P = i >> 8 & 1 == 1;
const U = i >> 7 & 1 == 1;
const B = i >> 6 & 1 == 1;
const W = i >> 5 & 1 == 1;
const L = i >> 4 & 1 == 1;
break :blk transfer(I, P, U, B, W, L);
},
else => switch (@as(u2, i >> 9 & 0x3)) {
// MSB is guaranteed to be 1
0b00 => blk: {
const P = i >> 8 & 1 == 1;
const U = i >> 7 & 1 == 1;
const S = i >> 6 & 1 == 1;
const W = i >> 5 & 1 == 1;
const L = i >> 4 & 1 == 1;
break :blk blockTransfer(P, U, S, W, L);
},
0b01 => blk: {
const L = i >> 8 & 1 == 1;
break :blk branch(L);
},
0b10 => und, // COP Data Transfer
0b11 => if (i >> 8 & 1 == 1) swi() else und, // COP Data Operation + Register Transfer
},
};
}
const thumbSoftwareInterrupt = @import("cpu/thumb/software_interrupt.zig").thumbSoftwareInterrupt; return ret;
};
}
};
pub const ArmInstrFn = fn (*Arm7tdmi, *Bus, u32) void; // THUMB Instructions
pub const ThumbInstrFn = fn (*Arm7tdmi, *Bus, u16) void; pub const thumb = struct {
const arm_lut: [0x1000]ArmInstrFn = armPopulate(); pub const InstrFn = fn (*Arm7tdmi, *Bus, u16) void;
const thumb_lut: [0x400]ThumbInstrFn = thumbPopulate(); const lut: [0x400]InstrFn = populate();
const enable_logging = @import("main.zig").enable_logging; const processing = @import("cpu/thumb/data_processing.zig");
const alu = @import("cpu/thumb/alu.zig").fmt4;
const transfer = @import("cpu/thumb/data_transfer.zig");
const block_transfer = @import("cpu/thumb/block_data_transfer.zig");
const swi = @import("cpu/thumb/software_interrupt.zig").fmt17;
const branch = @import("cpu/thumb/branch.zig");
/// Undefined THUMB Instruction Handler
fn und(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const id = thumbIdx(opcode);
cpu.panic("[CPU/Decode] ID: 0b{b:0>10} 0x{X:0>2} is an illegal opcode", .{ id, opcode });
}
fn populate() [0x400]InstrFn {
return comptime {
@setEvalBranchQuota(5025); // This is exact
var ret = [_]InstrFn{und} ** 0x400;
var i: usize = 0;
while (i < ret.len) : (i += 1) {
ret[i] = switch (@as(u3, i >> 7 & 0x7)) {
0b000 => if (i >> 5 & 0x3 == 0b11) blk: {
const I = i >> 4 & 1 == 1;
const is_sub = i >> 3 & 1 == 1;
const rn = i & 0x7;
break :blk processing.fmt2(I, is_sub, rn);
} else blk: {
const op = i >> 5 & 0x3;
const offset = i & 0x1F;
break :blk processing.fmt1(op, offset);
},
0b001 => blk: {
const op = i >> 5 & 0x3;
const rd = i >> 2 & 0x7;
break :blk processing.fmt3(op, rd);
},
0b010 => switch (@as(u2, i >> 5 & 0x3)) {
0b00 => if (i >> 4 & 1 == 1) blk: {
const op = i >> 2 & 0x3;
const h1 = i >> 1 & 1;
const h2 = i & 1;
break :blk processing.fmt5(op, h1, h2);
} else blk: {
const op = i & 0xF;
break :blk alu(op);
},
0b01 => blk: {
const rd = i >> 2 & 0x7;
break :blk transfer.fmt6(rd);
},
else => blk: {
const op = i >> 4 & 0x3;
const T = i >> 3 & 1 == 1;
break :blk transfer.fmt78(op, T);
},
},
0b011 => blk: {
const B = i >> 6 & 1 == 1;
const L = i >> 5 & 1 == 1;
const offset = i & 0x1F;
break :blk transfer.fmt9(B, L, offset);
},
else => switch (@as(u3, i >> 6 & 0x7)) {
// MSB is guaranteed to be 1
0b000 => blk: {
const L = i >> 5 & 1 == 1;
const offset = i & 0x1F;
break :blk transfer.fmt10(L, offset);
},
0b001 => blk: {
const L = i >> 5 & 1 == 1;
const rd = i >> 2 & 0x7;
break :blk transfer.fmt11(L, rd);
},
0b010 => blk: {
const isSP = i >> 5 & 1 == 1;
const rd = i >> 2 & 0x7;
break :blk processing.fmt12(isSP, rd);
},
0b011 => if (i >> 4 & 1 == 1) blk: {
const L = i >> 5 & 1 == 1;
const R = i >> 2 & 1 == 1;
break :blk block_transfer.fmt14(L, R);
} else blk: {
const S = i >> 1 & 1 == 1;
break :blk processing.fmt13(S);
},
0b100 => blk: {
const L = i >> 5 & 1 == 1;
const rb = i >> 2 & 0x7;
break :blk block_transfer.fmt15(L, rb);
},
0b101 => if (i >> 2 & 0xF == 0b1111) blk: {
break :blk thumb.swi();
} else blk: {
const cond = i >> 2 & 0xF;
break :blk branch.fmt16(cond);
},
0b110 => branch.fmt18(),
0b111 => blk: {
const is_low = i >> 5 & 1 == 1;
break :blk branch.fmt19(is_low);
},
},
};
}
return ret;
};
}
};
const cpu_logging = @import("emu.zig").cpu_logging;
const log = std.log.scoped(.Arm7Tdmi); const log = std.log.scoped(.Arm7Tdmi);
pub const Arm7tdmi = struct { pub const Arm7tdmi = struct {
const Self = @This(); const Self = @This();
r: [16]u32, r: [16]u32,
pipe: Pipline,
sched: *Scheduler, sched: *Scheduler,
bus: Bus, bus: *Bus,
cpsr: PSR, cpsr: PSR,
spsr: PSR, spsr: PSR,
@ -75,33 +249,25 @@ pub const Arm7tdmi = struct {
banked_spsr: [5]PSR, banked_spsr: [5]PSR,
log_file: ?*const File, logger: ?Logger,
log_buf: [0x100]u8,
binary_log: bool,
pub fn init(alloc: Allocator, sched: *Scheduler, paths: FilePaths) !Self { pub fn init(sched: *Scheduler, bus: *Bus) Self {
return Self{ return Self{
.r = [_]u32{0x00} ** 16, .r = [_]u32{0x00} ** 16,
.pipe = Pipline.init(),
.sched = sched, .sched = sched,
.bus = try Bus.init(alloc, sched, paths), .bus = bus,
.cpsr = .{ .raw = 0x0000_001F }, .cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 }, .spsr = .{ .raw = 0x0000_0000 },
.banked_fiq = [_]u32{0x00} ** 10, .banked_fiq = [_]u32{0x00} ** 10,
.banked_r = [_]u32{0x00} ** 12, .banked_r = [_]u32{0x00} ** 12,
.banked_spsr = [_]PSR{.{ .raw = 0x0000_0000 }} ** 5, .banked_spsr = [_]PSR{.{ .raw = 0x0000_0000 }} ** 5,
.log_file = null, .logger = null,
.log_buf = undefined,
.binary_log = false,
}; };
} }
pub fn deinit(self: Self) void { pub fn attach(self: *Self, log_file: std.fs.File) void {
self.bus.deinit(); self.logger = Logger.init(log_file);
}
pub fn useLogger(self: *Self, file: *const File, is_binary: bool) void {
self.log_file = file;
self.binary_log = is_binary;
} }
inline fn bankedIdx(mode: Mode, kind: BankedKind) usize { inline fn bankedIdx(mode: Mode, kind: BankedKind) usize {
@ -152,8 +318,21 @@ pub const Arm7tdmi = struct {
return self.bus.io.haltcnt == .Halt; return self.bus.io.haltcnt == .Halt;
} }
pub fn setCpsrNoFlush(self: *Self, value: u32) void {
if (value & 0x1F != self.cpsr.raw & 0x1F) self.changeModeFromIdx(@truncate(u5, value & 0x1F));
self.cpsr.raw = value;
}
pub fn setCpsr(self: *Self, value: u32) void { pub fn setCpsr(self: *Self, value: u32) void {
if (value & 0x1F != self.cpsr.raw & 0x1F) self.changeModeFromIdx(@truncate(u5, value & 0x1F)); if (value & 0x1F != self.cpsr.raw & 0x1F) self.changeModeFromIdx(@truncate(u5, value & 0x1F));
const new: PSR = .{ .raw = value };
if (self.cpsr.t.read() != new.t.read()) {
// If THUMB to ARM or ARM to THUMB, flush pipeline
self.r[15] &= if (new.t.read()) ~@as(u32, 1) else ~@as(u32, 3);
self.pipe.flush();
}
self.cpsr.raw = value; self.cpsr.raw = value;
} }
@ -256,19 +435,22 @@ pub const Arm7tdmi = struct {
} }
pub fn step(self: *Self) void { pub fn step(self: *Self) void {
if (self.cpsr.t.read()) { if (self.cpsr.t.read()) blk: {
const opcode = self.fetch(u16); const opcode = @truncate(u16, self.pipe.step(self, u16) orelse break :blk);
if (enable_logging) if (self.log_file) |file| self.debug_log(file, opcode); if (cpu_logging) self.logger.?.mgbaLog(self, opcode);
thumb_lut[thumbIdx(opcode)](self, &self.bus, opcode); thumb.lut[thumbIdx(opcode)](self, self.bus, opcode);
} else { } else blk: {
const opcode = self.fetch(u32); const opcode = self.pipe.step(self, u32) orelse break :blk;
if (enable_logging) if (self.log_file) |file| self.debug_log(file, opcode); if (cpu_logging) self.logger.?.mgbaLog(self, opcode);
if (checkCond(self.cpsr, @truncate(u4, opcode >> 28))) { if (checkCond(self.cpsr, @truncate(u4, opcode >> 28))) {
arm_lut[armIdx(opcode)](self, &self.bus, opcode); arm.lut[armIdx(opcode)](self, self.bus, opcode);
} }
} }
if (!self.pipe.flushed) self.r[15] += if (self.cpsr.t.read()) 2 else @as(u32, 4);
self.pipe.flushed = false;
} }
pub fn stepDmaTransfer(self: *Self) bool { pub fn stepDmaTransfer(self: *Self) bool {
@ -303,27 +485,26 @@ pub const Arm7tdmi = struct {
pub fn handleInterrupt(self: *Self) void { pub fn handleInterrupt(self: *Self) void {
const should_handle = self.bus.io.ie.raw & self.bus.io.irq.raw; const should_handle = self.bus.io.ie.raw & self.bus.io.irq.raw;
if (should_handle != 0) { // Return if IME is disabled, CPSR I is set or there is nothing to handle
self.bus.io.haltcnt = .Execute; if (!self.bus.io.ime or self.cpsr.i.read() or should_handle == 0) return;
// log.debug("An Interrupt was Fired!", .{});
// Either IME is not true or I in CPSR is true // If pipeline isn't full, return but reschedule the handling of the event
// Don't handle interrupts if (!self.pipe.isFull()) return;
if (!self.bus.io.ime or self.cpsr.i.read()) return;
// log.debug("An interrupt was Handled!", .{});
// retAddr.gba says r15 on it's own is off by -04h in both ARM and THUMB mode // log.debug("Handling Interrupt!", .{});
const r15 = self.r[15] + 4; self.bus.io.haltcnt = .Execute;
const cpsr = self.cpsr.raw;
self.changeMode(.Irq); const ret_addr = self.r[15] - if (self.cpsr.t.read()) 2 else @as(u32, 4);
self.cpsr.t.write(false); const new_spsr = self.cpsr.raw;
self.cpsr.i.write(true);
self.r[14] = r15; self.changeMode(.Irq);
self.spsr.raw = cpsr; self.cpsr.t.write(false);
self.r[15] = 0x000_0018; self.cpsr.i.write(true);
}
self.r[14] = ret_addr;
self.spsr.raw = new_spsr;
self.r[15] = 0x0000_0018;
self.pipe.flush();
} }
inline fn fetch(self: *Self, comptime T: type) T { inline fn fetch(self: *Self, comptime T: type) T {
@ -337,10 +518,6 @@ pub const Arm7tdmi = struct {
return self.bus.read(T, self.r[15]); return self.bus.read(T, self.r[15]);
} }
pub fn fakePC(self: *const Self) u32 {
return self.r[15] + 4;
}
fn debug_log(self: *const Self, file: *const File, opcode: u32) void { fn debug_log(self: *const Self, file: *const File, opcode: u32) void {
if (self.binary_log) { if (self.binary_log) {
self.skyLog(file) catch unreachable; self.skyLog(file) catch unreachable;
@ -406,25 +583,6 @@ pub const Arm7tdmi = struct {
}; };
} }
fn skyLog(self: *const Self, file: *const File) !void {
var buf: [18 * @sizeOf(u32)]u8 = undefined;
// Write Registers
var i: usize = 0;
while (i < 0x10) : (i += 1) {
skyWrite(&buf, i, self.r[i]);
}
skyWrite(&buf, 0x10, self.cpsr.raw);
skyWrite(&buf, 0x11, if (self.hasSPSR()) self.spsr.raw else self.cpsr.raw);
_ = try file.writeAll(&buf);
}
fn skyWrite(buf: []u8, i: usize, num: u32) void {
const j = @sizeOf(u32) * i;
std.mem.writeIntSliceNative(u32, buf[j..(j + @sizeOf(u32))], num);
}
fn mgbaLog(self: *const Self, file: *const File, opcode: u32) !void { fn mgbaLog(self: *const Self, file: *const File, opcode: u32) !void {
const thumb_fmt = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | {X:0>4}:\n"; const thumb_fmt = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | {X:0>4}:\n";
const arm_fmt = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | {X:0>8}:\n"; const arm_fmt = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | {X:0>8}:\n";
@ -445,7 +603,7 @@ pub const Arm7tdmi = struct {
const r12 = self.r[12]; const r12 = self.r[12];
const r13 = self.r[13]; const r13 = self.r[13];
const r14 = self.r[14]; const r14 = self.r[14];
const r15 = self.r[15]; const r15 = self.r[15] -| if (self.cpsr.t.read()) 2 else @as(u32, 4);
const c_psr = self.cpsr.raw; const c_psr = self.cpsr.raw;
@ -453,7 +611,7 @@ pub const Arm7tdmi = struct {
if (self.cpsr.t.read()) { if (self.cpsr.t.read()) {
if (opcode >> 11 == 0x1E) { if (opcode >> 11 == 0x1E) {
// Instruction 1 of a BL Opcode, print in ARM mode // Instruction 1 of a BL Opcode, print in ARM mode
const other_half = self.bus.debugRead(u16, self.r[15]); const other_half = self.bus.debugRead(u16, self.r[15] - 2);
const bl_opcode = @as(u32, opcode) << 16 | other_half; const bl_opcode = @as(u32, opcode) << 16 | other_half;
log_str = try std.fmt.bufPrint(&buf, arm_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, bl_opcode }); log_str = try std.fmt.bufPrint(&buf, arm_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, bl_opcode });
@ -497,177 +655,47 @@ pub fn checkCond(cpsr: PSR, cond: u4) bool {
}; };
} }
fn thumbPopulate() [0x400]ThumbInstrFn { const Pipline = struct {
return comptime { const Self = @This();
@setEvalBranchQuota(5025); // This is exact stage: [2]?u32,
var lut = [_]ThumbInstrFn{thumbUndefined} ** 0x400; flushed: bool,
var i: usize = 0; fn init() Self {
while (i < lut.len) : (i += 1) { return .{
lut[i] = switch (@as(u3, i >> 7 & 0x7)) { .stage = [_]?u32{null} ** 2,
0b000 => if (i >> 5 & 0x3 == 0b11) blk: { .flushed = false,
const I = i >> 4 & 1 == 1; };
const is_sub = i >> 3 & 1 == 1; }
const rn = i & 0x7;
break :blk format2(I, is_sub, rn);
} else blk: {
const op = i >> 5 & 0x3;
const offset = i & 0x1F;
break :blk format1(op, offset);
},
0b001 => blk: {
const op = i >> 5 & 0x3;
const rd = i >> 2 & 0x7;
break :blk format3(op, rd);
},
0b010 => switch (@as(u2, i >> 5 & 0x3)) {
0b00 => if (i >> 4 & 1 == 1) blk: {
const op = i >> 2 & 0x3;
const h1 = i >> 1 & 1;
const h2 = i & 1;
break :blk format5(op, h1, h2);
} else blk: {
const op = i & 0xF;
break :blk format4(op);
},
0b01 => blk: {
const rd = i >> 2 & 0x7;
break :blk format6(rd);
},
else => blk: {
const op = i >> 4 & 0x3;
const T = i >> 3 & 1 == 1;
break :blk format78(op, T);
},
},
0b011 => blk: {
const B = i >> 6 & 1 == 1;
const L = i >> 5 & 1 == 1;
const offset = i & 0x1F;
break :blk format9(B, L, offset);
},
else => switch (@as(u3, i >> 6 & 0x7)) {
// MSB is guaranteed to be 1
0b000 => blk: {
const L = i >> 5 & 1 == 1;
const offset = i & 0x1F;
break :blk format10(L, offset);
},
0b001 => blk: {
const L = i >> 5 & 1 == 1;
const rd = i >> 2 & 0x7;
break :blk format11(L, rd);
},
0b010 => blk: {
const isSP = i >> 5 & 1 == 1;
const rd = i >> 2 & 0x7;
break :blk format12(isSP, rd);
},
0b011 => if (i >> 4 & 1 == 1) blk: {
const L = i >> 5 & 1 == 1;
const R = i >> 2 & 1 == 1;
break :blk format14(L, R);
} else blk: {
const S = i >> 1 & 1 == 1;
break :blk format13(S);
},
0b100 => blk: {
const L = i >> 5 & 1 == 1;
const rb = i >> 2 & 0x7;
break :blk format15(L, rb); pub fn flush(self: *Self) void {
}, for (self.stage) |*opcode| opcode.* = null;
0b101 => if (i >> 2 & 0xF == 0b1111) blk: { self.flushed = true;
break :blk thumbSoftwareInterrupt(); }
} else blk: {
const cond = i >> 2 & 0xF;
break :blk format16(cond);
},
0b110 => format18(),
0b111 => blk: {
const is_low = i >> 5 & 1 == 1;
break :blk format19(is_low);
},
},
};
}
return lut; pub fn isFull(self: *const Self) bool {
}; return self.stage[0] != null and self.stage[1] != null;
} }
fn armPopulate() [0x1000]ArmInstrFn { pub fn step(self: *Self, cpu: *Arm7tdmi, comptime T: type) ?u32 {
return comptime { comptime std.debug.assert(T == u32 or T == u16);
@setEvalBranchQuota(0xE000);
var lut = [_]ArmInstrFn{armUndefined} ** 0x1000;
var i: usize = 0; const opcode = self.stage[0];
while (i < lut.len) : (i += 1) {
lut[i] = switch (@as(u2, i >> 10)) {
0b00 => if (i == 0x121) blk: {
break :blk branchAndExchange;
} else if (i & 0xFCF == 0x009) blk: {
const A = i >> 5 & 1 == 1;
const S = i >> 4 & 1 == 1;
break :blk multiply(A, S);
} else if (i & 0xFBF == 0x109) blk: {
const B = i >> 6 & 1 == 1;
break :blk singleDataSwap(B);
} else if (i & 0xF8F == 0x089) blk: {
const U = i >> 6 & 1 == 1;
const A = i >> 5 & 1 == 1;
const S = i >> 4 & 1 == 1;
break :blk multiplyLong(U, A, S);
} else if (i & 0xE49 == 0x009 or i & 0xE49 == 0x049) blk: {
const P = i >> 8 & 1 == 1;
const U = i >> 7 & 1 == 1;
const I = i >> 6 & 1 == 1;
const W = i >> 5 & 1 == 1;
const L = i >> 4 & 1 == 1;
break :blk halfAndSignedDataTransfer(P, U, I, W, L);
} else if (i & 0xD90 == 0x100) blk: {
const I = i >> 9 & 1 == 1;
const R = i >> 6 & 1 == 1;
const kind = i >> 4 & 0x3;
break :blk psrTransfer(I, R, kind);
} else blk: {
const I = i >> 9 & 1 == 1;
const S = i >> 4 & 1 == 1;
const instrKind = i >> 5 & 0xF;
break :blk dataProcessing(I, S, instrKind);
},
0b01 => if (i >> 9 & 1 == 1 and i & 1 == 1) armUndefined else blk: {
const I = i >> 9 & 1 == 1;
const P = i >> 8 & 1 == 1;
const U = i >> 7 & 1 == 1;
const B = i >> 6 & 1 == 1;
const W = i >> 5 & 1 == 1;
const L = i >> 4 & 1 == 1;
break :blk singleDataTransfer(I, P, U, B, W, L);
},
else => switch (@as(u2, i >> 9 & 0x3)) {
// MSB is guaranteed to be 1
0b00 => blk: {
const P = i >> 8 & 1 == 1;
const U = i >> 7 & 1 == 1;
const S = i >> 6 & 1 == 1;
const W = i >> 5 & 1 == 1;
const L = i >> 4 & 1 == 1;
break :blk blockDataTransfer(P, U, S, W, L);
},
0b01 => blk: {
const L = i >> 8 & 1 == 1;
break :blk branch(L);
},
0b10 => armUndefined, // COP Data Transfer
0b11 => if (i >> 8 & 1 == 1) armSoftwareInterrupt() else armUndefined, // COP Data Operation + Register Transfer
},
};
}
return lut; self.stage[0] = self.stage[1];
}; self.stage[1] = cpu.bus.read(T, cpu.r[15]);
}
return opcode;
}
fn reload(self: *Self, cpu: *Arm7tdmi, comptime T: type) void {
comptime std.debug.assert(T == u32 or T == u16);
const inc = if (T == u32) 4 else 2;
self.stage[0] = cpu.bus.read(T, cpu.r[15]);
self.stage[1] = cpu.bus.read(T, cpu.r[15] + inc);
cpu.r[15] += inc * 2;
}
};
pub const PSR = extern union { pub const PSR = extern union {
mode: Bitfield(u32, 0, 5), mode: Bitfield(u32, 0, 5),
@ -703,13 +731,3 @@ fn getMode(bits: u5) ?Mode {
fn getModeChecked(cpu: *const Arm7tdmi, bits: u5) Mode { fn getModeChecked(cpu: *const Arm7tdmi, bits: u5) Mode {
return getMode(bits) orelse cpu.panic("[CPU/CPSR] 0b{b:0>5} is an invalid CPU mode", .{bits}); return getMode(bits) orelse cpu.panic("[CPU/CPSR] 0b{b:0>5} is an invalid CPU mode", .{bits});
} }
fn armUndefined(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
const id = armIdx(opcode);
cpu.panic("[CPU/Decode] ID: 0x{X:0>3} 0x{X:0>8} is an illegal opcode", .{ id, opcode });
}
fn thumbUndefined(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const id = thumbIdx(opcode);
cpu.panic("[CPU/Decode] ID: 0b{b:0>10} 0x{X:0>2} is an illegal opcode", .{ id, opcode });
}

View File

@ -1,6 +1,6 @@
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ArmInstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
pub fn blockDataTransfer(comptime P: bool, comptime U: bool, comptime S: bool, comptime W: bool, comptime L: bool) InstrFn { pub fn blockDataTransfer(comptime P: bool, comptime U: bool, comptime S: bool, comptime W: bool, comptime L: bool) InstrFn {
return struct { return struct {
@ -55,8 +55,10 @@ pub fn blockDataTransfer(comptime P: bool, comptime U: bool, comptime S: bool, c
if (L) { if (L) {
cpu.r[15] = bus.read(u32, und_addr); cpu.r[15] = bus.read(u32, und_addr);
cpu.pipe.flush();
} else { } else {
bus.write(u32, und_addr, cpu.r[15] + 8); // FIXME: Should r15 on write be +12 ahead?
bus.write(u32, und_addr, cpu.r[15] + 4);
} }
cpu.r[rn] = if (U) cpu.r[rn] + 0x40 else cpu.r[rn] - 0x40; cpu.r[rn] = if (U) cpu.r[rn] + 0x40 else cpu.r[rn] - 0x40;
@ -86,17 +88,23 @@ pub fn blockDataTransfer(comptime P: bool, comptime U: bool, comptime S: bool, c
cpu.setUserModeRegister(i, bus.read(u32, address)); cpu.setUserModeRegister(i, bus.read(u32, address));
} else { } else {
const value = bus.read(u32, address); const value = bus.read(u32, address);
cpu.r[i] = if (i == 0xF) value & 0xFFFF_FFFC else value;
if (S and i == 0xF) cpu.setCpsr(cpu.spsr.raw); cpu.r[i] = value;
if (i == 0xF) {
cpu.r[i] &= ~@as(u32, 3); // Align r15
cpu.pipe.flush();
if (S) cpu.setCpsr(cpu.spsr.raw);
}
} }
} else { } else {
if (S) { if (S) {
// Always Transfer User mode Registers // Always Transfer User mode Registers
// This happens regardless if r15 is in the list // This happens regardless if r15 is in the list
const value = cpu.getUserModeRegister(i); const value = cpu.getUserModeRegister(i);
bus.write(u32, address, value + if (i == 0xF) 8 else @as(u32, 0)); // PC is already 4 ahead to make 12 bus.write(u32, address, value + if (i == 0xF) 4 else @as(u32, 0)); // PC is already 8 ahead to make 12
} else { } else {
bus.write(u32, address, cpu.r[i] + if (i == 0xF) 8 else @as(u32, 0)); bus.write(u32, address, cpu.r[i] + if (i == 0xF) 4 else @as(u32, 0));
} }
} }
} }

View File

@ -2,21 +2,26 @@ const std = @import("std");
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ArmInstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
const sext = @import("../../util.zig").sext; const sext = @import("../../util.zig").sext;
pub fn branch(comptime L: bool) InstrFn { pub fn branch(comptime L: bool) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void { fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
if (L) cpu.r[14] = cpu.r[15]; if (L) cpu.r[14] = cpu.r[15] - 4;
cpu.r[15] = cpu.fakePC() +% (sext(u32, u24, opcode) << 2);
cpu.r[15] +%= sext(u32, u24, opcode) << 2;
cpu.pipe.flush();
} }
}.inner; }.inner;
} }
pub fn branchAndExchange(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void { pub fn branchAndExchange(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
const rn = opcode & 0xF; const rn = opcode & 0xF;
cpu.cpsr.t.write(cpu.r[rn] & 1 == 1);
cpu.r[15] = cpu.r[rn] & 0xFFFF_FFFE; const thumb = cpu.r[rn] & 1 == 1;
cpu.r[15] = cpu.r[rn] & if (thumb) ~@as(u32, 1) else ~@as(u32, 3);
cpu.cpsr.t.write(thumb);
cpu.pipe.flush();
} }

View File

@ -1,6 +1,6 @@
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ArmInstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
const rotateRight = @import("../barrel_shifter.zig").rotateRight; const rotateRight = @import("../barrel_shifter.zig").rotateRight;
const execute = @import("../barrel_shifter.zig").execute; const execute = @import("../barrel_shifter.zig").execute;
@ -13,17 +13,12 @@ pub fn dataProcessing(comptime I: bool, comptime S: bool, comptime instrKind: u4
const old_carry = @boolToInt(cpu.cpsr.c.read()); const old_carry = @boolToInt(cpu.cpsr.c.read());
// If certain conditions are met, PC is 12 ahead instead of 8 // If certain conditions are met, PC is 12 ahead instead of 8
// TODO: What are these conditions? I can't remember
if (!I and opcode >> 4 & 1 == 1) cpu.r[15] += 4; if (!I and opcode >> 4 & 1 == 1) cpu.r[15] += 4;
const op1 = cpu.r[rn];
const op1 = if (rn == 0xF) cpu.fakePC() else cpu.r[rn]; const amount = @truncate(u8, (opcode >> 8 & 0xF) << 1);
const op2 = if (I) rotateRight(S, &cpu.cpsr, opcode & 0xFF, amount) else execute(S, cpu, opcode);
var op2: u32 = undefined;
if (I) {
const amount = @truncate(u8, (opcode >> 8 & 0xF) << 1);
op2 = rotateRight(S, &cpu.cpsr, opcode & 0xFF, amount);
} else {
op2 = execute(S, cpu, opcode);
}
// Undo special condition from above // Undo special condition from above
if (!I and opcode >> 4 & 1 == 1) cpu.r[15] -= 4; if (!I and opcode >> 4 & 1 == 1) cpu.r[15] -= 4;
@ -67,39 +62,31 @@ pub fn dataProcessing(comptime I: bool, comptime S: bool, comptime instrKind: u4
}, },
0x8 => { 0x8 => {
// TST // TST
if (rd == 0xF) { if (rd == 0xF)
undefinedTestBehaviour(cpu); return undefinedTestBehaviour(cpu);
return;
}
const result = op1 & op2; const result = op1 & op2;
setTestOpFlags(S, cpu, opcode, result); setTestOpFlags(S, cpu, opcode, result);
}, },
0x9 => { 0x9 => {
// TEQ // TEQ
if (rd == 0xF) { if (rd == 0xF)
undefinedTestBehaviour(cpu); return undefinedTestBehaviour(cpu);
return;
}
const result = op1 ^ op2; const result = op1 ^ op2;
setTestOpFlags(S, cpu, opcode, result); setTestOpFlags(S, cpu, opcode, result);
}, },
0xA => { 0xA => {
// CMP // CMP
if (rd == 0xF) { if (rd == 0xF)
undefinedTestBehaviour(cpu); return undefinedTestBehaviour(cpu);
return;
}
cmp(cpu, op1, op2); cmp(cpu, op1, op2);
}, },
0xB => { 0xB => {
// CMN // CMN
if (rd == 0xF) { if (rd == 0xF)
undefinedTestBehaviour(cpu); return undefinedTestBehaviour(cpu);
return;
}
cmn(cpu, op1, op2); cmn(cpu, op1, op2);
}, },
@ -127,6 +114,8 @@ pub fn dataProcessing(comptime I: bool, comptime S: bool, comptime instrKind: u4
setArmLogicOpFlags(S, cpu, rd, result); setArmLogicOpFlags(S, cpu, rd, result);
}, },
} }
if (rd == 0xF) cpu.pipe.flush();
} }
}.inner; }.inner;
} }
@ -280,5 +269,5 @@ fn setTestOpFlags(comptime S: bool, cpu: *Arm7tdmi, opcode: u32, result: u32) vo
fn undefinedTestBehaviour(cpu: *Arm7tdmi) void { fn undefinedTestBehaviour(cpu: *Arm7tdmi) void {
@setCold(true); @setCold(true);
cpu.setCpsr(cpu.spsr.raw); cpu.setCpsrNoFlush(cpu.spsr.raw);
} }

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ArmInstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
const sext = @import("../../util.zig").sext; const sext = @import("../../util.zig").sext;
const rotr = @import("../../util.zig").rotr; const rotr = @import("../../util.zig").rotr;
@ -15,20 +15,8 @@ pub fn halfAndSignedDataTransfer(comptime P: bool, comptime U: bool, comptime I:
const rm = opcode & 0xF; const rm = opcode & 0xF;
const imm_offset_high = opcode >> 8 & 0xF; const imm_offset_high = opcode >> 8 & 0xF;
var base: u32 = undefined; const base = cpu.r[rn] + if (!L and rn == 0xF) 4 else @as(u32, 0);
if (rn == 0xF) { const offset = if (I) imm_offset_high << 4 | rm else cpu.r[rm];
base = cpu.fakePC();
if (!L) base += 4;
} else {
base = cpu.r[rn];
}
var offset: u32 = undefined;
if (I) {
offset = imm_offset_high << 4 | rm;
} else {
offset = cpu.r[rm];
}
const modified_base = if (U) base +% offset else base -% offset; const modified_base = if (U) base +% offset else base -% offset;
var address = if (P) modified_base else base; var address = if (P) modified_base else base;

View File

@ -1,6 +1,6 @@
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ArmInstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
pub fn multiply(comptime A: bool, comptime S: bool) InstrFn { pub fn multiply(comptime A: bool, comptime S: bool) InstrFn {
return struct { return struct {

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ArmInstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
const PSR = @import("../../cpu.zig").PSR; const PSR = @import("../../cpu.zig").PSR;
const log = std.log.scoped(.PsrTransfer); const log = std.log.scoped(.PsrTransfer);

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ArmInstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
const rotr = @import("../../util.zig").rotr; const rotr = @import("../../util.zig").rotr;

View File

@ -4,7 +4,7 @@ const util = @import("../../util.zig");
const shifter = @import("../barrel_shifter.zig"); const shifter = @import("../barrel_shifter.zig");
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ArmInstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
const rotr = @import("../../util.zig").rotr; const rotr = @import("../../util.zig").rotr;
@ -14,13 +14,8 @@ pub fn singleDataTransfer(comptime I: bool, comptime P: bool, comptime U: bool,
const rn = opcode >> 16 & 0xF; const rn = opcode >> 16 & 0xF;
const rd = opcode >> 12 & 0xF; const rd = opcode >> 12 & 0xF;
var base: u32 = undefined; // rn is r15 and L is not set, the PC is 12 ahead
if (rn == 0xF) { const base = cpu.r[rn] + if (!L and rn == 0xF) 4 else @as(u32, 0);
base = cpu.fakePC();
if (!L) base += 4; // Offset of 12
} else {
base = cpu.r[rn];
}
const offset = if (I) shifter.immShift(false, cpu, opcode) else opcode & 0xFFF; const offset = if (I) shifter.immShift(false, cpu, opcode) else opcode & 0xFFF;
@ -40,18 +35,26 @@ pub fn singleDataTransfer(comptime I: bool, comptime P: bool, comptime U: bool,
} else { } else {
if (B) { if (B) {
// STRB // STRB
const value = if (rd == 0xF) cpu.r[rd] + 8 else cpu.r[rd]; const value = cpu.r[rd] + if (rd == 0xF) 4 else @as(u32, 0); // PC is 12 ahead
bus.write(u8, address, @truncate(u8, value)); bus.write(u8, address, @truncate(u8, value));
} else { } else {
// STR // STR
const value = if (rd == 0xF) cpu.r[rd] + 8 else cpu.r[rd]; const value = cpu.r[rd] + if (rd == 0xF) 4 else @as(u32, 0);
bus.write(u32, address, value); bus.write(u32, address, value);
} }
} }
address = modified_base; address = modified_base;
if (W and P or !P) cpu.r[rn] = address; if (W and P or !P) {
if (L) cpu.r[rd] = result; // This emulates the LDR rd == rn behaviour cpu.r[rn] = address;
if (rn == 0xF) cpu.pipe.flush();
}
if (L) {
// This emulates the LDR rd == rn behaviour
cpu.r[rd] = result;
if (rd == 0xF) cpu.pipe.flush();
}
} }
}.inner; }.inner;
} }

View File

@ -1,12 +1,12 @@
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ArmInstrFn; const InstrFn = @import("../../cpu.zig").arm.InstrFn;
pub fn armSoftwareInterrupt() InstrFn { pub fn armSoftwareInterrupt() InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, _: u32) void { fn inner(cpu: *Arm7tdmi, _: *Bus, _: u32) void {
// Copy Values from Current Mode // Copy Values from Current Mode
const r15 = cpu.r[15]; const ret_addr = cpu.r[15] - 4;
const cpsr = cpu.cpsr.raw; const cpsr = cpu.cpsr.raw;
// Switch Mode // Switch Mode
@ -14,9 +14,10 @@ pub fn armSoftwareInterrupt() InstrFn {
cpu.cpsr.t.write(false); // Force ARM Mode cpu.cpsr.t.write(false); // Force ARM Mode
cpu.cpsr.i.write(true); // Disable normal interrupts cpu.cpsr.i.write(true); // Disable normal interrupts
cpu.r[14] = r15; // Resume Execution cpu.r[14] = ret_addr; // Resume Execution
cpu.spsr.raw = cpsr; // Previous mode CPSR cpu.spsr.raw = cpsr; // Previous mode CPSR
cpu.r[15] = 0x0000_0008; cpu.r[15] = 0x0000_0008;
cpu.pipe.flush();
} }
}.inner; }.inner;
} }

View File

@ -18,11 +18,9 @@ pub fn execute(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 {
fn registerShift(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 { fn registerShift(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 {
const rs_idx = opcode >> 8 & 0xF; const rs_idx = opcode >> 8 & 0xF;
const rm = cpu.r[opcode & 0xF];
const rs = @truncate(u8, cpu.r[rs_idx]); const rs = @truncate(u8, cpu.r[rs_idx]);
const rm_idx = opcode & 0xF;
const rm = if (rm_idx == 0xF) cpu.fakePC() else cpu.r[rm_idx];
return switch (@truncate(u2, opcode >> 5)) { return switch (@truncate(u2, opcode >> 5)) {
0b00 => logicalLeft(S, &cpu.cpsr, rm, rs), 0b00 => logicalLeft(S, &cpu.cpsr, rm, rs),
0b01 => logicalRight(S, &cpu.cpsr, rm, rs), 0b01 => logicalRight(S, &cpu.cpsr, rm, rs),
@ -33,9 +31,7 @@ fn registerShift(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 {
pub fn immShift(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 { pub fn immShift(comptime S: bool, cpu: *Arm7tdmi, opcode: u32) u32 {
const amount = @truncate(u8, opcode >> 7 & 0x1F); const amount = @truncate(u8, opcode >> 7 & 0x1F);
const rm = cpu.r[opcode & 0xF];
const rm_idx = opcode & 0xF;
const rm = if (rm_idx == 0xF) cpu.fakePC() else cpu.r[rm_idx];
var result: u32 = undefined; var result: u32 = undefined;
if (amount == 0) { if (amount == 0) {

View File

@ -1,6 +1,6 @@
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ThumbInstrFn; const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
const adc = @import("../arm/data_processing.zig").adc; const adc = @import("../arm/data_processing.zig").adc;
const sbc = @import("../arm/data_processing.zig").sbc; const sbc = @import("../arm/data_processing.zig").sbc;
@ -15,7 +15,7 @@ const logicalRight = @import("../barrel_shifter.zig").logicalRight;
const arithmeticRight = @import("../barrel_shifter.zig").arithmeticRight; const arithmeticRight = @import("../barrel_shifter.zig").arithmeticRight;
const rotateRight = @import("../barrel_shifter.zig").rotateRight; const rotateRight = @import("../barrel_shifter.zig").rotateRight;
pub fn format4(comptime op: u4) InstrFn { pub fn fmt4(comptime op: u4) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const rs = opcode >> 3 & 0x7; const rs = opcode >> 3 & 0x7;

View File

@ -1,8 +1,8 @@
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ThumbInstrFn; const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
pub fn format14(comptime L: bool, comptime R: bool) InstrFn { pub fn fmt14(comptime L: bool, comptime R: bool) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
const count = @boolToInt(R) + countRlist(opcode); const count = @boolToInt(R) + countRlist(opcode);
@ -33,7 +33,8 @@ pub fn format14(comptime L: bool, comptime R: bool) InstrFn {
if (R) { if (R) {
if (L) { if (L) {
const value = bus.read(u32, address); const value = bus.read(u32, address);
cpu.r[15] = value & 0xFFFF_FFFE; cpu.r[15] = value & ~@as(u32, 1);
cpu.pipe.flush();
} else { } else {
bus.write(u32, address, cpu.r[14]); bus.write(u32, address, cpu.r[14]);
} }
@ -45,14 +46,20 @@ pub fn format14(comptime L: bool, comptime R: bool) InstrFn {
}.inner; }.inner;
} }
pub fn format15(comptime L: bool, comptime rb: u3) InstrFn { pub fn fmt15(comptime L: bool, comptime rb: u3) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
var address = cpu.r[rb]; var address = cpu.r[rb];
const end_address = cpu.r[rb] + 4 * countRlist(opcode); const end_address = cpu.r[rb] + 4 * countRlist(opcode);
if (opcode & 0xFF == 0) { if (opcode & 0xFF == 0) {
if (L) cpu.r[15] = bus.read(u32, address) else bus.write(u32, address, cpu.r[15] + 4); if (L) {
cpu.r[15] = bus.read(u32, address);
cpu.pipe.flush();
} else {
bus.write(u32, address, cpu.r[15] + 2);
}
cpu.r[rb] += 0x40; cpu.r[rb] += 0x40;
return; return;
} }

View File

@ -0,0 +1,54 @@
const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
const checkCond = @import("../../cpu.zig").checkCond;
const sext = @import("../../util.zig").sext;
pub fn fmt16(comptime cond: u4) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// B
if (cond == 0xE or cond == 0xF)
cpu.panic("[CPU/THUMB.16] Undefined conditional branch with condition {}", .{cond});
if (!checkCond(cpu.cpsr, cond)) return;
cpu.r[15] +%= sext(u32, u8, opcode & 0xFF) << 1;
cpu.pipe.flush();
}
}.inner;
}
pub fn fmt18() InstrFn {
return struct {
// B but conditional
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
cpu.r[15] +%= sext(u32, u11, opcode & 0x7FF) << 1;
cpu.pipe.flush();
}
}.inner;
}
pub fn fmt19(comptime is_low: bool) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// BL
const offset = opcode & 0x7FF;
if (is_low) {
// Instruction 2
const next_opcode = cpu.r[15] - 2;
cpu.r[15] = cpu.r[14] +% (offset << 1);
cpu.r[14] = next_opcode | 1;
cpu.pipe.flush();
} else {
// Instruction 1
const lr_offset = sext(u32, u11, offset) << 12;
cpu.r[14] = (cpu.r[15] +% lr_offset) & ~@as(u32, 1);
}
}
}.inner;
}

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ThumbInstrFn; const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
const shifter = @import("../barrel_shifter.zig"); const shifter = @import("../barrel_shifter.zig");
const add = @import("../arm/data_processing.zig").add; const add = @import("../arm/data_processing.zig").add;
@ -12,7 +12,7 @@ const setLogicOpFlags = @import("../arm/data_processing.zig").setLogicOpFlags;
const log = std.log.scoped(.Thumb1); const log = std.log.scoped(.Thumb1);
pub fn format1(comptime op: u2, comptime offset: u5) InstrFn { pub fn fmt1(comptime op: u2, comptime offset: u5) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const rs = opcode >> 3 & 0x7; const rs = opcode >> 3 & 0x7;
@ -55,7 +55,37 @@ pub fn format1(comptime op: u2, comptime offset: u5) InstrFn {
}.inner; }.inner;
} }
pub fn format2(comptime I: bool, is_sub: bool, rn: u3) InstrFn { pub fn fmt5(comptime op: u2, comptime h1: u1, comptime h2: u1) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const src_idx = @as(u4, h2) << 3 | (opcode >> 3 & 0x7);
const dst_idx = @as(u4, h1) << 3 | (opcode & 0x7);
const src = if (src_idx == 0xF) (cpu.r[src_idx] + 2) & 0xFFFF_FFFE else cpu.r[src_idx];
const dst = if (dst_idx == 0xF) (cpu.r[dst_idx] + 2) & 0xFFFF_FFFE else cpu.r[dst_idx];
switch (op) {
0b00 => {
// ADD
const sum = add(false, cpu, dst, src);
cpu.r[dst_idx] = if (dst_idx == 0xF) sum & 0xFFFF_FFFE else sum;
},
0b01 => cmp(cpu, dst, src), // CMP
0b10 => {
// MOV
cpu.r[dst_idx] = if (dst_idx == 0xF) src & 0xFFFF_FFFE else src;
},
0b11 => {
// BX
cpu.cpsr.t.write(src & 1 == 1);
cpu.r[15] = src & 0xFFFF_FFFE;
},
}
}
}.inner;
}
pub fn fmt2(comptime I: bool, is_sub: bool, rn: u3) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const rs = opcode >> 3 & 0x7; const rs = opcode >> 3 & 0x7;
@ -80,7 +110,7 @@ pub fn format2(comptime I: bool, is_sub: bool, rn: u3) InstrFn {
}.inner; }.inner;
} }
pub fn format3(comptime op: u2, comptime rd: u3) InstrFn { pub fn fmt3(comptime op: u2, comptime rd: u3) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const offset = @truncate(u8, opcode); const offset = @truncate(u8, opcode);
@ -99,19 +129,18 @@ pub fn format3(comptime op: u2, comptime rd: u3) InstrFn {
}.inner; }.inner;
} }
pub fn format12(comptime isSP: bool, comptime rd: u3) InstrFn { pub fn fmt12(comptime isSP: bool, comptime rd: u3) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// ADD // ADD
const left = if (isSP) cpu.r[13] else (cpu.r[15] + 2) & 0xFFFF_FFFD; const left = if (isSP) cpu.r[13] else cpu.r[15] & ~@as(u32, 2);
const right = (opcode & 0xFF) << 2; const right = (opcode & 0xFF) << 2;
const result = left + right; cpu.r[rd] = left + right;
cpu.r[rd] = result;
} }
}.inner; }.inner;
} }
pub fn format13(comptime S: bool) InstrFn { pub fn fmt13(comptime S: bool) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// ADD // ADD

View File

@ -2,23 +2,25 @@ const std = @import("std");
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ThumbInstrFn; const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
const rotr = @import("../../util.zig").rotr; const rotr = @import("../../util.zig").rotr;
pub fn format6(comptime rd: u3) InstrFn { pub fn fmt6(comptime rd: u3) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
// LDR // LDR
const offset = (opcode & 0xFF) << 2; const offset = (opcode & 0xFF) << 2;
cpu.r[rd] = bus.read(u32, (cpu.r[15] + 2 & 0xFFFF_FFFD) + offset);
// Bit 1 of the PC intentionally ignored
cpu.r[rd] = bus.read(u32, (cpu.r[15] & ~@as(u32, 2)) + offset);
} }
}.inner; }.inner;
} }
const sext = @import("../../util.zig").sext; const sext = @import("../../util.zig").sext;
pub fn format78(comptime op: u2, comptime T: bool) InstrFn { pub fn fmt78(comptime op: u2, comptime T: bool) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
const ro = opcode >> 6 & 0x7; const ro = opcode >> 6 & 0x7;
@ -78,7 +80,7 @@ pub fn format78(comptime op: u2, comptime T: bool) InstrFn {
}.inner; }.inner;
} }
pub fn format9(comptime B: bool, comptime L: bool, comptime offset: u5) InstrFn { pub fn fmt9(comptime B: bool, comptime L: bool, comptime offset: u5) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
const rb = opcode >> 3 & 0x7; const rb = opcode >> 3 & 0x7;
@ -110,7 +112,7 @@ pub fn format9(comptime B: bool, comptime L: bool, comptime offset: u5) InstrFn
}.inner; }.inner;
} }
pub fn format10(comptime L: bool, comptime offset: u5) InstrFn { pub fn fmt10(comptime L: bool, comptime offset: u5) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
const rb = opcode >> 3 & 0x7; const rb = opcode >> 3 & 0x7;
@ -130,7 +132,7 @@ pub fn format10(comptime L: bool, comptime offset: u5) InstrFn {
}.inner; }.inner;
} }
pub fn format11(comptime L: bool, comptime rd: u3) InstrFn { pub fn fmt11(comptime L: bool, comptime rd: u3) InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void { fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
const offset = (opcode & 0xFF) << 2; const offset = (opcode & 0xFF) << 2;

View File

@ -1,12 +1,12 @@
const Bus = @import("../../Bus.zig"); const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ThumbInstrFn; const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
pub fn thumbSoftwareInterrupt() InstrFn { pub fn fmt17() InstrFn {
return struct { return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, _: u16) void { fn inner(cpu: *Arm7tdmi, _: *Bus, _: u16) void {
// Copy Values from Current Mode // Copy Values from Current Mode
const r15 = cpu.r[15]; const ret_addr = cpu.r[15] - 2;
const cpsr = cpu.cpsr.raw; const cpsr = cpu.cpsr.raw;
// Switch Mode // Switch Mode
@ -14,9 +14,10 @@ pub fn thumbSoftwareInterrupt() InstrFn {
cpu.cpsr.t.write(false); // Force ARM Mode cpu.cpsr.t.write(false); // Force ARM Mode
cpu.cpsr.i.write(true); // Disable normal interrupts cpu.cpsr.i.write(true); // Disable normal interrupts
cpu.r[14] = r15; // Resume Execution cpu.r[14] = ret_addr; // Resume Execution
cpu.spsr.raw = cpsr; // Previous mode CPSR cpu.spsr.raw = cpsr; // Previous mode CPSR
cpu.r[15] = 0x0000_0008; cpu.r[15] = 0x0000_0008;
cpu.pipe.flush();
} }
}.inner; }.inner;
} }

View File

@ -5,12 +5,16 @@ const Bus = @import("Bus.zig");
const Scheduler = @import("scheduler.zig").Scheduler; const Scheduler = @import("scheduler.zig").Scheduler;
const Arm7tdmi = @import("cpu.zig").Arm7tdmi; const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
const FpsTracker = @import("util.zig").FpsTracker; const FpsTracker = @import("util.zig").FpsTracker;
const FilePaths = @import("util.zig").FilePaths;
const Timer = std.time.Timer; const Timer = std.time.Timer;
const Thread = std.Thread; const Thread = std.Thread;
const Atomic = std.atomic.Atomic; const Atomic = std.atomic.Atomic;
const Allocator = std.mem.Allocator;
const audio_sync = true; const sync_audio = false;
const sync_video: RunKind = .UnlimitedFPS;
pub const cpu_logging = false;
// 228 Lines which consist of 308 dots (which are 4 cycles long) // 228 Lines which consist of 308 dots (which are 4 cycles long)
const cycles_per_frame: u64 = 228 * (308 * 4); //280896 const cycles_per_frame: u64 = 228 * (308 * 4); //280896
@ -35,10 +39,10 @@ const RunKind = enum {
LimitedBusy, LimitedBusy,
}; };
pub fn run(kind: RunKind, quit: *Atomic(bool), fps: *FpsTracker, sched: *Scheduler, cpu: *Arm7tdmi) void { pub fn run(quit: *Atomic(bool), fps: *FpsTracker, sched: *Scheduler, cpu: *Arm7tdmi) void {
if (audio_sync) log.info("Audio sync enabled", .{}); if (sync_audio) log.info("Audio sync enabled", .{});
switch (kind) { switch (sync_video) {
.Unlimited => runUnsynchronized(quit, sched, cpu, null), .Unlimited => runUnsynchronized(quit, sched, cpu, null),
.Limited => runSynchronized(quit, sched, cpu, null), .Limited => runSynchronized(quit, sched, cpu, null),
.UnlimitedFPS => runUnsynchronized(quit, sched, cpu, fps), .UnlimitedFPS => runUnsynchronized(quit, sched, cpu, fps),
@ -50,14 +54,17 @@ pub fn run(kind: RunKind, quit: *Atomic(bool), fps: *FpsTracker, sched: *Schedul
pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void { pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
const frame_end = sched.tick + cycles_per_frame; const frame_end = sched.tick + cycles_per_frame;
while (true) { while (sched.tick < frame_end) {
while (sched.tick < std.math.min(frame_end, sched.nextTimestamp())) { if (!cpu.stepDmaTransfer()) {
if (cpu.stepDmaTransfer()) continue; // DMA is blocking, ticks scheduler if (cpu.isHalted()) {
if (!cpu.isHalted()) cpu.step() else sched.tick += 1; // Fast-forward to next Event
sched.tick = sched.queue.peek().?.tick;
} else {
cpu.step();
}
} }
if (sched.tick >= frame_end) break; if (sched.tick >= sched.nextTimestamp()) sched.handleEvent(cpu);
sched.handleEvent(cpu);
} }
} }
@ -77,14 +84,14 @@ pub fn runUnsynchronized(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi,
while (!quit.load(.SeqCst)) { while (!quit.load(.SeqCst)) {
runFrame(sched, cpu); runFrame(sched, cpu);
if (audio_sync) syncToAudio(cpu); if (sync_audio) syncToAudio(cpu);
tracker.tick(); tracker.tick();
} }
} else { } else {
while (!quit.load(.SeqCst)) { while (!quit.load(.SeqCst)) {
runFrame(sched, cpu); runFrame(sched, cpu);
if (audio_sync) syncToAudio(cpu); if (sync_audio) syncToAudio(cpu);
} }
} }
} }
@ -105,7 +112,7 @@ pub fn runSynchronized(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, f
// If we happen to also be syncing to audio, we choose to spin on // If we happen to also be syncing to audio, we choose to spin on
// the amount of time needed for audio to catch up rather than // the amount of time needed for audio to catch up rather than
// our expected wake-up time // our expected wake-up time
if (audio_sync) syncToAudio(cpu) else spinLoop(&timer, wake_time); if (sync_audio) syncToAudio(cpu) else spinLoop(&timer, wake_time);
wake_time = new_wake_time; wake_time = new_wake_time;
tracker.tick(); tracker.tick();
@ -115,7 +122,7 @@ pub fn runSynchronized(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, f
runFrame(sched, cpu); runFrame(sched, cpu);
const new_wake_time = syncToVideo(&timer, wake_time); const new_wake_time = syncToVideo(&timer, wake_time);
// see above comment // see above comment
if (audio_sync) syncToAudio(cpu) else spinLoop(&timer, wake_time); if (sync_audio) syncToAudio(cpu) else spinLoop(&timer, wake_time);
wake_time = new_wake_time; wake_time = new_wake_time;
} }

View File

@ -573,7 +573,7 @@ pub const Ppu = struct {
// See if HBlank DMA is present and not enabled // See if HBlank DMA is present and not enabled
if (!self.dispstat.vblank.read()) if (!self.dispstat.vblank.read())
pollBlankingDma(&cpu.bus, .HBlank); pollBlankingDma(cpu.bus, .HBlank);
self.dispstat.hblank.set(); self.dispstat.hblank.set();
self.sched.push(.HBlank, 68 * 4 -| late); self.sched.push(.HBlank, 68 * 4 -| late);
@ -615,7 +615,7 @@ pub const Ppu = struct {
self.aff_bg[1].latchRefPoints(); self.aff_bg[1].latchRefPoints();
// See if Vblank DMA is present and not enabled // See if Vblank DMA is present and not enabled
pollBlankingDma(&cpu.bus, .VBlank); pollBlankingDma(cpu.bus, .VBlank);
} }
if (scanline == 227) self.dispstat.vblank.unset(); if (scanline == 227) self.dispstat.vblank.unset();

View File

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const Log2Int = std.math.Log2Int; const Log2Int = std.math.Log2Int;
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
// Sign-Extend value of type `T` to type `U` // Sign-Extend value of type `T` to type `U`
pub fn sext(comptime T: type, comptime U: type, value: T) T { pub fn sext(comptime T: type, comptime U: type, value: T) T {
@ -112,3 +113,64 @@ pub fn writeUndefined(log: anytype, comptime format: []const u8, args: anytype)
log.warn(format, args); log.warn(format, args);
if (builtin.mode == .Debug) std.debug.panic("TODO: Implement I/O Register", .{}); if (builtin.mode == .Debug) std.debug.panic("TODO: Implement I/O Register", .{});
} }
pub const Logger = struct {
const Self = @This();
buf: std.io.BufferedWriter(4096 << 2, std.fs.File.Writer),
pub fn init(file: std.fs.File) Self {
return .{
.buf = .{ .unbuffered_writer = file.writer() },
};
}
pub fn print(self: *Self, comptime format: []const u8, args: anytype) !void {
try self.buf.writer().print(format, args);
}
pub fn mgbaLog(self: *Self, arm7tdmi: *const Arm7tdmi, opcode: u32) void {
const fmt_base = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | ";
const thumb_fmt = fmt_base ++ "{X:0>4}:\n";
const arm_fmt = fmt_base ++ "{X:0>8}:\n";
if (arm7tdmi.cpsr.t.read()) {
if (opcode >> 11 == 0x1E) {
// Instruction 1 of a BL Opcode, print in ARM mode
const low = arm7tdmi.bus.debugRead(u16, arm7tdmi.r[15]);
const bl_opcode = @as(u32, opcode) << 16 | low;
self.print(arm_fmt, Self.fmtArgs(arm7tdmi, bl_opcode)) catch @panic("failed to write to log file");
} else {
self.print(thumb_fmt, Self.fmtArgs(arm7tdmi, opcode)) catch @panic("failed to write to log file");
}
} else {
self.print(arm_fmt, Self.fmtArgs(arm7tdmi, opcode)) catch @panic("failed to write to log file");
}
}
fn fmtArgs(arm7tdmi: *const Arm7tdmi, opcode: u32) FmtArgTuple {
return .{
arm7tdmi.r[0],
arm7tdmi.r[1],
arm7tdmi.r[2],
arm7tdmi.r[3],
arm7tdmi.r[4],
arm7tdmi.r[5],
arm7tdmi.r[6],
arm7tdmi.r[7],
arm7tdmi.r[8],
arm7tdmi.r[9],
arm7tdmi.r[10],
arm7tdmi.r[11],
arm7tdmi.r[12],
arm7tdmi.r[13],
arm7tdmi.r[14],
arm7tdmi.r[15] - 4,
arm7tdmi.cpsr.raw,
opcode,
};
}
};
const FmtArgTuple = std.meta.Tuple(&.{ u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32 });

View File

@ -1,54 +0,0 @@
const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ThumbInstrFn;
const checkCond = @import("../../cpu.zig").checkCond;
const sext = @import("../../util.zig").sext;
pub fn format16(comptime cond: u4) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// B
const offset = sext(u32, u8, opcode & 0xFF) << 1;
const should_execute = switch (cond) {
0xE, 0xF => cpu.panic("[CPU/THUMB.16] Undefined conditional branch with condition {}", .{cond}),
else => checkCond(cpu.cpsr, cond),
};
if (should_execute) {
cpu.r[15] = (cpu.r[15] + 2) +% offset;
}
}
}.inner;
}
pub fn format18() InstrFn {
return struct {
// B but conditional
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const offset = sext(u32, u11, opcode & 0x7FF) << 1;
cpu.r[15] = (cpu.r[15] + 2) +% offset;
}
}.inner;
}
pub fn format19(comptime is_low: bool) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// BL
const offset = opcode & 0x7FF;
if (is_low) {
// Instruction 2
const old_pc = cpu.r[15];
cpu.r[15] = cpu.r[14] +% (offset << 1);
cpu.r[14] = old_pc | 1;
} else {
// Instruction 1
cpu.r[14] = (cpu.r[15] + 2) +% (sext(u32, u11, offset) << 12);
}
}
}.inner;
}

View File

@ -1,36 +0,0 @@
const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ThumbInstrFn;
const cmp = @import("../arm/data_processing.zig").cmp;
const add = @import("../arm/data_processing.zig").add;
pub fn format5(comptime op: u2, comptime h1: u1, comptime h2: u1) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const src_idx = @as(u4, h2) << 3 | (opcode >> 3 & 0x7);
const dst_idx = @as(u4, h1) << 3 | (opcode & 0x7);
const src = if (src_idx == 0xF) (cpu.r[src_idx] + 2) & 0xFFFF_FFFE else cpu.r[src_idx];
const dst = if (dst_idx == 0xF) (cpu.r[dst_idx] + 2) & 0xFFFF_FFFE else cpu.r[dst_idx];
switch (op) {
0b00 => {
// ADD
const sum = add(false, cpu, dst, src);
cpu.r[dst_idx] = if (dst_idx == 0xF) sum & 0xFFFF_FFFE else sum;
},
0b01 => cmp(cpu, dst, src), // CMP
0b10 => {
// MOV
cpu.r[dst_idx] = if (dst_idx == 0xF) src & 0xFFFF_FFFE else src;
},
0b11 => {
// BX
cpu.cpsr.t.write(src & 1 == 1);
cpu.r[15] = src & 0xFFFF_FFFE;
},
}
}
}.inner;
}

View File

@ -1,35 +1,23 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const SDL = @import("sdl2");
const clap = @import("clap");
const known_folders = @import("known_folders"); const known_folders = @import("known_folders");
const clap = @import("clap");
const emu = @import("emu.zig"); const Gui = @import("Gui.zig");
const Bus = @import("Bus.zig"); const Bus = @import("core/Bus.zig");
const Apu = @import("apu.zig").Apu; const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
const Arm7tdmi = @import("cpu.zig").Arm7tdmi; const Scheduler = @import("core/scheduler.zig").Scheduler;
const Scheduler = @import("scheduler.zig").Scheduler; const FilePaths = @import("core/util.zig").FilePaths;
const FpsTracker = @import("util.zig").FpsTracker;
const Timer = std.time.Timer; const Allocator = std.mem.Allocator;
const Thread = std.Thread; const log = std.log.scoped(.CLI);
const Atomic = std.atomic.Atomic; const width = @import("core/ppu.zig").width;
const File = std.fs.File; const height = @import("core/ppu.zig").height;
const arm7tdmi_logging = @import("core/emu.zig").cpu_logging;
const window_scale = 4;
const gba_width = @import("ppu.zig").width;
const gba_height = @import("ppu.zig").height;
const framebuf_pitch = @import("ppu.zig").framebuf_pitch;
const expected_rate = @import("emu.zig").frame_rate;
const sample_rate = @import("apu.zig").host_sample_rate;
pub const enable_logging: bool = false;
const is_binary: bool = false;
const log = std.log.scoped(.GUI);
pub const log_level = if (builtin.mode != .Debug) .info else std.log.default_level; pub const log_level = if (builtin.mode != .Debug) .info else std.log.default_level;
const asString = @import("util.zig").asString; // TODO: Reimpl Logging
// CLI Arguments + Help Text // CLI Arguments + Help Text
const params = clap.parseParamsComptime( const params = clap.parseParamsComptime(
@ -40,251 +28,78 @@ const params = clap.parseParamsComptime(
); );
pub fn main() anyerror!void { pub fn main() anyerror!void {
// Allocator for Emulator + CLI // Main Allocator for ZBA
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(!gpa.deinit()); defer std.debug.assert(!gpa.deinit());
const alloc = gpa.allocator(); const allocator = gpa.allocator();
// Setup CLI using zig-clap // Handle CLI Input
var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{}); const result = try clap.parse(clap.Help, &params, clap.parsers.default, .{});
defer res.deinit(); defer result.deinit();
const stderr = std.io.getStdErr(); const paths = try handleArguments(allocator, &result);
defer stderr.close(); defer if (paths.save) |path| allocator.free(path);
// Display Help, if requested // TODO: Take Emulator Init Code out of main.zig
// Grab ROM and BIOS paths if provided var scheduler = Scheduler.init(allocator);
if (res.args.help) return clap.help(stderr.writer(), clap.Help, &params, .{});
const rom_path = try getRomPath(res, stderr);
const bios_path: ?[]const u8 = if (res.args.bios) |p| p else null;
// Determine Save Directory
const save_dir = try getSavePath(alloc);
defer if (save_dir) |path| alloc.free(path);
log.info("Found save directory: {s}", .{save_dir});
// Initialize Scheduler and ARM7TDMI Emulator
// Provide GBA Bus (initialized with ARM7TDMI) with a valid ptr to ARM7TDMI
var scheduler = Scheduler.init(alloc);
defer scheduler.deinit(); defer scheduler.deinit();
const paths = .{ .bios = bios_path, .rom = rom_path, .save = save_dir }; var bus = try Bus.init(allocator, &scheduler, paths);
var cpu = try Arm7tdmi.init(alloc, &scheduler, paths); defer bus.deinit();
defer cpu.deinit();
cpu.bus.attach(&cpu);
// cpu.fastBoot(); // Uncomment to skip BIOS
// Copy ROM title while Emulator still belongs to this thread var arm7tdmi = Arm7tdmi.init(&scheduler, &bus);
const title = cpu.bus.pak.title;
// Initialize SDL2 const log_file: ?std.fs.File = if (arm7tdmi_logging) try std.fs.cwd().createFile("zba.log", .{}) else null;
initSdl2();
defer SDL.SDL_Quit();
const dev = initAudio(&cpu.bus.apu);
defer SDL.SDL_CloseAudioDevice(dev);
// TODO: Refactor or delete this Logging code
// I probably still need logging in some form though (e.g. Golden Sun IIRC)
const log_file: ?File = if (enable_logging) blk: {
const file = try std.fs.cwd().createFile(if (is_binary) "zba.bin" else "zba.log", .{});
cpu.useLogger(&file, is_binary);
break :blk file;
} else null;
defer if (log_file) |file| file.close(); defer if (log_file) |file| file.close();
var quit = Atomic(bool).init(false); if (log_file) |file| arm7tdmi.attach(file);
var emu_rate = FpsTracker.init(); bus.attach(&arm7tdmi); // TODO: Shrink Surface (only CPSR and r15?)
if (paths.bios == null) arm7tdmi.fastBoot();
// Run Emulator in it's separate thread var gui = Gui.init(bus.pak.title, width, height);
// From this point on, interacting with Arm7tdmi or Scheduler gui.initAudio(&bus.apu);
// be justified, as it will require to be thread-afe defer gui.deinit();
const emu_thread = try Thread.spawn(.{}, emu.run, .{ .LimitedFPS, &quit, &emu_rate, &scheduler, &cpu });
defer emu_thread.join();
var title_buf: [0x20]u8 = std.mem.zeroes([0x20]u8); try gui.run(&arm7tdmi, &scheduler);
const window_title = try std.fmt.bufPrint(&title_buf, "ZBA | {s}", .{asString(title)});
const window = createWindow(window_title, gba_width, gba_height);
defer SDL.SDL_DestroyWindow(window);
const renderer = createRenderer(window);
defer SDL.SDL_DestroyRenderer(renderer);
const texture = createTexture(renderer, gba_width, gba_height);
defer SDL.SDL_DestroyTexture(texture);
// Init FPS Timer
var dyn_title_buf: [0x100]u8 = [_]u8{0x00} ** 0x100;
emu_loop: while (true) {
var event: SDL.SDL_Event = undefined;
while (SDL.SDL_PollEvent(&event) != 0) {
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(),
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_i => log.err("Sample Count: {}", .{@intCast(u32, SDL.SDL_AudioStreamAvailable(cpu.bus.apu.stream)) / (2 * @sizeOf(u16))}),
SDL.SDLK_j => log.err("Scheduler Capacity: {} | Scheduler Event Count: {}", .{ scheduler.queue.capacity(), scheduler.queue.count() }),
SDL.SDLK_k => {
// Dump IWRAM to file
log.info("PC: 0x{X:0>8}", .{cpu.r[15]});
log.info("LR: 0x{X:0>8}", .{cpu.r[14]});
// const iwram_file = try std.fs.cwd().createFile("iwram.bin", .{});
// defer iwram_file.close();
// try iwram_file.writeAll(cpu.bus.iwram.buf);
},
else => {},
}
},
else => {},
}
}
// Emulator has an internal Double Buffer
const buf_ptr = cpu.bus.ppu.framebuf.get(.Renderer).ptr;
_ = SDL.SDL_UpdateTexture(texture, null, buf_ptr, framebuf_pitch);
_ = SDL.SDL_RenderCopy(renderer, texture, null, null);
SDL.SDL_RenderPresent(renderer);
const dyn_title = std.fmt.bufPrint(&dyn_title_buf, "{s} [Emu: {}fps] ", .{ window_title, emu_rate.value() }) catch unreachable;
SDL.SDL_SetWindowTitle(window, dyn_title.ptr);
}
quit.store(true, .SeqCst); // Terminate Emulator Thread
} }
const CliError = error{ fn getSavePath(allocator: Allocator) !?[]const u8 {
InsufficientOptions,
UnneededOptions,
};
fn sdlPanic() noreturn {
const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error";
@panic(std.mem.sliceTo(str, 0));
}
fn initSdl2() void {
const status = SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_GAMECONTROLLER);
if (status < 0) sdlPanic();
}
fn createWindow(title: []u8, width: c_int, height: c_int) *SDL.SDL_Window {
return SDL.SDL_CreateWindow(
title.ptr,
SDL.SDL_WINDOWPOS_CENTERED,
SDL.SDL_WINDOWPOS_CENTERED,
width * window_scale,
height * window_scale,
SDL.SDL_WINDOW_SHOWN,
) orelse sdlPanic();
}
fn createRenderer(window: *SDL.SDL_Window) *SDL.SDL_Renderer {
return SDL.SDL_CreateRenderer(window, -1, SDL.SDL_RENDERER_ACCELERATED | SDL.SDL_RENDERER_PRESENTVSYNC) orelse sdlPanic();
}
fn createTexture(renderer: *SDL.SDL_Renderer, width: c_int, height: c_int) *SDL.SDL_Texture {
return SDL.SDL_CreateTexture(
renderer,
SDL.SDL_PIXELFORMAT_RGBA8888,
SDL.SDL_TEXTUREACCESS_STREAMING,
width,
height,
) orelse sdlPanic();
}
fn initAudio(apu: *Apu) SDL.SDL_AudioDeviceID {
var have: SDL.SDL_AudioSpec = undefined;
var want: SDL.SDL_AudioSpec = .{
.freq = sample_rate,
.format = SDL.AUDIO_U16,
.channels = 2,
.samples = 0x100,
.callback = audioCallback,
.userdata = apu,
.silence = undefined,
.size = undefined,
.padding = undefined,
};
const dev = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0);
if (dev == 0) sdlPanic();
// Start Playback on the Audio device
SDL.SDL_PauseAudioDevice(dev, 0);
return dev;
}
export fn audioCallback(userdata: ?*anyopaque, stream: [*c]u8, len: c_int) void {
const apu = @ptrCast(*Apu, @alignCast(8, userdata));
const written = SDL.SDL_AudioStreamGet(apu.stream, stream, len);
// If we don't write anything, play silence otherwise garbage will be played
// FIXME: I don't think this hack to remove DC Offset is acceptable :thinking:
if (written == 0) std.mem.set(u8, stream[0..@intCast(usize, len)], 0x40);
}
fn getSavePath(alloc: std.mem.Allocator) !?[]const u8 {
const save_subpath = "zba" ++ [_]u8{std.fs.path.sep} ++ "save"; const save_subpath = "zba" ++ [_]u8{std.fs.path.sep} ++ "save";
const maybe_data_path = try known_folders.getPath(alloc, .data); const maybe_data_path = try known_folders.getPath(allocator, .data);
defer if (maybe_data_path) |path| alloc.free(path); defer if (maybe_data_path) |path| allocator.free(path);
const save_path = if (maybe_data_path) |base| try std.fs.path.join(alloc, &[_][]const u8{ base, "zba", "save" }) else null; const save_path = if (maybe_data_path) |base| try std.fs.path.join(allocator, &[_][]const u8{ base, "zba", "save" }) else null;
if (save_path) |_| { if (save_path) |_| {
// If we've determined what our save path should be, ensure the prereq directories // If we've determined what our save path should be, ensure the prereq directories
// are present so that we can successfully write to the path when necessary // are present so that we can successfully write to the path when necessary
const maybe_data_dir = try known_folders.open(alloc, .data, .{}); const maybe_data_dir = try known_folders.open(allocator, .data, .{});
if (maybe_data_dir) |data_dir| try data_dir.makePath(save_subpath); if (maybe_data_dir) |data_dir| try data_dir.makePath(save_subpath);
} }
return save_path; return save_path;
} }
fn getRomPath(res: clap.Result(clap.Help, &params, clap.parsers.default), stderr: std.fs.File) ![]const u8 { fn getRomPath(result: *const clap.Result(clap.Help, &params, clap.parsers.default)) ![]const u8 {
return switch (res.positionals.len) { return switch (result.positionals.len) {
1 => res.positionals[0], 1 => result.positionals[0],
0 => { 0 => std.debug.panic("ZBA requires a positional path to a GamePak ROM.\n", .{}),
try stderr.writeAll("ZBA requires a positional path to a GamePak ROM.\n"); else => std.debug.panic("ZBA received too many arguments.\n", .{}),
return CliError.InsufficientOptions; };
}, }
else => {
try stderr.writeAll("ZBA received too many arguments.\n"); pub fn handleArguments(allocator: Allocator, result: *const clap.Result(clap.Help, &params, clap.parsers.default)) !FilePaths {
return CliError.UnneededOptions; const rom_path = try getRomPath(result);
}, log.info("ROM path: {s}", .{rom_path});
const bios_path = result.args.bios;
if (bios_path) |path| log.info("BIOS path: {s}", .{path}) else log.info("No BIOS provided", .{});
const save_path = try getSavePath(allocator);
if (save_path) |path| log.info("Save path: {s}", .{path});
return FilePaths{
.rom = rom_path,
.bios = bios_path,
.save = save_path,
}; };
} }