Compare commits
8 Commits
ade188bc3c
...
4fb9651d5a
Author | SHA1 | Date |
---|---|---|
Rekai Nyangadzayi Musuka | 4fb9651d5a | |
Rekai Nyangadzayi Musuka | e26fbea0ad | |
Rekai Nyangadzayi Musuka | d61fa9848f | |
Rekai Nyangadzayi Musuka | 2c8616f610 | |
Rekai Nyangadzayi Musuka | 53eec5c3ff | |
Rekai Nyangadzayi Musuka | c397b7069d | |
Rekai Nyangadzayi Musuka | 9d037fdc3e | |
Rekai Nyangadzayi Musuka | 53191b0eeb |
|
@ -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));
|
||||||
|
}
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 });
|
|
||||||
}
|
|
|
@ -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 {
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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 {
|
|
@ -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);
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
|
@ -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;
|
|
@ -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];
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 });
|
|
@ -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;
|
|
||||||
}
|
|
301
src/main.zig
301
src/main.zig
|
@ -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, ¶ms, clap.parsers.default, .{});
|
const result = try clap.parse(clap.Help, ¶ms, 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, ¶ms, .{});
|
|
||||||
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, ¶ms, clap.parsers.default), stderr: std.fs.File) ![]const u8 {
|
fn getRomPath(result: *const clap.Result(clap.Help, ¶ms, 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, ¶ms, 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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue