Skip to content

Commit

Permalink
cleanup + make memory diffing conditionally compiled
Browse files Browse the repository at this point in the history
  • Loading branch information
190n committed May 12, 2024
1 parent ab0566f commit 6d4bcdd
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 44 deletions.
9 changes: 8 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn build(b: *std.Build) void {
const memory_size = b.option(
u13,
"memory_size",
"Size of the CHIP-8's memory. Higher than 4096 is unacceptable; lower than 4096 may break some programs.",
"Size of the CHIP-8's memory. Higher than 4096 is unacceptable; lower than 4096 may break some programs. Default 4096.",
);

const column_major_display = b.option(
Expand All @@ -35,9 +35,16 @@ pub fn build(b: *std.Build) void {
"Whether the display should use column-major layout instead of row-major. Default false.",
) orelse false;

const experimental_render_hooks = b.option(
bool,
"experimental_render_hooks",
"Enable functions for tracking draw operations and tracking whether memory used by a draw has been modified",
) orelse false;

const options = b.addOptions();
options.addOption(?u13, "memory_size", memory_size);
options.addOption(bool, "column_major_display", column_major_display);
options.addOption(bool, "experimental_render_hooks", experimental_render_hooks);
const options_module = options.createModule();

const native_library = b.addSharedLibrary(.{
Expand Down
50 changes: 30 additions & 20 deletions src/bindings.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const std = @import("std");

const Cpu = @import("./cpu.zig");
const build_options = @import("build_options");

fn cpuPtrCast(in: anytype) switch (@TypeOf(in)) {
?*anyopaque => *Cpu,
Expand All @@ -16,11 +17,11 @@ export const ZIP8_ERR_BAD_RETURN: u16 = @intFromError(error.BadReturn);
export const ZIP8_ERR_PROGRAM_TOO_LONG: u16 = @intFromError(error.ProgramTooLong);
export const ZIP8_ERR_FLAG_OVERFLOW: u16 = @intFromError(error.FlagOverflow);

export fn zip8GetErrorName(err: u16) callconv(.C) [*:0]const u8 {
export fn zip8GetErrorName(err: u16) [*:0]const u8 {
return @errorName(@errorFromInt(err));
}

export fn zip8CpuGetSize() callconv(.C) usize {
export fn zip8CpuGetSize() usize {
return @sizeOf(Cpu);
}

Expand All @@ -31,7 +32,7 @@ export fn zip8CpuInit(
program_len: usize,
seed: u64,
flags: u64,
) callconv(.C) c_int {
) c_int {
// directly storing in the pointer breaks this on RP2040 for whatever reason
// cpuPtrCast(cpu).* = Cpu.init(program.?[0..program_len], seed) catch |e| {
Cpu.initInPlace(
Expand All @@ -46,15 +47,15 @@ export fn zip8CpuInit(
return 0;
}

export fn zip8CpuCycle(err: ?*u16, cpu: ?*anyopaque) callconv(.C) c_int {
export fn zip8CpuCycle(err: ?*u16, cpu: ?*anyopaque) c_int {
cpuPtrCast(cpu).cycle() catch |e| {
err.?.* = @intFromError(e);
return 1;
};
return 0;
}

export fn zip8CpuSetKeys(cpu: ?*anyopaque, keys: u16) callconv(.C) void {
export fn zip8CpuSetKeys(cpu: ?*anyopaque, keys: u16) void {
var new_keys: [16]bool = undefined;
for (&new_keys, 0..) |*key, i_usize| {
const i: u4 = @intCast(i_usize);
Expand All @@ -63,56 +64,65 @@ export fn zip8CpuSetKeys(cpu: ?*anyopaque, keys: u16) callconv(.C) void {
cpuPtrCast(cpu).setKeys(&new_keys);
}

export fn zip8CpuIsWaitingForKey(cpu: ?*const anyopaque) callconv(.C) bool {
export fn zip8CpuIsWaitingForKey(cpu: ?*const anyopaque) bool {
return cpuPtrCast(cpu).next_key_register != null;
}

export fn zip8CpuTimerTick(cpu: ?*anyopaque) callconv(.C) void {
export fn zip8CpuTimerTick(cpu: ?*anyopaque) void {
cpuPtrCast(cpu).timerTick();
}

export fn zip8CpuDisplayIsDirty(cpu: ?*const anyopaque) callconv(.C) bool {
export fn zip8CpuDisplayIsDirty(cpu: ?*const anyopaque) bool {
return cpuPtrCast(cpu).display_dirty;
}

export fn zip8CpuSetDisplayNotDirty(cpu: ?*anyopaque) callconv(.C) void {
export fn zip8CpuSetDisplayNotDirty(cpu: ?*anyopaque) void {
cpuPtrCast(cpu).display_dirty = false;
}

export fn zip8CpuGetDisplay(cpu: ?*const anyopaque) callconv(.C) [*]const u8 {
export fn zip8CpuGetDisplay(cpu: ?*const anyopaque) [*]const u8 {
return &cpuPtrCast(cpu).display;
}

export fn zip8CpuGetInstruction(cpu: ?*const anyopaque) callconv(.C) u16 {
export fn zip8CpuGetInstruction(cpu: ?*const anyopaque) u16 {
const mem: []const u8 = &cpuPtrCast(cpu).mem;
const pc = cpuPtrCast(cpu).pc;
const high = mem[pc];
const low = mem[pc + 1];
return (@as(u16, high) << 8) + low;
}

export fn zip8CpuGetProgramCounter(cpu: ?*const anyopaque) callconv(.C) u16 {
export fn zip8CpuGetProgramCounter(cpu: ?*const anyopaque) u16 {
return cpuPtrCast(cpu).pc;
}

export fn zip8CpuGetFlags(cpu: ?*const anyopaque) callconv(.C) u64 {
export fn zip8CpuGetFlags(cpu: ?*const anyopaque) u64 {
return std.mem.nativeToLittle(u64, @as(u64, @bitCast(cpuPtrCast(cpu).flags)));
}

export fn zip8CpuFlagsAreDirty(cpu: ?*const anyopaque) callconv(.C) bool {
export fn zip8CpuFlagsAreDirty(cpu: ?*const anyopaque) bool {
return cpuPtrCast(cpu).flags_dirty;
}

export fn zip8CpuSetFlagsNotDirty(cpu: ?*anyopaque) callconv(.C) void {
export fn zip8CpuSetFlagsNotDirty(cpu: ?*anyopaque) void {
cpuPtrCast(cpu).flags_dirty = false;
}

export fn zip8CpuGetDrawBytes(cpu: ?*const anyopaque) callconv(.C) usize {
return cpuPtrCast(cpu).draw_bytes_this_frame;
}
comptime {
if (build_options.experimental_render_hooks) {
const render_hook_exports = struct {
fn zip8CpuGetDrawBytes(cpu: ?*const anyopaque) callconv(.C) usize {
return cpuPtrCast(cpu).draw_bytes_this_frame;
}

export fn zip8CpuResetDrawBytes(cpu: ?*anyopaque) callconv(.C) void {
cpuPtrCast(cpu).draw_bytes_this_frame = 0;
fn zip8CpuResetDrawBytes(cpu: ?*anyopaque) callconv(.C) void {
cpuPtrCast(cpu).draw_bytes_this_frame = 0;
}
};

@export(render_hook_exports.zip8CpuGetDrawBytes, .{ .name = "zip8CpuGetDrawBytes" });
@export(render_hook_exports.zip8CpuResetDrawBytes, .{ .name = "zip8CpuResetDrawBytes" });
}
}

comptime {
Expand Down
27 changes: 17 additions & 10 deletions src/cpu.zig
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,18 @@ stack: std.BoundedArray(u12, 16),
rand: std.rand.DefaultPrng,

/// 4 KiB of memory
mem: [memory_size]u8 = .{0x00} ** memory_size,
mem: [memory_size]u8,
/// track which memory has not been synchronized to clients
mem_dirty: [memory_size / 8]u8 = .{0xff} ** (memory_size / 8),
mem_dirty: if (build_options.experimental_render_hooks)
[memory_size / 8]u8
else
void =
if (build_options.experimental_render_hooks)
.{0xff} ** (memory_size / 8)
else {},

draw_bytes_this_frame: usize = 0,
draw_bytes_this_frame: if (build_options.experimental_render_hooks) usize else void =
if (build_options.experimental_render_hooks) 0 else {},

/// display is 64x32 stored row-major
display: [display_width * display_height / 8]u8 = .{0} ** (display_width * display_height / 8),
Expand All @@ -75,14 +82,16 @@ flags_dirty: bool = false,
pub fn initInPlace(cpu: *Cpu, program: []const u8, seed: u64, flags: [8]u8) error{ProgramTooLong}!void {
cpu.* = Cpu{
.rand = std.rand.DefaultPrng.init(seed),
.stack = std.BoundedArray(u12, stack_size).init(0) catch unreachable,
.stack = std.BoundedArray(u12, stack_size){},
.flags = flags,
.mem = undefined,
};
if (program.len > (memory_size - initial_pc)) {
return error.ProgramTooLong;
}
@memcpy(cpu.mem[font_base_address..].ptr, font_data);
@memcpy(cpu.mem[initial_pc..].ptr, program);
@memset(&cpu.mem, 0);
@memcpy((&cpu.mem)[font_base_address..].ptr, font_data);
@memcpy((&cpu.mem)[initial_pc..].ptr, program);
log.info("cpu init", .{});
}

Expand All @@ -94,9 +103,7 @@ pub fn init(program: []const u8, seed: u64, flags: [8]u8) error{ProgramTooLong}!
}

pub fn cycle(self: *Cpu) !void {
const hi: u8 = @as([*]u8, @ptrCast(&self.mem))[self.pc];
const lo: u8 = @as([*]u8, @ptrCast(&self.mem))[self.pc + 1];
const opcode: u16 = (@as(u16, hi) << 8) + lo;
const opcode = std.mem.readInt(u16, (&self.mem)[self.pc..][0..2], .big);
const inst = Instruction.decode(opcode);
log.debug("{any}", .{inst});
if (inst.exec(self) catch |e| {
Expand Down Expand Up @@ -164,7 +171,7 @@ pub fn getPixel(self: *const Cpu, x: u8, y: u8) u1 {
const byte_index = pixel_index / 8;
const bit_index: u3 = @truncate(pixel_index);

return @truncate(@as([*]const u8, @ptrCast(&self.display))[byte_index] >> bit_index);
return @truncate((&self.display)[byte_index] >> bit_index);
}

test "Cpu.init" {
Expand Down
31 changes: 20 additions & 11 deletions src/instruction.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const Instruction = @This();
const std = @import("std");

const Cpu = @import("./cpu.zig");
const build_options = @import("build_options");

/// 4-bit elements of opcode, most significant first
nibbles: [4]u4,
Expand Down Expand Up @@ -116,7 +117,9 @@ fn op00EX(self: Instruction, cpu: *Cpu) !?u12 {
@memset(&cpu.display, 0);
cpu.display_dirty = true;
draw_log.info("clear", .{});
cpu.draw_bytes_this_frame += 4;
if (build_options.experimental_render_hooks) {
cpu.draw_bytes_this_frame += 4;
}
return null;
},
0x00EE => {
Expand Down Expand Up @@ -670,19 +673,23 @@ fn opDraw(self: Instruction, cpu: *Cpu) !?u12 {
}

const mem_index = y_sprite + cpu.I;
if ((cpu.mem_dirty[mem_index / 8] >> @truncate(mem_index)) & 0x01 != 0) {
any_bytes_dirty = true;
cpu.mem_dirty[mem_index / 8] ^= (@as(u8, 1) << @truncate(mem_index));
if (build_options.experimental_render_hooks) {
if ((cpu.mem_dirty[mem_index / 8] >> @truncate(mem_index)) & 0x01 != 0) {
any_bytes_dirty = true;
cpu.mem_dirty[mem_index / 8] ^= (@as(u8, 1) << @truncate(mem_index));
}
}
}
}

if (any_bytes_dirty) {
draw_log.info("untaint: [{}, {})", .{ cpu.I, cpu.I + self.low4 });
cpu.draw_bytes_this_frame += self.low4;
if (build_options.experimental_render_hooks) {
if (any_bytes_dirty) {
draw_log.info("untaint: [{}, {})", .{ cpu.I, cpu.I + self.low4 });
cpu.draw_bytes_this_frame += self.low4;
}
draw_log.info("sprite: {} rows at ({}, {})", .{ sprite.len, x_start, y_start });
cpu.draw_bytes_this_frame += 4;
}
draw_log.info("sprite: {} rows at ({}, {})", .{ sprite.len, x_start, y_start });
cpu.draw_bytes_this_frame += 4;
return null;
}

Expand Down Expand Up @@ -947,7 +954,9 @@ fn opStore(self: Instruction, cpu: *Cpu) !?u12 {
for (0..(@as(u8, self.regX) + 1)) |offset| {
cpu.mem[cpu.I] = cpu.V[offset];
cpu.I +%= 1;
cpu.mem_dirty[cpu.I / 8] |= (@as(u8, 1) << @truncate(cpu.I));
if (build_options.experimental_render_hooks) {
cpu.mem_dirty[cpu.I / 8] |= (@as(u8, 1) << @truncate(cpu.I));
}
}
return null;
}
Expand Down Expand Up @@ -982,7 +991,7 @@ test "FX55 store registers" {
/// FX65: load values from memory starting at I into registers [V0, VX]; set I to I + X + 1
fn opLoad(self: Instruction, cpu: *Cpu) !?u12 {
for (0..(@as(u8, self.regX) + 1)) |offset| {
cpu.V[offset] = @as([*]const u8, @ptrCast(&cpu.mem))[cpu.I];
cpu.V[offset] = (&cpu.mem)[cpu.I];
cpu.I +%= 1;
}
return null;
Expand Down
4 changes: 2 additions & 2 deletions web-host/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ async function run(rom: ArrayBuffer) {
cpu.setKeys(keys);
}

cpu.resetDrawBytes();
// cpu.resetDrawBytes();

if (!cpu.isWaitingForKey()) {
for (let i = 0; i < instructionsPerTick && !halt; i++) {
Expand All @@ -119,7 +119,7 @@ async function run(rom: ArrayBuffer) {
}
}

console.log(cpu.getDrawBytes());
// console.log(cpu.getDrawBytes());
}

tick();
Expand Down

0 comments on commit 6d4bcdd

Please sign in to comment.