Commit f2fef240a1

Noam Preil <noam@pixelhero.dev>
2020-08-19 18:45:34
SPU-II: Test harness skeleton
1 parent f18636f
Changed files (4)
lib
src-self-hosted
test
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);
 }