What's new

Chip 8

Cabreak

New member
ChipsEmu v1.0 BETA

Hi (New to the forum)

I've made a CHIP8 Emulator for Windows. I know there are a lot around here but I just want to share it with you all. I also provided the source code which is written in Borland Delphi 7.
Almost all opcodes are programmed, some are buggy.


Features:
-(Fully) emulates the Chip8. Including beeps ,collision,sprites ...
- Converts a Chip8 game to a Windows executable. :drool:
- A nice GUI
- Visual or normal keyboard
- Box graphics mode.
- Force acceleration (on slower PC's)
- Debug the game (Registers,timers ..)
- Manually choose the framerate (to slow down your game)


Planned features:
- Converts a Chip8 game to a Gamecube or Nintendo DS executable.
- Emulation for SCHIP


Link (Executable): http://www.di-martino.net/ChipsEmu-v1Beta.zip
Link (Source, Delphi 7): http://www.di-martino.net/ChipsEmu-v1-BETASOURCE.zip


PS: I hope this is the right place to post it.

Thank you
Cabreak (Di-Martino.net)
 

Marce1991

Programmer
Basic questions

Yo guys, been a while since I posted here (6 years lol)...Well I'll make it fast:

I'm trying to code an 8chip emu, but first I'm testing the opcode reading process, cuz I'm a total newb in emulation, but I have some knowledge on cpp...
Here's what I have so far:

Code:
#include <cstdlib>
#include <iostream>
#include <fstream>

using namespace std;

ifstream::pos_type size;
char * Memory;
ifstream file ("PONG", ios::in|ios::binary|ios::ate);
int main(int argc, char *argv[])
{
    unsigned int I;
    unsigned int MemorySize; //How big is the ROM?
    unsigned int CurrentOpcode;
    Memory = new char [MemorySize]; //We will allocate memory dynamically for each ROM file.
    
    size = file.tellg();
    file.seekg (0, ios::beg);
    file.read (Memory, MemorySize);
    for(int i = 0;i< MemorySize; i++)
    {
    CurrentOpcode = Memory[i];
    if ((CurrentOpcode& 0x00ff) == 0xe0)
    { cout << "AHA!!!!"; }
    if ((CurrentOpcode& 0x00ff) == 0xee) { cout <<"WTH"; }
    if (CurrentOpcode == 0x4) {cout << "WOW"; }
}
    file.close();
    system("PAUSE");
    return EXIT_SUCCESS;
}

Is this working fine? Cuz I can't tell rofl, I do get some 'ahas' and 'wow's but I don't know, there's just too many things I don't understand at all...

You see, in this line:
if ((CurrentOpcode& 0x00ff) == 0xee) { cout <<"WTH"; }
is the '&' operand used for an AND operation here?
How do I compare the opcodes? I think they're 2 bytes long but I don't know how does that measure in a hex sentence. And why does it seem the emu's compare diferent sized mem. adresses like in the same sentence as cited above:
if ((CurrentOpcode& 0x00ff) == 0xee) { cout <<"WTH"; }
0x00ff is longer than 0xee, so are you comparing the first adress with only the 'start' of the 2nd?
Sooo I'm pretty sure my concepts are wrong, and that's why I'd like to know how emulators read and compare the adresses. The rest is simply defining functions, and setting delays, right??
Also my MemorySize is reaching an astoundly high number for PONG lol
I wonder if it's correct...
It reaches 4072505.
What does that mean? lol
Anyway, sorry for the one thousand questions...
Help from any kind is appreciated.
Basically, I just wanna know how the emulator read and compare memory adresses.

Thanks in advance!
 

Marce1991

Programmer
Hey guys, sorry for the third consecutive post, but now I have made tons of progress!!I have implemented nearly all the opcodes. But I came across some problems. Luckily, I think I managed to isolate them...
But first and foremost I would like you guys to evaluate way I made the CPU, it's unusual, but I think it's correct! (that's one of my biggest doubts)
Instead of writing these horrible "opcode&0xf000" structures, with array subscrits all over them, I wanted to make something perhaps a bit less efficient, but more 'readable' for me. (We're talking about chip 8 right? I'll have to slow it down, nevertheless)

Code:
void Hash( unsigned short int opcode, unsigned int n) {
//n ranging from 1 to 3
     switch(n){
     case 1:
     OpcodeHash[0] = opcode & 0xf000;
     OpcodeHash[0] >>= 12;
     OpcodeHash[1] = opcode & 0x0fff;
          break;
     case 2:
     OpcodeHash[0] = opcode & 0xf000;
     OpcodeHash[0] >>= 12;
     OpcodeHash[1] = opcode & 0x0f00;
     OpcodeHash[1] >>= 8;
     OpcodeHash[2] = opcode & 0x00ff;
          break;
     case 3:
     OpcodeHash[0] = opcode & 0xf000;
     OpcodeHash[0] >>= 12;
     OpcodeHash[1] = opcode & 0x0f00;
     OpcodeHash[1] >>= 8;
     OpcodeHash[2] = opcode & 0x00f0;
     OpcodeHash[2] >>= 4;
     OpcodeHash[3] = opcode & 0x000f;
          break;
     }
}

Exactly, I allowed myself to break the opcode into different patterns. Pattern 1 is A-BCD, Pattern 2 is A-B-CD, and Pattern 3 is A-B-C-D. And yes, that makes the opcodehash a global variable. (I wanted to make a triple return function but thats impossible in cpp).

Here's the basic emu structure, being developed under the Windows API:

Code:
char Memory[MemorySize];
unsigned int i = 0x200;

while ( true ) {
  while (PeekMessage...== TRUE) {  
                
            do typical windows crap;   
        } 
    //my emu 

FullOpcode = ((Memory[i] << 8) | (Memory[i + 1])) ; 
//yes yes I'll fuse it, then break it well, at least it seems to work, but please tell me if this doesn't!
   
   cout << FullOpcode << "\n";
//allows me to keep track of which opcode is being executed

Hash(FullOpcode, 1);
   //The typical mass switch
   switch(OpcodeHash[0]){
    case 0x0: 
                //do opcode...
    break;

    case 0x1:

    break;

.
.
.
}
i = i + 2; //---->Should I increment it twice? Memory[0xfff] is a char array, so I //suppose it has to be incremented twice since it records 1 byte per array index. 
}

Well it seems to be working decently in the internal opcodes but some are clearly messed up...
One is certainly the stack...
It keeps getting overflown! I thought about managing the array's index, but that would make me waste some elements on the stack, is that ok?
Well without delay here's the stack code:

(This is the call sub opcode, I decrement it by two because at the very end of the code I increment the i twice... ( i+=2 ))
Code:
case 0x2:
             if(SP <= 15){
                   
                SP++;
				stack[SP] = i;
				i = OpcodeHash[1] - 2; //Set it to NNN
    }

Here's the return from sub, ALSO overflowing sometimes:
(the stack is inside the else structure)

Code:
switch(OpcodeHash[0]){
    case 0x0:
         Hash(FullOpcode, 3);
         if(OpcodeHash[3] == 0x0) { //Clear the Screen 
                           for(int a = 1;a<65;a++){
                            for(int b = 1;b<33;b++){
                                    Pixels[a][b] = false;
                                    }
                                    }
                                    break;
                                    } 
               else { 
                           //return from sub
                i = stack[SP] - 2; //again, it will be incremented by 2 further on so...
			    SP--; 
                    }

Another problem is my drawing never seems to work lol
I've made tons of tests and came to the conclusion that the mistake is somewhere inside the void DrawScene() function:
(I know it sucks balls, but please just tell me what's wrong with it)

Code:
void DrawScene(){
HDC hdc = GetDC(hwnd);
RECT rct[64][32];

for (int x = 1; x < 65; x++) {
    for (int y = 1; y < 33; y++) {
    if(Pixels[x][y] == 0) {

    rct[x][y].left= x*5+ 65;
    rct[x][y].right= x*5+ 60;
    rct[x][y].top= y*5+ 65;
    rct[x][y].bottom= y*5+ 60;
    FillRect(hdc, &rct[x][y], blackBrush);
}
    else
{

    rct[x][y].left= x*5 + 65;
    rct[x][y].right= x*5 + 60;
    rct[x][y].top= y*5 + 65;
    rct[x][y].bottom= y*5 + 60;
    FillRect(hdc, &rct[x][y], whiteBrush);
}
}
}

}

As for the program output, well, it prints out opcodes, and then crashes when the stack overflows.

Finally, the full code, to any adventurer who dare dig through it (lol)

Code:
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <time.h>
#include <windows.h>
#include <SDL.h> //I'm migrating to the windows API so this is temporary
#pragma comment( lib , SDL_image.lib )
#include "CPU_Timing.h"
#define MemorySize 0xfff
using namespace std;

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
char szClassName[ ] = "8 Chip F.A.I.L.";

ifstream::pos_type size;
ifstream file ("PONG", ios::in|ios::binary);

void Hash( unsigned short int, unsigned int ); //variable to be hashed, number of 'cuts'
unsigned int Power( unsigned int, unsigned int );
unsigned int ConvertDecimalToBinary( unsigned int );

void OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode);
void DrawScene();

SDL_Event event;
//globals, I know you will want to kill me, 
//but just lemme get this working, and I'll remove some of them
unsigned short OpcodeHash[3]; //we're breaking it in as many parts as needed
HBRUSH whiteBrush = CreateSolidBrush(RGB(255,255,255));
HBRUSH blackBrush = CreateSolidBrush(RGB(0,0,0));
HWND hwnd;  
bool Pixels[64][32]; //1 for a white pixel 0 for a black pixel!


    int WINAPI WinMain (HINSTANCE hThisInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpszArgument,
                    int nFunsterStil)

{

    //HWND hwnd;               /* Oops it had to become global for me to access it through
                               //the DrawScene();
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */
     srand((unsigned)time(NULL)); //Needed for the random numbers
    
    unsigned short FullOpcode;
    unsigned short X[7]; //8-bit
    unsigned short Y[7]; //8-bit
    
    unsigned int I; //memory crap
    int SP = 0; //Stack Crap
    unsigned int DT = 0; //Delay Timer
	unsigned int ST = 0; //Sound Timer
	unsigned int N; //drawing stuff
    unsigned int V[0xf]; //0xf = 15 in hex...So gay to write it that way but wth
    unsigned int key[0xf];
    unsigned int stack[16];
    int tempHash; //binary encoding
    unsigned int A, B, C; //Also for the binary encoding
    
    unsigned int xpos; //sprite xpos
    unsigned int ypos; //sprite ypos
    
    unsigned int i = 0x200; //we must start at the adress 0x200 in hex or 512 in dec

    
    
    char Memory[MemorySize];
    
    //init keys
	for (int o = 0; o < 16; o++){ key[o] = 0; }
    
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        return false;
    }
    
    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);
    

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    
    //read rom
    size = file.tellg();
    file.seekg (0, ios::beg);
    file.read (Memory, MemorySize);
    printf("Rom loaded succesfully.");

    /* Black background */
    wincl.hbrBackground = CreateSolidBrush(RGB(0, 0, 0));

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
           0,                   /* Extended possibilites for variation */
           szClassName,         /* Classname */
           "8 Chip F.A.I.L.",       /* Title Text */
           WS_OVERLAPPEDWINDOW, /* default window */
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           640,                 /* The programs width */
           320,                 /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           hThisInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nFunsterStil);


    /* Run the message loop. It will run until GetMessage() returns 0 */
    //while (GetMessage (&messages, NULL, 0, 0))
    while (true)
    {
          start = clock(); //let the ticking begin!
          //DrawScene(); Too buggy for me to put you on right now
          while (PeekMessage(&messages, hwnd,  0, 0, PM_REMOVE) == TRUE) {  
                
                if (messages.message == WM_QUIT)
                break;
 
                /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
        } 

           if(i > MemorySize){
                cout << "Memory Overflow!\n"; //lol wtf?
                //system("PAUSE");
                //i = 0x200;
                }
                
  //First fuse'em
    FullOpcode = ((Memory[i] << 8) | (Memory[i + 1])) ;
   
   cout << FullOpcode << "\n"; //keep track of the code

   
                
                
                
                /*almost all opcodes have to be broken into one 4-bit part and
                another 12-bit part, and then we decide if we break the 12-bit part or not
                so... to the bit grinding!!*/

 
   //Now hash once (don't forget flour and butter)
   Hash(FullOpcode, 1);
   //Switch!
   switch(OpcodeHash[0]){
    case 0x0:
         Hash(FullOpcode, 3);
         if(OpcodeHash[3] == 0x0) { //Clear the Screen 
                          for(int a = 1;a<65;a++){
                            for(int b = 1;b<33;b++){
                                    Pixels[a][b] = false;
                                    }
                                    }
                                    break;
                                    } 
               else { 
                             //return from sub (wtf?)
                i = stack[SP] - 2; 
			    SP--;
                
                    }
    break;
    case 0x1:                 //Get ready to JUMP!
    
         i = (OpcodeHash[1] - 2); //NNN - 2 (it will be incremented at the end of the loop)
         break;
         
   case 0x2:
             if(SP <= 15){
                   
                SP++;
				stack[SP] = i;
				i = OpcodeHash[1] - 2; //Set it to NNN
    }
                
				
         break;
   
    case 0x3:
         
         Hash(FullOpcode, 2);
         if( V[OpcodeHash[1]] == OpcodeHash[2] ) { //VX = NN?
             i += 2; //skip
             }
         break;
         
    case 0x4:
         
         Hash(FullOpcode, 2);
         if( V[OpcodeHash[1]] != OpcodeHash[2] ) { //VX != NN?
             i += 2; //skip
             }
         break;
         
    case 0x5:
         
         Hash(FullOpcode, 3);
         if( V[OpcodeHash[1]] == V[OpcodeHash[2]] ) { //VX = VY?
             i += 2; //skip
             }
         break;
         
    case 0x6:
         
         Hash(FullOpcode, 2);
         V[OpcodeHash[1]] = OpcodeHash[2]; //VX = NN
         break;
         
    case 0x7:
         
         Hash(FullOpcode, 2);
         V[OpcodeHash[1]] += OpcodeHash[2]; //VX = VX + NN
         break;
         
    case 0x8:
         
         Hash(FullOpcode, 3); 
         
         switch(OpcodeHash[3]){
                               
         case 0:
         V[OpcodeHash[1]] = V[OpcodeHash[2]]; //VX = VY
         break;
         
         case 1: //or
         V[OpcodeHash[1]] = (V[OpcodeHash[1]] | V[OpcodeHash[2]]); //VX or VY
         break;
         
         case 2: //and
         V[OpcodeHash[1]] = (V[OpcodeHash[1]] & V[OpcodeHash[2]]); //VX and VY
         break;
         
         case 3: //xor
         V[OpcodeHash[1]] = (V[OpcodeHash[1]] ^ V[OpcodeHash[2]]); //VX xor VY
         break;
         
         case 4:
         if((V[OpcodeHash[1]] & V[OpcodeHash[2]]) != 0) {
         //That means that if we are to sum these numbers (binary),
         //there will be at least one carry!
         V[0xf] = 1;
         }
         else
         {
         V[0xf] = 0;
         }
         
         V[OpcodeHash[1]] = V[OpcodeHash[1]] + V[OpcodeHash[2]];
         break;
         
         case 5:
              X[0] = V[OpcodeHash[1]] & 1;
              Y[0] = V[OpcodeHash[2]] & 1;
              if(Y[0] > X[0]){ //must be one luck guy
                      V[0xf] = 0;
                      }
              else{
              for(int a = 1; a < 7; a++){
              X[a] = V[OpcodeHash[1]] & Power(2, a);
              Y[a] = V[OpcodeHash[2]] & Power(2, a);
              if (Y[a] > X[a]){ //there's the borrow!
                       V[0xf] = 0;
                       break;
              }
              else
              {
                  V[0xf] = 1;
                  }
              }
              }
         V[OpcodeHash[1]] = V[OpcodeHash[1]] - V[OpcodeHash[2]];
         break;
         
         case 6:
              V[0xf] = (V[OpcodeHash[1]] & 1); //VF is the least significant bit of VX
              V[OpcodeHash[1]] >>= 1; //Shifts VX by 1 (right)
         break;
         
         case 7:
              X[0] = V[OpcodeHash[1]] & 1;
              Y[0] = V[OpcodeHash[2]] & 1;
              if(X[0] > Y[0]){ //must be one luck guy
                      V[0xf] = 0;
                      }
              else{
              for(int a = 1; a < 7; a++){
              X[a] = V[OpcodeHash[1]] & Power(2, a);
              Y[a] = V[OpcodeHash[2]] & Power(2, a);
              if (X[a] > Y[a]){ //there's the borrow!
                       V[0xf] = 0;
                       break;
              }
              else
              {
                  V[0xf] = 1;
                  }
              }
              }
         V[OpcodeHash[1]] = V[OpcodeHash[2]] - V[OpcodeHash[1]];
         break;
         
         case 0xe:
              V[0xf] = V[OpcodeHash[1]] & 128; //VF is set to the most significant bit of VX
              V[OpcodeHash[1]] <<= 1; //Shifts VX by 1 (left)
         break;
         }
         
    break;
         
    case 0x9:
         Hash(FullOpcode, 3); 
         if(V[OpcodeHash[1]] != V[OpcodeHash[2]]){ //VX != VY?
                             i += 2; //skip
                             }
         break;
         
    case 0xa:
         I = OpcodeHash[1]; //I = NNN
         break;
         
    case 0xb:
         i = OpcodeHash[1] + V[0] - 2; //jumps to NNN + V0...Has to be decremented because it will be
         //incremented at the end of the code
         break;
         
    case 0xc:
         Hash(FullOpcode, 2); 
         V[OpcodeHash[1]] = int((double(rand())/RAND_MAX)*255); //max of 8 bit
         V[OpcodeHash[1]] = V[OpcodeHash[1]] & OpcodeHash[2];
         break;
         
    case 0xd: //yay, let's draw!!! But soon, because right now we can't even plot a pixel...
        /* Hash(FullOpcode, 3); 
         xpos = V[OpcodeHash[1]];
         ypos = V[OpcodeHash[2]];
         N = OpcodeHash[3];
         V[0xf] = 0; //initially we assume there's no collision
         for (int z = 1; z < N + 1; z++){ 
             for (int v = 1; v < 9; v++){
         if (Pixels[z + xpos][v + ypos] = false){ //No pixel! Must draw!!
         Pixels[z + xpos][v + ypos] = true;
         }
         else
         {
             //flip 'em
         V[0xf] = 1; //There has been a collision! Still, we must (un)draw!!
         Pixels[z + xpos][v + ypos] = false;
         }
         }
         }
         break; */
         
    case 0xe:
         Hash(FullOpcode, 3);
         if (OpcodeHash[2] == 9){
         if (key[V[OpcodeHash[1]]] == 1){ 
         i += 2; //skip
         }
         }
         else{
         if (key[V[OpcodeHash[1]]] != 1){ //triple array subscrit ftw
         //Same shit, different day
         i += 2; //skip
         }
         }
         break;
         
    case 0xf: //long list...again!!!!
         Hash(FullOpcode, 2);
         switch(OpcodeHash[2]){
                               case 0x07:
                                    V[OpcodeHash[1]] = DT;  //VX = DT
                                    break;
                                    
                               case 0x0A: //INPUT! =O More to add soon...
                                    while (SDL_PollEvent(&event)) {
						            if (event.type == SDL_KEYDOWN){
								    switch (event.key.keysym.sym) {
                                                 case SDLK_UP:
                                                      key[0x8] = 1;
                                                      V[OpcodeHash[1]] = 0x8; 
                                                      
                                                 break;
                                                 case SDLK_DOWN:  
                                                      key[0x2] = 1;
                                                      V[OpcodeHash[1]] = 0x2; 
                                                 break;
                                                 case SDLK_LEFT:  
                                                      key[0x4] = 1;
                                                      V[OpcodeHash[1]] = 0x4;   
                                                 break;
                                                 case SDLK_RIGHT: 
                                                      key[0x6] = 1;
                                                      V[OpcodeHash[1]] = 0x6; 
                                                 break;
                                                 case SDLK_ESCAPE: 
                                                     // return 0; //quits
                                                 break;

                                          }

                                   }
                                    }
                                    break;
                                    
                               case 0x18:
                                    ST = V[OpcodeHash[1]]; //ST = VX
                                    break;
                                    
                               case 0x1E:
                                    I += V[OpcodeHash[1]]; //I = I + VX
                                    break;
                                    
                               case 0x29: //TODO (Unimplemented)
                                    break;
                                    
                               case 0x33: //BCD - Binary Coded Decimal (255) = (0010) (0101) (0101)
                                         //  
                                         //                                   2       5      5 
                                    tempHash = V[OpcodeHash[1]];
                                    A = (tempHash / 100);  //hundred
                                    tempHash = tempHash - A*100;
                                    B = tempHash/10;  //dozen
                                    tempHash = tempHash - B*10; //unit
                                    C = tempHash;
                                    // Now that the number is split, we may encode it!     
                                    Memory[I] = ConvertDecimalToBinary(A);
                                    Memory[I + 1] = ConvertDecimalToBinary(B);
                                    Memory[I + 2] = ConvertDecimalToBinary(C);
                                    break;
                               case 0x55:
                                    for(int h = 0; h < OpcodeHash[1] + 1; h++){
                                            Memory[I] = V[h];
                                            I++;
                                            }
                                    break;
                               case 0x15:
                                    DT = V[OpcodeHash[1]]; //DT = VX
                                    break;
                               case 0x65:
                                    for(int h = 0; h < OpcodeHash[1] + 1; h++){
                                             V[h] = Memory[I];
                                            I++;
                                            }
                                    break;
                                    }
         break;
         }

    i += 2;    //Increment 2 bytes to the CPU.
    end = clock();
    cpuTime= (end-start)/ (CLOCKS_PER_SEC);
    delay( 1000/60 - cpuTime ); //60 hertz! No matter what pc!!
    //The above makes sure that to any PC, there will be a 1/60 second stop after each
    //instruction. That makes the emu run @ 60hz. Also syncs with the delay timers.
    DT = DT + 1; //Increment delay timer
    ST = ST + 1; //Increment sound timer
    
    
}
    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;

}

/*  This function is called by the Windows function DispatchMessage()  */

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  /* handle the messages */
    {
        case WM_DESTROY:
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
            break;
        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
    }
    file.close();
    return 0;
}
/*void Hash( unsigned short int opcode, unsigned int n) {
//n ranging from 1 to 3
     switch(n){
     case 1:
     OpcodeHash[0] = opcode >> 12;
     OpcodeHash[1] = opcode << 4;
     OpcodeHash[1] >>= 4;
          break;
     case 2:
     OpcodeHash[0] = opcode >> 12;
     OpcodeHash[1] = opcode << 4;
     OpcodeHash[1] >>= 12;
     OpcodeHash[2] = opcode << 8;
     OpcodeHash[2] >>= 8;
          break;
     case 3:
     OpcodeHash[0] = opcode >> 12;
     OpcodeHash[1] = opcode << 4; 
     OpcodeHash[1] >>= 12;
     OpcodeHash[2] = opcode << 8;
     OpcodeHash[2] >>= 12;
     OpcodeHash[3] = opcode << 12;
     OpcodeHash[3] >>= 12;
          break;
     }
}*/
void Hash( unsigned short int opcode, unsigned int n) {
//n ranging from 1 to 3
     switch(n){
     case 1:
     OpcodeHash[0] = opcode & 0xf000;
     OpcodeHash[0] >>= 12;
     OpcodeHash[1] = opcode & 0x0fff;
          break;
     case 2:
     OpcodeHash[0] = opcode & 0xf000;
     OpcodeHash[0] >>= 12;
     OpcodeHash[1] = opcode & 0x0f00;
     OpcodeHash[1] >>= 8;
     OpcodeHash[2] = opcode & 0x00ff;
          break;
     case 3:
     OpcodeHash[0] = opcode & 0xf000;
     OpcodeHash[0] >>= 12;
     OpcodeHash[1] = opcode & 0x0f00;
     OpcodeHash[1] >>= 8;
     OpcodeHash[2] = opcode & 0x00f0;
     OpcodeHash[2] >>= 4;
     OpcodeHash[3] = opcode & 0x000f;
          break;
     }
}
unsigned int Power( unsigned int power, unsigned int exponent ) {
         int base = power;
         int p = 1;
         while(p < exponent) {
                 power *= base;
                 p++;
         }
return power;
}
unsigned int ConvertDecimalToBinary( unsigned int decimal ){
         
    int a = 0;
    int b = 0;
    int c = 0;
    int d = 0;
    unsigned int binary;
    if (decimal & 1 == 1 ) { a = 1;}
    decimal >>=1;
    if (decimal & 1 == 1 ) { b = 1;}
    decimal >>=1;
    if (decimal & 1 == 1 ) { c = 1;}
    decimal >>=1;
    if (decimal & 1 == 1 ) { d = 1;}
    decimal >>=1;
    binary = a*1 + b*10 + c*100 + d*1000; 
    return binary;
}
void DrawScene(){
HDC hdc = GetDC(hwnd);
RECT rct[64][32];

for (int x = 1; x < 65; x++) {
    for (int y = 1; y < 33; y++) {
    if(Pixels[x][y] == 0) {

    rct[x][y].left= x*5+ 65;
    rct[x][y].right= x*5+ 60;
    rct[x][y].top= y*5+ 65;
    rct[x][y].bottom= y*5+ 60;
    FillRect(hdc, &rct[x][y], blackBrush);
}
    else
{

    rct[x][y].left= x*5 + 65;
    rct[x][y].right= x*5 + 60;
    rct[x][y].top= y*5 + 65;
    rct[x][y].bottom= y*5 + 60;
    FillRect(hdc, &rct[x][y], whiteBrush);
}
}
}

}

Well that's all folks, thanks in advance!
 

rcvalle

New member
Hi,

I did a quick implementation of a CHIP-8 interpreter based on available documentation. I used ANSI C and the SDL library, and I took about 24 hours to complete. However, the keyboard inputs are not working and I do not have time to fix it.

It is available at:
http://github.com/rcvalle/chip8

Best regards,
 

royboyjoytoy2

New member
[Please help]Making a Chip 8 Emulator in Java/AWT

What I am looking for is help with making a drawing method and fixing parts of my cpu emulation. I have spent about three hours trying to figure our how the drawing method works, but I wasn't able to get it emulated. I do have a 2D boolean array that does draw correctly to the screen (you can play with it if you want), all I need to do is figure out how to store/parse the data properly in the array. Some of my other methods might not be correct, If I could get help on this we could have a great applet-based Open Source CHIP-8 emulator :)

my current source code:


a quick look at my Chip-8 CPU Emulation method:
Code:
    public void doOpcode(int code) { //Chip8 CPU Emulator
    if(chipVarDT!=0) {
        chipVarDT -= 1;
    }
    if(chipVarST!=0) {
        chipVarDT -= 1;
    }
        String cstring = Integer.toHexString(code);
        boolean didOpcode = false;
        //log("called:"+Integer.toHexString(lastCode)+"::"+Integer.toHexString(code));
        //log("I"+Integer.toHexString(chipVarI));
        switch(code & 0xF000) { //Check first bit
            case 0x1000: //1NNN;	Jumps to address NNN.
              chipCount = (code & 0x0FFF);
              didOpcode = true;
                break;
            case 0x2000: //2NNN;	Calls subroutine at NNN.
              chipCopyCount[cccAt] = chipCount;
              cccAt += 1;
              chipCount = (code & 0x0FFF);
              didOpcode = true;
                break;
            case 0x3000: //3XNN;	Skips the next instruction if VX equals NN.
              if(chipReg[subBit(code, 2)]==(code & 0x00FF)) {
                  chipCount += 2;
              }
              didOpcode = true;
                break;
            case 0x6000: //6XNN;	Sets VX to NN.
              chipReg[subBit(code, 2)] = (code & 0x00FF);
              didOpcode = true;
                break;
            case 0x7000: //7XNN;	Adds NN to VX.
              chipReg[subBit(code, 2)] += (code & 0x00FF);
              didOpcode = true;
                break;
            case 0x8000: //8000; Math functions
              switch(code & 0x000F) {
                  case 0: //8XY0;	Sets VX to the value of VY.
                    chipReg[subBit(code, 2)] = subBit(code, 3);
                      break;
                  case 1: //8XY1;	Sets VX to VX or VY.
                    chipReg[subBit(code, 2)] = chipReg[subBit(code, 2)] | chipReg[subBit(code, 3)];
                      break;
                  case 2: //8XY2;	Sets VX to VX and VY.
                    chipReg[subBit(code, 2)] = chipReg[subBit(code, 2)] & chipReg[subBit(code, 3)];
                      break;
                  case 3: //8XY3;	Sets VX to VX xor VY.
                    chipReg[subBit(code, 2)] = chipReg[subBit(code, 2)] ^ chipReg[subBit(code, 3)];
                      break;
                  case 4: //8XY4;	Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't.
                    chipReg[0xF] = 0;
                    int value = subBit(code, 2) + subBit(code, 3);
                  	if(value > 255) {
                  		chipReg[0xF] = 1;
                    }
                    chipReg[subBit(code, 2)] = chipReg[subBit(code, 2)] + chipReg[subBit(code, 3)];
                  case 5: //8XY5;	VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
                    chipReg[0xF] = 1;
                    if (chipReg[subBit(code, 2)] < chipReg[subBit(code, 3)]) {
		                   chipReg[0xF] = 0;
		                }
                    chipReg[subBit(code, 2)] = chipReg[subBit(code, 2)] - chipReg[subBit(code, 3)];
                      break;
                  case 6: //8XY6;	Shifts VX right by one. VF is set to the value of the least significant bit of VX before the shift.
                    chipReg[0xF] = chipReg[((code & 0x0F00)>>8)] & 0x1 ;
                    chipReg[subBit(code, 2)] >>= 1;
                  case 7: //8XY7:	Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
                    chipReg[0xF] = 1;
                    if (chipReg[subBit(code, 3)] < chipReg[subBit(code, 2)]) {
		                   chipReg[0xF] = 0;
		                }
                    chipReg[subBit(code, 3)] = chipReg[subBit(code, 3)] - chipReg[subBit(code, 2)];
                      break;
                  case 0xE: //8XYE	Shifts VX left by one. VF is set to the value of the most significant bit of VX before the shift.
                    chipReg[0xF] = chipReg[((code & 0x0F00))>>8]>>7;
                    chipReg[subBit(code, 2)] >>= 1;
                      break;
              }
                break;
            case 0x9000: //9XY0;	Skips the next instruction if VX doesn't equal VY.
              if(chipReg[subBit(code, 2)]!=chipReg[subBit(code, 3)]) {
                  chipCount += 2;
              }
              didOpcode = true;
                break;
            case 0xA000: //ANNN; Sets I to the address NNN.
              chipVarI = (code & 0x0FFF);
              didOpcode = true;
                break;
            case 0xB000: //BNNN;	Jumps to the address NNN plus V0.
              chipCount = (code & 0x0FFF)+chipReg[0x0];
              didOpcode = true;
                break;
            case 0xC000: //CXNN;	Sets VX to a random number and NN.
              chipReg[subBit(code, 2)] = (new Random()).nextInt() & (code & 0x00FF);
              didOpcode = true;
                break;
            case 0xD000: //DXYN;	Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N pixels. As described above, VF is set to 1 if any screen pixels are flipped from set to unset when the sprite is drawn, and to 0 if that doesn't happen.
              OpcodeDXYN(code);
            case 0xE000: //Keyboard input
              switch(code & 0x00FF) {
                  case 0x9E: //EX9E;	Skips the next instruction if the key stored in VX is pressed.
                    if(true) {
                        chipCount += 2;
                    }
                      break;
                  case 0xA1: //EXA1;	Skips the next instruction if the key stored in VX isn't pressed.
                    if(false) {
                        chipCount += 2;
                    }
                      break;
              }
              didOpcode = true;
                break;
            case 0xF000: //Various
              switch(code & 0x00FF) {
                  case 0x07: //FX07;	Sets VX to the value of the delay timer.
                    chipReg[subBit(code, 2)] = chipVarDT;
                      break;
                  case 0xA1: //FX0A;	A key press is awaited, and then stored in VX.
                    chipReg[subBit(code, 2)] = 0xF;
                  case 0x15: //FX15;	Sets the delay timer to VX.
                    chipVarDT = chipReg[subBit(code, 2)];
                      break;
                  case 0x18: //FX18;	Sets the sound timer to VX.
                    chipVarST = chipReg[subBit(code, 2)];
                  case 0x1E: //FX1E;	Adds VX to I.
                    chipReg[subBit(code, 2)] = chipReg[subBit(code, 2)]+chipVarI;
                      break;
                  case 0x29: //FX29;	Sets I to the location of the sprite for the character in VX. Characters 0-F (in hexadecimal) are represented by a 4x5 font.
	                   chipVarI = chipReg[subBit(code, 2)]*5;
                      break;
                  case 0x33: //FX33;	Stores the Binary-coded decimal representation of VX at the addresses I, I plus 1, and I plus 2.
                  	int value = chipReg[subBit(code, 2)];
                  	int hundreds = value / 100;
                  	int tens = (value / 10) % 10;
                  	int units = value % 10;
                  
                  	chipReg[chipVarI] = hundreds;
                  	chipReg[chipVarI+1] = tens;
                  	chipReg[chipVarI+1] = units;
                      break;
              }
              didOpcode = true;
                break;
        }
        switch(code) { //Check whole code
            case 0x00E0: //00E0;	Clears the screen.
              chipPix = new boolean[64][32];
              didOpcode = true;
                break;
            case 0x00EE: //00EE;	Returns from a subroutine.
              cccAt -= 1;
              chipCount = chipCopyCount[cccAt];
              didOpcode = true;
                break;
        }
        if(!didOpcode) { 
            runCpu = false;
        } else {
            lastCode = code;
        }
    }
 

io-grey

New member
hi guys.
got a few questions.

-to which memory address do you load the game/rom data?

-at which address is the first instruction?
in cowgod's guide. it mentions 0x200 (most of the time) and 0x600 (sometimes for the ETI 660 computer). is there a way to determine the right address?

greetings
 
Last edited:

Tronix

New member
Hi, all.

Today i done my Delphi CHIP-8 emulator. I added SCHIP support, fixed annoying bug with keyboard, rewrite draw function and more...

During testing, I wrote simple TEST program for (S)CHIP-8 emulators. This program contain 26 tests, including:
FX65 instruction test
Font 8x5 test
BCD instruction test
Math operation tests ADD,SUB,SHL,SHR,XOR (carry flag, borrow)
HP48 flags test
FX1E instruction (buffer overflow test)

Full assembler source code included in archive.

This is my Delphi emulator. All tests passed:
ch1.png


And this David Winter CHIP8 emulator. Also all tests done:
ch11.png


Some emulators from this topic:
ch2njn.png

ch3.png

ch4.png

ch5.png

ch6.png

ch8.png



SCTEST

Small programm for test (S)CHIP-8 emulators. If all test passed, you see
"OK" on upper left corner, else programm print ERROR and error number.
Written by Sergey Naydenov, e-mail: tronix286[dog]rambler.ru (c) 2010

Errors:

ERROR INI
Emulator initialization failed. When program start, all registers
must be set to null.

ERROR BCD
BCD instruction problems.

ERROR 0
Problems with Fx65 instruction. Can't load zeroes from memory to
registers.

ERROR 1
System font 8x5 not found. In memory at offset 000h - zeroes.

ERROR 2
Addiction without overflow (254+1). VF register need to be set 0,
but after operation he still 1

ERROR 3
After operation 254+1, register v0 need set to 255, but he does't.

ERROR 4
Addiction with overflow (255+1). VF register must be set to 1, but
after operation he still 0

ERROR 5
Wrong result after addiction operation (255+1). Must be 0.

ERROR 6
After substraction 1-1 register VF must be 1, but he still 0.

ERROR 7
Wrong result after 1-1 operation. Result must be 0.

ERROR 8
Substract 0-1. VF register must be 0, but he still 1

ERROR 9
Wrong result after 0-1 operation. Register v0 must be 255.

ERROR 10
... skip ...
..sorry, i don't have time for writing all descriptions. Please see
source code SCTEST.C8..

ERROR 23
Can not restore HP48 flags (FX75/FX85 instructions).

ERROR 24
Check FX1E (I = I + VX) buffer overflow. If buffer overflow, register
VF must be set to 1, otherwise 0. As a result, register VF not set to 1.
This undocumented feature of the Chip-8 and used by Spacefight 2019!
game.

I can't attach file to this forum (i get "cant' copy/move file" error message).
You can download this test by clicking on SCTEST_12.ZIP from here: http://rghost.ru/2304760
 
Last edited:

Tronix

New member
Hello guys!

I apologize in advance for my English, because it's not my native language.

Recently i had a crazy idea - write a Pascal-like compiler for CHIP8.
So, i want to present you the alpha version of C8PASCAL :) My compiler (or translator, if you want) generate ASM listing, witch can be compiled using CHIPPER V2.11 by Christian Egeberg. Let me show you simple example program, written in C8PASCAL:

Code:
Program Example2;
{ C8PASCAL example - division without using DIV operator }

Var
 q,x,y,a,b,counter : byte;

Begin
      x := 20;               {First number}
      y := 4;                {Second number. As result we can get X DIV Y}
      q := 0;                {begin calculate}
      while x >= y do
            Begin
                  a := x shr 1;
                  b := y;
                  counter := 1;
                  while a >= b do
                        begin
                              b := b shl 1;
                              counter := counter shl 1;
                        end;
                  x := x - b;
                  q := q + counter;
            End;
      Debug(0,0,q);        {Print result X DIV Y}

      x := 20;      	   {First number}
      y := 4;              {Second number}
      Debug(0,10,x div y); {Print result using build-in DIV operator}
end.

Other examples in archive. This is screenshot of second example program - "calculate square root", written in C8PASCAL too:
hchip8.png


This example rendering Serpinski triangle:

serpinski.png


Code:
CHIP8 Pascal (C8PASCAL)

Version: 0.3 alpha - Very simple and slowly, becouse it is Alpha.

Written by Sergey Naydenov (c) 2010
e-mail: tronix286[dog]rambler.ru

C8PASCAL translate Pascal source code into CHIP8 assembler listing,
which can be compiled using CHIPPER V2.11 by Christian Egeberg.

===========================================
1) RUN-TIME ERRORS. RANGE CHECKING. MEMORY.
===========================================
C8PASCAL not support runtime range checking. It's mean, you need
be very careful, when you operate with variables. Also, C8PASCAL do not
overflow checking. Let's look at example:
   Var
     a,b,c : Byte;
     mas   : Array [0..7];  {8 bytes length array}
     d     : Byte;          {this variable comes immediately after the array}
   Begin
     A := 250;
     B := 6;
     C := A + B;   {here we get overflow. }
                   {As a result C contain 0; not 256}
     B := B - 6;   {B = B - 6 = 0}
     C := A div B; {Divide by zero. As a result - infinite loop or crash}
     a := Mas[20]; {range overflow. As result, in var "A" - random data}
     Mas[8] := 55; {range overflow. As result, in var "D" - 55}

CHIP8 has 3584 bytes RAM memory (#FFFh - #200h = #E000h). For
operating with variables C8PASCAL used own stack, located at the end of 
the program. Typically, the stack must have 10-12 bytes free, but if 
stack overflow, program crashed. Therefore it is desirable not to exceed 
the program size of 3000-3200 bytes.

============
2) VARIABLES
============
C8PASCAL can operate with BYTE (unsigned integer 8-bit) type only. So, all
variables must be defined as Byte. 
Ex.: 
     Var
         i     : Byte;            {loop variable}
         a,b,c : Byte;            (* some variables *)

============
3) CONSTANTS
============
Constants are well used for sprites or initial values of variables. The 
constants do not differ from the variables in terms of the main program.
You can change them in the course of the your program. Constants section
should be located before the variables section.
Ex:
     Const
        Sprite : Array [0..3] = (
                                 #01100110,  {Sprite 8x4. Two vertical}
                                 #01100110,  {lines}
                                 #01100110,
                                 #01100110
                                );
        FirstNumber  : Byte = 64;            {simple Byte constant}
        SecondNumber : Array [0..0] = ($40); {array with 1 element.}
                                             {hex $40 = 64 dec}
     Begin
        Debug(10,10,FirstNumber);            {draw 064 at X=10, Y=10}
        FirstNumber := FirstNumber + SecondNumber[0];
                                             {FirstNumber = 64+64=128}
        Debug(40,10,FirstNumber);            {draw 128 at X=40, Y=10)
        DrawSprite(10,20,4,^Sprite[0]);      {draw vert lines at X=10, Y=20}
     End.

=========
4) ARRAYS
=========
This C8PASCAL version support one-dimensional arrays only. Arrays allways
starting with 0 (zero) to N. Each array element has Byte type. Maximum array
size is 255 bytes. You don't needed writing "of Byte;" after array 
defination. If you type something like that: "mas : array [0..7] of Byte;" 
you get an error.
Ex.:
     Var
         mas   : Array [0..7];    {first sprite. Array size = 8 bytes}
         temp  : Array [10..15];  {ignore first number 10 and set array to}
                                  {16 bytes long}

In program you can operate arrays with help "[" and "]" token.
Ex.:
     Mas[0] := 2;       {set first array element to 2}
     a := Mas[i-1];     {let variable "a" contain "i-1" array element}
     b := Mas[(a div 3)+15];
Warning! No range checking there. If you define array as "mas : array [0..7];" 
and then try to do "a := mas[127];" you get random data in "a".

==================================
5) DIVISION AND MULTIPLY OPERATORS
==================================
Originaly CHIP8 don't have division and multiplication commands. C8PASCAL
software emulate this operation. Therefore, these operations very slowly.
If you want to divide or multiply by a power of two, please use SHR and
SHL operation.
Ex.:
    a := a div b;                {division}
    a := a mod b;                {the remainer of division}
    a := c * b;                  {multiply}

================
6) SHIFTING BITS
================
In original CHIP8 SHR and SHL shifting bits by one. For compartable, C8PASCAL
support shifting by any number and generate "SHL VX, VY" instruction for
CHIPPER. As far as i know, all emulators ignore "VY" and shifting "VX" by one.
So, if you need shifting more than one bits, you need do SHR/SHL operation
more times.
Ex.:
   a := a shr 1;                 {a = a div 2}
   a := a shl 1;                 {a = a * 2}
   a := a shr 4;                 {the same as "a shr 1". last argument (4) }
                                 {ignored by many emulators}
   a := (((a shr 1) shr 1) shr 1) shr 1; { a = a shr 4 }

==========
7) LOGICAL
==========
C8PASCAL support primitive logic only.
Ex.:
   If a = b then                 {if a equal b}
   If a <> b then                {if a not equal b}
   If a > b then                 {if a greater then b}
   If a < b then                 {if a less then b}
   If a >= b then                {if a greter or equal b}
   If a <= b then                {if a less or equeal b}
   If (a mod 2) = 0 then         {if a odd then ...}

===========================
8) PROCEDURES AND FUNCTIONS
===========================
Not support functions (yet). Simple support for procedures, without
parameters.
Ex:
   Var
	a : byte;

   Procedure InitVariable;    {initialize A variable}
   Begin
     a := 255;
   End;

   Begin                      {MAIN programm}
     InitVariable;            {Call InitVariable procedure}
     Debug(a);                {Print 255}
   End.

===========================
9) SPECIAL CHIP8 PROCEDURES
===========================

Procedure DrawSprite(X,Y, N : Byte; Sprite : Pointer);
Draw sprite at X,Y position, N lines. N must be constant.
Ex:
   Var
         i      : Byte;         {loop variable}
         OneDot : Byte;         {one dot mask. It's 8x1 sprite!}
         Fill   : Array [0..7]; {8x8 sprite}
   Begin
     OneDot := $80              {"1......." in binary. Its our one line sprite}
     For i := 0 to 7 do
        Fill[i] := #FF;         {fill 8x8 sprite. "11111111" in binary}
     DrawSprite(0,0,1,^OneDot); {draw 8x1 sprite (dot) in upper left corner}
     DrawSprite(30,20,8,^Fill[0]); {draw filled rectangle at X=30, Y=20}
   End.

Procedure DrawChar(X,Y, FntSize, Number : Byte);
Draw number symbol (from $0 to $F) at XY pos. If FntSize = 5 then draw 8x5
sprite and if FntSize = 10 draw s-chip 16x10 (8x10 in chip8 mode) sprite.
FntSize must be constant.
Ex:
   Var
      Num : Byte;
   Begin
      Num := 2;
      DrawChar(10,10,5,Num);    {Draw "2" on the screen}
   End.

Procedure Debug(X, Y, Num : Byte);
Print decimal number at XY position.
Ex:
    Var
      Num : Byte;
    Begin
      Num := 64 div 2;
      Debug(0,0,Num);           {Print 032 at upper left corner}
    End;

Procedure Cls;
Clear the screen.

Procedure SetHigh;
Set 128x64 SCHIP-8 mode.

Procedure SetLow;
Set 64x32 CHIP-8 mode.

Procedure Scroll_Left;
Scroll screen 4 pixels left.

Procedure Scroll_Right;
Scroll screen 4 pixels right.

Procedure Scroll_Down(N : Byte);
Scroll screen N lines down. N must be constant.

Function Random(N : Byte);
Get random number AND N.

Procedure Delay(N: Byte);
Set delay timer to N.

In next versions: ByteToBCD, LoadFont, etc...

========
10) TO DO
========

Bug fixing, Input support, Local variables, Functions, Procedure and Functions 
parameters, standart library functions, integer (signed) variable, 
code optimization, and much more...
If you want to join this project, please contact me by e-mail.

===========
11) HISTORY
===========
  v0.1 alpha 07.08.2010
     +  First alpha reliase. Basic functions.
  v0.2 alpha 13.08.2010
     +  Added constants support;
     +  Added SetHigh, SetLow, Cls, Scroll_Left, Scroll_Right, 
        Scroll_Down(N:byte) system procedures;
     +  Added to examples Serpinski triangle drawing programm
     *  First optimization for generated code. If possible, use free 
        registers with stack operations;
     *  Fix token parser - when variable begins from the reserved words,
        compiler gave an error;
  v0.3 alpha
     +  Added Random(N) function, Delay(N) procedure;
     +  Added stars demo example.
     *  Fix bug with arrays pointer.
     *  Fix REPEAT-UNTIL bug.

You can download C8PASCAL from here: http://rghost.ru/2341789 by clicking on C8PASCAL_03a.zip.
 
Last edited:

overclocked

New member
Hardware for Chip-8

Hi,

This is my first post here. It looks like some good chip-8 dudes have posted here so I'm going give my question a chance. I'm a C# programmer as a profession, but fiddles with hardware as a hobby. My last summer vacation project was a hardware chip-8 implemented in a single FPGA. I think it is one of the first of its kind so that's nice. It connects to a VGA-monitor for graphics and a Atari/Amiga-joystick for control.

Things are starting to work out quite nice but I have a timing problem that seems very hard to fix. The games I have tried out this far is:

BLITZ
BLINKY
SPACE INVADERS
MISSILE
TETRIS
HIDDEN

After reading up on timing I first locked the main frequency to approx. 400Hz (400 chip-8 instructions/second) and made the timer decrease every 1/60s. This works for some games but not all. Now I've embedded info for every game to get its own Hz(100-1000Hz) and also a divider to always lock the TimerRegister to approx. 60Hz regardless of main frequency. It still does not work well with for example HIDDEN.

Have somebody seen the same problem with software emulators? Is there somebody who made a table of settings that work well for all/some chip-8 games? Also when trying out different emulators with the game HIDDEN I've seen different behaviors with keyboard control. Should the cursor move one square at a time or move several squares at a time without requiring neutral keyboard value(no key pressed) in between.

Magnus
 

Tobiy

New member
Most Chip-8 games work well with about 14 instructions/timerupdate (840 instructions/second).
The waitkey-instruction reset the key state before waiting so the cursor should move one square at a time.

Tobiy
 

overclocked

New member
Thanks for the tip Tobiy, that was one of the "bugs" still left in the chip-8 core! Because of the way the hardware is connected, I now wait for NO keypress and then wait for the specific keypress on the FX0A-instruction. Seem to work great. Regarding the timing I try 840 inst/sec and it looks and works quite OK with all games I tried this far with the above keyboardfix.

Next up is sound output. Sound register and opcode-implementation already done but actual sound signal left to fix.

Then I will try to add SuperChip opcodes and graphics!

Some other glitches present is that BLITZ have two problems.

1) The original BLITZ always draw the skyscrapers one pixel too long which end up showing at the top of the screen making the plane run directly into these pixels and get "Game Over" right away. I've read some other emulators writers having problems with this one and as I understand it is a bug the the original code, but thanks to the original implementation not having "Sprite y-wrapping" the extra pixels never showed. I now use a fixed version of the ROM instead. Any thoughts about this?

2) Due to the fact that I'm using a static LFSR-register to implement the random instruction CXNN the skyline in BLITZ always start the same way. In my start sequence this means a skyscraper is drawn in the very left pixelrow (x=0). Is this normal? When the plane wraps around the X, the game automatically lower the plane 1 row in Y. This also mean that when trying to bomb the left-most skyscraper the plane now kills itself because the plane is lowered at the same time. How is this supposed to work? Anyone know anything, that would be great!

Thanks!
 

overclocked

New member
Yo, I don't know how many users that actually reads this long thread but I'll keep adding questions..

Does anybody know about a really good and exact Chip-8 emulator implementation? As a step in building the first CHIP-chip-8 (that is the first implementation using logic gates), I though I would try out writing something in assembler myself. To be able to do this I needed a tool chain for Chip-8 assembler. I've chosen the original Chipper assembler and a great Chip-8 emulator written in JAVA and called JChip8BR.

The chosen chip-8 program is a Monitor/HexMemEditor. Its already working as a HexViewer and just some details left before it can be used to Edit HexValues in memory. BUT, the only emulator I've got that actually can run it is my fixed JChip8BR. Anyone got another GOOD one I could try?

Tronix, I tried your genius sctest program on the JChip8BR and found 3 bugs in the opcodes. After fixing those, the program shows "OK", so I think its probably quite OK now. Is the author listening? Great work you've done. I've fixed up some good updates for the emulator that I'm willing to Commit to GoogleHosting if I were to get membership in the project.
Some updates:
1) Done refactoring to seperate disassembly/execution of chip8-instructions. Switched the const int's in Instruction.java for a new enum eInstruction. This enum holds both the opcode-hexcode, the string and the bitmask used to identify it. It also holds the specific opcode-value. The num also includes a static method to convert from int to eInstruction.
2) New generic/configurable methods to format chip8-addresses/values.
3) Switched fonts from monospaced to fixed size which is much better when formatting source code output. Much better and more output in Memory window.
4) Added a Restart/Reset button/method which does a cold boot (loads the last used file from disk again, resets all registers)
5) Added command-line support for a single parameter consisting of a full path to a chip8-binary file. The emulator now initializes (but does not execute) directly when started.
6) Fixed some misspellings in the source code.
7) Added simple handling of Breakpoints, just double-click in the disassembly window to Toggle breakpoint.
8) Now possible to change register values any time, for example when doing debugging/stepping, even the PC (program counter) can be changed.
 

xdaniel_FWB

Living RCP Hungup
Dusting off my account here (last post over 4 years ago, heh), here's the next person to have written a Chip-8 interpreter:

6fpA7.png
d3wXO.png

FNsLD.png
py6b2.png


- Written in C, using SDL, SDL_ttf and SDL_gfx
- Appears to be compatible with every regular Chip-8 game I've thrown at it

Download (Win32 binary): http://code.google.com/p/ozmav/downloads/detail?name=Azunyan8_v1.rar

Usage is simple; if the interpreter is started without any command line arguments, you get a file selection dialog (see screenshot 1) which allows you to open a Chip-8 program. In-game, you can press F11 to have a look at the remaining controls, while pressing F12 shows the about box and ESC exits the program. Here's hoping I didn't forget anything...

I will probably be trying to build the program on Linux, as well as post the source code later :)
 

MicBeaudoin

New member
Hello,

here is a little update of my old emu(Chiplol), it is now an upgraded version with a pseudo-3d rendering(change with Ctrl+2). I did it with two other people for a school project.

We compiled it for PSP and managed to... play online between a PSP and a PC! So useless lol(but funny)! It worked with the help of a java server, which I don't have anymore unfortunately.

It passes all tests in of SCTEST.
It's programmed in C++(the emu core is totally portable) with Qt for gui, that's why the download is a little bit big(big dlls)
Btw, it's all in french, sorry for this, I didn't translate it yet.
Here is a screenshot:
http://bayimg.com/FaPgMAACg

And the download(windows, about 3MB):http://rghost.net/2740686
 

david winter

New member
Hi there,

I thought I would never come back to my old 1990s MS-DOS Chip-8 emulator but my curiosity went too far. Luckily I picked an old CDROM with my sources. Got a headake to have MS-DOS booting from a floppy as I can't boot on a USB key. But I rapidly got sick of the old MS-DOS command line prompt and quickly wamped the whole thing from the hard drive.

Which brings me two questions for the heck of it...

1) What's the most accurate Windows-based emulator so far ? I'd like to play a few games for the old fun (both CHIP-8 and Schip).
2) If I had to port my sources to Windows for the challenge, what language would you recommend ?

Finally, are the original Cosmac-VIP games online ? I still have the old hex dumps on my CD and could post them here.
So far as I know, only one or two programs used a call to some 1802 machine code.

Any thoughts ? Thanks!
 

smcd

Active member
Hi there,

I thought I would never come back to my old 1990s MS-DOS Chip-8 emulator but my curiosity went too far. Luckily I picked an old CDROM with my sources. Got a headake to have MS-DOS booting from a floppy as I can't boot on a USB key. But I rapidly got sick of the old MS-DOS command line prompt and quickly wamped the whole thing from the hard drive.

...

Any thoughts ? Thanks!

thought i'd derail the thread a bit and respond to the emboldened text- if it's because your bios doesn't support usb booting... never fear! i had an old laptop and put Plop boot manager on it and it works fine, albeit with a couple more steps than official bios support :) http://www.plop.at/en/bootmanager.html
 

Top