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:
+116
-146
@@ -5,188 +5,158 @@ import "core:log"
|
|||||||
// CPU.odin -> instructions, fetch, decode, execute, run loop
|
// CPU.odin -> instructions, fetch, decode, execute, run loop
|
||||||
|
|
||||||
cycle :: proc(s: ^System) {
|
cycle :: proc(s: ^System) {
|
||||||
program_counter := s.pc
|
// Fetch
|
||||||
|
high_byte := s.memory[s.pc]
|
||||||
|
low_byte := s.memory[s.pc + 1]
|
||||||
|
s.pc += 2
|
||||||
|
|
||||||
// Fetch
|
// Get full opcode from the 2 bytes
|
||||||
// ---------------
|
opcode := (u16(high_byte) << 8) | u16(low_byte)
|
||||||
high_byte := s.memory[program_counter]
|
|
||||||
low_byte := s.memory[program_counter + 1]
|
|
||||||
|
|
||||||
// advance past this instruction early for our next opcode
|
// Pre-decode common components of opcodes
|
||||||
s.pc += 2
|
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)
|
||||||
|
|
||||||
// Instructions are made of 2 bytes we combine the high and low byte
|
// Decode + Execute
|
||||||
opcode := (u16(high_byte) << 8) | u16(low_byte)
|
switch first_nibble {
|
||||||
|
case 0x0:
|
||||||
// mask off everything except the first byte.
|
switch opcode {
|
||||||
// shift it 12 bits 'back' left with the first number
|
case 0x00E0: op_cls(s)
|
||||||
kind := (opcode & 0xF000) >> 12
|
case 0x00EE: op_ret(s)
|
||||||
|
}
|
||||||
// Decode + Execute
|
case 0x1: op_jp_addr(s, nnn)
|
||||||
// 1. opcodes are broken up to get their particulars
|
case 0x2: op_call_addr(s, nnn)
|
||||||
// 2. first nibble (4bits half byte) is the first hex number, this is the category
|
case 0x3: op_skip_on_condition(s, vx_idx, kk, true)
|
||||||
// ---------------
|
case 0x4: op_skip_on_condition(s, vx_idx, kk, false)
|
||||||
switch kind {
|
case 0x5: op_skip_reg_condition(s, vx_idx, vy_idx, true)
|
||||||
case 0x0:
|
case 0x6: op_ld(s, vx_idx, kk)
|
||||||
switch opcode {
|
case 0x7: op_add(s, vx_idx, kk)
|
||||||
case 0x00E0: op_cls(s)
|
case 0x8:
|
||||||
case 0x00EE: op_ret(s, opcode)
|
switch last_nibble {
|
||||||
}
|
case 0x0: op_alu_ld(s, vx_idx, vy_idx)
|
||||||
case 0x1: op_jp_add(s, opcode)
|
// case 0x1: op_alu_or(s, vx_idx, vy_idx)
|
||||||
case 0x2: op_call_add(s, opcode)
|
}
|
||||||
case 0x3: op_skip_on_condition(s, opcode, true)
|
case 0x9: op_skip_reg_condition(s, vx_idx, vy_idx, false)
|
||||||
case 0x4: op_skip_on_condition(s, opcode, false)
|
case 0xA: op_set(s, nnn)
|
||||||
case 0x5: op_skip_reg_condition(s, opcode, true)
|
case 0xB: op_jp_offset(s, nnn)
|
||||||
case 0x6: op_ld(s, opcode)
|
case 0xD: op_draw(s, vx_idx, vy_idx, last_nibble)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_opcode :: proc(opcode: u16) {
|
debug_opcode :: proc(opcode: u16) {
|
||||||
log.debugf("opcode: 0x%04X", opcode)
|
log.debugf("opcode: 0x%04X", opcode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instruction procs
|
|
||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
// CLS: Clear the display
|
// Instruction Implementations
|
||||||
op_cls :: proc(s: ^System) {
|
// -----------------------------------
|
||||||
// log.info("found clear screen instruction, zero out display buffer")
|
|
||||||
|
|
||||||
// Odin lets you zero a fixed array with just: {} is the zero value,
|
// 00E0 - CLS
|
||||||
// so it clears the entire buffer in one shot.
|
op_cls :: proc(s: ^System) {
|
||||||
s.display = {}
|
s.display = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RET: Return from a subroutine.
|
// 00EE - RET
|
||||||
op_ret :: proc(s: ^System, opcode: u16) {
|
op_ret :: proc(s: ^System) {
|
||||||
// decrement sp to the previous used slot
|
|
||||||
// current sp will always be empty
|
|
||||||
s.sp -= 1
|
s.sp -= 1
|
||||||
|
|
||||||
// restore the pc from where we called from
|
|
||||||
s.pc = s.stack[s.sp]
|
s.pc = s.stack[s.sp]
|
||||||
}
|
}
|
||||||
|
|
||||||
// JP addr: Jump to location nnn
|
// 1nnn - JP addr
|
||||||
op_jp_add :: proc(s: ^System, opcode: u16) {
|
op_jp_addr :: proc(s: ^System, nnn: u16) {
|
||||||
addr := (opcode & 0x0FFF)
|
s.pc = nnn
|
||||||
s.pc = addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call addr: Call subroutine at nnn.
|
// 2nnn - CALL addr
|
||||||
op_call_add :: proc(s: ^System, opcode: u16) {
|
op_call_addr :: proc(s: ^System, nnn: u16) {
|
||||||
addr := (opcode & 0x0FFF)
|
|
||||||
|
|
||||||
// Save current pc to stack for return location
|
|
||||||
s.stack[s.sp] = s.pc
|
s.stack[s.sp] = s.pc
|
||||||
// advance sp for next free slot
|
|
||||||
s.sp += 1
|
s.sp += 1
|
||||||
|
s.pc = nnn
|
||||||
s.pc = addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SE: Skip next instruction if Vx = kk.
|
// 3xkk - SE Vx, byte | 4xkk - SNE Vx, byte
|
||||||
// SNE: Skip next instruction if Vx != kk.
|
op_skip_on_condition :: proc(s: ^System, vx_idx: u16, kk: u8, condition: bool) {
|
||||||
op_skip_on_condition :: proc(s: ^System, opcode: u16, condition: bool) {
|
vx_val := s.v[vx_idx]
|
||||||
// Extract the register indices to get values
|
|
||||||
vx_index := (opcode & 0x0F00) >> 8
|
|
||||||
register_val := s.v[vx_index]
|
|
||||||
|
|
||||||
// Extract value to compare
|
if (vx_val == kk) == condition {
|
||||||
op_val := u8(opcode & 0x00FF)
|
|
||||||
|
|
||||||
if (register_val == op_val) == condition {
|
|
||||||
s.pc += 2
|
s.pc += 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SE Vx, Vy: Skip next instruction if Vx = Vy
|
// 5xy0 - SE Vx, Vy | 9xy0 - SNE Vx, Vy
|
||||||
// SNE Vx, Vy: Skip next instruction if Vx != Vy.
|
op_skip_reg_condition :: proc(s: ^System, vx_idx, vy_idx: u16, condition: bool) {
|
||||||
op_skip_reg_condition :: proc(s: ^System, opcode: u16, condition: bool) {
|
vx_val := s.v[vx_idx]
|
||||||
// Extract the register indices
|
vy_val := s.v[vy_idx]
|
||||||
vx_index := (opcode & 0x0F00) >> 8
|
|
||||||
vy_index := (opcode & 0x00F0) >> 4
|
|
||||||
|
|
||||||
// Read the actual value from the registers.
|
if (vx_val == vy_val) == condition {
|
||||||
rx := s.v[vx_index]
|
|
||||||
ry := s.v[vy_index]
|
|
||||||
|
|
||||||
if (rx == ry) == condition {
|
|
||||||
s.pc += 2
|
s.pc += 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bnnn - JP V0, addr
|
||||||
// continue Bnnn
|
// @TODO: add configurable quirk for chip-48
|
||||||
|
op_jp_offset :: proc(s: ^System, nnn: u16) {
|
||||||
// LD: Load value kk into x register
|
s.pc = nnn
|
||||||
op_ld :: proc(s: ^System, opcode: u16) {
|
s.pc += u16(s.v[0])
|
||||||
register := (opcode & 0x0F00) >> 8
|
|
||||||
value := (opcode & 0x00FF)
|
|
||||||
|
|
||||||
s.v[register] = u8(value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADD: Adds the value kk to the value of register Vx, then stores the result in Vx.
|
// 6xkk - LD Vx, byte
|
||||||
op_add :: proc(s: ^System, opcode: u16) {
|
op_ld :: proc(s: ^System, vx_idx: u16, kk: u8) {
|
||||||
register := (opcode & 0x0F00) >> 8
|
s.v[vx_idx] = kk
|
||||||
value := (opcode & 0x00FF)
|
|
||||||
s.v[register] += u8(value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SET: The value of register I is set to nnn.
|
// 7xkk - ADD Vx, byte
|
||||||
op_set :: proc(s: ^System, opcode: u16) {
|
op_add :: proc(s: ^System, vx_idx: u16, kk: u8) {
|
||||||
index_addr := (opcode & 0x0FFF)
|
s.v[vx_idx] += kk
|
||||||
s.i = index_addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DRAW: Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.
|
// 8xy0 - LD Vx, Vy
|
||||||
op_draw :: proc(s: ^System, opcode: u16) {
|
op_alu_ld :: proc(s: ^System, vx_idx, vy_idx: u16) {
|
||||||
// Extract the register indices that hold the sprite's X and Y screen position
|
s.v[vx_idx] = s.v[vy_idx]
|
||||||
vx_index := (opcode & 0x0F00) >> 8
|
}
|
||||||
vy_index := (opcode & 0x00F0) >> 4
|
|
||||||
|
// Annn - LD I, addr
|
||||||
// Read the actual coordinates from those registers.
|
op_set :: proc(s: ^System, nnn: u16) {
|
||||||
// Wrap to screen bounds: 64 wide (0x3F mask), 32 tall (0x1F mask)
|
s.i = nnn
|
||||||
screen_x := s.v[vx_index] & 0x3F
|
}
|
||||||
screen_y := s.v[vy_index] & 0x1F
|
|
||||||
|
// Dxyn - DRW Vx, Vy, nibble
|
||||||
// VF is the collision flag — clear it before drawing
|
op_draw :: proc(s: ^System, vx_idx, vy_idx, n: u16) {
|
||||||
s.v[0xF] = 0
|
// Read positions and apply bounds masks (64 wide, 32 tall)
|
||||||
|
screen_x := s.v[vx_idx] & 0x3F
|
||||||
// Number of rows in the sprite (1 byte per row, each byte = 8 pixels)
|
screen_y := s.v[vy_idx] & 0x1F
|
||||||
sprite_height := opcode & 0x000F
|
|
||||||
|
// Clear collision flag (VF) before drawing
|
||||||
for row in 0..<sprite_height {
|
s.v[0xF] = 0
|
||||||
// Each byte in memory is one row of 8 pixels, MSB is the leftmost pixel
|
|
||||||
sprite_row_bits := s.memory[s.i + row]
|
for row in 0..<n {
|
||||||
|
// Fetch current row byte from memory (each bit represents a horizontal pixel)
|
||||||
for col in 0..<8 {
|
sprite_row_bits := s.memory[s.i + row]
|
||||||
// Shift a 1 from the MSB right to isolate the bit at this column
|
|
||||||
bit_mask := u8(0x80) >> u8(col)
|
for col in 0..<8 {
|
||||||
|
// Isolate individual bits from left (MSB) to right (LSB)
|
||||||
// Only draw if this bit in the sprite is set
|
bit_mask := u8(0x80) >> u8(col)
|
||||||
if sprite_row_bits & bit_mask != 0 {
|
|
||||||
// Wrap the draw position if the sprite goes off-screen
|
// Only process and draw if the sprite bit is active (1)
|
||||||
pixel_x := (screen_x + u8(col)) % 64
|
if sprite_row_bits & bit_mask != 0 {
|
||||||
pixel_y := (screen_y + u8(row)) % 32
|
// Wrap pixel coordinates if they go over screen bounds
|
||||||
|
pixel_x := (screen_x + u8(col)) % 64
|
||||||
pixel := &s.display[pixel_y][pixel_x]
|
pixel_y := (screen_y + u8(row)) % 32
|
||||||
|
|
||||||
// CHIP-8 XORs pixels — if we're about to unset a lit pixel, that's a collision
|
pixel := &s.display[pixel_y][pixel_x]
|
||||||
if pixel^ == 1 {
|
|
||||||
s.v[0xF] = 1
|
// 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
|
}
|
||||||
pixel^ ~= 1
|
|
||||||
}
|
// Toggle pixel state via XOR
|
||||||
}
|
pixel^ ~= 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user