mirror of
https://github.com/ziglang/zig.git
synced 2025-12-16 19:23:08 +00:00
SPU-II: Test harness skeleton
This commit is contained in:
parent
f18636fa58
commit
f2fef240a1
159
lib/std/spu/interpreter.zig
Normal file
159
lib/std/spu/interpreter.zig
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -571,6 +571,56 @@ pub const TestContext = struct {
|
|||||||
std.debug.assert(!case.cbe);
|
std.debug.assert(!case.cbe);
|
||||||
|
|
||||||
update_node.estimated_total_items = 4;
|
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_result = x: {
|
||||||
var exec_node = update_node.start("execute", null);
|
var exec_node = update_node.start("execute", null);
|
||||||
exec_node.activate();
|
exec_node.activate();
|
||||||
@ -635,7 +685,6 @@ pub const TestContext = struct {
|
|||||||
var test_node = update_node.start("test", null);
|
var test_node = update_node.start("test", null);
|
||||||
test_node.activate();
|
test_node.activate();
|
||||||
defer test_node.end();
|
defer test_node.end();
|
||||||
|
|
||||||
defer allocator.free(exec_result.stdout);
|
defer allocator.free(exec_result.stdout);
|
||||||
defer allocator.free(exec_result.stderr);
|
defer allocator.free(exec_result.stderr);
|
||||||
switch (exec_result.term) {
|
switch (exec_result.term) {
|
||||||
|
|||||||
23
test/stage2/spu-ii.zig
Normal file
23
test/stage2/spu-ii.zig
Normal 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();
|
||||||
|
\\}
|
||||||
|
, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -785,4 +785,5 @@ pub fn addCases(ctx: *TestContext) !void {
|
|||||||
\\}
|
\\}
|
||||||
\\extern var foo;
|
\\extern var foo;
|
||||||
, &[_][]const u8{":4:1: error: unable to infer variable type"});
|
, &[_][]const u8{":4:1: error: unable to infer variable type"});
|
||||||
|
try @import("spu-ii.zig").addCases(ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user