authorgravatar for noam@pixelhero.devNoam Preil <noam@pixelhero.dev> 2020-08-19 12:45:34-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-22 12:45:29-07:00
logf2fef240a1a1d7caa06daa8e1bdf58206b42a73c
treed28d4329aba8e4afe87dc39e35817234fb032a0d
parentf18636fa58905ec85d246cb14c18418f2ce74317

SPU-II: Test harness skeleton


4 files changed, 233 insertions(+), 1 deletions(-)

lib/std/spu/interpreter.zig created+159
......@@ -0,0 +1,159 @@
1const std = @import("std");
2usingnamespace @import("defines.zig");
3
4pub fn Interpreter(comptime Bus: type) type {
5 return struct {
6 ip: u16 = 0,
7 sp: u16 = undefined,
8 bp: u16 = undefined,
9 fr: FlagRegister = @bitCast(FlagRegister, @as(u16, 0)),
10 /// This is set to true when we hit an undefined0 instruction, allowing it to
11 /// be used as a trap for testing purposes
12 undefined0: bool = false,
13 bus: Bus,
14
15 pub fn ExecuteBlock(self: *@This(), comptime size: ?u32) !void {
16 var count: usize = 0;
17 while (size == null or count < size.?) {
18 count += 1;
19 var instruction = @bitCast(Instruction, self.bus.read16(self.ip));
20
21 std.log.debug(.SPU_2_Interpreter, "Executing {}\n", .{instruction});
22
23 self.ip +%= 2;
24
25 const execute = switch (instruction.condition) {
26 .always => true,
27 .not_zero => !self.fr.zero,
28 .when_zero => self.fr.zero,
29 .overflow => self.fr.carry,
30 ExecutionCondition.greater_or_equal_zero => !self.fr.negative,
31 else => return error.unimplemented,
32 };
33
34 if (execute) {
35 const val0 = switch (instruction.input0) {
36 .zero => @as(u16, 0),
37 .immediate => i: {
38 const val = self.bus.read16(@intCast(u16, self.ip));
39 self.ip +%= 2;
40 break :i val;
41 },
42 else => |e| e: {
43 // peek or pop; show value at current SP, and if pop, increment sp
44 const val = self.bus.read16(self.sp);
45 if (e == .pop) {
46 self.sp +%= 2;
47 }
48 break :e val;
49 },
50 };
51 const val1 = switch (instruction.input1) {
52 .zero => @as(u16, 0),
53 .immediate => i: {
54 const val = self.bus.read16(@intCast(u16, self.ip));
55 self.ip +%= 2;
56 break :i val;
57 },
58 else => |e| e: {
59 // peek or pop; show value at current SP, and if pop, increment sp
60 const val = self.bus.read16(self.sp);
61 if (e == .pop) {
62 self.sp +%= 2;
63 }
64 break :e val;
65 },
66 };
67
68 const output: u16 = switch (instruction.command) {
69 .get => self.bus.read16(self.bp +% (2 *% val0)),
70 .set => a: {
71 self.bus.write16(self.bp +% 2 *% val0, val1);
72 break :a val1;
73 },
74 .load8 => self.bus.read8(val0),
75 .load16 => self.bus.read16(val0),
76 .store8 => a: {
77 const val = @truncate(u8, val1);
78 self.bus.write8(val0, val);
79 break :a val;
80 },
81 .store16 => a: {
82 self.bus.write16(val0, val1);
83 break :a val1;
84 },
85 .copy => val0,
86 .add => a: {
87 var val: u16 = undefined;
88 self.fr.carry = @addWithOverflow(u16, val0, val1, &val);
89 break :a val;
90 },
91 .sub => a: {
92 var val: u16 = undefined;
93 self.fr.carry = @subWithOverflow(u16, val0, val1, &val);
94 break :a val;
95 },
96 .spset => a: {
97 self.sp = val0;
98 break :a val0;
99 },
100 .bpset => a: {
101 self.bp = val0;
102 break :a val0;
103 },
104 .frset => a: {
105 const val = (@bitCast(u16, self.fr) & val1) | (val0 & ~val1);
106 self.fr = @bitCast(FlagRegister, val);
107 break :a val;
108 },
109 .bswap => (val0 >> 8) | (val0 << 8),
110 .bpget => self.bp,
111 .spget => self.sp,
112 .ipget => self.ip +% (2 *% val0),
113 .lsl => val0 << 1,
114 .lsr => val0 >> 1,
115 .@"and" => val0 & val1,
116 .@"or" => val0 | val1,
117 .xor => val0 ^ val1,
118 .not => ~val0,
119 .undefined0 => {
120 self.undefined0 = true;
121 // Break out of the loop, and let the caller decide what to do
122 return;
123 },
124 .undefined1 => return error.BadInstruction,
125 .signext => if ((val0 & 0x80) != 0)
126 (val0 & 0xFF) | 0xFF00
127 else
128 (val0 & 0xFF),
129 else => return error.unimplemented,
130 };
131
132 switch (instruction.output) {
133 .discard => {},
134 .push => {
135 self.sp -%= 2;
136 self.bus.write16(self.sp, output);
137 },
138 .jump => {
139 self.ip = output;
140 if (!(instruction.command == .copy and instruction.input0 == .immediate)) {
141 // Not absolute. Break, for compatibility with JIT.
142 break;
143 }
144 },
145 else => return error.unimplemented,
146 }
147 if (instruction.modify_flags) {
148 self.fr.negative = (output & 0x8000) != 0;
149 self.fr.zero = (output == 0x0000);
150 }
151 } else {
152 if (instruction.input0 == .immediate) self.ip +%= 2;
153 if (instruction.input1 == .immediate) self.ip +%= 2;
154 break;
155 }
156 }
157 }
158 };
159}
src-self-hosted/test.zig+50-1
......@@ -571,6 +571,56 @@ pub const TestContext = struct {
571571 std.debug.assert(!case.cbe);
572572
573573 update_node.estimated_total_items = 4;
574 if (case.target.cpu_arch) |arch| {
575 if (arch == .spu_2) {
576 if (case.target.os_tag) |os| {
577 if (os != .freestanding) {
578 std.debug.panic("Only freestanding makes sense for SPU-II tests!", .{});
579 }
580 } else {
581 std.debug.panic("SPU_2 has no native OS, check the test!", .{});
582 }
583 var output = std.ArrayList(u8).init(allocator);
584 // defer output.deinit();
585 const uart = struct {
586 fn in(_out: usize) !u8 {
587 return error.not_implemented;
588 }
589 fn out(_output: usize, val: u8) !void {
590 try @intToPtr(*std.ArrayList(u8), _output).append(val);
591 }
592 };
593 var interpreter = std.spu.interpreter(struct {
594 RAM: [0x10000]u8 = undefined,
595
596 pub fn read8(bus: @This(), addr: u16) u8 {
597 return bus.RAM[addr];
598 }
599 pub fn read16(bus: @This(), addr: u16) u16 {
600 return std.mem.readIntLittle(u16, bus.RAM[addr..][0..2]);
601 }
602
603 pub fn write8(bus: *@This(), addr: u16, val: u8) void {
604 bus.RAM[addr] = val;
605 }
606
607 pub fn write16(bus: *@This(), addr: u16, val: u16) void {
608 std.mem.writeIntLittle(u16, bus.RAM[addr..][0..2], val);
609 }
610 }){
611 .bus = .{},
612 };
613
614 //defer interpreter.deinit(allocator);
615 std.debug.print("TODO implement SPU-II test loader\n", .{});
616 std.process.exit(1);
617 // TODO: loop detection? Solve the halting problem? fork() and limit by wall clock?
618 // Limit by emulated cycles?
619 // while (!interpreter.undefined0) {
620 // try interpreter.ExecuteBlock(100);
621 // }
622 }
623 }
574624 var exec_result = x: {
575625 var exec_node = update_node.start("execute", null);
576626 exec_node.activate();
......@@ -635,7 +685,6 @@ pub const TestContext = struct {
635685 var test_node = update_node.start("test", null);
636686 test_node.activate();
637687 defer test_node.end();
638
639688 defer allocator.free(exec_result.stdout);
640689 defer allocator.free(exec_result.stderr);
641690 switch (exec_result.term) {
test/stage2/spu-ii.zig created+23
......@@ -0,0 +1,23 @@
1const std = @import("std");
2const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
3
4const spu = std.zig.CrossTarget{
5 .cpu_arch = .spu_2,
6 .os_tag = .freestanding,
7};
8
9pub fn addCases(ctx: *TestContext) !void {
10 {
11 var case = ctx.exe("SPU-II Basic Test", spu);
12 case.addCompareOutput(
13 \\fn killEmulator() noreturn {
14 \\ asm volatile ("undefined0");
15 \\ unreachable;
16 \\}
17 \\
18 \\export fn _start() noreturn {
19 \\ killEmulator();
20 \\}
21 , "");
22 }
23}
test/stage2/test.zig+1
......@@ -785,4 +785,5 @@ pub fn addCases(ctx: *TestContext) !void {
785785 \\}
786786 \\extern var foo;
787787 , &[_][]const u8{":4:1: error: unable to infer variable type"});
788 try @import("spu-ii.zig").addCases(ctx);
788789}