const std = @import("std"); const expect = @import("std").testing.expect; const panic = @import("std").debug.panic; const Dir = @import("std").fs.Dir; const Instruction = @import("instructions.zig").Instruction; const InstrType = @import("instructions.zig").Type; const draw_sprite = @import("display.zig").draw_sprite; const Display = @import("display.zig").Display; const Point = @import("display.zig").Point; pub const Cpu = struct { i: u16, pc: u16, sp: u16, v: [16]u8, stack: [16]u16, memory: [0x1000]u8, disp: Display, }; pub fn fetch(cpu: *Cpu) u16 { const high: u16 = cpu.memory[cpu.pc]; const low: u16 = cpu.memory[cpu.pc + 1]; cpu.pc += 2; return high << 8 | low; } pub fn decode(cpu: *Cpu, word: u16) ?Instruction { const nib1: u4 = @intCast(u4, (word & 0xF000) >> 12); const nib2: u4 = @intCast(u4, (word & 0x0F00) >> 8); const nib3: u4 = @intCast(u4, (word & 0x00F0) >> 4); const nib4: u4 = @intCast(u4, (word & 0x000F) >> 0); if (nib1 == 0x0) { // nib2 is 0x0, nib3 is 0xE if (nib4 == 0) { // 0x00E0 | CLS return Instruction.CLS; } if (nib4 == 0xE) { // 0x00EE | RET return Instruction.RET; } } else if (nib1 == 0x1) { // 0x1nnn | JP addr const addr = calc_u12(nib2, nib3, nib4); return Instruction { .JP = addr }; } else if (nib1 == 0x2) { // 0x2nnn | CALL addr const addr = calc_u12(nib2, nib3, nib4); return Instruction { .CALL = addr }; } else if (nib1 == 0x3) { // 0x3xkk | SE Vx, u8 return Instruction { .SE_3 = .{ .x = nib2, .kk = calc_u8(nib3, nib4) }}; } else if (nib1 == 0x6) { // 0x6xkk | LD Vx, u8 return Instruction { .LD_6 = .{ .x = nib2, .kk = calc_u8(nib3, nib4) }}; } else if(nib1 == 0x7) { // 0x7xkk | LD Vx, kk return Instruction { .ADD_7 = .{ .x = nib2, .kk = calc_u8(nib3, nib4) }}; } else if (nib1 == 0xA) { // 0xAnnn | LD I, addr const addr = calc_u12(nib2, nib3, nib4); return Instruction { .LD_I = addr }; } else if (nib1 == 0xD) { // 0xDxyn | DRW Vx, Vy, n return Instruction {.DRW = .{ .x = nib2, .y = nib3, .n = nib4 }}; } return null; } pub fn execute(cpu: *Cpu, instr: Instruction) void { switch (instr) { .CLS => std.log.debug("CLS", .{}), .RET => { std.log.debug("RET", .{}); cpu.pc = cpu.stack[cpu.sp]; cpu.sp -= 1; }, .JP => |addr| { std.log.debug("JP 0x{X:}", .{ addr }); cpu.pc = addr; }, .CALL => |addr| { std.log.debug("CALL 0x{X:}", .{ addr }); cpu.sp += 1; cpu.stack[cpu.sp] = cpu.pc; cpu.pc = addr; }, .SE_3 => |args| { const x = args.x; const kk = args.kk; std.log.debug("SE V{}, 0x{X:}", .{ x, kk }); if (cpu.v[x] != kk) { cpu.pc += 2; } }, .LD_6 => |args| { const x = args.x; const kk = args.kk; std.log.debug("LD V{} 0x{X:}", .{ x, kk }); cpu.v[x] = kk; }, .LD_I => |addr| { std.log.debug("LD I, 0x{X:}", .{ addr }); cpu.i = addr; }, .DRW => |args| { const x = args.x; const y = args.y; const n = args.n; std.log.debug("DRW V{}, V{}, 0x{X:}", .{ x, y, n }); const draw_pos: Point = .{ .x = cpu.v[x], .y = cpu.v[y] }; const sprite_data = cpu.memory[cpu.i..(cpu.i + n)]; // Draw Sprite const collision = draw_sprite(&cpu.disp, &draw_pos, &sprite_data); cpu.v[0xF] = if (collision) 1 else 0; // Request Redraw }, .ADD_7 => |args| { const x = args.x; const kk = args.kk; std.log.debug("ADD V{}, 0x{X:}", .{ x, kk }); cpu.v[x] = cpu.v[x] + kk; } } } fn load_rom(cpu: *Cpu, buf: []u8) void { var mem_index: usize = 0x200; for (buf) |byte| { cpu.memory[mem_index] = byte; mem_index += 1; } } pub fn load_rom_path(cpu: *Cpu, path: []const u8) !void { var heap = std.heap.page_allocator; const file = try std.fs.cwd().openFile(path, .{ .read = true }); defer file.close(); const file_size = try file.getEndPos(); const rom_buf = try file.readToEndAlloc(heap, file_size); defer heap.free(rom_buf); load_rom(cpu, rom_buf); } pub fn step(cpu: *Cpu) void { const opcode = fetch(cpu); const maybe_instr = decode(cpu, opcode); if (maybe_instr) |instr| { execute(cpu, instr); } else { panic("Unknown Opcode: 0x{X:}", . { opcode }); } } fn calc_u12(nib2: u4, nib3: u4, nib4: u4) u12 { return @as(u12, nib2) << 8 | @as(u12, nib3) << 4 | @as(u12, nib4); } fn calc_u8(nib_a: u4, nib_b: u4) u8 { return @as(u8, nib_a) << 4 | @as(u8, nib_b); } test "calc_u12 works" { const left: u12 = 0xABC; const right: u12 = calc_u12(0xA, 0xB, 0xC); expect(left == right); } test "calc_u8 works" { const left: u8 = 0xAB; const right: u12 = calc_u8(0xA, 0xB); expect(left == right); }