Initial Commit
This commit is contained in:
commit
4ef56bbcaf
|
@ -0,0 +1,8 @@
|
||||||
|
# Zig Build Directories
|
||||||
|
/zig-cache
|
||||||
|
/zig-out
|
||||||
|
|
||||||
|
|
||||||
|
# ROM directory
|
||||||
|
/bin
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
};
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue