399 lines
14 KiB
Zig
399 lines
14 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 Bitfield = @import("bitfield").Bitfield;
|
||
|
const Bit = @import("bitfield").Bit;
|
||
|
|
||
|
const condition_lut = [_]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
|
||
|
0x0000, // NV - never
|
||
|
};
|
||
|
|
||
|
pub fn Arm32(comptime arch: Architecture) type {
|
||
|
return struct {
|
||
|
const Self = @This();
|
||
|
|
||
|
r: [16]u32 = [_]u32{0x00} ** 16,
|
||
|
pipe: Pipeline = Pipeline.init(),
|
||
|
sched: Scheduler,
|
||
|
bus: Bus,
|
||
|
cpsr: PSR,
|
||
|
spsr: PSR,
|
||
|
|
||
|
bank: Bank = Bank.create(),
|
||
|
|
||
|
const arm = @import("arm/v4t.zig").arm(Self);
|
||
|
const thumb = @import("arm/v4t.zig").thumb(Self);
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
pub fn init(scheduler: Scheduler, bus: Bus) Self {
|
||
|
return Self{
|
||
|
.sched = scheduler,
|
||
|
.bus = bus,
|
||
|
.cpsr = .{ .raw = 0x0000_001F },
|
||
|
.spsr = .{ .raw = 0x0000_0000 },
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// 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 },
|
||
|
};
|
||
|
}
|
||
|
|
||
|
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(u5, 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(@enumToInt(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 = @truncate(u16, self.pipe.step(self, u16) orelse return);
|
||
|
thumb.lut[thumb.idx(opcode)](self, self.bus, opcode);
|
||
|
} else {
|
||
|
const opcode = self.pipe.step(self, u32) orelse return;
|
||
|
|
||
|
if (self.cpsr.check(@truncate(u4, opcode >> 28))) {
|
||
|
arm.lut[arm.idx(opcode)](self, self.bus, 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.bus.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 (arch) {
|
||
|
.v4t => .{ .v4t = self },
|
||
|
.v5te => .{ .v5te = self },
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
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),
|
||
|
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(), cond: u4) bool {
|
||
|
const flags = @truncate(u4, self.raw >> 28);
|
||
|
|
||
|
return condition_lut[cond] & (@as(u16, 1) << flags) != 0;
|
||
|
}
|
||
|
};
|