You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
7.6 KiB
264 lines
7.6 KiB
/* Chip8.Ops.cpp |
|
* |
|
* CHIP-8 opcodes. |
|
*/ |
|
|
|
#include <iomanip> |
|
#include <sstream> |
|
|
|
#include "../../PlipEmulationException.h" |
|
|
|
#include "Chip8.h" |
|
|
|
namespace Plip::Cpu { |
|
void Chip8::Op0NNN(uint16_t address) { |
|
// RCA 1802 routine. Not implemented. |
|
} |
|
|
|
void Chip8::Op00E0() { |
|
// Clears the screen. |
|
for(auto i = 0; i < VideoSize; i++) |
|
m_videoBuffer[i] = 0; |
|
} |
|
|
|
void Chip8::Op00EE() { |
|
// Returns from a subroutine. |
|
if(m_sp == 0) { |
|
std::stringstream ex; |
|
ex << "stack underflow\n\n" << DumpRegisters(); |
|
throw PlipEmulationException(ex.str().c_str()); |
|
} |
|
|
|
m_pc = m_stack[--m_sp]; |
|
} |
|
|
|
void Chip8::Op1NNN(uint16_t address) { |
|
// Jumps to an address. |
|
m_pc = address; |
|
} |
|
|
|
void Chip8::Op2NNN(uint16_t address) { |
|
// Calls a subroutine. |
|
if(m_sp >= StackSize) { |
|
std::stringstream ex; |
|
ex << "stack overflow\n\n" << DumpRegisters(); |
|
throw PlipEmulationException(ex.str().c_str()); |
|
} |
|
|
|
m_stack[m_sp++] = m_pc; |
|
m_pc = address; |
|
} |
|
|
|
void Chip8::Op3XNN(uint8_t reg, uint8_t value) { |
|
// Skips the next instruction if reg == value. |
|
if(m_reg[reg] == value) m_pc += 2; |
|
} |
|
|
|
void Chip8::Op4XNN(uint8_t reg, uint8_t value) { |
|
// Skips the next instruction if reg != value. |
|
if(m_reg[reg] != value) m_pc += 2; |
|
} |
|
|
|
void Chip8::Op5XY0(uint8_t left, uint8_t right) { |
|
// Skips the next instruction if left == right. |
|
if(m_reg[left] == m_reg[right]) m_pc += 2; |
|
} |
|
|
|
void Chip8::Op6XNN(uint8_t reg, uint8_t value) { |
|
// Sets reg to value. |
|
m_reg[reg] = value; |
|
} |
|
|
|
void Chip8::Op7XNN(uint8_t reg, uint8_t value) { |
|
// Adds value to reg. |
|
m_reg[reg] += value; |
|
} |
|
|
|
void Chip8::Op8XYO(uint8_t left, uint8_t right, uint8_t op) { |
|
uint8_t old; |
|
|
|
// Various bitwise and math operations. |
|
switch(op) { |
|
case 0x0: |
|
// left = right |
|
m_reg[left] = m_reg[right]; |
|
break; |
|
|
|
case 0x1: |
|
// left |= right |
|
m_reg[left] |= m_reg[right]; |
|
break; |
|
|
|
case 0x2: |
|
// left &= right |
|
m_reg[left] &= m_reg[right]; |
|
break; |
|
|
|
case 0x3: |
|
// left ^= right |
|
m_reg[left] ^= m_reg[right]; |
|
break; |
|
|
|
case 0x4: |
|
// left += right, VF = 1 on carry, otherwise 0 |
|
old = m_reg[left]; |
|
m_reg[left] += m_reg[right]; |
|
m_reg[0xF] = old > m_reg[left] ? 1 : 0; |
|
break; |
|
|
|
case 0x5: |
|
// left -= right, VF = 1 on borrow, otherwise 0 |
|
old = m_reg[left]; |
|
m_reg[left] -= m_reg[right]; |
|
m_reg[0xF] = old < m_reg[left] ? 0 : 1; |
|
break; |
|
|
|
case 0x6: |
|
// left >>= 1, VF = LSB of right before shift |
|
m_reg[0xF] = m_reg[left] & 0x1; |
|
m_reg[left] >>= 1; |
|
break; |
|
|
|
case 0x7: |
|
// left = right - left, VF = 1 on borrow, otherwise 0 |
|
m_reg[left] = m_reg[right] - m_reg[left]; |
|
m_reg[0xF] = m_reg[right] < m_reg[left] ? 0 : 1; |
|
break; |
|
|
|
case 0xE: |
|
// left <<= 1, VF = MSB of right before shift |
|
m_reg[0xF] = (m_reg[left] & 0x80) > 0 ? 1 : 0; |
|
m_reg[left] <<= 1; |
|
break; |
|
|
|
default: break; |
|
} |
|
} |
|
|
|
void Chip8::Op9XY0(uint8_t left, uint8_t right) { |
|
// Skips the next instruction if left != right. |
|
if(m_reg[left] != m_reg[right]) m_pc += 2; |
|
} |
|
|
|
void Chip8::OpANNN(uint16_t address) { |
|
// Sets i to address. |
|
m_i = address; |
|
} |
|
|
|
void Chip8::OpBNNN(uint16_t address) { |
|
// Jumps to address + V0. |
|
m_pc = address + m_reg[0]; |
|
} |
|
|
|
void Chip8::OpCXNN(uint8_t reg, uint8_t value) { |
|
// Sets reg to random(0-255) & value. |
|
std::uniform_int_distribution<int> num(0, 255); |
|
m_reg[reg] = num(m_rng) & value; |
|
} |
|
|
|
void Chip8::OpDXYN(uint8_t xReg, uint8_t yReg, uint8_t size) { |
|
// Draws an (8 x size) sprite at address I to (xReg, yReg). |
|
auto sprite = new uint64_t[size]; |
|
auto x = m_reg[xReg]; |
|
auto y = m_reg[yReg]; |
|
|
|
for(auto i = 0; i < size; i++) { |
|
// Push sprites all the way to the highest bits, then |
|
// bump them to the right as much as necessary. |
|
sprite[i] = ((uint64_t)m_memory->GetByte(m_i + i) << 56) >> x; |
|
} |
|
|
|
m_reg[0xF] = 0; |
|
for(auto i = 0; i < size; i++) { |
|
if(y + i >= VideoSize) return; // Don't attempt to write past maxY. |
|
|
|
uint64_t oldRow = m_videoBuffer[y + i]; |
|
m_videoBuffer[y + i] ^= sprite[i]; |
|
|
|
// Invert the old row, OR it with the new row, and invert the result. |
|
// If it's greater than 0, a bit has been turned off. |
|
oldRow = ~oldRow; |
|
if(~(oldRow | m_videoBuffer[y + i]) > 0) |
|
m_reg[0xF] = 1; |
|
} |
|
} |
|
|
|
void Chip8::OpEXOO(uint8_t reg, uint8_t op) { |
|
// Input functions. |
|
switch(op) { |
|
case 0x9E: |
|
// Skips the next instruction if the key in reg is pressed. |
|
if(m_input->GetInput(m_reg[reg]).digital) m_pc += 2; |
|
break; |
|
|
|
case 0xA1: |
|
// Skips the next instruction if the key in reg is released. |
|
if(!m_input->GetInput(m_reg[reg]).digital) m_pc += 2; |
|
break; |
|
|
|
default: break; |
|
} |
|
} |
|
|
|
void Chip8::OpFXOO(uint8_t reg, uint8_t op) { |
|
std::stringstream ss; |
|
std::string str; |
|
|
|
// Various other instructions. |
|
switch(op) { |
|
case 0x07: |
|
// Store the value of the delay timer in reg. |
|
m_reg[reg] = m_timerDelay; |
|
break; |
|
|
|
case 0x0A: |
|
// Wait for a keypress and store the result in reg. |
|
m_waitForKey = true; |
|
m_keyRegister = reg; |
|
break; |
|
|
|
case 0x15: |
|
// Set the delay timer to reg. |
|
m_timerDelay = m_reg[reg]; |
|
break; |
|
|
|
case 0x18: |
|
// Set the audio timer to reg. |
|
m_timerAudio = m_reg[reg]; |
|
break; |
|
|
|
case 0x1E: |
|
// Add reg to I. |
|
m_i += m_reg[reg]; |
|
break; |
|
|
|
case 0x29: |
|
// Set I to the address of the charset value in reg. |
|
m_i = m_charsetAddress + (m_reg[reg] * 5); |
|
break; |
|
|
|
case 0x33: |
|
// Stores reg as a binary-coded decimal, starting at I. |
|
ss << std::setfill('0') << std::setw(3) << std::to_string(m_reg[reg]); |
|
str = ss.str(); |
|
for(auto i = 0; i < 3; i++) |
|
m_memory->SetByte(m_i + i, str[i] - '0'); |
|
break; |
|
|
|
case 0x55: |
|
// Stores the values of V0 to reg, inclusive, in I and up. |
|
for(auto i = 0; i < reg + 1; i++) |
|
m_memory->SetByte(m_i + i, m_reg[i]); |
|
break; |
|
|
|
case 0x65: |
|
// Fills registers V0 to reg, inclusive, with the values in |
|
// memory starting with I. |
|
for(auto i = 0; i < reg + 1; i++) |
|
m_reg[i] = m_memory->GetByte(m_i + i); |
|
break; |
|
|
|
default: break; |
|
} |
|
} |
|
} |