init: abstractions over chip8 stuff
This commit is contained in:
commit
40223a179d
18 changed files with 443 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
use flake
|
||||
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
### Nix ###
|
||||
# Ignore build outputs from performing a nix-build or `nix build` command
|
||||
result
|
||||
result-*
|
||||
|
||||
# Ignore automatically generated direnv output
|
||||
.direnv
|
||||
### End of Nix ###
|
||||
|
||||
### Zig ###
|
||||
.zig-cache/
|
||||
zig-out/
|
||||
*.o
|
||||
### End of Zig ###
|
||||
|
||||
36
build.zig
Normal file
36
build.zig
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const mod = b.createModule(.{
|
||||
.root_source_file = b.path("./src/octochip/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const raylib_dep = b.dependency("raylib_zig", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "octochip",
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("./src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{
|
||||
.{ .name = "octochip", .module = mod },
|
||||
.{ .name = "raylib", .module = raylib_dep.module("raylib") },
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
const run_step = b.step("run", "Run octochip");
|
||||
const run_exe = b.addRunArtifact(exe);
|
||||
run_step.dependOn(&run_exe.step);
|
||||
}
|
||||
17
build.zig.zon
Normal file
17
build.zig.zon
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
.{
|
||||
.name = .octochip,
|
||||
.version = "0.0.0",
|
||||
.fingerprint = 0xd60bf7ea9ec78472,
|
||||
.minimum_zig_version = "0.15.2",
|
||||
.dependencies = .{
|
||||
.raylib_zig = .{
|
||||
.url = "git+https://github.com/raylib-zig/raylib-zig#cd71c85d571027ac8033357f83b124ee051825b3",
|
||||
.hash = "raylib_zig-5.6.0-dev-KE8REENOBQC-m5nK7M2b5aKSIubJPbPLUYcRhT7aT3RN",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
},
|
||||
}
|
||||
77
flake.lock
generated
Normal file
77
flake.lock
generated
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1772408722,
|
||||
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"import-tree": {
|
||||
"locked": {
|
||||
"lastModified": 1772999353,
|
||||
"narHash": "sha256-dPb0WxUhFaz6wuR3B6ysqFJpsu8txKDPZvS47AT2XLI=",
|
||||
"owner": "vic",
|
||||
"repo": "import-tree",
|
||||
"rev": "545a4df146fce44d155573e47f5a777985acf912",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "vic",
|
||||
"repo": "import-tree",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1773122722,
|
||||
"narHash": "sha256-FIqHByVqxCprNjor1NqF80F2QQoiiyqanNNefdlvOg4=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "62dc67aa6a52b4364dd75994ec00b51fbf474e50",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1772328832,
|
||||
"narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"import-tree": "import-tree",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
11
flake.nix
Normal file
11
flake.nix
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
import-tree.url = "github:vic/import-tree";
|
||||
};
|
||||
|
||||
outputs = inputs:
|
||||
inputs.flake-parts.lib.mkFlake {inherit inputs;}
|
||||
(inputs.import-tree ./packages/nix);
|
||||
}
|
||||
19
packages/nix/shell.nix
Normal file
19
packages/nix/shell.nix
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{...}: {
|
||||
perSystem = {pkgs, ...}: {
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = [
|
||||
pkgs.zig
|
||||
|
||||
pkgs.libGLX
|
||||
pkgs.libx11
|
||||
pkgs.libxcursor
|
||||
pkgs.libxext
|
||||
pkgs.libxfixes
|
||||
pkgs.libxi
|
||||
pkgs.libxinerama
|
||||
pkgs.libxrandr
|
||||
pkgs.libxrender
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
3
packages/nix/systems.nix
Normal file
3
packages/nix/systems.nix
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{...}: {
|
||||
systems = ["x86_64-linux"];
|
||||
}
|
||||
7
src/main.zig
Normal file
7
src/main.zig
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const std = @import("std");
|
||||
const octo = @import("octochip");
|
||||
|
||||
pub fn main() !void {
|
||||
const ins = try octo.machine.Instruction.from_bytes(0xabcd);
|
||||
std.log.info("instruction: {f}", .{ins});
|
||||
}
|
||||
7
src/octochip/components/display.zig
Normal file
7
src/octochip/components/display.zig
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub const Display = struct {
|
||||
pixels: [32][64]bool = .{},
|
||||
|
||||
fn new() @This() {
|
||||
return .{};
|
||||
}
|
||||
};
|
||||
40
src/octochip/components/keyboard.zig
Normal file
40
src/octochip/components/keyboard.zig
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
pub const Keyboard = struct {
|
||||
// FEDCBA9876543210
|
||||
pressed: u16 = 0,
|
||||
|
||||
fn new() @This() {
|
||||
return .{};
|
||||
}
|
||||
|
||||
fn released(self: *const @This()) u16 {
|
||||
return ~self.pressed;
|
||||
}
|
||||
|
||||
fn mask(key: u4) u16 {
|
||||
return (@as(u16, 1) << key);
|
||||
}
|
||||
|
||||
fn press(self: *@This(), key: u4) void {
|
||||
self.pressed |= mask(key);
|
||||
}
|
||||
|
||||
fn release(self: *@This(), key: u4) void {
|
||||
self.pressed &= ~mask(key);
|
||||
}
|
||||
|
||||
fn press_or_release_if(self: *@This(), key: u4, cond: bool) void {
|
||||
if (cond) {
|
||||
self.press(key);
|
||||
} else {
|
||||
self.release(key);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_pressed(self: *const @This(), key: u4) bool {
|
||||
return (self.pressed & mask(key)) != 0;
|
||||
}
|
||||
|
||||
fn is_released(self: *const @This(), key: u4) bool {
|
||||
return !self.is_pressed(key);
|
||||
}
|
||||
};
|
||||
15
src/octochip/components/memory.zig
Normal file
15
src/octochip/components/memory.zig
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
pub const Memory = struct {
|
||||
memory: [4096]u8 = .{},
|
||||
|
||||
pub fn new() @This() {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn put(self: *@This(), addr: usize, value: u8) void {
|
||||
self.memory[addr] = value;
|
||||
}
|
||||
|
||||
pub fn peek(self: *const @This(), addr: usize) u8 {
|
||||
return self.memory[addr];
|
||||
}
|
||||
};
|
||||
20
src/octochip/components/registers.zig
Normal file
20
src/octochip/components/registers.zig
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
pub const Registers = struct {
|
||||
V: [16]u8 = .{},
|
||||
DT: u8 = 0,
|
||||
ST: u8 = 0,
|
||||
I: u16 = 0,
|
||||
PC: u16 = 0x200,
|
||||
SP: u8 = 0,
|
||||
|
||||
pub fn new() Registers {
|
||||
return Registers{};
|
||||
}
|
||||
|
||||
pub fn get(self: *const Registers, index: u4) u8 {
|
||||
return self.V[index];
|
||||
}
|
||||
|
||||
pub fn set(self: *Registers, index: u4, value: u8) void {
|
||||
self.V[index] = value;
|
||||
}
|
||||
};
|
||||
4
src/octochip/components/root.zig
Normal file
4
src/octochip/components/root.zig
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub const Memory = @import("memory.zig").Memory;
|
||||
pub const Registers = @import("registers.zig").Registers;
|
||||
pub const Keyboard = @import("keyboard.zig").Keyboard;
|
||||
pub const Display = @import("display.zig").Display;
|
||||
167
src/octochip/machine/instruction.zig
Normal file
167
src/octochip/machine/instruction.zig
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
const std = @import("std");
|
||||
|
||||
// NOTE: Some instructions have X and then XR
|
||||
// It means that X operates on values (xkk) and XR operates on registers Vx and Vy
|
||||
// An instance of this can be found with SE and SER
|
||||
|
||||
// NOTE: Packed structs for instructions.
|
||||
// Variants for E--- and F--- can simply use Xxkk with pattern matching
|
||||
const Xnnn = packed struct { nnn: u12, X: u4 };
|
||||
const Xxkk = packed struct { kk: u8, x: u4, X: u4 };
|
||||
const XxyY = packed struct { Y: u4, y: u4, x: u4, X: u4 };
|
||||
const Xxyn = packed struct { n: u4, y: u4, x: u4, X: u4 };
|
||||
|
||||
pub const Instruction = union(enum) {
|
||||
SYS: struct { location: u12 },
|
||||
CLS: void,
|
||||
RET: void,
|
||||
JP: struct { location: u12 },
|
||||
CALL: struct { subroutine: u12 },
|
||||
SE: struct { register: u4, value: u8 },
|
||||
SNE: struct { register: u4, value: u8 },
|
||||
SER: struct { first: u4, second: u4 },
|
||||
LD: struct { register: u4, value: u8 },
|
||||
ADD: struct { register: u4, value: u8 },
|
||||
LDR: struct { first: u4, second: u4 },
|
||||
OR: struct { first: u4, second: u4 },
|
||||
AND: struct { first: u4, second: u4 },
|
||||
XOR: struct { first: u4, second: u4 },
|
||||
ADDR: struct { first: u4, second: u4 },
|
||||
SUB: struct { first: u4, second: u4 },
|
||||
SHR: struct { register: u4 },
|
||||
SUBN: struct { first: u4, second: u4 },
|
||||
SHL: struct { register: u4 },
|
||||
SNER: struct { first: u4, second: u4 },
|
||||
LDI: struct { value: u12 },
|
||||
JPV0: struct { add: u12 },
|
||||
RND: struct { register: u4, value: u8 },
|
||||
DRW: struct { x_register: u4, y_register: u4, bytes: u4 },
|
||||
SKP: struct { key: u4 },
|
||||
SKNP: struct { key: u4 },
|
||||
LDDTIN: struct { register: u4 },
|
||||
LDK: struct { register: u4 },
|
||||
LDDT: struct { register: u4 },
|
||||
LDST: struct { register: u4 },
|
||||
ADDI: struct { register: u4 },
|
||||
LDF: struct { register: u4 },
|
||||
LDB: struct { register: u4 },
|
||||
LDRO: struct { until_register: u4 },
|
||||
LDRI: struct { until_register: u4 },
|
||||
|
||||
pub fn from_bytes(instruction: u16) !@This() {
|
||||
switch (instruction) {
|
||||
0x00E0 => return .CLS,
|
||||
0x00EE => return .RET,
|
||||
else => {},
|
||||
}
|
||||
|
||||
const xnnn: Xnnn = @bitCast(instruction);
|
||||
const xxkk: Xxkk = @bitCast(instruction);
|
||||
const xxyy: XxyY = @bitCast(instruction);
|
||||
const xxyn: Xxyn = @bitCast(instruction);
|
||||
|
||||
switch (xnnn.X) {
|
||||
0x0 => return .{ .SYS = .{ .location = xnnn.nnn } },
|
||||
0x1 => return .{ .JP = .{ .location = xnnn.nnn } },
|
||||
0x2 => return .{ .CALL = .{ .subroutine = xnnn.nnn } },
|
||||
0xA => return .{ .LDI = .{ .value = xnnn.nnn } },
|
||||
0xB => return .{ .JPV0 = .{ .add = xnnn.nnn } },
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (xxkk.X) {
|
||||
0x3 => return .{ .SE = .{ .register = xxkk.x, .value = xxkk.kk } },
|
||||
0x4 => return .{ .SNE = .{ .register = xxkk.x, .value = xxkk.kk } },
|
||||
0x6 => return .{ .LD = .{ .register = xxkk.x, .value = xxkk.kk } },
|
||||
0x7 => return .{ .ADD = .{ .register = xxkk.x, .value = xxkk.kk } },
|
||||
0xC => return .{ .RND = .{ .register = xxkk.x, .value = xxkk.kk } },
|
||||
0xE => switch (xxkk.kk) {
|
||||
0x9E => return .{ .SKP = .{ .key = xxkk.x } },
|
||||
0xA1 => return .{ .SKNP = .{ .key = xxkk.x } },
|
||||
else => return error.InvalidInstruction,
|
||||
},
|
||||
0xF => switch (xxkk.kk) {
|
||||
0x07 => return .{ .LDDTIN = .{ .register = xxkk.x } },
|
||||
0x0A => return .{ .LDK = .{ .register = xxkk.x } },
|
||||
0x15 => return .{ .LDDT = .{ .register = xxkk.x } },
|
||||
0x18 => return .{ .LDST = .{ .register = xxkk.x } },
|
||||
0x1E => return .{ .ADDI = .{ .register = xxkk.x } },
|
||||
0x29 => return .{ .LDF = .{ .register = xxkk.x } },
|
||||
0x33 => return .{ .LDB = .{ .register = xxkk.x } },
|
||||
0x55 => return .{ .LDRO = .{ .until_register = xxkk.x } },
|
||||
0x65 => return .{ .LDRI = .{ .until_register = xxkk.x } },
|
||||
else => return error.InvalidInstruction,
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (xxyy.X) {
|
||||
0x5 => return .{ .SER = .{ .first = xxyy.x, .second = xxyy.y } },
|
||||
0x8 => switch (xxyy.Y) {
|
||||
0x0 => return .{ .LDR = .{ .first = xxyy.x, .second = xxyy.y } },
|
||||
0x1 => return .{ .OR = .{ .first = xxyy.x, .second = xxyy.y } },
|
||||
0x2 => return .{ .AND = .{ .first = xxyy.x, .second = xxyy.y } },
|
||||
0x3 => return .{ .XOR = .{ .first = xxyy.x, .second = xxyy.y } },
|
||||
0x4 => return .{ .ADDR = .{ .first = xxyy.x, .second = xxyy.y } },
|
||||
0x5 => return .{ .SUB = .{ .first = xxyy.x, .second = xxyy.y } },
|
||||
0x6 => return .{ .SHR = .{ .register = xxyy.x } },
|
||||
0x7 => return .{ .SUBN = .{ .first = xxyy.x, .second = xxyy.y } },
|
||||
0xE => return .{ .SHL = .{ .register = xxyy.x } },
|
||||
else => return error.InvalidInstruction,
|
||||
},
|
||||
0x9 => return .{ .SNER = .{ .first = xxyy.x, .second = xxyy.y } },
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (xxyn.X) {
|
||||
0xD => return .{ .DRW = .{ .x_register = xxyn.x, .y_register = xxyn.y, .bytes = xxyn.n } },
|
||||
else => {},
|
||||
}
|
||||
|
||||
return error.InvalidInstruction;
|
||||
}
|
||||
|
||||
pub fn format(
|
||||
self: @This(),
|
||||
writer: anytype,
|
||||
) !void {
|
||||
switch (self) {
|
||||
.CLS => try writer.writeAll("CLS"),
|
||||
.RET => try writer.writeAll("RET"),
|
||||
.SYS => |v| try writer.print("SYS {x}", .{v.location}),
|
||||
.JP => |v| try writer.print("JP {x}", .{v.location}),
|
||||
.CALL => |v| try writer.print("CALL {x}", .{v.subroutine}),
|
||||
.SE => |v| try writer.print("SE V{X}, {x}", .{ v.register, v.value }),
|
||||
.SNE => |v| try writer.print("SNE V{X}, {x}", .{ v.register, v.value }),
|
||||
.SER => |v| try writer.print("SE V{X}, V{X}", .{ v.first, v.second }),
|
||||
.LD => |v| try writer.print("LD V{X}, {x}", .{ v.register, v.value }),
|
||||
.ADD => |v| try writer.print("ADD V{X}, {x}", .{ v.register, v.value }),
|
||||
.LDR => |v| try writer.print("LD V{X}, V{X}", .{ v.first, v.second }),
|
||||
.OR => |v| try writer.print("OR V{X}, V{X}", .{ v.first, v.second }),
|
||||
.AND => |v| try writer.print("AND V{X}, V{X}", .{ v.first, v.second }),
|
||||
.XOR => |v| try writer.print("XOR V{X}, V{X}", .{ v.first, v.second }),
|
||||
.ADDR => |v| try writer.print("ADD V{X}, V{X}", .{ v.first, v.second }),
|
||||
.SUB => |v| try writer.print("SUB V{X}, V{X}", .{ v.first, v.second }),
|
||||
.SHR => |v| try writer.print("SHR V{X}", .{v.register}),
|
||||
.SUBN => |v| try writer.print("SUBN V{X}, V{X}", .{ v.first, v.second }),
|
||||
.SHL => |v| try writer.print("SHL V{X}", .{v.register}),
|
||||
.SNER => |v| try writer.print("SNE V{X}, V{X}", .{ v.first, v.second }),
|
||||
.LDI => |v| try writer.print("LD I, {x}", .{v.value}),
|
||||
.JPV0 => |v| try writer.print("JP V0, {x}", .{v.add}),
|
||||
.RND => |v| try writer.print("RND V{X}, {x}", .{ v.register, v.value }),
|
||||
.DRW => |v| try writer.print("DRW V{X}, V{X}, {}", .{ v.x_register, v.y_register, v.bytes }),
|
||||
.SKP => |v| try writer.print("SKP V{X}", .{v.key}),
|
||||
.SKNP => |v| try writer.print("SKNP V{X}", .{v.key}),
|
||||
.LDDTIN => |v| try writer.print("LD V{X}, DT", .{v.register}),
|
||||
.LDK => |v| try writer.print("LD V{X}, K", .{v.register}),
|
||||
.LDDT => |v| try writer.print("LD DT, V{X}", .{v.register}),
|
||||
.LDST => |v| try writer.print("LD ST, V{X}", .{v.register}),
|
||||
.ADDI => |v| try writer.print("ADD I, V{X}", .{v.register}),
|
||||
.LDF => |v| try writer.print("LD F, V{X}", .{v.register}),
|
||||
.LDB => |v| try writer.print("LD B, V{X}", .{v.register}),
|
||||
.LDRO => |v| try writer.print("LD [I], V0..V{X}", .{v.until_register}),
|
||||
.LDRI => |v| try writer.print("LD V0..V{X}, [I]", .{v.until_register}),
|
||||
}
|
||||
}
|
||||
};
|
||||
1
src/octochip/machine/machine.zig
Normal file
1
src/octochip/machine/machine.zig
Normal file
|
|
@ -0,0 +1 @@
|
|||
// the part that actually executes chip8
|
||||
1
src/octochip/machine/root.zig
Normal file
1
src/octochip/machine/root.zig
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub const Instruction = @import("instruction.zig").Instruction;
|
||||
2
src/octochip/root.zig
Normal file
2
src/octochip/root.zig
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub const components = @import("components/root.zig");
|
||||
pub const machine = @import("machine/root.zig");
|
||||
Loading…
Add table
Add a link
Reference in a new issue