From 6a4ed63138b1418b7781ca53fdde12e47a1d3b45 Mon Sep 17 00:00:00 2001 From: Jason Hilder Date: Tue, 26 May 2026 07:50:34 +0200 Subject: [PATCH] Added instructions for flag test rom. --- src/machine/cpu.odin | 181 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 146 insertions(+), 35 deletions(-) diff --git a/src/machine/cpu.odin b/src/machine/cpu.odin index 1dfe2ce..87d89bd 100644 --- a/src/machine/cpu.odin +++ b/src/machine/cpu.odin @@ -1,6 +1,7 @@ package machine import "core:log" +import "base:intrinsics" // CPU.odin -> instructions, fetch, decode, execute, run loop @@ -15,35 +16,50 @@ cycle :: proc(s: ^System) { // Pre-decode common components of opcodes first_nibble := (opcode & 0xF000) >> 12 - vx_idx := (opcode & 0x0F00) >> 8 - vy_idx := (opcode & 0x00F0) >> 4 - last_nibble := (opcode & 0x000F) - kk := u8(opcode & 0x00FF) - nnn := (opcode & 0x0FFF) + vx_idx := (opcode & 0x0F00) >> 8 // A 4-bit value, the lower 4 bits of the high byte of the instruction + vy_idx := (opcode & 0x00F0) >> 4 // A 4-bit value, the upper 4 bits of the low byte of the instruction + last_nibble := (opcode & 0x000F) // A 4-bit value, the lowest 4 bits of the instruction + kk := u8(opcode & 0x00FF) // An 8-bit value, the lowest 8 bits of the instruction + nnn := (opcode & 0x0FFF) // A 12-bit value, the lowest 12 bits of the instruction // Decode + Execute switch first_nibble { - case 0x0: - switch opcode { - case 0x00E0: op_cls(s) - case 0x00EE: op_ret(s) + case 0x0: + switch opcode { + case 0x00E0: op_cls(s) + case 0x00EE: op_ret(s) + } + case 0x1: op_jp_addr(s, nnn) + case 0x2: op_call_addr(s, nnn) + case 0x3: op_skip_on_condition(s, vx_idx, kk, true) + case 0x4: op_skip_on_condition(s, vx_idx, kk, false) + case 0x5: op_skip_reg_condition(s, vx_idx, vy_idx, true) + case 0x6: op_ld(s, vx_idx, kk) + case 0x7: op_add(s, vx_idx, kk) + case 0x8: + switch last_nibble { + case 0x0: op_alu_ld(s, vx_idx, vy_idx) + case 0x1: op_alu_or(s, vx_idx, vy_idx) + case 0x2: op_alu_and(s, vx_idx, vy_idx) + case 0x3: op_alu_xor(s, vx_idx, vy_idx) + case 0x4: op_alu_add(s, vx_idx, vy_idx) + case 0x5: op_alu_sub(s, vx_idx, vy_idx) + case 0x6: op_alu_shr(s, vx_idx, vy_idx) + case 0x7: op_alu_subn(s, vx_idx, vy_idx) + case 0xE: op_alu_shl(s, vx_idx, vy_idx) + } + case 0x9: op_skip_reg_condition(s, vx_idx, vy_idx, false) + case 0xA: op_set(s, nnn) + case 0xB: op_jp_offset(s, nnn) + case 0xD: op_draw(s, vx_idx, vy_idx, last_nibble) + case 0xF: { + switch kk { + case 0x1E: op_add_i(s, vx_idx) + case 0x33: op_split_i(s, vx_idx) + case 0x55: op_mem_set(s, vx_idx) + case 0x65: op_mem_get(s, vx_idx) + } } - case 0x1: op_jp_addr(s, nnn) - case 0x2: op_call_addr(s, nnn) - case 0x3: op_skip_on_condition(s, vx_idx, kk, true) - case 0x4: op_skip_on_condition(s, vx_idx, kk, false) - case 0x5: op_skip_reg_condition(s, vx_idx, vy_idx, true) - case 0x6: op_ld(s, vx_idx, kk) - case 0x7: op_add(s, vx_idx, kk) - case 0x8: - switch last_nibble { - case 0x0: op_alu_ld(s, vx_idx, vy_idx) - // case 0x1: op_alu_or(s, vx_idx, vy_idx) - } - case 0x9: op_skip_reg_condition(s, vx_idx, vy_idx, false) - case 0xA: op_set(s, nnn) - case 0xB: op_jp_offset(s, nnn) - case 0xD: op_draw(s, vx_idx, vy_idx, last_nibble) } } @@ -97,13 +113,6 @@ op_skip_reg_condition :: proc(s: ^System, vx_idx, vy_idx: u16, condition: bool) } } -// Bnnn - JP V0, addr -// @TODO: add configurable quirk for chip-48 -op_jp_offset :: proc(s: ^System, nnn: u16) { - s.pc = nnn - s.pc += u16(s.v[0]) -} - // 6xkk - LD Vx, byte op_ld :: proc(s: ^System, vx_idx: u16, kk: u8) { s.v[vx_idx] = kk @@ -119,11 +128,74 @@ op_alu_ld :: proc(s: ^System, vx_idx, vy_idx: u16) { s.v[vx_idx] = s.v[vy_idx] } +// 8xy1 - OR Vx, Vy +op_alu_or :: proc(s: ^System, vx_idx, vy_idx: u16) { + s.v[vx_idx] = (s.v[vx_idx] | s.v[vy_idx]) +} + +// 8xy2 - AND Vx, Vy +op_alu_and :: proc(s: ^System, vx_idx, vy_idx: u16) { + s.v[vx_idx] = (s.v[vx_idx] & s.v[vy_idx]) +} + +// 8xy3 - XOR Vx, Vy +op_alu_xor :: proc(s: ^System, vx_idx, vy_idx: u16) { + s.v[vx_idx] = (s.v[vx_idx] ~ s.v[vy_idx]) +} + +// 8xy4 - ADD Vx, Vy +op_alu_add :: proc(s: ^System, vx_idx, vy_idx: u16) { + // Cool feature of odin, intrinsics to check for overflows + result, overflowed := intrinsics.overflow_add(s.v[vx_idx], s.v[vy_idx]) + s.v[vx_idx] = result + s.v[0xF] = u8(1) if overflowed else u8(0) +} + +// 8xy5 - SUB Vx, Vy +op_alu_sub :: proc(s: ^System, vx_idx, vy_idx: u16) { + no_borrow := s.v[vx_idx] >= s.v[vy_idx] + s.v[vx_idx] = (s.v[vx_idx] - s.v[vy_idx]) + s.v[0xF] = u8(1) if no_borrow else u8(0) +} + +// 8xy6 - SHR Vx, Vy +op_alu_shr :: proc(s: ^System, vx_idx, vy_idx: u16) { + lsb := s.v[vx_idx] & 0x1 + // divide by 2 + s.v[vx_idx] = (s.v[vx_idx] >> 1) + // if last bit is 1 set F register + s.v[0xF] = lsb +} + +// 8xy7 - SUBN Vx, Vy +op_alu_subn :: proc(s: ^System, vx_idx, vy_idx: u16) { + no_borrow := s.v[vy_idx] >= s.v[vx_idx] + s.v[vx_idx] = (s.v[vy_idx] - s.v[vx_idx]) + s.v[0xF] = u8(1) if no_borrow else u8(0) +} + +// 8xyE - SHL Vx, Vy +op_alu_shl :: proc(s: ^System, vx_idx, vy_idx: u16) { + msb := (s.v[vx_idx] & 0x80) >> 7 + // multiply by 2 + s.v[vx_idx] = (s.v[vx_idx] << 1) + s.v[0xF] = msb +} + // Annn - LD I, addr op_set :: proc(s: ^System, nnn: u16) { s.i = nnn } +// Bnnn - JP V0, addr +// @TODO: add configurable quirk for chip-48 +op_jp_offset :: proc(s: ^System, nnn: u16) { + s.pc = nnn + s.pc += u16(s.v[0]) +} + +// Cxkk - RND Vx, byte : @TODO! + // Dxyn - DRW Vx, Vy, nibble op_draw :: proc(s: ^System, vx_idx, vy_idx, n: u16) { // Read positions and apply bounds masks (64 wide, 32 tall) @@ -149,14 +221,53 @@ op_draw :: proc(s: ^System, vx_idx, vy_idx, n: u16) { pixel := &s.display[pixel_y][pixel_x] + // Toggle pixel state via XOR + pixel^ ~= 1 + // If target pixel is already lit, we have a collision (XOR will turn it off) if pixel^ == 1 { s.v[0xF] = 1 } - - // Toggle pixel state via XOR - pixel^ ~= 1 } } } } + +// Ex9E - SKP Vx : @TODO +// ExA1 - SKNP Vx : @TODO +// Fx07 - LD Vx, DT : @TODO +// Fx0A - LD Vx, K : @TODO +// Fx15 - LD DT, Vx : @TODO +// Fx18 - LD ST, Vx : @TODO + +// Fx1E - ADD I, Vx +op_add_i :: proc(s: ^System, vx_idx: u16) { + s.i = s.i + u16(s.v[vx_idx]) + s.v[0xF] = u8(1) if s.i > 0xFFF else u8(0) +} + +// Fx29 - LD F, Vx : @TODO + +// Fx33 - LD B, Vx +op_split_i :: proc(s: ^System, vx_idx: u16) { + xreg_val := s.v[vx_idx] + s.memory[s.i] = (xreg_val / 100) + s.memory[s.i + 1] = ((xreg_val / 10) % 10) + s.memory[s.i + 2] = (xreg_val % 10) +} + +// Fx55/Fx65: I is used as a base address only, not modified during store/load. +// Modern behavior (CHIP48+): I unchanged after op. Original COSMAC VIP: I += X + 1. +// Fx55 - LD [I], Vx +op_mem_set :: proc(s: ^System, vx_idx: u16) { + for loop_idx in 0..=vx_idx { + s.memory[s.i + loop_idx] = s.v[loop_idx] + } +} + +// Fx65 - LD Vx, [I] +op_mem_get :: proc(s: ^System, vx_idx: u16) { + for loop_idx in 0..=vx_idx { + s.v[loop_idx] = s.memory[s.i + loop_idx] + } +}