Skip to content

Instantly share code, notes, and snippets.

@ericek111
Last active August 31, 2018 00:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericek111/d00a91b1293e9bffe4ae2945536ab474 to your computer and use it in GitHub Desktop.
Save ericek111/d00a91b1293e9bffe4ae2945536ab474 to your computer and use it in GitHub Desktop.
[Squirrel] CS:GO CHIP-8 emulator
// ent_fire first_script runscriptfile first_map/first.nut;ent_fire first_script callscriptfunction ButtonPressed
// http://devernay.free.fr/hacks/chip8/C8TECH10.HTM
::toHex <- function (val) {
return format("%04X", val);
}
DoIncludeScript("first_map/data.nut", this)
class CPU {
scope = null;
stack = null;
reg = null;
mach = null;
mem = null;
debug_disp = null;
shouldRun = true;
operand = 0;
opDesc = "";
constructor(scopeForThis, machine) {
mach = machine
mem = mach.mem
scope = scopeForThis
stack = array(16, 0)
reg = {
V = array(0x10, 0), // V0 - VF
I = 0, // index
PC = 0x200, // Program Counter
SP = 0, // Stack Pointer
DT = 0, // delay timer - wastes cycles by subtracting -1 if > 0
ST = 0 // sound timer - (both) decremented at 60 Hz, if > 0, buzzer sounds
}
}
function cycle() {
if (!shouldRun)
return;
// fetch operand
operand = mem[reg.PC] << 8 | mem[reg.PC + 1]
// move PC to next instruction
reg.PC += 2
// execute current instruction
exec()
if (reg.DT > 0)
reg.DT--
if (reg.ST > 0) {
reg.ST--
if (reg.ST == 0)
mach.playSound()
}
}
function exec() {
local x = (operand >> 8) & 0xF;
local y = (operand >> 4) & 0xF;
opDesc = format("db %02xh %02xh", operand >> 8, operand & 0xFF)
switch (operand >> 12) { // 0x0 - 0xF
case 0:
switch (operand & 0xFF) {
// CLS / 00E0 / Clear the display.
case 0xE0:
opDesc = "CLS"
mach.disp.clearScreen()
break;
// RET / 00EE / Return from a subroutine.
case 0xEE:
opDesc = "RET"
reg.PC = stack[reg.SP--]
break;
// SYS addr / 0nnn / Jump to a machine code routine at nnn.
default:
opDesc = format("SYS %xh", operand & 0xFFF)
// NOT IMPLEMENTED
}
break;
// JP addr / 1nnn / Jump to location nnn.
case 1:
opDesc = format("JP %xh", operand & 0xFFF)
reg.PC = operand & 0xFFF;
break;
// CALL addr / 2nnn / Call subroutine at nnn.
case 2:
opDesc = format("CALL %xh", operand & 0xFFF)
stack[reg.SP] = reg.PC
reg.SP++
reg.PC = operand & 0xFFF
break;
// SE Vx, kk / 2xkk / Skip next instruction if VX == kk.
case 3:
opDesc = format("SE V%X, %d", x, operand & 0xFF)
if (reg.V[x] == (operand & 0xFF))
reg.PC += 2
break;
// SNE Vx, kk / 3xkk / Skip next instruction if VX != kk.
case 4:
opDesc = format("SNE V%X, %d", x, operand & 0xFF)
if (reg.V[x] != (operand & 0xFF))
reg.PC += 2
break;
// SE Vx, Vy / 5xy0 / Skip next instruction if VX == VY.
case 5:
opDesc = format("SE V%X, V%X", x, y)
if (reg.V[x] == reg.V[y])
reg.PC += 2
break;
// LD Vx. kk / 6xkk / Set register Vx to value kk.
case 6:
opDesc = format("LD V%X, %d", x, operand & 0xFF)
reg.V[x] = operand & 0xFF
break;
// ADD Vx, kk / 7xkk / Increment Vx by kk.
case 7:
opDesc = format("ADD V%X, %d", x, operand & 0xFF)
reg.V[x] += operand & 0xFF
reg.V[x] = reg.V[x] & 0xFF
break;
case 8:
switch (operand & 0xF) {
// LD Vx, Vy / 8xy0 / Store the value of register Vy in Vx.
case 0:
opDesc = format("LD V%X, V%X", x, y)
reg.V[x] = reg.V[y]
break;
// OR Vx, Vy / 8xy1 / Perform a bitwise OR on regs Vx and Vy.
case 1:
opDesc = format("OR V%X, V%X", x, y)
reg.V[x] = reg.V[x] | reg.V[y]
break;
// AND Vx, Vy / 8xy2 / Perform a bitwise AND on regs Vx and Vy.
case 2:
opDesc = format("AND V%X, V%X", x, y)
reg.V[x] = reg.V[x] & reg.V[y]
break;
// XOR Vx, Vy / 8xy3 / Perform a bitwise XOR on regs Vx and Vy.
case 3:
opDesc = format("XOR V%X, V%X", x, y)
reg.V[x] = reg.V[x] ^ reg.V[y]
break;
// ADD Vx, Vy / 8xy4 / Add Vy to Vx, set VF = carry.
case 4:
opDesc = format("ADD V%X, V%X", x, y)
reg.V[x] += reg.V[y]
if (reg.V[x] > 0xFF)
reg.V[0xF] = 1
reg.V[x] = reg.V[x] & 0xFF
break;
// SUB Vx, Vy / 8xy5 / Subtract Vy from Vx, set VF = NOT borrow.
case 5:
opDesc = format("SUB V%X, V%X", x, y)
if (reg.V[x] > reg.V[y])
reg.V[0xF] = 1
reg.V[x] -= reg.V[y]
reg.V[x] = reg.V[x] & 0xFF
break;
// SHR Vx (, Vy) / 8xy6 / Shift Vx one bit right, put lowest bit into VF.
case 6:
opDesc = format("SHR V%X", x)
reg.V[0xF] = reg.V[x] & 1
reg.V[x] = reg.V[x] >>> 1
break;
// SUBN Vx, Vy / 8xy7 / Subtract Vx from Vy, store in Vx. VF = NOT borrow.
case 7:
opDesc = format("SUBN V%X, V%X", x, y)
if (reg.V[x] < reg.V[y])
reg.V[0xF] = 1
reg.V[x] = reg.V[y] - reg.V[x]
reg.V[x] = reg.V[x] & 0xFF
break;
// SHL Vx (, Vy) / 8xyE / Shift Vx one bit left, put highest bit into VF.
case 0xE:
opDesc = format("SHR V%X", x)
reg.V[0xF] = (reg.V[x] >> 7) & 1
reg.V[x] = reg.V[x] << 1
break;
default:
notRecognized(operand)
}
// SNE Vx, Vy / 9xy0 / Skip next instruction if Vx != Vy.
case 9:
opDesc = format("SNE V%X, V%X", x, y)
if (reg.V[x] != reg.V[y])
reg.PC += 2
break;
// LD I, nnn / Annn / Set I to nnn.
case 0xA:
opDesc = format("LD I, %d", operand & 0xFFF)
reg.I = operand & 0xFFF;
break;
// JP V0, addr / Bnnn / Jump to location V0 + nnn.
case 0xB:
opDesc = format("JP V0, %03X ", operand & 0xFFF)
reg.PC = reg.V[0] + operand & 0xFFF;
break;
// RND Vx, nn / Cxkk / Set register Vx to (random integer AND kk).
case 0xC:
opDesc = format("RND V%X, %02X", x, operand & 0xFF)
reg.V[x] = RandomInt(0, 255) & (operand & 0xFF)
break;
// DRW Vx, Vy, n / Dxyn / Display n-byte sprite from memory location I at (Vx, Vy), set VF = collision.
case 0xD:
opDesc = format("DRW V%X, V%X, %d", x, y, operand & 0xF)
reg.V[0xF] = 0
local s = 0
for (local py = 0; py < (operand & 0xF); py++) {
s = mem[reg.I + py];
for (local px = 0; px < 8; px++) {
if ((s & 0x80) > 0) {
if (mach.disp.togglePixel(reg.V[x] + px, reg.V[y] + py))
reg.V[0xF] = 1
}
s = s << 1;
}
}
mach.disp.drawFlag = true
break;
case 0xE:
switch (opcode & 0xFF) {
// SKP Vx / Ex9E / Skip next instruction if key with index of Vx is pressed.
case 0x9E:
opDesc = format("SKP V%X", x)
// TODO: Vx > 0xF handling
if (mach.input.key[reg.V[x]] > 0)
reg.PC += 2;
break;
// SKNP Vx / ExA1 / Skip next instruction if key with index of Vx is NOT pressed.
case 0xA1:
opDesc = format("SKNP V%X", x)
if (mach.input.key[reg.V[x]] == 0)
reg.PC += 2;
break;
default:
notRecognized(operand)
}
case 0xF:
switch (operand & 0xFF) {
// LD Vx, DT / Fx07 / Store the value of register DT in Vx
case 0x07:
opDesc = format("LD V%X, DT", x)
reg.V[x] = reg.DT;
break;
// LD Vx, K / Fx0A / Wait for a key press, store value of the key in Vx.
case 0x0A:
opDesc = format("LD V%X, K", x)
// TODO
break;
// LD DT, Vx / Fx15 / Set register DT to value of Vx.
case 0x15:
opDesc = format("LD DT, V%X", x)
reg.DT = reg.V[x]
break;
// LD ST, Vx / Fx18 / Set register ST to value of Vx.
case 0x18:
opDesc = format("LD ST, V%X", x)
reg.ST = reg.V[x]
break;
// ADD I, Vx / Fx1E / Add value of register Vx to I.
case 0x1E:
opDesc = format("ADD I, V%X", x)
reg.I += reg.V[x]
break;
// LD F, Vx / Fx29 / Set I = location of sprite for digit Vx.
case 0x29:
opDesc = format("LD F, V%X", x)
reg.I = reg.V[x] * 5
break;
// LD B, Vx / Fx33 / Store BCD representation of Vx in memory locations I, I+1, and I+2.
case 0x33:
opDesc = format("LD B, V%X", x)
local num = reg.V[x];
for (local i = 3; i > 0; i--) {
mem[reg.I + i - 1] = num % 10
num /= 10;
}
break;
// LD [I], Vx / Fx55 / Store registers V0 through Vx in memory starting at location I.
case 0x55:
opDesc = format("LD [I], V%X", x)
for (local i = 0; i <= x; i++) {
mem[reg.I + i] = reg.V[i]
}
break;
// LD Vx, [I] / Fx65 / Read registers V0 through Vx from memory starting at location I.
case 0x65:
opDesc = format("LD V%X, [I]", x)
for (local i = 0; i <= x; i++) {
reg.V[i] = mem[reg.I + i]
}
break;
default:
notRecognized(operand)
}
break;
default:
notRecognized(operand)
}
}
function dumpState() {
print(format("@ %03X: %02X %02X, ", reg.PC, operand >> 8, operand & 0xFF))
foreach (idx, val in reg.V) {
print(format("V%01X: %02X, ", idx, val))
}
print(format("I: %02X, ", reg.I))
print(format("PC: %03X, ", reg.PC))
print(format("SP: %d, ", reg.SP))
print(format("DT: %02X, ", reg.DT))
printl(format("ST: %02X", reg.ST))
}
function notRecognized(operand) {
shouldRun = false;
printl("Unknown instruction @ " + toHex(reg.PC) + ": " + toHex(operand) + " > " + opDesc)
dumpState()
}
}
class DebugRender {
scope = null;
mach = null;
debug_disp = null;
debug_regs = {
V = [
Entities.FindByName(null, "reg_V0"),
Entities.FindByName(null, "reg_V1"),
Entities.FindByName(null, "reg_V2"),
Entities.FindByName(null, "reg_V3"),
Entities.FindByName(null, "reg_V4"),
Entities.FindByName(null, "reg_V5"),
Entities.FindByName(null, "reg_V6"),
Entities.FindByName(null, "reg_V7"),
Entities.FindByName(null, "reg_V8"),
Entities.FindByName(null, "reg_V9"),
Entities.FindByName(null, "reg_VA"),
Entities.FindByName(null, "reg_VB"),
Entities.FindByName(null, "reg_VC"),
Entities.FindByName(null, "reg_VD"),
Entities.FindByName(null, "reg_VE"),
Entities.FindByName(null, "reg_VF")
],
I = Entities.FindByName(null, "reg_I"),
PC = Entities.FindByName(null, "reg_PC"),
SP = Entities.FindByName(null, "reg_SP"),
DT = Entities.FindByName(null, "reg_DT"),
ST = Entities.FindByName(null, "reg_ST"),
operand = Entities.FindByName(null, "reg_operand")
}
constructor(scopeForThis, machine) {
scope = scopeForThis
mach = machine
debug_disp = array(mach.disp.HEIGHT)
local e = Entities.FindByName(null, "disp_0");
for (local i = 0; i < mach.disp.HEIGHT - 1; i++) {
e = Entities.Next(e)
debug_disp[i] = e
}
debug_draw()
}
function debug_draw() {
for (local y = 0; y < mach.disp.HEIGHT; y++) {
if (debug_disp[y] == null)
break;
local line = "";
for (local x = 0; x < mach.disp.WIDTH; x++) {
line += mach.disp.buf[y * mach.disp.WIDTH + x] == 0 ? " " : "#";
}
debug_disp[y].__KeyValueFromString("message", line)
}
}
function draw() {
debug_draw()
}
function postCycle() {
debug_regs.operand.__KeyValueFromString("message", "op: " + mach.cpu.opDesc)
}
function preCycle() {
foreach (idx, value in debug_regs.V) {
value.__KeyValueFromString("message", "V" + format("%01X", idx) + ": " + format("%02X", mach.cpu.reg.V[idx]))
}
debug_regs.DT.__KeyValueFromString("message", "DT: " + format("%02X", mach.cpu.reg.DT))
debug_regs.ST.__KeyValueFromString("message", "ST: " + format("%02X", mach.cpu.reg.ST))
debug_regs.I.__KeyValueFromString("message", "I: " + format("%02X", mach.cpu.reg.I))
debug_regs.PC.__KeyValueFromString("message", "PC: " + format("%03X", mach.cpu.reg.PC))
debug_regs.SP.__KeyValueFromString("message", "SP: " + mach.cpu.reg.SP)
}
}
class Display {
buf = null;
scope = null;
mach = null;
WIDTH = 64;
HEIGHT = 32;
hexChars = [
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
];
drawFlag = false;
renderers = null;
constructor (scopeForThis, machine) {
scope = scopeForThis
mach = machine
buf = array(WIDTH * HEIGHT, 0)
for (local i = 0; i < hexChars.len(); i++) {
mach.mem[i] = hexChars[i]
}
renderers = []
}
function addRenderer(r) {
renderers.push(r)
}
function preCycle() {
foreach (r in renderers) {
r.preCycle()
}
}
function postCycle() {
foreach (r in renderers) {
r.postCycle()
}
if (!drawFlag)
return;
foreach (r in renderers) {
r.draw()
}
drawFlag = false;
}
function setPixel(x, y, val) {
// Wrap around screen if the location exceeds the dimensions
if (x > WIDTH)
x -= WIDTH
else if (x < 0)
x += WIDTH
if (y > HEIGHT)
y -= HEIGHT
else if (y < 0)
y += HEIGHT
buf[y * WIDTH + x] = val
}
function togglePixel(x, y) {
local loc = y * WIDTH + x
setPixel(x, y, buf[loc] ^ 1)
return buf[loc] == 0
}
function clearScreen() {
for (local i = 0; i < buf.len(); i++) {
buf[i] = 0;
}
}
function dump() {
for (local i = 0; i < buf.len(); i++) {
if (i % WIDTH == 0)
printl("")
print(buf[i] == 0 ? "_" : "#")
}
}
}
class Input {
key = null;
scope = null;
mach = null;
constructor(scopeForThis, machine) {
scope = scopeForThis
mach = machine
key = array(16, 0)
}
function set(idx, val) {
if (idx >= key.len()) {
printl("Invalid key: " + idx)
return
}
key[idx] = val;
}
function press(idx) {
key[idx] = 1;
}
function release(idx) {
key[idx] = 0;
}
}
class Machine {
mem = null;
disp = null;
input = null;
scope = null;
cpu = null;
constructor(scopeForThis, rom) {
scope = scopeForThis
printl("Power ON!")
mem = array(0x1000, 0)
cpu = scope.CPU(scope, this);
disp = scope.Display(scope, this)
input = scope.Input(scope, this);
disp.addRenderer(scope.DebugRender(scope, this));
for (local i = 0; i < rom.len(); i++) {
mem[0x200 + i] = rom[i]
}
}
function cycle() {
disp.preCycle();
cpu.cycle()
disp.postCycle();
}
function playSound() {
EmitSound("buttons/blip1")
}
function start() {
cpu.shouldRun = true
}
function stop() {
cpu.shouldRun = false
}
}
mach <- null
function Step() {
mach.cycle()
}
function Run() {
EntFire("emu_clock", "enable", "")
mach.start()
}
function Stop() {
EntFire("emu_clock", "disable", "")
mach.stop()
}
function Reset() {
mach = Machine(this, program_data)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment