# include "pixelGameEngine.h"
# include "Polygon.h"
# include <ios>
# include <stack>
# include <bitset>
# include <random>
using namespace olc ;
# define WIDTH 640
# define HEIGHT 480
class Chip8Emulator : public olc : : PixelGameEngine
{
public :
Chip8Emulator ( )
{
sAppName = " CHIP-8! " ;
}
public :
std : : array < uint8_t , 4096 > memory = {
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 : : array < bool , 2048 > display ;
std : : stack < uint8_t > stack ;
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 : : array < uint8_t , 16 > reg ;
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 : : 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.
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 ( ) ;
}
bool OnUserCreate ( ) override
{
gen = std : : mt19937 ( rd ( ) ) ;
distrib = std : : uniform_int_distribution < > ( 0 , 255 ) ;
for ( int i = 0 ; i < display . size ( ) ; i + + ) {
display [ i ] = 0 ;
}
std : : ifstream file ( " output.ch8 " , std : : ios_base : : binary ) ;
uint16_t counter = 0x200 ;
while ( file . good ( ) ) {
int val = file . get ( ) ;
if ( val ! = - 1 ) {
memory [ counter + + ] = val ;
}
}
/*memory[0x200]=0x00;
memory [ 0x201 ] = 0xE0 ;
memory [ 0x202 ] = 0xA0 ;
memory [ 0x203 ] = 0x50 ;
memory [ 0x204 ] = 0x60 ;
memory [ 0x205 ] = 0x04 ;
memory [ 0x206 ] = 0xD0 ;
memory [ 0x207 ] = 0x05 ;
memory [ 0x208 ] = 0xD0 ;
memory [ 0x209 ] = 0x05 ;
memory [ 0x210 ] = 0x12 ;
memory [ 0x211 ] = 0x10 ; */
//0x200 program start
return true ;
}
bool OnUserUpdate ( float fElapsedTime ) override
{
pulse + = fElapsedTime ;
if ( pulse > = 1 / 60.f ) {
if ( delay_timer > 0 ) {
delay_timer - - ;
}
if ( sound_timer > 0 ) {
sound_timer - - ;
}
pulse - = 1 / 60.f ;
for ( int i = 0 ; i < 10 ; i + + ) {
//FETCH
uint16_t opcode = memory [ pc ] < < 8 | memory [ pc + 1 ] ;
pc + = 2 ;
//std::cout<<"Next instruction is 0x"<<std::hex<<pc<<std::endl;
//DECODE
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 ;
//std::cout<<"Opcode 0x"<<std::hex<<opcode<<std::dec<<": "<<Display8(nibble1)<<", "<<Display8(X)<<"/"<<Display8(Y)<<"/"<<Display8(N)<<"/"<<Display8(NN)<<"/"<<Display16(NNN)<<std::endl;
switch ( nibble1 ) {
case 0x0 : {
switch ( NNN ) {
case 0x0E0 : { //Clear screen.
for ( int i = 0 ; i < display . size ( ) ; i + + ) {
display [ i ] = 0 ;
}
} break ;
case 0x0EE : { //Return from subroutine.
uint8_t byte1 , byte2 ;
byte1 = stack . top ( ) ;
stack . pop ( ) ;
byte2 = stack . top ( ) ;
stack . pop ( ) ;
pc = byte2 < < 8 | byte1 ;
//std::cout<<"Exiting subroutine to 0x"<<std::hex<<pc<<std::endl;
} break ;
}
} break ;
case 0x1 : { //Jump.
pc = NNN ;
} break ;
case 0x2 : { //calls the subroutine at memory location NNN
stack . push ( pc > > 8 ) ;
stack . push ( pc & 0xFF ) ;
//std::cout<<"Pushed 0x"<<std::hex<<pc<<" onto stack"<<std::endl;
pc = NNN ;
//std::cout<<"Entering subroutine @ 0x"<<std::hex<<NNN<<std::endl;
} break ;
case 0x3 : { //If register X is equal to NN, skip instruction.
if ( reg [ X ] = = NN ) {
pc + = 2 ;
}
} break ;
case 0x4 : { //If register X is not equal to NN, skip instruction.
if ( reg [ X ] ! = NN ) {
pc + = 2 ;
}
} break ;
case 0x5 : { //If register X is equal to register Y, skip instruction.
if ( reg [ X ] = = reg [ Y ] ) {
pc + = 2 ;
}
} break ;
case 0x6 : { //Set the register X to NN.
reg [ X ] = NN ;
//std::cout<<(int)reg[X]<<std::endl;
} break ;
case 0x7 : { //Add w/no carry
reg [ X ] + = NN ;
//std::cout<<(int)reg[X]<<std::endl;
} break ;
case 0x8 : {
switch ( N ) {
case 0x0 : { //VX is set to the value of VY.
reg [ X ] = reg [ Y ] ;
} break ;
case 0x1 : { //Binary OR VX w/VY.
reg [ X ] | = reg [ Y ] ;
} break ;
case 0x2 : { //Binary AND VX w/VY.
reg [ X ] & = reg [ Y ] ;
} break ;
case 0x3 : { //Logical XOR VX w/VY.
reg [ X ] ^ = reg [ Y ] ;
} break ;
case 0x4 : { //Add VY to VX, set carry bit if overflow occurs.
reg [ 0xF ] = 0 ;
if ( ( int ) reg [ X ] + ( int ) reg [ Y ] > 255 ) {
reg [ 0xF ] = 1 ;
}
reg [ X ] + = reg [ Y ] ;
} break ;
case 0x5 : { // sets VX to the result of VX - VY.
reg [ 0xF ] = 0 ;
if ( reg [ X ] > reg [ Y ] ) {
reg [ 0xF ] = 1 ;
}
reg [ X ] - = reg [ Y ] ;
} 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 ( 8 XY6 ) or left ( 8 XYE ) . 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 1990 s , these instructions were changed so that they shifted VX in place , and ignored the Y completely .
*/
reg [ X ] = reg [ Y ] ;
}
reg [ 0xF ] = reg [ X ] & 0x1 ;
reg [ X ] > > = 1 ;
} break ;
case 0x7 : { //sets VX to the result of VY - VX. It's a reverse subtraction.
reg [ 0xF ] = 0 ;
if ( reg [ Y ] > reg [ X ] ) {
reg [ 0xF ] = 1 ;
}
reg [ X ] = reg [ Y ] - reg [ X ] ;
} break ;
case 0xE : { //Shift Left
//std::cout<<"Y is: "<<(int)Y<<std::endl;
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 ( 8 XY6 ) or left ( 8 XYE ) . 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 1990 s , these instructions were changed so that they shifted VX in place , and ignored the Y completely .
*/
reg [ X ] = reg [ Y ] ;
}
reg [ 0xF ] = ( reg [ X ] & 0x80 ) > > 7 ;
reg [ X ] < < = 1 ;
} 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 ] % ScreenWidth ( ) ;
uint8_t start_Y = reg [ Y ] % ScreenHeight ( ) ;
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 ( ) ) {
//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 ) {
//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 ;
}
}
}
} break ;
case 0xE : {
switch ( NN ) {
case 0x9E : { //EX9E: Skip an instruction if key in VX is pressed.
if ( GetKey ( keymap [ reg [ X ] ] ) . bHeld ) {
pc + = 2 ;
}
} break ;
case 0xA1 : { //EXA1: Skip an instruction if key in VX is not pressed.
if ( ! GetKey ( keymap [ reg [ X ] ] ) . bHeld ) {
pc + = 2 ;
}
} break ;
}
} break ;
case 0xF : {
switch ( NN ) {
case 0x07 : { //sets VX to the current value of the delay timer
reg [ X ] = delay_timer ;
} break ;
case 0x15 : { //sets the delay timer to the value in VX
delay_timer = reg [ X ] ;
} break ;
case 0x18 : { //sets the sound timer to the value in VX
sound_timer = reg [ X ] ;
} break ;
case 0x1E : { //The index register I will get the value in VX added to it.
reg [ 0xF ] = 0 ;
if ( index + reg [ X ] > = 0x1000 ) {
reg [ 0xF ] = 1 ;
}
index + = reg [ X ] ;
} break ;
case 0x0A : { //This instruction “blocks”; it stops executing instructions and waits for key input
for ( int i = 0 ; i < keymap . size ( ) ; i + + ) {
if ( GetKey ( keymap [ i ] ) . bHeld ) {
reg [ X ] = i ;
goto pass ;
}
}
pc - = 2 ;
pass : ;
} break ;
case 0x29 : { //The index register I is set to the address of the hexadecimal character in VX.
index = 0x50 + ( reg [ X ] & 0xF ) * 5 ;
} break ;
case 0x33 : { //Binary-coded decimal conversion
uint8_t numb = reg [ X ] ; //156
memory [ index + 2 ] = numb % 10 ;
numb / = 10 ;
memory [ index + 1 ] = numb % 10 ;
numb / = 10 ;
memory [ index ] = numb % 10 ;
} break ;
case 0x55 : { //Stores registers from V0 to VX in memory pointed by index.
for ( int i = 0 ; i < = X ; i + + ) {
memory [ index + i ] = reg [ i ] ;
}
if ( USE_ORIGINAL_CHIP8_SET ) {
/*The original CHIP-8 interpreter for the COSMAC VIP actually incremented the I register while it worked. Each time it stored or loaded one register, it incremented I. After the instruction was finished, I would be set to the new value I + X + 1.*/
index + = X + 1 ;
}
} break ;
case 0x65 : { //Retrieves registers from V0 to VX in memory pointed by index.
for ( int i = 0 ; i < = X ; i + + ) {
reg [ i ] = memory [ index + i ] ;
}
if ( USE_ORIGINAL_CHIP8_SET ) {
/*The original CHIP-8 interpreter for the COSMAC VIP actually incremented the I register while it worked. Each time it stored or loaded one register, it incremented I. After the instruction was finished, I would be set to the new value I + X + 1.*/
index + = X + 1 ;
}
} break ;
}
} break ;
}
}
Clear ( VERY_DARK_BLUE ) ;
for ( int x = 0 ; x < ScreenWidth ( ) ; x + + ) {
for ( int y = 0 ; y < ScreenHeight ( ) ; y + + ) {
Draw ( { x , y } , display [ y * ScreenWidth ( ) + x ] ? WHITE : BLACK ) ;
}
}
}
return true ;
}
bool OnUserDestroy ( ) override {
return true ;
}
} ;
int main ( )
{
Chip8Emulator demo ;
if ( demo . Construct ( 64 , 32 , 10 , 10 ) )
demo . Start ( ) ;
return 0 ;
}