Skip to content

Commit c037360

Browse files
committed
Custom bit array and work around ziglang/zig#16343
1 parent 602d10f commit c037360

File tree

3 files changed

+47
-27
lines changed

3 files changed

+47
-27
lines changed

src/bindings.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export fn zip8CpuSetDisplayNotDirty(cpu: ?*anyopaque) callconv(.C) void {
8080
}
8181

8282
export fn zip8CpuGetDisplay(cpu: ?*const anyopaque) callconv(.C) [*]const u8 {
83-
return &cpuPtrCast(cpu).display.bytes;
83+
return &cpuPtrCast(cpu).display;
8484
}
8585

8686
export fn zip8CpuGetInstruction(cpu: ?*const anyopaque) callconv(.C) u16 {

src/cpu.zig

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ const std = @import("std");
55
const Instruction = @import("./instruction.zig");
66
const font_data = @import("./font.zig").font_data;
77

8-
pub const memory_size = @import("build_options").memory_size orelse 4096;
8+
const build_options = @import("build_options");
9+
10+
pub const memory_size = build_options.memory_size orelse 4096;
911

1012
comptime {
1113
if (memory_size > 4096) {
@@ -43,10 +45,7 @@ stack: std.BoundedArray(u12, 16),
4345
rand: std.rand.DefaultPrng,
4446

4547
/// display is 64x32 stored row-major
46-
display: std.PackedIntArray(u1, 2048) = blk: {
47-
@setEvalBranchQuota(20000);
48-
break :blk std.PackedIntArray(u1, 2048).initAllTo(0);
49-
},
48+
display: [display_width * display_height / 8]u8 = .{0} ** (display_width * display_height / 8),
5049
/// whether the contents of the screen have changed since the last time this flag was set to false
5150
display_dirty: bool = false,
5251

@@ -89,7 +88,9 @@ pub fn init(program: []const u8, seed: u64, flags: [8]u8) error{ProgramTooLong}!
8988
}
9089

9190
pub fn cycle(self: *Cpu) !void {
92-
const opcode: u16 = (@as(u16, self.mem[self.pc]) << 8) | self.mem[self.pc + 1];
91+
const hi: u8 = @as([*]u8, @ptrCast(&self.mem))[self.pc];
92+
const lo: u8 = @as([*]u8, @ptrCast(&self.mem))[self.pc + 1];
93+
const opcode: u16 = (@as(u16, hi) << 8) + lo;
9394
const inst = Instruction.decode(opcode);
9495
log.debug("{any}", .{inst});
9596
if (inst.exec(self) catch |e| {
@@ -138,6 +139,22 @@ pub fn setKeys(self: *Cpu, new_keys: *const [16]bool) void {
138139
@memcpy(&self.keys, new_keys);
139140
}
140141

142+
pub fn invertPixel(self: *Cpu, x: u8, y: u8) void {
143+
const pixel_index = display_width * @as(u16, y) + @as(u16, x);
144+
const byte_index = pixel_index / 8;
145+
const bit_index: u3 = @truncate(pixel_index);
146+
147+
self.display[byte_index] ^= (@as(u8, 1) << bit_index);
148+
}
149+
150+
pub fn getPixel(self: *const Cpu, x: u8, y: u8) u1 {
151+
const pixel_index = display_width * @as(u16, y) + @as(u16, x);
152+
const byte_index = pixel_index / 8;
153+
const bit_index: u3 = @truncate(pixel_index);
154+
155+
return @truncate(@as([*]const u8, @ptrCast(&self.display))[byte_index] >> bit_index);
156+
}
157+
141158
test "Cpu.init" {
142159
const cpu = try Cpu.init("abc", 0, .{0} ** 8);
143160
try std.testing.expectEqualSlices(u8, &(.{0} ** 16), &cpu.V);
@@ -148,7 +165,7 @@ test "Cpu.init" {
148165
try std.testing.expectEqual(@as(usize, 0), cpu.stack.len);
149166

150167
for (0..cpu.display.len) |i| {
151-
try std.testing.expectEqual(@as(u1, 0), cpu.display.get(i));
168+
try std.testing.expectEqual(@as(u8, 0), cpu.display[i]);
152169
}
153170

154171
try std.testing.expectEqualSlices(bool, &(.{false} ** 16), &cpu.keys);

src/instruction.zig

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ fn opIllegalArithmetic(vx: u8, vy: u8) !ArithmeticOpcodeResult {
111111
fn op00EX(self: Instruction, cpu: *Cpu) !?u12 {
112112
switch (self.opcode) {
113113
0x00E0 => {
114-
cpu.display.setAll(0);
114+
@memset(&cpu.display, 0);
115115
cpu.display_dirty = true;
116116
return null;
117117
},
@@ -131,10 +131,10 @@ test "00E0 clear screen" {
131131
0x00, 0xE0,
132132
}, testing_seed, .{0} ** 8);
133133
// fill screen
134-
cpu.display.setAll(1);
134+
@memset(&cpu.display, 0xff);
135135
try cpu.cycle();
136-
for (0..cpu.display.len) |i| {
137-
try std.testing.expectEqual(@as(u1, 0), cpu.display.get(i));
136+
for (cpu.display) |byte| {
137+
try std.testing.expectEqual(@as(u8, 0), byte);
138138
}
139139
try std.testing.expect(cpu.display_dirty);
140140
}
@@ -639,6 +639,13 @@ test "CXNN random" {
639639
try std.testing.expectEqual(@as(u8, 0x00), cpu.V[0x0]);
640640
}
641641

642+
export fn draw(ptr: ?*anyopaque, xReg: u8, yReg: u8, rows: u8) callconv(.C) void {
643+
const cpu: *Cpu = @alignCast(@ptrCast(ptr.?));
644+
const inst = Instruction.decode(0xd000 | (@as(u16, xReg) << 8) | (yReg << 4) | rows);
645+
_ = inst.exec(cpu) catch unreachable;
646+
cpu.pc +%= 2;
647+
}
648+
642649
/// DXYN: draw an 8xN sprite from memory starting at I at (VX, VY); set VF to 1 if any pixel was
643650
/// turned off, 0 otherwise
644651
fn opDraw(self: Instruction, cpu: *Cpu) !?u12 {
@@ -648,22 +655,19 @@ fn opDraw(self: Instruction, cpu: *Cpu) !?u12 {
648655
const y_start = cpu.V[self.regY] % Cpu.display_height;
649656
for (sprite, 0..) |row, y_sprite| {
650657
for (0..8) |x_sprite| {
651-
const mask = @as(u8, 0b10000000) >> @as(u3, @intCast(x_sprite));
652-
const pixel = @intFromBool(row & mask != 0);
653-
const x = x_start + x_sprite;
654-
const y = y_start + y_sprite;
658+
const pixel: u1 = @truncate(row >> @intCast(7 - x_sprite));
659+
660+
const x: u8 = @intCast(x_start + x_sprite);
661+
const y: u8 = @intCast(y_start + y_sprite);
655662
if (x >= Cpu.display_width or y >= Cpu.display_height) {
656663
continue;
657664
}
658665

659-
const index = y * Cpu.display_width + x;
660-
if (pixel == 1 and cpu.display.get(index) == 1) {
661-
// pixel turned off
662-
cpu.V[0xF] = 1;
666+
if (pixel == 1) {
667+
if (cpu.getPixel(x, y) == 1) cpu.V[0xF] = 1;
668+
cpu.invertPixel(x, y);
669+
cpu.display_dirty = true;
663670
}
664-
// draw using XOR
665-
cpu.display.set(index, @intFromBool(pixel != cpu.display.get(index)));
666-
cpu.display_dirty = cpu.display_dirty or pixel == 1;
667671
}
668672
}
669673
return null;
@@ -696,8 +700,7 @@ test "DXYN draw" {
696700
for (sprites, 0..) |pixel, y| {
697701
for (0..8) |x| {
698702
const expected: u1 = @truncate(pixel >> @intCast(7 - x));
699-
const actual_index = Cpu.display_width * (y + 17) + x + 8;
700-
try std.testing.expectEqual(expected, cpu.display.get(actual_index));
703+
try std.testing.expectEqual(expected, cpu.getPixel(@intCast(x + 8), @intCast(y + 17)));
701704
}
702705
}
703706

@@ -708,7 +711,7 @@ test "DXYN draw" {
708711
try std.testing.expect(cpu.display_dirty);
709712
// all off
710713
for (0..cpu.display.len) |i| {
711-
try std.testing.expectEqual(@as(u1, 0), cpu.display.get(i));
714+
try std.testing.expectEqual(@as(u8, 0), cpu.display[i]);
712715
}
713716
}
714717

@@ -965,7 +968,7 @@ test "FX55 store registers" {
965968
/// FX65: load values from memory starting at I into registers [V0, VX]; set I to I + X + 1
966969
fn opLoad(self: Instruction, cpu: *Cpu) !?u12 {
967970
for (0..(@as(u8, self.regX) + 1)) |offset| {
968-
cpu.V[offset] = cpu.mem[cpu.I];
971+
cpu.V[offset] = @as([*]const u8, @ptrCast(&cpu.mem))[cpu.I];
969972
cpu.I +%= 1;
970973
}
971974
return null;

0 commit comments

Comments
 (0)