From 63ef7af6622732dbf4a78348725f8dd441f56c60 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Wed, 15 May 2024 20:55:01 -0700 Subject: [PATCH] more functions --- src/tail.zig | 59 ++++++++++------ src/tailfuncs.zig | 173 +++++++++++++++++++++++++--------------------- 2 files changed, 132 insertions(+), 100 deletions(-) diff --git a/src/tail.zig b/src/tail.zig index 6176e86..a392f0e 100644 --- a/src/tail.zig +++ b/src/tail.zig @@ -11,12 +11,18 @@ pub const Cpu = struct { mem: [4096]u8, code: [4096]Inst, instructions: usize = 0, + random: std.rand.DefaultPrng, pub fn init(rom: []const u8) Cpu { var cpu = Cpu{ .display = undefined, .mem = undefined, .code = undefined, + .random = std.rand.DefaultPrng.init(blk: { + var buf: u64 = undefined; + std.posix.getrandom(std.mem.asBytes(&buf)) catch unreachable; + break :blk buf; + }), }; @memcpy(cpu.mem[0..font_data.len], font_data); @memset(cpu.mem[font_data.len..0x200], 0); @@ -29,11 +35,11 @@ pub const Cpu = struct { pub fn run(self: *Cpu, instructions: usize) void { self.instructions = instructions; - self.code[self.pc].func(self, self.code[self.pc].decoded); + self.code[self.pc].func(self, self.code[self.pc].decoded.to()); } }; -const GadgetFunc = *const fn (*Cpu, Decoded) callconv(.Unspecified) void; +const GadgetFunc = *const fn (*Cpu, u32) callconv(.C) void; pub const Decoded = union { xy: [2]u4, @@ -41,8 +47,7 @@ pub const Decoded = union { nnn: u12, xyn: [3]u4, - fn from(opcode: u16, cpu: *Cpu, which: enum { xy, xnn, nnn, xyn }) Decoded { - _ = cpu; // autofix + fn decode(opcode: u16, which: enum { xy, xnn, nnn, xyn }) Decoded { switch (which) { .xy => { const x: u4 = @truncate(opcode >> 8); @@ -63,6 +68,14 @@ pub const Decoded = union { }, } } + + pub inline fn from(x: u32) Decoded { + return @as(*const Decoded, @ptrCast(&x)).*; + } + + pub inline fn to(self: Decoded) u32 { + return @as(*align(2) const u32, @ptrCast(&self)).*; + } }; const Inst = struct { @@ -70,7 +83,7 @@ const Inst = struct { decoded: Decoded, }; -fn decode(cpu: *Cpu, decoded: Decoded) void { +fn decode(cpu: *Cpu, decoded: u32) callconv(.C) void { _ = decoded; const opcode = std.mem.readInt(u16, cpu.mem[cpu.pc..][0..2], .big); @@ -80,16 +93,16 @@ fn decode(cpu: *Cpu, decoded: Decoded) void { 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) }, + 0x1 => .{ .func = &funcs.jump, .decoded = Decoded.decode(opcode, .nnn) }, + 0x2 => .{ .func = &funcs.call, .decoded = Decoded.decode(opcode, .nnn) }, + 0x3 => .{ .func = &funcs.skipIfEqual, .decoded = Decoded.decode(opcode, .xnn) }, + 0x4 => .{ .func = &funcs.skipIfNotEqual, .decoded = Decoded.decode(opcode, .xnn) }, 0x5 => switch (opcode & 0xf) { - 0x0 => .{ .func = &funcs.skipIfRegistersEqual, .decoded = Decoded.from(opcode, cpu, .xy) }, + 0x0 => .{ .func = &funcs.skipIfRegistersEqual, .decoded = Decoded.decode(opcode, .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) }, + 0x6 => .{ .func = &funcs.setRegister, .decoded = Decoded.decode(opcode, .xnn) }, + 0x7 => .{ .func = &funcs.addImmediate, .decoded = Decoded.decode(opcode, .xnn) }, 0x8 => .{ .func = switch (@as(u4, @truncate(opcode))) { 0x0 => &funcs.setRegisterToRegister, @@ -103,19 +116,19 @@ fn decode(cpu: *Cpu, decoded: Decoded) void { 0xE => &funcs.shiftLeft, else => &invalid, }, - .decoded = Decoded.from(opcode, cpu, .xy), + .decoded = Decoded.decode(opcode, .xy), }, 0x9 => switch (opcode & 0xf) { - 0x0 => .{ .func = &funcs.skipIfRegistersNotEqual, .decoded = Decoded.from(opcode, cpu, .xy) }, + 0x0 => .{ .func = &funcs.skipIfRegistersNotEqual, .decoded = Decoded.decode(opcode, .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) }, + 0xA => .{ .func = &funcs.setI, .decoded = Decoded.decode(opcode, .nnn) }, + 0xB => .{ .func = &funcs.jumpV0, .decoded = Decoded.decode(opcode, .nnn) }, + 0xC => .{ .func = &funcs.random, .decoded = Decoded.decode(opcode, .xnn) }, + 0xD => .{ .func = &funcs.draw, .decoded = Decoded.decode(opcode, .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) }, + 0x9E => .{ .func = &funcs.skipIfPressed, .decoded = Decoded.decode(opcode, .xnn) }, + 0xA1 => .{ .func = &funcs.skipIfNotPressed, .decoded = Decoded.decode(opcode, .xnn) }, else => .{ .func = &invalid, .decoded = undefined }, }, 0xF => .{ @@ -133,14 +146,14 @@ fn decode(cpu: *Cpu, decoded: Decoded) void { 0x85 => &funcs.loadFlags, else => &invalid, }, - .decoded = Decoded.from(opcode, cpu, .xnn), + .decoded = Decoded.decode(opcode, .xnn), }, }; cpu.code[cpu.pc] = inst; - @call(.always_tail, inst.func, .{ cpu, inst.decoded }); + @call(.always_tail, inst.func, .{ cpu, inst.decoded.to() }); } -fn invalid(cpu: *Cpu, decoded: Decoded) void { +fn invalid(cpu: *Cpu, decoded: u32) callconv(.C) void { _ = decoded; // autofix std.debug.panic( "invalid instruction: {x:0>4}", diff --git a/src/tailfuncs.zig b/src/tailfuncs.zig index 26b2a4e..42afb7e 100644 --- a/src/tailfuncs.zig +++ b/src/tailfuncs.zig @@ -3,236 +3,255 @@ const std = @import("std"); const Cpu = @import("./tail.zig").Cpu; const Decoded = @import("./tail.zig").Decoded; -inline fn cont(cpu: *Cpu) void { +inline fn cont(cpu: *Cpu, comptime inc_by_2: bool) void { if (cpu.instructions == 0) { @setCold(true); return; } + cpu.instructions -= 1; + if (inc_by_2) { + cpu.pc += 2; + } const next = cpu.code[cpu.pc]; - @call(.always_tail, next.func, .{ cpu, next.decoded }); + @call(.always_tail, next.func, .{ cpu, next.decoded.to() }); } /// 00E0: clear the screen -pub fn clear(cpu: *Cpu, decoded: Decoded) void { +pub fn clear(cpu: *Cpu, decoded: u32) callconv(.C) void { _ = decoded; @memset(&cpu.display, 0); - cpu.pc += 2; - cont(cpu); + cont(cpu, true); } /// 00EE: return -pub fn ret(cpu: *Cpu, decoded: Decoded) void { +pub fn ret(cpu: *Cpu, decoded: u32) callconv(.C) void { _ = decoded; cpu.pc = cpu.stack.popOrNull() orelse @panic("empty stack"); - cont(cpu); + cont(cpu, false); } /// 1NNN: jump to NNN -pub fn jump(cpu: *Cpu, decoded: Decoded) void { - cpu.pc = decoded.nnn; - cont(cpu); +pub fn jump(cpu: *Cpu, decoded: u32) callconv(.C) void { + cpu.pc = Decoded.from(decoded).nnn; + cont(cpu, false); } /// 2NNN: call address NNN -pub fn call(cpu: *Cpu, decoded: Decoded) void { +pub fn call(cpu: *Cpu, decoded: u32) callconv(.C) void { cpu.stack.append(cpu.pc + 2) catch @panic("full stack"); - cpu.pc = decoded.nnn; - cont(cpu); + cpu.pc = Decoded.from(decoded).nnn; + cont(cpu, false); } /// 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}); +pub fn skipIfEqual(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, const nn = Decoded.from(decoded).xnn; + cpu.pc += if (cpu.v[x] == nn) 4 else 2; + cont(cpu, false); } /// 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}); +pub fn skipIfNotEqual(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, const nn = Decoded.from(decoded).xnn; + cpu.pc += if (cpu.v[x] != nn) 4 else 2; + cont(cpu, false); } /// 5XY0: skip next instruction if VX == VY -pub fn skipIfRegistersEqual(cpu: *Cpu, decoded: Decoded) void { +pub fn skipIfRegistersEqual(cpu: *Cpu, decoded: u32) callconv(.C) 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; +pub fn setRegister(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, const nn = Decoded.from(decoded).xnn; cpu.v[x] = nn; - cpu.pc += 2; - cont(cpu); + cont(cpu, true); } /// 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}); +pub fn addImmediate(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, const nn = Decoded.from(decoded).xnn; + cpu.v[x] +%= nn; + cont(cpu, true); } /// 8XY0: set VX to VY -pub fn setRegisterToRegister(cpu: *Cpu, decoded: Decoded) void { - _ = decoded; - std.debug.panic("setRegisterToRegister at {x:0>3}", .{cpu.pc}); +pub fn setRegisterToRegister(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, const y = Decoded.from(decoded).xy; + cpu.v[x] = cpu.v[y]; + cont(cpu, true); } /// 8XY1: set VX to VX | VY -pub fn bitwiseOr(cpu: *Cpu, decoded: Decoded) void { +pub fn bitwiseOr(cpu: *Cpu, decoded: u32) callconv(.C) 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}); +pub fn bitwiseAnd(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, const y = Decoded.from(decoded).xy; + cpu.v[x] &= cpu.v[y]; + cont(cpu, true); } /// 8XY3: set VX to VX ^ VY -pub fn bitwiseXor(cpu: *Cpu, decoded: Decoded) void { +pub fn bitwiseXor(cpu: *Cpu, decoded: u32) callconv(.C) 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; +pub fn addRegisters(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, const y = Decoded.from(decoded).xy; cpu.v[x], cpu.v[0xF] = @addWithOverflow(cpu.v[x], cpu.v[y]); - cpu.pc += 2; - cont(cpu); + cont(cpu, true); } /// 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}); +pub fn subRegisters(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, const y = Decoded.from(decoded).xy; + cpu.v[x], const overflow = @subWithOverflow(cpu.v[x], cpu.v[y]); + cpu.v[0xF] = ~overflow; + cont(cpu, true); } /// 8XY6: set VX to VY >> 1, set VF to the former least significant bit of VY -pub fn shiftRight(cpu: *Cpu, decoded: Decoded) void { +pub fn shiftRight(cpu: *Cpu, decoded: u32) callconv(.C) 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 { +pub fn subRegistersReverse(cpu: *Cpu, decoded: u32) callconv(.C) 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}); +pub fn shiftLeft(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, const y = Decoded.from(decoded).xy; + cpu.v[0xF] = (cpu.v[y] >> 7) & 1; + cpu.v[x] = cpu.v[y] << 1; + cont(cpu, true); } /// 9XY0: skip next instruction if VX != VY -pub fn skipIfRegistersNotEqual(cpu: *Cpu, decoded: Decoded) void { +pub fn skipIfRegistersNotEqual(cpu: *Cpu, decoded: u32) callconv(.C) 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}); +pub fn setI(cpu: *Cpu, decoded: u32) callconv(.C) void { + cpu.i = Decoded.from(decoded).nnn; + cont(cpu, true); } /// BNNN: jump to NNN + V0 -pub fn jumpV0(cpu: *Cpu, decoded: Decoded) void { +pub fn jumpV0(cpu: *Cpu, decoded: u32) callconv(.C) 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}); +pub fn random(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, const nn = Decoded.from(decoded).xnn; + const byte = cpu.random.random().int(u8); + cpu.v[x] = byte & nn; + cont(cpu, true); } /// 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 { +pub fn draw(cpu: *Cpu, decoded: u32) callconv(.C) void { _ = decoded; - std.debug.panic("draw at {x:0>3}", .{cpu.pc}); + // std.debug.panic("draw at {x:0>3}, {} to go", .{ cpu.pc, cpu.instructions }); + std.log.warn("draw", .{}); + cont(cpu, true); } /// EX9E: skip next instruction if the key in VX is pressed -pub fn skipIfPressed(cpu: *Cpu, decoded: Decoded) void { +pub fn skipIfPressed(cpu: *Cpu, decoded: u32) callconv(.C) 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 { +pub fn skipIfNotPressed(cpu: *Cpu, decoded: u32) callconv(.C) 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 { +pub fn readDt(cpu: *Cpu, decoded: u32) callconv(.C) 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 { +pub fn waitForKey(cpu: *Cpu, decoded: u32) callconv(.C) 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 { +pub fn setDt(cpu: *Cpu, decoded: u32) callconv(.C) 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 { +pub fn setSt(cpu: *Cpu, decoded: u32) callconv(.C) 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}); +pub fn incrementI(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, _ = Decoded.from(decoded).xnn; + cpu.i +%= cpu.v[x]; + cont(cpu, true); } /// FX29: set I to the address of the sprite for the digit in VX -pub fn setIToFont(cpu: *Cpu, decoded: Decoded) void { +pub fn setIToFont(cpu: *Cpu, decoded: u32) callconv(.C) 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 { +pub fn storeBcd(cpu: *Cpu, decoded: u32) callconv(.C) void { _ = decoded; - std.debug.panic("storeBcd at {x:0>3}", .{cpu.pc}); + std.debug.panic("storeBcd at {x:0>3}, {} to go", .{ cpu.pc, cpu.instructions }); } /// 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}); +pub fn store(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, _ = Decoded.from(decoded).xnn; + @memcpy(cpu.mem[cpu.i .. cpu.i + x + 1], cpu.v[0 .. x + 1]); + cpu.i = cpu.i + x + 1; + cont(cpu, true); } /// 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}); +pub fn load(cpu: *Cpu, decoded: u32) callconv(.C) void { + const x, _ = Decoded.from(decoded).xnn; + @memcpy(cpu.v[0 .. x + 1], cpu.mem[cpu.i .. cpu.i + x + 1]); + cpu.i = cpu.i + x + 1; + cont(cpu, true); } /// FX75: store registers [V0, VX] in flags. X < 8 -pub fn storeFlags(cpu: *Cpu, decoded: Decoded) void { +pub fn storeFlags(cpu: *Cpu, decoded: u32) callconv(.C) 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 { +pub fn loadFlags(cpu: *Cpu, decoded: u32) callconv(.C) void { _ = decoded; std.debug.panic("loadFlags at {x:0>3}", .{cpu.pc}); }