500 lines
15 KiB
Zig
500 lines
15 KiB
Zig
const Self = @This();
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
const Errors = error { UnknownInstruction };
|
|
|
|
allocator: Allocator,
|
|
|
|
display: []bool,
|
|
display_width: u8,
|
|
display_height: u8,
|
|
|
|
memory: []u8,
|
|
stack: [16]u16,
|
|
rng: std.rand.DefaultPrng,
|
|
input: [16]bool,
|
|
|
|
V: [16]u8,
|
|
|
|
I: u16, // Address pointer
|
|
PC: u16, // Program counter
|
|
SP: u8, // Stack pointer
|
|
DT: u8, // Delay timer
|
|
ST: u8, // Sound timer
|
|
|
|
fn get_inst_x(inst: u16) u4 {
|
|
return @truncate((inst & 0x0F00) >> 8);
|
|
}
|
|
|
|
fn get_inst_kk(inst: u16) u8 {
|
|
return @truncate(inst & 0x00FF);
|
|
}
|
|
|
|
fn get_inst_y(inst: u16) u4 {
|
|
return @truncate((inst & 0x00F0) >> 4);
|
|
}
|
|
|
|
fn get_inst_nnn(inst: u16) u12 {
|
|
return @truncate(inst & 0x0FFF);
|
|
}
|
|
|
|
fn get_inst_n(inst: u16) u4 {
|
|
return @truncate(inst & 0x000F);
|
|
}
|
|
|
|
pub fn init(allocator: Allocator) !Self {
|
|
const seed_bits: u128 = @bitCast(std.time.nanoTimestamp());
|
|
const seed: u64 = @truncate(seed_bits);
|
|
|
|
const display_width = 64;
|
|
const display_height = 32;
|
|
const memory_size = 4096;
|
|
const memory = try allocator.alloc(u8, memory_size);
|
|
@memset(memory, 0);
|
|
|
|
var self = Self {
|
|
.allocator = allocator,
|
|
.display = try allocator.alloc(bool, display_width * display_height),
|
|
.display_width = display_width,
|
|
.display_height = display_height,
|
|
.memory = memory,
|
|
.stack = [1]u16{0} ** 16,
|
|
.V = [1]u8{0} ** 16,
|
|
.I = 0,
|
|
.PC = 0x200,
|
|
.SP = 0,
|
|
.DT = 0,
|
|
.ST = 0,
|
|
.rng = std.rand.DefaultPrng.init(seed),
|
|
.input = [1]bool{false} ** 16
|
|
};
|
|
|
|
self.init_default_sprites();
|
|
return self;
|
|
}
|
|
|
|
pub fn deinit(self: *Self) void {
|
|
self.allocator.free(self.display);
|
|
self.allocator.free(self.memory);
|
|
}
|
|
|
|
pub fn init_default_sprites(self: *Self) void {
|
|
self.set_memory(0x000, &[_]u8{
|
|
0xF0, 0x90, 0x90, 0x90, 0xF0, // "0"
|
|
0x20, 0x60, 0x20, 0x20, 0x70, // "1"
|
|
0xF0, 0x10, 0xF0, 0x80, 0xF0, // "2"
|
|
0xF0, 0x10, 0xF0, 0x10, 0xF0, // "3"
|
|
0x90, 0x90, 0xF0, 0x10, 0x10, // "4"
|
|
0xF0, 0x80, 0xF0, 0x10, 0xF0, // "5"
|
|
0xF0, 0x80, 0xF0, 0x90, 0xF0, // "6"
|
|
0xF0, 0x10, 0x20, 0x40, 0x40, // "7"
|
|
0xF0, 0x90, 0xF0, 0x90, 0xF0, // "8"
|
|
0xF0, 0x90, 0xF0, 0x10, 0xF0, // "9"
|
|
0xF0, 0x90, 0xF0, 0x90, 0x90, // "A"
|
|
0xE0, 0x90, 0xE0, 0x90, 0xE0, // "B"
|
|
0xF0, 0x80, 0x80, 0x80, 0xF0, // "C"
|
|
0xE0, 0x90, 0x90, 0x90, 0xE0, // "D"
|
|
0xF0, 0x80, 0xF0, 0x80, 0xF0, // "E"
|
|
0xF0, 0x80, 0xF0, 0x80, 0x80 // "F"
|
|
});
|
|
}
|
|
|
|
pub fn display_get(self: *Self, x: u8, y: u8) bool {
|
|
const idx: u16 = @as(u16, y) * self.display_width + x;
|
|
return self.display[idx];
|
|
}
|
|
|
|
pub fn display_set(self: *Self, x: u8, y: u8, value: bool) void {
|
|
const idx: u16 = @as(u16, y) * self.display_width + x;
|
|
self.display[idx] = value;
|
|
}
|
|
|
|
pub fn draw(self: *Self, x: u8, y: u8, n: u4) bool {
|
|
var result = false;
|
|
for (0..n) |i| {
|
|
const sprite_row = self.memory[self.I + i];
|
|
for (0..8) |ix| {
|
|
const disp_x: u8 = @intCast((x+ix) % self.display_width);
|
|
const disp_y: u8 = @intCast((y+i) % self.display_height);
|
|
const sprite_pixel = ((sprite_row >> @intCast(7-ix)) & 1) == 1;
|
|
|
|
const display_pixel = self.display_get(disp_x, disp_y);
|
|
const new_pixel = (display_pixel != sprite_pixel);
|
|
self.display_set(disp_x, disp_y, new_pixel);
|
|
if (display_pixel and display_pixel != new_pixel) {
|
|
result = true;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
pub fn is_input_pressed(self: *Self, key: u4) bool {
|
|
return self.input[key];
|
|
}
|
|
|
|
pub fn set_memory(self: *Self, base_address: u16, memory: []const u8) void {
|
|
for (0.., memory) |offset, byte| {
|
|
self.memory[base_address + offset] = byte;
|
|
}
|
|
}
|
|
|
|
pub fn set_memory_from_file(self: *Self, base_address: u16, file: std.fs.File) !void {
|
|
const stat = try file.stat();
|
|
|
|
var buffer: [512]u8 = undefined;
|
|
var offset: usize = 0;
|
|
var bytes_left: i64 = @intCast(stat.size);
|
|
while (bytes_left > 0) : (bytes_left -= buffer.len) {
|
|
const bytes_read = try file.read(&buffer);
|
|
for (0..bytes_read) |byte_index| {
|
|
self.memory[base_address + offset + byte_index] = buffer[byte_index];
|
|
}
|
|
offset += buffer.len;
|
|
}
|
|
}
|
|
|
|
pub fn current_instruction(self: *Self) u16 {
|
|
const high_byte: u16 = self.memory[self.PC];
|
|
const low_byte: u16 = self.memory[self.PC+1];
|
|
return (high_byte << 8) | low_byte;
|
|
}
|
|
|
|
pub fn step(self: *Self) !void {
|
|
const instruction: u16 = self.current_instruction();
|
|
self.PC += 2;
|
|
try self.run_instruction(instruction);
|
|
}
|
|
|
|
pub fn update_timer(self: *Self) void {
|
|
if (self.DT > 0) {
|
|
self.DT -= 1;
|
|
}
|
|
if (self.ST > 0) {
|
|
self.ST -= 1;
|
|
}
|
|
}
|
|
|
|
pub fn clear_display(self: *Self) void {
|
|
const display_size = @as(u16, self.display_width) * self.display_height;
|
|
for (0..display_size) |i| {
|
|
self.display[i] = false;
|
|
}
|
|
}
|
|
|
|
pub fn decode_instruction_fmt(inst: u16) [*:0]const u8 {
|
|
if (inst & 0xFFFF == 0x00E0) { // 00E0 - CLS
|
|
return "CLS";
|
|
|
|
} else if (inst & 0xFFFF == 0x00EE) { // 00EE - RET
|
|
return "RET";
|
|
|
|
} else if (inst & 0xF000 == 0x0000) { // 0nnn - SYS addr
|
|
return "SYS {nnn}";
|
|
|
|
} else if (inst & 0xF000 == 0x1000) { // 1nnn - JP addr
|
|
return "JP {nnn}";
|
|
|
|
} else if (inst & 0xF000 == 0x2000) { // 2nnn - CALL addr
|
|
return "CALL {nnn}";
|
|
|
|
} else if (inst & 0xF000 == 0x3000) { // 3xkk - SE Vx, byte
|
|
return "SE V{x}, {kk}";
|
|
|
|
} else if (inst & 0xF000 == 0x4000) { // 4xkk - SNE Vx, byte
|
|
return "SNE V{x}, {kk}";
|
|
|
|
} else if (inst & 0xF00F == 0x5000) { // 5xy0 - SE Vx, Vy
|
|
return "SE V{x}, V{y}";
|
|
|
|
} else if (inst & 0xF000 == 0x6000) { // 6xkk - LD Vx, byte
|
|
return "LD V{x}, {kk}";
|
|
|
|
} else if (inst & 0xF000 == 0x7000) { // 7xkk - ADD Vx, byte
|
|
return "ADD V{x}, {kk}";
|
|
|
|
} else if (inst & 0xF000 == 0x8000) {
|
|
if (inst & 0x000F == 0x0) { // 8xy0 - LD Vx, Vy
|
|
return "LD V{x}, V{y}";
|
|
|
|
} else if (inst & 0x000F == 0x1) { // 8xy1 - OR Vx, Vy
|
|
return "OR V{x}, V{y}";
|
|
|
|
} else if (inst & 0x000F == 0x2) { // 8xy2 - AND Vx, Vy
|
|
return "AND V{x}, V{y}";
|
|
|
|
} else if (inst & 0x000F == 0x3) { // 8xy3 - XOR Vx, Vy
|
|
return "XOR V{x}, V{y}";
|
|
|
|
} else if (inst & 0x000F == 0x4) { // 8xy4 - ADD Vx, Vy, set VF = carry
|
|
return "ADD V{x}, V{y}";
|
|
|
|
} else if (inst & 0x000F == 0x5) { // 8xy5 - SUB Vx, Vy, set VF = NOT borrow
|
|
return "SUB V{x}, V{y}";
|
|
|
|
} else if (inst & 0x000F == 0x6) { // 8xy6 - Vx SHR 1
|
|
return "SHR V{x}";
|
|
|
|
} else if (inst & 0x000F == 0x7) { // 8xy7 - SUBN Vx, Vy
|
|
return "SUBN V{x}, V{y}";
|
|
|
|
} else if (inst & 0x000F == 0xE) { // 8xyE - Vx SHR 1
|
|
return "SHL V{x}";
|
|
}
|
|
|
|
} else if (inst & 0xF00F == 0x9000) { // 9xy0 - SNE Vx, Vy
|
|
return "SNE V{x}, V{y}";
|
|
|
|
} else if (inst & 0xF000 == 0xA000) { // Annn - LD I, addr
|
|
return "LD I, {nnn}";
|
|
|
|
} else if (inst & 0xF000 == 0xB000) { // Bnnn - JP V0, addr
|
|
return "JP V0, {nnn}";
|
|
|
|
} else if (inst & 0xF000 == 0xC000) { // Cxkk - RNG Vx, byte
|
|
return "RNG V{x}, {kk}";
|
|
|
|
} else if (inst & 0xF000 == 0xD000) { // Dxyn - DRW Vx, Vy, nibble
|
|
return "DRW V{x}, V{x}, {n}\n";
|
|
|
|
} else if (inst & 0xF0FF == 0xE09E) { // Ex9E - SKP Vx
|
|
return "SKP V{x}";
|
|
|
|
} else if (inst & 0xF0FF == 0xE0A1) { // ExA1 - SKNP Vx
|
|
return "SKNP V{x}";
|
|
|
|
} else if (inst & 0xF000 == 0xF000) {
|
|
if (inst & 0x00FF == 0x07) { // Fx07 - LD Vx, DT
|
|
return "LD V{x}, DT";
|
|
|
|
} else if (inst & 0x00FF == 0x0A) { // Fx0A - LD Vx, K
|
|
return "LD V{x}, K";
|
|
|
|
} else if (inst & 0x00FF == 0x15) { // Fx15 - LD DT, Vx
|
|
return "LD DT, V{x}";
|
|
|
|
} else if (inst & 0x00FF == 0x18) { // Fx1E - LD ST, Vx
|
|
return "LD ST, V{x}";
|
|
|
|
} else if (inst & 0x00FF == 0x1E) { // Fx1E - ADD I, Vx
|
|
return "ADD I, V{x}";
|
|
|
|
} else if (inst & 0x00FF == 0x29) { // Fx29 - LD F, Vx
|
|
return "LD F, V{x}";
|
|
|
|
} else if (inst & 0x00FF == 0x33) { // Fx33 - LD B, Vx
|
|
return "LD B, V{x}";
|
|
|
|
} else if (inst & 0x00FF == 0x55) { // Fx55 - LD [I], Vx
|
|
return "LD [I], V{x}";
|
|
|
|
} else if (inst & 0x00FF == 0x65) { // Fx65 - LD Vx, [I]
|
|
return "LD V{x}, [I]";
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
pub fn run_instruction(self: *Self, inst: u16) !void {
|
|
if (inst & 0xFFFF == 0x00E0) { // 00E0 - CLS
|
|
self.clear_display();
|
|
|
|
} else if (inst & 0xFFFF == 0x00EE) { // 00EE - RET
|
|
self.PC = self.stack[self.SP];
|
|
self.SP -= 1;
|
|
|
|
} else if (inst & 0xF000 == 0x0000) { // 0nnn - SYS addr
|
|
// Ignore
|
|
|
|
} else if (inst & 0xF000 == 0x1000) { // 1nnn - JP addr
|
|
const addr = inst & 0x0FFF;
|
|
self.PC = addr;
|
|
|
|
} else if (inst & 0xF000 == 0x2000) { // 2nnn - CALL addr
|
|
const addr = inst & 0x0FFF;
|
|
self.SP += 1;
|
|
self.stack[self.SP] = self.PC;
|
|
self.PC = addr;
|
|
|
|
} else if (inst & 0xF000 == 0x3000) { // 3xkk - SE Vx, byte
|
|
const Vx = get_inst_x(inst);
|
|
const byte = get_inst_kk(inst);
|
|
if (self.V[Vx] == byte) {
|
|
self.PC += 2;
|
|
}
|
|
|
|
} else if (inst & 0xF000 == 0x4000) { // 4xkk - SNE Vx, byte
|
|
const x = get_inst_x(inst);
|
|
const byte = get_inst_kk(inst);
|
|
if (self.V[x] != byte) {
|
|
self.PC += 2;
|
|
}
|
|
|
|
} else if (inst & 0xF00F == 0x5000) { // 5xy0 - SE Vx, Vy
|
|
const Vx = get_inst_x(inst);
|
|
const Vy = get_inst_y(inst);
|
|
if (self.V[Vx] == self.V[Vy]) {
|
|
self.PC += 2;
|
|
}
|
|
|
|
} else if (inst & 0xF000 == 0x6000) { // 6xkk - LD Vx, byte
|
|
const Vx = get_inst_x(inst);
|
|
const byte = get_inst_kk(inst);
|
|
self.V[Vx] = byte;
|
|
|
|
} else if (inst & 0xF000 == 0x7000) { // 7xkk - ADD Vx, byte
|
|
const x = get_inst_x(inst);
|
|
const byte = get_inst_kk(inst);
|
|
self.V[x] +%= byte;
|
|
|
|
} else if (inst & 0xF000 == 0x8000) {
|
|
if (inst & 0x000F == 0x0) { // 8xy0 - LD Vx, Vy
|
|
const x = get_inst_x(inst);
|
|
const y = get_inst_y(inst);
|
|
self.V[x] = self.V[y];
|
|
|
|
} else if (inst & 0x000F == 0x1) { // 8xy1 - OR Vx, Vy
|
|
const x = get_inst_x(inst);
|
|
const y = get_inst_y(inst);
|
|
self.V[x] |= self.V[y];
|
|
|
|
} else if (inst & 0x000F == 0x2) { // 8xy2 - AND Vx, Vy
|
|
const x = get_inst_x(inst);
|
|
const y = get_inst_y(inst);
|
|
self.V[x] &= self.V[y];
|
|
|
|
} else if (inst & 0x000F == 0x3) { // 8xy3 - XOR Vx, Vy
|
|
const Vx = get_inst_x(inst);
|
|
const Vy = get_inst_y(inst);
|
|
self.V[Vx] ^= self.V[Vy];
|
|
|
|
} else if (inst & 0x000F == 0x4) { // 8xy4 - ADD Vx, Vy, set VF = carry
|
|
const x = get_inst_x(inst);
|
|
const y = get_inst_y(inst);
|
|
const result = @addWithOverflow(self.V[x], self.V[y]);
|
|
self.V[x] = result[0];
|
|
self.V[0xF] = (1 - result[1]);
|
|
|
|
} else if (inst & 0x000F == 0x5) { // 8xy5 - SUB Vx, Vy, set VF = NOT borrow
|
|
const x = get_inst_x(inst);
|
|
const y = get_inst_y(inst);
|
|
const result = @subWithOverflow(self.V[x], self.V[y]);
|
|
self.V[x] = result[0];
|
|
self.V[0xF] = (1 - result[1]);
|
|
|
|
} else if (inst & 0x000F == 0x6) { // 8xy6 - Vx SHR 1
|
|
const x = get_inst_x(inst);
|
|
self.V[0xF] = self.V[x] & 1;
|
|
self.V[x] = (self.V[x] >> 1);
|
|
|
|
} else if (inst & 0x000F == 0x7) { // 8xy7 - SUBN Vx, Vy
|
|
const x = get_inst_x(inst);
|
|
const y = get_inst_y(inst);
|
|
const result = @subWithOverflow(self.V[y], self.V[x]);
|
|
self.V[x] = result[0];
|
|
self.V[0xF] = (1 - result[1]);
|
|
|
|
} else if (inst & 0x000F == 0xE) { // 8xyE - Vx SHR 1
|
|
const Vx = get_inst_x(inst);
|
|
self.V[0xF] = self.V[Vx] & 128;
|
|
self.V[Vx] = (self.V[Vx] << 1);
|
|
|
|
} else {
|
|
return Errors.UnknownInstruction;
|
|
}
|
|
|
|
} else if (inst & 0xF00F == 0x9000) { // 9xy0 - SNE Vx, Vy
|
|
const Vx = get_inst_x(inst);
|
|
const Vy = get_inst_y(inst);
|
|
if (self.V[Vx] != self.V[Vy]) {
|
|
self.PC += 2;
|
|
}
|
|
|
|
} else if (inst & 0xF000 == 0xA000) { // Annn - LD I, addr
|
|
const addr = get_inst_nnn(inst);
|
|
self.I = addr;
|
|
|
|
} else if (inst & 0xF000 == 0xB000) { // Bnnn - JP V0, addr
|
|
const addr = get_inst_nnn(inst);
|
|
self.PC = self.V[0] + addr;
|
|
|
|
} else if (inst & 0xF000 == 0xC000) { // Cxkk - RNG Vx, byte
|
|
const Vx = get_inst_x(inst);
|
|
const mask = get_inst_kk(inst);
|
|
self.V[Vx] = self.rng.random().int(u8) & mask;
|
|
|
|
} else if (inst & 0xF000 == 0xD000) { // Dxyn - DRW Vx, Vy, nibble
|
|
const Vx = get_inst_x(inst);
|
|
const Vy = get_inst_y(inst);
|
|
const n = get_inst_n(inst);
|
|
self.V[0xF] = @intFromBool(self.draw(self.V[Vx], self.V[Vy], n));
|
|
|
|
} else if (inst & 0xF0FF == 0xE09E) { // Ex9E - SKP Vx
|
|
const x = get_inst_x(inst);
|
|
if (self.is_input_pressed(@truncate(self.V[x]))) {
|
|
self.PC += 2;
|
|
}
|
|
|
|
} else if (inst & 0xF0FF == 0xE0A1) { // ExA1 - SKNP Vx
|
|
const x = get_inst_x(inst);
|
|
if (!self.is_input_pressed(@truncate(self.V[x]))) {
|
|
self.PC += 2;
|
|
}
|
|
|
|
} else if (inst & 0xF000 == 0xF000) {
|
|
if (inst & 0x00FF == 0x07) { // Fx07 - LD Vx, DT
|
|
const x = get_inst_x(inst);
|
|
self.V[x] = self.DT;
|
|
|
|
} else if (inst & 0x00FF == 0x0A) { // Fx0A - LD Vx, K
|
|
const x = get_inst_x(inst);
|
|
if (!self.is_input_pressed(@truncate(self.V[x]))) {
|
|
self.PC -= 2;
|
|
}
|
|
|
|
} else if (inst & 0x00FF == 0x15) { // Fx15 - LD DT, Vx
|
|
const x = get_inst_x(inst);
|
|
self.DT = self.V[x];
|
|
|
|
} else if (inst & 0x00FF == 0x18) { // Fx1E - LD ST, Vx
|
|
const x = get_inst_x(inst);
|
|
self.ST = self.V[x];
|
|
|
|
} else if (inst & 0x00FF == 0x1E) { // Fx1E - ADD I, Vx
|
|
const x = get_inst_x(inst);
|
|
self.I += self.V[x];
|
|
|
|
} else if (inst & 0x00FF == 0x29) { // Fx29 - LD F, Vx
|
|
const x = get_inst_x(inst);
|
|
self.I = self.V[x] * 5;
|
|
|
|
} else if (inst & 0x00FF == 0x33) { // Fx33 - LD B, Vx
|
|
const x = get_inst_x(inst);
|
|
const Vx = self.V[x];
|
|
self.memory[self.I+0] = @divFloor(Vx, 100);
|
|
self.memory[self.I+1] = @divFloor(Vx, 10) % 10;
|
|
self.memory[self.I+2] = Vx % 10;
|
|
|
|
} else if (inst & 0x00FF == 0x55) { // Fx55 - LD [I], Vx
|
|
const x = get_inst_x(inst);
|
|
var i: u4 = 0;
|
|
while (i <= x) : (i += 1) {
|
|
self.memory[self.I + i] = self.V[i];
|
|
}
|
|
|
|
} else if (inst & 0x00FF == 0x65) { // Fx65 - LD Vx, [I]
|
|
const x = get_inst_x(inst);
|
|
var i: u4 = 0;
|
|
while (i <= x) : (i += 1) {
|
|
self.V[i] = self.memory[self.I + i];
|
|
}
|
|
} else {
|
|
return Errors.UnknownInstruction;
|
|
}
|
|
} else {
|
|
return Errors.UnknownInstruction;
|
|
}
|
|
}
|