#include "pixelGameEngine.h" #include "Polygon.h" #define OLC_PGEX_SPLASHSCREEN #include "splash.h" #include #include #include #include using namespace olc; bool USE_DEBUG_DISPLAY=false; int EMULATOR_SCREEN_WIDTH = 64; int EMULATOR_SCREEN_HEIGHT = 32; int EMULATOR_PIXEL_SIZE=5; class Chip8Emulator : public olc::PixelGameEngine { public: //SplashScreen s; Chip8Emulator() { sAppName = "CHIP-8!"; } public: std::arraymemory={ 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, //Font starts at 0x50 0xF0,0x90,0x90,0x90,0xF0, // 0 0x20,0x60,0x20,0x20,0x70, // 1 0xF0,0x10,0xF0,0x80,0xF0, // 2 0xF0,0x10,0xF0,0x10,0xF0, // 3 0x90,0x90,0xF0,0x10,0x10, // 4 0xF0,0x80,0xF0,0x10,0xF0, // 5 0xF0,0x80,0xF0,0x90,0xF0, // 6 0xF0,0x10,0x20,0x40,0x40, // 7 0xF0,0x90,0xF0,0x90,0xF0, // 8 0xF0,0x90,0xF0,0x10,0xF0, // 9 0xF0,0x90,0xF0,0x90,0x90, // A 0xE0,0x90,0xE0,0x90,0xE0, // B 0xF0,0x80,0x80,0x80,0xF0, // C 0xE0,0x90,0x90,0x90,0xE0, // D 0xF0,0x80,0xF0,0x80,0xF0, // E 0xF0,0x80,0xF0,0x80,0x80 // F }; std::arraydisplay; std::arrayscreen; std::liststack; float pulse=0; uint8_t delay_timer,sound_timer; uint16_t pc=0x200; uint16_t index; //One 16-bit index register called “I” which is used to point at locations in memory std::arrayreg; std::random_device rd; //Will be used to obtain a seed for the random number engine std::mt19937 gen; //Standard mersenne_twister_engine seeded with rd() std::uniform_int_distribution<> distrib; std::arraykeymap{X,K1,K2,K3,Q,W,E,A,S,D,Z,C,K4,R,F,V}; bool USE_ORIGINAL_CHIP8_SET=true; //True means use the original CHIP-8 spec (COSMAC VIP emulation). Set to false to use CHIP-48 spec. bool PAUSED=true; long instructionCount=0; uint16_t watchMemoryAddress=0; std::string Display8(int number){ std::bitset<8>numb(number); return numb.to_string(); } std::string Display16(int number){ std::bitset<16>numb(number); return numb.to_string(); } void advanceTimers(){ if (delay_timer>0){ delay_timer--; } if (sound_timer>0){ sound_timer--; } } bool OnUserCreate() override { gen=std::mt19937(rd()); distrib=std::uniform_int_distribution<>(0,255); for (int i=0;i=1/60.f){ advanceTimers(); pulse-=1/60.f; for (int i=0;i<10;i++){ RunInstruction(); } DrawDisplay(); } } else { if (GetKey(G).bPressed){ TextEntryEnable(true); } if (!IsTextEntryEnabled()){ if (GetKey(OEM_6).bPressed){ RunInstruction(); instructionCount++; if (instructionCount%10==0){ advanceTimers(); //After 10 instructions, 1/60th of a second has passed. } DrawDisplay(); } else if (GetKey(SPACE).bHeld){ RunInstruction(); instructionCount++; if (instructionCount%10==0){ advanceTimers(); //After 10 instructions, 1/60th of a second has passed. } DrawDisplay(); } } } if (IsTextEntryEnabled()){ DrawStringDecal({2,2},"Goto Memory Address: "+TextEntryGetString()); } std::stringstream s; s<<"PC: 0x"<=10)?3:4)<<(int)reg[i]; DrawStringDecal(vi2d{12+i/8*64,EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_HEIGHT+54+i%8*10},s.str()); } std::stringstream index_s; index_s<<"0x"<>12; uint8_t X = opcode>>8&0xF; uint8_t Y = opcode>>4&0xF; uint8_t N = opcode&0xF; uint8_t NN = opcode&0x00FF; uint16_t NNN = opcode&0x0FFF; switch(nibble1){ case 0x0:{ switch (NNN){ case 0x0E0:{ //Clear screen. return "CLEAR SCREEN"; }break; case 0x0EE:{ //Return from subroutine. return "<<>1"; }break; case 0x7:{//sets VX to the result of VY - VX. It's a reverse subtraction. return "SUBTRACT V"+std::to_string(X)+"=V"+std::to_string(Y)+"-V"+std::to_string(X); }break; case 0xE:{//Shift Left return "LSHIFT V"+std::to_string(X)+"<<1"; }break; } }break; case 0x9:{//If register X is not equal to register Y, skip instruction. return "IF V"+std::to_string(X)+"==V"+std::to_string(Y); }break; case 0xA:{ //This sets the index register I to the value NNN. std::stringstream set; set<<"SET I=0x"< V"+std::to_string(X); }break; case 0x15:{//sets the delay timer to the value in VX return "SET DELAY_TIMER = [V"+std::to_string(X)+"]"; }break; case 0x18:{//sets the sound timer to the value in VX return "SET SOUND_TIMER = [V"+std::to_string(X)+"]"; }break; case 0x1E:{//The index register I will get the value in VX added to it. return "ADD I+=[V"+std::to_string(X)+"]"; }break; case 0x0A:{//This instruction “blocks”; it stops executing instructions and waits for key input return "WAIT KEY_INPUT"; }break; case 0x29:{//The index register I is set to the address of the hexadecimal character in VX. return "SET I = FONT([V"+std::to_string(X)+"])"; }break; case 0x33:{//Binary-coded decimal conversion return "SET [I~I+3] = BCD([V"+std::to_string(X)+"])"; }break; case 0x55:{//Stores registers from V0 to VX in memory pointed by index. return "STORE V0~V"+std::to_string(X)+" in I~I+"+std::to_string(X); }break; case 0x65:{//Retrieves registers from V0 to VX in memory pointed by index. return "LOAD V0~V"+std::to_string(X)+" in I~I+"+std::to_string(X); }break; } }break; } return "???"; } void RunInstruction(){ //FETCH uint16_t opcode = memory[pc]<<8|memory[pc+1]; pc+=2; //std::cout<<"Next instruction is 0x"<>12; uint8_t X = opcode>>8&0xF; uint8_t Y = opcode>>4&0xF; uint8_t N = opcode&0xF; uint8_t NN = opcode&0x00FF; uint16_t NNN = opcode&0x0FFF; //std::cout<<"Opcode 0x"<>8); stack.push_front(pc&0xFF); //std::cout<<"Pushed 0x"<255){ carryFlag=1; } reg[X]+=reg[Y]; reg[0xF]=carryFlag; }break; case 0x5:{// sets VX to the result of VX - VY. bool carryFlag=0; if (reg[X]>reg[Y]){ //reg[0xF]=1; carryFlag=1; } reg[X]-=reg[Y]; reg[0xF]=carryFlag; }break; case 0x6:{//Shift Right if (USE_ORIGINAL_CHIP8_SET){ /* In the CHIP-8 interpreter for the original COSMAC VIP, this instruction did the following: It put the value of VY into VX, and then shifted the value in VX 1 bit to the right (8XY6) or left (8XYE). VY was not affected, but the flag register VF would be set to the bit that was shifted out. However, starting with CHIP-48 and SUPER-CHIP in the early 1990s, these instructions were changed so that they shifted VX in place, and ignored the Y completely. */ reg[X]=reg[Y]; } bool carryFlag=reg[X]&0x1; reg[X]>>=1; reg[0xF]=carryFlag; }break; case 0x7:{//sets VX to the result of VY - VX. It's a reverse subtraction. //reg[0xF]=0; bool carryFlag=false; if (reg[Y]>reg[X]){ //reg[0xF]=1; carryFlag=true; } reg[X]=reg[Y]-reg[X]; reg[0xF]=carryFlag; }break; case 0xE:{//Shift Left //std::cout<<"Y is: "<<(int)Y<>7; reg[X]<<=1; reg[0xF]=carryFlag; }break; } }break; case 0x9:{//If register X is not equal to register Y, skip instruction. if(reg[X]!=reg[Y]){ pc+=2; } }break; case 0xA:{ //This sets the index register I to the value NNN. index=NNN; }break; case 0xB:{//Jump w/Offset pc=NNN+reg[0x0]; }break; case 0xC:{//Random number from 0 to NN. reg[X]=distrib(gen)&NN; }break; case 0xD:{ //Display uint8_t start_X=reg[X]%EMULATOR_SCREEN_WIDTH; uint8_t start_Y=reg[Y]%EMULATOR_SCREEN_HEIGHT; reg[0xF]=0; for (uint8_t y=0;y>(8-x-1)&0x1; if (display[(start_Y+y)*EMULATOR_SCREEN_WIDTH+(start_X+x)]&&pixel){ //std::cout<<"Double on."<=0x1000){ //reg[0xF]=1; carryFlag=true; } index+=reg[X]; reg[0xF]=carryFlag; }break; case 0x0A:{//This instruction “blocks”; it stops executing instructions and waits for key input for (int i=0;i