aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCan2025-01-20 11:34:53 +0100
committerCan2025-01-20 11:34:53 +0100
commit1d02ba022a44784277b3e5829c61d365afb869f7 (patch)
treed226dc839dace81fe4f95b51dce247f3b739e1c6
parentac4653ef25dce82ac95f035faa36ec3c71d72807 (diff)
feat: timers
-rw-r--r--src/cpu.effekt453
-rw-r--r--src/renderers/js.effekt15
2 files changed, 249 insertions, 219 deletions
diff --git a/src/cpu.effekt b/src/cpu.effekt
index acd6c79..4a18d22 100644
--- a/src/cpu.effekt
+++ b/src/cpu.effekt
@@ -42,6 +42,7 @@ def makeCPU() {r: Renderer} = {
var last_key_update_time in global = getNow()
var last_instruction_run_time in global = getNow()
var last_display_update_time in global = getNow()
+ var last_timer_update in global = getNow()
new CPU {
def initCPU(rom: ByteArray) = {
@@ -49,253 +50,271 @@ def makeCPU() {r: Renderer} = {
r.fill("black")
}
def cycleCPU() = {
- 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))
+ val currentTime = getNow()
+ if (currentTime - last_instruction_run_time >= 2) { // ~500Hz
+ 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)
- val nnn = bitwiseAnd(inst, 4095)
- val nn = bitwiseAnd(inst, 255)
- val n = bitwiseAnd(inst, 15)
- val x = bitwiseAnd(bitwiseShr(inst, 8), 15)
- val y = bitwiseAnd(bitwiseShr(inst, 4), 15)
+ // Decode
+ val opcode = bitwiseShr(bitwiseAnd(inst, 61440), 12)
+ val nnn = bitwiseAnd(inst, 4095)
+ val nn = bitwiseAnd(inst, 255)
+ val n = bitwiseAnd(inst, 15)
+ val x = bitwiseAnd(bitwiseShr(inst, 8), 15)
+ val y = bitwiseAnd(bitwiseShr(inst, 4), 15)
- // Execute
- opcode match {
- case 0 => {
- nn match {
- // Clear the screen
- case 224 => {
- r.clear()
- r.log("Clearing the screen...")
+ // Execute
+ opcode match {
+ case 0 => {
+ nn match {
+ // Clear the screen
+ case 224 => {
+ r.clear()
+ r.log("Clearing the screen...")
+ pc = pc + 2
+ }
+ // Return from a subroutine
+ case 238 => {
+ // pc = stack[Int].popFront[Int]().value[Int]()
+ pc = stack.popFront().getOrElse { 0 }
+ }
+ } else {
+ r.log("Unknown Instruction: " ++ show(inst))
+ }
+ }
+ // Jump to address NNN
+ case 1 => {
+ pc = nnn
+ }
+ // Call subroutine at NNN
+ case 2 => {
+ stack.pushFront(pc + 2)
+ pc = nnn
+ }
+ // Skip next instruction if VX == NN
+ case 3 => {
+ if (v.unsafeGet(x).toInt() == nn) {
pc = pc + 2
}
- // Return from a subroutine
- case 238 => {
- // pc = stack[Int].popFront[Int]().value[Int]()
- pc = stack.popFront().getOrElse { 0 }
+ pc = pc + 2
+ r.log("Skipping next instruction...")
+ }
+ // Skip next instruction if VX != NN
+ case 4 => {
+ if (v.unsafeGet(x).toInt() != nn) {
+ pc = pc + 2
}
- } else {
- r.log("Unknown Instruction: " ++ show(inst))
+ pc = pc + 2
}
- }
- // Jump to address NNN
- case 1 => {
- pc = nnn
- }
- // Call subroutine at NNN
- case 2 => {
- stack.pushFront(pc + 2)
- pc = nnn
- }
- // Skip next instruction if VX == NN
- case 3 => {
- if (v.unsafeGet(x).toInt() == nn) {
+ // Skip next instruction if VX == VY
+ case 5 => {
+ if (v.unsafeGet(x).toInt() == v.unsafeGet(y).toInt()) {
+ pc = pc + 2
+ }
pc = pc + 2
}
- pc = pc + 2
- }
- // Skip next instruction if VX != NN
- case 4 => {
- if (v.unsafeGet(x).toInt() != nn) {
+ // Set VX = NN
+ case 6 => {
+ v.set(x, nn)
pc = pc + 2
}
- pc = pc + 2
- }
- // Skip next instruction if VX == VY
- case 5 => {
- if (v.unsafeGet(x).toInt() == v.unsafeGet(y).toInt()) {
+ // Set VX = VX + NN
+ case 7 => {
+ v.set(x, v.unsafeGet(x).toInt() + nn)
pc = pc + 2
}
- pc = pc + 2
- }
- // Set VX = NN
- case 6 => {
- v.set(x, nn)
- pc = pc + 2
- }
- // Set VX = VX + NN
- case 7 => {
- v.set(x, v.unsafeGet(x).toInt() + nn)
- pc = pc + 2
- }
- case 8 => {
- n match {
- // Set VX = VY
- case 0 => {
- v.set(x, v.unsafeGet(y))
- }
- // Set VX = VX OR VY
- case 1 => {
- v.set(x, bitwiseOr(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()))
- }
- // Set VX = VX AND VY
- case 2 => {
- v.set(x, bitwiseAnd(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()))
- }
- // Set VX = VX XOR VY
- case 3 => {
- v.set(x, bitwiseXor(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()))
- }
- // Set VX = VX + VY, set VF = carry
- case 4 => {
- val sum = v.unsafeGet(x).toInt() + v.unsafeGet(y).toInt()
- v.set(15, if (sum > 255) 1 else 0)
- v.set(x, sum)
-
- }
- // Set VX = VX - VY, set VF = NOT borrow
- case 5 => {
- v.set(15, if (v.unsafeGet(x).toInt() > v.unsafeGet(y).toInt()) 1 else 0)
- v.set(x, v.unsafeGet(x).toInt() - v.unsafeGet(y).toInt())
- }
- // Set VX = VX SHR 1
- case 6 => {
- v.set(15, bitwiseAnd(v.unsafeGet(x).toInt(), 1))
- v.set(x, bitwiseShr(v.unsafeGet(x).toInt(), 1))
- }
- // Set VX = VY - VX, set VF = NOT borrow
- case 7 => {
- v.set(15, if (v.unsafeGet(y).toInt() > v.unsafeGet(x).toInt()) 1 else 0)
- v.set(x, v.unsafeGet(y).toInt() - v.unsafeGet(x).toInt())
+ case 8 => {
+ n match {
+ // Set VX = VY
+ case 0 => {
+ v.set(x, v.unsafeGet(y))
+ }
+ // Set VX = VX OR VY
+ case 1 => {
+ v.set(x, bitwiseOr(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()))
+ }
+ // Set VX = VX AND VY
+ case 2 => {
+ v.set(x, bitwiseAnd(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()))
+ }
+ // Set VX = VX XOR VY
+ case 3 => {
+ v.set(x, bitwiseXor(v.unsafeGet(x).toInt(), v.unsafeGet(y).toInt()))
+ }
+ // Set VX = VX + VY, set VF = carry
+ case 4 => {
+ r.log("Adding" ++ show(v.unsafeGet(x)) ++ " and " ++ show(v.unsafeGet(y)))
+ val sum = v.unsafeGet(x).toInt() + v.unsafeGet(y).toInt()
+ r.log("Sum: " ++ show(sum) ++ " Carry: " ++ show(if (sum > 255) 1 else 0))
+ v.set(15, if (sum > 255) 1 else 0)
+ v.set(x, sum.toByte())
+ }
+ // Set VX = VX - VY, set VF = NOT borrow
+ case 5 => {
+ v.set(15, if (v.unsafeGet(x).toInt() > v.unsafeGet(y).toInt()) 1 else 0)
+ v.set(x, v.unsafeGet(x).toInt() - v.unsafeGet(y).toInt())
+ }
+ // Set VX = VX SHR 1
+ case 6 => {
+ v.set(15, bitwiseAnd(v.unsafeGet(x).toInt(), 1))
+ v.set(x, bitwiseShr(v.unsafeGet(x).toInt(), 1))
+ }
+ // Set VX = VY - VX, set VF = NOT borrow
+ case 7 => {
+ v.set(15, if (v.unsafeGet(y).toInt() > v.unsafeGet(x).toInt()) 1 else 0)
+ v.set(x, v.unsafeGet(y).toInt() - v.unsafeGet(x).toInt())
+ }
+ // Set VX = VX SHL 1
+ case 14 => {
+ v.set(15, bitwiseAnd(v.unsafeGet(x).toInt(), 128))
+ v.set(x, bitwiseShl(v.unsafeGet(x).toInt(), 1))
+ }
+ } else {
+ r.log("Unknown Instruction: " ++ show(inst))
}
- // Set VX = VX SHL 1
- case 14 => {
- v.set(15, bitwiseAnd(v.unsafeGet(x).toInt(), 128))
- v.set(x, bitwiseShl(v.unsafeGet(x).toInt(), 1))
+ pc = pc + 2
+ }
+ // Skip next instruction if VX != VY
+ case 9 => {
+ if (v.unsafeGet(x).toInt() != v.unsafeGet(y).toInt()) {
+ pc = pc + 2
}
- } else {
- r.log("Unknown Instruction: " ++ show(inst))
+ pc = pc + 2
}
- pc = pc + 2
- }
- // Skip next instruction if VX != VY
- case 9 => {
- if (v.unsafeGet(x).toInt() != v.unsafeGet(y).toInt()) {
+ // Set I = NNN
+ case 10 => {
+ i = nnn
pc = pc + 2
}
- pc = pc + 2
- }
- // Set I = NNN
- case 10 => {
- i = nnn
- pc = pc + 2
- }
- // Jump to location NNN + V0
- case 11 => {
- pc = nnn + v.unsafeGet(0).toInt()
- }
+ // Jump to location NNN + V0
+ case 11 => {
+ pc = nnn + v.unsafeGet(0).toInt()
+ }
- // Set VX = random byte AND NN
- case 12 => {
- val randInt: Int = floor(random() * 255.0)
- v.set(x, bitwiseAnd(randInt, nn))
- pc = pc + 2
- }
- // Display n-byte sprite starting at memory location I at (VX, VY), set VF = collision
- case 13 => {
- val vx = v.unsafeGet(x).toInt()
- val vy = v.unsafeGet(y).toInt()
- var collision = false
- var yline = 0
- while (yline < n) {
- val pixel = ram.getAddr(i + yline)
- var xline = 0
- while (xline < 8) {
- if (bitwiseAnd(pixel.toInt(), bitwiseShr(128, xline)) != 0) {
- if (r.get(vx + xline, vy + yline)) {
- collision = true
+ // Set VX = random byte AND NN
+ case 12 => {
+ val randInt: Int = floor(random() * 255.0)
+ v.set(x, bitwiseAnd(randInt, nn))
+ pc = pc + 2
+ }
+ // Display n-byte sprite starting at memory location I at (VX, VY), set VF = collision
+ case 13 => {
+ val vx = v.unsafeGet(x).toInt()
+ val vy = v.unsafeGet(y).toInt()
+ var collision = false
+ var yline = 0
+ while (yline < n) {
+ val pixel = ram.getAddr(i + yline)
+ var xline = 0
+ while (xline < 8) {
+ if (bitwiseAnd(pixel.toInt(), bitwiseShr(128, xline)) != 0) {
+ if (r.get(vx + xline, vy + yline)) {
+ collision = true
+ }
+ r.draw(vx + xline, vy + yline)
+ // r.log("Drawing at: " ++ show(vx + xline) ++ ", " ++ show(vy + yline))
}
- r.draw(vx + xline, vy + yline)
- r.log("Drawing at: " ++ show(vx + xline) ++ ", " ++ show(vy + yline))
+ xline = xline + 1
}
- xline = xline + 1
+ yline = yline + 1
}
- yline = yline + 1
+ v.set(15, if (collision) 1 else 0)
+ pc = pc + 2
}
- v.set(15, if (collision) 1 else 0)
- pc = pc + 2
- }
- case 14 => {
- nn match {
- // Skip next instruction if key with the value of VX is pressed
- case 161 => {
- // TODO: missing keyboard input
- ()
- }
- // Skip next instruction if key with the value of VX is not pressed
- case 158 => {
- // TODO: missing keyboard input
- ()
+ case 14 => {
+ nn match {
+ // Skip next instruction if key with the value of VX is pressed
+ case 161 => {
+ // TODO: missing keyboard input
+ ()
+ }
+ // Skip next instruction if key with the value of VX is not pressed
+ case 158 => {
+ // TODO: missing keyboard input
+ ()
+ }
+ } else {
+ r.log("Unknown Instruction: " ++ show(inst))
}
- } else {
- r.log("Unknown Instruction: " ++ show(inst))
}
- }
- case 15 => {
- nn match {
- // Set VX = delay timer value
- case 7 => {
- v.set(x, delay)
- }
- // Wait for a key press, store the value of the key in VX
- case 10 => {
- // TODO: missing keyboard input
- ()
- }
- // SET delay timer = VX
- case 21 => {
- delay = v.unsafeGet(x)
- }
- // Set sound timer = VX
- case 24 => {
- sound = v.unsafeGet(x)
- }
- // SET I = I + VX
- case 30 => {
- i = i + v.unsafeGet(x).toInt()
- }
- // Set I = location of sprite for digit VX
- case 41 => {
- i = v.unsafeGet(x).toInt() * 5
- }
- // Store BCD representation of VX in memory locations I, I+1, and I+2
- case 51 => {
- val vx = v.unsafeGet(x).toInt()
- ram.setAddr(i, (vx / 100).toByte())
- ram.setAddr(i + 1, mod((vx / 10), 10).toByte())
- ram.setAddr(i + 2, mod(vx, 10).toByte())
- }
- // Store registers V0 through VX in memory starting at location I
- case 85 => {
- var index = 0
- while (index <= x) {
- ram.setAddr(i + index, v.unsafeGet(index))
- index = index + 1
+ case 15 => {
+ nn match {
+ // Set VX = delay timer value
+ case 7 => {
+ v.set(x, delay)
}
- i = i + x + 1
- }
- // Read registers V0 through VX from memory starting at location I
- case 101 => {
- var index = 0
- while (index <= x) {
- v.set(index, ram.getAddr(i + index))
- index = index + 1
+ // Wait for a key press, store the value of the key in VX
+ case 10 => {
+ // TODO: missing keyboard input
+ ()
+ }
+ // SET delay timer = VX
+ case 21 => {
+ delay = v.unsafeGet(x)
+ }
+ // Set sound timer = VX
+ case 24 => {
+ sound = v.unsafeGet(x)
+ }
+ // SET I = I + VX
+ case 30 => {
+ i = i + v.unsafeGet(x).toInt()
+ }
+ // Set I = location of sprite for digit VX
+ case 41 => {
+ i = v.unsafeGet(x).toInt() * 5
+ }
+ // Store BCD representation of VX in memory locations I, I+1, and I+2
+ case 51 => {
+ val vx = v.unsafeGet(x).toInt()
+ ram.setAddr(i, (vx / 100).toByte())
+ ram.setAddr(i + 1, mod((vx / 10), 10).toByte())
+ ram.setAddr(i + 2, mod(vx, 10).toByte())
}
- i = i + x + 1
+ // Store registers V0 through VX in memory starting at location I
+ case 85 => {
+ var index = 0
+ while (index <= x) {
+ ram.setAddr(i + index, v.unsafeGet(index))
+ index = index + 1
+ }
+ i = i + x + 1
+ }
+ // Read registers V0 through VX from memory starting at location I
+ case 101 => {
+ var index = 0
+ while (index <= x) {
+ v.set(index, ram.getAddr(i + index))
+ index = index + 1
+ }
+ i = i + x + 1
+ }
+ } else {
+ r.log("Unknown Instruction: " ++ show(inst))
}
- } else {
- r.log("Unknown Instruction: " ++ show(inst))
+ pc = pc + 2
+ }
+ } else {
+ r.log("Unknown Instruction: " ++ show(inst))
+ }
+ last_instruction_run_time = getNow()
+
+ // Update timers if needed
+ if (currentTime - last_timer_update >= 16) { // 60Hz timer updates
+ // Update timers
+ if (delay.toInt() > 0) {
+ delay = (delay.toInt() - 1).toByte()
+ }
+ if (sound.toInt() > 0) {
+ sound = (sound.toInt() - 1).toByte()
}
- pc = pc + 2
+
+ last_timer_update = currentTime
}
- } else {
- r.log("Unknown Instruction: " ++ show(inst))
}
- last_instruction_run_time = getNow()
}
}
}
diff --git a/src/renderers/js.effekt b/src/renderers/js.effekt
index 080555d..ab7cc53 100644
--- a/src/renderers/js.effekt
+++ b/src/renderers/js.effekt
@@ -44,13 +44,24 @@ val pageContent = """
<input type="file" id="rom" accept=".ch8" />
<button id="start" disabled>Start</button>
<hr />
- <canvas id="canvas" width="640" height="320"></canvas>
+ <canvas id="canvas" width="640" height="320" style="border: 10px solid black;"></canvas>
<hr />
<div>
<h3>Logs:</h3>
<pre id="logs"></pre>
</div>
</body>
+ <style>
+ body {
+ font-family: Arial, sans-serif;
+ margin: 0;
+ padding: 0;
+ }
+ canvas {
+ display: block;
+ margin: 0 auto;
+ }
+ </style>
</html>
"""
@@ -112,7 +123,7 @@ extern io def get(x: Int, y: Int): Bool = jsWeb """
extern io def update(f: () => Unit at {io, global}): Unit = jsWeb """
setInterval(() => {
$effekt.runToplevel((ks) => ${f}(ks));
- }, 1000 / 60);
+ }, 1000 / 120);
"""
namespace JSRenderer {