import std.stdio; import derelict.sdl2.sdl; import derelict.imgui.imgui; import glad.gl.all; import window; import imgui; string doCapture(string sym, uint start, uint end)(){ import std.string : format; enum bits = end - start; static if (bits > 16) { alias RT = uint; } else static if (bits > 8) { alias RT = ushort; } else { alias RT = ubyte; } auto str = "0b"; foreach (i; 0 .. end) { if (i >= start) str ~= "1"; else str ~= "0"; } return format(q{ %s result = ((%s & %s) >> %d); return result; }, RT.stringof, sym, str, start); } // doCapture auto capture(uint start, uint end)(ushort op) { enum str = doCapture!(op.stringof, start, end)(); mixin(str); } // capture struct Chip8 { alias OpCode = ushort; alias ProgramCounter = ushort; alias Memory = ubyte[4096]; alias Stack = ushort[16]; alias Register = ubyte; alias Registers = Register[16]; alias IndexRegister = ushort; alias KeyPad = ubyte[16]; struct CPU { OpCode opcode = 0; //current opcode ProgramCounter pc = 0x200; Registers v; //16 general purpose registers IndexRegister i; Register delay_timer; Register sound_timer; } // CPU CPU cpu; Memory ram; Stack stack; ubyte sp; KeyPad kp; ubyte[64*32] screen_buf; ubyte[3][64*32] screen_data; bool run_flag; bool draw_flag; void load(size_t offset, in ubyte[] data) { assert(offset + data.length < ram.length); ram[offset .. offset + data.length] = data[]; } // load void step() { cpu.opcode = ram[cpu.pc] << 8 | ram[cpu.pc + 1]; auto pc_target = cast(ProgramCounter)(cpu.pc + 2); writefln("opcode: 0x%X", cpu.opcode); switch (cpu.opcode & 0xF000) with (cpu) { case 0x0000: switch (cpu.opcode & 0x0FFF) { case 0x00E0: // 0x00E0 Clears the screen. screen_buf[0..$] = 0; draw_flag = true; break; case 0x00EE: // 0x00EE Returns from a subroutine. pc_target = stack[--sp]; break; default: // 0x0NNN Calls RCA 1802 program at address NNN. Not necessary for most ROMs. //assert(0, "0x0NNN RCA 1802 program opcode not implemented!"); break; } break; case 0x1000: // 0x1NNN Jumps to address NNN. pc_target = cpu.opcode.capture!(0, 12); break; case 0x2000: // 0x2NNN Calls subroutine at NNN. stack[sp++] = cpu.pc; pc_target = cpu.opcode.capture!(0, 12); break; case 0x3000: // 0x3XNN Skips the next instruction if VX equals NN. if (cpu.v[cpu.opcode.capture!(8, 12)] == cpu.opcode.capture!(0, 8)) { pc_target += 2; } break; case 0x4000: // 0x4XNN Skips the next instruction if VX doesn't equal NN. if (cpu.v[cpu.opcode.capture!(8, 12)] != cpu.opcode.capture!(0, 8)) { pc_target += 2; } break; case 0x5000: // 0x5XYO Skips the next instruction if VX equals VY. if (cpu.v[cpu.opcode.capture!(8, 12)] == cpu.v[cpu.opcode.capture!(4, 8)]) { pc_target += 2; } break; case 0x6000: // 0x6XNN Sets VX to NN. cpu.v[cpu.opcode.capture!(8, 12)] = cpu.opcode.capture!(0, 8); break; case 0x7000: // 0x7XNN Adds NN to VX. cpu.v[cpu.opcode.capture!(8, 12)] += cpu.opcode.capture!(0, 8); break; case 0x8000: auto x = cpu.opcode.capture!(8, 12); auto y = cpu.opcode.capture!(4, 8); switch (cpu.opcode & 0x000F) { case 0x0000: // 0x8XY0 Sets VX to the value of VY. cpu.v[x] = cpu.v[y]; break; case 0x0001: // 0x8XY1 Sets VX to VX or VY. cpu.v[x] = cpu.v[x] | cpu.v[y]; break; case 0x0002: // 0x8XY2 Sets VX to VX and VY. cpu.v[x] = cpu.v[x] & cpu.v[y]; break; case 0x0003: // 0x8XY3 Sets VX to VX xor VY. cpu.v[x] = cpu.v[x] ^ cpu.v[y]; break; case 0x0004: // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't. cpu.v[x] += cpu.v[y]; //TODO carry flag break; case 0x0005: // 0x8XY5 VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't. cpu.v[x] -= cpu.v[y]; //TODO borrow flag break; case 0x0006: // 0x8XY6 Shifts VX right by one. VF is set to the value of the least significant bit of VX before the shift. auto vx = cpu.v[x]; cpu.v[0xF] = (vx & 0b10000000) >> 7; cpu.v[x] >>= 1; break; case 0x0007: // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't. cpu.v[x] = cast(Register)(cpu.v[y] - cpu.v[x]); //TODO borrow flag break; case 0x000E: // 0x8XYE Shifts VX left by one. VF is set to the value of the most significant bit of VX before the shift. auto vx = cpu.v[x]; cpu.v[0xF] = (vx & 0b10000000) >> 7; cpu.v[x] <<= 1; break; default: //unhandled for some reason writefln("unknown opcode: 0x%x", cpu.opcode); break; } break; case 0x9000: // 0x9XYO Skips the next instruction if VX doesn't equal VY. if (cpu.v[cpu.opcode.capture!(8, 12)] != cpu.v[cpu.opcode.capture!(4, 8)]) { pc_target += 2; //do skip yes } break; case 0xA000: // 0xANNN Sets I to the address NNN. cpu.i = cpu.opcode.capture!(0, 12); break; case 0xB000: // 0xBNNN Jumps to the address NNN plus V0. pc_target = cast(ubyte)cpu.opcode.capture!(0, 12) + cpu.v[0x0]; break; case 0xC000: // 0xCXNN Sets VX to the result of a bitwise and operation on a random number and NN. import std.random : uniform; auto x = cpu.opcode.capture!(8, 12); cpu.v[x] = uniform(Register.min, Register.max) & cpu.opcode.capture!(0, 8); break; // 0xDXYN // Sprites stored in memory at location in index register (I), 8bits wide. // Wraps around the screen. If when drawn, clears a pixel, register VF is set to 1 otherwise it is zero. // All drawing is XOR drawing (i.e. it toggles the screen pixels). // Sprites are drawn starting at position VX, VY. N is the number of 8bit rows that need to be drawn. // If N is greater than 1, second line continues at position VX, VY+1, and so on. case 0xD000: auto spr_addr = cpu.i; auto x = cpu.opcode.capture!(8, 12); auto y = cpu.opcode.capture!(4, 8); auto n = cpu.opcode.capture!(0, 4); ushort pixel; foreach(int row; 0 .. n) { pixel = ram[spr_addr + row]; foreach (int col; 0 .. 8) { if ((pixel & (0x80 >> col)) != 0) { if (screen_buf[(x + col + ((y + row) * 64))] == 1) { cpu.v[0xF] = 1; } screen_buf[x + row + ((y + col) * 64)] ^= 1; } } } draw_flag = true; break; case 0xE000: auto x = cpu.opcode.capture!(8, 12); auto key = cpu.v[x]; switch (cpu.opcode & 0x000F) { case 0x000E: // 0xEX9E Skips the next instruction if the key stored in VX is pressed. writefln("0xEXA1: skip instruction if VX not pressed: %x", key); break; case 0x0001: // 0xEXA1 Skips the next instruction if the key stored in VX isn't pressed. writefln("0xEXA1: skip instruction if VX not pressed: %x", key); break; default: //unhandled for some reason writefln("unknown opcode: 0x%x", cpu.opcode); break; } break; case 0xF000: switch (cpu.opcode & 0x00FF) { case 0x0007: // 0xFX07 Sets VX to the value of the delay timer. cpu.v[cpu.opcode.capture!(8, 12)] = cpu.delay_timer; break; case 0x000A: // 0xFX0A A key press is awaited, and then stored in VX. break; case 0x0015: // 0xFX15 Sets the delay timer to VX. cpu.delay_timer = cpu.v[cpu.opcode.capture!(8, 12)]; break; case 0x0018: // 0xFX18 Sets the sound timer to VX. cpu.sound_timer = cpu.v[cpu.opcode.capture!(8, 12)]; break; case 0x001E: // 0xFX1E Adds VX to I. cpu.i += cpu.v[cpu.opcode.capture!(8, 12)]; break; case 0x0029: // 0xFX29 Sets I to the location of the sprite for the character in VX. auto vx = cpu.v[cpu.opcode.capture!(8, 12)]; ushort char_addr = 0x200 + (vx * 40); // base of char sprites + value of vx * bits per character cpu.i = char_addr; break; // 0xFX33 Stores the Binary-coded decimal representation of VX, // with the most significant of three digits at the address in I, // the middle digit at I plus 1, and the least significant digit at I plus 2. case 0x0033: auto vx = cpu.v[cpu.opcode.capture!(8, 12)]; ram[cpu.i] = vx / 100; ram[cpu.i + 1] = (vx / 10) % 10; ram[cpu.i + 2] = (vx % 100) % 10; break; case 0x0055: // 0xFX55 Stores V0 to VX in memory starting at address I. auto addr = cpu.i; foreach (reg; cpu.v) { ram[addr++] = reg; } break; case 0x0065: // 0xFX65 Fills V0 to VX with values from memory starting at address I. auto addr = cpu.i; foreach (ref reg; cpu.v) { reg = ram[addr++]; } break; default: //unhandled for some reason writefln("unknown opcode: 0x%x", cpu.opcode); break; } break; default: writefln("unknown opcode: 0x%x", cpu.opcode); } /* now update pc and timer registers. */ cpu.pc = pc_target; if (cpu.delay_timer != 0u) { --cpu.delay_timer; } if (cpu.sound_timer != 0u) { if (cpu.sound_timer == 1) { writefln("beep!"); } --cpu.sound_timer; } } // step void handle_event(ref SDL_Event ev) { } // handle_event void tick() { if (run_flag) { step(); } } // tick void draw() { if (draw_flag) { // update buffer with new pixel data draw_flag = false; } // glPixelZoom(rt.width / 64, rt.height / 32); // glDrawPixels(64, 32, GL_RGB, GL_UNSIGNED_BYTE, screen_data.ptr); } // draw } // Emulator void load_libs() { import glad.gl.loader; DerelictSDL2.load(); DerelictImgui.load(); auto status = gladLoadGL(); } void init_libs() { SDL_Init(SDL_INIT_VIDEO); } void setup_imgui() { } void main() { load_libs(); init_libs(); Emulator emu; emu.create(); emu.run(); } struct Emulator { bool running; Window window; Chip8 chip8; void create() { // create window window.create_window(640, 480); } void handle_events() { SDL_Event event; while (SDL_PollEvent(&event)) { chip8.handle_event(event); switch (event.type) with (SDL_EventType) { case SDL_QUIT: { running = false; break; } default: { break; } } } } // handle_events void run() { running = true; while (running) { handle_events(); tick(); draw(); } } void tick() { } void draw() { } }