Commit f2fef240a1
Changed files (4)
lib/std/spu/interpreter.zig
@@ -0,0 +1,159 @@
+const std = @import("std");
+usingnamespace @import("defines.zig");
+
+pub fn Interpreter(comptime Bus: type) type {
+ return struct {
+ ip: u16 = 0,
+ sp: u16 = undefined,
+ bp: u16 = undefined,
+ fr: FlagRegister = @bitCast(FlagRegister, @as(u16, 0)),
+ /// This is set to true when we hit an undefined0 instruction, allowing it to
+ /// be used as a trap for testing purposes
+ undefined0: bool = false,
+ bus: Bus,
+
+ pub fn ExecuteBlock(self: *@This(), comptime size: ?u32) !void {
+ var count: usize = 0;
+ while (size == null or count < size.?) {
+ count += 1;
+ var instruction = @bitCast(Instruction, self.bus.read16(self.ip));
+
+ std.log.debug(.SPU_2_Interpreter, "Executing {}\n", .{instruction});
+
+ self.ip +%= 2;
+
+ const execute = switch (instruction.condition) {
+ .always => true,
+ .not_zero => !self.fr.zero,
+ .when_zero => self.fr.zero,
+ .overflow => self.fr.carry,
+ ExecutionCondition.greater_or_equal_zero => !self.fr.negative,
+ else => return error.unimplemented,
+ };
+
+ if (execute) {
+ const val0 = switch (instruction.input0) {
+ .zero => @as(u16, 0),
+ .immediate => i: {
+ const val = self.bus.read16(@intCast(u16, self.ip));
+ self.ip +%= 2;
+ break :i val;
+ },
+ else => |e| e: {
+ // peek or pop; show value at current SP, and if pop, increment sp
+ const val = self.bus.read16(self.sp);
+ if (e == .pop) {
+ self.sp +%= 2;
+ }
+ break :e val;
+ },
+ };
+ const val1 = switch (instruction.input1) {
+ .zero => @as(u16, 0),
+ .immediate => i: {
+ const val = self.bus.read16(@intCast(u16, self.ip));
+ self.ip +%= 2;
+ break :i val;
+ },
+ else => |e| e: {
+ // peek or pop; show value at current SP, and if pop, increment sp
+ const val = self.bus.read16(self.sp);
+ if (e == .pop) {
+ self.sp +%= 2;
+ }
+ break :e val;
+ },
+ };
+
+ const output: u16 = switch (instruction.command) {
+ .get => self.bus.read16(self.bp +% (2 *% val0)),
+ .set => a: {
+ self.bus.write16(self.bp +% 2 *% val0, val1);
+ break :a val1;
+ },
+ .load8 => self.bus.read8(val0),
+ .load16 => self.bus.read16(val0),
+ .store8 => a: {
+ const val = @truncate(u8, val1);
+ self.bus.write8(val0, val);
+ break :a val;
+ },
+ .store16 => a: {
+ self.bus.write16(val0, val1);
+ break :a val1;
+ },
+ .copy => val0,
+ .add => a: {
+ var val: u16 = undefined;
+ self.fr.carry = @addWithOverflow(u16, val0, val1, &val);
+ break :a val;
+ },
+ .sub => a: {
+ var val: u16 = undefined;
+ self.fr.carry = @subWithOverflow(u16, val0, val1, &val);
+ break :a val;
+ },
+ .spset => a: {
+ self.sp = val0;
+ break :a val0;
+ },
+ .bpset => a: {
+ self.bp = val0;
+ break :a val0;
+ },
+ .frset => a: {
+ const val = (@bitCast(u16, self.fr) & val1) | (val0 & ~val1);
+ self.fr = @bitCast(FlagRegister, val);
+ break :a val;
+ },
+ .bswap => (val0 >> 8) | (val0 << 8),
+ .bpget => self.bp,
+ .spget => self.sp,
+ .ipget => self.ip +% (2 *% val0),
+ .lsl => val0 << 1,
+ .lsr => val0 >> 1,
+ .@"and" => val0 & val1,
+ .@"or" => val0 | val1,
+ .xor => val0 ^ val1,
+ .not => ~val0,
+ .undefined0 => {
+ self.undefined0 = true;
+ // Break out of the loop, and let the caller decide what to do
+ return;
+ },
+ .undefined1 => return error.BadInstruction,
+ .signext => if ((val0 & 0x80) != 0)
+ (val0 & 0xFF) | 0xFF00
+ else
+ (val0 & 0xFF),
+ else => return error.unimplemented,
+ };
+
+ switch (instruction.output) {
+ .discard => {},
+ .push => {
+ self.sp -%= 2;
+ self.bus.write16(self.sp, output);
+ },
+ .jump => {
+ self.ip = output;
+ if (!(instruction.command == .copy and instruction.input0 == .immediate)) {
+ // Not absolute. Break, for compatibility with JIT.
+ break;
+ }
+ },
+ else => return error.unimplemented,
+ }
+ if (instruction.modify_flags) {
+ self.fr.negative = (output & 0x8000) != 0;
+ self.fr.zero = (output == 0x0000);
+ }
+ } else {
+ if (instruction.input0 == .immediate) self.ip +%= 2;
+ if (instruction.input1 == .immediate) self.ip +%= 2;
+ break;
+ }
+ }
+ }
+ };
+}
src-self-hosted/test.zig
@@ -571,6 +571,56 @@ pub const TestContext = struct {
std.debug.assert(!case.cbe);
update_node.estimated_total_items = 4;
+ if (case.target.cpu_arch) |arch| {
+ if (arch == .spu_2) {
+ if (case.target.os_tag) |os| {
+ if (os != .freestanding) {
+ std.debug.panic("Only freestanding makes sense for SPU-II tests!", .{});
+ }
+ } else {
+ std.debug.panic("SPU_2 has no native OS, check the test!", .{});
+ }
+ var output = std.ArrayList(u8).init(allocator);
+ // defer output.deinit();
+ const uart = struct {
+ fn in(_out: usize) !u8 {
+ return error.not_implemented;
+ }
+ fn out(_output: usize, val: u8) !void {
+ try @intToPtr(*std.ArrayList(u8), _output).append(val);
+ }
+ };
+ var interpreter = std.spu.interpreter(struct {
+ RAM: [0x10000]u8 = undefined,
+
+ pub fn read8(bus: @This(), addr: u16) u8 {
+ return bus.RAM[addr];
+ }
+ pub fn read16(bus: @This(), addr: u16) u16 {
+ return std.mem.readIntLittle(u16, bus.RAM[addr..][0..2]);
+ }
+
+ pub fn write8(bus: *@This(), addr: u16, val: u8) void {
+ bus.RAM[addr] = val;
+ }
+
+ pub fn write16(bus: *@This(), addr: u16, val: u16) void {
+ std.mem.writeIntLittle(u16, bus.RAM[addr..][0..2], val);
+ }
+ }){
+ .bus = .{},
+ };
+
+ //defer interpreter.deinit(allocator);
+ std.debug.print("TODO implement SPU-II test loader\n", .{});
+ std.process.exit(1);
+ // TODO: loop detection? Solve the halting problem? fork() and limit by wall clock?
+ // Limit by emulated cycles?
+ // while (!interpreter.undefined0) {
+ // try interpreter.ExecuteBlock(100);
+ // }
+ }
+ }
var exec_result = x: {
var exec_node = update_node.start("execute", null);
exec_node.activate();
@@ -635,7 +685,6 @@ pub const TestContext = struct {
var test_node = update_node.start("test", null);
test_node.activate();
defer test_node.end();
-
defer allocator.free(exec_result.stdout);
defer allocator.free(exec_result.stderr);
switch (exec_result.term) {
test/stage2/spu-ii.zig
@@ -0,0 +1,23 @@
+const std = @import("std");
+const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
+
+const spu = std.zig.CrossTarget{
+ .cpu_arch = .spu_2,
+ .os_tag = .freestanding,
+};
+
+pub fn addCases(ctx: *TestContext) !void {
+ {
+ var case = ctx.exe("SPU-II Basic Test", spu);
+ case.addCompareOutput(
+ \\fn killEmulator() noreturn {
+ \\ asm volatile ("undefined0");
+ \\ unreachable;
+ \\}
+ \\
+ \\export fn _start() noreturn {
+ \\ killEmulator();
+ \\}
+ , "");
+ }
+}
test/stage2/test.zig
@@ -785,4 +785,5 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
\\extern var foo;
, &[_][]const u8{":4:1: error: unable to infer variable type"});
+ try @import("spu-ii.zig").addCases(ctx);
}