VM技术(二)从CHIP8入手CPU的模拟(四)

完整的CHIP8类

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;
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券