diff --git a/src/machine/cpu.odin b/src/machine/cpu.odin index aabfab7..339c32e 100644 --- a/src/machine/cpu.odin +++ b/src/machine/cpu.odin @@ -30,10 +30,19 @@ cycle :: proc(s: ^System) { case 0x0: switch opcode { case 0x00E0: op_cls(s) + case 0x00EE: op_ret(s, opcode) } case 0x1: op_jp_add(s, opcode) + case 0x2: op_call_add(s, opcode) + case 0x3: op_skip_on_condition(s, opcode, true) + case 0x4: op_skip_on_condition(s, opcode, false) + case 0x5: op_skip_reg_condition(s, opcode, true) case 0x6: op_ld(s, opcode) case 0x7: op_add(s, opcode) + + // todo 0x8 with last bit for ALU + + case 0x9: op_skip_reg_condition(s, opcode, false) case 0xA: op_set(s, opcode) case 0xD: op_draw(s, opcode) } @@ -54,12 +63,68 @@ op_cls :: proc(s: ^System) { s.display = {} } +// RET: Return from a subroutine. +op_ret :: proc(s: ^System, opcode: u16) { + // decrement sp to the previous used slot + // current sp will always be empty + s.sp -= 1 + + // restore the pc from where we called from + s.pc = s.stack[s.sp] +} + // JP addr: Jump to location nnn op_jp_add :: proc(s: ^System, opcode: u16) { addr := (opcode & 0x0FFF) s.pc = addr } +// Call addr: Call subroutine at nnn. +op_call_add :: proc(s: ^System, opcode: u16) { + addr := (opcode & 0x0FFF) + + // Save current pc to stack for return location + s.stack[s.sp] = s.pc + // advance sp for next free slot + s.sp += 1 + + s.pc = addr +} + +// SE: Skip next instruction if Vx = kk. +// SNE: Skip next instruction if Vx != kk. +op_skip_on_condition :: proc(s: ^System, opcode: u16, condition: bool) { + // Extract the register indices to get values + vx_index := (opcode & 0x0F00) >> 8 + register_val := s.v[vx_index] + + // Extract value to compare + op_val := u8(opcode & 0x00FF) + + if (register_val == op_val) == condition { + s.pc += 2 + } +} + +// SE Vx, Vy: Skip next instruction if Vx = Vy +// SNE Vx, Vy: Skip next instruction if Vx != Vy. +op_skip_reg_condition :: proc(s: ^System, opcode: u16, condition: bool) { + // Extract the register indices + vx_index := (opcode & 0x0F00) >> 8 + vy_index := (opcode & 0x00F0) >> 4 + + // Read the actual value from the registers. + rx := s.v[vx_index] + ry := s.v[vy_index] + + if (rx == ry) == condition { + s.pc += 2 + } +} + + +// continue Bnnn + // LD: Load value kk into x register op_ld :: proc(s: ^System, opcode: u16) { register := (opcode & 0x0F00) >> 8 @@ -83,34 +148,44 @@ op_set :: proc(s: ^System, opcode: u16) { // DRAW: Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. op_draw :: proc(s: ^System, opcode: u16) { - x_register := (opcode & 0x0F00) >> 8 - y_register := (opcode & 0x00F0) >> 4 + // Extract the register indices that hold the sprite's X and Y screen position + vx_index := (opcode & 0x0F00) >> 8 + vy_index := (opcode & 0x00F0) >> 4 - // Handle wrapping - x_coord := (s.v[x_register] & 0x03F) - y_coord := (s.v[y_register] & 0x1F) + // Read the actual coordinates from those registers. + // Wrap to screen bounds: 64 wide (0x3F mask), 32 tall (0x1F mask) + screen_x := s.v[vx_index] & 0x3F + screen_y := s.v[vy_index] & 0x1F + // VF is the collision flag — clear it before drawing s.v[0xF] = 0 - n_bytes := (opcode & 0x000F) + // Number of rows in the sprite (1 byte per row, each byte = 8 pixels) + sprite_height := opcode & 0x000F - for row in 0..> u8(col) + // Shift a 1 from the MSB right to isolate the bit at this column + bit_mask := u8(0x80) >> u8(col) - if sprite_row & mask != 0 { - // this bit is set Xor the pixel - y_pos := ((y_coord + u8(row)) % 32) - x_pos := ((x_coord + u8(col)) % 64) + // Only draw if this bit in the sprite is set + if sprite_row_bits & bit_mask != 0 { + // Wrap the draw position if the sprite goes off-screen + pixel_x := (screen_x + u8(col)) % 64 + pixel_y := (screen_y + u8(row)) % 32 - pixel := &s.display[y_pos][x_pos] + pixel := &s.display[pixel_y][pixel_x] + + // CHIP-8 XORs pixels — if we're about to unset a lit pixel, that's a collision if pixel^ == 1 { s.v[0xF] = 1 } - pixel^ = pixel^ ~ 1 + + // XOR the pixel: toggles it on if off, off if on + pixel^ ~= 1 } } }