266 lines
9.8 KiB
Zig
266 lines
9.8 KiB
Zig
const std = @import("std");
|
|
const disp = @import("display.zig");
|
|
const util = @import("util.zig");
|
|
const sched = @import("scheduler.zig");
|
|
const Chip8 = @import("chip8.zig").Chip8;
|
|
const Instruction = @import("instruction.zig").Instruction;
|
|
const Point = disp.Point;
|
|
const Display = disp.Display;
|
|
const Scheduler = sched.Scheduler;
|
|
const EventKind = sched.EventKind;
|
|
|
|
const OPS_PER_HZ = sched.OPS_PER_HZ;
|
|
const panic = std.debug.panic;
|
|
var prng = std.rand.DefaultPrng.init(1337);
|
|
|
|
pub const Cpu = struct {
|
|
i: u16,
|
|
pc: u16,
|
|
sp: u16,
|
|
v: [16]u8,
|
|
stack: [16]u16,
|
|
scheduler: *Scheduler,
|
|
|
|
pub fn new(scheduler: *Scheduler) Cpu {
|
|
return .{
|
|
.i = 0x0000,
|
|
.pc = 0x0200,
|
|
.sp = 0x0000,
|
|
.v = [_]u8{0x00} ** 16,
|
|
.stack = [_]u16{0x0000} ** 16,
|
|
.scheduler = scheduler,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub fn step(cpu: *Cpu, display: *Display, mem: *[0x1000]u8) u64 {
|
|
const opcode = fetch(cpu, mem);
|
|
|
|
// std.log.debug("0x{X:}", .{opcode});
|
|
execute(cpu, display, mem, decode(opcode));
|
|
|
|
return 1;
|
|
}
|
|
|
|
fn fetch(cpu: *Cpu, mem: *[0x1000]u8) u16 {
|
|
const high: u16 = mem[cpu.pc];
|
|
const low: u16 = mem[cpu.pc + 1];
|
|
|
|
cpu.pc += 2;
|
|
|
|
return high << 8 | low;
|
|
}
|
|
|
|
fn decode(opcode: u16) Instruction {
|
|
const n1: u4 = @intCast(u4, (opcode & 0xF000) >> 12);
|
|
const n2: u4 = @intCast(u4, (opcode & 0x0F00) >> 8);
|
|
const n3: u4 = @intCast(u4, (opcode & 0x00F0) >> 4);
|
|
const n4: u4 = @intCast(u4, (opcode & 0x000F) >> 0);
|
|
|
|
return switch (n1) {
|
|
0x0 => switch (n4) {
|
|
0x0 => return Instruction.CLS, // 0x00E0 | CLS
|
|
0xE => return Instruction.RET, // 0x00EE | RET
|
|
else => panic("0x{X:} is an unknown 0x0-prefixed opcode", .{opcode}),
|
|
},
|
|
0x1 => Instruction{ .JP = util.calc12bit(n2, n3, n4) }, // 0x1nnn | JP u12
|
|
0x2 => Instruction{ .CALL = util.calc12bit(n2, n3, n4) }, // 0x2nnn | CALL u12
|
|
0x3 => Instruction{ .SE_Vx = .{ .x = n2, .kk = util.calc8bit(n3, n4) } }, // 0x3xkk | SE Vx, u8
|
|
0x4 => Instruction{ .SNE_Vx = .{ .x = n2, .kk = util.calc8bit(n3, n4) } }, // 0x4xkk | SNE Vx, u8
|
|
0x5 => Instruction{ .SE_Vx_Vy = .{ .x = n2, .y = n3 } }, // 0x5xy0 | SE Vx, Vy
|
|
0x6 => Instruction{ .LD_Vx = .{ .x = n2, .kk = util.calc8bit(n3, n4) } }, // 0x6xkk | LD Vx, u8
|
|
0x7 => Instruction{ .ADD_Vx = .{ .x = n2, .kk = util.calc8bit(n3, n4) } }, // 0x7xkk | ADD Vx, u8
|
|
0x8 => switch (n4) {
|
|
0x0 => return Instruction{ .LD_Vx_Vy = .{ .x = n2, .y = n3 } }, // 0x8xy0 | LD Vx, Vy
|
|
0x1 => return Instruction{ .OR = .{ .x = n2, .y = n3 } }, // 0x8xy1 | OR Vx, Vy
|
|
0x2 => return Instruction{ .AND = .{ .x = n2, .y = n3 } }, // 0x8xy2 | AND Vx, Vy
|
|
0x3 => return Instruction{ .XOR = .{ .x = n2, .y = n3 } }, // 0x8xy3 | XOR Vx, Vy
|
|
0x4 => return Instruction{ .ADD_Vx_Vy = .{ .x = n2, .y = n3 } }, // 0x8xy4 | ADD Vx, Vy
|
|
0x5 => return Instruction{ .SUB = .{ .x = n2, .y = n3 } }, // 0x8xy5 | SUB Vx, Vy
|
|
0x6 => return Instruction{ .SHR = .{ .x = n2, .y = n3 } }, // 0x8xy6 | SHR Vx, Vy
|
|
0x7 => return Instruction{ .SUBN = .{ .x = n2, .y = n3 } }, // 0x8xy7 | SUBN Vx, Vy
|
|
0xE => return Instruction{ .SHL = .{ .x = n2, .y = n3 } }, // 0x8xyE | SHL Vx, Vy
|
|
else => panic("0x{X:} is an unknown 0x8-prefixed opcode", .{opcode}),
|
|
},
|
|
0x9 => Instruction{ .SNE_Vx_Vy = .{ .x = n2, .y = n3 } }, // 0x9xy0 | SNE Vx, Vy
|
|
0xA => Instruction{ .LD_I = util.calc12bit(n2, n3, n4) }, // 0xAnnn | LD I, u12
|
|
0xB => Instruction{ .JP_V0 = util.calc12bit(n2, n3, n4) }, // 0xBnnn | JP V0, u12
|
|
0xC => Instruction{ .RND = .{ .x = n2, .kk = util.calc8bit(n3, n4) } }, // 0xCxkk | RND Vx, u8
|
|
0xD => Instruction{ .DRW = .{ .x = n2, .y = n3, .n = n4 } }, // 0xDxyn | DRW Vx, Vy, u4
|
|
0xE => switch (n3) {
|
|
0x9 => return Instruction{ .SKP = n2 }, // 0xEx9E | SKP Vx
|
|
0xA => return Instruction{ .SKNP = n2 }, // 0xExA1 | SKNP Vx
|
|
else => panic("0x{X:} is an unknown 0xE-prefixed opcode", .{opcode}),
|
|
},
|
|
0xF => switch (n3) {
|
|
0x0 => switch (n4) {
|
|
0x7 => return Instruction{ .LD_Vx_DT = n2 }, // 0xFx07 | LD Vx, DT
|
|
0xA => return Instruction{ .LD_Vx_K = n2 }, // 0xFx0A | LD Vx, K
|
|
else => panic("0x{X:} is an unknown 0xFx0-prefixed opcode", .{opcode}),
|
|
},
|
|
0x1 => switch (n4) {
|
|
0x5 => return Instruction{ .LD_DT_Vx = n2 }, // 0xFx15 | LD DT, Vx
|
|
0x8 => return Instruction{ .LD_ST_Vx = n2 }, // 0xFx18 | LD ST, Vx
|
|
0xE => return Instruction{ .ADD_I_Vx = n2 }, // 0xFx1E | ADD I, Vx
|
|
else => panic("0x{X:} is an unknown 0xFx1-prefixed opcode", .{opcode}),
|
|
},
|
|
0x2 => return Instruction{ .LD_F_Vx = n2 }, // 0xFx29 | LD F, Vx
|
|
0x3 => return Instruction{ .LD_B_Vx = n2 }, // 0xFx33 | LD B, Vx
|
|
0x5 => return Instruction{ .LD_IPTR_Vx = n2 }, // 0xFx55 | LD [I], Vx
|
|
0x6 => return Instruction{ .LD_Vx_IPTR = n2 }, // 0xFx64 | LD Vx, [I]
|
|
else => panic("0x{X:} is an unknown 0xFx-prefixed opcode", .{opcode}),
|
|
},
|
|
};
|
|
}
|
|
|
|
fn execute(cpu: *Cpu, display: *Display, mem: *[0x1000]u8, instr: Instruction) void {
|
|
const scheduler = cpu.scheduler;
|
|
|
|
switch (instr) {
|
|
.CLS => {
|
|
display.buf = [_]u8{0x00} ** (disp.WIDTH * disp.HEIGHT);
|
|
scheduler.push(EventKind.RequestRedraw, scheduler.now());
|
|
},
|
|
.RET => {
|
|
cpu.pc = cpu.stack[cpu.sp];
|
|
cpu.sp -= 1;
|
|
},
|
|
.JP => |addr| {
|
|
cpu.pc = addr;
|
|
},
|
|
.CALL => |addr| {
|
|
cpu.sp += 1;
|
|
cpu.stack[cpu.sp] = cpu.pc;
|
|
cpu.pc = addr;
|
|
},
|
|
.SE_Vx => |args| {
|
|
if (cpu.v[args.x] == args.kk) {
|
|
cpu.pc += 2;
|
|
}
|
|
},
|
|
.SNE_Vx => |args| {
|
|
if (cpu.v[args.x] != args.kk) {
|
|
cpu.pc += 2;
|
|
}
|
|
},
|
|
.SE_Vx_Vy => |args| {
|
|
if (cpu.v[args.x] == cpu.v[args.y]) {
|
|
cpu.pc += 2;
|
|
}
|
|
},
|
|
.LD_Vx => |args| {
|
|
cpu.v[args.x] = args.kk;
|
|
},
|
|
.ADD_Vx => |args| {
|
|
cpu.v[args.x] +%= args.kk;
|
|
},
|
|
.LD_Vx_Vy => |args| {
|
|
cpu.v[args.x] = cpu.v[args.y];
|
|
},
|
|
.OR => |args| {
|
|
cpu.v[args.x] |= cpu.v[args.y];
|
|
},
|
|
.AND => |args| {
|
|
cpu.v[args.x] &= cpu.v[args.y];
|
|
},
|
|
.XOR => |args| {
|
|
cpu.v[args.x] ^= cpu.v[args.y];
|
|
},
|
|
.ADD_Vx_Vy => |args| {
|
|
const did_overflow = @addWithOverflow(u8, cpu.v[args.x], cpu.v[args.y], &cpu.v[args.x]);
|
|
cpu.v[0xF] = @boolToInt(did_overflow);
|
|
},
|
|
.SUB => |args| {
|
|
cpu.v[0xF] = @boolToInt(cpu.v[args.x] > cpu.v[args.y]);
|
|
cpu.v[args.x] -%= cpu.v[args.y];
|
|
},
|
|
.SHR => |args| {
|
|
cpu.v[0xF] = @boolToInt((cpu.v[args.x] & 0x01) == 0x01);
|
|
cpu.v[args.x] >>= 1;
|
|
},
|
|
.SUBN => |args| {
|
|
cpu.v[0xF] = @boolToInt(cpu.v[args.y] > cpu.v[args.x]);
|
|
cpu.v[args.x] = cpu.v[args.y] -% cpu.v[args.x];
|
|
},
|
|
.SHL => |args| {
|
|
cpu.v[0xF] = @boolToInt(((cpu.v[args.x] >> 7) & 0x01) == 0x01);
|
|
cpu.v[args.x] <<= 1;
|
|
},
|
|
.SNE_Vx_Vy => |args| {
|
|
if (cpu.v[args.x] != cpu.v[args.y]) {
|
|
cpu.pc += 2;
|
|
}
|
|
},
|
|
.LD_I => |addr| {
|
|
cpu.i = addr;
|
|
},
|
|
.JP_V0 => |addr| {
|
|
cpu.pc = addr + cpu.v[0];
|
|
},
|
|
.RND => |args| {
|
|
cpu.v[args.x] = prng.random().int(u8) & args.kk;
|
|
},
|
|
.DRW => |args| {
|
|
const x = args.x;
|
|
const y = args.y;
|
|
const n = args.n;
|
|
|
|
const draw_pos = Point{ .x = cpu.v[x], .y = cpu.v[y] };
|
|
const sprite_data = mem[cpu.i..(cpu.i + n)];
|
|
|
|
// Draw Sprite
|
|
const collision = disp.drawSprite(display, &draw_pos, &sprite_data);
|
|
|
|
cpu.v[0xF] = @boolToInt(collision);
|
|
scheduler.push(EventKind.RequestRedraw, scheduler.now());
|
|
},
|
|
.SKP => |x| {
|
|
std.log.debug("TODO: SKP V{}", .{x});
|
|
},
|
|
.SKNP => |x| {
|
|
std.log.debug("TODO: SKNP V{}", .{x});
|
|
},
|
|
.LD_Vx_DT => |x| {
|
|
if (scheduler.find(EventKind.DelayTimer)) |e| {
|
|
const num = (e.timestamp - scheduler.now()) / OPS_PER_HZ;
|
|
cpu.v[x] = @intCast(u8, num);
|
|
} else {
|
|
cpu.v[x] = 0;
|
|
}
|
|
},
|
|
.LD_Vx_K => |x| {
|
|
std.log.debug("TODO: LD V{}, K", .{x});
|
|
},
|
|
.LD_DT_Vx => |x| {
|
|
const when = scheduler.now() + OPS_PER_HZ * @as(u64, cpu.v[x]);
|
|
scheduler.replaceOrPush(EventKind.DelayTimer, when);
|
|
},
|
|
.LD_ST_Vx => |x| {
|
|
const when = scheduler.now() + OPS_PER_HZ * @as(u64, cpu.v[x]);
|
|
scheduler.replaceOrPush(EventKind.SoundTimer, when);
|
|
},
|
|
.ADD_I_Vx => |x| {
|
|
const sum: u16 = cpu.i + cpu.v[x];
|
|
cpu.v[0xF] = @boolToInt(sum >= 0x1000);
|
|
cpu.i = sum & 0x0FFF;
|
|
},
|
|
.LD_F_Vx => |x| {
|
|
cpu.i = 0x50 + (5 * cpu.v[x]);
|
|
},
|
|
.LD_B_Vx => |x| {
|
|
const value = cpu.v[x];
|
|
mem[cpu.i] = value / 100;
|
|
mem[cpu.i + 1] = (value / 10) % 10;
|
|
mem[cpu.i + 2] = value % 10;
|
|
},
|
|
.LD_IPTR_Vx => |x| {
|
|
const upper = x + 1;
|
|
std.mem.copy(u8, mem[(cpu.i)..(cpu.i + upper)], cpu.v[0..upper]);
|
|
},
|
|
.LD_Vx_IPTR => |x| {
|
|
const upper = x + 1;
|
|
std.mem.copy(u8, cpu.v[0..upper], mem[cpu.i..(cpu.i + upper)]);
|
|
},
|
|
}
|
|
}
|