CHIP8.h
//
// Created by Pulsar on 2019/7/18.
//
#ifndef EASYMVM_CHIP8_H
#define EASYMVM_CHIP8_H
#include <QtWidgets>
#include <QtCore/QtCore>
#include <cstdint>
#include <cstdlib>
#include <stdio.h>
#include <cstring>
#include <ctime>
#include <modules/VMCore/include/base_vm_env/base_vm_env.hpp>
//储存大小
#define MEM_SIZE 4096
//屏幕高度
#define GFX_ROWS 32
//屏幕宽度
#define GFX_COLS 64
//像素个数
#define GFX_SIZE (GFX_ROWS * GFX_COLS)
//栈大小
#define STACK_SIZE 16
//按键大小
#define KEY_SIZE 16
//像素大小
#define PIXEL_SIZE 5
//CPU频率
#define CLOCK_HZ 60
//CPU周期
#define CLOCK_RATE_MS ((int) ((1.0 / CLOCK_HZ) * 1000 + 0.5))
//颜色
#define BLACK 0
#define WHITE 255
//屏幕行数
#define SCREEN_ROWS (GFX_ROWS * PIXEL_SIZE)
//屏幕列数
#define SCREEN_COLS (GFX_COLS * PIXEL_SIZE)
//屏幕索引
#define GFX_INDEX(row, col) ((row)*GFX_COLS + (col))
//最大游戏文件大小
#define MAX_GAME_SIZE (0x1000 - 0x200)
class CHIP8 : public base_vm_env {
public:
unsigned char screen[SCREEN_ROWS][SCREEN_COLS][3];
unsigned short opcode;
//TODO:0x000-0x1FF - Chip 8解释器(包含用于显示的字体)
// 0x050-0x0A0 - 用于生成 4x5 像素的字体集合 (从’0’到’F’)
// 0x200-0xFFF - 游戏ROM 与工作RAM
uint8_t memory[MEM_SIZE];
//TODO:CPU 寄存器:Chip 8 有16个单字节(1 byte)寄存器,
// 名字为V0,V1...到VF. 前15个寄存器为通用寄存器,
// 最后一个寄存器(VF)是个进位标志(carry flag)
uint8_t V[16];
//TODO: 索引寄存器I(Index register,暂译为“索引寄存器”)
// 程序计数器PC(program counter),值域为0x000 到 0xFFF:
uint16_t I;
uint16_t PC;
uint8_t gfx[GFX_ROWS][GFX_COLS];
//TODO:计数器
uint8_t delay_timer;
uint8_t sound_timer;
//TODO:堆栈
uint16_t stack[STACK_SIZE];
uint16_t SP;
//TODO:按键
uint8_t key[KEY_SIZE];
int screen_rows = SCREEN_ROWS;
int screen_cols = SCREEN_COLS;
int clock_rate_ms = CLOCK_RATE_MS;
int clock_hz = CLOCK_HZ;
int pixel_size = PIXEL_SIZE;
char *name = "CHIP8";
//绘图标志位
bool draw_flag;
//周期函数
void runCycle();
void loadGame(char *file_path);
void debug_screen();
void setkeys(int index,int state);
//计时函数
void tick();
//绘制精灵
void draw_sprite(uint8_t x, uint8_t y, uint8_t n);
//打印当前状态
void printState();
//初始化CPU
void initialize();
//打印CPU信息
void print_cpu_info() ;
};
#endif //EASYMVM_CHIP8_H
CHIP8.cpp
//
// Created by Pulsar on 2019/7/18.
//
#include <iostream>
#include "modules/CHIP8/include/CHIP8.h"
#ifdef __linux__
void Beep(int val1,int val2){};
#endif
#define unknown_opcode(op) \
do { \
fprintf(stderr, "Unknown opcode: 0x%x\n", op); \
fprintf(stderr, "kk: 0x%02x\n", kk); \
fprintf(stderr, "System Shutdown!!!\n"); \
exit(42); \
} while (0)
//#define DEBUG
#ifdef DEBUG
#define p(...) printf(__VA_ARGS__);
#else
#define p(...)
#endif
#define IS_BIT_SET(byte, bit) (((0x80 >> (bit)) & (byte)) != 0x0)
#define FONTSET_ADDRESS 0x00
#define FONTSET_BYTES_PER_CHAR 5
unsigned char chip8_fontset[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
};
static inline uint8_t randbyte() { return (rand() % 256); }
void CHIP8::initialize() {
//TODO:初始化内存与寄存器(注意这个操作只需执行一次)
int i;
PC = 0x200;
opcode = 0;
I = 0;
SP = 0;
memset(memory, 0, sizeof(uint8_t) * MEM_SIZE);
memset(V, 0, sizeof(uint8_t) * 16);
memset(gfx, 0, sizeof(uint8_t) * GFX_SIZE);
memset(stack, 0, sizeof(uint16_t) * STACK_SIZE);
memset(key, 0, sizeof(uint8_t) * KEY_SIZE);
for (i = 0; i < 80; i++) {
memory[FONTSET_ADDRESS + i] = chip8_fontset[i];
}
draw_flag = true;
delay_timer = 0;
sound_timer = 0;
srand(time(NULL));
}
void CHIP8::runCycle() {
int i;
uint8_t x, y, n;
uint8_t kk;
uint16_t nnn;
//TODO:获取opcode
opcode = memory[PC] << 8 | memory[PC + 1];
//获取高四位和低四位
x = (opcode >> 8) & 0x000F; // 取低4位数据
y = (opcode >> 4) & 0x000F; // 取高4位数据
//获取操作码
n = opcode & 0x000F; // 取四位汇编操作码
kk = opcode & 0x00FF; // 取四位汇编寄存器码
nnn = opcode & 0x0FFF; // 取数据码
#ifdef DEBUG
// std::cout<<"PC:"<<PC<<"Op:"<<opcode<<std::endl;
// printf("PC: 0x%04x Op: 0x%04x\n", PC, opcode);
#endif
switch (opcode & 0xF000) {
case 0x0000:
switch (kk) {
case 0x00E0:
p("Clear the screen\n");
memset(gfx, 0, sizeof(uint8_t) * GFX_SIZE);
draw_flag = true;
PC += 2;
break;
case 0x00EE: // ret
p("ret\n");
PC = stack[--SP];
break;
default:
unknown_opcode(opcode);
}
break;
case 0x1000:
p("Jump to address 0x%x\n", nnn);
PC = nnn;
break;
case 0x2000:
p("Call address 0x%x\n", nnn);
stack[SP++] = PC + 2;
PC = nnn;
break;
case 0x3000:
p("Skip next instruction if 0x%x == 0x%x\n", V[x], kk);
PC += (V[x] == kk) ? 4 : 2;
break;
case 0x4000:
p("Skip next instruction if 0x%x != 0x%x\n", V[x], kk);
PC += (V[x] != kk) ? 4 : 2;
break;
case 0x5000:
p("Skip next instruction if 0x%x == 0x%x\n", V[x], V[y]);
PC += (V[x] == V[y]) ? 4 : 2;
break;
case 0x6000:
p("Set V[0x%x] to 0x%x\n", x, kk);
V[x] = kk;
PC += 2;
break;
case 0x7000:
p("Set V[0x%d] to V[0x%d] + 0x%x\n", x, x, kk);
V[x] += kk;
PC += 2;
break;
case 0x8000:
switch (n) {
case 0x0:
p("V[0x%x] = V[0x%x] = 0x%x\n", x, y, V[y]);
V[x] = V[y];
break;
case 0x1:
p("V[0x%x] |= V[0x%x] = 0x%x\n", x, y, V[y]);
V[x] = V[x] | V[y];
break;
case 0x2:
p("V[0x%x] &= V[0x%x] = 0x%x\n", x, y, V[y]);
V[x] = V[x] & V[y];
break;
case 0x3:
p("V[0x%x] ^= V[0x%x] = 0x%x\n", x, y, V[y]);
V[x] = V[x] ^ V[y];
break;
case 0x4:
p("V[0x%x] = V[0x%x] + V[0x%x] = 0x%x + 0x%x\n", x, x, y, V[x], V[y]);
V[0xF] = ((int) V[x] + (int) V[y]) > 255 ? 1 : 0;
V[x] = V[x] + V[y];
break;
case 0x5:
p("V[0x%x] = V[0x%x] - V[0x%x] = 0x%x - 0x%x\n", x, x, y, V[x], V[y]);
V[0xF] = (V[x] > V[y]) ? 1 : 0;
V[x] = V[x] - V[y];
break;
case 0x6:
p("V[0x%x] = V[0x%x] >> 1 = 0x%x >> 1\n", x, x, V[x]);
V[0xF] = V[x] & 0x1;
V[x] = (V[x] >> 1);
break;
case 0x7:
p("V[0x%x] = V[0x%x] - V[0x%x] = 0x%x - 0x%x\n", x, y, x, V[y], V[x]);
V[0xF] = (V[y] > V[x]) ? 1 : 0;
V[x] = V[y] - V[x];
break;
case 0xE:
p("V[0x%x] = V[0x%x] << 1 = 0x%x << 1\n", x, x, V[x]);
V[0xF] = (V[x] >> 7) & 0x1;
V[x] = (V[x] << 1);
break;
default:
unknown_opcode(opcode);
}
PC += 2;
break;
case 0x9000:
switch (n) {
case 0x0:
p("Skip next instruction if 0x%x != 0x%x\n", V[x], V[y]);
PC += (V[x] != V[y]) ? 4 : 2;
break;
default:
unknown_opcode(opcode);
}
break;
case 0xA000:
p("Set I to 0x%x\n", nnn);
I = nnn;
PC += 2;
break;
case 0xB000:
p("Jump to 0x%x + V[0] (0x%x)\n", nnn, V[0]);
PC = nnn + V[0];
break;
case 0xC000:
p("V[0x%x] = random byte\n", x);
V[x] = randbyte() & kk;
PC += 2;
break;
case 0xD000:
p("Draw sprite at (V[0x%x], V[0x%x]) = (0x%x, 0x%x) of height %d",
x, y, V[x], V[y], n);
draw_sprite(V[x], V[y], n);
PC += 2;
draw_flag = true;
break;
case 0xE000: // 按键事件处理
switch (kk) {
case 0x9E:
p("Skip next instruction if key[%d] is pressed\n", x);
PC += (key[V[x]]) ? 4 : 2;
break;
case 0xA1:
p("Skip next instruction if key[%d] is NOT pressed\n", x);
PC += (!key[V[x]]) ? 4 : 2;
break;
default:
unknown_opcode(opcode);
}
break;
case 0xF000: // misc
switch (kk) {
case 0x07:
p("V[0x%x] = delay timer = %d\n", x, delay_timer);
V[x] = delay_timer;
PC += 2;
break;
case 0x0A:
i = 0;
printf("Wait for key instruction\n");
while (true) {
for (i = 0; i < KEY_SIZE; i++) {
if (key[i]) {
V[x] = i;
goto got_key_press;
}
}
}
got_key_press:
PC += 2;
break;
case 0x15:
p("delay timer = V[0x%x] = %d\n", x, V[x]);
delay_timer = V[x];
PC += 2;
break;
case 0x18:
p("sound timer = V[0x%x] = %d\n", x, V[x]);
sound_timer = V[x];
PC += 2;
break;
case 0x1E:
p("I = I + V[0x%x] = 0x%x + 0x%x\n", x, I, V[x]);
V[0xF] = (I + V[x] > 0xfff) ? 1 : 0;
I = I + V[x];
PC += 2;
break;
case 0x29:
p("I = location of font for character V[0x%x] = 0x%x\n", x, V[x]);
I = FONTSET_BYTES_PER_CHAR * V[x];
PC += 2;
break;
case 0x33:
p("Store BCD for %d starting at address 0x%x\n", V[x], I);
memory[I] = (V[x] % 1000) / 100; // hundred's digit
memory[I + 1] = (V[x] % 100) / 10; // ten's digit
memory[I + 2] = (V[x] % 10); // one's digit
PC += 2;
break;
case 0x55:
p("Copy sprite from registers 0 to 0x%x into memory at address 0x%x\n", x, I);
for (i = 0; i <= x; i++) { memory[I + i] = V[i]; }
I += x + 1;
PC += 2;
break;
case 0x65:
p("Copy sprite from memory at address 0x%x into registers 0 to 0x%x\n", x, I);
for (i = 0; i <= x; i++) { V[i] = memory[I + i]; }
I += x + 1;
PC += 2;
break;
default:
unknown_opcode(opcode);
}
break;
default:
unknown_opcode(opcode);
}
#ifdef DEBUG
// this->printState();
#endif
}
void CHIP8::tick() {
// 更新计时器
if (delay_timer > 0) {
--delay_timer;
}
if (sound_timer > 0) {
--sound_timer;
if (sound_timer == 0) {
Beep(400,400);//注意Beep函数只有Windows才有,Linux另外安装libbeep
#ifdef DEBUG
printf("BEEP!\n");
#endif
}
}
}
void CHIP8::draw_sprite(uint8_t x, uint8_t y, uint8_t n) {
unsigned row = y, col = x;
unsigned byte_index;
unsigned bit_index;
// 中断寄存器置 0
V[0xF] = 0;
for (byte_index = 0; byte_index < n; byte_index++) {
uint8_t byte = memory[I + byte_index];
for (bit_index = 0; bit_index < 8; bit_index++) {
uint8_t bit = (byte >> bit_index) & 0x1;
uint8_t *pixelp = &gfx[(row + byte_index) % GFX_ROWS]
[(col + (7 - bit_index)) % GFX_COLS];
if (bit == 1 && *pixelp == 1) V[0xF] = 1;
*pixelp = *pixelp ^ bit;
}
}
}
void CHIP8::loadGame(char *file_path) {
FILE *fgame;
fgame = fopen(file_path, "rb");
if (NULL == fgame) {
fprintf(stderr, "Unable to open game: %s\n", file_path);
exit(42);
}
fread(&memory[0x200], 1, MAX_GAME_SIZE, fgame);
fclose(fgame);
}
void CHIP8::printState() {
printf("------------------------------------------------------------------\n");
printf("\n");
printf("V0: 0x%02x V4: 0x%02x V8: 0x%02x VC: 0x%02x\n",
V[0], V[4], V[8], V[12]);
printf("V1: 0x%02x V5: 0x%02x V9: 0x%02x VD: 0x%02x\n",
V[1], V[5], V[9], V[13]);
printf("V2: 0x%02x V6: 0x%02x VA: 0x%02x VE: 0x%02x\n",
V[2], V[6], V[10], V[14]);
printf("V3: 0x%02x V7: 0x%02x VB: 0x%02x VF: 0x%02x\n",
V[3], V[7], V[11], V[15]);
printf("\n");
printf("PC: 0x%04x\n", PC);
printf("\n");
printf("\n");
}
void CHIP8::debug_screen() {
int x, y;
for (y = 0; y < GFX_ROWS; y++) {
for (x = 0; x < GFX_COLS; x++) {
if (gfx[y][x] == 0) printf("0");
else printf(" ");
}
printf("\n");
}
printf("\n");
}
void CHIP8::print_cpu_info() {
std::cout << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << std::endl;
std::cout << "CPU name | " << this->name << std::endl;
std::cout << "Screen rows | " << this->screen_rows << std::endl;
std::cout << "Screen cols | " << this->screen_cols << std::endl;
std::cout << "Clock rate_ms | " << this->clock_rate_ms << std::endl;
std::cout << "Clock hz | " << this->clock_hz << std::endl;
std::cout << "Pixel size | " << this->pixel_size << std::endl;
std::cout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl;
}
void CHIP8::setkeys(int index,int state) {
this->key[index]=state;
}