scratch/2021-03-16/zig8/src/emu.zig

204 lines
5.2 KiB
Zig

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);
}