chip8

Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.vgx.fr/chip8
Log | Files | Refs

commit 024662694be4fddbe0ccbdd0631a14406d9c2497
Author: Léo Villeveygoux <leo.villeveygoux@u-bordeaux.fr>
Date:   Fri, 30 Nov 2018 19:18:59 +0100

initial commit: base emulator

Working stuff:
* all 36 base CHIP-8 instruction implemented
* loading file argument at address 0x200
* basic terminal diplay
* fixed 1ms sleep between instructions

Thing probably needed to play basic games:
* input handling
* frequency tuning
* sound
* graphic display, debug outputs, input config, pause, save, shaders, VR, …

Diffstat:
Achip8.c | 382+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 382 insertions(+), 0 deletions(-)

diff --git a/chip8.c b/chip8.c @@ -0,0 +1,382 @@ +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +unsigned char RAM[0x1000]; + +unsigned char V[16]; +unsigned short I; + +unsigned char DT; +unsigned char ST; + +unsigned short PC=0x200; +unsigned char SP; + +unsigned short STACK[16]; + +unsigned short KEYBOARD; + +unsigned char SCREEN[256]; + +#define CHAR_SPRITES_OFFSET 0x100 +char char_sprites[80] = { + /* Source: Cowgod's Chip-8 Technical Reference */ + /* 0 */ + 0xF0, + 0x90, + 0x90, + 0x90, + 0xF0, + /* 1 */ + 0x20, + 0x60, + 0x20, + 0x20, + 0x70, + /* 2 */ + 0xF0, + 0x10, + 0xF0, + 0x80, + 0xF0, + /* 3 */ + 0xF0, + 0x10, + 0xF0, + 0x10, + 0xF0, + /* 4 */ + 0x90, + 0x90, + 0xF0, + 0x10, + 0x10, + /* 5 */ + 0xF0, + 0x80, + 0xF0, + 0x10, + 0xF0, + /* 6 */ + 0xF0, + 0x80, + 0xF0, + 0x90, + 0xF0, + /* 7 */ + 0xF0, + 0x10, + 0x20, + 0x40, + 0x40, + /* 8 */ + 0xF0, + 0x90, + 0xF0, + 0x90, + 0xF0, + /* 9 */ + 0xF0, + 0x90, + 0xF0, + 0x10, + 0xF0, + /* A */ + 0xF0, + 0x90, + 0xF0, + 0x90, + 0x90, + /* B */ + 0xE0, + 0x90, + 0xE0, + 0x90, + 0xE0, + /* C */ + 0xF0, + 0x80, + 0x80, + 0x80, + 0xF0, + /* D */ + 0xE0, + 0x90, + 0x90, + 0x90, + 0xE0, + /* E */ + 0xF0, + 0x80, + 0xF0, + 0x80, + 0xF0, + /* F */ + 0xF0, + 0x80, + 0xF0, + 0x80, + 0x80, +}; + +/* instructions are a 2 char array */ + +#define I_SHORT(instr) (short)((instr)[0]<<8u | (instr)[1]) +#define I_CLASS(instr) ((instr)[0]>>4u) +#define I_NNN(instr) ((((instr)[0]&15u)<<8u) | (instr)[1]) +#define I_ADDR(instr) I_NNN(instr) +#define I_NN(instr) ((instr)[1]) +#define I_KK(instr) I_NN(instr) +#define I_N(instr) ((instr)[1]&15u) +#define I_NIBBLE(instr) I_N(inst) +#define I_X(instr) ((instr)[0]&15u) +#define I_Y(instr) ((instr)[1]>>4u) + +/*XXX:debug*/ +void display(); + +void step(){ + unsigned char instr[2] = {RAM[PC], RAM[PC+1]}; + /* TODO: debug: print state */ + PC+=2; + switch(I_CLASS(instr)){ + case 0: + switch(I_SHORT(instr)){ + case 0x00E0: /* clear screen */ + for(int i=0 ; i<256 ; i++) + SCREEN[i] = 0; + break; + case 0x00EE: /* return from a subroutine */ + if(SP) + PC=STACK[--SP]; + break; + default: /*legacy instr*/ + /*TODO: warn*/ + break; + } + break; + case 1: /* jump */ + PC=I_ADDR(instr); + break; + case 2: /* call a subroutine */ + STACK[SP++] = PC; + PC=I_ADDR(instr); + break; + case 3: + if(V[I_X(instr)] == I_NN(instr)) + PC+=2; + break; + case 4: + if(V[I_X(instr)] != I_NN(instr)) + PC+=2; + break; + case 5: + if(V[I_X(instr)] == V[I_Y(instr)]) + PC+=2; + break; + case 6: + V[I_X(instr)] = I_NN(instr); + break; + case 7: + V[I_X(instr)] += I_NN(instr); + break; + case 8: + switch(I_N(instr)){ + case 0: + V[I_X(instr)] = V[I_Y(instr)]; + break; + case 1: + V[I_X(instr)] |= V[I_Y(instr)]; + break; + case 2: + V[I_X(instr)] &= V[I_Y(instr)]; + break; + case 3: + V[I_X(instr)] ^= V[I_Y(instr)]; + break; + case 4: + V[0xf] = V[I_X(instr)] + V[I_Y(instr)] > 0xff ? 1 : 0; + V[I_X(instr)] += V[I_Y(instr)]; + break; + case 5: + V[0xf] = V[I_X(instr)] > V[I_Y(instr)] ? 1 : 0; + V[I_X(instr)] -= V[I_Y(instr)]; + break; + case 6: + V[0xf] = V[I_X(instr)]&1u; + V[I_X(instr)] >>= 1u; + break; + case 7: + V[0xf] = V[I_X(instr)] < V[I_Y(instr)] ? 1 : 0; + V[I_X(instr)] = V[I_Y(instr)] - V[I_X(instr)] ; + break; + case 0xE: + V[0xf] = V[I_X(instr)]>>7u; + V[I_X(instr)] <<= 1u; + break; + + + + default: /*unknown instruction*/ + /*TODO: warn*/ + break; + } + break; + case 9: + if(V[I_X(instr)] != V[I_Y(instr)]) + PC+=2; + break; + case 0xA: + I=I_ADDR(instr); + break; + case 0xB: + PC = I_ADDR(instr) + V[0]; + break; + case 0xC: + V[I_X(instr)] = rand() & I_NN(instr); + break; + case 0xD: /*display*/ + /*TODO:check OOB coords*/ + /*assert(Vx in [0;63], Vy in [0;31]), Vy+nible in [0;31]*/ + V[0xf] = 0; + unsigned char x = V[I_X(instr)]; + unsigned char y = V[I_Y(instr)]; + unsigned char shift = x%8; + + for(int i=0;i<I_N(instr);i++){ + unsigned char *screen_tile = &SCREEN[(y+i)*8+x/8]; + unsigned char sprite_tile = RAM[I+i]>>shift; + V[0xf] |= *screen_tile & sprite_tile ? 1:0; + *screen_tile ^= sprite_tile; + if(shift){ + screen_tile = &SCREEN[(y+i)*8+(x/8+1)%8]; + sprite_tile = RAM[I+i]<<(8u-shift); + V[0xf] |= *screen_tile & sprite_tile ? 1:0; + *screen_tile ^= sprite_tile; + } + } + /*XXX:debug*/ + display(); + //usleep(500000); + break; + case 0xE: + switch(I_NN(instr)){ + case 0x9E: + if(KEYBOARD & 1<<(V[I_X(instr)])) + PC+=2; + break; + case 0xA1: + if(!(KEYBOARD & 1<<(V[I_X(instr)]))) + PC+=2; + break; + default: /*unknown instruction*/ + /*TODO: warn*/ + break; + + } + break; + case 0xF: + switch(I_NN(instr)){ + case 0x07: + V[I_X(instr)]=DT; + break; + case 0x0A: + if(!KEYBOARD) + PC-=2; + else { + V[I_X(instr)]=0; + for(unsigned char k=KEYBOARD;!(k&1);k>>=1) + V[I_X(instr)]++; + } + break; + case 0x15: + DT=V[I_X(instr)]; + break; + case 0x18: + ST=V[I_X(instr)]; + break; + case 0x1E: + I+=V[I_X(instr)]; + break; + case 0x29: + /*TODO: warn OOB*/ + if(V[I_X(instr)]<=0xF) + I = CHAR_SPRITES_OFFSET + 5*V[I_X(instr)]; + break; + case 0x33: + RAM[I+2] = V[I_X(instr)]%10; + RAM[I+1] = (V[I_X(instr)]/10)%10; + RAM[I] = (V[I_X(instr)]/100)%10; + break; + case 0x55: + for(int i=0 ; i<I_X(instr) ; i++) + RAM[I+i] = V[i]; + break; + case 0x65: + for(int i=0 ; i<I_X(instr) ; i++) + V[i] = RAM[I+i]; + break; + default: /*unknown instruction*/ + /*TODO: warn*/ + break; + } + break; + default: /*unknown instr*/ + /*TODO: warn*/ + break; + } + +} + +void display(){ + printf("\033[H"); + for(int y=0 ; y<32 ; y++){ + for(int xc=0 ; xc<8 ; xc++){ + for(int xb=7 ; xb>=0 ; xb--){ + printf("%s",SCREEN[y*8+xc]&1<<xb ? "█":" "); + } + } + printf("\n"); + } +} + +int main(int argc, char **argv){ + + /* Set up char sprites */ + for(int i=0 ; i<sizeof(char_sprites) ; i++) + RAM[CHAR_SPRITES_OFFSET+i] = char_sprites[i]; + + /* Load ROM */ + if(argc<2) + return 1; + FILE *rom=fopen(argv[1], "r"); + if(!rom) + return 2; + fread(RAM+0x200,1,0x1000-0x200,rom); + if(ferror(rom)) + return 3; + fclose(rom); + + /*TODO: init more stuff: graphics, input, sound, timer*/ + + /* Let's go */ + for(int i=0 ; 1 ; i++){ + step(); + /*XXX:debug*/ + /*TODO: other stuff*/ + //display(); + nanosleep(&(struct timespec){0,1000000},NULL); + if(!(i%16)){ + //display(); + if(DT) + DT--; + if(ST) + ST--; + } + printf("i=%5d, DT=%3d \r", i, (int)DT); + } + + return 0; +}