Major clean before doing the rest of instructions.
After looking at the code, I noticed I was doing the same operations in the functions and most of the instructions require the same compoenents if not some so I split them out into a pre decode step and pass down.
This commit is contained in:
+82
-112
@@ -5,46 +5,45 @@ import "core:log"
|
||||
// CPU.odin -> instructions, fetch, decode, execute, run loop
|
||||
|
||||
cycle :: proc(s: ^System) {
|
||||
program_counter := s.pc
|
||||
|
||||
// Fetch
|
||||
// ---------------
|
||||
high_byte := s.memory[program_counter]
|
||||
low_byte := s.memory[program_counter + 1]
|
||||
|
||||
// advance past this instruction early for our next opcode
|
||||
high_byte := s.memory[s.pc]
|
||||
low_byte := s.memory[s.pc + 1]
|
||||
s.pc += 2
|
||||
|
||||
// Instructions are made of 2 bytes we combine the high and low byte
|
||||
// Get full opcode from the 2 bytes
|
||||
opcode := (u16(high_byte) << 8) | u16(low_byte)
|
||||
|
||||
// mask off everything except the first byte.
|
||||
// shift it 12 bits 'back' left with the first number
|
||||
kind := (opcode & 0xF000) >> 12
|
||||
// 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)
|
||||
|
||||
// Decode + Execute
|
||||
// 1. opcodes are broken up to get their particulars
|
||||
// 2. first nibble (4bits half byte) is the first hex number, this is the category
|
||||
// ---------------
|
||||
switch kind {
|
||||
switch first_nibble {
|
||||
case 0x0:
|
||||
switch opcode {
|
||||
case 0x00E0: op_cls(s)
|
||||
case 0x00EE: op_ret(s, opcode)
|
||||
case 0x00EE: op_ret(s)
|
||||
}
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,139 +51,110 @@ debug_opcode :: proc(opcode: u16) {
|
||||
log.debugf("opcode: 0x%04X", opcode)
|
||||
}
|
||||
|
||||
// Instruction procs
|
||||
// -----------------------------------
|
||||
// CLS: Clear the display
|
||||
op_cls :: proc(s: ^System) {
|
||||
// log.info("found clear screen instruction, zero out display buffer")
|
||||
// Instruction Implementations
|
||||
// -----------------------------------
|
||||
|
||||
// Odin lets you zero a fixed array with just: {} is the zero value,
|
||||
// so it clears the entire buffer in one shot.
|
||||
// 00E0 - CLS
|
||||
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
|
||||
// 00EE - RET
|
||||
op_ret :: proc(s: ^System) {
|
||||
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
|
||||
// 1nnn - JP addr
|
||||
op_jp_addr :: proc(s: ^System, nnn: u16) {
|
||||
s.pc = nnn
|
||||
}
|
||||
|
||||
// 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
|
||||
// 2nnn - CALL addr
|
||||
op_call_addr :: proc(s: ^System, nnn: u16) {
|
||||
s.stack[s.sp] = s.pc
|
||||
// advance sp for next free slot
|
||||
s.sp += 1
|
||||
|
||||
s.pc = addr
|
||||
s.pc = nnn
|
||||
}
|
||||
|
||||
// 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]
|
||||
// 3xkk - SE Vx, byte | 4xkk - SNE Vx, byte
|
||||
op_skip_on_condition :: proc(s: ^System, vx_idx: u16, kk: u8, condition: bool) {
|
||||
vx_val := s.v[vx_idx]
|
||||
|
||||
// Extract value to compare
|
||||
op_val := u8(opcode & 0x00FF)
|
||||
|
||||
if (register_val == op_val) == condition {
|
||||
if (vx_val == kk) == 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
|
||||
// 5xy0 - SE Vx, Vy | 9xy0 - SNE Vx, Vy
|
||||
op_skip_reg_condition :: proc(s: ^System, vx_idx, vy_idx: u16, condition: bool) {
|
||||
vx_val := s.v[vx_idx]
|
||||
vy_val := s.v[vy_idx]
|
||||
|
||||
// Read the actual value from the registers.
|
||||
rx := s.v[vx_index]
|
||||
ry := s.v[vy_index]
|
||||
|
||||
if (rx == ry) == condition {
|
||||
if (vx_val == vy_val) == condition {
|
||||
s.pc += 2
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// continue Bnnn
|
||||
|
||||
// LD: Load value kk into x register
|
||||
op_ld :: proc(s: ^System, opcode: u16) {
|
||||
register := (opcode & 0x0F00) >> 8
|
||||
value := (opcode & 0x00FF)
|
||||
|
||||
s.v[register] = u8(value)
|
||||
// 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])
|
||||
}
|
||||
|
||||
// ADD: Adds the value kk to the value of register Vx, then stores the result in Vx.
|
||||
op_add :: proc(s: ^System, opcode: u16) {
|
||||
register := (opcode & 0x0F00) >> 8
|
||||
value := (opcode & 0x00FF)
|
||||
s.v[register] += u8(value)
|
||||
// 6xkk - LD Vx, byte
|
||||
op_ld :: proc(s: ^System, vx_idx: u16, kk: u8) {
|
||||
s.v[vx_idx] = kk
|
||||
}
|
||||
|
||||
// SET: The value of register I is set to nnn.
|
||||
op_set :: proc(s: ^System, opcode: u16) {
|
||||
index_addr := (opcode & 0x0FFF)
|
||||
s.i = index_addr
|
||||
// 7xkk - ADD Vx, byte
|
||||
op_add :: proc(s: ^System, vx_idx: u16, kk: u8) {
|
||||
s.v[vx_idx] += kk
|
||||
}
|
||||
|
||||
// DRAW: Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.
|
||||
op_draw :: proc(s: ^System, opcode: u16) {
|
||||
// Extract the register indices that hold the sprite's X and Y screen position
|
||||
vx_index := (opcode & 0x0F00) >> 8
|
||||
vy_index := (opcode & 0x00F0) >> 4
|
||||
// 8xy0 - LD Vx, Vy
|
||||
op_alu_ld :: proc(s: ^System, vx_idx, vy_idx: u16) {
|
||||
s.v[vx_idx] = s.v[vy_idx]
|
||||
}
|
||||
|
||||
// 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
|
||||
// Annn - LD I, addr
|
||||
op_set :: proc(s: ^System, nnn: u16) {
|
||||
s.i = nnn
|
||||
}
|
||||
|
||||
// VF is the collision flag — clear it before drawing
|
||||
// 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)
|
||||
screen_x := s.v[vx_idx] & 0x3F
|
||||
screen_y := s.v[vy_idx] & 0x1F
|
||||
|
||||
// Clear collision flag (VF) before drawing
|
||||
s.v[0xF] = 0
|
||||
|
||||
// Number of rows in the sprite (1 byte per row, each byte = 8 pixels)
|
||||
sprite_height := opcode & 0x000F
|
||||
|
||||
for row in 0..<sprite_height {
|
||||
// Each byte in memory is one row of 8 pixels, MSB is the leftmost pixel
|
||||
for row in 0..<n {
|
||||
// Fetch current row byte from memory (each bit represents a horizontal pixel)
|
||||
sprite_row_bits := s.memory[s.i + row]
|
||||
|
||||
for col in 0..<8 {
|
||||
// Shift a 1 from the MSB right to isolate the bit at this column
|
||||
// Isolate individual bits from left (MSB) to right (LSB)
|
||||
bit_mask := u8(0x80) >> u8(col)
|
||||
|
||||
// Only draw if this bit in the sprite is set
|
||||
// Only process and draw if the sprite bit is active (1)
|
||||
if sprite_row_bits & bit_mask != 0 {
|
||||
// Wrap the draw position if the sprite goes off-screen
|
||||
// Wrap pixel coordinates if they go over screen bounds
|
||||
pixel_x := (screen_x + u8(col)) % 64
|
||||
pixel_y := (screen_y + u8(row)) % 32
|
||||
|
||||
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 target pixel is already lit, we have a collision (XOR will turn it off)
|
||||
if pixel^ == 1 {
|
||||
s.v[0xF] = 1
|
||||
}
|
||||
|
||||
// XOR the pixel: toggles it on if off, off if on
|
||||
// Toggle pixel state via XOR
|
||||
pixel^ ~= 1
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user