summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJordan Halase <jordan@halase.me>2019-09-27 13:19:41 -0500
committerJordan Halase <jordan@halase.me>2019-09-27 13:19:41 -0500
commitd36694ca5344dbc922e94dd9c5ab19175e56034b (patch)
tree7436c650be7e7db6cbf3e9705ed9e1094b328dea
Initial commitHEADmaster
-rw-r--r--Fishie.ch8bin0 -> 160 bytes
-rw-r--r--main.c399
-rw-r--r--readme.txt12
3 files changed, 411 insertions, 0 deletions
diff --git a/Fishie.ch8 b/Fishie.ch8
new file mode 100644
index 0000000..ca1eaf3
--- /dev/null
+++ b/Fishie.ch8
Binary files 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 <jordan@halase.me>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <time.h>
+
+#include <sys/stat.h>
+
+#include <SDL2/SDL.h>
+
+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`