Browse Source

Added threading safety, Split timers into seperate threads, Add C8E namespace, Commenting improvements

tags/0.3.0
Jake Wakeling 2 months ago
parent
commit
43f77105a0
5 changed files with 162 additions and 135 deletions
  1. 2
    2
      CMakeLists.txt
  2. 33
    36
      src/chip8.cpp
  3. 22
    19
      src/chip8.h
  4. 84
    65
      src/main.cpp
  5. 21
    13
      src/main.h

+ 2
- 2
CMakeLists.txt View File

@@ -5,8 +5,8 @@ project(C8E) # Project Name
set(CMAKE_CXX_STANDARD 17) # C++ Standard

# Output Directories
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)

link_directories( # Library Directories

+ 33
- 36
src/chip8.cpp View File

@@ -5,6 +5,8 @@

#include "chip8.h"

using namespace C8E;

uint8_t fontset[80] = { // Fontset
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
0x20, 0x60, 0x20, 0x20, 0x70, // 1
@@ -24,33 +26,31 @@ uint8_t fontset[80] = { // Fontset
0xF0, 0x80, 0xF0, 0x80, 0x80, // F
};

// Initialise Chip-8 Emulator
// Initialise Chip-8 emulator
void Chip8::Init(void) {
memset(V, 0x00, sizeof(V)); // Clear Registers
memset(M, 0x00, sizeof(M)); // Clear Memory
memset(S, 0x00, sizeof(S)); // Clear Stack
memset(V, 0x00, sizeof(V)); // Clear registers
memset(M, 0x00, sizeof(M)); // Clear memory
memset(S, 0x00, sizeof(S)); // Clear stack

SP = 0x00; // Clear Stack Pointer
PC = 0x0200; // Set Program Counter to 0x0200
I = 0x00; // Clear Index Register
OP = 0x00; // Clear Opcode
SP = 0x00; // Clear stack pointer
PC = 0x0200; // Set program counter to 0x0200
I = 0x00; // Clear index register
OP = 0x00; // Clear opcode

timerDelay = 0x00; // Clear Delay Timer
timerSound = 0x00; // Clear Sound Timer
timerDelay = 0x00; // Clear delay timer
timerSound = 0x00; // Clear sound timer

memset(graphics, 0, sizeof(graphics)); // Clear Display
memset(graphics, 0, sizeof(graphics)); // Clear display

memcpy(M, fontset, sizeof(fontset)); // Load Fontset into Memory
memcpy(M, fontset, sizeof(fontset)); // Load fontset into memory

drawFlag = true; // Set drawFlag to True
drawFlag = true; // Set drawFlag to true

#ifdef _DEBUG
printf("C8E Initialised\n");
#endif
printf("C8E Initialised\n"); // Print success message
return;
}

// Load Program Binary
// Load program binary into memory
// C8Ex0102: Failed to open file for reading
// C8Ex0103: Failed to allocate memory
// C8Ex0104: Failed to read from file
@@ -67,27 +67,25 @@ void Chip8::LoadProgram(std::string filePath) {
size_t fileSize = file.tellg(); // Store file size
file.seekg(0, file.beg); // Seek beginning of file

#ifdef _DEBUG
printf("Filesize: %llu byte(s)\n", fileSize);
#endif
printf("Filesize: %llu byte(s)\n", fileSize); // Print loaded file size

std::byte *buffer;
try {
buffer = new std::byte[fileSize]; // Create File Buffer
buffer = new std::byte[fileSize]; // Create file buffer
}
catch (std::bad_alloc) { // Check for allocation error
fprintf(stderr, "C8Ex0103: Failed to allocate memory\n");
exit(0x0103);
}

file.read((char *)buffer, fileSize); // Copy File into Buffer
file.read((char *)buffer, fileSize); // Copy file into buffer
if (file.fail()) { // Check for file read error
fprintf(stderr, "C8Ex0104: Failed to read from %s\n", filePath.c_str());
exit(0x0104);
}

if ((4096 - 512) > fileSize) { // Check that Chip-8 has enough memory
memcpy((M + 512), buffer, fileSize); // Copy Buffer to Memory
memcpy((M + 512), buffer, fileSize); // Copy buffer to memory
}
else {
fprintf(stderr, "C8Ex0105: Program file too large\n");
@@ -100,17 +98,16 @@ void Chip8::LoadProgram(std::string filePath) {
return;
}

// Emulate Cycle
// Emulate Chip-8 CPU cycle
void Chip8::EmulateCycle(void) {
OP = (M[PC] << 8) | M[PC + 1]; // Fetch Opcode
PC += 2; // Increment Program Counter
OP = (M[PC] << 8) | M[PC + 1]; // Fetch opcode
PC += 2; // Increment program counter

#ifdef _DEBUG
printf("PC: %X OP: %X\n", PC, OP); // Print Program Counter and Opcode
printf("PC: %X OP: %04X\n", PC, OP); // Print program counter and opcode
#endif

// Decode and Execute Instruction
switch (OP & 0xF000) {
switch (OP & 0xF000) { // Decode and execute instruction
case 0x0000: {
switch (OP & 0x000F) {
case 0x0000: { // 0x00E0: Clears the screen
@@ -128,7 +125,7 @@ void Chip8::EmulateCycle(void) {
break;
}
default: {
printf("Unknown Instruction: %X", OP);
printf("Unknown Instruction: %X\n", OP);
break;
}
}
@@ -229,7 +226,7 @@ void Chip8::EmulateCycle(void) {
break;
}
default: {
printf("Unknown Instruction: %X", OP);
printf("Unknown Instruction: %X\n", OP);
break;
}
}
@@ -295,7 +292,7 @@ void Chip8::EmulateCycle(void) {
break;
}
default: {
printf("Unknown Instruction: %X", OP);
printf("Unknown Instruction: %X\n", OP);
break;
}
}
@@ -324,7 +321,7 @@ void Chip8::EmulateCycle(void) {
break;
}
default: {
printf("Unknown Instruction: %X", OP);
printf("Unknown Instruction: %X\n", OP);
break;
}
}
@@ -351,7 +348,7 @@ void Chip8::EmulateCycle(void) {
break;
}
default: {
printf("Unknown Instruction: %X", OP);
printf("Unknown Instruction: %X\n", OP);
break;
}
}
@@ -382,14 +379,14 @@ void Chip8::EmulateCycle(void) {
break;
}
default: {
printf("Unknown Instruction: %X", OP);
printf("Unknown Instruction: %X\n", OP);
break;
}
}
break;
}
default: {
printf("Unknown Instruction: %X", OP);
printf("Unknown Instruction: %X\n", OP);
break;
}
}

+ 22
- 19
src/chip8.h View File

@@ -6,32 +6,35 @@
#ifndef CHIP8_H
#define CHIP8_H

#include <atomic>
#include <fstream>

class Chip8 {
public:
uint8_t graphics[64][32]; // Graphics buffer
uint8_t keys[16]; // Keypad
namespace C8E {
class Chip8 {
public:
std::atomic_uint8_t graphics[64][32]; // Graphics Buffer
std::atomic_uint8_t keys[16]; // Keypad

uint8_t timerDelay; // Delay Timer (60Hz)
uint8_t timerSound; // Sound Timer (60Hz)
std::atomic_uint8_t timerDelay; // Delay Timer (60Hz)
std::atomic_uint8_t timerSound; // Sound Timer (60Hz)

bool drawFlag; // Draw Flag
std::atomic_bool drawFlag; // Draw Flag

void Init(void);
void LoadProgram(std::string filePath);
void EmulateCycle(void);
void Init(void);
void LoadProgram(std::string filePath);
void EmulateCycle(void);

private:
uint8_t V[16]; // Registers (V0-VF)
uint8_t M[4096]; // Memory (4KiB)
private:
uint8_t V[16]; // Registers (V0-VF)
uint8_t M[4096]; // Memory (4KiB)

uint16_t S[16]; // Stack
uint16_t SP; // Stack Pointer
uint16_t S[16]; // Stack
uint16_t SP; // Stack Pointer

uint16_t PC; // Program Counter
uint16_t I; // Index Register
uint16_t OP; // Current Opcode
};
uint16_t PC; // Program Counter
uint16_t I; // Index Register
uint16_t OP; // Current Opcode
};
}

#endif // CHIP8_H

+ 84
- 65
src/main.cpp View File

@@ -34,8 +34,8 @@ uint8_t keyMap(SDL_Keycode key) {
}

int main(int argc, char *argv[]) {
chip8.Init(); // Initialise Emulator
InitSDL(); // Initialise SDL
chip8.Init(); // Initialise emulator
InitSDL(); // Initialise SDL

if (argc > 1) {
chip8.LoadProgram(argv[1]);
@@ -66,27 +66,29 @@ int main(int argc, char *argv[]) {
#endif
}

std::thread threadDisplay = std::thread(loopDisplay);
std::thread threadEmulate = std::thread(loopEmulate);
std::thread threadTimers = std::thread(UpdateTimers);
std::thread threadDisplay = std::thread(LoopDisplay); // Create display thread
std::thread threadEmulate = std::thread(LoopEmulate); // Create emulate thread
std::thread threadTDelay = std::thread(UpdateTimerDelay); // Create delay timer thread
std::thread threadTSound = std::thread(UpdateTimerSound); // Create sound timer thread

threadDisplay.detach();
threadEmulate.detach();
threadTimers.detach();
threadDisplay.detach(); // Detatch display thread
threadEmulate.detach(); // Detatch emulate thread
threadTDelay.detach(); // Detatch timer thread
threadTSound.detach(); // Detatch timer thread

while (running) { // Main Loop
SDL_WaitEvent(&event); // Wait for Event
while (running) {
SDL_WaitEvent(&event);
switch (event.type) {
case SDL_QUIT: { // Quit Event
case SDL_QUIT: { // Quit
running = false;
break;
}
case SDL_KEYDOWN: { // Keydown Event
case SDL_KEYDOWN: { // Keydown
uint8_t index = keyMap(event.key.keysym.sym);
if (index >= 0) { chip8.keys[index] = 1; }
break;
}
case SDL_KEYUP: { // Keyup Event
case SDL_KEYUP: { // Keyup
uint8_t index = keyMap(event.key.keysym.sym);
if (index >= 0) { chip8.keys[index] = 0; }
break;
@@ -101,73 +103,107 @@ int main(int argc, char *argv[]) {
}

// Initialise SDL
// SDLx0201: SDL_INIT_VIDEO Error
// SDLx0202: SDL_CreateWindow Error
// SDLx0203: SDL_CreateRenderer Error
// SDLx0204: SDL_CreateTexture Error
void InitSDL(void) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL_INIT_VIDEO Error: %s\n", SDL_GetError());
fprintf(stderr, "SDLx0201: SDL_INIT_VIDEO Error %s\n", SDL_GetError());
exit(0x0201);
}
else {
window = SDL_CreateWindow("C8E", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, screenWidth, screenHeight,
SDL_WINDOW_SHOWN);
if (window == NULL) {
printf("SDL_CreateWindow Error: %s\n", SDL_GetError());
exit(0x0202);
}
else {
render = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED |
SDL_RENDERER_PRESENTVSYNC);
if (render == NULL) {
printf("SDL_CreateRenderer Error: %s\n", SDL_GetError());
exit(0x0203);
}
else {
SDL_RenderSetScale(render, 10, 10);
}
}

window = SDL_CreateWindow("C8E", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screenWidth, screenHeight, 0);
if (window == NULL) {
fprintf(stderr, "SDLx0202: SDL_CreateWindow Error %s\n", SDL_GetError());
exit(0x0202);
}

renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL) {
fprintf(stderr, "SDLx0203: SDL_CreateRenderer Error %s\n", SDL_GetError());
exit(0x0203);
}

SDL_RenderSetScale(renderer, 10, 10);

return;
}

// Display Loop
int loopDisplay(void) {
void LoopDisplay(void) {
while (running) {
if (chip8.drawFlag) {
UpdateDisplay(); // Update Display
chip8.drawFlag = false; // Reset Draw Flag
UpdateDisplay(); // Update display
!chip8.drawFlag; // Reset draw flag
}
}
return 0;
return;
}

// Tick Timer Thread (500Hz)
void TickThread(void) {
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}

// Emulation Loop
int loopEmulate(void) {
// Emulation Loop (500Hz)
void LoopEmulate(void) {
//std::chrono::steady_clock::time_point startTime, endTime;

while (running) {
chip8.EmulateCycle(); // Emulate Cycle
std::this_thread::sleep_for(std::chrono::milliseconds(2));
//startTime = std::chrono::high_resolution_clock::now();

std::thread tick(TickThread);
chip8.EmulateCycle(); // Emulate Cycle
tick.join();

//endTime = std::chrono::high_resolution_clock::now();
//printf("%lldms\n", (endTime - startTime) / std::chrono::milliseconds(1));
}
return 0;
return;
}

// Update Display
void UpdateDisplay(void) {
for (int x = 0; x < 64; x++) { // Copy Graphics to SDL
for (int x = 0; x < 64; x++) { // Copy Graphics to SDL
for (int y = 0; y < 32; y++) {
if (chip8.graphics[x][y] == 0) {
SDL_SetRenderDrawColor(render, 0, 0, 0, 255);
SDL_RenderDrawPoint(render, x, y);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderDrawPoint(renderer, x, y);
}
else {
SDL_SetRenderDrawColor(render, 255, 255, 255, 255);
SDL_RenderDrawPoint(render, x, y);
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderDrawPoint(renderer, x, y);
}
}
}

SDL_RenderPresent(render); // Update Renderer
SDL_RenderPresent(renderer); // Update Renderer
return;
}

// Delay Timer Loop (60Hz)
void UpdateTimerDelay(void) {
while (running) {
if (chip8.timerDelay > 0) { // Check if delay timer is positive
--chip8.timerDelay; // Decrement delay timer
}
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
}

// Delay Timer Loop (60Hz)
void UpdateTimerSound(void) {
while (running) {
if (chip8.timerSound > 0) { // Check if sound timer is positive
if (--chip8.timerSound == 0) { // Decrement sound timer
PerformBeep(); // When sound timer reaches zero, perform beep
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
}

// Perform Beep
void PerformBeep(void) {
#ifdef WIN32
@@ -177,23 +213,6 @@ void PerformBeep(void) {
#endif
}

// Timer Loop
void UpdateTimers(void) {
while (running) {
if (chip8.timerDelay > 0) {
--chip8.timerDelay; // Decrement Delay Timer
}
if (chip8.timerSound > 0) {
if (--chip8.timerSound == 0) { // Decrement Sound Timer
std::thread performBeep = std::thread(PerformBeep);
performBeep.detach();
}
}
std::this_thread::sleep_for(std::chrono::nanoseconds(16666666));
}
return;
}

// Print usage information
void PrintUsage(void) {
printf("Usage: %s [file]\n", PROGRAM_NAME);

+ 21
- 13
src/main.h View File

@@ -8,10 +8,13 @@

#define PROGRAM_NAME "c8e"

#include <atomic>
#include <chrono>
#include <SDL.h>
#include <iostream>
#include <thread>

#include <SDL.h>

#include "chip8.h"

#ifdef WIN32
@@ -19,27 +22,32 @@
#include <commdlg.h>
#endif

Chip8 chip8; // Define Emulator Class
bool running = true; // Running flag
C8E::Chip8 chip8; // Emulator Class
std::atomic_bool running = true; // Running Flag

size_t displayWidth = 64;
size_t displayHeight = 32;
size_t displayMultiplier = 10;
const uint16_t displayWidth = 64;
const uint16_t displayHeight = 32;
const uint16_t displayMultiplier = 10;

size_t screenWidth = displayWidth * displayMultiplier;
size_t screenHeight = displayHeight * displayMultiplier;
uint16_t screenWidth = displayWidth * displayMultiplier;
uint16_t screenHeight = displayHeight * displayMultiplier;

SDL_Window *window = nullptr; // SDL Window
SDL_Renderer *renderer = nullptr; // SDL Renderer

SDL_Window *window = NULL; // SDL Window
SDL_Renderer *render = NULL; // SDL Renderer
SDL_Event event; // SDL Event

void InitSDL(void);

int loopDisplay(void);
int loopEmulate(void);
void LoopDisplay(void);
void LoopEmulate(void);

void UpdateDisplay(void);
void UpdateTimers(void);

void UpdateTimerDelay(void);
void UpdateTimerSound(void);

void PerformBeep(void);

void PrintUsage(void);


Loading…
Cancel
Save