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 & SpritePixel 则 VF = 1 否则不变
异或绘制像素 ScreenPixel ^= SpritePixel
由于异或的真值表如下:
| a | b | a ^ b |
|---|---|---|
| true | true | false |
| true | false | true |
| false | true | true |
| false | false | false |
故又可理解为: 若 SpritePixel 为 true 则 ScreenPixel 需要被翻转!
一些代码(归档参考, 人工整理或有疏漏)
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;
}

浙公网安备 33010602011771号