* move SPU code from std to self hosted compiler * change std lib comments to be descriptive rather than prescriptive * avoid usingnamespace * fix case style of error codes * remove duplication of producer_string * generalize handling of less than 64 bit arch pointers * clean up SPU II related test harness code
167 lines
7.1 KiB
Zig
167 lines
7.1 KiB
Zig
const std = @import("std");
|
|
const log = std.log.scoped(.SPU_2_Interpreter);
|
|
const spu = @import("../spu-mk2.zig");
|
|
const FlagRegister = spu.FlagRegister;
|
|
const Instruction = spu.Instruction;
|
|
const ExecutionCondition = spu.ExecutionCondition;
|
|
|
|
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,
|
|
/// This is set to true when we hit an undefined1 instruction, allowing it to
|
|
/// be used as a trap for testing purposes. undefined1 is used as a breakpoint.
|
|
undefined1: 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));
|
|
|
|
log.debug("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 => {
|
|
self.undefined1 = true;
|
|
// Break out of the loop, and let the caller decide what to do
|
|
return;
|
|
},
|
|
.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;
|
|
},
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|