initial commit
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
/zig-out/
 | 
			
		||||
/.zig-cache/
 | 
			
		||||
/.idea/
 | 
			
		||||
							
								
								
									
										128
									
								
								build.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								build.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
// Although this function looks imperative, note that its job is to
 | 
			
		||||
// declaratively construct a build graph that will be executed by an external
 | 
			
		||||
// runner.
 | 
			
		||||
pub fn build(b: *std.Build) 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 optimization options allow the person running `zig build` to select
 | 
			
		||||
    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
 | 
			
		||||
    // set a preferred release mode, allowing the user to decide how to optimize.
 | 
			
		||||
    const optimize = b.standardOptimizeOption(.{});
 | 
			
		||||
 | 
			
		||||
    const raylib_dep = b.dependency("raylib_zig", .{
 | 
			
		||||
        .target = target,
 | 
			
		||||
        .optimize = optimize,
 | 
			
		||||
        .shared = true,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const raylib = raylib_dep.module("raylib");
 | 
			
		||||
    const raylib_artifact = raylib_dep.artifact("raylib");
 | 
			
		||||
 | 
			
		||||
    // This creates a "module", which represents a collection of source files alongside
 | 
			
		||||
    // some compilation options, such as optimization mode and linked system libraries.
 | 
			
		||||
    // Every executable or library we compile will be based on one or more modules.
 | 
			
		||||
    // const lib_mod = b.createModule(.{
 | 
			
		||||
    //     // `root_source_file` is the Zig "entry point" of the module. If a module
 | 
			
		||||
    //     // only contains e.g. external object files, you can make this `null`.
 | 
			
		||||
    //     // In this case the main source file is merely a path, however, in more
 | 
			
		||||
    //     // complicated build scripts, this could be a generated file.
 | 
			
		||||
    //     .root_source_file = b.path("src/root.zig"),
 | 
			
		||||
    //     .target = target,
 | 
			
		||||
    //     .optimize = optimize,
 | 
			
		||||
    // });
 | 
			
		||||
 | 
			
		||||
    // We will also create a module for our other entry point, 'main.zig'.
 | 
			
		||||
    const exe_mod = b.createModule(.{
 | 
			
		||||
        // `root_source_file` is the Zig "entry point" of the module. If a module
 | 
			
		||||
        // only contains e.g. external object files, you can make this `null`.
 | 
			
		||||
        // In this case the main source file is merely a path, however, in more
 | 
			
		||||
        // complicated build scripts, this could be a generated file.
 | 
			
		||||
        .root_source_file = b.path("src/main.zig"),
 | 
			
		||||
        .target = target,
 | 
			
		||||
        .optimize = optimize,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Modules can depend on one another using the `std.Build.Module.addImport` function.
 | 
			
		||||
    // This is what allows Zig source code to use `@import("foo")` where 'foo' is not a
 | 
			
		||||
    // file path. In this case, we set up `exe_mod` to import `lib_mod`.
 | 
			
		||||
    // exe_mod.addImport("chip8_emulator_lib", lib_mod);
 | 
			
		||||
 | 
			
		||||
    // Now, we will create a static library based on the module we created above.
 | 
			
		||||
    // This creates a `std.Build.Step.Compile`, which is the build step responsible
 | 
			
		||||
    // for actually invoking the compiler.
 | 
			
		||||
    // const lib = b.addLibrary(.{
 | 
			
		||||
    //     .linkage = .static,
 | 
			
		||||
    //     .name = "chip8_emulator",
 | 
			
		||||
    //     .root_module = lib_mod,
 | 
			
		||||
    // });
 | 
			
		||||
 | 
			
		||||
    // This declares intent for the library to be installed into the standard
 | 
			
		||||
    // location when the user invokes the "install" step (the default step when
 | 
			
		||||
    // running `zig build`).
 | 
			
		||||
    // b.installArtifact(lib);
 | 
			
		||||
 | 
			
		||||
    // This creates another `std.Build.Step.Compile`, but this one builds an executable
 | 
			
		||||
    // rather than a static library.
 | 
			
		||||
    const exe = b.addExecutable(.{
 | 
			
		||||
        .name = "chip8_emulator",
 | 
			
		||||
        .root_module = exe_mod,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // This declares intent for the executable to be installed into the
 | 
			
		||||
    // standard location when the user invokes the "install" step (the default
 | 
			
		||||
    // step when running `zig build`).
 | 
			
		||||
    b.installArtifact(exe);
 | 
			
		||||
 | 
			
		||||
    exe.linkLibrary(raylib_artifact);
 | 
			
		||||
    exe.root_module.addImport("raylib", raylib);
 | 
			
		||||
 | 
			
		||||
    // This *creates* a Run step in the build graph, to be executed when another
 | 
			
		||||
    // step is evaluated that depends on it. The next line below will establish
 | 
			
		||||
    // such a dependency.
 | 
			
		||||
    const run_cmd = b.addRunArtifact(exe);
 | 
			
		||||
 | 
			
		||||
    // By making the run step depend on the install step, it will be run from the
 | 
			
		||||
    // installation directory rather than directly from within the cache directory.
 | 
			
		||||
    // This is not necessary, however, if the application depends on other installed
 | 
			
		||||
    // files, this ensures they will be present and in the expected location.
 | 
			
		||||
    run_cmd.step.dependOn(b.getInstallStep());
 | 
			
		||||
 | 
			
		||||
    // This allows the user to pass arguments to the application in the build
 | 
			
		||||
    // command itself, like this: `zig build run -- arg1 arg2 etc`
 | 
			
		||||
    if (b.args) |args| {
 | 
			
		||||
        run_cmd.addArgs(args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // This creates a build step. It will be visible in the `zig build --help` menu,
 | 
			
		||||
    // and can be selected like this: `zig build run`
 | 
			
		||||
    // This will evaluate the `run` step rather than the default, which is "install".
 | 
			
		||||
    const run_step = b.step("run", "Run the app");
 | 
			
		||||
    run_step.dependOn(&run_cmd.step);
 | 
			
		||||
 | 
			
		||||
    // Creates a step for unit testing. This only builds the test executable
 | 
			
		||||
    // but does not run it.
 | 
			
		||||
    // const lib_unit_tests = b.addTest(.{
 | 
			
		||||
    //     .root_module = lib_mod,
 | 
			
		||||
    // });
 | 
			
		||||
 | 
			
		||||
    // const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
 | 
			
		||||
 | 
			
		||||
    const exe_unit_tests = b.addTest(.{
 | 
			
		||||
        .root_module = exe_mod,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
 | 
			
		||||
 | 
			
		||||
    // Similar to creating the run step earlier, this exposes a `test` step to
 | 
			
		||||
    // the `zig build --help` menu, providing a way for the user to request
 | 
			
		||||
    // running the unit tests.
 | 
			
		||||
    const test_step = b.step("test", "Run unit tests");
 | 
			
		||||
    // test_step.dependOn(&run_lib_unit_tests.step);
 | 
			
		||||
    test_step.dependOn(&run_exe_unit_tests.step);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								build.zig.zon
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								build.zig.zon
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
.{
 | 
			
		||||
    // This is the default name used by packages depending on this one. For
 | 
			
		||||
    // example, when a user runs `zig fetch --save <url>`, this field is used
 | 
			
		||||
    // as the key in the `dependencies` table. Although the user can choose a
 | 
			
		||||
    // different name, most users will stick with this provided value.
 | 
			
		||||
    //
 | 
			
		||||
    // It is redundant to include "zig" in this name because it is already
 | 
			
		||||
    // within the Zig package namespace.
 | 
			
		||||
    .name = .chip8_emulator,
 | 
			
		||||
 | 
			
		||||
    // This is a [Semantic Version](https://semver.org/).
 | 
			
		||||
    // In a future version of Zig it will be used for package deduplication.
 | 
			
		||||
    .version = "0.0.0",
 | 
			
		||||
 | 
			
		||||
    // Together with name, this represents a globally unique package
 | 
			
		||||
    // identifier. This field is generated by the Zig toolchain when the
 | 
			
		||||
    // package is first created, and then *never changes*. This allows
 | 
			
		||||
    // unambiguous detection of one package being an updated version of
 | 
			
		||||
    // another.
 | 
			
		||||
    //
 | 
			
		||||
    // When forking a Zig project, this id should be regenerated (delete the
 | 
			
		||||
    // field and run `zig build`) if the upstream project is still maintained.
 | 
			
		||||
    // Otherwise, the fork is *hostile*, attempting to take control over the
 | 
			
		||||
    // original project's identity. Thus it is recommended to leave the comment
 | 
			
		||||
    // on the following line intact, so that it shows up in code reviews that
 | 
			
		||||
    // modify the field.
 | 
			
		||||
    .fingerprint = 0x11aa205190765268, // Changing this has security and trust implications.
 | 
			
		||||
 | 
			
		||||
    // Tracks the earliest Zig version that the package considers to be a
 | 
			
		||||
    // supported use case.
 | 
			
		||||
    .minimum_zig_version = "0.14.0",
 | 
			
		||||
 | 
			
		||||
    // This field is optional.
 | 
			
		||||
    // Each dependency must either provide a `url` and `hash`, or a `path`.
 | 
			
		||||
    // `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
 | 
			
		||||
    // Once all dependencies are fetched, `zig build` no longer requires
 | 
			
		||||
    // internet connectivity.
 | 
			
		||||
    .dependencies = .{
 | 
			
		||||
        .raylib_zig = .{
 | 
			
		||||
            .url = "git+https://github.com/Not-Nik/raylib-zig?ref=devel#0de5f8aed0565f2dbd8bc6851499c85df9e73534",
 | 
			
		||||
            .hash = "raylib_zig-5.6.0-dev-KE8REGMrBQCs5X69dptNzjw9Z7MYM1fgdaKrnuKf8zyr",
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    .paths = .{
 | 
			
		||||
        "build.zig",
 | 
			
		||||
        "build.zig.zon",
 | 
			
		||||
        "src",
 | 
			
		||||
        // For example...
 | 
			
		||||
        //"LICENSE",
 | 
			
		||||
        //"README.md",
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										442
									
								
								src/main.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										442
									
								
								src/main.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,442 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const rl = @import("raylib");
 | 
			
		||||
 | 
			
		||||
const WIDTH = 64;
 | 
			
		||||
const HEIGHT = 32;
 | 
			
		||||
const SCALE = 15;
 | 
			
		||||
const FREQUENCY = 30;
 | 
			
		||||
 | 
			
		||||
const MEMORY_SIZE = 4096;
 | 
			
		||||
const FONT_START = 0x050;
 | 
			
		||||
const PROGRAM_START = 0x200;
 | 
			
		||||
const PROGRAM_ALLOC = MEMORY_SIZE - PROGRAM_START - 1;
 | 
			
		||||
 | 
			
		||||
const Emulator = struct {
 | 
			
		||||
    screen: [HEIGHT][WIDTH]u8 = clearScreen(),
 | 
			
		||||
    memory: [MEMORY_SIZE]u8 = std.mem.zeroes([MEMORY_SIZE]u8),
 | 
			
		||||
    stack: std.ArrayList(u16),
 | 
			
		||||
    v: [16]u8,
 | 
			
		||||
    i: u16,
 | 
			
		||||
    key_pressed: u5, // 4 bits for tracking the key pressed, 1 bit for tracking if the key IS pressed
 | 
			
		||||
    pc: u16,
 | 
			
		||||
    delay_timer: u8,
 | 
			
		||||
    sound_timer: u8,
 | 
			
		||||
    legacy: bool,
 | 
			
		||||
    legacy_memory: bool,
 | 
			
		||||
    add_index_exception: bool,
 | 
			
		||||
    prng: std.Random.Xoshiro256,
 | 
			
		||||
 | 
			
		||||
    pub fn init(allocator: std.mem.Allocator) !Emulator {
 | 
			
		||||
        return Emulator{ .screen = clearScreen(), .memory = try initMemory(allocator), .stack = std.ArrayList(u16).init(allocator), .v = std.mem.zeroes([16]u8), .i = 0, .key_pressed = 0, .pc = PROGRAM_START, .delay_timer = FREQUENCY, .sound_timer = 0, .legacy = true, .legacy_memory = false, .add_index_exception = false, .prng = std.Random.DefaultPrng.init(blk: {
 | 
			
		||||
            var seed: u64 = undefined;
 | 
			
		||||
            try std.posix.getrandom(std.mem.asBytes(&seed));
 | 
			
		||||
            break :blk seed;
 | 
			
		||||
        }) };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn destroy(self: *Emulator) void {
 | 
			
		||||
        self.stack.deinit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn instructionCycle(self: *Emulator) !void {
 | 
			
		||||
        const instruction: u16 = @as(u16, self.memory[self.pc]) << 8 | @as(u16, self.memory[self.pc + 1]);
 | 
			
		||||
        var bd: [10]u8 = undefined;
 | 
			
		||||
        _ = try std.fmt.bufPrint(&bd, "{X:0>4}", .{instruction});
 | 
			
		||||
        std.debug.print("Instruction: {s}\n\n", .{bd});
 | 
			
		||||
        const first: u8 = @truncate(instruction >> 12);
 | 
			
		||||
 | 
			
		||||
        const x: u8 = @truncate((instruction >> 8) & 0xF);
 | 
			
		||||
        const y: u8 = @truncate((instruction >> 4) & 0xF);
 | 
			
		||||
        const n: u8 = @truncate(instruction & 0xF);
 | 
			
		||||
        const nn: u8 = @truncate(instruction & 0xFF);
 | 
			
		||||
        const nnn: u16 = instruction & 0xFFF;
 | 
			
		||||
 | 
			
		||||
        switch (first) {
 | 
			
		||||
            0x0 => {
 | 
			
		||||
                if (instruction == 0x00E0) {
 | 
			
		||||
                    // clear screen
 | 
			
		||||
                    self.screen = clearScreen();
 | 
			
		||||
                } else if (instruction == 0x00EE) {
 | 
			
		||||
                    // pop the pc from stack, unless the binary is shit there should be no reason to unwrap optional
 | 
			
		||||
                    self.pc = self.stack.pop().?;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            0x1 => {
 | 
			
		||||
                // jump to specified address (if old address is identical to new one, return)
 | 
			
		||||
                if (self.pc == nnn) return;
 | 
			
		||||
                self.pc = nnn;
 | 
			
		||||
                return;
 | 
			
		||||
            },
 | 
			
		||||
            0x2 => {
 | 
			
		||||
                // pushes current pc onto stack and sets nnn as pc
 | 
			
		||||
                try self.stack.append(self.pc);
 | 
			
		||||
                self.pc = nnn;
 | 
			
		||||
            },
 | 
			
		||||
            0x3 => {
 | 
			
		||||
                // skips an instruction ahead if register x equals to nn
 | 
			
		||||
                if (self.v[x] == nn)
 | 
			
		||||
                    self.nextInstruction();
 | 
			
		||||
            },
 | 
			
		||||
            0x4 => {
 | 
			
		||||
                // skips an instruction ahead if register x does not equal to nn
 | 
			
		||||
                if (self.v[x] != nn)
 | 
			
		||||
                    self.nextInstruction();
 | 
			
		||||
            },
 | 
			
		||||
            0x5 => {
 | 
			
		||||
                // skips an instruction if register x equals to register y
 | 
			
		||||
                if (n == 0x0) { // we check only last 4 digits are zero
 | 
			
		||||
                    if (self.v[x] == self.v[y])
 | 
			
		||||
                        self.nextInstruction();
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            0x6 => {
 | 
			
		||||
                // sets register x to nn
 | 
			
		||||
                self.v[x] = nn;
 | 
			
		||||
            },
 | 
			
		||||
            0x7 => {
 | 
			
		||||
                // adds nn to register x, doesn't care about overflow
 | 
			
		||||
                const ov = @addWithOverflow(self.v[x], nn);
 | 
			
		||||
                self.v[x] = ov[0];
 | 
			
		||||
            },
 | 
			
		||||
            0x8 => {
 | 
			
		||||
                switch (n) {
 | 
			
		||||
                    0x0 => {
 | 
			
		||||
                        self.v[x] = self.v[y];
 | 
			
		||||
                    },
 | 
			
		||||
                    0x1 => {
 | 
			
		||||
                        self.v[x] |= self.v[y];
 | 
			
		||||
                    },
 | 
			
		||||
                    0x2 => {
 | 
			
		||||
                        self.v[x] &= self.v[y];
 | 
			
		||||
                    },
 | 
			
		||||
                    0x3 => {
 | 
			
		||||
                        self.v[x] ^= self.v[y];
 | 
			
		||||
                    },
 | 
			
		||||
                    0x4 => {
 | 
			
		||||
                        // adds and stores overflow bit in 0xF register
 | 
			
		||||
                        const ov = @addWithOverflow(self.v[x], self.v[y]);
 | 
			
		||||
                        self.v[0xF] = ov[1];
 | 
			
		||||
                        self.v[x] = ov[0];
 | 
			
		||||
                    },
 | 
			
		||||
                    0x5 => {
 | 
			
		||||
                        // subtracts and inverts the "usual" overflow
 | 
			
		||||
                        const ov = @subWithOverflow(self.v[x], self.v[y]);
 | 
			
		||||
                        self.v[x] = ov[0];
 | 
			
		||||
                        self.v[0xF] = ov[1] ^ 1;
 | 
			
		||||
                    },
 | 
			
		||||
                    0x6 => {
 | 
			
		||||
                        // shifting v[x] right and saving the dropped bit into V[0xF]
 | 
			
		||||
                        // if (self.legacy) {
 | 
			
		||||
                        //     self.v[x] = self.v[y];
 | 
			
		||||
                        // }
 | 
			
		||||
 | 
			
		||||
                        self.v[0xF] = self.v[x] & 0x1;
 | 
			
		||||
                        self.v[x] >>= 1;
 | 
			
		||||
                    },
 | 
			
		||||
                    0x7 => {
 | 
			
		||||
                        // subtracts and inverts the "usual" overflow
 | 
			
		||||
                        const ov = @subWithOverflow(self.v[y], self.v[x]);
 | 
			
		||||
                        self.v[x] = ov[0];
 | 
			
		||||
                        self.v[0xF] = ov[1] ^ 1;
 | 
			
		||||
                    },
 | 
			
		||||
                    0xE => {
 | 
			
		||||
                        // shifting v[x] left and saving the dropped bit into V[0xF]
 | 
			
		||||
                        // if (self.legacy) {
 | 
			
		||||
                        //     self.v[x] = self.v[y];
 | 
			
		||||
                        // }
 | 
			
		||||
 | 
			
		||||
                        self.v[0xF] = (self.v[x] & 0x80) >> 7;
 | 
			
		||||
                        self.v[x] <<= 1;
 | 
			
		||||
                    },
 | 
			
		||||
                    else => {},
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            0x9 => {
 | 
			
		||||
                // skips an instruction if register x equals to register y
 | 
			
		||||
                if (n == 0x0) { // we check only last 4 digits are zero
 | 
			
		||||
                    if (self.v[x] != self.v[y])
 | 
			
		||||
                        self.nextInstruction();
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            0xA => {
 | 
			
		||||
                // sets index register to nnn
 | 
			
		||||
                self.i = nnn;
 | 
			
		||||
            },
 | 
			
		||||
            0xB => {
 | 
			
		||||
                // jumps to different address depending on interpreter design, legacy is default
 | 
			
		||||
                const addr: u16 = if (self.legacy) 0x0 else x;
 | 
			
		||||
                self.pc = nnn + addr;
 | 
			
		||||
 | 
			
		||||
                return; // we do not want any further jumps
 | 
			
		||||
            },
 | 
			
		||||
            0xC => {
 | 
			
		||||
                // gen random number AND with NN => v[x]
 | 
			
		||||
                const rand = self.prng.random();
 | 
			
		||||
                self.v[x] = rand.int(u8) & nn;
 | 
			
		||||
            },
 | 
			
		||||
            0xD => {
 | 
			
		||||
                // draws the screen matrix
 | 
			
		||||
                var x_coord = self.v[x] % WIDTH;
 | 
			
		||||
                var y_coord = self.v[y] % HEIGHT;
 | 
			
		||||
 | 
			
		||||
                self.v[0xF] = 0;
 | 
			
		||||
 | 
			
		||||
                for (0..n) |it| {
 | 
			
		||||
                    const byte = self.memory[self.i + it];
 | 
			
		||||
 | 
			
		||||
                    var b: i4 = 7;
 | 
			
		||||
                    while (b >= 0) : (b -= 1) {
 | 
			
		||||
                        const bit_pos: u3 = @intCast(b); // loop condition ensures the number never reaches negative
 | 
			
		||||
                        const bit = (byte & (@as(u8, 1) << bit_pos));
 | 
			
		||||
                        if (bit != 0) {
 | 
			
		||||
                            const old_pixel = self.screen[y_coord][x_coord];
 | 
			
		||||
                            self.screen[y_coord][x_coord] ^= 1;
 | 
			
		||||
                            if (old_pixel != 0 and self.screen[y_coord][x_coord] == 0)
 | 
			
		||||
                                self.v[0xF] = 1;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        x_coord += 1;
 | 
			
		||||
                        if (x_coord >= WIDTH) break;
 | 
			
		||||
                    }
 | 
			
		||||
                    x_coord = self.v[x] % WIDTH;
 | 
			
		||||
                    y_coord += 1;
 | 
			
		||||
                    if (y_coord >= HEIGHT) break;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            0xE => {
 | 
			
		||||
                // if key is pressed (5th bit)
 | 
			
		||||
                if (self.key_pressed & 0x10 != 0) {
 | 
			
		||||
                    const key: u4 = @truncate(self.key_pressed & 0xF);
 | 
			
		||||
                    // we check for instruction and if the v[x] matches last 4 bits (1xxxx)
 | 
			
		||||
                    if (nn == 0x9E and self.v[x] == key) {
 | 
			
		||||
                        self.nextInstruction();
 | 
			
		||||
                    } else if (nn == 0xA1 and self.v[x] != key) {
 | 
			
		||||
                        self.nextInstruction();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            0xF => {
 | 
			
		||||
                switch (nn) {
 | 
			
		||||
                    // set delay
 | 
			
		||||
                    0x07 => self.v[x] = self.delay_timer,
 | 
			
		||||
                    0x15 => {
 | 
			
		||||
                        // set delay, adjust fps
 | 
			
		||||
                        self.delay_timer = self.v[x];
 | 
			
		||||
                        rl.setTargetFPS(self.delay_timer);
 | 
			
		||||
                    },
 | 
			
		||||
                    // set sound timer
 | 
			
		||||
                    0x18 => self.sound_timer = self.v[x],
 | 
			
		||||
                    0x1E => {
 | 
			
		||||
                        // add v[x] to index register
 | 
			
		||||
                        const ov = @addWithOverflow(self.i, self.v[x]);
 | 
			
		||||
                        self.i = ov[0];
 | 
			
		||||
                        if (self.add_index_exception)
 | 
			
		||||
                            self.v[0xF] = ov[1];
 | 
			
		||||
                    },
 | 
			
		||||
                    0x0A => {
 | 
			
		||||
                        // this instructions blocks the flow of the program until a key is pressed
 | 
			
		||||
                        if (self.key_pressed & 0x10 == 0) return;
 | 
			
		||||
                        self.v[x] = self.key_pressed & 0xF;
 | 
			
		||||
                    },
 | 
			
		||||
                    0x29 => {
 | 
			
		||||
                        // point to the starting font location of hexadecimal character
 | 
			
		||||
                        self.i = FONT_START + 5 * self.v[x];
 | 
			
		||||
                    },
 | 
			
		||||
                    0x33 => {
 | 
			
		||||
                        // split instruction into digits and save them at apppropriate address
 | 
			
		||||
                        var num = self.v[x];
 | 
			
		||||
 | 
			
		||||
                        var it: i3 = 2;
 | 
			
		||||
                        while (it >= 0) : (it -= 1) {
 | 
			
		||||
                            self.memory[self.i + @as(u16, @intCast(it))] = num % 10;
 | 
			
		||||
                            num /= 10;
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    0x55 => {
 | 
			
		||||
                        // storing registers into memory
 | 
			
		||||
                        var it: usize = 0;
 | 
			
		||||
                        while (it <= x) : (it += 1) {
 | 
			
		||||
                            self.memory[self.i + it] = self.v[it];
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (self.legacy_memory)
 | 
			
		||||
                            self.i = @intCast(it);
 | 
			
		||||
                    },
 | 
			
		||||
                    0x65 => {
 | 
			
		||||
                        // loading registers from the memory
 | 
			
		||||
                        var it: usize = 0;
 | 
			
		||||
                        while (it <= x) : (it += 1) {
 | 
			
		||||
                            self.v[it] = self.memory[self.i + it];
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (self.legacy_memory)
 | 
			
		||||
                            self.i = @intCast(it);
 | 
			
		||||
                    },
 | 
			
		||||
                    else => {},
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            else => {},
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.nextInstruction();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn nextInstruction(self: *Emulator) void {
 | 
			
		||||
        self.pc += 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn getInput(self: *Emulator) void {
 | 
			
		||||
        var key: u4 = 0x0;
 | 
			
		||||
        switch (rl.getKeyPressed()) {
 | 
			
		||||
            .one => key = 0x1,
 | 
			
		||||
            .two => key = 0x2,
 | 
			
		||||
            .three => key = 0x3,
 | 
			
		||||
            .four => key = 0xC,
 | 
			
		||||
 | 
			
		||||
            .q => key = 0x4,
 | 
			
		||||
            .w => key = 0x5,
 | 
			
		||||
            .e => key = 0x6,
 | 
			
		||||
            .r => key = 0xD,
 | 
			
		||||
 | 
			
		||||
            .a => key = 0x7,
 | 
			
		||||
            .s => key = 0x8,
 | 
			
		||||
            .d => key = 0x9,
 | 
			
		||||
            .f => key = 0xE,
 | 
			
		||||
 | 
			
		||||
            .z => key = 0xA,
 | 
			
		||||
            .x => key = 0x0,
 | 
			
		||||
            .c => key = 0xB,
 | 
			
		||||
            .v => key = 0xF,
 | 
			
		||||
 | 
			
		||||
            else => {
 | 
			
		||||
                self.key_pressed = 0x0;
 | 
			
		||||
                return;
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.key_pressed = 0x10 | @as(u5, key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn initMemory(allocator: std.mem.Allocator) ![MEMORY_SIZE]u8 {
 | 
			
		||||
        var mem = std.mem.zeroes([MEMORY_SIZE]u8);
 | 
			
		||||
 | 
			
		||||
        const font_data = [_]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
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // load font data into the memory
 | 
			
		||||
        for (font_data, FONT_START..) |value, i| {
 | 
			
		||||
            mem[i] = value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // load program into the memory
 | 
			
		||||
        const instructions = try readFile(allocator);
 | 
			
		||||
 | 
			
		||||
        // load font data into the memory
 | 
			
		||||
        for (instructions, PROGRAM_START..) |value, i| {
 | 
			
		||||
            mem[i] = value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return mem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn draw(self: *Emulator) void {
 | 
			
		||||
        rl.beginDrawing();
 | 
			
		||||
        defer rl.endDrawing();
 | 
			
		||||
 | 
			
		||||
        rl.clearBackground(.black);
 | 
			
		||||
        for (0..self.screen.len) |y| {
 | 
			
		||||
            for (0..self.screen[y].len) |x| {
 | 
			
		||||
                if (self.screen[y][x] == 0) continue;
 | 
			
		||||
 | 
			
		||||
                rl.drawRectangle(@as(i32, @intCast(x)) * SCALE, @as(i32, @intCast(y)) * SCALE, SCALE, SCALE, .white);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn print_instructions(self: *Emulator) void {
 | 
			
		||||
        var i: usize = PROGRAM_START;
 | 
			
		||||
        while (i < self.memory.len) : (i += 2) {
 | 
			
		||||
            if (i != 0 and i % 8 == 0) {
 | 
			
		||||
                std.debug.print("\n", .{});
 | 
			
		||||
            }
 | 
			
		||||
            if (self.memory[i] == 0xAA and self.memory[i + 1] == 0xAA) break;
 | 
			
		||||
            std.debug.print("{X:0>2}{X:0>2} ", .{ self.memory[i], self.memory[i + 1] });
 | 
			
		||||
        }
 | 
			
		||||
        std.debug.print("\n", .{});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn clearScreen() [HEIGHT][WIDTH]u8 {
 | 
			
		||||
        return std.mem.zeroes([HEIGHT][WIDTH]u8);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn main() !void {
 | 
			
		||||
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
 | 
			
		||||
    defer arena.deinit();
 | 
			
		||||
    const allocator = arena.allocator();
 | 
			
		||||
 | 
			
		||||
    var chip8 = try Emulator.init(allocator);
 | 
			
		||||
    defer chip8.destroy();
 | 
			
		||||
 | 
			
		||||
    chip8.print_instructions();
 | 
			
		||||
 | 
			
		||||
    rl.initWindow(WIDTH * SCALE, HEIGHT * SCALE, "CHIP-8 Emulator");
 | 
			
		||||
    defer rl.closeWindow();
 | 
			
		||||
 | 
			
		||||
    rl.setTargetFPS(chip8.delay_timer);
 | 
			
		||||
    var cycle_count: usize = 0;
 | 
			
		||||
 | 
			
		||||
    while (!rl.windowShouldClose()) {
 | 
			
		||||
        cycle_count += 1;
 | 
			
		||||
        std.debug.print("Cycle count: {d}\n", .{cycle_count});
 | 
			
		||||
        chip8.getInput();
 | 
			
		||||
        try chip8.instructionCycle();
 | 
			
		||||
        chip8.draw();
 | 
			
		||||
        // todo sound timer decrease i guess
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn readFile(allocator: std.mem.Allocator) ![PROGRAM_ALLOC]u8 {
 | 
			
		||||
    const fs = std.fs;
 | 
			
		||||
    const filename = try getArgs(allocator);
 | 
			
		||||
    defer allocator.free(filename);
 | 
			
		||||
 | 
			
		||||
    var buffer: [PROGRAM_ALLOC]u8 = undefined;
 | 
			
		||||
 | 
			
		||||
    const file = try fs.cwd().openFile(filename, .{ .mode = .read_only });
 | 
			
		||||
    defer file.close();
 | 
			
		||||
 | 
			
		||||
    const bytes_read = try file.readAll(&buffer);
 | 
			
		||||
 | 
			
		||||
    if (PROGRAM_START + bytes_read > MEMORY_SIZE) {
 | 
			
		||||
        std.debug.print("[ERROR] Instruction set size exceeds memory size ({d})!", .{MEMORY_SIZE});
 | 
			
		||||
        return error.MemorySizeExceeded;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn getArgs(allocator: std.mem.Allocator) ![]const u8 {
 | 
			
		||||
    const args = try std.process.argsAlloc(allocator);
 | 
			
		||||
    defer std.process.argsFree(allocator, args);
 | 
			
		||||
 | 
			
		||||
    if (args.len < 2) return error.InsufficientArgs;
 | 
			
		||||
 | 
			
		||||
    return allocator.dupe(u8, args[1]);
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user