feat: implement Coprocessor Interface
This commit is contained in:
parent
5d70e4bd1d
commit
514e4d6014
74
src/arm.zig
74
src/arm.zig
|
@ -4,6 +4,7 @@ const Architecture = enum { v4t, v5te };
|
||||||
const Interpreter = @import("lib.zig").Interpreter;
|
const Interpreter = @import("lib.zig").Interpreter;
|
||||||
const Bus = @import("lib.zig").Bus;
|
const Bus = @import("lib.zig").Bus;
|
||||||
const Scheduler = @import("lib.zig").Scheduler;
|
const Scheduler = @import("lib.zig").Scheduler;
|
||||||
|
const Coprocessor = @import("lib.zig").Coprocessor;
|
||||||
|
|
||||||
const Bitfield = @import("bitfield").Bitfield;
|
const Bitfield = @import("bitfield").Bitfield;
|
||||||
const Bit = @import("bitfield").Bit;
|
const Bit = @import("bitfield").Bit;
|
||||||
|
@ -46,6 +47,7 @@ pub fn Arm32(comptime isa: Architecture) type {
|
||||||
// The following will be `void` on.v4t but exist on v5te
|
// The following will be `void` on.v4t but exist on v5te
|
||||||
itcm: if (is_v5te) Itcm else void,
|
itcm: if (is_v5te) Itcm else void,
|
||||||
dtcm: if (is_v5te) Dtcm else void,
|
dtcm: if (is_v5te) Dtcm else void,
|
||||||
|
cp15: if (is_v5te) Coprocessor else void,
|
||||||
|
|
||||||
const arm = switch (isa) {
|
const arm = switch (isa) {
|
||||||
.v4t => @import("arm/v4t.zig").arm,
|
.v4t => @import("arm/v4t.zig").arm,
|
||||||
|
@ -154,6 +156,37 @@ pub fn Arm32(comptime isa: Architecture) type {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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 {
|
pub fn init(scheduler: Scheduler, bus: Bus) Self {
|
||||||
return .{
|
return .{
|
||||||
.sched = scheduler,
|
.sched = scheduler,
|
||||||
|
@ -161,11 +194,27 @@ pub fn Arm32(comptime isa: Architecture) type {
|
||||||
.cpsr = .{ .raw = 0x0000_001F },
|
.cpsr = .{ .raw = 0x0000_001F },
|
||||||
.spsr = .{ .raw = 0x0000_0000 },
|
.spsr = .{ .raw = 0x0000_0000 },
|
||||||
|
|
||||||
.dtcm = if (is_v5te) .{} else {},
|
.cp15 = {},
|
||||||
.itcm = if (is_v5te) .{} else {},
|
.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
|
// 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
|
// 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 {
|
pub fn read(self: *Self, comptime T: type, address: u32) T {
|
||||||
|
@ -179,10 +228,10 @@ pub fn Arm32(comptime isa: Architecture) type {
|
||||||
|
|
||||||
// FIXME: verify correctness + can this be faster?
|
// FIXME: verify correctness + can this be faster?
|
||||||
if (itcm_base < address and address < itcm_base + itcm_size)
|
if (itcm_base < address and address < itcm_base + itcm_size)
|
||||||
return readInt(T, self.itcm.buf[address & 0x0000_7FFF ..][0..@sizeOf(T)]);
|
return readInt(T, self.itcm.buf[address..][0..@sizeOf(T)]);
|
||||||
|
|
||||||
if (dtcm_base < address and address < dtcm_base + dtcm_size)
|
if (dtcm_base < address and address < dtcm_base + dtcm_size)
|
||||||
return readInt(T, self.itcm.buf[address & 0x0000_3FFF ..][0..@sizeOf(T)]);
|
return readInt(T, self.dtcm.buf[address..][0..@sizeOf(T)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.bus.read(T, address);
|
return self.bus.read(T, address);
|
||||||
|
@ -199,28 +248,15 @@ pub fn Arm32(comptime isa: Architecture) type {
|
||||||
|
|
||||||
// FIXME: verify correctness + can this be faster?
|
// FIXME: verify correctness + can this be faster?
|
||||||
if (itcm_base < address and address < itcm_base + itcm_size)
|
if (itcm_base < address and address < itcm_base + itcm_size)
|
||||||
return writeInt(T, self.itcm.buf[address & 0x0000_7FFF ..][0..@sizeOf(T)], value);
|
return writeInt(T, self.itcm.buf[address..][0..@sizeOf(T)], value);
|
||||||
|
|
||||||
if (dtcm_base < address and address < dtcm_base + dtcm_size)
|
if (dtcm_base < address and address < dtcm_base + dtcm_size)
|
||||||
return writeInt(T, self.itcm.buf[address & 0x0000_3FFF ..][0..@sizeOf(T)], value);
|
return writeInt(T, self.dtcm.buf[address..][0..@sizeOf(T)], value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.bus.write(T, address, value);
|
return self.bus.write(T, address, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 = if (is_v5te) .{} else {},
|
|
||||||
.itcm = if (is_v5te) .{} else {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn hasSPSR(self: *const Self) bool {
|
pub inline fn hasSPSR(self: *const Self) bool {
|
||||||
const mode = Mode.getChecked(self, self.cpsr.mode.read());
|
const mode = Mode.getChecked(self, self.cpsr.mode.read());
|
||||||
return switch (mode) {
|
return switch (mode) {
|
||||||
|
|
237
src/lib.zig
237
src/lib.zig
|
@ -32,6 +32,38 @@ pub const Interpreter = union(enum) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test "create ARMv4T interface" {
|
||||||
|
var bus_impl = ExampleBus{};
|
||||||
|
var scheduler_impl = ExampleScheduler{};
|
||||||
|
|
||||||
|
const bus_interface = Bus.init(&bus_impl);
|
||||||
|
const scheduler_interface = Scheduler.init(&scheduler_impl);
|
||||||
|
|
||||||
|
var arm7tdmi = Arm7tdmi.init(scheduler_interface, bus_interface);
|
||||||
|
var icpu = arm7tdmi.interface();
|
||||||
|
|
||||||
|
icpu.reset();
|
||||||
|
icpu.step();
|
||||||
|
// TODO: call icpu.panic()
|
||||||
|
}
|
||||||
|
|
||||||
|
test "create ARMv5TE interface" {
|
||||||
|
var bus_impl = ExampleBus{};
|
||||||
|
var scheduler_impl = ExampleScheduler{};
|
||||||
|
var cop_impl = ExampleCoprocessor{};
|
||||||
|
|
||||||
|
const bus_interface = Bus.init(&bus_impl);
|
||||||
|
const scheduler_interface = Scheduler.init(&scheduler_impl);
|
||||||
|
const coprocessor_interface = Coprocessor.init(&cop_impl);
|
||||||
|
|
||||||
|
var arm946es = Arm946es.init(scheduler_interface, bus_interface, coprocessor_interface);
|
||||||
|
var icpu = arm946es.interface();
|
||||||
|
|
||||||
|
icpu.reset();
|
||||||
|
icpu.step();
|
||||||
|
// TODO: call icpu.panic();
|
||||||
|
}
|
||||||
|
|
||||||
pub const Bus = struct {
|
pub const Bus = struct {
|
||||||
ptr: *anyopaque,
|
ptr: *anyopaque,
|
||||||
vtable: *const Vtable,
|
vtable: *const Vtable,
|
||||||
|
@ -196,6 +228,118 @@ pub const Bus = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test "create Bus" {
|
||||||
|
var bus_impl = ExampleBus{};
|
||||||
|
const iface = Bus.init(&bus_impl);
|
||||||
|
_ = iface;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "call Bus reads" {
|
||||||
|
var bus_impl = ExampleBus{};
|
||||||
|
const iface = Bus.init(&bus_impl);
|
||||||
|
|
||||||
|
_ = iface.read(u32, 0x0000_0000);
|
||||||
|
_ = iface.read(u16, 0x0000_0000);
|
||||||
|
_ = iface.read(u8, 0x0000_0000);
|
||||||
|
|
||||||
|
_ = iface.dbgRead(u32, 0x0000_0000);
|
||||||
|
_ = iface.dbgRead(u16, 0x0000_0000);
|
||||||
|
_ = iface.dbgRead(u8, 0x0000_0000);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "call Bus writes" {
|
||||||
|
var bus_impl = ExampleBus{};
|
||||||
|
const iface = Bus.init(&bus_impl);
|
||||||
|
|
||||||
|
_ = iface.write(u32, 0x0000_0000, 0x0000_0000);
|
||||||
|
_ = iface.write(u16, 0x0000_0000, 0x0000);
|
||||||
|
_ = iface.write(u8, 0x0000_0000, 0x00);
|
||||||
|
|
||||||
|
_ = iface.dbgWrite(u32, 0x0000_0000, 0x0000_0000);
|
||||||
|
_ = iface.dbgWrite(u16, 0x0000_0000, 0x0000);
|
||||||
|
_ = iface.dbgWrite(u8, 0x0000_0000, 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Coprocessor = struct {
|
||||||
|
ptr: *anyopaque,
|
||||||
|
|
||||||
|
// VTable
|
||||||
|
readFn: *const fn (ptr: *anyopaque, op1: u3, cn: u4, cm: u4, op2: u3) u32,
|
||||||
|
writeFn: *const fn (ptr: *anyopaque, op1: u3, cn: u4, cm: u4, op2: u3, value: u32) void,
|
||||||
|
resetFn: *const fn (ptr: *anyopaque) void,
|
||||||
|
|
||||||
|
pub fn init(obj: anytype) @This() {
|
||||||
|
const P = @TypeOf(obj);
|
||||||
|
const info = @typeInfo(P);
|
||||||
|
|
||||||
|
std.debug.assert(info == .Pointer); // `anytype` is a Pointer
|
||||||
|
std.debug.assert(info.Pointer.size == .One); // Single-Item Pointer
|
||||||
|
std.debug.assert(@typeInfo(info.Pointer.child) == .Struct); // Pointer Child is a `struct`
|
||||||
|
|
||||||
|
const impl = struct {
|
||||||
|
fn read(ptr: *anyopaque, op1: u3, cn: u4, cm: u4, op2: u3) u32 {
|
||||||
|
const self: P = @ptrCast(@alignCast(ptr));
|
||||||
|
return self.read(op1, cn, cm, op2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(ptr: *anyopaque, op1: u3, cn: u4, cm: u4, op2: u3, value: u32) void {
|
||||||
|
const self: P = @ptrCast(@alignCast(ptr));
|
||||||
|
return self.write(op1, cn, cm, op2, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(ptr: *anyopaque) void {
|
||||||
|
const self: P = @ptrCast(@alignCast(ptr));
|
||||||
|
return self.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.ptr = obj,
|
||||||
|
.readFn = impl.read,
|
||||||
|
.writeFn = impl.write,
|
||||||
|
.resetFn = impl.reset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(self: @This(), op1: u3, cn: u4, cm: u4, op2: u3) u32 {
|
||||||
|
return self.readFn(self.ptr, op1, cn, cm, op2);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(self: @This(), op1: u3, cn: u4, cm: u4, op2: u3, value: u32) void {
|
||||||
|
return self.writeFn(self.ptr, op1, cn, cm, op2, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(self: @This()) void {
|
||||||
|
return self.resetFn(self.ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "create Coprocessor" {
|
||||||
|
var cop_impl = ExampleCoprocessor{};
|
||||||
|
_ = Coprocessor.init(&cop_impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Coprocessor.read" {
|
||||||
|
var cop_impl = ExampleCoprocessor{};
|
||||||
|
const iface = Coprocessor.init(&cop_impl);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u32, 0xDEADBEEF), iface.read(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Coprocessor.write" {
|
||||||
|
var cop_impl = ExampleCoprocessor{};
|
||||||
|
const iface = Coprocessor.init(&cop_impl);
|
||||||
|
|
||||||
|
iface.write(0, 0, 0, 0, 0xDEADBEEF);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Coprocessor.reset" {
|
||||||
|
var cop_impl = ExampleCoprocessor{};
|
||||||
|
const iface = Coprocessor.init(&cop_impl);
|
||||||
|
|
||||||
|
iface.reset();
|
||||||
|
}
|
||||||
|
|
||||||
pub const Scheduler = struct {
|
pub const Scheduler = struct {
|
||||||
ptr: *anyopaque,
|
ptr: *anyopaque,
|
||||||
|
|
||||||
|
@ -235,6 +379,27 @@ pub const Scheduler = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test "create Scheduler" {
|
||||||
|
var scheduler_impl = ExampleScheduler{};
|
||||||
|
const iface = Scheduler.init(&scheduler_impl);
|
||||||
|
_ = iface;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Scheduler.now()" {
|
||||||
|
var scheduler_impl = ExampleScheduler{};
|
||||||
|
const iface = Scheduler.init(&scheduler_impl);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u64, 0), iface.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Scheduler.reset()" {
|
||||||
|
var scheduler_impl = ExampleScheduler{ .tick = std.math.maxInt(u64) };
|
||||||
|
const iface = Scheduler.init(&scheduler_impl);
|
||||||
|
iface.reset();
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u64, 0), scheduler_impl.tick);
|
||||||
|
}
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
// TESTING
|
// TESTING
|
||||||
// ---
|
// ---
|
||||||
|
@ -282,61 +447,27 @@ const ExampleScheduler = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
test "create IBus" {
|
const ExampleCoprocessor = struct {
|
||||||
var bus_impl = ExampleBus{};
|
pub fn read(self: *@This(), op1: u3, cn: u4, cm: u4, op2: u3) u32 {
|
||||||
const iface = Bus.init(&bus_impl);
|
_ = op2;
|
||||||
_ = iface;
|
_ = cm;
|
||||||
|
_ = cn;
|
||||||
|
_ = op1;
|
||||||
|
_ = self;
|
||||||
|
|
||||||
|
return 0xDEADBEEF;
|
||||||
}
|
}
|
||||||
|
|
||||||
test "call IBus reads" {
|
pub fn write(self: *@This(), op1: u3, cn: u4, cm: u4, op2: u3, value: u32) void {
|
||||||
var bus_impl = ExampleBus{};
|
_ = value;
|
||||||
const iface = Bus.init(&bus_impl);
|
_ = op2;
|
||||||
|
_ = cm;
|
||||||
_ = iface.read(u32, 0x0000_0000);
|
_ = cn;
|
||||||
_ = iface.read(u16, 0x0000_0000);
|
_ = op1;
|
||||||
_ = iface.read(u8, 0x0000_0000);
|
_ = self;
|
||||||
|
|
||||||
_ = iface.dbgRead(u32, 0x0000_0000);
|
|
||||||
_ = iface.dbgRead(u16, 0x0000_0000);
|
|
||||||
_ = iface.dbgRead(u8, 0x0000_0000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "call IBus writes" {
|
pub fn reset(self: *@This()) void {
|
||||||
var bus_impl = ExampleBus{};
|
self.* = .{};
|
||||||
const iface = Bus.init(&bus_impl);
|
|
||||||
|
|
||||||
_ = iface.write(u32, 0x0000_0000, 0x0000_0000);
|
|
||||||
_ = iface.write(u16, 0x0000_0000, 0x0000);
|
|
||||||
_ = iface.write(u8, 0x0000_0000, 0x00);
|
|
||||||
|
|
||||||
_ = iface.dbgWrite(u32, 0x0000_0000, 0x0000_0000);
|
|
||||||
_ = iface.dbgWrite(u16, 0x0000_0000, 0x0000);
|
|
||||||
_ = iface.dbgWrite(u8, 0x0000_0000, 0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "create ARMv4T interface" {
|
|
||||||
var bus_impl = ExampleBus{};
|
|
||||||
var scheduler_impl = ExampleScheduler{};
|
|
||||||
|
|
||||||
const bus_interface = Bus.init(&bus_impl);
|
|
||||||
const scheduler_interface = Scheduler.init(&scheduler_impl);
|
|
||||||
|
|
||||||
var arm7tdmi = Arm7tdmi.init(scheduler_interface, bus_interface);
|
|
||||||
var icpu = arm7tdmi.interface();
|
|
||||||
|
|
||||||
icpu.reset();
|
|
||||||
icpu.step();
|
|
||||||
// TODO: call icpu.panic()
|
|
||||||
}
|
|
||||||
|
|
||||||
test "create ARMv5TE interface" {
|
|
||||||
var bus_impl = ExampleBus{};
|
|
||||||
var scheduler_impl = ExampleScheduler{};
|
|
||||||
|
|
||||||
const bus_interface = Bus.init(&bus_impl);
|
|
||||||
const scheduler_interface = Scheduler.init(&scheduler_impl);
|
|
||||||
|
|
||||||
var arm946es = Arm946es.init(scheduler_interface, bus_interface);
|
|
||||||
|
|
||||||
_ = arm946es.interface();
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue