Chip8笔记

Chip8

寄存器

Name Size 初始值 Desc
V0 - VF 1 字节 0 每个 1 字节, 共 16 个
DT 1 字节 0 60Hz 递减, 每 1 / 60 秒减 1
ST 1 字节 0 60Hz 递减, 每 1 / 60 秒减 1
SC bool 0 SC = if (ST > 0)
PC 2 字节 0x200 Program Counter 程序计数器, 多数情况初始化为 0x200 (ETI 660 版为 0x600)
I 2 字节 0 地址寄存器, 可用于储存 12 位地址
SP 2 字节 取决于栈的实现 Stack Pointer 栈指针
Stack 2 字节 每个2字节, 多数为 16 个

内存

4K 内存, 低 512 字节保留。
内存布局 (其实不重要, 而且这块不同文档争议很大):

Address Size Desc Note
0x000 - 0x1FF 512 系统保留, 不允许用户程序访问 有的实现将本区域供用户使用
0xEA0 - 0xEFF 96 保留系统栈和其他内部使用 也可留给用户程序使用
0xF00 - 0xFFF 256 保留显示缓存 也可留给用户程序使用
0x000-0x050 或 0x050-0x0A0 80 字体数据 实现 FX29(LD F, VX) 指令需注意

按键

╔═══╦═══╦═══╦═══╗
║ 1 ║ 2 ║ 3 ║ C ║
╠═══╬═══╬═══╬═══╣
║ 4 ║ 5 ║ 6 ║ D ║
╠═══╬═══╬═══╬═══╣
║ 7 ║ 8 ║ 9 ║ E ║
╠═══╬═══╬═══╬═══╣
║ A ║ 0 ║ B ║ F ║
╚═══╩═══╩═══╩═══╝

屏幕

宽 64 高 32 黑白屏

字体

unsigned char fonts[] = { // 每个字 5 字节, 共 0 - F 有 16 个字
    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
};

频率

不明, 多数实现一般 700 HZ, 700指令每秒。

指令集

指令操作数说明 :
X / Y : 4 位寄存器描述
N : 4 位立即数,最多可组成 12 位
助记符说明 :
K : pressed key, JP = jump, S = skip, LD = load,
E / NE = equel/notEqual, KP / KNP = keyPressed/keyNotPresse

Opcode Mnemonic Description
00E0 CLS Clear 清幕
00EE RET PC = Stack.pop(), 返回
0NNN SYS NNN 调用 CDP1802 位于 NNN 子程序, 一般情况下弃用该指令
2NNN CALL NNN Stack.push(PC); PC = NNN, 调用函数
1NNN JP NNN PC = NNN, 无条件跳转到 NNN
BNNN JP V0, NNN PC = V0 + NNN
3XNN SE VX, NN 跳过下条指令 if VX == NN
4XNN SNE VX, NN 跳过下条指令 if VX != NN
5XY0 SE VX, VY 跳过下条指令 if VX == VY
9XY0 SNE VX, VY 跳过下条指令 if VX != VY
EX9E SKP VX 跳过下条指令 if key(VX) pressed
EXA1 SKNP VX 跳过下条指令 if key(VX) not pressed
FX0A LD VX, K VX = pressed key, 阻塞式等待
6XNN LD VX, NN VX = NN
8XY0 LD VX, VY VX = VY
FX07 LD VX, DT VX = DT
FX15 LD DT, VX DT = VX
FX18 LD ST, VX ST = VX
ANNN LD I, NNN I = NNN
FX29 LD F, VX I = VX * 5 + 字体起始位置, 将 I 指向系统字体 VX 的位置(共有 0 - F 个字)
FX55 LD [I], VX Memory[I:I+VX] = V[0:VX]); 争议: 是否改变 I
FX65 LD VX, [I] V[0:VX]) = Memory[I:I+VX]; 争议: 是否改变 I
FX1E ADD I, VX I += VX, 争议: 是否根据溢出设置 VF
7XNN ADD VX, NN VX = VX + NN
8XY4 ADD VX, VY VX = VX + VY; VF = carry, 需处理 X == F 的情况
8XY5 SUB VX, VY VX = VX - VY; VF = borrow, 需处理 X == F 的情况
8XY7 SUBN VX, VY VX = VY - VX; VF = borrow, 需处理 X == F 的情况
8XY1 OR VX, VY VX = VX OR VY
8XY2 AND VX, VY VX = VX AND VY
8XY3 XOR VX, VY VX = VX XOR VY
8XY6 SHR VX VX >>= 1; VF = overflow, 需处理 X == F 的情况; 争议意: VX = VY << 1
8XYE SHL VX VX <<= 1; VF = overflow, 需处理 X == F 的情况; 争议意: VX = VY >> 1
FX33 BCD VX mem[I:I+2] = dec(VX) 从低到高为百位、十位、个位
CXNN RND VX, NN VX = rand & N
DXYN DRW VX, VY, N 在屏幕 (VX, VY) 处绘制 N 行 8 列精灵图像, VF = 是否像素碰撞

DXYN 指令详解

本指令细节较多,且多数测试 ROM 均未测试该指令,俄罗斯方块 ROM 使用了这个指令的特性

精灵数据 Sprite = Memory[I:I+N], 一个字节为一行, 一个位为一个像素:
获取 SpritePixel = Sprite[row][col], ScreenPixel = Screen[(col + VX) % Screen_W][(row + VY) % Screen_H]
如果 ScreenPixel & SpritePixelVF = 1 否则不变
异或绘制像素 ScreenPixel ^= SpritePixel

由于异或的真值表如下:

a b a ^ b
true true false
true false true
false true true
false false false

故又可理解为: 若 SpritePixeltrueScreenPixel 需要被翻转!

一些代码(归档参考, 人工整理或有疏漏)

Chip8核心

class chip8_core {
public: // Hardware Units :
    unsigned char V[16] = { 0 };      // 16 个寄存器 V0 - VF
    unsigned char ST = 0;             // Sound Timer : 60HZ 速度递减直到 0 , 非 0 时 SC 发声
    unsigned char DT = 0;             // Delay Timer : 60HZ 速度递减直到 0
    unsigned short PC = 0x200;        // Program Counter 程序计数器, 初始化为 0x200
    unsigned short SP = 0;            // Stack Pointer 栈指针
    unsigned short I = 0;             // 地址寄存器
    unsigned short Stack[16] = { 0 }; // 程序栈
    unsigned char Mem[4096] = { 0 };  // 4k 内存, 低 512 字节保留

    bool SC = false; // 声音通道
    class screen_T {
    public:
        uint64_t pixel_lines[32] = { false }; // 64 * 32 黑白屏, true = 黑色
        void reset() { memset(pixel_lines, 0, sizeof(uint64_t) * 32); }
        bool get_pixel(unsigned char x, unsigned char y) { return pixel_lines[y] & (0x8000000000000000 >> x); }
        void set_pixel(unsigned char x, unsigned char y, bool lighted) {
            if (lighted) { pixel_lines[y] |= (0x8000000000000000 >> x); } // 置 1
            else { pixel_lines[y] &= (~(0x8000000000000000 >> x)); }      // 置 0
        }
        uint64_t get_line(unsigned char row) { return pixel_lines[row]; }
        void set_line(unsigned char row, uint64_t line) { pixel_lines[row] = line; }
    } Screen;
    class key_pad_T {
    public:
        /* 按键布局 :
        * 1 2 3 C
        * 4 5 6 D
        * 7 8 9 E
        * A 0 B F
        */
        uint16_t key_matrix = 0; // 按位表示 16 位按键, true = 按下
        void reset() { key_matrix = 0; }
        bool is_key_pressed(unsigned char id) { return key_matrix & (1 << id); }
        void set_key_pressed(unsigned char id, bool pressed) {
            if (pressed) { key_matrix |= (1 << id); } // 置 1
            else { key_matrix &= (~(1 << id)); }      // 置 0
        }
    } KeyPad;

public: // 辅助函数
    void reset() { // 复位
        memset(V, 0, sizeof(unsigned char) * 16);
        ST = 0; DT = 0; SC = false; PC = 0x200; SP = 0; I = 0;
        memset(Stack, 0, sizeof(unsigned short) * 16);
        memset(Mem + 0X200, 0, sizeof(unsigned char) * 0xE00); // 保留区不覆盖
        Screen.reset(); KeyPad.reset();
        set_rand_seed(INIT_RAND_SEED);
    }
    unsigned short get_opcode() { return (Mem[PC] << 8) + Mem[PC + 1]; } // 取指
    void init_xyn() { X = 0; Y = 0; N = 0; }                             // 初始化操作数
    void get_n(unsigned char input) { N = (N << 4) + input; }            // 获取操作数 N
    void get_x(unsigned char input) { X = input; }                       // 获取操作数 X
    void get_y(unsigned char input) { Y = input; }                       // 获取操作数 Y
    void load_rom(unsigned char* p_rom, size_t rom_size) { memcpy(&Mem[0x200], p_rom, rom_size); }

public: // 指令集 :
    unsigned short N;   // 获取的操作数 N
    unsigned char X, Y; // 获取的操作数 XY
    void op_00E0(); void op_00EE(); void op_0NNN(); void op_2NNN();
    void op_1NNN(); void op_BNNN(); void op_3XNN(); void op_4XNN();
    void op_5XY0(); void op_9XY0(); void op_EX9E(); void op_EXA1();
    void op_FX0A(); void op_6XNN(); void op_8XY0(); void op_FX07();
    void op_FX15(); void op_FX18(); void op_ANNN(); void op_FX29();
    void op_FX55(); void op_FX65(); void op_FX1E(); void op_7XNN();
    void op_8XY4(); void op_8XY5(); void op_8XY7(); void op_8XY1();
    void op_8XY2(); void op_8XY3(); void op_8XY6(); void op_8XYE();
    void op_FX33(); void op_CXNN(); void op_DXYN(); 
}

指令信息

class chip8_core; // 内核
struct chip8_opcodeInfo { // 指令集信息
    std::string Id; // 指令表示
    void (chip8_core::* ProcExecFunc)(void) = nullptr;
    std::string Mnemonic; // 助记符
};
std::vector<chip8_opInfo> chip8_opcodes = { // 所有指令
    { "00E0", &chip8_core::op_00E0, "CLS" }, // Clear 清屏
    { "00EE", &chip8_core::op_00EE, "RET" }, // PC = Stack.pop(), 返回
    { "0NNN", &chip8_core::op_0NNN, "SYS {N}{N}{N}" }, // 一般情况下弃用该指令
    { "2NNN", &chip8_core::op_2NNN, "CALL {N}{N}{N}" }, // Stack.push(PC); PC = N, 调用函数
    { "1NNN", &chip8_core::op_1NNN, "JP {N}{N}{N}" }, // PC = N, 无条件跳转到 N
    { "BNNN", &chip8_core::op_BNNN, "JP V0, {N}{N}{N}" }, // PC = V0 + N
    { "3XNN", &chip8_core::op_3XNN, "SE V{X}, {N}{N}" }, // skip if VX == N
    { "4XNN", &chip8_core::op_4XNN, "SNE V{X}, {N}{N}" }, // skip if VX != N
    { "5XY0", &chip8_core::op_5XY0, "SE V{X}, V{Y}" }, // skip if VX == VY
    { "9XY0", &chip8_core::op_9XY0, "SNE V{X}, V{Y}" }, // skip if VX == VY
    { "EX9E", &chip8_core::op_EX9E, "SKP V{X}" }, // skip if Key == true
    { "EXA1", &chip8_core::op_EXA1, "SKNP V{X}" }, // skip if Key == false
    { "FX0A", &chip8_core::op_FX0A, "LD V{X}, K" }, // [blocked] VX = pressed key
    { "6XNN", &chip8_core::op_6XNN, "LD V{X}, {N}{N}" }, // VX = N
    { "8XY0", &chip8_core::op_8XY0, "LD V{X}, V{Y}" }, // VX = VY
    { "FX07", &chip8_core::op_FX07, "LD V{X}, DT" }, // VX = DT
    { "FX15", &chip8_core::op_FX15, "LD DT, V{X}" }, // DT = VX
    { "FX18", &chip8_core::op_FX18, "LD ST, V{X}" }, // ST = VX
    { "ANNN", &chip8_core::op_ANNN, "LD I, {N}{N}{N}" }, // I = N
    { "FX29", &chip8_core::op_FX29, "LD F, V{X}" }, // I = VX * 5, 将 I 指向系统字体 VX 的位置(共有 0 - F 个字)
    { "FX55", &chip8_core::op_FX55, "LD [I], V{X}" }, // mem[I:I+VX] = V[0:VX]), 争议: 是否改变 I
    { "FX65", &chip8_core::op_FX65, "LD V{X}, [I]" }, // V[0:VX]) = mem[I:I+VX], 争议: 是否改变 I
    { "FX1E", &chip8_core::op_FX1E, "ADD I, V{X}" }, // I += VX, 争议: 是否改变 VF
    { "7XNN", &chip8_core::op_7XNN, "ADD V{X}, {N}{N}" }, // VX += N
    { "8XY4", &chip8_core::op_8XY4, "ADD V{X}, V{Y}" }, // VX += VY; VF = carry, 需处理 X == F 的情况
    { "8XY5", &chip8_core::op_8XY5, "SUB V{X}, V{Y}" }, // VX -= VY; VF = borrow, 需处理 X == F 的情况
    { "8XY7", &chip8_core::op_8XY7, "SUBN V{X}, V{Y}" }, // VX = VY - VX; VF = borrow, 需处理 X == F 的情况
    { "8XY1", &chip8_core::op_8XY1, "OR V{X}, V{Y}" }, // VX |= VY
    { "8XY2", &chip8_core::op_8XY2, "AND V{X}, V{Y}" }, // VX &= VY
    { "8XY3", &chip8_core::op_8XY3, "XOR V{X}, V{Y}" }, // VX ^= VY
    { "8XY6", &chip8_core::op_8XY6, "SHR V{X}" }, // V[X] >>= 1; VF = overflow, 需处理 X == F 的情况, 争议意: VX = VY << 1
    { "8XYE", &chip8_core::op_8XYE, "SHL V{X}" }, // V[X] <<= 1; VF = overflow, 需处理 X == F 的情况, 争议意: VX = VY >> 1
    { "FX33", &chip8_core::op_FX33, "BCD V{X}" }, // mem[I:I+2] = dec(VX) 从低到高为百位、十位、个位
    { "CXNN", &chip8_core::op_CXNN, "RND V{X}, {N}{N}" }, // VX = rand & N
    { "DXYN", &chip8_core::op_DXYN, "DRW V{X}, V{Y}, {N}" }, // 绘制精灵
};

译指

std::string BuildMnemonic(unsigned short opcode) { // 生成助记符
    chip8_opcodeInfo opcode_info = Analyse(opcode); // 译指分析获取指令信息
    std::string mnemonic = opcode_info->Mnemonic;
    for (int i = 0; i < 4; i++) {
        if (!helper::isHex(info->Id[i])) { // 发现声明的操作数 X Y N, 需要替换为对应的十六进制值
            std::string ostr = { '{', info->Id[i], '}' };
            helper::replaceFirst(mnemonic, ostr, helper::byte2hex((opcode >> (12 - i * 4)) & 0xF));
        }
    }
    return mnemonic;
}
void Execute(unsigned short opcode) {
    chip8_opcodeInfo opcode_info = Analyse(opcode);
    core_ptr->init_xyn();
    for (int i = 0; i < 4; i++) {
        unsigned char cnt = (opcode_info >> (12 - i * 4)) & 0xF; // 逐位取数据
        switch (info->Id[i]) {
        case 'X': core_ptr->get_x(cnt); break; // 获取操作数 X
        case 'Y': core_ptr->get_y(cnt); break; // 获取操作数 Y
        case 'N': core_ptr->get_n(cnt); break; // 获取操作数 N
        default: break;                        // Hex magic, 非操作数
        }
    }
    (core_ptr->*(info->ProcExecFunc))(); // 执行指令
}

指令实现

void chip8_core::p_00E0() { Screen.reset(); PC += 2; } // CLS
void chip8_core::p_00EE() { SP--; PC = Stack[SP]; } // RET
void chip8_core::p_0NNN() {} // SYS NNN = NOP
void chip8_core::p_2NNN() { Stack[SP] = PC + 2; SP++; PC = N; } // CALL NNN
void chip8_core::p_1NNN() { PC = N; } // JP NNN
void chip8_core::p_BNNN() { PC = V[0] + N; } // JP V0, NNN
void chip8_core::p_3XNN() { PC += (V[X] == N) ? 4 : 2; } // SE VX, NN
void chip8_core::p_4XNN() { PC += (V[X] != N) ? 4 : 2; }// SNE VX, NN
void chip8_core::p_5XY0() { PC += (V[X] == V[Y]) ? 4 : 2; } // SE VX, VY
void chip8_core::p_9XY0() { PC += (V[X] != V[Y]) ? 4 : 2; } // SNE VX, VY
void chip8_core::p_EX9E() { PC += (KeyPad.is_key_pressed(V[X])) ? 4 : 2; } // SKP VX
void chip8_core::p_EXA1() { PC += (!KeyPad.is_key_pressed(V[X])) ? 4 : 2; } // SKNP VX
void chip8_core::p_FX0A() { // LD VX, K
    bool keypressed = false;
    for (int i = 0; i < 16; i++) {
        if (KeyPad.is_key_pressed(V[X])) { V[X] = i; keypressed = true; break; }
    }
    if (keypressed) { PC += 2; }
}
void chip8_core::p_6XNN() { V[X] = N; PC += 2; } // LD VX, NN
void chip8_core::p_8XY0() { V[X] = V[Y]; PC += 2; } // LD VX, VY
void chip8_core::p_FX07() { V[X] = DT; PC += 2; } // LD VX, DT
void chip8_core::p_FX15() { DT = V[X]; PC += 2; } // LD DT, VX
void chip8_core::p_FX18() { ST = V[X]; PC += 2; } // LD ST, VX
void chip8_core::p_ANNN() { I = N; PC += 2; } // LD I, NNN
void chip8_core::p_FX29() { I = V[X] * 5; PC += 2; } // LD F, VX
void chip8_core::p_FX55() { for (int i = 0; i <= X; i++) { Mem[I + i] = V[i]; } PC += 2; } // LD [I], VX
void chip8_core::p_FX65() { for (int i = 0; i <= X; i++) { V[i] = Mem[I + i]; } PC += 2; } // LD VX, [I]
void chip8_core::p_FX1E() { I += V[X]; PC += 2; } // ADD I, VX
void chip8_core::p_7XNN() { V[X] += N; PC += 2; } // ADD VX, NN
void chip8_core::p_8XY4() { // ADD VX, VY
    unsigned char VF = (V[X] > 255 - V[Y]) ? 1 : 0;
    V[X] += V[Y]; V[0xF] = VF; PC += 2;
}
void chip8_core::p_8XY5() { // SUB VX, VY
    unsigned char VF = (V[X] >= V[Y]) ? 1 : 0;
    V[X] -= V[Y]; V[0xF] = VF; PC += 2;
}
void chip8_core::p_8XY7() { // SUBN VX, VY
    unsigned char VF = (V[Y] >= V[X]) ? 1 : 0;
    V[X] = V[Y] - V[X]; V[0xF] = VF; PC += 2;
}
void chip8_core::p_8XY1() { V[X] |= V[Y]; PC += 2; } // OR VX, VY
void chip8_core::p_8XY2() { V[X] &= V[Y]; PC += 2; } // AND VX, VY
void chip8_core::p_8XY3() { V[X] ^= V[Y]; PC += 2; }// XOR VX, VY
void chip8_core::p_8XY6() { // SHR VX
    unsigned char VF = (V[X] & 1) ? 1 : 0; // 最右侧, 溢出位
    V[X] >>= 1; V[0xF] = VF; PC += 2;
}
void chip8_core::p_8XYE() { // SHL VX
    unsigned char VF = (V[X] & 0x80) ? 1 : 0; // 最左侧, 溢出位
    V[X] <<= 1; V[0xF] = VF; PC += 2;
}
void chip8_core::p_FX33() { // BCD VX
    Mem[I + 0] = (V[X] / 100); // 百位
    Mem[I + 1] = (V[X] / 10) % 10; // 十位
    Mem[I + 2] = V[X] % 10; // 个位
    PC += 2;
}
void chip8_core::p_CXNN() { V[X] = get_rand() & N; PC += 2; } // RND VX, NN
void chip8_core::p_DXYN() { // DRW VX, VY, N
    /* 从 I 处读取 N 个字节作为 N 行每行 8 列, 以屏幕坐标(VX, VY) 为左上角开始绘制 */
    /* 如有像素有碰撞 VF = 1 否则为 0 */
    V[0xF] = 0; // 重置碰撞标志
    for (int row = 0; row < N; row++) {
        unsigned char sprite_row = Mem[I + row]; // 获取 8 位精灵像素
        uint64_t old_row = Screen.get_line((row + V[Y]) % 32); // 旧行
        // old :      oooo oooo oooo oooo : 初如左所示, 左移 len(o) - len(n) 将左对齐, 
        // new : n1n1 ---- ---- ---- n0n0 : 如需环绕, 将 n0 左侧 len(o) 处复制一 n1 !
        uint64_t new_row = (((uint64_t(sprite_row)) << 56) >> V[X]) | (((uint64_t(sprite_row)) << (120 - V[X]))); //左移 56 到开头环绕右移 VX 得到新行
        if (old_row & new_row) { V[0xF] = 1; } // 如果 old_row & new_row 将 VF 设 1 否则不变!
        Screen.set_line((row + V[Y]) % 32, old_row ^ new_row); // 最终结果为 二者异或
    }
    PC += 2;
}
posted @ 2025-06-27 05:59  public_static_云水  阅读(12)  评论(0)    收藏  举报