Something went wrong. Try again.
a fantasy console written in Zig
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213const std = @import("std");
pub const System = struct { // 64KB of total system RAM ram: [0x10000]u8 = std.mem.zeroes([0x10000]u8),
// CPU Registers pc: u16 = 0, // Program Counter sp: u16 = 0x01ff, // Stack Pointer a: u8 = 0, // Accumulator b: u8 = 0, // B Register c: u8 = 0, // C Register d: u8 = 0, // D Register e: u8 = 0, // E Register f: u8 = 0, // F Register g: u8 = 0, // G Register h: u8 = 0, // H Register l: u8 = 0, // L Register flags: packed struct { z: bool, c: bool, s: bool, o: bool, } = .{ .z = false, .c = false, .s = false, .o = false, }, // Status flags (Zero, Negative, Carry, etc)
// Memory-Mapped Layout Constants pub const VRAM_START: u16 = 0x8000; pub const OAM_START: u16 = 0xfc00; // Object Attribute Memory (Sprites) pub const IO_START: u16 = 0xff00; // Controller / Audio registers
// Width/Height pub const WIDTH = 128; pub const HEIGHT = 128;
// Flag Bits pub const ZERO_FLAG_BIT: u8 = 0x01; pub const CARRY_FLAG_BIT: u8 = 0x02; pub const SIGN_FLAG_BIT: u8 = 0x04; pub const OVERFLOW_FLAG_BIT: u8 = 0x08;
pub fn read(self: *System, address: u16) u8 { return self.ram[address]; }
pub fn write(self: *System, address: u16, value: u8) void { self.ram[address] = value; }
pub fn get_register_ptr(sys: *System, address_val: u8) SystemError!*u8 { if (address_val == 0x00) { return &sys.a; } else if (address_val == 0x01) { return &sys.b; } else if (address_val == 0x02) { return &sys.c; } else if (address_val == 0x03) { return &sys.d; } else if (address_val == 0x04) { return &sys.e; } else if (address_val == 0x05) { return &sys.f; } else if (address_val == 0x06) { return &sys.g; } else if (address_val == 0x07) { return &sys.h; } else if (address_val == 0x08) { return &sys.l; }
return SystemError.InvalidRegister; }
pub fn step(sys: *System) !void { const opcode_byte = sys.read(sys.pc); const instruction = @as(Opcode, @enumFromInt(opcode_byte)); sys.pc += 1;
switch (instruction) { .NOP => {}, .LDA_IMMEDIATE => { sys.a = sys.read(sys.pc); sys.pc += 1; sys.updateFlags(@intCast(sys.a)); }, .LDA_ABSOLUTE => { const low = sys.read(sys.pc); const high = sys.read(sys.pc + 1); sys.pc += 2; const addr = (@as(u16, high) << 8) | low; sys.a = sys.read(addr); sys.updateFlags(@intCast(sys.a)); }, .LD_ADDRESS => { sys.h = sys.read(sys.pc); sys.l = sys.read(sys.pc + 1); sys.pc += 2; }, .LDA_ADDRESS => { const addr = (@as(u16, sys.h) << 8) | sys.l; sys.a = sys.read(addr); sys.updateFlags(@intCast(sys.a)); }, .STA_ABSOLUTE => { const low = sys.read(sys.pc); const high = sys.read(sys.pc + 1); sys.pc += 2; const addr = (@as(u16, high) << 8) | low; sys.write(addr, sys.a); }, .ADD_REGISTER => { const reg_val = sys.read(sys.pc); sys.pc += 1; const value: *u8 = try sys.get_register_ptr(reg_val); const result: u16 = @as(u16, sys.a) + @as(u16, value.*); sys.a = @truncate(result); sys.updateFlags(result); }, .ADD_IMMEDIATE => { sys.a += sys.read(sys.pc); sys.pc += 1; sys.updateFlags(@intCast(sys.a)); }, .JMP => { const low = sys.read(sys.pc); const high = sys.read(sys.pc + 1); sys.pc += 2; const addr = (@as(u16, high) << 8) | low; sys.pc = addr; }, .JZ => { const low = sys.read(sys.pc); const high = sys.read(sys.pc + 1); sys.pc += 2; const addr = (@as(u16, high) << 8) | low;
if (sys.flags.z) { sys.pc = addr; } }, .JNZ => { const low = sys.read(sys.pc); const high = sys.read(sys.pc + 1); sys.pc += 2; const addr = (@as(u16, high) << 8) | low;
if (!sys.flags.z) { sys.pc = addr; } }, .CMP => { const reg_val = sys.read(sys.pc); sys.pc += 1; const value = try sys.get_register_ptr(reg_val); const compare_val: u16 = @as(u16, sys.a) - @as(u16, value.*); sys.updateFlags(compare_val); }, .MOV => { const reg_a = sys.read(sys.pc); const reg_b = sys.read(sys.pc + 1); sys.pc += 2; const val_a = try sys.get_register_ptr(reg_a); const val_b = try sys.get_register_ptr(reg_b); val_b.* = val_a.*; }, } }
pub fn updateFlags(sys: *System, value: u16) void { sys.flags.z = (value == 0); sys.flags.s = ((value & 0x80) != 0); sys.flags.c = (value > 0xff); }
pub fn loadProgramRaw(sys: *System, data: []const u8) void { @memcpy(sys.ram[0..data.len], data); }
pub fn loadFile(sys: *System, io: std.Io, filename: []const u8) !void { const file = try std.Io.Dir.cwd().openFile(io, filename, .{ .mode = .read_only }); defer file.close(io); var buffer: [1000]u8 = undefined; _ = try file.readPositionalAll(io, &buffer, 0); @memcpy(sys.ram[0..buffer.len], &buffer); const file_len = try file.length(io); sys.ram[file_len] = 0x00; }};
pub const Opcode = enum(u8) { NOP = 0x00, LDA_IMMEDIATE = 0x01, // Load Accumulator with a fixed value LDA_ABSOLUTE = 0x02, // Load Accumulator from a RAM address LD_ADDRESS = 0x03, // Load address into HL register LDA_ADDRESS = 0x04, // Load Accumulator from RAM address store in HL register STA_ABSOLUTE = 0x05, // Store Accumulator to a RAM address ADD_REGISTER = 0x06, // Add the value of Accumulator to value in register and store in Accumulator ADD_IMMEDIATE = 0x07, // Add the value of Accumulator to fixed value and store in Accumulator JMP = 0x08, // Jump to address JZ = 0x09, // Jump to address if zero flag set JNZ = 0x0a, // Jump to address if zero flag is not set CMP = 0x0b, // Compare the value in Accumulator with value in register (Accumulator - register, discards result) MOV = 0x0c, // Copy value from one register to another};
pub const SystemError = error{ InvalidRegister,};