SPU-II: Test harness skeleton

This commit is contained in:
Noam Preil 2020-08-19 12:45:34 -04:00 committed by Andrew Kelley
parent f18636fa58
commit f2fef240a1
4 changed files with 233 additions and 1 deletions

159
lib/std/spu/interpreter.zig Normal file
View File

@ -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;
}
}
}
};
}

View File

@ -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) {

23
test/stage2/spu-ii.zig Normal file
View File

@ -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();
\\}
, "");
}
}

View File

@ -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);
}