515 lines
18 KiB
Zig
515 lines
18 KiB
Zig
const std = @import("std");
|
|
|
|
const Architecture = enum { v4t, v5te };
|
|
const Interpreter = @import("lib.zig").Interpreter;
|
|
const Bus = @import("lib.zig").Bus;
|
|
const Scheduler = @import("lib.zig").Scheduler;
|
|
const Coprocessor = @import("lib.zig").Coprocessor;
|
|
|
|
const Bitfield = @import("bitfield").Bitfield;
|
|
const Bit = @import("bitfield").Bit;
|
|
|
|
fn condition_lut(comptime isa: Architecture) [16]u16 {
|
|
return [_]u16{
|
|
0xF0F0, // EQ - Equal
|
|
0x0F0F, // NE - Not Equal
|
|
0xCCCC, // CS - Unsigned higher or same
|
|
0x3333, // CC - Unsigned lower
|
|
0xFF00, // MI - Negative
|
|
0x00FF, // PL - Positive or Zero
|
|
0xAAAA, // VS - Overflow
|
|
0x5555, // VC - No Overflow
|
|
0x0C0C, // HI - unsigned hierh
|
|
0xF3F3, // LS - unsigned lower or same
|
|
0xAA55, // GE - greater or equal
|
|
0x55AA, // LT - less than
|
|
0x0A05, // GT - greater than
|
|
0xF5FA, // LE - less than or equal
|
|
0xFFFF, // AL - always
|
|
if (isa == .v4t) 0x0000 else 0xFFFF, // NV - never
|
|
};
|
|
}
|
|
|
|
pub fn Arm32(comptime isa: Architecture) type {
|
|
const is_v5te = isa == .v5te;
|
|
|
|
return struct {
|
|
const Self = @This();
|
|
pub const arch = isa;
|
|
|
|
r: [16]u32 = [_]u32{0x00} ** 16,
|
|
pipe: Pipeline = Pipeline.init(),
|
|
sched: Scheduler,
|
|
bus: Bus,
|
|
cpsr: PSR,
|
|
spsr: PSR,
|
|
|
|
bank: Bank = Bank.create(),
|
|
|
|
// The following will be `void` on.v4t but exist on v5te
|
|
itcm: if (is_v5te) Itcm else void,
|
|
dtcm: if (is_v5te) Dtcm else void,
|
|
cp15: if (is_v5te) Coprocessor else void,
|
|
|
|
const arm = switch (isa) {
|
|
.v4t => @import("arm/v4t.zig").arm,
|
|
.v5te => @import("arm/v5te.zig").arm,
|
|
};
|
|
|
|
const thumb = switch (isa) {
|
|
.v4t => @import("arm/v4t.zig").thumb,
|
|
.v5te => @import("arm/v5te.zig").thumb,
|
|
};
|
|
|
|
// FIXME: What about .v5te?
|
|
const Pipeline = struct {
|
|
stage: [2]?u32,
|
|
flushed: bool,
|
|
|
|
fn init() @This() {
|
|
return .{
|
|
.stage = [_]?u32{null} ** 2,
|
|
.flushed = false,
|
|
};
|
|
}
|
|
|
|
pub fn isFull(self: *const @This()) bool {
|
|
return self.stage[0] != null and self.stage[1] != null;
|
|
}
|
|
|
|
// TODO: Why does this not return T?
|
|
pub fn step(self: *@This(), cpu: *Self, comptime T: type) ?u32 {
|
|
comptime std.debug.assert(T == u32 or T == u16);
|
|
|
|
const opcode = self.stage[0];
|
|
self.stage[0] = self.stage[1];
|
|
self.stage[1] = cpu.fetch(T, cpu.r[15]);
|
|
|
|
return opcode;
|
|
}
|
|
|
|
pub fn reload(self: *@This(), cpu: *Self) void {
|
|
if (cpu.cpsr.t.read()) {
|
|
self.stage[0] = cpu.fetch(u16, cpu.r[15]);
|
|
self.stage[1] = cpu.fetch(u16, cpu.r[15] + 2);
|
|
cpu.r[15] += 4;
|
|
} else {
|
|
self.stage[0] = cpu.fetch(u32, cpu.r[15]);
|
|
self.stage[1] = cpu.fetch(u32, cpu.r[15] + 4);
|
|
cpu.r[15] += 8;
|
|
}
|
|
|
|
self.flushed = true;
|
|
}
|
|
};
|
|
|
|
/// Bank of Registers from other CPU Modes
|
|
pub const Bank = struct {
|
|
/// Storage for r13_<mode>, r14_<mode>
|
|
/// e.g. [r13, r14, r13_svc, r14_svc]
|
|
r: [2 * 6]u32,
|
|
|
|
/// Storage for R8_fiq -> R12_fiq and their normal counterparts
|
|
/// e.g [r[0 + 8], fiq_r[0 + 8], r[1 + 8], fiq_r[1 + 8]...]
|
|
fiq: [2 * 5]u32,
|
|
|
|
spsr: [5]PSR,
|
|
|
|
const Kind = enum(u1) {
|
|
R13 = 0,
|
|
R14,
|
|
};
|
|
|
|
pub fn create() Bank {
|
|
return .{
|
|
.r = [_]u32{0x00} ** 12,
|
|
.fiq = [_]u32{0x00} ** 10,
|
|
.spsr = [_]PSR{.{ .raw = 0x0000_0000 }} ** 5,
|
|
};
|
|
}
|
|
|
|
// public so that we can set up fast-boot
|
|
pub inline fn regIdx(mode: Mode, kind: Kind) usize {
|
|
const idx: usize = switch (mode) {
|
|
.User, .System => 0,
|
|
.Supervisor => 1,
|
|
.Abort => 2,
|
|
.Undefined => 3,
|
|
.Irq => 4,
|
|
.Fiq => 5,
|
|
};
|
|
|
|
return (idx * 2) + if (kind == .R14) @as(usize, 1) else 0;
|
|
}
|
|
|
|
inline fn spsrIdx(mode: Mode) usize {
|
|
return switch (mode) {
|
|
.Supervisor => 0,
|
|
.Abort => 1,
|
|
.Undefined => 2,
|
|
.Irq => 3,
|
|
.Fiq => 4,
|
|
else => std.debug.panic("[CPU/Mode] {} does not have a SPSR Register", .{mode}),
|
|
};
|
|
}
|
|
|
|
inline fn fiqIdx(i: usize, mode: Mode) usize {
|
|
return (i * 2) + if (mode == .Fiq) @as(usize, 1) else 0;
|
|
}
|
|
};
|
|
|
|
// FIXME: Is this a hack or idiomatic?
|
|
// See https://github.com/ziglang/zig/blob/1a0e6bcdb140c844384d62b78a7f4247753f9ffd/lib/std/atomic/Atomic.zig#L156-L176
|
|
pub usingnamespace if (is_v5te) struct {
|
|
// FIXME: this is pretty NDS9 specific lol
|
|
pub fn init(scheduler: Scheduler, bus: Bus, cp15: Coprocessor) Self {
|
|
return .{
|
|
.sched = scheduler,
|
|
.bus = bus,
|
|
.cpsr = .{ .raw = 0x0000_001F },
|
|
.spsr = .{ .raw = 0x0000_0000 },
|
|
|
|
.cp15 = cp15,
|
|
.dtcm = .{},
|
|
.itcm = .{},
|
|
};
|
|
}
|
|
|
|
// FIXME: Resetting disables logging (if enabled)
|
|
pub fn reset(self: *Self) void {
|
|
self.* = .{
|
|
.sched = self.sched,
|
|
.bus = self.bus,
|
|
.cpsr = .{ .raw = 0x0000_001F },
|
|
.spsr = .{ .raw = 0x0000_0000 },
|
|
|
|
.dtcm = .{},
|
|
.itcm = .{},
|
|
.cp15 = self.cp15,
|
|
};
|
|
}
|
|
} else struct {
|
|
pub fn init(scheduler: Scheduler, bus: Bus) Self {
|
|
return .{
|
|
.sched = scheduler,
|
|
.bus = bus,
|
|
.cpsr = .{ .raw = 0x0000_001F },
|
|
.spsr = .{ .raw = 0x0000_0000 },
|
|
|
|
.cp15 = {},
|
|
.dtcm = {},
|
|
.itcm = {},
|
|
};
|
|
}
|
|
|
|
// FIXME: Resetting disables logging (if enabled)
|
|
pub fn reset(self: *Self) void {
|
|
self.* = .{
|
|
.sched = self.sched,
|
|
.bus = self.bus,
|
|
.cpsr = .{ .raw = 0x0000_001F },
|
|
.spsr = .{ .raw = 0x0000_0000 },
|
|
|
|
.dtcm = {},
|
|
.itcm = {},
|
|
.cp15 = {},
|
|
};
|
|
}
|
|
};
|
|
|
|
// CPU needs it's own read/write fns due to ICTM and DCTM present in v5te
|
|
// I considered implementing Bus.cpu_read and Bus.cpu_write but ended up considering that a bit too leaky
|
|
pub fn read(self: *Self, comptime T: type, address: u32) T {
|
|
const readInt = std.mem.readIntSliceLittle;
|
|
|
|
if (is_v5te) {
|
|
const dtcm_base = self.dtcm.base_address;
|
|
const dtcm_size = self.dtcm.virt.size;
|
|
|
|
if (address < 0x0000_0000 + self.itcm.virt.size)
|
|
return readInt(T, self.itcm.buf[address & self.itcm.virt.mask ..][0..@sizeOf(T)]);
|
|
|
|
if (dtcm_base <= address and address < dtcm_base + dtcm_size)
|
|
return readInt(T, self.dtcm.buf[address & self.dtcm.virt.mask ..][0..@sizeOf(T)]);
|
|
}
|
|
|
|
return self.bus.read(T, address);
|
|
}
|
|
|
|
pub fn write(self: *Self, comptime T: type, address: u32, value: T) void {
|
|
const writeInt = std.mem.writeIntSliceLittle;
|
|
|
|
if (is_v5te) {
|
|
const dtcm_base = self.dtcm.base_address;
|
|
const dtcm_size = self.dtcm.virt.size;
|
|
|
|
if (address < 0x0000_0000 + self.itcm.virt.size)
|
|
return writeInt(T, self.itcm.buf[address & self.itcm.virt.mask ..][0..@sizeOf(T)], value);
|
|
|
|
if (dtcm_base <= address and address < dtcm_base + dtcm_size)
|
|
return writeInt(T, self.dtcm.buf[address & self.dtcm.virt.mask ..][0..@sizeOf(T)], value);
|
|
}
|
|
|
|
return self.bus.write(T, address, value);
|
|
}
|
|
|
|
pub inline fn hasSPSR(self: *const Self) bool {
|
|
const mode = Mode.getChecked(self, self.cpsr.mode.read());
|
|
return switch (mode) {
|
|
.System, .User => false,
|
|
else => true,
|
|
};
|
|
}
|
|
|
|
pub inline fn isPrivileged(self: *const Self) bool {
|
|
const mode = Mode.getChecked(self, self.cpsr.mode.read());
|
|
return switch (mode) {
|
|
.User => false,
|
|
else => true,
|
|
};
|
|
}
|
|
|
|
pub fn setCpsr(self: *Self, value: u32) void {
|
|
if (value & 0x1F != self.cpsr.raw & 0x1F) self.changeModeFromIdx(@truncate(value & 0x1F));
|
|
self.cpsr.raw = value;
|
|
}
|
|
|
|
fn changeModeFromIdx(self: *Self, next: u5) void {
|
|
self.changeMode(Mode.getChecked(self, next));
|
|
}
|
|
|
|
pub fn setUserModeRegister(self: *Self, idx: usize, value: u32) void {
|
|
const current = Mode.getChecked(self, self.cpsr.mode.read());
|
|
|
|
switch (idx) {
|
|
8...12 => {
|
|
if (current == .Fiq) {
|
|
self.bank.fiq[Bank.fiqIdx(idx - 8, .User)] = value;
|
|
} else self.r[idx] = value;
|
|
},
|
|
13, 14 => switch (current) {
|
|
.User, .System => self.r[idx] = value,
|
|
else => {
|
|
const kind = std.meta.intToEnum(Bank.Kind, idx - 13) catch unreachable;
|
|
self.bank.r[Bank.regIdx(.User, kind)] = value;
|
|
},
|
|
},
|
|
else => self.r[idx] = value, // R0 -> R7 and R15
|
|
}
|
|
}
|
|
|
|
pub fn getUserModeRegister(self: *Self, idx: usize) u32 {
|
|
const current = Mode.getChecked(self, self.cpsr.mode.read());
|
|
|
|
return switch (idx) {
|
|
8...12 => if (current == .Fiq) self.bank.fiq[Bank.fiqIdx(idx - 8, .User)] else self.r[idx],
|
|
13, 14 => switch (current) {
|
|
.User, .System => self.r[idx],
|
|
else => blk: {
|
|
const kind = std.meta.intToEnum(Bank.Kind, idx - 13) catch unreachable;
|
|
break :blk self.bank.r[Bank.regIdx(.User, kind)];
|
|
},
|
|
},
|
|
else => self.r[idx], // R0 -> R7 and R15
|
|
};
|
|
}
|
|
|
|
pub fn changeMode(self: *Self, next: Mode) void {
|
|
const now = Mode.getChecked(self, self.cpsr.mode.read());
|
|
|
|
// Bank R8 -> r12
|
|
for (0..5) |i| {
|
|
self.bank.fiq[Bank.fiqIdx(i, now)] = self.r[8 + i];
|
|
}
|
|
|
|
// Bank r13, r14, SPSR
|
|
switch (now) {
|
|
.User, .System => {
|
|
self.bank.r[Bank.regIdx(now, .R13)] = self.r[13];
|
|
self.bank.r[Bank.regIdx(now, .R14)] = self.r[14];
|
|
},
|
|
else => {
|
|
self.bank.r[Bank.regIdx(now, .R13)] = self.r[13];
|
|
self.bank.r[Bank.regIdx(now, .R14)] = self.r[14];
|
|
self.bank.spsr[Bank.spsrIdx(now)] = self.spsr;
|
|
},
|
|
}
|
|
|
|
// Grab R8 -> R12
|
|
for (0..5) |i| {
|
|
self.r[8 + i] = self.bank.fiq[Bank.fiqIdx(i, next)];
|
|
}
|
|
|
|
// Grab r13, r14, SPSR
|
|
switch (next) {
|
|
.User, .System => {
|
|
self.r[13] = self.bank.r[Bank.regIdx(next, .R13)];
|
|
self.r[14] = self.bank.r[Bank.regIdx(next, .R14)];
|
|
},
|
|
else => {
|
|
self.r[13] = self.bank.r[Bank.regIdx(next, .R13)];
|
|
self.r[14] = self.bank.r[Bank.regIdx(next, .R14)];
|
|
self.spsr = self.bank.spsr[Bank.spsrIdx(next)];
|
|
},
|
|
}
|
|
|
|
self.cpsr.mode.write(@intFromEnum(next));
|
|
}
|
|
|
|
pub fn step(self: *Self) void {
|
|
defer {
|
|
if (!self.pipe.flushed) self.r[15] += if (self.cpsr.t.read()) 2 else @as(u32, 4);
|
|
self.pipe.flushed = false;
|
|
}
|
|
|
|
if (self.cpsr.t.read()) {
|
|
const opcode: u16 = @truncate(self.pipe.step(self, u16) orelse return);
|
|
thumb.lut[thumb.idx(opcode)](self, opcode);
|
|
} else {
|
|
const opcode = self.pipe.step(self, u32) orelse return;
|
|
const cond: u4 = @truncate(opcode >> 28);
|
|
|
|
if (self.cpsr.check(Self.arch, cond)) {
|
|
if (isa == .v5te and cond == 0b1111) {
|
|
std.log.debug("TODO: Unconditional Instruction Extension Space\nopcode: 0x{X:0>8} | idx: 0x{X:} | ptr: {any}", .{ opcode, arm.idx(opcode), arm.lut[arm.idx(opcode)] });
|
|
}
|
|
|
|
arm.lut[arm.idx(opcode)](self, opcode);
|
|
}
|
|
}
|
|
}
|
|
|
|
inline fn fetch(self: *Self, comptime T: type, address: u32) T {
|
|
comptime std.debug.assert(T == u32 or T == u16); // Opcode may be 32-bit (ARM) or 16-bit (THUMB)
|
|
|
|
// Bus.read will advance the scheduler. There are different timings for CPU fetches,
|
|
// so we want to undo what Bus.read will apply. We can do this by caching the current tick
|
|
// This is very dumb.
|
|
//
|
|
// FIXME: Please rework this
|
|
// FIXME: Please Re-enable this
|
|
// const tick_cache = self.sched.tick;
|
|
// defer self.sched.tick = tick_cache + Bus.fetch_timings[@boolToInt(T == u32)][@truncate(u4, address >> 24)];
|
|
|
|
return self.read(T, address);
|
|
}
|
|
|
|
pub fn panic(self: *const Self, comptime format: []const u8, args: anytype) noreturn {
|
|
var i: usize = 0;
|
|
while (i < 16) : (i += 4) {
|
|
const i_1 = i + 1;
|
|
const i_2 = i + 2;
|
|
const i_3 = i + 3;
|
|
std.debug.print("R{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\n", .{ i, self.r[i], i_1, self.r[i_1], i_2, self.r[i_2], i_3, self.r[i_3] });
|
|
}
|
|
std.debug.print("cpsr: 0x{X:0>8} ", .{self.cpsr.raw});
|
|
self.cpsr.toString();
|
|
|
|
std.debug.print("spsr: 0x{X:0>8} ", .{self.spsr.raw});
|
|
self.spsr.toString();
|
|
|
|
std.debug.print("pipeline: {??X:0>8}\n", .{self.pipe.stage});
|
|
|
|
if (self.cpsr.t.read()) {
|
|
const opcode = self.bus.dbgRead(u16, self.r[15] - 4);
|
|
const id = thumb.idx(opcode);
|
|
std.debug.print("opcode: ID: 0x{b:0>10} 0x{X:0>4}\n", .{ id, opcode });
|
|
} else {
|
|
const opcode = self.bus.dbgRead(u32, self.r[15] - 4);
|
|
const id = arm.idx(opcode);
|
|
std.debug.print("opcode: ID: 0x{X:0>3} 0x{X:0>8}\n", .{ id, opcode });
|
|
}
|
|
|
|
std.debug.print("tick: {}\n\n", .{self.sched.now()});
|
|
|
|
std.debug.panic(format, args);
|
|
}
|
|
|
|
pub fn interface(self: *Self) Interpreter {
|
|
return switch (isa) {
|
|
.v4t => .{ .v4t = self },
|
|
.v5te => .{ .v5te = self },
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
fn Tcm(comptime count: usize, comptime default_addr: u32) type {
|
|
const KiB = 0x400;
|
|
|
|
return struct {
|
|
buf: [count * KiB]u8 = [_]u8{0x00} ** (count * KiB),
|
|
base_address: u32 = default_addr,
|
|
virt: struct { size: u32, mask: u32 } = .{ .size = count * KiB, .mask = (count * KiB) - 1 },
|
|
};
|
|
}
|
|
|
|
const Itcm = Tcm(32, 0x0000_0000);
|
|
const Dtcm = Tcm(16, 0x0080_0000); // GBATEK says default is 0x027C_0000...
|
|
|
|
pub const Mode = enum(u5) {
|
|
User = 0b10000,
|
|
Fiq = 0b10001,
|
|
Irq = 0b10010,
|
|
Supervisor = 0b10011,
|
|
Abort = 0b10111,
|
|
Undefined = 0b11011,
|
|
System = 0b11111,
|
|
|
|
pub fn toString(self: Mode) []const u8 {
|
|
return switch (self) {
|
|
.User => "usr",
|
|
.Fiq => "fiq",
|
|
.Irq => "irq",
|
|
.Supervisor => "svc",
|
|
.Abort => "abt",
|
|
.Undefined => "und",
|
|
.System => "sys",
|
|
};
|
|
}
|
|
|
|
fn get(bits: u5) ?Mode {
|
|
return std.meta.intToEnum(Mode, bits) catch null;
|
|
}
|
|
|
|
fn getChecked(cpu: anytype, bits: u5) Mode {
|
|
return get(bits) orelse cpu.panic("[CPU/CPSR] 0b{b:0>5} is an invalid CPU mode", .{bits});
|
|
}
|
|
};
|
|
|
|
pub const PSR = extern union {
|
|
mode: Bitfield(u32, 0, 5),
|
|
t: Bit(u32, 5),
|
|
f: Bit(u32, 6),
|
|
i: Bit(u32, 7),
|
|
|
|
q: Bit(u32, 27), // ARMv5TE only
|
|
v: Bit(u32, 28),
|
|
c: Bit(u32, 29),
|
|
z: Bit(u32, 30),
|
|
n: Bit(u32, 31),
|
|
raw: u32,
|
|
|
|
fn toString(self: @This()) void {
|
|
std.debug.print("[", .{});
|
|
|
|
if (self.n.read()) std.debug.print("N", .{}) else std.debug.print("-", .{});
|
|
if (self.z.read()) std.debug.print("Z", .{}) else std.debug.print("-", .{});
|
|
if (self.c.read()) std.debug.print("C", .{}) else std.debug.print("-", .{});
|
|
if (self.v.read()) std.debug.print("V", .{}) else std.debug.print("-", .{});
|
|
if (self.i.read()) std.debug.print("I", .{}) else std.debug.print("-", .{});
|
|
if (self.f.read()) std.debug.print("F", .{}) else std.debug.print("-", .{});
|
|
if (self.t.read()) std.debug.print("T", .{}) else std.debug.print("-", .{});
|
|
std.debug.print("|", .{});
|
|
if (Mode.get(self.mode.read())) |m| std.debug.print("{s}", .{m.toString()}) else std.debug.print("---", .{});
|
|
|
|
std.debug.print("]\n", .{});
|
|
}
|
|
|
|
pub inline fn check(self: @This(), isa: Architecture, cond: u4) bool {
|
|
const flags: u4 = @truncate(self.raw >> 28);
|
|
|
|
return condition_lut(isa)[cond] & (@as(u16, 1) << flags) != 0;
|
|
}
|
|
};
|