From 4ef56bbcafa6b6d4e72931289e9e870b7f10daab Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Thu, 17 Jun 2021 00:45:23 -0500 Subject: [PATCH] Initial Commit --- .gitignore | 8 ++ build.zig | 29 +++++++ src/cpu.zig | 203 +++++++++++++++++++++++++++++++++++++++++++ src/display.zig | 55 ++++++++++++ src/instructions.zig | 12 +++ src/main.zig | 54 ++++++++++++ 6 files changed, 361 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 src/cpu.zig create mode 100644 src/display.zig create mode 100644 src/instructions.zig create mode 100644 src/main.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10b7310 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Zig Build Directories +/zig-cache +/zig-out + + +# ROM directory +/bin + diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..d1c0a58 --- /dev/null +++ b/build.zig @@ -0,0 +1,29 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("zig8", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + exe.linkSystemLibrary("SDL2"); + exe.linkSystemLibrary("c"); + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} diff --git a/src/cpu.zig b/src/cpu.zig new file mode 100644 index 0000000..6746ba3 --- /dev/null +++ b/src/cpu.zig @@ -0,0 +1,203 @@ +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.debug.print("CLS\n", .{}), + .RET => { + std.debug.print("RET\n", .{}); + + cpu.pc = cpu.stack[cpu.sp]; + cpu.sp -= 1; + }, + .JP => |addr| { + std.debug.print("JP 0x{X:}\n", .{ addr }); + + cpu.pc = addr; + }, + .CALL => |addr| { + std.debug.print("CALL 0x{X:}\n", .{ 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.debug.print("SE V{}, 0x{X:}\n", .{ x, kk }); + + if (cpu.v[x] != kk) { + cpu.pc += 2; + } + }, + .LD_6 => |args| { + const x = args.x; + const kk = args.kk; + std.debug.print("LD V{} 0x{X:}\n", .{ x, kk }); + + cpu.v[x] = kk; + }, + .LD_I => |addr| { + std.debug.print("LD I, 0x{X:}\n", .{ addr }); + cpu.i = addr; + }, + .DRW => |args| { + const x = args.x; + const y = args.y; + const n = args.n; + std.debug.print("DRW V{}, V{}, 0x{X:}\n", .{ 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.debug.print("ADD V{}, 0x{X:}\n", .{ 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 alloc = 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(alloc, file_size); + + load_rom(cpu, rom_buf); +} + +pub fn cycle(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); +} diff --git a/src/display.zig b/src/display.zig new file mode 100644 index 0000000..389ffc2 --- /dev/null +++ b/src/display.zig @@ -0,0 +1,55 @@ +const DISPLAY_WIDTH: usize = 64; +const DISPLAY_HEIGHT: usize = 32; +const std = @import("std"); + +pub const Point = struct { + x: u16, + y: u16, +}; + + +pub const Display = struct { + buf: [DISPLAY_HEIGHT * DISPLAY_WIDTH]u8, + + + pub fn new() Display { + return .{ + .buf = [_]u8{0x00} ** (DISPLAY_HEIGHT * DISPLAY_WIDTH) + }; + } +}; + +pub fn clear(disp: *Display) void { + disp.buf = u8[DISPLAY_WIDTH * DISPLAY_HEIGHT]{0x00}; +} + +pub fn draw_sprite(disp: *Display, pos: *const Point, data: *const []u8) bool { + var set_vf: bool = false; + + for (data.*) |byte, y_offset| { + var offset_count: u8 = 0; + + while (offset_count < 8) : (offset_count += 1) { + const x_bit_offset = @intCast(u3, offset_count); + const x = @intCast(u8, pos.x + (7 - x_bit_offset)); + const y = @intCast(u8, pos.y + y_offset); + + + const temp = (byte >> x_bit_offset) & 0x01; + const bit = @intCast(u1, temp); + + const i = DISPLAY_WIDTH * y + x; + + if (i >= disp.buf.len) break; + + if (bit == 0x1 and disp.buf[i] == 0x01) { + set_vf = true; + } + + disp.buf[i] ^= bit; + } + } + + + return set_vf; +} \ No newline at end of file diff --git a/src/instructions.zig b/src/instructions.zig new file mode 100644 index 0000000..f0fa619 --- /dev/null +++ b/src/instructions.zig @@ -0,0 +1,12 @@ + +pub const Instruction = union(enum) { + CLS: void, // 0x00E0 + RET: void, // 0x00EE + JP: u12, // 0x1nnn + CALL: u12, // 0x2nnn + SE_3: struct { x: u4, kk: u8 }, // 0x3xkk + LD_6: struct {x: u4, kk: u8}, // 0x6xkk + LD_I: u12, // 0xAnnn + DRW: struct { x: u4, y: u4, n: u4 }, // 0xDxyn + ADD_7: struct { x: u4, kk: u8 } // 0x7xkk +}; \ No newline at end of file diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..1d3da4a --- /dev/null +++ b/src/main.zig @@ -0,0 +1,54 @@ +const std = @import("std"); +const cpu = @import("cpu.zig"); +const Display = @import("display.zig").Display; +const c = @cImport(@cInclude("SDL.h")); + +const WIDTH = 64; +const HEIGHT = 32; + + +pub fn main() anyerror!void { + _ = c.SDL_Init(c.SDL_INIT_VIDEO); + defer c.SDL_Quit(); + + const window = c.SDL_CreateWindow("CHIP-8", c.SDL_WINDOWPOS_CENTERED, c.SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, c.SDL_WINDOW_OPENGL); + defer c.SDL_DestroyWindow(window); + + const renderer = c.SDL_CreateRenderer(window, -1, c.SDL_RENDERER_ACCELERATED); + defer c.SDL_DestroyRenderer(renderer); + + const texture = c.SDL_CreateTexture(renderer, c.SDL_PIXELFORMAT_RGBA8888, c.SDL_TEXTUREACCESS_STATIC, WIDTH, HEIGHT); + defer c.SDL_DestroyTexture(texture); + + + var zig8 = cpu.Cpu { + .i = 0x00, + .pc = 0x200, + .sp = 0x00, + .v = [_]u8{0x00} ** 16, + .stack = [_]u16{0x00} ** 16, + .memory = [_]u8{0x0000} ** 0x1000, + .disp = Display.new(), + }; + + try cpu.load_rom_path(&zig8, "./bin/IBM Logo.ch8"); + + const size = WIDTH * HEIGHT * @sizeOf(u32); + var pixels: [size]u8 = [_]u8 {0xFF} ** size; + + + emuloop: while(true) { + var event: c.SDL_Event = undefined; + + while(c.SDL_PollEvent(&event) != 0) { + switch(event.type) { + c.SDL_QUIT => break :emuloop, + else => {} + } + } + + var c_ptr = @ptrCast(*const c_void, &pixels); + + _ = c.SDL_UpdateTexture(texture, null, c_ptr, WIDTH * @sizeOf(u32)); + } +} \ No newline at end of file