diff --git a/src/bench.zig b/src/bench.zig index 6e143f9..5e89e6a 100644 --- a/src/bench.zig +++ b/src/bench.zig @@ -1,10 +1,10 @@ const std = @import("std"); -const Cpu = @import("./cpu.zig"); +const Cpu = @import("./tail.zig").Cpu; -pub const std_options = std.Options{ - .log_level = .warn, -}; +// pub const std_options = std.Options{ +// .log_level = .warn, +// }; pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); @@ -17,17 +17,20 @@ pub fn main() !void { return error.BadUsage; } - const rom = try std.fs.cwd().readFileAlloc(allocator, argv[1], Cpu.memory_size - Cpu.initial_pc); + const rom = try std.fs.cwd().readFileAlloc(allocator, argv[1], 4096 - 512); defer allocator.free(rom); const instructions = try std.fmt.parseInt(usize, argv[2], 10); - var cpu = try Cpu.init(rom, 0, .{0} ** 8); + var cpu = Cpu.init(rom); const before = std.posix.getrusage(std.posix.rusage.SELF); - for (0..instructions) |_| { - try cpu.cycle(); - cpu.dt = 0; - } + // for (0..instructions) |_| { + // try cpu.cycle(); + // cpu.dt = 0; + // } + + cpu.run(instructions); + const after = std.posix.getrusage(std.posix.rusage.SELF); const before_sec = @as(f64, @floatFromInt(before.utime.tv_sec)) + @as(f64, @floatFromInt(before.utime.tv_usec)) / 1_000_000.0; diff --git a/src/tail.zig b/src/tail.zig new file mode 100644 index 0000000..6176e86 --- /dev/null +++ b/src/tail.zig @@ -0,0 +1,158 @@ +const std = @import("std"); + +const font_data = @import("./font.zig").font_data; + +pub const Cpu = struct { + v: [16]u8 = .{0} ** 16, + i: u12 = 0, + pc: u12 = 0x200, + stack: std.BoundedArray(u12, 16) = .{}, + display: [64 * 32 / 8]u8, + mem: [4096]u8, + code: [4096]Inst, + instructions: usize = 0, + + pub fn init(rom: []const u8) Cpu { + var cpu = Cpu{ + .display = undefined, + .mem = undefined, + .code = undefined, + }; + @memcpy(cpu.mem[0..font_data.len], font_data); + @memset(cpu.mem[font_data.len..0x200], 0); + @memcpy(cpu.mem[0x200..][0..rom.len], rom); + @memset(cpu.mem[0x200 + rom.len ..], 0); + @memset(&cpu.display, 0); + @memset(&cpu.code, .{ .func = &decode, .decoded = undefined }); + return cpu; + } + + pub fn run(self: *Cpu, instructions: usize) void { + self.instructions = instructions; + self.code[self.pc].func(self, self.code[self.pc].decoded); + } +}; + +const GadgetFunc = *const fn (*Cpu, Decoded) callconv(.Unspecified) void; + +pub const Decoded = union { + xy: [2]u4, + xnn: struct { u4, u8 }, + nnn: u12, + xyn: [3]u4, + + fn from(opcode: u16, cpu: *Cpu, which: enum { xy, xnn, nnn, xyn }) Decoded { + _ = cpu; // autofix + switch (which) { + .xy => { + const x: u4 = @truncate(opcode >> 8); + const y: u4 = @truncate(opcode >> 4); + return Decoded{ .xy = .{ x, y } }; + }, + .xnn => { + const x: u4 = @truncate(opcode >> 8); + const nn: u8 = @truncate(opcode); + return Decoded{ .xnn = .{ x, nn } }; + }, + .nnn => return Decoded{ .nnn = @truncate(opcode) }, + .xyn => { + const x: u4 = @truncate(opcode >> 8); + const y: u4 = @truncate(opcode >> 4); + const n: u4 = @truncate(opcode); + return Decoded{ .xyn = .{ x, y, n } }; + }, + } + } +}; + +const Inst = struct { + func: GadgetFunc, + decoded: Decoded, +}; + +fn decode(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + + const opcode = std.mem.readInt(u16, cpu.mem[cpu.pc..][0..2], .big); + const inst: Inst = switch (@as(u4, @truncate(opcode >> 12))) { + 0x0 => switch (opcode & 0xff) { + 0xE0 => .{ .func = &funcs.clear, .decoded = undefined }, + 0xEE => .{ .func = &funcs.ret, .decoded = undefined }, + else => .{ .func = &invalid, .decoded = undefined }, + }, + 0x1 => .{ .func = &funcs.jump, .decoded = Decoded.from(opcode, cpu, .nnn) }, + 0x2 => .{ .func = &funcs.call, .decoded = Decoded.from(opcode, cpu, .nnn) }, + 0x3 => .{ .func = &funcs.skipIfEqual, .decoded = Decoded.from(opcode, cpu, .xnn) }, + 0x4 => .{ .func = &funcs.skipIfNotEqual, .decoded = Decoded.from(opcode, cpu, .xnn) }, + 0x5 => switch (opcode & 0xf) { + 0x0 => .{ .func = &funcs.skipIfRegistersEqual, .decoded = Decoded.from(opcode, cpu, .xy) }, + else => .{ .func = &invalid, .decoded = undefined }, + }, + 0x6 => .{ .func = &funcs.setRegister, .decoded = Decoded.from(opcode, cpu, .xnn) }, + 0x7 => .{ .func = &funcs.addImmediate, .decoded = Decoded.from(opcode, cpu, .xnn) }, + 0x8 => .{ + .func = switch (@as(u4, @truncate(opcode))) { + 0x0 => &funcs.setRegisterToRegister, + 0x1 => &funcs.bitwiseOr, + 0x2 => &funcs.bitwiseAnd, + 0x3 => &funcs.bitwiseXor, + 0x4 => &funcs.addRegisters, + 0x5 => &funcs.subRegisters, + 0x6 => &funcs.shiftRight, + 0x7 => &funcs.subRegistersReverse, + 0xE => &funcs.shiftLeft, + else => &invalid, + }, + .decoded = Decoded.from(opcode, cpu, .xy), + }, + 0x9 => switch (opcode & 0xf) { + 0x0 => .{ .func = &funcs.skipIfRegistersNotEqual, .decoded = Decoded.from(opcode, cpu, .xy) }, + else => .{ .func = &invalid, .decoded = undefined }, + }, + 0xA => .{ .func = &funcs.setI, .decoded = Decoded.from(opcode, cpu, .nnn) }, + 0xB => .{ .func = &funcs.jumpV0, .decoded = Decoded.from(opcode, cpu, .nnn) }, + 0xC => .{ .func = &funcs.random, .decoded = Decoded.from(opcode, cpu, .xnn) }, + 0xD => .{ .func = &funcs.draw, .decoded = Decoded.from(opcode, cpu, .xyn) }, + 0xE => switch (opcode & 0xff) { + 0x9E => .{ .func = &funcs.skipIfPressed, .decoded = Decoded.from(opcode, cpu, .xnn) }, + 0xA1 => .{ .func = &funcs.skipIfNotPressed, .decoded = Decoded.from(opcode, cpu, .xnn) }, + else => .{ .func = &invalid, .decoded = undefined }, + }, + 0xF => .{ + .func = switch (opcode & 0xff) { + 0x07 => &funcs.readDt, + 0x0A => &funcs.waitForKey, + 0x15 => &funcs.setDt, + 0x18 => &funcs.setSt, + 0x1E => &funcs.incrementI, + 0x29 => &funcs.setIToFont, + 0x33 => &funcs.storeBcd, + 0x55 => &funcs.store, + 0x65 => &funcs.load, + 0x75 => &funcs.storeFlags, + 0x85 => &funcs.loadFlags, + else => &invalid, + }, + .decoded = Decoded.from(opcode, cpu, .xnn), + }, + }; + cpu.code[cpu.pc] = inst; + @call(.always_tail, inst.func, .{ cpu, inst.decoded }); +} + +fn invalid(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; // autofix + std.debug.panic( + "invalid instruction: {x:0>4}", + .{std.mem.readInt(u16, cpu.mem[cpu.pc..][0..2], .big)}, + ); +} + +const funcs = @import("./tailfuncs.zig"); + +// export fn inc_stub(cpu: *Cpu, decoded: Decoded, pc: u16) void { +// // const decoded: Decoded = @bitCast(decoded_i); +// decoded.xy[0].*, cpu.v[0xf] = @addWithOverflow(decoded.xy[0].*, decoded.xy[1].*); +// const next = cpu.code[cpu.pc + 2]; +// @call(.always_tail, next.func, .{ cpu, next.decoded, pc + 2 }); +// } diff --git a/src/tailfuncs.zig b/src/tailfuncs.zig new file mode 100644 index 0000000..26b2a4e --- /dev/null +++ b/src/tailfuncs.zig @@ -0,0 +1,238 @@ +const std = @import("std"); + +const Cpu = @import("./tail.zig").Cpu; +const Decoded = @import("./tail.zig").Decoded; + +inline fn cont(cpu: *Cpu) void { + if (cpu.instructions == 0) { + @setCold(true); + return; + } + const next = cpu.code[cpu.pc]; + @call(.always_tail, next.func, .{ cpu, next.decoded }); +} + +/// 00E0: clear the screen +pub fn clear(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + @memset(&cpu.display, 0); + cpu.pc += 2; + cont(cpu); +} + +/// 00EE: return +pub fn ret(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + cpu.pc = cpu.stack.popOrNull() orelse @panic("empty stack"); + cont(cpu); +} + +/// 1NNN: jump to NNN +pub fn jump(cpu: *Cpu, decoded: Decoded) void { + cpu.pc = decoded.nnn; + cont(cpu); +} + +/// 2NNN: call address NNN +pub fn call(cpu: *Cpu, decoded: Decoded) void { + cpu.stack.append(cpu.pc + 2) catch @panic("full stack"); + cpu.pc = decoded.nnn; + cont(cpu); +} + +/// 3XNN: skip next instruction if VX == NN +pub fn skipIfEqual(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("skipIfEqual at {x:0>3}", .{cpu.pc}); +} + +/// 4XNN: skip next instruction if VX != NN +pub fn skipIfNotEqual(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("skipIfNotEqual at {x:0>3}", .{cpu.pc}); +} + +/// 5XY0: skip next instruction if VX == VY +pub fn skipIfRegistersEqual(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("skipIfRegistersEqual at {x:0>3}", .{cpu.pc}); +} + +/// 6XNN: set VX to NN +pub fn setRegister(cpu: *Cpu, decoded: Decoded) void { + const x, const nn = decoded.xnn; + cpu.v[x] = nn; + cpu.pc += 2; + cont(cpu); +} + +/// 7XNN: add NN to VX without carry +pub fn addImmediate(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("addImmediate at {x:0>3}", .{cpu.pc}); +} + +/// 8XY0: set VX to VY +pub fn setRegisterToRegister(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("setRegisterToRegister at {x:0>3}", .{cpu.pc}); +} + +/// 8XY1: set VX to VX | VY +pub fn bitwiseOr(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("bitwiseOr at {x:0>3}", .{cpu.pc}); +} + +/// 8XY2: set VX to VX & VY +pub fn bitwiseAnd(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("bitwiseAnd at {x:0>3}", .{cpu.pc}); +} + +/// 8XY3: set VX to VX ^ VY +pub fn bitwiseXor(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("bitwiseXor at {x:0>3}", .{cpu.pc}); +} + +/// 8XY4: set VX to VX + VY; set VF to 1 if carry occurred, 0 otherwise +pub fn addRegisters(cpu: *Cpu, decoded: Decoded) void { + const x, const y = decoded.xy; + cpu.v[x], cpu.v[0xF] = @addWithOverflow(cpu.v[x], cpu.v[y]); + cpu.pc += 2; + cont(cpu); +} + +/// 8XY5: set VX to VX - VY; set VF to 0 if borrow occurred, 1 otherwise +pub fn subRegisters(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("subRegisters at {x:0>3}", .{cpu.pc}); +} + +/// 8XY6: set VX to VY >> 1, set VF to the former least significant bit of VY +pub fn shiftRight(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("shiftRight at {x:0>3}", .{cpu.pc}); +} + +/// 8XY7: set VX to VY - VX; set VF to 0 if borrow occurred, 1 otherwise +pub fn subRegistersReverse(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("subRegistersReverse at {x:0>3}", .{cpu.pc}); +} + +/// 8XYE: set VX to VY << 1, set VF to the former most significant bit of VY +pub fn shiftLeft(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("shiftLeft at {x:0>3}", .{cpu.pc}); +} + +/// 9XY0: skip next instruction if VX != VY +pub fn skipIfRegistersNotEqual(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("skipIfRegistersNotEqual at {x:0>3}", .{cpu.pc}); +} + +/// ANNN: set I to NNN +pub fn setI(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("setI at {x:0>3}", .{cpu.pc}); +} + +/// BNNN: jump to NNN + V0 +pub fn jumpV0(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("jumpV0 at {x:0>3}", .{cpu.pc}); +} + +/// CXNN: set VX to rand() & NN +pub fn random(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("random at {x:0>3}", .{cpu.pc}); +} + +/// DXYN: draw an 8xN sprite from memory starting at I at (VX, VY); set VF to 1 if any pixel was +/// turned off, 0 otherwise +pub fn draw(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("draw at {x:0>3}", .{cpu.pc}); +} + +/// EX9E: skip next instruction if the key in VX is pressed +pub fn skipIfPressed(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("skipIfPressed at {x:0>3}", .{cpu.pc}); +} + +/// EXA1: skip next instruction if the key in VX is not pressed +pub fn skipIfNotPressed(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("skipIfNotPressed at {x:0>3}", .{cpu.pc}); +} + +/// FX07: store the value of the delay timer in VX +pub fn readDt(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("readDt at {x:0>3}", .{cpu.pc}); +} + +/// FX0A: wait until any key is pressed, then store the key that was pressed in VX +pub fn waitForKey(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("waitForKey at {x:0>3}", .{cpu.pc}); +} + +/// FX15: set the delay timer to the value of VX +pub fn setDt(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("setDt at {x:0>3}", .{cpu.pc}); +} + +/// FX18: set the sound timer to the value of VX +pub fn setSt(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("setSt at {x:0>3}", .{cpu.pc}); +} + +/// FX1E: increment I by the value of VX +pub fn incrementI(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("incrementI at {x:0>3}", .{cpu.pc}); +} + +/// FX29: set I to the address of the sprite for the digit in VX +pub fn setIToFont(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("setIToFont at {x:0>3}", .{cpu.pc}); +} + +/// FX33: store the binary-coded decimal version of the value of VX in I, I + 1, and I + 2 +pub fn storeBcd(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("storeBcd at {x:0>3}", .{cpu.pc}); +} + +/// FX55: store registers [V0, VX] in memory starting at I; set I to I + X + 1 +pub fn store(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("store at {x:0>3}", .{cpu.pc}); +} + +/// FX65: load values from memory starting at I into registers [V0, VX]; set I to I + X + 1 +pub fn load(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("load at {x:0>3}", .{cpu.pc}); +} + +/// FX75: store registers [V0, VX] in flags. X < 8 +pub fn storeFlags(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("storeFlags at {x:0>3}", .{cpu.pc}); +} + +/// FX75: load flags into [V0, VX]. X < 8 +pub fn loadFlags(cpu: *Cpu, decoded: Decoded) void { + _ = decoded; + std.debug.panic("loadFlags at {x:0>3}", .{cpu.pc}); +}