|
|
|
@ -9,8 +9,10 @@ |
|
|
|
|
|
|
|
|
|
using namespace olc; |
|
|
|
|
|
|
|
|
|
#define WIDTH 640 |
|
|
|
|
#define HEIGHT 480 |
|
|
|
|
bool USE_DEBUG_DISPLAY=true; |
|
|
|
|
int EMULATOR_SCREEN_WIDTH = 64; |
|
|
|
|
int EMULATOR_SCREEN_HEIGHT = 32; |
|
|
|
|
int EMULATOR_PIXEL_SIZE=5; |
|
|
|
|
|
|
|
|
|
class Chip8Emulator : public olc::PixelGameEngine |
|
|
|
|
{ |
|
|
|
@ -62,6 +64,7 @@ public: |
|
|
|
|
std::uniform_int_distribution<> distrib; |
|
|
|
|
std::array<Key,16>keymap{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; |
|
|
|
|
|
|
|
|
|
std::string Display8(int number){ |
|
|
|
|
std::bitset<8>numb(number); |
|
|
|
@ -80,7 +83,7 @@ public: |
|
|
|
|
for (int i=0;i<display.size();i++){ |
|
|
|
|
display[i]=0; |
|
|
|
|
} |
|
|
|
|
std::ifstream file("output.ch8",std::ios_base::binary); |
|
|
|
|
std::ifstream file("ChipWar.ch8",std::ios_base::binary); |
|
|
|
|
uint16_t counter=0x200; |
|
|
|
|
while (file.good()){ |
|
|
|
|
int val = file.get(); |
|
|
|
@ -106,6 +109,7 @@ public: |
|
|
|
|
|
|
|
|
|
bool OnUserUpdate(float fElapsedTime) override |
|
|
|
|
{ |
|
|
|
|
if (!USE_DEBUG_DISPLAY||!PAUSED){ |
|
|
|
|
pulse+=fElapsedTime; |
|
|
|
|
if (pulse>=1/60.f){ |
|
|
|
|
if (delay_timer>0){ |
|
|
|
@ -117,7 +121,211 @@ public: |
|
|
|
|
pulse-=1/60.f; |
|
|
|
|
|
|
|
|
|
for (int i=0;i<10;i++){ |
|
|
|
|
RunInstruction(); |
|
|
|
|
} |
|
|
|
|
DrawDisplay(); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if (GetKey(OEM_6).bPressed){ |
|
|
|
|
RunInstruction(); |
|
|
|
|
DrawDisplay(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
std::stringstream s; |
|
|
|
|
s<<"PC: 0x"<<std::setfill('0')<< std::setw(4)<<std::hex<<pc; |
|
|
|
|
DrawStringDecal(vi2d{8,EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_HEIGHT+16},s.str()); |
|
|
|
|
std::stringstream instruction; |
|
|
|
|
instruction<<"0x"<<std::setfill('0')<< std::setw(2)<<std::hex<<(int)memory[pc]<<std::setw(2)<<(int)memory[pc+1]; |
|
|
|
|
DrawStringDecal(vi2d{12,EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_HEIGHT+26},instruction.str()); |
|
|
|
|
|
|
|
|
|
for (int i=0;i<21;i++){ |
|
|
|
|
uint16_t tmp=pc+(i-10)*2; |
|
|
|
|
if (tmp<memory.size()){ |
|
|
|
|
std::stringstream tmp_s; |
|
|
|
|
tmp_s<<"0x"<<std::setfill('0')<< std::setw(4)<<std::hex<<tmp<<" "<<std::setw(2)<<(int)memory[tmp]<<std::setw(2)<<(int)memory[tmp+1]<<" "<<GetInstructionDescription(memory[tmp]<<8|memory[tmp+1]); |
|
|
|
|
std::string ss=tmp_s.str(); |
|
|
|
|
std::transform(ss.begin(),ss.end(),ss.begin(),::toupper); |
|
|
|
|
DrawStringDecal(vi2d{14+EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_WIDTH,8+i*10},ss,(tmp==pc)?YELLOW:WHITE); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool OnUserDestroy()override{ |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void DrawDisplay(){ |
|
|
|
|
if (USE_DEBUG_DISPLAY){ |
|
|
|
|
Clear(VERY_DARK_BLUE); |
|
|
|
|
for (int x=0;x<EMULATOR_SCREEN_WIDTH;x++){ |
|
|
|
|
for (int y=0;y<EMULATOR_SCREEN_HEIGHT;y++){ |
|
|
|
|
if (display[y*EMULATOR_SCREEN_WIDTH+x]){ |
|
|
|
|
screen[y*EMULATOR_SCREEN_WIDTH+x]=255; |
|
|
|
|
FillRect(vi2d{x*EMULATOR_PIXEL_SIZE,y*EMULATOR_PIXEL_SIZE}+vi2d{8,8},vi2d{EMULATOR_PIXEL_SIZE,EMULATOR_PIXEL_SIZE}); |
|
|
|
|
} else { |
|
|
|
|
screen[y*EMULATOR_SCREEN_WIDTH+x]=std::max(0,screen[y*EMULATOR_SCREEN_WIDTH+x]-36); |
|
|
|
|
FillRect(vi2d{x*EMULATOR_PIXEL_SIZE,y*EMULATOR_PIXEL_SIZE}+vi2d{8,8},vi2d{EMULATOR_PIXEL_SIZE,EMULATOR_PIXEL_SIZE},{screen[y*EMULATOR_SCREEN_WIDTH+x],screen[y*EMULATOR_SCREEN_WIDTH+x],screen[y*EMULATOR_SCREEN_WIDTH+x]}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
Clear(VERY_DARK_BLUE); |
|
|
|
|
for (int x=0;x<EMULATOR_SCREEN_WIDTH;x++){ |
|
|
|
|
for (int y=0;y<EMULATOR_SCREEN_HEIGHT;y++){ |
|
|
|
|
if (display[y*EMULATOR_SCREEN_WIDTH+x]){ |
|
|
|
|
screen[y*EMULATOR_SCREEN_WIDTH+x]=255; |
|
|
|
|
Draw({x,y},WHITE); |
|
|
|
|
} else { |
|
|
|
|
screen[y*EMULATOR_SCREEN_WIDTH+x]=std::max(0,screen[y*EMULATOR_SCREEN_WIDTH+x]-36); |
|
|
|
|
Draw({x,y},{screen[y*EMULATOR_SCREEN_WIDTH+x],screen[y*EMULATOR_SCREEN_WIDTH+x],screen[y*EMULATOR_SCREEN_WIDTH+x]}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
std::string GetInstructionDescription(uint16_t opcode){ |
|
|
|
|
uint8_t nibble1 = opcode>>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 "<<<RETURN"; |
|
|
|
|
}break; |
|
|
|
|
} |
|
|
|
|
}break; |
|
|
|
|
case 0x1:{ //Jump.
|
|
|
|
|
std::stringstream jump; |
|
|
|
|
jump<<"JUMP PC=0x"<<std::hex<<std::setfill('0')<<std::setw(4)<<NNN; |
|
|
|
|
return jump.str(); |
|
|
|
|
}break; |
|
|
|
|
case 0x2:{ //calls the subroutine at memory location NNN
|
|
|
|
|
std::stringstream call; |
|
|
|
|
call<<"CALL ("<<std::hex<<std::setfill('0')<<std::setw(4)<<NNN<<")"; |
|
|
|
|
return call.str(); |
|
|
|
|
}break; |
|
|
|
|
case 0x3:{//If register X is equal to NN, skip instruction.
|
|
|
|
|
return "IF V"+std::to_string(X)+"!="+std::to_string(NN); |
|
|
|
|
}break; |
|
|
|
|
case 0x4:{//If register X is not equal to NN, skip instruction.
|
|
|
|
|
return "IF V"+std::to_string(X)+"=="+std::to_string(NN); |
|
|
|
|
}break; |
|
|
|
|
case 0x5:{//If register X is equal to register Y, skip instruction.
|
|
|
|
|
return "IF V"+std::to_string(X)+"!=V"+std::to_string(Y); |
|
|
|
|
}break; |
|
|
|
|
case 0x6:{ //Set the register X to NN.
|
|
|
|
|
return "SET V"+std::to_string(X)+"="+std::to_string(NN); |
|
|
|
|
}break; |
|
|
|
|
case 0x7:{ //Add w/no carry
|
|
|
|
|
return "ADD_NC V"+std::to_string(X)+"+="+std::to_string(NN); |
|
|
|
|
}break; |
|
|
|
|
case 0x8:{ |
|
|
|
|
switch(N){ |
|
|
|
|
case 0x0:{//VX is set to the value of VY.
|
|
|
|
|
return "SET V"+std::to_string(X)+"=V"+std::to_string(Y); |
|
|
|
|
}break; |
|
|
|
|
case 0x1:{//Binary OR VX w/VY.
|
|
|
|
|
return " OR V"+std::to_string(X)+"|=V"+std::to_string(Y); |
|
|
|
|
}break; |
|
|
|
|
case 0x2:{//Binary AND VX w/VY.
|
|
|
|
|
return " AND V"+std::to_string(X)+"&=V"+std::to_string(Y); |
|
|
|
|
}break; |
|
|
|
|
case 0x3:{//Logical XOR VX w/VY.
|
|
|
|
|
reg[X]^=reg[Y]; |
|
|
|
|
return " XOR V"+std::to_string(X)+"^=V"+std::to_string(Y); |
|
|
|
|
}break; |
|
|
|
|
case 0x4:{//Add VY to VX, set carry bit if overflow occurs.
|
|
|
|
|
return "ADD V"+std::to_string(X)+"+=V"+std::to_string(Y); |
|
|
|
|
}break; |
|
|
|
|
case 0x5:{// sets VX to the result of VX - VY.
|
|
|
|
|
return "SUBTRACT V"+std::to_string(X)+"-=V"+std::to_string(Y); |
|
|
|
|
}break; |
|
|
|
|
case 0x6:{//Shift Right
|
|
|
|
|
return "RSHIFT V"+std::to_string(X)+">>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"<<std::hex<<std::setfill('0')<<std::setw(4)<<NNN; |
|
|
|
|
return set.str(); |
|
|
|
|
}break; |
|
|
|
|
case 0xB:{//Jump w/Offset
|
|
|
|
|
std::stringstream jump; |
|
|
|
|
jump<<"JUMP_OFFSET PC"<<"=0x"<<std::hex<<std::setfill('0')<<std::setw(4)<<NNN<<"+[V0]"; |
|
|
|
|
return jump.str(); |
|
|
|
|
}break; |
|
|
|
|
case 0xC:{//Random number from 0 to NN.
|
|
|
|
|
return "RAND (0-"+std::to_string(NN)+")"; |
|
|
|
|
}break; |
|
|
|
|
case 0xD:{ //Display
|
|
|
|
|
return "DISPLAY X:[V"+std::to_string(X)+"],Y:[V"+std::to_string(Y)+"] H:"+std::to_string(N); |
|
|
|
|
}break; |
|
|
|
|
case 0xE:{ |
|
|
|
|
switch (NN){ |
|
|
|
|
case 0x9E:{//EX9E: Skip an instruction if key in VX is pressed.
|
|
|
|
|
return "IF KEY_UNHELD [V"+std::to_string(X)+"]"; |
|
|
|
|
}break; |
|
|
|
|
case 0xA1:{//EXA1: Skip an instruction if key in VX is not pressed.
|
|
|
|
|
return "IF KEY_HELD [V"+std::to_string(X)+"]"; |
|
|
|
|
}break; |
|
|
|
|
} |
|
|
|
|
}break; |
|
|
|
|
case 0xF:{ |
|
|
|
|
switch (NN){ |
|
|
|
|
case 0x07:{//sets VX to the current value of the delay timer
|
|
|
|
|
return "GET DELAY_TIMER -> 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; |
|
|
|
@ -260,22 +468,22 @@ public: |
|
|
|
|
reg[X]=distrib(gen)&NN; |
|
|
|
|
}break; |
|
|
|
|
case 0xD:{ //Display
|
|
|
|
|
uint8_t start_X=reg[X]%ScreenWidth(); |
|
|
|
|
uint8_t start_Y=reg[Y]%ScreenHeight(); |
|
|
|
|
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<N;y++){ |
|
|
|
|
uint16_t spriteRow=memory[index+y]; |
|
|
|
|
//std::cout<<spriteRow<<std::endl;
|
|
|
|
|
for (uint8_t x=0;x<8;x++){ |
|
|
|
|
if (start_X+x<ScreenWidth()&&start_Y+y<ScreenHeight()){ |
|
|
|
|
if (start_X+x<EMULATOR_SCREEN_WIDTH&&start_Y+y<EMULATOR_SCREEN_HEIGHT){ |
|
|
|
|
//Memory loc: 0x50
|
|
|
|
|
//0xF0,0x90,0x90,0x90,0xF0
|
|
|
|
|
bool pixel = spriteRow>>(8-x-1)&0x1; |
|
|
|
|
if (display[(start_Y+y)*ScreenWidth()+(start_X+x)]&&pixel){ |
|
|
|
|
if (display[(start_Y+y)*EMULATOR_SCREEN_WIDTH+(start_X+x)]&&pixel){ |
|
|
|
|
//std::cout<<"Double on."<<std::endl;
|
|
|
|
|
reg[0xF]=0; |
|
|
|
|
} |
|
|
|
|
display[(start_Y+y)*ScreenWidth()+(start_X+x)]=display[(start_Y+y)*ScreenWidth()+(start_X+x)]^pixel; |
|
|
|
|
display[(start_Y+y)*EMULATOR_SCREEN_WIDTH+(start_X+x)]=display[(start_Y+y)*EMULATOR_SCREEN_WIDTH+(start_X+x)]^pixel; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -355,33 +563,19 @@ public: |
|
|
|
|
}break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Clear(VERY_DARK_BLUE); |
|
|
|
|
for (int x=0;x<ScreenWidth();x++){ |
|
|
|
|
for (int y=0;y<ScreenHeight();y++){ |
|
|
|
|
if (display[y*ScreenWidth()+x]){ |
|
|
|
|
screen[y*ScreenWidth()+x]=255; |
|
|
|
|
Draw({x,y},WHITE); |
|
|
|
|
} else { |
|
|
|
|
screen[y*ScreenWidth()+x]=std::max(0,screen[y*ScreenWidth()+x]-36); |
|
|
|
|
Draw({x,y},{screen[y*ScreenWidth()+x],screen[y*ScreenWidth()+x],screen[y*ScreenWidth()+x]}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool OnUserDestroy()override{ |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
int main() |
|
|
|
|
{
|
|
|
|
|
Chip8Emulator demo; |
|
|
|
|
if (USE_DEBUG_DISPLAY){ |
|
|
|
|
if (demo.Construct(800, 360, 2, 2)) |
|
|
|
|
demo.Start(); |
|
|
|
|
} else { |
|
|
|
|
if (demo.Construct(64, 32, 10, 10)) |
|
|
|
|
demo.Start(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|