arm32/src/arm.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;
}
};