Compare commits

...

8 Commits

34 changed files with 728 additions and 646 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

@ -481,20 +481,20 @@ pub const WinV = extern union {
pub const WinIn = extern union { pub const WinIn = extern union {
w0_bg: Bitfield(u16, 0, 4), w0_bg: Bitfield(u16, 0, 4),
w0_obj: Bit(u16, 4), w0_obj: Bit(u16, 4),
w0_colour: Bit(u16, 5), w0_bld: Bit(u16, 5),
w1_bg: Bitfield(u16, 8, 4), w1_bg: Bitfield(u16, 8, 4),
w1_obj: Bit(u16, 12), w1_obj: Bit(u16, 12),
w1_colour: Bit(u16, 13), w1_bld: Bit(u16, 13),
raw: u16, raw: u16,
}; };
pub const WinOut = extern union { pub const WinOut = extern union {
out_bg: Bitfield(u16, 0, 4), out_bg: Bitfield(u16, 0, 4),
out_obj: Bit(u16, 4), out_obj: Bit(u16, 4),
out_colour: Bit(u16, 5), out_bld: Bit(u16, 5),
obj_bg: Bitfield(u16, 8, 4), obj_bg: Bitfield(u16, 8, 4),
obj_obj: Bit(u16, 12), obj_obj: Bit(u16, 12),
obj_colour: Bit(u16, 13), obj_bld: Bit(u16, 13),
raw: u16, raw: u16,
}; };

View File

@ -6,54 +6,227 @@ 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 {
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 psrTransfer = @import("cpu/arm/psr_transfer.zig").psrTransfer;
const singleDataTransfer = @import("cpu/arm/single_data_transfer.zig").singleDataTransfer; const transfer = @import("cpu/arm/single_data_transfer.zig").singleDataTransfer;
const halfAndSignedDataTransfer = @import("cpu/arm/half_signed_data_transfer.zig").halfAndSignedDataTransfer; const halfSignedTransfer = @import("cpu/arm/half_signed_data_transfer.zig").halfAndSignedDataTransfer;
const blockDataTransfer = @import("cpu/arm/block_data_transfer.zig").blockDataTransfer; const blockTransfer = @import("cpu/arm/block_data_transfer.zig").blockDataTransfer;
const branch = @import("cpu/arm/branch.zig").branch; const branch = @import("cpu/arm/branch.zig").branch;
const branchAndExchange = @import("cpu/arm/branch.zig").branchAndExchange; const branchExchange = @import("cpu/arm/branch.zig").branchAndExchange;
const armSoftwareInterrupt = @import("cpu/arm/software_interrupt.zig").armSoftwareInterrupt; const swi = @import("cpu/arm/software_interrupt.zig").armSoftwareInterrupt;
const singleDataSwap = @import("cpu/arm/single_data_swap.zig").singleDataSwap; const swap = @import("cpu/arm/single_data_swap.zig").singleDataSwap;
const multiply = @import("cpu/arm/multiply.zig").multiply; const multiply = @import("cpu/arm/multiply.zig").multiply;
const multiplyLong = @import("cpu/arm/multiply.zig").multiplyLong; const multiplyLong = @import("cpu/arm/multiply.zig").multiplyLong;
// THUMB Instruction Groups // Undefined ARM Instruction handler
const format1 = @import("cpu/thumb/data_processing.zig").format1; fn und(cpu: *Arm7tdmi, _: *Bus, opcode: u32) void {
const format2 = @import("cpu/thumb/data_processing.zig").format2; const id = armIdx(opcode);
const format3 = @import("cpu/thumb/data_processing.zig").format3; cpu.panic("[CPU/Decode] ID: 0x{X:0>3} 0x{X:0>8} is an illegal opcode", .{ id, opcode });
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; fn populate() [0x1000]InstrFn {
const format5 = @import("cpu/thumb/processing_branch.zig").format5; return comptime {
@setEvalBranchQuota(0xE000);
var ret = [_]InstrFn{und} ** 0x1000;
const format6 = @import("cpu/thumb/data_transfer.zig").format6; var i: usize = 0;
const format78 = @import("cpu/thumb/data_transfer.zig").format78; while (i < ret.len) : (i += 1) {
const format9 = @import("cpu/thumb/data_transfer.zig").format9; ret[i] = switch (@as(u2, i >> 10)) {
const format10 = @import("cpu/thumb/data_transfer.zig").format10; 0b00 => if (i == 0x121) blk: {
const format11 = @import("cpu/thumb/data_transfer.zig").format11; break :blk branchExchange;
const format14 = @import("cpu/thumb/block_data_transfer.zig").format14; } else if (i & 0xFCF == 0x009) blk: {
const format15 = @import("cpu/thumb/block_data_transfer.zig").format15; 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; return ret;
const format18 = @import("cpu/thumb/branch.zig").format18; };
const format19 = @import("cpu/thumb/branch.zig").format19; }
};
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; const processing = @import("cpu/thumb/data_processing.zig");
pub const ThumbInstrFn = fn (*Arm7tdmi, *Bus, u16) void; const alu = @import("cpu/thumb/alu.zig").fmt4;
const arm_lut: [0x1000]ArmInstrFn = armPopulate(); const transfer = @import("cpu/thumb/data_transfer.zig");
const thumb_lut: [0x400]ThumbInstrFn = thumbPopulate(); 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); const log = std.log.scoped(.Arm7Tdmi);
pub const Arm7tdmi = struct { pub const Arm7tdmi = struct {
@ -61,7 +234,7 @@ pub const Arm7tdmi = struct {
r: [16]u32, r: [16]u32,
sched: *Scheduler, sched: *Scheduler,
bus: Bus, bus: *Bus,
cpsr: PSR, cpsr: PSR,
spsr: PSR, spsr: PSR,
@ -75,33 +248,24 @@ 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,
.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 {
@ -258,15 +422,15 @@ 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()) {
const opcode = self.fetch(u16); const opcode = self.fetch(u16);
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 {
const opcode = self.fetch(u32); const opcode = self.fetch(u32);
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);
} }
} }
} }
@ -341,14 +505,6 @@ pub const Arm7tdmi = struct {
return self.r[15] + 4; return self.r[15] + 4;
} }
fn debug_log(self: *const Self, file: *const File, opcode: u32) void {
if (self.binary_log) {
self.skyLog(file) catch unreachable;
} else {
self.mgbaLog(file, opcode) catch unreachable;
}
}
pub fn panic(self: *const Self, comptime format: []const u8, args: anytype) noreturn { pub fn panic(self: *const Self, comptime format: []const u8, args: anytype) noreturn {
var i: usize = 0; var i: usize = 0;
while (i < 16) : (i += 4) { while (i < 16) : (i += 4) {
@ -406,25 +562,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";
@ -497,178 +634,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;
};
}
pub const PSR = extern union { pub const PSR = extern union {
mode: Bitfield(u32, 0, 5), mode: Bitfield(u32, 0, 5),
t: Bit(u32, 5), t: Bit(u32, 5),
@ -703,13 +668,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 {

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;

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;

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;

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;

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 armSoftwareInterrupt() InstrFn { pub fn armSoftwareInterrupt() InstrFn {
return struct { return struct {

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);
@ -45,7 +45,7 @@ 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];

View File

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

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,7 +129,7 @@ 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
@ -111,7 +141,7 @@ pub fn format12(comptime isSP: bool, comptime rd: u3) InstrFn {
}.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,11 +2,11 @@ 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
@ -18,7 +18,7 @@ pub fn format6(comptime rd: u3) InstrFn {
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 +78,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 +110,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 +130,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,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 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

View File

@ -5,13 +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 sync_audio = false; const sync_audio = false;
const sync_video: RunKind = .UnlimitedFPS; 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

View File

@ -278,16 +278,17 @@ pub const Ppu = struct {
aff_x += self.aff_bg[n - 2].pa; aff_x += self.aff_bg[n - 2].pa;
aff_y += self.aff_bg[n - 2].pc; aff_y += self.aff_bg[n - 2].pc;
if (!shouldDrawBackground(n, self.bldcnt, &self.scanline, i)) continue; const x = @bitCast(u32, ix);
const y = @bitCast(u32, iy);
const win_bounds = self.windowBounds(@truncate(u9, x), @truncate(u8, y));
if (!shouldDrawBackground(self, n, win_bounds, i)) continue;
if (self.bg[n].cnt.display_overflow.read()) { if (self.bg[n].cnt.display_overflow.read()) {
ix = if (ix > px_width) @rem(ix, px_width) else if (ix < 0) px_width + @rem(ix, px_width) else ix; ix = if (ix > px_width) @rem(ix, px_width) else if (ix < 0) px_width + @rem(ix, px_width) else ix;
iy = if (iy > px_height) @rem(iy, px_height) else if (iy < 0) px_height + @rem(iy, px_height) else iy; iy = if (iy > px_height) @rem(iy, px_height) else if (iy < 0) px_height + @rem(iy, px_height) else iy;
} else if (ix > px_width or iy > px_height or ix < 0 or iy < 0) continue; } else if (ix > px_width or iy > px_height or ix < 0 or iy < 0) continue;
const x = @bitCast(u32, ix);
const y = @bitCast(u32, iy);
const tile_id: u32 = self.vram.read(u8, screen_base + ((y / 8) * @bitCast(u32, tile_width) + (x / 8))); const tile_id: u32 = self.vram.read(u8, screen_base + ((y / 8) * @bitCast(u32, tile_width) + (x / 8)));
const row = y & 7; const row = y & 7;
const col = x & 7; const col = x & 7;
@ -297,7 +298,7 @@ pub const Ppu = struct {
if (pal_id != 0) { if (pal_id != 0) {
const bgr555 = self.palette.read(u16, pal_id * 2); const bgr555 = self.palette.read(u16, pal_id * 2);
copyToBackgroundBuffer(n, self.bldcnt, &self.scanline, i, bgr555); self.copyToBackgroundBuffer(n, win_bounds, i, bgr555);
} }
} }
@ -306,7 +307,7 @@ pub const Ppu = struct {
self.aff_bg[n - 2].y_latch.? += self.aff_bg[n - 2].pd; // PD is added to BGxY self.aff_bg[n - 2].y_latch.? += self.aff_bg[n - 2].pd; // PD is added to BGxY
} }
fn drawBackround(self: *Self, comptime n: u2) void { fn drawBackground(self: *Self, comptime n: u2) void {
// A Tile in a charblock is a byte, while a Screen Entry is a halfword // A Tile in a charblock is a byte, while a Screen Entry is a halfword
const char_base = 0x4000 * @as(u32, self.bg[n].cnt.char_base.read()); const char_base = 0x4000 * @as(u32, self.bg[n].cnt.char_base.read());
@ -326,10 +327,11 @@ pub const Ppu = struct {
var i: u32 = 0; var i: u32 = 0;
while (i < width) : (i += 1) { while (i < width) : (i += 1) {
if (!shouldDrawBackground(n, self.bldcnt, &self.scanline, i)) continue;
const x = hofs + i; const x = hofs + i;
const win_bounds = self.windowBounds(@truncate(u9, x), @truncate(u8, y));
if (!shouldDrawBackground(self, n, win_bounds, i)) continue;
// Grab the Screen Entry from VRAM // Grab the Screen Entry from VRAM
const entry_addr = screen_base + tilemapOffset(size, x, y); const entry_addr = screen_base + tilemapOffset(size, x, y);
const entry = @bitCast(ScreenEntry, self.vram.read(u16, entry_addr)); const entry = @bitCast(ScreenEntry, self.vram.read(u16, entry_addr));
@ -354,7 +356,7 @@ pub const Ppu = struct {
if (pal_id != 0) { if (pal_id != 0) {
const bgr555 = self.palette.read(u16, pal_id * 2); const bgr555 = self.palette.read(u16, pal_id * 2);
copyToBackgroundBuffer(n, self.bldcnt, &self.scanline, i, bgr555); self.copyToBackgroundBuffer(n, win_bounds, i, bgr555);
} }
} }
} }
@ -380,10 +382,10 @@ pub const Ppu = struct {
var layer: usize = 0; var layer: usize = 0;
while (layer < 4) : (layer += 1) { while (layer < 4) : (layer += 1) {
self.drawSprites(@truncate(u2, layer)); self.drawSprites(@truncate(u2, layer));
if (layer == self.bg[0].cnt.priority.read() and bg_enable & 1 == 1) self.drawBackround(0); if (layer == self.bg[0].cnt.priority.read() and bg_enable & 1 == 1) self.drawBackground(0);
if (layer == self.bg[1].cnt.priority.read() and bg_enable >> 1 & 1 == 1) self.drawBackround(1); if (layer == self.bg[1].cnt.priority.read() and bg_enable >> 1 & 1 == 1) self.drawBackground(1);
if (layer == self.bg[2].cnt.priority.read() and bg_enable >> 2 & 1 == 1) self.drawBackround(2); if (layer == self.bg[2].cnt.priority.read() and bg_enable >> 2 & 1 == 1) self.drawBackground(2);
if (layer == self.bg[3].cnt.priority.read() and bg_enable >> 3 & 1 == 1) self.drawBackround(3); if (layer == self.bg[3].cnt.priority.read() and bg_enable >> 3 & 1 == 1) self.drawBackground(3);
} }
// Copy Drawn Scanline to Frame Buffer // Copy Drawn Scanline to Frame Buffer
@ -408,8 +410,8 @@ pub const Ppu = struct {
var layer: usize = 0; var layer: usize = 0;
while (layer < 4) : (layer += 1) { while (layer < 4) : (layer += 1) {
self.drawSprites(@truncate(u2, layer)); self.drawSprites(@truncate(u2, layer));
if (layer == self.bg[0].cnt.priority.read() and bg_enable & 1 == 1) self.drawBackround(0); if (layer == self.bg[0].cnt.priority.read() and bg_enable & 1 == 1) self.drawBackground(0);
if (layer == self.bg[1].cnt.priority.read() and bg_enable >> 1 & 1 == 1) self.drawBackround(1); if (layer == self.bg[1].cnt.priority.read() and bg_enable >> 1 & 1 == 1) self.drawBackground(1);
if (layer == self.bg[2].cnt.priority.read() and bg_enable >> 2 & 1 == 1) self.drawAffineBackground(2); if (layer == self.bg[2].cnt.priority.read() and bg_enable >> 2 & 1 == 1) self.drawAffineBackground(2);
} }
@ -534,6 +536,93 @@ pub const Ppu = struct {
return self.palette.getBackdrop(); return self.palette.getBackdrop();
} }
fn copyToBackgroundBuffer(self: *Self, comptime n: u2, bounds: ?WindowBounds, i: usize, bgr555: u16) void {
if (self.bldcnt.mode.read() != 0b00) {
// Standard Alpha Blending
const a_layers = self.bldcnt.layer_a.read();
const is_blend_enabled = (a_layers >> n) & 1 == 1;
// If Alpha Blending is enabled and we've found an eligible layer for
// Pixel A, store the pixel in the bottom pixel buffer
const win_part = if (bounds) |win| blk: {
// Window Enabled
break :blk switch (win) {
.win0 => self.win.in.w0_bld.read(),
.win1 => self.win.in.w1_bld.read(),
.out => self.win.out.out_bld.read(),
};
} else true;
if (win_part and is_blend_enabled) {
self.scanline.btm()[i] = bgr555;
return;
}
}
self.scanline.top()[i] = bgr555;
}
const WindowBounds = enum { win0, win1, out };
fn windowBounds(self: *Self, x: u9, y: u8) ?WindowBounds {
const win0 = self.dispcnt.win_enable.read() & 1 == 1;
const win1 = (self.dispcnt.win_enable.read() >> 1) & 1 == 1;
const winObj = self.dispcnt.obj_win_enable.read();
if (!(win0 or win1 or winObj)) return null;
if (win0 and self.win.inRange(0, x, y)) return .win0;
if (win1 and self.win.inRange(1, x, y)) return .win1;
return .out;
}
fn shouldDrawBackground(self: *Self, comptime n: u2, bounds: ?WindowBounds, i: usize) bool {
// If a pixel has been drawn on the top layer, it's because:
// 1. The pixel is to be blended with a pixel on the bottom layer
// 2. The pixel is not to be blended at all
// Also, if we find a pixel on the top layer we don't need to bother with this I think?
if (self.scanline.top()[i] != null) return false;
if (bounds) |win| {
switch (win) {
.win0 => if ((self.win.in.w0_bg.read() >> n) & 1 == 0) return false,
.win1 => if ((self.win.in.w1_bg.read() >> n) & 1 == 0) return false,
.out => if ((self.win.out.out_bg.read() >> n) & 1 == 0) return false,
}
}
if (self.scanline.btm()[i] != null) {
// The pixel found in the bottom layer is:
// 1. From a higher priority background
// 2. From a background that is marked for blending (Pixel A)
// If Alpha Blending isn't enabled, then we've already found a higher prio
// pixel, we can return early
if (self.bldcnt.mode.read() != 0b01) return false;
const b_layers = self.bldcnt.layer_b.read();
const win_part = if (bounds) |win| blk: {
// Window Enabled
break :blk switch (win) {
.win0 => self.win.in.w0_bld.read(),
.win1 => self.win.in.w1_bld.read(),
.out => self.win.out.out_bld.read(),
};
} else true;
// If the Background is not marked for blending, we've already found
// a higher priority pixel, move on.
const is_blend_enabled = win_part and ((b_layers >> n) & 1 == 1);
if (!is_blend_enabled) return false;
}
return true;
}
// TODO: Comment this + get a better understanding // TODO: Comment this + get a better understanding
fn tilemapOffset(size: u2, x: u32, y: u32) u32 { fn tilemapOffset(size: u2, x: u32, y: u32) u32 {
// Current Row: (y % PIXEL_COUNT) / 8 // Current Row: (y % PIXEL_COUNT) / 8
@ -573,7 +662,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 +704,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();
@ -792,6 +881,25 @@ const Window = struct {
}; };
} }
fn inRange(self: *const Self, comptime id: u1, x: u9, y: u8) bool {
const h = self.h[id];
const v = self.v[id];
const y1 = v.y1.read();
const y2 = if (y1 > v.y2.read()) 160 else std.math.min(160, v.y2.read());
if (y1 <= y and y < y2) {
// Within Y bounds
const x1 = h.x1.read();
const x2 = if (x1 > h.x2.read()) 240 else std.math.min(240, h.x2.read());
// Within X Bounds
return x1 <= x and x < x2;
}
return false;
}
pub fn setH(self: *Self, value: u32) void { pub fn setH(self: *Self, value: u32) void {
self.h[0].raw = @truncate(u16, value); self.h[0].raw = @truncate(u16, value);
self.h[1].raw = @truncate(u16, value >> 16); self.h[1].raw = @truncate(u16, value >> 16);
@ -1133,37 +1241,6 @@ fn alphaBlend(top: u16, btm: u16, bldalpha: io.BldAlpha) u16 {
return (bld_b << 10) | (bld_g << 5) | bld_r; return (bld_b << 10) | (bld_g << 5) | bld_r;
} }
fn shouldDrawBackground(comptime n: u2, bldcnt: io.BldCnt, scanline: *Scanline, i: usize) bool {
// If a pixel has been drawn on the top layer, it's because
// Either the pixel is to be blended with a pixel on the bottom layer
// or the pixel is not to be blended at all
// Consequentially, if we find a pixel on the top layer, there's no need
// to render anything I think?
if (scanline.top()[i] != null) return false;
if (scanline.btm()[i] != null) {
// The Pixel found in the Bottom layer is
// 1. From a higher priority
// 2. From a Backround that is marked for Blending (Pixel A)
//
// We now have to confirm whether this current Background can be used
// as Pixel B or not.
// If Alpha Blending isn't enabled, we've aready found a higher
// priority pixel to render. Move on
if (bldcnt.mode.read() != 0b01) return false;
const b_layers = bldcnt.layer_b.read();
const is_blend_enabled = (b_layers >> n) & 1 == 1;
// If the Background is not marked for blending, we've already found
// a higher priority pixel, move on.
if (!is_blend_enabled) return false;
}
return true;
}
fn shouldDrawSprite(bldcnt: io.BldCnt, scanline: *Scanline, x: u9) bool { fn shouldDrawSprite(bldcnt: io.BldCnt, scanline: *Scanline, x: u9) bool {
if (scanline.top()[x] != null) return false; if (scanline.top()[x] != null) return false;
@ -1178,23 +1255,6 @@ fn shouldDrawSprite(bldcnt: io.BldCnt, scanline: *Scanline, x: u9) bool {
return true; return true;
} }
fn copyToBackgroundBuffer(comptime n: u2, bldcnt: io.BldCnt, scanline: *Scanline, i: usize, bgr555: u16) void {
if (bldcnt.mode.read() != 0b00) {
// Standard Alpha Blending
const a_layers = bldcnt.layer_a.read();
const is_blend_enabled = (a_layers >> n) & 1 == 1;
// If Alpha Blending is enabled and we've found an eligible layer for
// Pixel A, store the pixel in the bottom pixel buffer
if (is_blend_enabled) {
scanline.btm()[i] = bgr555;
return;
}
}
scanline.top()[i] = bgr555;
}
fn copyToSpriteBuffer(bldcnt: io.BldCnt, scanline: *Scanline, x: u9, bgr555: u16) void { fn copyToSpriteBuffer(bldcnt: io.BldCnt, scanline: *Scanline, x: u9, bgr555: u16) void {
if (bldcnt.mode.read() != 0b00) { if (bldcnt.mode.read() != 0b00) {
// Alpha Blending // Alpha Blending

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],
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,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, .{ &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 fn getSavePath(allocator: Allocator) !?[]const u8 {
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 {
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,
}; };
} }