Initial Commit
This commit is contained in:
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| /zig-cache | ||||
| /zig-out | ||||
| /bin | ||||
| /.vscode | ||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| [submodule "third_party/SDL.zig"] | ||||
| 	path = third_party/SDL.zig | ||||
| 	url = https://github.com/MasterQ32/SDL.zig | ||||
							
								
								
									
										7
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| { | ||||
|     "recommendations": [ | ||||
|         "augusterame.zls-vscode", | ||||
|         "tiehuis.zig", | ||||
|         "vadimcn.vscode-lldb" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										34
									
								
								build.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								build.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| const std = @import("std"); | ||||
| const Sdk = @import("third_party/SDL.zig/Sdk.zig"); | ||||
|  | ||||
| pub fn build(b: *std.build.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 sdk = Sdk.init(b); | ||||
|  | ||||
|     const exe = b.addExecutable("zig8", "src/main.zig"); | ||||
|     exe.setTarget(target); | ||||
|     exe.setBuildMode(mode); | ||||
|  | ||||
|     sdk.link(exe, .dynamic); | ||||
|     exe.addPackage(sdk.getNativePackage("sdl2")); | ||||
|  | ||||
|     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); | ||||
| } | ||||
							
								
								
									
										60
									
								
								src/chip8.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/chip8.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| const std = @import("std"); | ||||
| const cpu = @import("cpu.zig"); | ||||
| const Scheduler = @import("scheduler.zig").Scheduler; | ||||
| const Display = @import("display.zig").Display; | ||||
| const Allocator = std.mem.Allocator; | ||||
| const Cpu = cpu.Cpu; | ||||
|  | ||||
| const FONT_SET: [80]u8 = [_]u8{ | ||||
|     0xF0, 0x90, 0x90, 0x90, 0xF0, | ||||
|     0x20, 0x60, 0x20, 0x20, 0x70, | ||||
|     0xF0, 0x10, 0xF0, 0x80, 0xF0, | ||||
|     0xF0, 0x10, 0xF0, 0x10, 0xF0, | ||||
|     0x90, 0x90, 0xF0, 0x10, 0x10, | ||||
|     0xF0, 0x80, 0xF0, 0x10, 0xF0, | ||||
|     0xF0, 0x80, 0xF0, 0x90, 0xF0, | ||||
|     0xF0, 0x10, 0x20, 0x40, 0x40, | ||||
|     0xF0, 0x90, 0xF0, 0x90, 0xF0, | ||||
|     0xF0, 0x90, 0xF0, 0x10, 0xF0, | ||||
|     0xF0, 0x90, 0xF0, 0x90, 0x90, | ||||
|     0xE0, 0x90, 0xE0, 0x90, 0xE0, | ||||
|     0xF0, 0x80, 0x80, 0x80, 0xF0, | ||||
|     0xE0, 0x90, 0x90, 0x90, 0xE0, | ||||
|     0xF0, 0x80, 0xF0, 0x80, 0xF0, | ||||
|     0xF0, 0x80, 0xF0, 0x80, 0x80, | ||||
| }; | ||||
|  | ||||
| pub const Chip8 = struct { | ||||
|     cpu: Cpu, | ||||
|     mem: [0x1000]u8, | ||||
|     disp: Display, | ||||
|  | ||||
|     pub fn fromFile(alloc: *Allocator, scheduler: *Scheduler, path: []const u8) !Chip8 { | ||||
|         const file = try std.fs.cwd().openFile(path, .{ .read = true }); | ||||
|         defer file.close(); | ||||
|  | ||||
|         // Read file into allocated buffer | ||||
|         const size = try file.getEndPos(); | ||||
|         const rom_buf = try file.readToEndAlloc(alloc, size); | ||||
|         defer alloc.free(rom_buf); | ||||
|  | ||||
|         var chip8 = Chip8{ | ||||
|             .cpu = Cpu.new(scheduler), | ||||
|             .mem = [_]u8{0x00} ** 0x1000, | ||||
|             .disp = Display.new(), | ||||
|         }; | ||||
|  | ||||
|         std.mem.copy(u8, chip8.mem[0x0200..], rom_buf); // Copy ROM | ||||
|         std.mem.copy(u8, chip8.mem[0x50..0xA0], &FONT_SET); // Copy FONT_SET | ||||
|  | ||||
|         return chip8; | ||||
|     } | ||||
|  | ||||
|     pub fn step(self: *Chip8) u64 { | ||||
|         return cpu.step(&self.cpu, &self.disp, &self.mem); | ||||
|     } | ||||
|  | ||||
|     pub fn getDisplay(self: *Chip8) *const Display { | ||||
|         return &self.disp; | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										266
									
								
								src/cpu.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								src/cpu.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,266 @@ | ||||
| const std = @import("std"); | ||||
| const disp = @import("display.zig"); | ||||
| const util = @import("util.zig"); | ||||
| const sched = @import("scheduler.zig"); | ||||
| const Chip8 = @import("chip8.zig").Chip8; | ||||
| const Instruction = @import("instruction.zig").Instruction; | ||||
| const Point = disp.Point; | ||||
| const Display = disp.Display; | ||||
| const Scheduler = sched.Scheduler; | ||||
| const EventKind = sched.EventKind; | ||||
|  | ||||
| const OPS_PER_HZ = sched.OPS_PER_HZ; | ||||
| const panic = std.debug.panic; | ||||
| var prng = std.rand.DefaultPrng.init(1337); | ||||
|  | ||||
| pub const Cpu = struct { | ||||
|     i: u16, | ||||
|     pc: u16, | ||||
|     sp: u16, | ||||
|     v: [16]u8, | ||||
|     stack: [16]u16, | ||||
|     scheduler: *Scheduler, | ||||
|  | ||||
|     pub fn new(scheduler: *Scheduler) Cpu { | ||||
|         return .{ | ||||
|             .i = 0x0000, | ||||
|             .pc = 0x0200, | ||||
|             .sp = 0x0000, | ||||
|             .v = [_]u8{0x00} ** 16, | ||||
|             .stack = [_]u16{0x0000} ** 16, | ||||
|             .scheduler = scheduler, | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| pub fn step(cpu: *Cpu, display: *Display, mem: *[0x1000]u8) u64 { | ||||
|     const opcode = fetch(cpu, mem); | ||||
|  | ||||
|     // std.log.debug("0x{X:}", .{opcode}); | ||||
|     execute(cpu, display, mem, decode(opcode)); | ||||
|  | ||||
|     return 1; | ||||
| } | ||||
|  | ||||
| fn fetch(cpu: *Cpu, mem: *[0x1000]u8) u16 { | ||||
|     const high: u16 = mem[cpu.pc]; | ||||
|     const low: u16 = mem[cpu.pc + 1]; | ||||
|  | ||||
|     cpu.pc += 2; | ||||
|  | ||||
|     return high << 8 | low; | ||||
| } | ||||
|  | ||||
| fn decode(opcode: u16) Instruction { | ||||
|     const n1: u4 = @intCast(u4, (opcode & 0xF000) >> 12); | ||||
|     const n2: u4 = @intCast(u4, (opcode & 0x0F00) >> 8); | ||||
|     const n3: u4 = @intCast(u4, (opcode & 0x00F0) >> 4); | ||||
|     const n4: u4 = @intCast(u4, (opcode & 0x000F) >> 0); | ||||
|  | ||||
|     return switch (n1) { | ||||
|         0x0 => switch (n4) { | ||||
|             0x0 => return Instruction.CLS, // 0x00E0 | CLS | ||||
|             0xE => return Instruction.RET, // 0x00EE | RET | ||||
|             else => panic("0x{X:} is an unknown 0x0-prefixed opcode", .{opcode}), | ||||
|         }, | ||||
|         0x1 => Instruction{ .JP = util.calc12bit(n2, n3, n4) }, // 0x1nnn | JP u12 | ||||
|         0x2 => Instruction{ .CALL = util.calc12bit(n2, n3, n4) }, // 0x2nnn | CALL u12 | ||||
|         0x3 => Instruction{ .SE_Vx = .{ .x = n2, .kk = util.calc8bit(n3, n4) } }, // 0x3xkk | SE Vx, u8 | ||||
|         0x4 => Instruction{ .SNE_Vx = .{ .x = n2, .kk = util.calc8bit(n3, n4) } }, // 0x4xkk | SNE Vx, u8 | ||||
|         0x5 => Instruction{ .SE_Vx_Vy = .{ .x = n2, .y = n3 } }, // 0x5xy0 | SE Vx, Vy | ||||
|         0x6 => Instruction{ .LD_Vx = .{ .x = n2, .kk = util.calc8bit(n3, n4) } }, // 0x6xkk | LD Vx, u8 | ||||
|         0x7 => Instruction{ .ADD_Vx = .{ .x = n2, .kk = util.calc8bit(n3, n4) } }, // 0x7xkk | ADD Vx, u8 | ||||
|         0x8 => switch (n4) { | ||||
|             0x0 => return Instruction{ .LD_Vx_Vy = .{ .x = n2, .y = n3 } }, // 0x8xy0 | LD Vx, Vy | ||||
|             0x1 => return Instruction{ .OR = .{ .x = n2, .y = n3 } }, // 0x8xy1 | OR Vx, Vy | ||||
|             0x2 => return Instruction{ .AND = .{ .x = n2, .y = n3 } }, // 0x8xy2 | AND Vx, Vy | ||||
|             0x3 => return Instruction{ .XOR = .{ .x = n2, .y = n3 } }, // 0x8xy3 | XOR Vx, Vy | ||||
|             0x4 => return Instruction{ .ADD_Vx_Vy = .{ .x = n2, .y = n3 } }, // 0x8xy4 | ADD Vx, Vy | ||||
|             0x5 => return Instruction{ .SUB = .{ .x = n2, .y = n3 } }, // 0x8xy5 | SUB Vx, Vy | ||||
|             0x6 => return Instruction{ .SHR = .{ .x = n2, .y = n3 } }, // 0x8xy6 | SHR Vx, Vy | ||||
|             0x7 => return Instruction{ .SUBN = .{ .x = n2, .y = n3 } }, // 0x8xy7 | SUBN Vx, Vy | ||||
|             0xE => return Instruction{ .SHL = .{ .x = n2, .y = n3 } }, // 0x8xyE | SHL Vx, Vy | ||||
|             else => panic("0x{X:} is an unknown 0x8-prefixed opcode", .{opcode}), | ||||
|         }, | ||||
|         0x9 => Instruction{ .SNE_Vx_Vy = .{ .x = n2, .y = n3 } }, // 0x9xy0 | SNE Vx, Vy | ||||
|         0xA => Instruction{ .LD_I = util.calc12bit(n2, n3, n4) }, // 0xAnnn | LD I, u12 | ||||
|         0xB => Instruction{ .JP_V0 = util.calc12bit(n2, n3, n4) }, // 0xBnnn | JP V0, u12 | ||||
|         0xC => Instruction{ .RND = .{ .x = n2, .kk = util.calc8bit(n3, n4) } }, // 0xCxkk | RND Vx, u8 | ||||
|         0xD => Instruction{ .DRW = .{ .x = n2, .y = n3, .n = n4 } }, // 0xDxyn | DRW Vx, Vy, u4 | ||||
|         0xE => switch (n3) { | ||||
|             0x9 => return Instruction{ .SKP = n2 }, // 0xEx9E | SKP Vx | ||||
|             0xA => return Instruction{ .SKNP = n2 }, // 0xExA1 | SKNP Vx | ||||
|             else => panic("0x{X:} is an unknown 0xE-prefixed opcode", .{opcode}), | ||||
|         }, | ||||
|         0xF => switch (n3) { | ||||
|             0x0 => switch (n4) { | ||||
|                 0x7 => return Instruction{ .LD_Vx_DT = n2 }, // 0xFx07 | LD Vx, DT | ||||
|                 0xA => return Instruction{ .LD_Vx_K = n2 }, // 0xFx0A | LD Vx, K | ||||
|                 else => panic("0x{X:} is an unknown 0xFx0-prefixed opcode", .{opcode}), | ||||
|             }, | ||||
|             0x1 => switch (n4) { | ||||
|                 0x5 => return Instruction{ .LD_DT_Vx = n2 }, // 0xFx15 | LD DT, Vx | ||||
|                 0x8 => return Instruction{ .LD_ST_Vx = n2 }, // 0xFx18 | LD ST, Vx | ||||
|                 0xE => return Instruction{ .ADD_I_Vx = n2 }, // 0xFx1E | ADD I, Vx | ||||
|                 else => panic("0x{X:} is an unknown 0xFx1-prefixed opcode", .{opcode}), | ||||
|             }, | ||||
|             0x2 => return Instruction{ .LD_F_Vx = n2 }, // 0xFx29 | LD F, Vx | ||||
|             0x3 => return Instruction{ .LD_B_Vx = n2 }, // 0xFx33 | LD B, Vx | ||||
|             0x5 => return Instruction{ .LD_IPTR_Vx = n2 }, // 0xFx55 | LD [I], Vx | ||||
|             0x6 => return Instruction{ .LD_Vx_IPTR = n2 }, // 0xFx64 | LD Vx, [I] | ||||
|             else => panic("0x{X:} is an unknown 0xFx-prefixed opcode", .{opcode}), | ||||
|         }, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| fn execute(cpu: *Cpu, display: *Display, mem: *[0x1000]u8, instr: Instruction) void { | ||||
|     const scheduler = cpu.scheduler; | ||||
|  | ||||
|     switch (instr) { | ||||
|         .CLS => { | ||||
|             display.buf = [_]u8{0x00} ** (disp.WIDTH * disp.HEIGHT); | ||||
|             scheduler.push(EventKind.RequestRedraw, scheduler.now()); | ||||
|         }, | ||||
|         .RET => { | ||||
|             cpu.pc = cpu.stack[cpu.sp]; | ||||
|             cpu.sp -= 1; | ||||
|         }, | ||||
|         .JP => |addr| { | ||||
|             cpu.pc = addr; | ||||
|         }, | ||||
|         .CALL => |addr| { | ||||
|             cpu.sp += 1; | ||||
|             cpu.stack[cpu.sp] = cpu.pc; | ||||
|             cpu.pc = addr; | ||||
|         }, | ||||
|         .SE_Vx => |args| { | ||||
|             if (cpu.v[args.x] == args.kk) { | ||||
|                 cpu.pc += 2; | ||||
|             } | ||||
|         }, | ||||
|         .SNE_Vx => |args| { | ||||
|             if (cpu.v[args.x] != args.kk) { | ||||
|                 cpu.pc += 2; | ||||
|             } | ||||
|         }, | ||||
|         .SE_Vx_Vy => |args| { | ||||
|             if (cpu.v[args.x] == cpu.v[args.y]) { | ||||
|                 cpu.pc += 2; | ||||
|             } | ||||
|         }, | ||||
|         .LD_Vx => |args| { | ||||
|             cpu.v[args.x] = args.kk; | ||||
|         }, | ||||
|         .ADD_Vx => |args| { | ||||
|             cpu.v[args.x] +%= args.kk; | ||||
|         }, | ||||
|         .LD_Vx_Vy => |args| { | ||||
|             cpu.v[args.x] = cpu.v[args.y]; | ||||
|         }, | ||||
|         .OR => |args| { | ||||
|             cpu.v[args.x] |= cpu.v[args.y]; | ||||
|         }, | ||||
|         .AND => |args| { | ||||
|             cpu.v[args.x] &= cpu.v[args.y]; | ||||
|         }, | ||||
|         .XOR => |args| { | ||||
|             cpu.v[args.x] ^= cpu.v[args.y]; | ||||
|         }, | ||||
|         .ADD_Vx_Vy => |args| { | ||||
|             const did_overflow = @addWithOverflow(u8, cpu.v[args.x], cpu.v[args.y], &cpu.v[args.x]); | ||||
|             cpu.v[0xF] = @boolToInt(did_overflow); | ||||
|         }, | ||||
|         .SUB => |args| { | ||||
|             cpu.v[0xF] = @boolToInt(cpu.v[args.x] > cpu.v[args.y]); | ||||
|             cpu.v[args.x] -%= cpu.v[args.y]; | ||||
|         }, | ||||
|         .SHR => |args| { | ||||
|             cpu.v[0xF] = @boolToInt((cpu.v[args.x] & 0x01) == 0x01); | ||||
|             cpu.v[args.x] >>= 1; | ||||
|         }, | ||||
|         .SUBN => |args| { | ||||
|             cpu.v[0xF] = @boolToInt(cpu.v[args.y] > cpu.v[args.x]); | ||||
|             cpu.v[args.x] = cpu.v[args.y] -% cpu.v[args.x]; | ||||
|         }, | ||||
|         .SHL => |args| { | ||||
|             cpu.v[0xF] = @boolToInt(((cpu.v[args.x] >> 7) & 0x01) == 0x01); | ||||
|             cpu.v[args.x] <<= 1; | ||||
|         }, | ||||
|         .SNE_Vx_Vy => |args| { | ||||
|             if (cpu.v[args.x] != cpu.v[args.y]) { | ||||
|                 cpu.pc += 2; | ||||
|             } | ||||
|         }, | ||||
|         .LD_I => |addr| { | ||||
|             cpu.i = addr; | ||||
|         }, | ||||
|         .JP_V0 => |addr| { | ||||
|             cpu.pc = addr + cpu.v[0]; | ||||
|         }, | ||||
|         .RND => |args| { | ||||
|             cpu.v[args.x] = prng.random.int(u8) & args.kk; | ||||
|         }, | ||||
|         .DRW => |args| { | ||||
|             const x = args.x; | ||||
|             const y = args.y; | ||||
|             const n = args.n; | ||||
|  | ||||
|             const draw_pos = Point{ .x = cpu.v[x], .y = cpu.v[y] }; | ||||
|             const sprite_data = mem[cpu.i..(cpu.i + n)]; | ||||
|  | ||||
|             // Draw Sprite | ||||
|             const collision = disp.drawSprite(display, &draw_pos, &sprite_data); | ||||
|  | ||||
|             cpu.v[0xF] = @boolToInt(collision); | ||||
|             scheduler.push(EventKind.RequestRedraw, scheduler.now()); | ||||
|         }, | ||||
|         .SKP => |x| { | ||||
|             std.log.debug("TODO: SKP V{}", .{x}); | ||||
|         }, | ||||
|         .SKNP => |x| { | ||||
|             std.log.debug("TODO: SKNP V{}", .{x}); | ||||
|         }, | ||||
|         .LD_Vx_DT => |x| { | ||||
|             if (scheduler.find(EventKind.DelayTimer)) |e| { | ||||
|                 const num = (e.timestamp - scheduler.now()) / OPS_PER_HZ; | ||||
|                 cpu.v[x] = @intCast(u8, num); | ||||
|             } else { | ||||
|                 cpu.v[x] = 0; | ||||
|             } | ||||
|         }, | ||||
|         .LD_Vx_K => |x| { | ||||
|             std.log.debug("TODO: LD V{}, K", .{x}); | ||||
|         }, | ||||
|         .LD_DT_Vx => |x| { | ||||
|             const when = scheduler.now() + OPS_PER_HZ * @as(u64, cpu.v[x]); | ||||
|             scheduler.ReplaceOrPush(EventKind.DelayTimer, when); | ||||
|         }, | ||||
|         .LD_ST_Vx => |x| { | ||||
|             const when = scheduler.now() + OPS_PER_HZ * @as(u64, cpu.v[x]); | ||||
|             scheduler.ReplaceOrPush(EventKind.SoundTimer, when); | ||||
|         }, | ||||
|         .ADD_I_Vx => |x| { | ||||
|             const sum: u16 = cpu.i + cpu.v[x]; | ||||
|             cpu.v[0xF] = @boolToInt(sum >= 0x1000); | ||||
|             cpu.i = sum & 0x0FFF; | ||||
|         }, | ||||
|         .LD_F_Vx => |x| { | ||||
|             cpu.i = 0x50 + (5 * cpu.v[x]); | ||||
|         }, | ||||
|         .LD_B_Vx => |x| { | ||||
|             var value = cpu.v[x]; | ||||
|  | ||||
|             mem[cpu.i] = value / 100; | ||||
|             mem[cpu.i + 1] = (value / 10) % 10; | ||||
|             mem[cpu.i + 2] = value % 10; | ||||
|         }, | ||||
|         .LD_IPTR_Vx => |x| { | ||||
|             const upper = x + 1; | ||||
|             std.mem.copy(u8, mem[(cpu.i)..(cpu.i + upper)], cpu.v[0..upper]); | ||||
|         }, | ||||
|         .LD_Vx_IPTR => |x| { | ||||
|             const upper = x + 1; | ||||
|             std.mem.copy(u8, cpu.v[0..upper], mem[cpu.i..(cpu.i + upper)]); | ||||
|         }, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										46
									
								
								src/display.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/display.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| pub const WIDTH: usize = 64; | ||||
| pub const HEIGHT: usize = 32; | ||||
| pub const PIXELBUF_LEN = WIDTH * HEIGHT * @sizeOf(u32); | ||||
| pub const SCALE = 10; | ||||
|  | ||||
| pub const Display = struct { | ||||
|     buf: [WIDTH * HEIGHT]u8, | ||||
|  | ||||
|     pub fn new() Display { | ||||
|         return .{ .buf = [_]u8{0x00} ** (HEIGHT * WIDTH) }; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| pub const Point = struct { | ||||
|     x: u16, | ||||
|     y: u16, | ||||
| }; | ||||
|  | ||||
| pub fn drawSprite(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 = 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; | ||||
| } | ||||
							
								
								
									
										36
									
								
								src/instruction.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/instruction.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| pub const Instruction = union(enum) { | ||||
|     CLS: void, // 0x00E0 | ||||
|     RET: void, // 0x00EE | ||||
|     JP: u12, // 0x1nnn | ||||
|     CALL: u12, // 0x2nnn | ||||
|     SE_Vx: struct { x: u4, kk: u8 }, // 0x3xkk | ||||
|     SNE_Vx: struct { x: u4, kk: u8 }, // 0x4xkk | ||||
|     SE_Vx_Vy: struct { x: u4, y: u4 }, // 0x5xy0 | ||||
|     LD_Vx: struct { x: u4, kk: u8 }, // 0x6xkk | ||||
|     ADD_Vx: struct { x: u4, kk: u8 }, // 0x7xkk | ||||
|     LD_Vx_Vy: struct { x: u4, y: u4 }, // 0x8xy0 | ||||
|     OR: struct { x: u4, y: u4 }, // 0x8xy1 | ||||
|     AND: struct { x: u4, y: u4 }, // 0x8xy2 | ||||
|     XOR: struct { x: u4, y: u4 }, // 0x8xy3 | ||||
|     ADD_Vx_Vy: struct { x: u4, y: u4 }, // 0x8xy4 | ||||
|     SUB: struct { x: u4, y: u4 }, // 0x8xy5 | ||||
|     SHR: struct { x: u4, y: u4 }, // 0x8xy6 | ||||
|     SUBN: struct { x: u4, y: u4 }, // 0x8xy7 | ||||
|     SHL: struct { x: u4, y: u4 }, // 0x8xyE | ||||
|     SNE_Vx_Vy: struct { x: u4, y: u4 }, // 0x9xy0 | ||||
|     LD_I: u12, // 0xAnnn | ||||
|     JP_V0: u12, // 0xBnnn | ||||
|     RND: struct { x: u4, kk: u8 }, // 0xCxkk | ||||
|     DRW: struct { x: u4, y: u4, n: u4 }, // 0xDxyn | ||||
|     SKP: u4, // 0xEx9E | ||||
|     SKNP: u4, // 0xExA1 | ||||
|     LD_Vx_DT: u4, // 0xFx07 | ||||
|     LD_Vx_K: u4, // 0xFx0A | ||||
|     LD_DT_Vx: u4, // 0xFx15 | ||||
|     LD_ST_Vx: u4, // 0xFx18 | ||||
|     ADD_I_Vx: u4, // 0xFx1E | ||||
|     LD_F_Vx: u4, // 0xFx29 | ||||
|     LD_B_Vx: u4, // 0xFx33 | ||||
|     LD_IPTR_Vx: u4, // 0xFx55 | ||||
|     LD_Vx_IPTR: u4, // 0xFx65 | ||||
| }; | ||||
							
								
								
									
										89
									
								
								src/main.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/main.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| const std = @import("std"); | ||||
| const SDL = @import("sdl2"); | ||||
| const sched = @import("scheduler.zig"); | ||||
| const emu = @import("chip8.zig"); | ||||
| const display = @import("display.zig"); | ||||
|  | ||||
| const Timer = std.time.Timer; | ||||
| const Chip8 = emu.Chip8; | ||||
| const Display = display.Display; | ||||
| const Scheduler = sched.Scheduler; | ||||
|  | ||||
| const OPS_PER_HZ = sched.OPS_PER_HZ; | ||||
| const CHIP8_WIDTH = display.WIDTH; | ||||
| const CHIP8_HEIGHT = display.HEIGHT; | ||||
| const WINDOW_WIDTH = CHIP8_WIDTH * display.SCALE; | ||||
| const WINDOW_HEIGHT = CHIP8_HEIGHT * display.SCALE; | ||||
| const PIXELBUF_LEN = display.PIXELBUF_LEN; | ||||
|  | ||||
| pub fn main() anyerror!void { | ||||
|     const heap = std.heap.page_allocator; | ||||
|     var arg_it = std.process.args(); | ||||
|  | ||||
|     // Skip process name | ||||
|     _ = arg_it.skip(); | ||||
|  | ||||
|     const os_string = try arg_it.next(heap) orelse { | ||||
|         std.debug.warn("Expected first argument to be path to CHIP-8 rom\n", .{}); | ||||
|         return; | ||||
|     }; | ||||
|     const rom_path = try std.fs.path.resolve(heap, &[_][]const u8{os_string}); | ||||
|  | ||||
|     if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0) { | ||||
|         sdlPanic(); | ||||
|     } | ||||
|     defer SDL.SDL_Quit(); | ||||
|  | ||||
|     const window = SDL.SDL_CreateWindow( | ||||
|         "Zig CHIP-8 Emulator", | ||||
|         SDL.SDL_WINDOWPOS_CENTERED, | ||||
|         SDL.SDL_WINDOWPOS_CENTERED, | ||||
|         WINDOW_WIDTH, | ||||
|         WINDOW_HEIGHT, | ||||
|         SDL.SDL_WINDOW_SHOWN, | ||||
|     ) orelse sdlPanic(); | ||||
|     defer _ = SDL.SDL_DestroyWindow(window); | ||||
|  | ||||
|     const renderer = SDL.SDL_CreateRenderer(window, -1, SDL.SDL_RENDERER_ACCELERATED) orelse sdlPanic(); | ||||
|     defer _ = SDL.SDL_DestroyRenderer(renderer); | ||||
|  | ||||
|     const texture = SDL.SDL_CreateTexture(renderer, SDL.SDL_PIXELFORMAT_RGBA8888, SDL.SDL_TEXTUREACCESS_STATIC, 64, 32); | ||||
|     defer SDL.SDL_DestroyTexture(texture); | ||||
|  | ||||
|     var pixels: [PIXELBUF_LEN]u8 = [_]u8{0x00} ** PIXELBUF_LEN; | ||||
|  | ||||
|     var scheduler = Scheduler.new(heap, &pixels); | ||||
|     var chip8 = try Chip8.fromFile(heap, &scheduler, rom_path); | ||||
|  | ||||
|     const frametime = 1_000_000_000 / OPS_PER_HZ; | ||||
|     var timer = Timer.start() catch unreachable; | ||||
|  | ||||
|     emuloop: while (true) { | ||||
|         var e: SDL.SDL_Event = undefined; | ||||
|         while (SDL.SDL_PollEvent(&e) != 0) { | ||||
|             if (e.type == SDL.SDL_QUIT) { | ||||
|                 break :emuloop; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         _ = SDL.SDL_UpdateTexture(texture, null, &pixels, CHIP8_WIDTH * @sizeOf(u32)); | ||||
|         _ = SDL.SDL_RenderCopy(renderer, texture, null, null); | ||||
|         SDL.SDL_RenderPresent(renderer); | ||||
|  | ||||
|         sched.run_until(&scheduler, &chip8, scheduler.now() + OPS_PER_HZ); | ||||
|  | ||||
|         while (true) { | ||||
|             const diff = timer.read(); | ||||
|  | ||||
|             if (diff >= frametime) { | ||||
|                 timer.reset(); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn sdlPanic() noreturn { | ||||
|     const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error"; | ||||
|     @panic(std.mem.sliceTo(str, 0)); | ||||
| } | ||||
							
								
								
									
										122
									
								
								src/scheduler.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/scheduler.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| const std = @import("std"); | ||||
| const disp = @import("display.zig"); | ||||
| const Chip8 = @import("chip8.zig").Chip8; | ||||
| const Order = std.math.Order; | ||||
| const Allocator = std.mem.Allocator; | ||||
| const PriorityQueue = std.PriorityQueue; | ||||
| const Display = disp.Display; | ||||
|  | ||||
| pub const OPS_PER_HZ = 500; | ||||
|  | ||||
| const PIXELBUF_LEN = disp.PIXELBUF_LEN; | ||||
|  | ||||
| pub const Scheduler = struct { | ||||
|     pixels_ptr: *[PIXELBUF_LEN]u8, | ||||
|     timestamp: u64, | ||||
|     queue: PriorityQueue(Event), | ||||
|  | ||||
|     pub fn new(alloc: *Allocator, pixels_ptr: *[PIXELBUF_LEN]u8) Scheduler { | ||||
|         var queue = Scheduler{ | ||||
|             .timestamp = 0, | ||||
|             .pixels_ptr = pixels_ptr, | ||||
|             .queue = PriorityQueue(Event).init(alloc, less_than), | ||||
|         }; | ||||
|  | ||||
|         queue.pushEvent(.{ | ||||
|             .kind = EventKind.HeatDeath, | ||||
|             .timestamp = std.math.maxInt(u64), | ||||
|         }); | ||||
|  | ||||
|         return queue; | ||||
|     } | ||||
|  | ||||
|     pub fn now(self: *const Scheduler) u64 { | ||||
|         return self.timestamp; | ||||
|     } | ||||
|  | ||||
|     pub fn push(self: *Scheduler, kind: EventKind, when: u64) void { | ||||
|         self.pushEvent(Event{ .kind = kind, .timestamp = when }); | ||||
|     } | ||||
|  | ||||
|     pub fn ReplaceOrPush(self: *Scheduler, kind: EventKind, when: u64) void { | ||||
|         if (self.find(kind)) |event| { | ||||
|             self.queue.update(event, Event{ .kind = kind, .timestamp = when }) catch unreachable; | ||||
|         } else { | ||||
|             self.push(kind, when); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn pushEvent(self: *Scheduler, event: Event) void { | ||||
|         self.queue.add(event) catch unreachable; | ||||
|     } | ||||
|  | ||||
|     pub fn find(self: *Scheduler, kind: EventKind) ?Event { | ||||
|         var it = self.queue.iterator(); | ||||
|  | ||||
|         while (it.next()) |e| { | ||||
|             if (e.kind == kind) return e; | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     fn handleEvent(self: *Scheduler, chip8: *Chip8, event: Event) void { | ||||
|         switch (event.kind) { | ||||
|             .HeatDeath => { | ||||
|                 std.debug.panic("Reached u64 overflow somehow", .{}); | ||||
|             }, | ||||
|             .RequestRedraw => draw(&chip8.disp, self.pixels_ptr), | ||||
|             .DelayTimer => { | ||||
|                 std.log.debug("{} | Delay Timer Expire", .{self.timestamp}); | ||||
|             }, | ||||
|             .SoundTimer => { | ||||
|                 std.log.debug("{} | Sound Timer Expire", .{self.timestamp}); | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const Event = struct { | ||||
|     kind: EventKind, | ||||
|     timestamp: u64, | ||||
| }; | ||||
|  | ||||
| pub const EventKind = enum { | ||||
|     HeatDeath, | ||||
|     RequestRedraw, | ||||
|     SoundTimer, | ||||
|     DelayTimer, | ||||
| }; | ||||
|  | ||||
| fn less_than(a: Event, b: Event) Order { | ||||
|     return std.math.order(a.timestamp, b.timestamp); | ||||
| } | ||||
|  | ||||
| pub fn run_until(sched: *Scheduler, chip8: *Chip8, timestamp: u64) void { | ||||
|     while (sched.timestamp <= timestamp) { | ||||
|         sched.timestamp += chip8.step(); | ||||
|  | ||||
|         const should_handle = if (sched.queue.peek()) |e| sched.timestamp >= e.timestamp else false; | ||||
|  | ||||
|         if (should_handle) { | ||||
|             const event = sched.queue.remove(); | ||||
|             sched.handleEvent(chip8, event); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn draw(display: *const Display, buf: *[PIXELBUF_LEN]u8) void { | ||||
|     const WHITE = [_]u8{ 0xFF, 0xFF, 0xFF, 0xFF }; | ||||
|     const BLACK = [_]u8{ 0xFF, 0x00, 0x00, 0x00 }; | ||||
|     var i: usize = 0; | ||||
|  | ||||
|     while (i < display.buf.len) : (i += 1) { | ||||
|         var px_i = i * 4; | ||||
|  | ||||
|         if (display.buf[i] == 0x01) { | ||||
|             std.mem.copy(u8, buf[px_i..(px_i + 4)], &WHITE); | ||||
|         } else { | ||||
|             std.mem.copy(u8, buf[px_i..(px_i + 4)], &BLACK); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/util.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/util.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| const std = @import("std"); | ||||
| const expect = std.testing.expect; | ||||
|  | ||||
| pub fn calc12bit(nib2: u4, nib3: u4, nib4: u4) u12 { | ||||
|     return @as(u12, nib2) << 8 | @as(u12, nib3) << 4 | @as(u12, nib4); | ||||
| } | ||||
|  | ||||
| pub fn calc8bit(nib_a: u4, nib_b: u4) u8 { | ||||
|     return @as(u8, nib_a) << 4 | @as(u8, nib_b); | ||||
| } | ||||
|  | ||||
| test "calc12bit works" { | ||||
|     const left: u12 = 0xABC; | ||||
|     const right: u12 = calc12bit(0xA, 0xB, 0xC); | ||||
|  | ||||
|     expect(left == right); | ||||
| } | ||||
|  | ||||
| test "calc8bit works" { | ||||
|     const left: u8 = 0xAB; | ||||
|     const right: u12 = calc8bit(0xA, 0xB); | ||||
|  | ||||
|     expect(left == right); | ||||
| } | ||||
							
								
								
									
										1
									
								
								third_party/SDL.zig
									
									
									
									
										vendored
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								third_party/SDL.zig
									
									
									
									
										vendored
									
									
										Submodule
									
								
							 Submodule third_party/SDL.zig added at 5118ef94e9
									
								
							
		Reference in New Issue
	
	Block a user