Added handler for 0x1000 and added documentation for most functions and classes
parent
632873078b
commit
fce922f6c6
|
@ -7,21 +7,70 @@
|
|||
|
||||
class InstructionHandler;
|
||||
|
||||
/**
|
||||
* The main Chip8 used to emulate/interpret the Chip8 program
|
||||
*/
|
||||
class Chip8 {
|
||||
private:
|
||||
/**
|
||||
* The memory of the Chip8 program
|
||||
*/
|
||||
unsigned char memory[0xFFF] = {};
|
||||
|
||||
/**
|
||||
* The 16 general purpose 8-bit registers, generally used to store memory addresses
|
||||
*/
|
||||
unsigned char v[0x10] = {};
|
||||
|
||||
/**
|
||||
* Stack of 16-bit values, used to store addresses the interpreter should return to when finished with a subroutine
|
||||
*/
|
||||
std::stack<unsigned short> stack{};
|
||||
|
||||
/**
|
||||
* The program counter which holds the currently executing address
|
||||
*/
|
||||
unsigned short pc;
|
||||
|
||||
/**
|
||||
* The Display object holding information and functions used for the display
|
||||
*/
|
||||
Display display;
|
||||
|
||||
/**
|
||||
* The instruction handler used to handle instructions
|
||||
*/
|
||||
InstructionHandler* instructionHandler;
|
||||
public:
|
||||
Chip8();
|
||||
~Chip8();
|
||||
void loadRom(const std::string&);
|
||||
|
||||
/**
|
||||
* Loads a rom in the memory
|
||||
* @param rom The rom to be loaded in the memory
|
||||
*/
|
||||
void loadRom(const std::string& rom);
|
||||
|
||||
/**
|
||||
* Gets the Display object
|
||||
* @return
|
||||
*/
|
||||
[[nodiscard]] Display getDisplay() const;
|
||||
|
||||
/**
|
||||
* Cycle to handle the next instruction
|
||||
*/
|
||||
void emulateCycle();
|
||||
|
||||
/**
|
||||
* Pops a 16-bit value from the stack
|
||||
* @return a unsigned 16-bit integer
|
||||
*/
|
||||
[[nodiscard]] unsigned short popFromStack();
|
||||
void setProgramCounter(unsigned short);
|
||||
|
||||
/**
|
||||
* Sets the program counter (PC) to the given value
|
||||
*/
|
||||
void setProgramCounter(unsigned short value);
|
||||
};
|
||||
#endif //CHIP8_CHIP8_H
|
||||
|
|
|
@ -3,12 +3,32 @@
|
|||
|
||||
#include <chrono>
|
||||
|
||||
/**
|
||||
* Class used to work with dates and times
|
||||
*/
|
||||
class Date {
|
||||
public:
|
||||
/**
|
||||
* Creates a new Date object with the given time_point
|
||||
* @param tp the time_point object
|
||||
*/
|
||||
explicit Date(std::chrono::system_clock::time_point tp);
|
||||
|
||||
/**
|
||||
* Gets a Date object with the current time as time_point
|
||||
* @return The Date object
|
||||
*/
|
||||
static Date now();
|
||||
std::string to_string() const;
|
||||
|
||||
/**
|
||||
* Converts the time_point to a string with format "Y-%m-%d %H:%M:%S"
|
||||
* @return the formatted datetime string
|
||||
*/
|
||||
[[nodiscard]] std::string to_string() const;
|
||||
private:
|
||||
/**
|
||||
* The time_point to use
|
||||
*/
|
||||
std::chrono::system_clock::time_point tp;
|
||||
};
|
||||
|
||||
|
|
|
@ -6,13 +6,36 @@
|
|||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
/**
|
||||
* The display class used to handle drawing pixels to the SDL renderer
|
||||
*/
|
||||
class Display {
|
||||
private:
|
||||
/**
|
||||
* Flag whether the display should be redrawn
|
||||
*/
|
||||
bool flagRedraw = false;
|
||||
|
||||
/**
|
||||
* The 64x32-pixel monochrome display
|
||||
*/
|
||||
bool display[64 * 32] = {};
|
||||
public:
|
||||
void draw(SDL_Renderer*);
|
||||
/**
|
||||
* Draws the current display to the SDL renderer
|
||||
* @param renderer the SDL renderer to draw to
|
||||
*/
|
||||
void draw(SDL_Renderer* renderer);
|
||||
|
||||
/**
|
||||
* Gets whether the display should be redrawn
|
||||
* @return true if the display should be redrawn, false otherwise
|
||||
*/
|
||||
[[nodiscard]] bool needsRedraw() const;
|
||||
|
||||
/**
|
||||
* Clears the display
|
||||
*/
|
||||
void clear();
|
||||
};
|
||||
#endif //CHIP8_DISPLAY_H
|
||||
|
|
|
@ -1,14 +1,45 @@
|
|||
#ifndef CHIP8_INSTRUCTION_H
|
||||
#define CHIP8_INSTRUCTION_H
|
||||
/**
|
||||
* The instruction class stores the opcode and allows easier access to the Chip8 values
|
||||
*/
|
||||
class Instruction {
|
||||
private:
|
||||
/**
|
||||
* The basic opcode
|
||||
*/
|
||||
const unsigned short opcode;
|
||||
public:
|
||||
explicit Instruction(unsigned short);
|
||||
explicit Instruction(unsigned short opcode);
|
||||
|
||||
/**
|
||||
* Gets the NNN Chip8 value
|
||||
* @return
|
||||
*/
|
||||
[[nodiscard]] unsigned short nnn() const;
|
||||
[[nodiscard]] unsigned short n() const;
|
||||
|
||||
/**
|
||||
* Gets the N Chip8 value
|
||||
* @return
|
||||
*/
|
||||
[[nodiscard]] unsigned char n() const;
|
||||
|
||||
/**
|
||||
* Gets the KK/Byte/NN Chip8 value
|
||||
* @return
|
||||
*/
|
||||
[[nodiscard]] unsigned short kk() const;
|
||||
|
||||
/**
|
||||
* Gets the X Chip8 value
|
||||
* @return
|
||||
*/
|
||||
[[nodiscard]] unsigned char x() const;
|
||||
|
||||
/**
|
||||
* Gets the Y Chip8 value
|
||||
* @return
|
||||
*/
|
||||
[[nodiscard]] unsigned char y() const;
|
||||
};
|
||||
#endif //CHIP8_INSTRUCTION_H
|
||||
|
|
|
@ -5,13 +5,39 @@
|
|||
#include "chip8.h"
|
||||
#include "instruction.h"
|
||||
|
||||
/**
|
||||
* The InstructionHandler class holds the Chip8 instruction set and all handlers
|
||||
*/
|
||||
class InstructionHandler {
|
||||
private:
|
||||
/**
|
||||
* The typedef for the handler functions
|
||||
*/
|
||||
typedef void (*Handler)(Chip8&, Instruction);
|
||||
|
||||
/**
|
||||
* Map which stores all instructions with their handler
|
||||
*/
|
||||
std::map<unsigned short, Handler> handlers = {};
|
||||
|
||||
/**
|
||||
* Handles Chip8 instruction 0x0000, as well as 0x00E0 and 0x00EE
|
||||
*/
|
||||
static void handle0000(Chip8&, Instruction);
|
||||
|
||||
/**
|
||||
* Handles Chip8 instruction 0x1000
|
||||
*/
|
||||
static void handle1000(Chip8&, Instruction);
|
||||
public:
|
||||
InstructionHandler();
|
||||
void handleInstruction(unsigned short, Chip8&, Instruction);
|
||||
|
||||
/**
|
||||
* Attempts to handle a chip8 instruction
|
||||
* @param instructionNumber the 16-bit value to determine the instruction
|
||||
* @param chip8 the Chip8 instance
|
||||
* @param instruction the Instruction instance
|
||||
*/
|
||||
void handleInstruction(unsigned short instructionNumber, Chip8& chip8, Instruction instruction);
|
||||
};
|
||||
#endif //CHIP8_INSTRUCTION_HANDLER_H
|
||||
|
|
|
@ -5,14 +5,26 @@
|
|||
#include <string>
|
||||
#include "date.h"
|
||||
|
||||
/**
|
||||
* The LogType of a log
|
||||
*/
|
||||
enum LogType {
|
||||
INFO,
|
||||
DEBUG,
|
||||
WARN,
|
||||
ERROR
|
||||
};
|
||||
|
||||
/**
|
||||
* The logger class allows logging to either the output stream or the error stream
|
||||
*/
|
||||
class Logger {
|
||||
private:
|
||||
/**
|
||||
* Converts the LogType enum to a string to display it in the log
|
||||
* @param logType the LogType enum to convert
|
||||
* @return the LogType as a string
|
||||
*/
|
||||
static const char* logTypeToString(LogType logType) {
|
||||
switch (logType) {
|
||||
case LogType::INFO:
|
||||
|
@ -28,6 +40,13 @@ private:
|
|||
return "";
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* Writes log to either the 'cout' or 'cerr' streams
|
||||
*
|
||||
* @tparam Args Variadic template representing any number and type of arguments that
|
||||
* @param logType The type of the log. If logType is LogType::ERROR, output to 'cerr' instead of 'cout'
|
||||
* @param args The contents of the log, this can be any number of arguments
|
||||
*/
|
||||
template<typename... Args>
|
||||
static void log(LogType logType, Args&&... args) {
|
||||
std::ostream& stream = (logType == LogType::ERROR) ? std::cerr : std::cout;
|
||||
|
@ -36,21 +55,45 @@ public:
|
|||
stream << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an info log to the cout stream
|
||||
*
|
||||
* @tparam Args @see self::log
|
||||
* @param args @see self::log
|
||||
*/
|
||||
template<typename... Args>
|
||||
static void info(Args&&... args) {
|
||||
log(LogType::INFO, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an debug log to the cout stream
|
||||
*
|
||||
* @tparam Args @see self::log
|
||||
* @param args @see self::log
|
||||
*/
|
||||
template<typename... Args>
|
||||
static void debug(Args&&... args) {
|
||||
log(LogType::DEBUG, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an warn log to the cout stream
|
||||
*
|
||||
* @tparam Args @see self::log
|
||||
* @param args @see self::log
|
||||
*/
|
||||
template<typename... Args>
|
||||
static void warn(Args&&... args) {
|
||||
log(LogType::WARN, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an error log to the cerr stream
|
||||
*
|
||||
* @tparam Args @see self::log
|
||||
* @param args @see self::log
|
||||
*/
|
||||
template<typename... Args>
|
||||
static void error(Args&&... args) {
|
||||
log(LogType::ERROR, std::forward<Args>(args)...);
|
||||
|
|
|
@ -13,6 +13,13 @@ Chip8::~Chip8() {
|
|||
delete instructionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all bytes from 0x200 onward and put them in the memory.
|
||||
* If the file does not exist, the program is exited.
|
||||
*
|
||||
* @param file The Chip8 rom file
|
||||
* @todo Check if the Chip8 rom file is a valid rom file
|
||||
*/
|
||||
void Chip8::loadRom(const std::string& file) {
|
||||
FILE* game = fopen(("roms/" + file).c_str(), "rb");
|
||||
if (game == nullptr) {
|
||||
|
@ -28,12 +35,19 @@ Display Chip8::getDisplay() const {
|
|||
return this->display;
|
||||
}
|
||||
|
||||
/**
|
||||
* This emulates one cycle by getting the opcode and handling the instruction corresponding that opcode
|
||||
*/
|
||||
void Chip8::emulateCycle() {
|
||||
unsigned short opcode = (this->memory[this->pc] << 8 | this->memory[this->pc + 1]);
|
||||
this->pc += 2;
|
||||
this->instructionHandler->handleInstruction(opcode & 0xF000, *this, Instruction(opcode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value from the top of the stack, pop the top of the stack and returns the value
|
||||
* @return A 16-bit value from the stack
|
||||
*/
|
||||
unsigned short Chip8::popFromStack() {
|
||||
unsigned short value = this->stack.top();
|
||||
this->stack.pop();
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
#include "../include/display.h"
|
||||
|
||||
/**
|
||||
* Draws the display to the SDL renderer
|
||||
* If the bool in the display is true, a white pixel will be written on that position
|
||||
* The redraw flag is set to false afterwards
|
||||
*
|
||||
* @param renderer The SDL renderer
|
||||
*/
|
||||
void Display::draw(SDL_Renderer * renderer) {
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
|
@ -7,7 +14,7 @@ void Display::draw(SDL_Renderer * renderer) {
|
|||
|
||||
for (int y = 0; y < 32; y++) {
|
||||
for (int x = 0; x < 64; x++) {
|
||||
if (display[(y * 64) + x] != STATE_OFF) {
|
||||
if (display[(y * 64) + x] == STATE_ON) {
|
||||
SDL_Rect r;
|
||||
r.x = x + 10;
|
||||
r.y = y + 10;
|
||||
|
@ -26,7 +33,10 @@ bool Display::needsRedraw() const {
|
|||
return this->flagRedraw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the display by setting all pixels to false
|
||||
*/
|
||||
void Display::clear() {
|
||||
std::fill(display, display + 64 * 32, false);
|
||||
std::fill(display, display + 64 * 32, STATE_OFF);
|
||||
this->flagRedraw = true;
|
||||
}
|
||||
|
|
|
@ -4,22 +4,59 @@ Instruction::Instruction(unsigned short opcode) : opcode(opcode) {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the NNN Chip8 value, also known as the addr.
|
||||
* This is a 12-bit value, the lowest 12 bits of the instruction.
|
||||
* @return
|
||||
*/
|
||||
unsigned short Instruction::nnn() const {
|
||||
return this->opcode & 0x0FFF;
|
||||
}
|
||||
|
||||
unsigned short Instruction::n() const {
|
||||
/**
|
||||
* Gets the N Chip8 value, also known as the nibble.
|
||||
* This is a 4-bit value, the lowest 4 bits of the instruction.
|
||||
* @return
|
||||
*/
|
||||
unsigned char Instruction::n() const {
|
||||
return this->opcode & 0x000F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the KK Chip8 value, also known as byte (or NN by Wikipedia).
|
||||
* The naming is inconsistent, as Wikipedia has it as NN
|
||||
* and http://devernay.free.fr/hacks/chip8/C8TECH10.HTM has it as KK (with byte as alternative name).
|
||||
* This is a 8-bit value, the lowest 8 bits of the instruction.
|
||||
* @return
|
||||
*/
|
||||
unsigned short Instruction::kk() const {
|
||||
return this->opcode & 0x00FF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the X Chip8 value.
|
||||
* This is a 4-bit value, the lower 4 bits of the high byte of the instruction.
|
||||
* The high byte is the second nibble.
|
||||
* Example:
|
||||
*
|
||||
* 0101 1010 1100 0011
|
||||
* Here, the 1010 is the X value.
|
||||
* @return
|
||||
*/
|
||||
unsigned char Instruction::x() const {
|
||||
return (this->opcode & 0x0F00) >> 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Y Chip8 value.
|
||||
* This is a 4-bit value, the upper 4 bits of the low byte of the instruction.
|
||||
* The low byte is the second nibble.
|
||||
* Example:
|
||||
*
|
||||
* 0101 1010 1100 0011
|
||||
* Here, the 1100 is the Y value.
|
||||
* @return
|
||||
*/
|
||||
unsigned char Instruction::y() const {
|
||||
return (this->opcode & 0x00F0) >> 4;
|
||||
}
|
|
@ -1,11 +1,19 @@
|
|||
#include "../include/instruction_handler.h"
|
||||
#include "../include/logger.h"
|
||||
#include "../include/display.h"
|
||||
|
||||
InstructionHandler::InstructionHandler() {
|
||||
this->handlers[0x0000] = handle0000;
|
||||
this->handlers[0x1000] = handle1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to handle the Chip8 instruction by the given instruction number.
|
||||
* If the handler doesn't exist for the instruction, an error is logged, but the program resumes.
|
||||
*
|
||||
* @param instructionNumber the 16-bit value to determine the instruction
|
||||
* @param chip8 the Chip8 instance
|
||||
* @param instruction the Instruction instance
|
||||
*/
|
||||
void InstructionHandler::handleInstruction(unsigned short instructionNumber, Chip8& chip8, Instruction instruction) {
|
||||
if (!this->handlers.contains(instructionNumber)) {
|
||||
Logger::error("Unregistered instruction", instructionNumber);
|
||||
|
@ -15,6 +23,17 @@ void InstructionHandler::handleInstruction(unsigned short instructionNumber, Chi
|
|||
this->handlers[instructionNumber](chip8, instruction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the 0x0000 Chip-8 instruction.
|
||||
* This handles 2 other instructions:
|
||||
* - 0x00E0 which is used to clear the screen.
|
||||
* - 0x00EE which is used to return from a subroutine.
|
||||
*
|
||||
* In older interpreters, 0x0NNN was also used to jump to a machine code at NNN, but this shouldn't be implemented.
|
||||
*
|
||||
* @param chip8 The Chip8 instance
|
||||
* @param instruction The Instruction instance
|
||||
*/
|
||||
void InstructionHandler::handle0000(Chip8& chip8, Instruction instruction) {
|
||||
switch (instruction.kk()) {
|
||||
case 0x00E0:
|
||||
|
@ -26,3 +45,14 @@ void InstructionHandler::handle0000(Chip8& chip8, Instruction instruction) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the 0x1NNN Chip8 instruction which is used to jump to location NNN.
|
||||
* This will set the program counter to the NNN value.
|
||||
*
|
||||
* @param chip8 The Chip8 instance
|
||||
* @param instruction The Instruction instance
|
||||
*/
|
||||
void InstructionHandler::handle1000(Chip8& chip8, Instruction instruction) {
|
||||
chip8.setProgramCounter(instruction.nnn());
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue