From d36694ca5344dbc922e94dd9c5ab19175e56034b Mon Sep 17 00:00:00 2001 From: Jordan Halase Date: Fri, 27 Sep 2019 13:19:41 -0500 Subject: Initial commit --- Fishie.ch8 | Bin 0 -> 160 bytes main.c | 399 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ readme.txt | 12 ++ 3 files changed, 411 insertions(+) create mode 100644 Fishie.ch8 create mode 100644 main.c create mode 100644 readme.txt diff --git a/Fishie.ch8 b/Fishie.ch8 new file mode 100644 index 0000000..ca1eaf3 Binary files /dev/null and b/Fishie.ch8 differ diff --git a/main.c b/main.c new file mode 100644 index 0000000..c662320 --- /dev/null +++ b/main.c @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2019 Jordan Halase + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include + +#include + +struct Machine { + /** + * Memory map: + * 0x000-0x1ff Chip-8 interpreter reserved + * 0x050-0x0a0 4x5 pixel font set (0x0-0xf) + * 0x200-0xfff Program ROM and memory + */ + uint8_t memory[4096]; + + /** 64x32 1-bit video memory */ + uint8_t video[32][64]; + + /** Register set */ + uint8_t v[16]; + + /** Key set */ + uint8_t key[16]; + + /** Delay timer */ + uint8_t dt; + + /** Sound timer */ + uint8_t st; + + /** Address register */ + uint16_t i; + + /** Program counter */ + uint16_t pc; + + /** Stack pointer */ + uint16_t sp; + + /** Stack */ + uint16_t stack[24]; +}; + +static const uint8_t font[80] = { + 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 +}; + +SDL_atomic_t osc_coef; + +#define PROGRAM_START 0x200 +#define GETX(op) (((op)>>8)&0xf) +#define GETY(op) (((op)>>4)&0xf) +#define ADDR(op) ((op)&0xfff) +#define IMM(op) ((op)&0xff) +#define FUNC(op) ((op)&0xf) + +int interpret(struct Machine *vm, long cycles) +{ + uint16_t opcode; + int x, y, h; + while (cycles--) { + opcode = vm->memory[vm->pc++] << 8 | vm->memory[vm->pc++]; + //printf("%x\t%x\n", vm->pc-2, opcode); + switch ((opcode>>12) & 0xf) { + case 0x0: + switch (opcode) { + case 0x00e0: // clear screen + memset(vm->video, 0, sizeof(vm->video)); + break; + case 0x00ee: // return + vm->pc = vm->stack[--vm->sp]; + break; + default: + goto err; + } + break; + case 0x2: // call + vm->stack[vm->sp++] = vm->pc; + case 0x1: // goto + vm->pc = ADDR(opcode); + //printf("GOTO %x\n", vm->pc); + break; + case 0x3: // vx == immediate + if (vm->v[GETX(opcode)] == IMM(opcode)) + vm->pc += 2; + break; + case 0x4: // vx != immediate + if (vm->v[GETX(opcode)] != IMM(opcode)) + vm->pc += 2; + break; + case 0x5: // vx == vy + if (FUNC(opcode) != 0) { + goto err; + } + if (vm->v[GETX(opcode)] == vm->v[GETY(opcode)]) + vm->pc += 2; + break; + case 0x6: // set vx to immediate + vm->v[GETX(opcode)] = IMM(opcode); + break; + case 0x7: // add immediate to vx (no carry) + vm->v[GETX(opcode)] += IMM(opcode); + break; + case 0x8: + x = GETX(opcode), y = GETY(opcode); + switch (FUNC(opcode)) { + case 0: // vx = vy + vm->v[x] = vm->v[y]; + break; + case 1: // vx |= vy + vm->v[x] |= vm->v[y]; + break; + case 2: // vx &= vy + vm->v[x] &= vm->v[y]; + break; + case 3: // vx ^= vy + vm->v[x] ^= vm->v[y]; + break; + case 4: // FIXME: flags register vf through case 7 + h = vm->v[x] + vm->v[y]; + if (h > 0xff) { + h &= 0xff; + vm->v[0xf] = 1; + } else { + vm->v[0xf] = 0; + } + vm->v[x] = h; + break; + case 5: + vm->v[0xf] = vm->v[x] > vm->v[y] ? 1 : 0; + vm->v[x] -= vm->v[y]; + break; + case 6: + if (vm->v[x] & 1) { + vm->v[0xf] = 1; + } else { + vm->v[0xf] = 0; + } + vm->v[x] >>= 1; + break; + case 7: + vm->v[0xf] = vm->v[y] > vm->v[x] ? 1 : 0; + vm->v[x] = vm->v[y] - vm->v[x]; + break; + case 0xe: + vm->v[0xf] = vm->v[x] & 0x80 ? 1 : 0; + vm->v[x] <<= 1; + break; + default: + goto err; + } + break; + case 0x9: // vx != vy + if (FUNC(opcode) != 0) { + goto err; + } + if (vm->v[GETX(opcode)] != vm->v[GETY(opcode)]) + vm->pc += 2; + break; + case 0xa: // set address register + vm->i = ADDR(opcode); + break; + case 0xb: // jump to address offset + vm->pc = vm->v[0] + ADDR(opcode); + break; + case 0xc: // vx = random() & immediate + vm->v[GETX(opcode)] = rand() & IMM(opcode); + break; + case 0xd: // draw + x = vm->v[GETX(opcode)]; + y = vm->v[GETY(opcode)]; + h = FUNC(opcode); + vm->v[0xf] = 0; + for (int yy = 0; yy < h; ++yy) { + const uint16_t p = vm->memory[vm->i + yy]; + for (int xx = 0; xx < 8; ++xx) { + if (p&(0x80>>xx)) { + if (vm->video[(y+yy)&0x1f][(x+xx)&0x3f]) { + vm->v[0xf] = 1; + } + vm->video[(y+yy)&0x1f][(x+xx)&0x3f] ^= 1; + } + } + } + break; + case 0xe: + switch (IMM(opcode)) { + case 0x9e: + if (vm->key[vm->v[GETX(opcode)]]) + vm->pc += 2; + break; + case 0xa1: + if (!vm->key[vm->v[GETX(opcode)]]) + vm->pc += 2; + break; + default: + goto err; + } + break; + case 0xf: + x = GETX(opcode); + switch (IMM(opcode)) { + case 0x07: + vm->v[x] = vm->dt; + break; + case 0x0a: + //printf("WAITKEY\n"); // TODO + vm->pc -= 2; + //goto err; + break; + case 0x15: + vm->dt = vm->v[x]; + break; + case 0x18: + vm->st = vm->v[x]; + SDL_AtomicSet(&osc_coef, 1); + break; + case 0x1e: + vm->i += vm->v[x]; + break; + case 0x29: + vm->i = 0x50 + vm->v[x]*5; + break; + case 0x33: + vm->memory[vm->i+0] = vm->v[x] / 100; + vm->memory[vm->i+1] = (vm->v[x] / 10) % 10; + vm->memory[vm->i+2] = vm->v[x] % 10; + break; + case 0x55: + memcpy(&vm->memory[vm->i], &vm->v, x+1); + break; + case 0x65: + memcpy(&vm->v, &vm->memory[vm->i], x+1); + break; + default: + goto err; + } + break; + default: +err: fprintf(stderr, "Illegal instruction\n%x:\t%x\n", + vm->pc, opcode); + return 1; + } + } + return 0; +} + +void read_file(const char *path, uint8_t *buffer, size_t *size) +{ + FILE *fp = fopen(path, "rb"); + if (!fp) { + fprintf(stderr, "Cannot open file '%s'\n", path); + exit(1); + } + const int fd = fileno(fp); + struct stat statbuf; + if (fstat(fd, &statbuf) == -1) { + fprintf(stderr, "Cannot read file status\n"); + exit(1); + } + printf("File of %ld bytes\n", statbuf.st_size); + size_t r = fread(buffer, statbuf.st_size, 1, fp); + if (r != 1) { + fprintf(stderr, "Could not read file in one go (FIXME) %ld\n", r); + exit(1); + } + fclose(fp); + if (size) + *size = statbuf.st_size; +} + +void audio_callback(void *data, Uint8 *stream, int len) +{ + int i = 0; + uint64_t *buf = (uint64_t*)stream; + for (i = 0; i < (len>>3); ++i) { + const int c = SDL_AtomicGet(&osc_coef); + buf[i] = c ? i&4 ? 0xc0c0c0c0c0c0c0c0 : 0x4040404040404040 : 0x8080808080808080; + } +} + +int main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, "No ROM provided\n"); + exit(1); + } + srand(time(NULL)); + SDL_Init(SDL_INIT_EVERYTHING); + + SDL_AudioSpec want = { + .freq = 48000, + .format = AUDIO_U8, + .channels = 1, + .samples = 1024, + .callback = audio_callback + }, have = {0}; + + SDL_AudioDeviceID dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); + if (!dev) { + fprintf(stderr, "%s\n", SDL_GetError()); + exit(1); + } + printf("format\t%d\nchannels\t%d\nsilence\t%d\nsamples\t%d\n", + have.format, have.channels, have.silence, have.samples); + SDL_PauseAudioDevice(dev, 0); + + struct Machine *vm = calloc(1, sizeof(*vm)); + memcpy(&vm->memory[0x50], font, sizeof(font)); + read_file(argv[1], &vm->memory[PROGRAM_START], NULL); + SDL_Surface *surf = SDL_CreateRGBSurfaceWithFormatFrom( + vm->video, + 64, + 32, + 1, + 64, + SDL_PIXELFORMAT_INDEX8 + ); + if (!surf) { + fprintf(stderr, "%s\n", SDL_GetError()); + exit(1); + } + memset(surf->format->palette->colors, 0x22, 3); + SDL_Window *win = SDL_CreateWindow("Chip8", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + 640, 320, 0); + SDL_Renderer *ren = SDL_CreateRenderer(win, -1, + SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + SDL_Surface *scrgb = SDL_CreateRGBSurfaceWithFormat( + 0, 64, 32, 8, + SDL_PIXELFORMAT_ARGB8888 + ); + SDL_Texture *tex = SDL_CreateTexture(ren, + SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 64, 32); + vm->pc = PROGRAM_START; + printf("Machine size:\t%lu\tB\n", sizeof(struct Machine)); + printf("Font size:\t%lu\tB\n", sizeof(font)); + SDL_Event e; + while(1) { + while (SDL_PollEvent(&e)) { + switch (e.type) { + case SDL_QUIT: + goto quit; + } + } + if (interpret(vm, 32)) goto quit; + SDL_BlitSurface(surf, NULL, scrgb, NULL); + SDL_UpdateTexture(tex, NULL, scrgb->pixels, scrgb->pitch); + SDL_RenderClear(ren); + SDL_RenderCopy(ren, tex, NULL, NULL); + SDL_RenderPresent(ren); + vm->dt = vm->dt > 0 ? vm->dt-1 : 0; + if (vm->st > 0) { + if (--vm->st == 0) { + SDL_AtomicSet(&osc_coef, 0); + } + } + } +quit: + free(vm); + SDL_PauseAudioDevice(dev, 1); + SDL_Quit(); + return 0; +} diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..48c3c23 --- /dev/null +++ b/readme.txt @@ -0,0 +1,12 @@ +This is a simple CHIP-8 virtual machine written in C. +This project is mainly an exercise in emulator programming. + +CHIP-8 was an interpreted bytecode to allow games to be easily programmed +for mid-1970s 8-bit microcomputers. + +Compiles with gcc or clang. Not tested on Windows. + +Requires SDL2 for input and rendering. + +Compile with `gcc main.c -lSDL2 -o chip8` on Linux. A sample is included, and can be +loaded via `./chip8 Fishie.ch8` -- cgit v1.2.1