Compare commits

..

9 Commits

34 changed files with 622 additions and 593 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,54 +6,227 @@ const Bit = @import("bitfield").Bit;
const Bitfield = @import("bitfield").Bitfield;
const Scheduler = @import("scheduler.zig").Scheduler;
const FilePaths = @import("util.zig").FilePaths;
const Logger = @import("util.zig").Logger;
const Allocator = std.mem.Allocator;
const File = std.fs.File;
// ARM Instruction Groups
const dataProcessing = @import("cpu/arm/data_processing.zig").dataProcessing;
// ARM Instructions
pub const arm = struct {
pub const InstrFn = fn (*Arm7tdmi, *Bus, u32) void;
const lut: [0x1000]InstrFn = populate();
const processing = @import("cpu/arm/data_processing.zig").dataProcessing;
const psrTransfer = @import("cpu/arm/psr_transfer.zig").psrTransfer;
const singleDataTransfer = @import("cpu/arm/single_data_transfer.zig").singleDataTransfer;
const halfAndSignedDataTransfer = @import("cpu/arm/half_signed_data_transfer.zig").halfAndSignedDataTransfer;
const blockDataTransfer = @import("cpu/arm/block_data_transfer.zig").blockDataTransfer;
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 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 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;
const multiply = @import("cpu/arm/multiply.zig").multiply;
const multiplyLong = @import("cpu/arm/multiply.zig").multiplyLong;
// THUMB Instruction Groups
const format1 = @import("cpu/thumb/data_processing.zig").format1;
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;
// Undefined ARM Instruction handler
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 format4 = @import("cpu/thumb/alu.zig").format4;
const format5 = @import("cpu/thumb/processing_branch.zig").format5;
fn populate() [0x1000]InstrFn {
return comptime {
@setEvalBranchQuota(0xE000);
var ret = [_]InstrFn{und} ** 0x1000;
const format6 = @import("cpu/thumb/data_transfer.zig").format6;
const format78 = @import("cpu/thumb/data_transfer.zig").format78;
const format9 = @import("cpu/thumb/data_transfer.zig").format9;
const format10 = @import("cpu/thumb/data_transfer.zig").format10;
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;
var i: usize = 0;
while (i < ret.len) : (i += 1) {
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 format16 = @import("cpu/thumb/branch.zig").format16;
const format18 = @import("cpu/thumb/branch.zig").format18;
const format19 = @import("cpu/thumb/branch.zig").format19;
return ret;
};
}
};
const thumbSoftwareInterrupt = @import("cpu/thumb/software_interrupt.zig").thumbSoftwareInterrupt;
// THUMB Instructions
pub const thumb = struct {
pub const InstrFn = fn (*Arm7tdmi, *Bus, u16) void;
const lut: [0x400]InstrFn = populate();
pub const ArmInstrFn = fn (*Arm7tdmi, *Bus, u32) void;
pub const ThumbInstrFn = fn (*Arm7tdmi, *Bus, u16) void;
const arm_lut: [0x1000]ArmInstrFn = armPopulate();
const thumb_lut: [0x400]ThumbInstrFn = thumbPopulate();
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");
const enable_logging = @import("main.zig").enable_logging;
/// 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);
pub const Arm7tdmi = struct {
@ -62,7 +235,7 @@ pub const Arm7tdmi = struct {
r: [16]u32,
pipe: Pipline,
sched: *Scheduler,
bus: Bus,
bus: *Bus,
cpsr: PSR,
spsr: PSR,
@ -76,34 +249,25 @@ pub const Arm7tdmi = struct {
banked_spsr: [5]PSR,
log_file: ?*const File,
log_buf: [0x100]u8,
binary_log: bool,
logger: ?Logger,
pub fn init(alloc: Allocator, sched: *Scheduler, paths: FilePaths) !Self {
pub fn init(sched: *Scheduler, bus: *Bus) Self {
return Self{
.r = [_]u32{0x00} ** 16,
.pipe = Pipline.init(),
.sched = sched,
.bus = try Bus.init(alloc, sched, paths),
.bus = bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.banked_fiq = [_]u32{0x00} ** 10,
.banked_r = [_]u32{0x00} ** 12,
.banked_spsr = [_]PSR{.{ .raw = 0x0000_0000 }} ** 5,
.log_file = null,
.log_buf = undefined,
.binary_log = false,
.logger = null,
};
}
pub fn deinit(self: Self) void {
self.bus.deinit();
}
pub fn useLogger(self: *Self, file: *const File, is_binary: bool) void {
self.log_file = file;
self.binary_log = is_binary;
pub fn attach(self: *Self, log_file: std.fs.File) void {
self.logger = Logger.init(log_file);
}
inline fn bankedIdx(mode: Mode, kind: BankedKind) usize {
@ -273,15 +437,16 @@ pub const Arm7tdmi = struct {
pub fn step(self: *Self) void {
if (self.cpsr.t.read()) blk: {
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 blk: {
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)))
arm_lut[armIdx(opcode)](self, &self.bus, opcode);
if (checkCond(self.cpsr, @truncate(u4, opcode >> 28))) {
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);
@ -418,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 {
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";
@ -509,178 +655,6 @@ pub fn checkCond(cpsr: PSR, cond: u4) bool {
};
}
fn thumbPopulate() [0x400]ThumbInstrFn {
return comptime {
@setEvalBranchQuota(5025); // This is exact
var lut = [_]ThumbInstrFn{thumbUndefined} ** 0x400;
var i: usize = 0;
while (i < lut.len) : (i += 1) {
lut[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 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);
},
0b101 => if (i >> 2 & 0xF == 0b1111) blk: {
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;
};
}
fn armPopulate() [0x1000]ArmInstrFn {
return comptime {
@setEvalBranchQuota(0xE000);
var lut = [_]ArmInstrFn{armUndefined} ** 0x1000;
var i: usize = 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;
};
}
const Pipline = struct {
const Self = @This();
stage: [2]?u32,
@ -757,13 +731,3 @@ fn getMode(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});
}
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 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 {
return struct {

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Bus = @import("../../Bus.zig");
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;

View File

@ -1,6 +1,6 @@
const Bus = @import("../../Bus.zig");
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 execute = @import("../barrel_shifter.zig").execute;

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Bus = @import("../../Bus.zig");
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 rotr = @import("../../util.zig").rotr;

View File

@ -1,6 +1,6 @@
const Bus = @import("../../Bus.zig");
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 {
return struct {

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Bus = @import("../../Bus.zig");
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 log = std.log.scoped(.PsrTransfer);

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Bus = @import("../../Bus.zig");
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;

View File

@ -4,7 +4,7 @@ const util = @import("../../util.zig");
const shifter = @import("../barrel_shifter.zig");
const Bus = @import("../../Bus.zig");
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;

View File

@ -1,6 +1,6 @@
const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ArmInstrFn;
const InstrFn = @import("../../cpu.zig").arm.InstrFn;
pub fn armSoftwareInterrupt() InstrFn {
return struct {

View File

@ -1,6 +1,6 @@
const Bus = @import("../../Bus.zig");
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 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 rotateRight = @import("../barrel_shifter.zig").rotateRight;
pub fn format4(comptime op: u4) InstrFn {
pub fn fmt4(comptime op: u4) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const rs = opcode >> 3 & 0x7;

View File

@ -1,8 +1,8 @@
const Bus = @import("../../Bus.zig");
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 {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
const count = @boolToInt(R) + countRlist(opcode);
@ -46,7 +46,7 @@ pub fn format14(comptime L: bool, comptime R: bool) InstrFn {
}.inner;
}
pub fn format15(comptime L: bool, comptime rb: u3) InstrFn {
pub fn fmt15(comptime L: bool, comptime rb: u3) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
var address = cpu.r[rb];

View File

@ -1,11 +1,11 @@
const Bus = @import("../../Bus.zig");
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
const InstrFn = @import("../../cpu.zig").ThumbInstrFn;
const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
const checkCond = @import("../../cpu.zig").checkCond;
const sext = @import("../../util.zig").sext;
pub fn format16(comptime cond: u4) InstrFn {
pub fn fmt16(comptime cond: u4) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// B
@ -20,7 +20,7 @@ pub fn format16(comptime cond: u4) InstrFn {
}.inner;
}
pub fn format18() InstrFn {
pub fn fmt18() InstrFn {
return struct {
// B but conditional
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
@ -30,7 +30,7 @@ pub fn format18() InstrFn {
}.inner;
}
pub fn format19(comptime is_low: bool) InstrFn {
pub fn fmt19(comptime is_low: bool) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// BL

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Bus = @import("../../Bus.zig");
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 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);
pub fn format1(comptime op: u2, comptime offset: u5) InstrFn {
pub fn fmt1(comptime op: u2, comptime offset: u5) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const rs = opcode >> 3 & 0x7;
@ -55,7 +55,37 @@ pub fn format1(comptime op: u2, comptime offset: u5) InstrFn {
}.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 {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const rs = opcode >> 3 & 0x7;
@ -80,7 +110,7 @@ pub fn format2(comptime I: bool, is_sub: bool, rn: u3) InstrFn {
}.inner;
}
pub fn format3(comptime op: u2, comptime rd: u3) InstrFn {
pub fn fmt3(comptime op: u2, comptime rd: u3) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
const offset = @truncate(u8, opcode);
@ -99,18 +129,18 @@ pub fn format3(comptime op: u2, comptime rd: u3) InstrFn {
}.inner;
}
pub fn format12(comptime SP: bool, comptime rd: u3) InstrFn {
pub fn fmt12(comptime isSP: bool, comptime rd: u3) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// ADD
const left = if (SP) cpu.r[13] else cpu.r[15] & ~@as(u32, 2);
const left = if (isSP) cpu.r[13] else cpu.r[15] & ~@as(u32, 2);
const right = (opcode & 0xFF) << 2;
cpu.r[rd] = left + right;
}
}.inner;
}
pub fn format13(comptime S: bool) InstrFn {
pub fn fmt13(comptime S: bool) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, _: *Bus, opcode: u16) void {
// ADD

View File

@ -2,11 +2,11 @@ const std = @import("std");
const Bus = @import("../../Bus.zig");
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;
pub fn format6(comptime rd: u3) InstrFn {
pub fn fmt6(comptime rd: u3) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
// LDR
@ -20,7 +20,7 @@ pub fn format6(comptime rd: u3) InstrFn {
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 {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
const ro = opcode >> 6 & 0x7;
@ -80,7 +80,7 @@ pub fn format78(comptime op: u2, comptime T: bool) InstrFn {
}.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 {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
const rb = opcode >> 3 & 0x7;
@ -112,7 +112,7 @@ pub fn format9(comptime B: bool, comptime L: bool, comptime offset: u5) InstrFn
}.inner;
}
pub fn format10(comptime L: bool, comptime offset: u5) InstrFn {
pub fn fmt10(comptime L: bool, comptime offset: u5) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
const rb = opcode >> 3 & 0x7;
@ -132,7 +132,7 @@ pub fn format10(comptime L: bool, comptime offset: u5) InstrFn {
}.inner;
}
pub fn format11(comptime L: bool, comptime rd: u3) InstrFn {
pub fn fmt11(comptime L: bool, comptime rd: u3) InstrFn {
return struct {
fn inner(cpu: *Arm7tdmi, bus: *Bus, opcode: u16) void {
const offset = (opcode & 0xFF) << 2;

View File

@ -1,8 +1,8 @@
const Bus = @import("../../Bus.zig");
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 {
fn inner(cpu: *Arm7tdmi, _: *Bus, _: u16) void {
// Copy Values from Current Mode

View File

@ -5,12 +5,16 @@ const Bus = @import("Bus.zig");
const Scheduler = @import("scheduler.zig").Scheduler;
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
const FpsTracker = @import("util.zig").FpsTracker;
const FilePaths = @import("util.zig").FilePaths;
const Timer = std.time.Timer;
const Thread = std.Thread;
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)
const cycles_per_frame: u64 = 228 * (308 * 4); //280896
@ -35,10 +39,10 @@ const RunKind = enum {
LimitedBusy,
};
pub fn run(kind: RunKind, quit: *Atomic(bool), fps: *FpsTracker, sched: *Scheduler, cpu: *Arm7tdmi) void {
if (audio_sync) log.info("Audio sync enabled", .{});
pub fn run(quit: *Atomic(bool), fps: *FpsTracker, sched: *Scheduler, cpu: *Arm7tdmi) void {
if (sync_audio) log.info("Audio sync enabled", .{});
switch (kind) {
switch (sync_video) {
.Unlimited => runUnsynchronized(quit, sched, cpu, null),
.Limited => runSynchronized(quit, sched, cpu, null),
.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 {
const frame_end = sched.tick + cycles_per_frame;
while (true) {
while (sched.tick < std.math.min(frame_end, sched.nextTimestamp())) {
if (cpu.stepDmaTransfer()) continue; // DMA is blocking, ticks scheduler
if (!cpu.isHalted()) cpu.step() else sched.tick += 1;
while (sched.tick < frame_end) {
if (!cpu.stepDmaTransfer()) {
if (cpu.isHalted()) {
// Fast-forward to next Event
sched.tick = sched.queue.peek().?.tick;
} else {
cpu.step();
}
}
if (sched.tick >= frame_end) break;
sched.handleEvent(cpu);
if (sched.tick >= sched.nextTimestamp()) sched.handleEvent(cpu);
}
}
@ -77,14 +84,14 @@ pub fn runUnsynchronized(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi,
while (!quit.load(.SeqCst)) {
runFrame(sched, cpu);
if (audio_sync) syncToAudio(cpu);
if (sync_audio) syncToAudio(cpu);
tracker.tick();
}
} else {
while (!quit.load(.SeqCst)) {
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
// the amount of time needed for audio to catch up rather than
// 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;
tracker.tick();
@ -115,7 +122,7 @@ pub fn runSynchronized(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, f
runFrame(sched, cpu);
const new_wake_time = syncToVideo(&timer, wake_time);
// 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;
}

View File

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

@ -1,6 +1,7 @@
const std = @import("std");
const builtin = @import("builtin");
const Log2Int = std.math.Log2Int;
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
// Sign-Extend value of type `T` to type `U`
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);
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,42 +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_mask = if (src_idx == 0xF) ~@as(u32, 1) else ~@as(u32, 0);
const dst_mask = if (dst_idx == 0xF) ~@as(u32, 1) else ~@as(u32, 0);
const src = cpu.r[src_idx] & src_mask;
const dst = cpu.r[dst_idx] & dst_mask;
switch (op) {
0b00 => {
// ADD
const sum = add(false, cpu, dst, src);
cpu.r[dst_idx] = sum & dst_mask;
},
0b01 => cmp(cpu, dst, src), // CMP
0b10 => {
// MOV
cpu.r[dst_idx] = src & dst_mask;
},
0b11 => {
// BX
cpu.cpsr.t.write(src & 1 == 1);
cpu.r[15] = src & ~@as(u32, 1);
cpu.pipe.flush();
},
}
if (dst_idx == 0xF) cpu.pipe.flush();
}
}.inner;
}

View File

@ -1,35 +1,23 @@
const std = @import("std");
const builtin = @import("builtin");
const SDL = @import("sdl2");
const clap = @import("clap");
const known_folders = @import("known_folders");
const clap = @import("clap");
const emu = @import("emu.zig");
const Bus = @import("Bus.zig");
const Apu = @import("apu.zig").Apu;
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
const Scheduler = @import("scheduler.zig").Scheduler;
const FpsTracker = @import("util.zig").FpsTracker;
const Gui = @import("Gui.zig");
const Bus = @import("core/Bus.zig");
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
const Scheduler = @import("core/scheduler.zig").Scheduler;
const FilePaths = @import("core/util.zig").FilePaths;
const Timer = std.time.Timer;
const Thread = std.Thread;
const Atomic = std.atomic.Atomic;
const File = std.fs.File;
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 = true;
const is_binary: bool = false;
const log = std.log.scoped(.GUI);
const Allocator = std.mem.Allocator;
const log = std.log.scoped(.CLI);
const width = @import("core/ppu.zig").width;
const height = @import("core/ppu.zig").height;
const arm7tdmi_logging = @import("core/emu.zig").cpu_logging;
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
const params = clap.parseParamsComptime(
@ -40,251 +28,78 @@ const params = clap.parseParamsComptime(
);
pub fn main() anyerror!void {
// Allocator for Emulator + CLI
// Main Allocator for ZBA
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(!gpa.deinit());
const alloc = gpa.allocator();
const allocator = gpa.allocator();
// Setup CLI using zig-clap
var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{});
defer res.deinit();
// Handle CLI Input
const result = try clap.parse(clap.Help, &params, clap.parsers.default, .{});
defer result.deinit();
const stderr = std.io.getStdErr();
defer stderr.close();
const paths = try handleArguments(allocator, &result);
defer if (paths.save) |path| allocator.free(path);
// Display Help, if requested
// Grab ROM and BIOS paths if provided
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);
// TODO: Take Emulator Init Code out of main.zig
var scheduler = Scheduler.init(allocator);
defer scheduler.deinit();
const paths = .{ .bios = bios_path, .rom = rom_path, .save = save_dir };
var cpu = try Arm7tdmi.init(alloc, &scheduler, paths);
defer cpu.deinit();
cpu.bus.attach(&cpu);
cpu.fastBoot(); // Uncomment to skip BIOS
var bus = try Bus.init(allocator, &scheduler, paths);
defer bus.deinit();
// Copy ROM title while Emulator still belongs to this thread
const title = cpu.bus.pak.title;
var arm7tdmi = Arm7tdmi.init(&scheduler, &bus);
// Initialize SDL2
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;
const log_file: ?std.fs.File = if (arm7tdmi_logging) try std.fs.cwd().createFile("zba.log", .{}) else null;
defer if (log_file) |file| file.close();
var quit = Atomic(bool).init(false);
var emu_rate = FpsTracker.init();
if (log_file) |file| arm7tdmi.attach(file);
bus.attach(&arm7tdmi); // TODO: Shrink Surface (only CPSR and r15?)
if (paths.bios == null) arm7tdmi.fastBoot();
// Run Emulator in it's separate thread
// From this point on, interacting with Arm7tdmi or Scheduler
// be justified, as it will require to be thread-afe
const emu_thread = try Thread.spawn(.{}, emu.run, .{ .LimitedFPS, &quit, &emu_rate, &scheduler, &cpu });
defer emu_thread.join();
var gui = Gui.init(bus.pak.title, width, height);
gui.initAudio(&bus.apu);
defer gui.deinit();
var title_buf: [0x20]u8 = std.mem.zeroes([0x20]u8);
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 => {},
}
try gui.run(&arm7tdmi, &scheduler);
}
// 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{
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 {
fn getSavePath(allocator: Allocator) !?[]const u8 {
const save_subpath = "zba" ++ [_]u8{std.fs.path.sep} ++ "save";
const maybe_data_path = try known_folders.getPath(alloc, .data);
defer if (maybe_data_path) |path| alloc.free(path);
const maybe_data_path = try known_folders.getPath(allocator, .data);
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 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
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);
}
return save_path;
}
fn getRomPath(res: clap.Result(clap.Help, &params, clap.parsers.default), stderr: std.fs.File) ![]const u8 {
return switch (res.positionals.len) {
1 => res.positionals[0],
0 => {
try stderr.writeAll("ZBA requires a positional path to a GamePak ROM.\n");
return CliError.InsufficientOptions;
},
else => {
try stderr.writeAll("ZBA received too many arguments.\n");
return CliError.UnneededOptions;
},
fn getRomPath(result: *const clap.Result(clap.Help, &params, clap.parsers.default)) ![]const u8 {
return switch (result.positionals.len) {
1 => result.positionals[0],
0 => std.debug.panic("ZBA requires a positional path to a GamePak ROM.\n", .{}),
else => std.debug.panic("ZBA received too many arguments.\n", .{}),
};
}
pub fn handleArguments(allocator: Allocator, result: *const clap.Result(clap.Help, &params, clap.parsers.default)) !FilePaths {
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,
};
}