CHIP-8 Port pt 2 - Machine Object

The CHIP-8 machine is pretty simple. Here is a list of things it will do:

  1. Own the emulator.

  2. Initialize it

  3. Put the program into its memory

  4. Call the CPU emulator the right amount of times per second to get the speed right

  5. Decrement CHIP-8's timers at the appropriate frequency (60Hz)

  6. Manage the CHIP-8 emulator keyboard data structure

  7. Give a pointer to CHIP-8's screen data to the display code

The interface file

   #include "chip8emu.h"

   @interface Chip8Machine : NSObject    
   {    
       Chip8State   *state;    

       double      lastTimer;    
       double      lastTick;    

       NSTimer     *emulatorTimer;    

   }    

   -(void) ReadFile:(NSString*)filename IntoMemoryAt:(uint32_t)memoffset;    
   -(id) init;    
   -(double) timeusec;    

   -(void) doCPU;    
   -(void) startEmulation;    

   - (void) KeyDown: (uint8_t) key;    
   - (void) KeyUp: (uint8_t) key;    

   -(void *) framebuffer;    

Calling the CPU

We'll use a Cocoa run loop timer to get time periodically. When the "doCPU" routine is called, I'll execute the number of instructions the real machine would execute in that amount of clock time.

   -(double) timeusec    
   {    
       struct timeval time;    
       gettimeofday(&time, NULL);    
       return ((double)time.tv_sec * 1E6) + ((double)time.tv_usec);    
   }

   -(void) doCPU    
   {    
       double now = [self timeusec];    

       if (lastTick == 0.0)  //first run    
           lastTick = now;    

       [self handleTimers:now];    
       int cycles_to_catch_up = (1800.0 * ((now - lastTimer)/1e6));    
       int cycles = 0;    

       while (cycles_to_catch_up > cycles)    
       {    
           cycles += 1;    
           EmulateChip8Op(state);    
           //Could handle timers here    
           //[self handleTimers:[self timeusec]];    

       }    
       lastTimer  = now;    
   }    

   - (void) startEmulation    
   {    
       emulatorTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1    
                                                    target: self    
                                                      selector:@selector(doCPU)    
                                                      userInfo: nil repeats:YES];    
   }    

For cycles_to_catch_up, I have just picked a value that I like. I can not find a canonical reference to say exactly what frequency it is supposed to run at, or how many cycles each instruction is supposed to take. I made it slow enough that I can watch the blips draw - it somehow seems to fit these programs.

Decrementing the timers

The "documentation" of the CHIP-8 timers says they count down to zero at a 60Hz rate; or in other words the counter will decrease by 60 each second. I am only calling this once per run-loop timer, but sometimes more than 1/60 of a second has passed. This code handles that by sometimes subtracting more than one from the timers.

I have not seen code that cares that the timer sort of jumps around from 47 to 41 to 34 to 29 (etc.) but if you found some that did care, you'd have to go to more effort to make this count down more regularly and only by one. One way to do that would be to make your "doCPU" routine get called more often.

   -(void) handleTimers:(double)now    
   {    
       //Decrement the timers    
       if (now - lastTick > 16667.0) //1/60Hz, or 16.667ms    
       {    
        //How many ticks have passed?    
           uint32_t tickspast = (int) (now - lastTick);    
           tickspast /= 16667;

           if (state->sound > 0)    
               state->sound = state->sound - ((state->sound > tickspast) ? tickspast : state->sound);    
           if (state->delay > 0)    
               state->delay = state->delay - ((state->delay > tickspast) ? tickspast : state->delay);    
           lastTick = now;    
       }    
   }    

Keyboard

I'll briefly mention the keyboard functions. (More details are in the previous CHIP-8 emulator section.) All I have to do here is map a keyboard character to the key_state data structure the emulator uses. Something like this:

   - (void) KeyDown: (uint8_t) key    
   {    
       switch (key)    
       {    
           case '0': state->key_state[0] = 1; break;    
           case '1': state->key_state[1] = 1; break;    
        /* etc... */    
           case 'e': state->key_state[0xe] = 1; break;    
           case 'f': state->key_state[0xf] = 1; break;    
       }    
   }

   - (void) KeyUp: (uint8_t) key    
   {    
       switch (key)    
       {    
           case '0': state->key_state[0] = 0; break;    
           case '1': state->key_state[1] = 0; break;    
        /* etc... */    
           case 'e': state->key_state[0xe] = 0; break;    
           case 'f': state->key_state[0xf] = 0; break;    
       }    
   }    

There is a chance that if the timers don't align, and the keypress is brief, that the emulator code might miss the keypress. There aren't any details about whether the original machines had "latches" for the keys. I don't think it did because it has instructions that poll the keyboard. If its true the keys weren't latched, it would have been possible for the original machine to miss keypresses too.

← Prev: chip-8-cocoa-port-part-1   Next: chip-8-port-pt-3---chip8view.m →