diff options
Diffstat (limited to 'src-self-hosted/codegen')
| -rw-r--r-- | src-self-hosted/codegen/spu-mk2.zig | 161 | ||||
| -rw-r--r-- | src-self-hosted/codegen/spu-mk2/interpreter.zig | 166 |
2 files changed, 326 insertions, 1 deletions
diff --git a/src-self-hosted/codegen/spu-mk2.zig b/src-self-hosted/codegen/spu-mk2.zig index 0a95dce663..542862caca 100644 --- a/src-self-hosted/codegen/spu-mk2.zig +++ b/src-self-hosted/codegen/spu-mk2.zig @@ -1,4 +1,163 @@ -pub usingnamespace @import("std").spu; +const std = @import("std"); + +pub const Interpreter = @import("spu-mk2/interpreter.zig").Interpreter; + +pub const ExecutionCondition = enum(u3) { + always = 0, + when_zero = 1, + not_zero = 2, + greater_zero = 3, + less_than_zero = 4, + greater_or_equal_zero = 5, + less_or_equal_zero = 6, + overflow = 7, +}; + +pub const InputBehaviour = enum(u2) { + zero = 0, + immediate = 1, + peek = 2, + pop = 3, +}; + +pub const OutputBehaviour = enum(u2) { + discard = 0, + push = 1, + jump = 2, + jump_relative = 3, +}; + +pub const Command = enum(u5) { + copy = 0, + ipget = 1, + get = 2, + set = 3, + store8 = 4, + store16 = 5, + load8 = 6, + load16 = 7, + undefined0 = 8, + undefined1 = 9, + frget = 10, + frset = 11, + bpget = 12, + bpset = 13, + spget = 14, + spset = 15, + add = 16, + sub = 17, + mul = 18, + div = 19, + mod = 20, + @"and" = 21, + @"or" = 22, + xor = 23, + not = 24, + signext = 25, + rol = 26, + ror = 27, + bswap = 28, + asr = 29, + lsl = 30, + lsr = 31, +}; + +pub const Instruction = packed struct { + condition: ExecutionCondition, + input0: InputBehaviour, + input1: InputBehaviour, + modify_flags: bool, + output: OutputBehaviour, + command: Command, + reserved: u1 = 0, + + pub fn format(instr: Instruction, comptime fmt: []const u8, options: std.fmt.FormatOptions, out: anytype) !void { + try std.fmt.format(out, "0x{x:0<4} ", .{@bitCast(u16, instr)}); + try out.writeAll(switch (instr.condition) { + .always => " ", + .when_zero => "== 0", + .not_zero => "!= 0", + .greater_zero => " > 0", + .less_than_zero => " < 0", + .greater_or_equal_zero => ">= 0", + .less_or_equal_zero => "<= 0", + .overflow => "ovfl", + }); + try out.writeAll(" "); + try out.writeAll(switch (instr.input0) { + .zero => "zero", + .immediate => "imm ", + .peek => "peek", + .pop => "pop ", + }); + try out.writeAll(" "); + try out.writeAll(switch (instr.input1) { + .zero => "zero", + .immediate => "imm ", + .peek => "peek", + .pop => "pop ", + }); + try out.writeAll(" "); + try out.writeAll(switch (instr.command) { + .copy => "copy ", + .ipget => "ipget ", + .get => "get ", + .set => "set ", + .store8 => "store8 ", + .store16 => "store16 ", + .load8 => "load8 ", + .load16 => "load16 ", + .undefined0 => "undefined", + .undefined1 => "undefined", + .frget => "frget ", + .frset => "frset ", + .bpget => "bpget ", + .bpset => "bpset ", + .spget => "spget ", + .spset => "spset ", + .add => "add ", + .sub => "sub ", + .mul => "mul ", + .div => "div ", + .mod => "mod ", + .@"and" => "and ", + .@"or" => "or ", + .xor => "xor ", + .not => "not ", + .signext => "signext ", + .rol => "rol ", + .ror => "ror ", + .bswap => "bswap ", + .asr => "asr ", + .lsl => "lsl ", + .lsr => "lsr ", + }); + try out.writeAll(" "); + try out.writeAll(switch (instr.output) { + .discard => "discard", + .push => "push ", + .jump => "jmp ", + .jump_relative => "rjmp ", + }); + try out.writeAll(" "); + try out.writeAll(if (instr.modify_flags) + "+ flags" + else + " "); + } +}; + +pub const FlagRegister = packed struct { + zero: bool, + negative: bool, + carry: bool, + carry_enabled: bool, + interrupt0_enabled: bool, + interrupt1_enabled: bool, + interrupt2_enabled: bool, + interrupt3_enabled: bool, + reserved: u8 = 0, +}; pub const Register = enum { dummy, diff --git a/src-self-hosted/codegen/spu-mk2/interpreter.zig b/src-self-hosted/codegen/spu-mk2/interpreter.zig new file mode 100644 index 0000000000..1ec99546c6 --- /dev/null +++ b/src-self-hosted/codegen/spu-mk2/interpreter.zig @@ -0,0 +1,166 @@ +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; + } + } + } + }; +} |
