diff options
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | src/cpu.effekt | 22 | ||||
-rw-r--r-- | src/main.effekt | 2 | ||||
-rw-r--r-- | src/renderers/js.effekt | 1 | ||||
-rw-r--r-- | src/test.effekt | 69 |
5 files changed, 88 insertions, 15 deletions
@@ -49,6 +49,15 @@ effekt src/main.effekt --backend js-web --includes . This will then produce a `main.html` and `main.js` file in the `out` directory. You can open the `main.html` file in your browser to run the emulator. Chip-8 ROMs can be found online and loaded into the emulator by clicking the "Load ROM" button in the GUI. +If you want to enable audio, you can do so by clicking the "Enable Audio" button in the GUI. + +## Keyboard Layout + +| 1 (1) | 2 (2) | 3 (3) | 4 (C) | +| ----- | ----- | ----- | ----- | +| Q (4) | W (5) | E (6) | R (D) | +| A (7) | S (8) | D (9) | F (E) | +| Z (A) | X (0) | C (B) | V (F) | ## Resources diff --git a/src/cpu.effekt b/src/cpu.effekt index c2a929e..bd441fd 100644 --- a/src/cpu.effekt +++ b/src/cpu.effekt @@ -17,7 +17,16 @@ CPU Cycle and Effects: 6. Wait for next cycle */ -extern io def getNow(): Int = jsWeb "Date.now()" +extern io def getNow(): Int = + js "Date.now()" + chez "(current-milliseconds)" + llvm """ + %time = call i64 @time(ptr null) + %ms = mul i64 %time, 1000 + ret i64 %ms + """ + vm "effekt::getNow()" + interface CPU { def initCPU(rom: ByteArray): Unit def cycleCPU(): Unit @@ -75,7 +84,6 @@ def makeCPU() {r: Renderer} = { val key_ = r.getKeyPressed().getOrElse { "P" } if (key_ != "P") { key = convertKey(key_) - r.log("Key pressed: " ++ show(key)) } last_key_update_time = currentTime } @@ -84,8 +92,6 @@ def makeCPU() {r: Renderer} = { val h = ram.getAddr(pc) val l = ram.getAddr(pc + 1) val inst = bitwiseOr(bitwiseShl(h.toInt(), 8), l.toInt()) - r.log("PC: " ++ show(pc)) - r.log("Instruction: " ++ show(inst)) // Decode val opcode = bitwiseShr(bitwiseAnd(inst, 61440), 12) @@ -184,11 +190,9 @@ def makeCPU() {r: Renderer} = { val vx = v.unsafeGet(x).toInt() val vy = v.unsafeGet(y).toInt() val sub = vx - vy - r.log(show(vx) ++ "(" ++ show(x) ++ ") - " ++ show(vy) ++ "(" ++ show(y) ++ ") = " ++ show(sub) ++ "with borrow of " ++ show(if (vx > vy) 1 else 0)) + v.unsafeSet(x, sub.toByte()) v.unsafeSet(15, (if (vx > vy) 1 else 0).toByte()) - r.log("vx is now " ++ show(v.unsafeGet(x))) - r.log("vF is now " ++ show(v.unsafeGet(15))) } // Set VX = VX SHR 1 and VF = LSB of VX case 6 => { @@ -203,11 +207,9 @@ def makeCPU() {r: Renderer} = { val vx = v.unsafeGet(x).toInt() val vy = v.unsafeGet(y).toInt() val sub = vy - vx - r.log(show(vy) ++ "(" ++ show(y) ++ ") - " ++ show(vx) ++ "(" ++ show(x) ++ ") = " ++ show(sub) ++ "with borrow of " ++ show(if (vy > vx) 1 else 0)) + v.unsafeSet(x, sub.toByte()) v.unsafeSet(15, (if (vy > vx) 1 else 0).toByte()) - r.log("vx is now " ++ show(v.unsafeGet(x))) - r.log("vF is now " ++ show(v.unsafeGet(15))) } // Set VX = VX SHL 1 and VF = MSB of VX case 14 => { diff --git a/src/main.effekt b/src/main.effekt index befe021..fd9f7c0 100644 --- a/src/main.effekt +++ b/src/main.effekt @@ -1,4 +1,4 @@ -module main // must be named same as the file! +module main import src/cpu import src/renderers/js diff --git a/src/renderers/js.effekt b/src/renderers/js.effekt index 3292481..f1da8e6 100644 --- a/src/renderers/js.effekt +++ b/src/renderers/js.effekt @@ -23,6 +23,7 @@ extern jsWeb """ function log(msg) { const message = new Date().toLocaleTimeString() + ' - ' + msg + ' \n' console.log(message); + document.getElementById('logs').textContent += message; } function romCheck() { diff --git a/src/test.effekt b/src/test.effekt index f605d19..7048bab 100644 --- a/src/test.effekt +++ b/src/test.effekt @@ -1,10 +1,71 @@ module src/test import test -import src/lib +import src/cpu +import src/renderer +import bytearray +import array -def main() = mainSuite("lib") { - test("Hello world") { - assertEqual("Hello, world!", "Hello, world!") +// Mock renderer for testing +def makeMockRenderer() = { + var screen: Array[Bool] = allocate(32 *64) + var lastKey: String = "P" + var beeping: Bool = false + new Renderer { + def init(run: (ByteArray) => Unit at {io, global}) = () + def clear() = screen = allocate(32 *64) + def draw(x: Int, y: Int, color: String) = { + screen.get(y).set(x, color == "white") + } + def fill(color: String) = { + screen = allocate(32 *64, color == "white") + } + def get(x: Int, y: Int): Bool = screen(y)(x) + def update(f: () => Unit at {io, global}) = () + def log(msg: String) = () + def getKeyPressed() = Some(lastKey) + def beep() = { beeping = true } + def stopBeep() = { beeping = false } } } +def main() = mainSuite("CHIP-8") { + // CPU Tests + + test("Key conversion") { + assertEqual(convertKey("1"), 1) + assertEqual(convertKey("2"), 2) + assertEqual(convertKey("v"), 15) + assertEqual(convertKey("invalid"), -1) + + test("CPU initialization") { + val renderer = makeMockRenderer() + val cpu = makeCPU() { renderer } + val testRom = allocate(10) + cpu.initCPU(testRom) + // Add assertions here for initial state + } + + test("Basic instructions") { + val renderer = makeMockRenderer() + val cpu = makeCPU() { renderer } + val testRom = allocate(4) + // Set up a simple instruction in ROM + testRom.unsafeSet(0, 96) // 0x60 + testRom.unsafeSet(1, 66) // 0x42 + cpu.initCPU(testRom) + cpu.cycleCPU() + + } + } + + // Renderer Tests + + test("Screen operations") { + val renderer = makeMockRenderer() + renderer.clear() + renderer.draw(0, 0, "white") + assert(renderer.get(0, 0)) + renderer.draw(0, 0, "black") + assert(renderer.get(0, 0), false) + } +}
\ No newline at end of file |