-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchip8.c
More file actions
474 lines (411 loc) · 14.1 KB
/
chip8.c
File metadata and controls
474 lines (411 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
/******************************************************************************
chip8.c
CHIP-8 cpu interpreter.
(c) 2018 Jos van Mourik
******************************************************************************/
// includes
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "chip8.h"
// global variables
unsigned char memory[4096]; // program memory
unsigned short opcode; // current opcode
unsigned short pc; // program counter
unsigned char V[16]; // data register
unsigned short I; // index register
unsigned short stack[16]; // stack
unsigned short sp; // stack pointer
unsigned char delay_timer; // delay timer
unsigned char sound_timer; // sound timer
unsigned char screen[64][32];// screen
unsigned char key[16]; // input
unsigned char keyflag; // flag for input update used in FX0A
// initialize all memory and registers
void chip8_init(void)
{
// clear screen
memset(screen, 0, sizeof(screen[0][0])*64*32);
// clear input flag
keyflag = 16;
// clear registers and stack
pc = 0x200;
memset(V, 0, sizeof(V));
I = 0;
memset(stack, 0, sizeof(stack));
sp = 0;
opcode = 0;
// reset timers
delay_timer = 0;
sound_timer = 0;
printf("CHIP-8 initialized succesfully\n");
}
// load rom file into memory
bool chip8_load(char* rom)
{
// clear memory and load fontset
memset(memory, 0, sizeof(memory));
for(int i = 0; i < 80; i++) memory[i+0x50] = chip8_fontset[i];
// open file
FILE * fp = fopen(rom, "rb");
// check if file exists
if(fp == NULL)
{
printf("ERROR opening %s\n", rom);
return 1;
}
// get file size
fseek(fp, 0, SEEK_END);
size_t len = ftell(fp);
fseek(fp, 0, SEEK_SET);
// read rom file into buffer
unsigned char *buffer = malloc(len);
fread(buffer, len, 1, fp);
// check for memory overflow
if (len <= (4096-0x200)) printf("Loaded %s\n", rom);
// read buffer into memory
for(int i = 0; i < len; i++) memory[i+0x200] = buffer[i];
// close file and free buffer
fclose(fp);
free(buffer);
return 0;
}
// emulate a single cpu cycle
bool chip8_cycle(bool debug, bool screen_wrap, bool cowgod)
{
// print all registers if debug is enabled
if(debug)
{
printf("\nSP=%X Stack=[", sp);
for(int i = 0; i < 15; i++) printf("%03X ", stack[i]);
printf("%03X]\nPC=0x%X I=0x%03X V=[",stack[15], pc, I);
for(int i = 0; i < 15; i++) printf("%02X ", V[i]);
printf("%02X]\n", V[15]);
}
// fetch opcode
opcode = memory[pc] << 8 | memory[pc + 1];
// decode opcode
switch(opcode & 0xF000)
{
case 0x0000:
switch(opcode & 0x000F)
{
// 00E0 Display disp_clear() Clears the screen.
case 0x0000:
if(debug) printf("Opcode=0x%04X: 00E0 disp_clear\n", opcode);
memset(screen, 0, sizeof(screen[0][0])*64*32);
pc += 2;
return 1; // screen update flag
break;
// 00EE Flow return; Returns from a subroutine.
case 0x000E:
if(debug) printf("Opcode=0x%04X: 00EE return\n", opcode);
sp--;
pc = stack[sp];
break;
default:
printf ("pc=0x%X ERROR: unknown opcode: 0x%X\n", opcode);
}
break;
// 1NNN Flow goto NNN; Jumps to address NNN.
case 0x1000:
if(debug) printf("Opcode=0x%04X: 1NNN goto NNN\n", opcode);
pc = opcode & 0x0FFF;
break;
// 2NNN Flow *(0xNNN)() Calls subroutine at NNN.
case 0x2000:
if(debug) printf("Opcode=0x%04X: 2NNN Call subroutine NNN\n", opcode);
stack[sp] = pc + 2;
sp++;
pc = opcode & 0x0FFF;
break;
// 3XNN Cond if(Vx==NN) Skips the next instruction if VX equals NN.
case 0x3000:
if(debug) printf("Opcode=0x%04X: 3XNN skip if(Vx==NN)\n", opcode);
if(V[(opcode & 0x0F00) >> 8] == (opcode & 0x00FF)) pc += 4;
else pc += 2;
break;
// 4XNN Cond if(Vx!=NN) Skips the next instruction if VX doesn't equal NN.
case 0x4000:
if(debug) printf("Opcode=0x%04X: 4XNN skip if(Vx!=NN)\n", opcode);
if(V[(opcode & 0x0F00) >> 8] != (opcode & 0x00FF)) pc += 4;
else pc += 2;
break;
// 5XY0 Cond if(Vx==Vy) Skips the next instruction if VX equals VY.
case 0x5000:
if(debug) printf("Opcode=0x%04X: 5XY0 skip if(Vx==Vy)\n", opcode);
if(V[(opcode & 0x0F00) >> 8] == V[(opcode & 0x00F0) >> 4]) pc += 4;
else pc += 2;
break;
// 6XNN Const Vx = NN Sets VX to NN.
case 0x6000:
if(debug) printf("Opcode=0x%04X: 6XNN Vx = NN \n", opcode);
V[(opcode & 0x0F00) >> 8] = opcode & 0x00FF;
pc += 2;
break;
// 7XNN Const Vx += NN Adds NN to VX. (Carry flag is not changed)
case 0x7000:
if(debug) printf("Opcode=0x%04X: 7XNN Vx += NN\n", opcode);
V[(opcode & 0x0F00) >> 8] += opcode & 0x00FF;
pc += 2;
break;
case 0x8000:
switch(opcode & 0x000F)
{
// 8XY0 Assign Vx=Vy Sets VX to the value of VY.
case 0x0000:
if(debug) printf("Opcode=0x%04X: 8XY0 Vx=Vy\n", opcode);
V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4];
pc += 2;
break;
// 8XY1 BitOp Vx=Vx|Vy Sets VX to VX or VY. (Bitwise OR operation)
case 0x0001:
if(debug) printf("Opcode=0x%04X: 8XY1 Vx|Vy\n", opcode);
V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x0F00) >> 8] | V[(opcode & 0x00F0) >> 4];
pc += 2;
break;
// 8XY2 BitOp Vx=Vx&Vy Sets VX to VX and VY. (Bitwise AND operation)
case 0x0002:
if(debug) printf("Opcode=0x%04X: 8XY2 Vx=Vx&Vy\n", opcode);
V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x0F00) >> 8] & V[(opcode & 0x00F0) >> 4];
pc += 2;
break;
// 8XY3 BitOp Vx=Vx^Vy Sets VX to VX xor VY.
case 0x0003:
if(debug) printf("Opcode=0x%04X: 8XY3 Vx=Vx^Vy\n", opcode);
V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x0F00) >> 8] ^ V[(opcode & 0x00F0) >> 4];
pc += 2;
break;
// 8XY4 Math Vx += Vy Adds VY to VX.
// VF is set to 1 when there's a carry, and to 0 when there isn't.
case 0x0004:
if(debug) printf("Opcode=0x%04X: 8XY4 Vx += Vy\n", opcode);
if((V[(opcode & 0x0F00) >> 8] + V[(opcode & 0x00F0) >> 4]) > 255) V[0xF] = 1;
else V[0xF] = 0;
V[(opcode & 0x0F00) >> 8] += V[(opcode & 0x00F0) >> 4];
pc += 2;
break;
// 8XY5 Math Vx -= Vy VY is subtracted from VX.
// VF is set to 0 when there's a borrow, and 1 when there isn't.
case 0x0005:
if(debug) printf("Opcode=0x%04X: 8XY5 Vx -= Vy\n", opcode);
if(V[(opcode & 0x0F00) >> 8] < V[(opcode & 0x00F0) >> 4]) V[0xF] = 0;
else V[0xF] = 1;
V[(opcode & 0x0F00) >> 8] -= V[(opcode & 0x00F0) >> 4];
pc += 2;
break;
// 8XY6 BitOp Vx=Vy=Vy>>1 Shifts VY right by one and copies the result to VX.
// VF is set to the value of the least significant bit of VY before the shift.
case 0x0006:
if(debug) printf("Opcode=0x%04X: 8XY6 Vx=Vy=Vy>>1\n", opcode);
if(cowgod)
{
V[0xF] = V[(opcode & 0x0F00) >> 8] & 1;
V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x0F00) >> 8] >> 1;
}
else
{
V[0xF] = V[(opcode & 0x00F0) >> 4] & 1;
V[(opcode & 0x00F0) >> 4] = V[(opcode & 0x00F0) >> 4] >> 1;
V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4];
}
pc += 2;
break;
// 8XY7 Math Vx=Vy-Vx Sets VX to VY minus VX.
// VF is set to 0 when there's a borrow, and 1 when there isn't.
case 0x0007:
if(debug) printf("Opcode=0x%04X: 8XY7 Vx=Vy-Vx\n", opcode);
if(V[(opcode & 0x0F00) >> 8] > V[(opcode & 0x00F0) >> 4]) V[0xF] = 0;
else V[0xF] = 1;
V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4] - V[(opcode & 0x0F00) >> 8];
pc += 2;
break;
// 8XYE BitOp Vx=Vy=Vy<<1 Shifts VY left by one and copies the result to VX.
// VF is set to the value of the most significant bit of VY before the shift.
case 0x000E:
if(debug) printf("Opcode=0x%04X: 8XYE Vx=Vy=Vy<<1\n", opcode);
if(cowgod)
{
V[0xF] = (V[(opcode & 0x0F00) >> 8] & 128) >> 7;
V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x0F00) >> 8] << 1;
}
else
{
V[0xF] = (V[(opcode & 0x00F0) >> 4] & 128) >> 7;
V[(opcode & 0x00F0) >> 4] = V[(opcode & 0x00F0) >> 4] << 1;
V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4];
}
pc += 2;
break;
default:
printf ("pc=0x%X ERROR: unknown opcode: 0x%X\n", opcode);
}
break;
// 9XY0 Cond if(Vx!=Vy) Skips the next instruction if VX doesn't equal VY.
// (Usually the next instruction is a jump to skip a code block)
case 0x9000:
if(debug) printf("Opcode=0x%04X: 9XY0 skip if(Vx!=Vy)\n", opcode);
if(V[(opcode & 0x0F00) >> 8] != V[(opcode & 0x00F0) >> 4]) pc += 4;
else pc += 2;
break;
// ANNN MEM I = NNN Sets I to the address NNN.
case 0xA000:
if(debug) printf("Opcode=0x%04X: ANNN I = NNN\n", opcode);
I = opcode & 0x0FFF;
pc += 2;
break;
// BNNN Flow PC=V0+NNN Jumps to the address NNN plus V0.
case 0xB000:
if(debug) printf("Opcode=0x%04X: BNNN PC=V0+NNN\n", opcode);
pc = (opcode & 0x0FFF) + V[0];
break;
// CXNN Rand Vx=rand()&NN
// Sets VX to the result of a bitwise and operation on a random number (Typically: 0 to 255) and NN.
case 0xC000:
if(debug) printf("Opcode=0x%04X: CXNN Vx=rand()&NN\n", opcode);
V[(opcode & 0x0F00) >> 8] = (rand() % 256) & (opcode & 0x00FF);
pc += 2;
break;
// DXYN Disp draw(Vx,Vy,N)
// Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N pixels.
case 0xD000:;
if(debug) printf("Opcode=0x%04X: DXYN draw(Vx,Vy,N)\n", opcode);
unsigned char xs = V[(opcode & 0x0F00) >> 8];
unsigned char ys = V[(opcode & 0x00F0) >> 4];
unsigned char n = opcode & 0x000F;
unsigned char spritebyte;
V[0xF] = 0;
// sprite draw routine
for (int y = 0; y < n; y++)
{
spritebyte = memory[I+y];
for(int x = 0; x < 8; x++)
{
// check pixel value and wrap if enabled
if((spritebyte & (0x80 >> x)) >> (7 - x) && (screen_wrap || ((x+xs) < 64 && (y+ys) < 32)))
{
if(screen[(x+xs) % 64][(y+ys) % 32]) V[0xF] = 1; // collision detect
screen[(x+xs) % 64][(y+ys) % 32] ^= 1; // draw pixel
}
}
}
pc += 2;
return 1; // screen update flag
break;
case 0xE000:
switch(opcode & 0x000F)
{
// EX9E KeyOp if(key()==Vx) Skips the next instruction if the key stored in VX is pressed.
case 0x000E:
if(debug) printf("Opcode=0x%04X: EX9E if(key()==Vx)\n", opcode);
if(key[V[(opcode & 0x0F00) >> 8]]) pc += 4;
else pc += 2;
break;
// EXA1 KeyOp if(key()!=Vx) Skips the next instruction if the key stored in VX isn't pressed.
case 0x0001:
if(debug) printf("Opcode=0x%04X: EXA1 if(key()!=Vx)\n", opcode);
if(!key[V[(opcode & 0x0F00) >> 8]]) pc += 4;
else pc += 2;
break;
default:
printf ("pc=0x%X ERROR: unknown opcode: 0x%X\n", opcode);
}
break;
case 0xF000:
switch(opcode & 0x00FF)
{
// FX07 Timer Vx = get_delay() Sets VX to the value of the delay timer.
case 0x0007:
if(debug) printf("Opcode=0x%04X: FX07 Vx = get_delay()\n", opcode);
V[(opcode & 0x0F00) >> 8] = delay_timer;
pc += 2;
break;
// FX0A KeyOp Vx = get_key() A key press is awaited, and then stored in VX.
// (Blocking Operation. All instruction halted until next key event)
case 0x000A:
if(debug) printf("Opcode=0x%04X: FX0A\n", opcode);
for(int i = 0; i < 16; i++) // read input if no key was pressed last time
{
if(key[i] && keyflag == 16)
{
keyflag = i;
}
}
if(!key[keyflag] && keyflag != 16) // continue if key was released
{
V[(opcode & 0x0F00) >> 8] = keyflag;
keyflag = 16;
pc += 2;
}
break;
// FX15 Timer delay_timer(Vx) Sets the delay timer to VX.
case 0x0015:
if(debug) printf("Opcode=0x%04X: FX15 delay_timer(Vx)\n", opcode);
delay_timer = V[(opcode & 0x0F00) >> 8];
pc += 2;
break;
// FX18 Sound sound_timer(Vx) Sets the sound timer to VX.
case 0x0018:
if(debug) printf("Opcode=0x%04X: FX18 sound_timer(Vx)\n", opcode);
sound_timer = V[(opcode & 0x0F00) >> 8];
pc += 2;
break;
// FX1E MEM I +=Vx Adds VX to I.[3]
case 0x001E:
if(debug) printf("Opcode=0x%04X: FX1E I +=Vx\n", opcode);
I += V[(opcode & 0x0F00) >> 8];
pc += 2;
break;
// FX29 MEM I=sprite_addr[Vx] Sets I to the location of the sprite for the character in VX.
case 0x0029:
if(debug) printf("Opcode=0x%04X: FX29 I=sprite_addr[Vx] \n", opcode);
I = 0x50 + (V[(opcode & 0x0F00) >> 8] * 5);
pc += 2;
break;
// FX33 BCD set_BCD(Vx); Stores the binary-coded decimal representation of VX
case 0x0033:
if(debug) printf("Opcode=0x%04X: FX33 set_BCD(Vx);\n", opcode);
memory[I] = V[(opcode & 0x0F00) >> 8] / 100;
memory[I+1] = (V[(opcode & 0x0F00) >> 8] / 10) % 10;
memory[I+2] = (V[(opcode & 0x0F00) >> 8] % 100) % 10;
pc += 2;
break;
// FX55 MEM reg_dump(Vx,&I) Stores V0 to VX (including VX) in memory starting at address I.
// I is increased by 1 for each value written.
case 0x0055:
if(debug) printf("Opcode=0x%04X: FX55 reg_dump(Vx,&I)\n", opcode);
for(int i = 0; i <= ((opcode & 0x0F00) >> 8); i++) memory[I+i] = V[i];
if(!cowgod) I += ((opcode & 0x0F00) >> 8) + 1;
pc += 2;
break;
// FX65 MEM reg_load(Vx,&I) Fills V0 to VX (including VX) with values from memory starting at address I.
// I is increased by 1 for each value written.
case 0x0065:
if(debug) printf("Opcode=0x%04X: FX65 reg_load(Vx,&I)\n", opcode);
for(int i = 0; i <= ((opcode & 0x0F00) >> 8); i++) V[i] = memory[I+i];
if(!cowgod) I += ((opcode & 0x0F00) >> 8) + 1;
pc += 2;
break;
default:
printf ("pc=0x%X ERROR: unknown opcode: 0x%X\n", opcode);
}
break;
default:
printf ("pc=0x%X ERROR: unknown opcode: 0x%X\n", opcode);
}
// screen update status
return 0;
}
// update timer counts
void chip8_timerupdate(void)
{
// update delay timer
if (delay_timer) delay_timer--;
// update sound timer
if (sound_timer)
{
if (sound_timer == 1) printf("Beep...\a\n"); // plays OS beep
sound_timer--;
}
}