From fce922f6c60b759efcf4d7edb34a4da6d799bf83 Mon Sep 17 00:00:00 2001 From: Tiger Date: Sat, 9 Sep 2023 21:35:24 +0200 Subject: [PATCH] Added handler for 0x1000 and added documentation for most functions and classes --- include/chip8.h | 53 +++++++++++++++++++++++++++++++++-- include/date.h | 22 ++++++++++++++- include/display.h | 25 ++++++++++++++++- include/instruction.h | 35 +++++++++++++++++++++-- include/instruction_handler.h | 28 +++++++++++++++++- include/logger.h | 43 ++++++++++++++++++++++++++++ src/chip8.cpp | 14 +++++++++ src/display.cpp | 14 +++++++-- src/instruction.cpp | 39 +++++++++++++++++++++++++- src/instruction_handler.cpp | 32 ++++++++++++++++++++- 10 files changed, 294 insertions(+), 11 deletions(-) diff --git a/include/chip8.h b/include/chip8.h index a652459..6fc6752 100644 --- a/include/chip8.h +++ b/include/chip8.h @@ -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 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 diff --git a/include/date.h b/include/date.h index 088a1d3..01baefa 100644 --- a/include/date.h +++ b/include/date.h @@ -3,12 +3,32 @@ #include +/** + * 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; }; diff --git a/include/display.h b/include/display.h index 6d58105..dc7fcb7 100644 --- a/include/display.h +++ b/include/display.h @@ -6,13 +6,36 @@ #include +/** + * 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 diff --git a/include/instruction.h b/include/instruction.h index 905a687..ee1328b 100644 --- a/include/instruction.h +++ b/include/instruction.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 diff --git a/include/instruction_handler.h b/include/instruction_handler.h index 2f36423..f79aabc 100644 --- a/include/instruction_handler.h +++ b/include/instruction_handler.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 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 diff --git a/include/logger.h b/include/logger.h index 627abce..1441ffc 100644 --- a/include/logger.h +++ b/include/logger.h @@ -5,14 +5,26 @@ #include #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 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 static void info(Args&&... args) { log(LogType::INFO, std::forward(args)...); } + /** + * Writes an debug log to the cout stream + * + * @tparam Args @see self::log + * @param args @see self::log + */ template static void debug(Args&&... args) { log(LogType::DEBUG, std::forward(args)...); } + /** + * Writes an warn log to the cout stream + * + * @tparam Args @see self::log + * @param args @see self::log + */ template static void warn(Args&&... args) { log(LogType::WARN, std::forward(args)...); } + /** + * Writes an error log to the cerr stream + * + * @tparam Args @see self::log + * @param args @see self::log + */ template static void error(Args&&... args) { log(LogType::ERROR, std::forward(args)...); diff --git a/src/chip8.cpp b/src/chip8.cpp index 96fc306..f35353f 100644 --- a/src/chip8.cpp +++ b/src/chip8.cpp @@ -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(); diff --git a/src/display.cpp b/src/display.cpp index 376d3bd..38eb3c2 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -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; } diff --git a/src/instruction.cpp b/src/instruction.cpp index f9c2503..482209c 100644 --- a/src/instruction.cpp +++ b/src/instruction.cpp @@ -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; } \ No newline at end of file diff --git a/src/instruction_handler.cpp b/src/instruction_handler.cpp index 235b00b..edf46a2 100644 --- a/src/instruction_handler.cpp +++ b/src/instruction_handler.cpp @@ -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()); +} +