Emu?
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

/* 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;
}
}
}