aboutsummaryrefslogtreecommitdiff
path: root/main.ha
diff options
context:
space:
mode:
Diffstat (limited to 'main.ha')
-rw-r--r--main.ha569
1 files changed, 569 insertions, 0 deletions
diff --git a/main.ha b/main.ha
new file mode 100644
index 0000000..7a4e135
--- /dev/null
+++ b/main.ha
@@ -0,0 +1,569 @@
+use fmt;
+use fs;
+use io;
+use math::random;
+use os;
+use time;
+use types;
+use sdl2;
+
+def SCALE: int = 10;
+def CLOCKSPEED: int = 540;
+
+def SCREEN_WIDTH: size = 64;
+def SCREEN_HEIGHT: size = 32;
+
+def MEM_SIZE: size = 4096;
+def START_ADDR: u16 = 0x200;
+
+def FONT_SIZE: size = 80;
+def FONT: [FONT_SIZE]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
+];
+
+type inst = struct {
+ op: u16,
+ X: u8,
+ Y: u8,
+ N: u8,
+ NN: u8,
+ NNN: u16
+};
+
+type state = struct {
+ mem: [MEM_SIZE]u8,
+ display: [SCREEN_WIDTH * SCREEN_HEIGHT]bool,
+ draw: bool,
+ keys: [16]bool,
+ stack: [16]u16,
+ SP: u16,
+ PC: u16,
+ V: [16]u8,
+ I: u16,
+ DT: u8,
+ ST: u8,
+};
+
+fn chip8() state = {
+ let state = state {
+ PC = START_ADDR,
+ ...
+ };
+
+ state.mem[..FONT_SIZE] = FONT[..];
+
+ return state;
+};
+
+fn reset(state: *state) void = {
+ state.display = [false...];
+ state.keys = [false...];
+ state.stack = [0...];
+ state.SP = 0;
+ state.PC = START_ADDR;
+ state.V = [0...];
+ state.I = 0;
+ state.DT = 0;
+ state.ST = 0;
+
+ state.mem[..FONT_SIZE] = FONT[..];
+};
+
+fn instruction(opcode: u16) inst = inst {
+ op = opcode,
+ X = (opcode >> 8): u8 & 0x0F,
+ Y = (opcode >> 4): u8 & 0x0F,
+ N = opcode: u8 & 0x0F,
+ NN = opcode: u8 & 0xFF,
+ NNN = opcode & 0x0FFF,
+};
+
+fn print_instruction(state: *state, inst: inst) void = {
+ fmt::printf("Address: 0x{:.4X}, OP: 0x{:.4X}, Desc: ", state.PC - 2, inst.op)!;
+
+ switch ((inst.op & 0xF000) >> 12) {
+ case 0x00 =>
+ if (inst.NNN == 0x0E0) {
+ fmt::printfln("Clear screen")!;
+ } else if (inst.NNN == 0x0EE) {
+ fmt::printfln("Return from subroutine to 0x{:.4X}", state.stack[state.SP - 1])!;
+ } else fmt::printfln("Unkown OP code")!;
+ case 0x01 =>
+ fmt::printfln("Jump to 0x{:.4X}", inst.NNN)!;
+ case 0x02 =>
+ fmt::printfln("Call subroutine at 0x{:.4X}", inst.NNN)!;
+ case 0x03 =>
+ fmt::printfln("Skip the next instruction if V{:X} (0x{:.2X}) == 0x{:.2X}",
+ inst.X, state.V[inst.X], inst.NN)!;
+ case 0x04 =>
+ fmt::printfln("Skip the next instruction if V{:X} (0x{:.2X}) != 0x{:.2X}",
+ inst.X, state.V[inst.X], inst.NN)!;
+ case 0x05 =>
+ fmt::printfln("Skip the next instruction if V{:X} (0x{:.2X}) == V{:X} (0x{:.2X})",
+ inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!;
+ case 0x06 =>
+ fmt::printfln("V{:X} (0x{:.2X}) = 0x{:.2X}", inst.X, state.V[inst.X], inst.NN)!;
+ case 0x07 =>
+ fmt::printfln("V{:X} (0x{:.2X}) += 0x{:.2X}", inst.X, state.V[inst.X], inst.NN)!;
+ case 0x08 =>
+ switch (inst.N) {
+ case 0x00 =>
+ fmt::printfln("V{:X} (0x{:.2X}) = V{:X} (0x{:.2X})",
+ inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!;
+ case 0x01 =>
+ fmt::printfln("V{:X} (0x{:.2X}) |= V{:X} (0x{:.2X})",
+ inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!;
+ case 0x02 =>
+ fmt::printfln("V{:X} (0x{:.2X}) &= V{:X} (0x{:.2X})",
+ inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!;
+ case 0x03 =>
+ fmt::printfln("V{:X} (0x{:.2X}) ^= V{:X} (0x{:.2X})",
+ inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!;
+ case 0x04 =>
+ fmt::printfln("V{:X} (0x{:.2X}) += V{:X} (0x{:.2X})",
+ inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!;
+ case 0x05 =>
+ fmt::printfln("V{:X} (0x{:.2X}) -= V{:X} (0x{:.2X})",
+ inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!;
+ case 0x06 =>
+ fmt::printfln("V{:X} >>= 1", inst.X)!;
+ case 0x07 =>
+ fmt::printfln("V{:X} = V{:X} (0x{:.2X}) - V{:X} (0x{:.2X})",
+ inst.X, inst.Y, state.V[inst.Y], inst.X, state.V[inst.X])!;
+ case 0x0E =>
+ fmt::printfln("V{:X} <<= 1", inst.X)!;
+ case =>
+ fmt::printfln("Unkown OP code")!;
+ };
+ case 0x09 =>
+ fmt::printfln("Skip the next instruction if V{:X} (0x{:.2X}) != V{:X} (0x{:.2X})",
+ inst.X, state.V[inst.X], inst.Y, state.V[inst.Y])!;
+ case 0x0A =>
+ fmt::printfln("I (0x{:.4X}) = 0x{:.4X}", state.I, inst.NNN)!;
+ case 0x0B =>
+ fmt::printfln("Jump to 0x{:.4X}", state.V[0x00] + inst.NNN)!;
+ case 0x0C =>
+ fmt::printfln("V{:X} (0x{:.2X}) = rand() & 0x{:.2X}",
+ inst.X, state.V[inst.X], inst.NN)!;
+ case 0x0D =>
+ fmt::printfln("Draw 8 pixel wide, {} pixel tall sprite at X = V{:X} and Y = V{:X} ({}, {}), reading from memory location 0x{:.4X} onward",
+ inst.N, inst.X, inst.Y, state.V[inst.X], state.V[inst.Y], state.I)!;
+ case 0x0E =>
+ if (inst.NN == 0x9E) {
+ fmt::printfln("Skip next instruction if the key stored in V{:X} (0x{:X}) is pressed",
+ inst.X, state.V[inst.X] & 0x0F)!;
+ } else if (inst.NN == 0xA1) {
+ fmt::printfln("Skip next instruction if the key stored in V{:X} (0x{:X}) is not pressed",
+ inst.X, state.V[inst.X] & 0x0F)!;
+ } else fmt::printfln("Unkown OP code")!;
+ case 0x0F =>
+ switch (inst.NN) {
+ case 0x07 =>
+ fmt::printfln("V{:X} (0x{:.2X}) = delay timer (0x{:.2X})",
+ inst.X, state.V[inst.X], state.DT)!;
+ case 0x0A =>
+ fmt::printfln("Wait for keypress and store it in V{:X} (0x{:.2X})",
+ inst.X, state.V[inst.X])!;
+ case 0x15 =>
+ fmt::printfln("Set delay timer (0x{:.2X}) to V{:X} (0x{:.2X})",
+ state.DT, inst.X, state.V[inst.X])!;
+ case 0x18 =>
+ fmt::printfln("Set sound timer (0x{:.2X}) to V{:X} (0x{:.2X})",
+ state.ST, inst.X, state.V[inst.X])!;
+ case 0x1E =>
+ fmt::printfln("I += V{:X} (0x{:.2X})", inst.X, state.V[inst.X])!;
+ case 0x29 =>
+ fmt::printfln("I = sprite location for character {:X} (0x{:.4X})",
+ state.V[inst.X] & 0x0F, (state.V[inst.X] & 0x0F) * 5)!;
+ case 0x33 =>
+ fmt::printfln("Stores the BCD representation of V{:X} (0x{:.2X}) with hundreds at 0x{:.4X}, tens at 0x{:.4X} and ones at 0x{:.4X}",
+ inst.X, state.V[inst.X], state.I, state.I + 1, state.I + 2)!;
+ case 0x55 =>
+ fmt::printfln("Stores from V0 to (and including) V{:X} in memory, starting at 0x{:.4X}",
+ inst.X, state.I)!;
+ case 0x65 =>
+ fmt::printfln("Fills from V0 to (and including) V{:X} with values from memory, starting at 0x{:.4X}",
+ inst.X, state.I)!;
+ case =>
+ fmt::printfln("Unkown OP code")!;
+ };
+ case =>
+ fmt::printfln("Unkown OP code")!;
+ };
+};
+
+fn execute_instruction(state: *state, inst: inst, random: *random::random) void = {
+ switch ((inst.op & 0xF000) >> 12) {
+ case 0x00 =>
+ if (inst.NNN == 0x0E0) {
+ state.display[..] = [false...];
+ state.draw = true;
+ } else if (inst.NNN == 0x0EE) {
+ state.SP -= 1;
+ state.PC = state.stack[state.SP];
+ };
+ case 0x01 =>
+ state.PC = inst.NNN;
+ case 0x02 =>
+ state.stack[state.SP] = state.PC;
+ state.SP += 1;
+ state.PC = inst.NNN;
+ case 0x03 =>
+ if (state.V[inst.X] == inst.NN)
+ state.PC += 2;
+ case 0x04 =>
+ if (state.V[inst.X] != inst.NN)
+ state.PC += 2;
+ case 0x05 =>
+ if (state.V[inst.X] == state.V[inst.Y])
+ state.PC += 2;
+ case 0x06 =>
+ state.V[inst.X] = inst.NN;
+ case 0x07 =>
+ state.V[inst.X] += inst.NN;
+ case 0x08 =>
+ switch (inst.N) {
+ case 0x00 =>
+ state.V[inst.X] = state.V[inst.Y];
+ case 0x01 =>
+ state.V[inst.X] |= state.V[inst.Y];
+ case 0x02 =>
+ state.V[inst.X] &= state.V[inst.Y];
+ case 0x03 =>
+ state.V[inst.X] ^= state.V[inst.Y];
+ case 0x04 =>
+ state.V[0x0F] = if (state.V[inst.X] > types::U8_MAX - state.V[inst.Y]) 1 else 0;
+ state.V[inst.X] += state.V[inst.Y];
+ case 0x05 =>
+ state.V[0x0F] = if (state.V[inst.X] >= state.V[inst.Y]) 1 else 0;
+
+ state.V[inst.X] -= state.V[inst.Y];
+ case 0x06 =>
+ state.V[0x0F] = state.V[inst.X] & 0x01;
+ state.V[inst.X] >>= 1;
+ case 0x07 =>
+ state.V[0x0F] = if (state.V[inst.Y] >= state.V[inst.X]) 1 else 0;
+
+ state.V[inst.X] = state.V[inst.Y] - state.V[inst.X];
+ case 0x0E =>
+ state.V[0x0F] = state.V[inst.X] & 0x80;
+ state.V[inst.X] <<= 1;
+ case =>
+ return;
+ };
+ case 0x09 =>
+ if (state.V[inst.X] != state.V[inst.Y])
+ state.PC += 2;
+ case 0x0A =>
+ state.I = inst.NNN;
+ case 0x0B =>
+ state.PC = state.V[0x00] + inst.NNN;
+ case 0x0C =>
+ state.V[inst.X] = (random::next(random) & 255): u8 & inst.NN;
+ case 0x0D =>
+ const rx = state.V[inst.X];
+ const ry = state.V[inst.Y];
+ const sprite_height = inst.N;
+ let sprite_row = 0u8;
+
+ state.V[0x0F] = 0;
+
+ for (let y = 0u8; y < sprite_height; y += 1) {
+ sprite_row = state.mem[state.I + y];
+
+ for (let x = 0u8; x < 8; x += 1) {
+ if ((sprite_row & (0x80 >> x)) != 0) {
+ let pixel = &state.display[((ry + y) * SCREEN_WIDTH + rx + x) % (SCREEN_WIDTH * SCREEN_HEIGHT)];
+
+ if (*pixel)
+ state.V[0x0F] = 1;
+
+ *pixel = !*pixel;
+ };
+ };
+ };
+
+ state.draw = true;
+ case 0x0E =>
+ if (inst.NN == 0x9E) {
+ if (state.keys[state.V[inst.X] & 0x0F])
+ state.PC += 2;
+ } else if (inst.NN == 0xA1) {
+ if (!state.keys[state.V[inst.X] & 0x0F])
+ state.PC += 2;
+ };
+ case 0x0F =>
+ switch (inst.NN) {
+ case 0x07 =>
+ state.V[inst.X] = state.DT;
+ case 0x0A =>
+ let any_pressed = false;
+ let key: u8 = 0xFF;
+
+ for (let i = 0z; key == 0xFF && i < len(state.keys); i += 1)
+ if (state.keys[i]) {
+ any_pressed = true;
+ key = i: u8;
+ break;
+ };
+
+ if (!any_pressed) state.PC -= 2
+ else {
+ if (state.keys[key])
+ state.PC -= 2
+ else {
+ state.V[inst.X] = key;
+ key = 0xFF;
+ any_pressed = false;
+ };
+ };
+ case 0x15 =>
+ state.DT = state.V[inst.X];
+ case 0x18 =>
+ state.ST = state.V[inst.X];
+ case 0x1E =>
+ state.I += state.V[inst.X];
+ case 0x29 =>
+ state.I = (state.V[inst.X] & 0x0F) * 5;
+ case 0x33 =>
+ let BCD = state.V[inst.X];
+ state.mem[state.I + 2] = BCD % 10;
+ BCD /= 10;
+ state.mem[state.I + 1] = BCD % 10;
+ BCD /= 10;
+ state.mem[state.I] = BCD;
+ case 0x55 =>
+ for (let i = 0u8; i <= inst.X; i += 1) {
+ state.mem[state.I] = state.V[i];
+ state.I += 1;
+ };
+ case 0x65 =>
+ for (let i = 0u8; i <= inst.X; i += 1) {
+ state.V[i] = state.mem[state.I];
+ state.I += 1;
+ };
+ case =>
+ return;
+ };
+ case =>
+ return;
+ };
+};
+
+fn tick_timers(state: *state) void = {
+ if (state.DT > 0)
+ state.DT -= 1;
+
+ if (state.ST > 0) {
+ // BEEP
+
+ state.ST -= 1;
+ };
+};
+
+fn tick(state: *state, random: *random::random, debug: bool) void = {
+ let op_hi = state.mem[state.PC]: u16;
+ let op_lo = state.mem[state.PC + 1]: u16;
+ let op = (op_hi << 8) | op_lo;
+ state.PC += 2;
+
+ if (debug)
+ print_instruction(state, instruction(op));
+
+ execute_instruction(state, instruction(op), random);
+};
+
+fn update_screen(state: *state, renderer: *sdl2::SDL_Renderer) (void | sdl2::error) = {
+ let rect = sdl2::SDL_Rect {
+ x = 0,
+ y = 0,
+ w = SCALE,
+ h = SCALE
+ };
+
+ for (let i = 0z; i < len(state.display); i += 1) {
+ rect.x = (i % SCREEN_WIDTH): int * SCALE;
+ rect.y = (i / SCREEN_WIDTH): int * SCALE;
+
+ if (state.display[i]) {
+ sdl2::SDL_SetRenderDrawColor(renderer, 0xFE, 0xFE, 0xFE, 0xFF)?;
+ sdl2::SDL_RenderFillRect(renderer, &rect)?;
+
+ sdl2::SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF)?;
+ sdl2::SDL_RenderDrawRect(renderer, &rect)?;
+ } else {
+ sdl2::SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF)?;
+ sdl2::SDL_RenderFillRect(renderer, &rect)?;
+ };
+ };
+
+ sdl2::SDL_RenderPresent(renderer);
+};
+
+fn run() (void | fs::error | io::error | sdl2::error) = {
+ if (len(os::args) < 2) {
+ fmt::fatalf("Usage: {} <rom>", os::args[0]);
+ };
+
+ let debug = os::getenv("CHIP8_DEBUG") is str;
+
+ const chip8 = chip8();
+ let seed = time::now(time::clock::REALTIME).nsec: u64;
+ let random = random::init(seed);
+
+ const rom = os::open(os::args[1])?;
+ defer io::close(rom)!;
+
+ io::read(rom, chip8.mem[START_ADDR..])?;
+
+ sdl2::SDL_Init(sdl2::SDL_INIT_VIDEO | sdl2::SDL_INIT_AUDIO)?;
+ defer sdl2::SDL_Quit();
+
+ let window = sdl2::SDL_CreateWindow("CHIP-8", sdl2::SDL_WINDOWPOS_CENTERED, sdl2::SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH: int * SCALE, SCREEN_HEIGHT: int * SCALE, sdl2::SDL_WindowFlags::SHOWN)?;
+ defer sdl2::SDL_DestroyWindow(window);
+
+ let renderer = sdl2::SDL_CreateRenderer(window, -1, sdl2::SDL_RendererFlags::ACCELERATED)?;
+ defer sdl2::SDL_DestroyRenderer(renderer);
+
+ sdl2::SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF)?;
+ sdl2::SDL_RenderClear(renderer)?;
+
+ let quit = false;
+ for (!quit) {
+ let event = sdl2::event { ... };
+ for (sdl2::SDL_PollEvent(&event)? == 1)
+ switch (event.event_type) {
+ case sdl2::SDL_EventType::QUIT =>
+ quit = true;
+ case sdl2::SDL_EventType::KEYDOWN =>
+ switch (event.key.keysym.scancode) {
+ case sdl2::SDL_Scancode::ESCAPE =>
+ quit = true;
+ case sdl2::SDL_Scancode::TWO =>
+ chip8.keys[0x1] = true;
+ case sdl2::SDL_Scancode::THREE =>
+ chip8.keys[0x2] = true;
+ case sdl2::SDL_Scancode::FOUR =>
+ chip8.keys[0x3] = true;
+ case sdl2::SDL_Scancode::FIVE =>
+ chip8.keys[0xC] = true;
+ case sdl2::SDL_Scancode::W =>
+ chip8.keys[0x4] = true;
+ case sdl2::SDL_Scancode::E =>
+ chip8.keys[0x5] = true;
+ case sdl2::SDL_Scancode::R =>
+ chip8.keys[0x6] = true;
+ case sdl2::SDL_Scancode::T =>
+ chip8.keys[0xD] = true;
+ case sdl2::SDL_Scancode::S =>
+ chip8.keys[0x7] = true;
+ case sdl2::SDL_Scancode::D =>
+ chip8.keys[0x8] = true;
+ case sdl2::SDL_Scancode::F =>
+ chip8.keys[0x9] = true;
+ case sdl2::SDL_Scancode::G =>
+ chip8.keys[0xE] = true;
+ case sdl2::SDL_Scancode::X =>
+ chip8.keys[0xA] = true;
+ case sdl2::SDL_Scancode::C =>
+ chip8.keys[0x0] = true;
+ case sdl2::SDL_Scancode::V =>
+ chip8.keys[0xB] = true;
+ case sdl2::SDL_Scancode::B =>
+ chip8.keys[0xF] = true;
+ case =>
+ void;
+ };
+ case sdl2::SDL_EventType::KEYUP =>
+ switch (event.key.keysym.scancode) {
+ case sdl2::SDL_Scancode::ESCAPE =>
+ quit = true;
+ case sdl2::SDL_Scancode::TWO =>
+ chip8.keys[0x1] = false;
+ case sdl2::SDL_Scancode::THREE =>
+ chip8.keys[0x2] = false;
+ case sdl2::SDL_Scancode::FOUR =>
+ chip8.keys[0x3] = false;
+ case sdl2::SDL_Scancode::FIVE =>
+ chip8.keys[0xC] = false;
+ case sdl2::SDL_Scancode::W =>
+ chip8.keys[0x4] = false;
+ case sdl2::SDL_Scancode::E =>
+ chip8.keys[0x5] = false;
+ case sdl2::SDL_Scancode::R =>
+ chip8.keys[0x6] = false;
+ case sdl2::SDL_Scancode::T =>
+ chip8.keys[0xD] = false;
+ case sdl2::SDL_Scancode::S =>
+ chip8.keys[0x7] = false;
+ case sdl2::SDL_Scancode::D =>
+ chip8.keys[0x8] = false;
+ case sdl2::SDL_Scancode::F =>
+ chip8.keys[0x9] = false;
+ case sdl2::SDL_Scancode::G =>
+ chip8.keys[0xE] = false;
+ case sdl2::SDL_Scancode::X =>
+ chip8.keys[0xA] = false;
+ case sdl2::SDL_Scancode::C =>
+ chip8.keys[0x0] = false;
+ case sdl2::SDL_Scancode::V =>
+ chip8.keys[0xB] = false;
+ case sdl2::SDL_Scancode::B =>
+ chip8.keys[0xF] = false;
+ case =>
+ void;
+ };
+ case => void;
+ };
+
+ const frame_start = sdl2::SDL_GetPerformanceCounter();
+
+ for (let i = 0; i < CLOCKSPEED / 60; i += 1) {
+ tick(&chip8, &random, debug);
+
+ if ((chip8.mem[chip8.PC - 2] >> 4) == 0x0D)
+ break;
+ };
+
+ const frame_end = sdl2::SDL_GetPerformanceCounter();
+
+ const time_elapsed = ((frame_end - frame_start) * 1000): f64 / sdl2::SDL_GetPerformanceFrequency(): f64;
+
+ sdl2::SDL_Delay(if (16.67 > time_elapsed) (16.67 - time_elapsed): u32 else 0);
+
+ if (chip8.draw) {
+ update_screen(&chip8, renderer)?;
+ chip8.draw = false;
+ };
+
+ tick_timers(&chip8);
+ };
+};
+
+export fn main() void = {
+ match (run()) {
+ case let err: fs::error =>
+ fmt::fatal("FS error: ", fs::strerror(err));
+ case let err: io::error =>
+ fmt::fatal("I/O error: ", io::strerror(err));
+ case let err: sdl2::error =>
+ fmt::fatal("SDL error: ", sdl2::strerror(err));
+ case void =>
+ return;
+ };
+};