【GameBoy模拟器 3】实现CPU指令的模板流程

先实现几条指令执行一下俄罗斯方块再说

目标:

  • 熟悉在本代码框架下,实现一个指令的大致流程
  • 实现JP指令,让程序计数器跳转;实现XOR指令和DI指令,尽可能多地执行几条俄罗斯方块的代码

知识点

  • 新增指令的流程
    • 在instructions.c中的instructions大数组表中新增指令结构体(指令类型、寻址模式、涉及的寄存器、条件等)
    • 在cpu_proc.c中,为新的指令类型实现一个对应的处理函数,并在IN_PROC processors[]指针表中新增指令及其处理函数,使得处理函数能通过函数指针调用
    • 参考指令表,实现指令具体操作内容、条件、标志位设置、时钟开销等
  • 函数指针
    • 业务场景其实很简单,就是实现一个从“指令类型”->“处理函数”的映射typedef void (*IN_PROC)(cpu_context *);的意思是:定义了一个自定义的函数指针类型,它的名字叫做IN_PROC,它的作用是指向一个以cpu_context为输入的函数且无返回值的函数。
 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
###cpu.h

typedef void (*IN_PROC)(cpu_context *);

// get the function processor by the instruction type
IN_PROC insr_get_processor(in_type type);

###cpu_proc.c

static void proc_di(cpu_context *ctx) {
  // DI inst will disable interrupt
    ctx->int_master_enabled = false;
}

// like a hashmap in array form
static IN_PROC processors[] = {
    [IN_NONE] = proc_none,
    [IN_NOP] = proc_nop,
    [IN_LD] = proc_ld,
    [IN_JP] = proc_jp,
    [IN_DI] = proc_di,
    [IN_XOR] = proc_xor
};

IN_PROC insr_get_processor(in_type type) {
    return processors[type];
}
  • ctx.cur_inst->type中的.和->
    • 省流:结构体变量用.访问成员,结构体指针用->访问成员
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
typedef struct {
    int a;
    int b;
} MyStruct;

MyStruct s;
s.a = 10;  // 使用 . 运算符访问成员 a
s.b = 20;  // 使用 . 运算符访问成员 b

typedef struct {
    int a;
    int b;
} MyStruct;

MyStruct *p = &s;
p->a = 10;  // 使用 -> 运算符访问成员 a
p->b = 20;  // 使用 -> 运算符访问成员 b
  • 枚举类型的妙用(无用的知识又增加了)

    • C3的JP指令为[0xC3] = {IN_JP, AM_D16},没有定义条件和寄存器,然而调试时却能通过check_cond,这是因为在C语言中,枚举类型(enum)的默认值从0开始递增。如果你不显式地为枚举类型的成员赋值,那么第一个枚举成员的值将是0,第二个成员的值将是1,以此类推。

    在你的枚举类型 reg_type 中,RT_NONE 是第一个枚举成员,因此它的值是0。如果你有一个结构体成员类型为 reg_type,并且你没有显式地初始化它,那么它的默认值将是0,也就是 RT_NONE。

    • 等价于[0xC3] = {IN_JP, AM_D16, RT_NONE, RT_NONE, CT_NONE, 0}
 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
#include <stdio.h>

typedef enum {
    RT_NONE, // 默认值为0
    RT_A,    // 默认值为1
    RT_F,    // 默认值为2
    RT_B,    // 默认值为3
    RT_C,    // 默认值为4
    RT_D,    // 默认值为5
    RT_E,    // 默认值为6
    RT_H,    // 默认值为7
    RT_L,    // 默认值为8
    RT_AF,   // 默认值为9
    RT_BC,   // 默认值为10
    RT_DE,   // 默认值为11
    RT_HL,   // 默认值为12
    RT_SP,   // 默认值为13
    RT_PC    // 默认值为14
} reg_type;

typedef struct {
    reg_type reg;
} MyStruct;

int main() {
    MyStruct s;
    printf("Default value of reg: %d\n", s.reg); // 输出0,即RT_NONE
    return 0;
}

成果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(base)$ gbemu/gbemu ../../roms/Tetris.gb
Opened: ../../roms/Tetris.gb
Cartridge Loaded:
         Title    : TETRIS
         Type     : 00 (ROM ONLY)
         ROM Size : 32 KB
         RAM Size : 00
         LIC Code : 01 (Nintendo R&D1)
         ROM Vers : 01
         Checksum : 0A (PASSED)
Cart loaded..
SDL INIT
TTF INIT
0100: NOP      (00 C3 50) A: 01 B: 00 C: 00
0101: JP       (C3 50 01) A: 01 B: 00 C: 00
0150: JP       (C3 0C 02) A: 01 B: 00 C: 00
020C: XOR      (AF 21 FF) A: 01 B: 00 C: 00
020D: <NONE>   (21 FF DF) A: 00 B: 00 C: 00
INVALID INSTRUCTION!

可以看到,对于Tetris俄罗斯方块游戏,手动设置pc从100开始执行,发现:

  • 成功执行了JP指令(观察到pc值跳转)
  • 成功执行了XOR指令(观察到寄存器a的值为异或的计算结果)

代码实现:

https://github.com/SimpleCodeJust4Fun/CBoy/pull/2

Licensed under CC BY-NC-SA 4.0
最后更新于 Jun 25, 2025 15:02 UTC
Built with Hugo
Jimmy 设计的 Stack 主题