第 4 章:寄存器实现
学习目标
- 理解 RISC-V 寄存器组织
- 实现寄存器读写
- 处理 x0 特殊性
4.1 思考:为什么需要寄存器?
你有没有想过,为什么 CPU 需要寄存器?
想象一下,如果每次计算都要从内存读取数据:
int a = memory[0x1000];
int b = memory[0x1004];
int c = a + b; // 需要两次内存访问!
而使用寄存器:
int a = reg[10]; // 寄存器访问,极快!
int b = reg[11];
int c = a + b;
寄存器就是 CPU 内部的高速存储,比内存快 100 倍以上!
4.2 RISC-V 有多少个寄存器?
RISC-V 有 32 个通用寄存器,编号 x0-x31。
问题:为什么是 32 个?为什么不是 16 个或 64 个?
思考一下:
- 太少:频繁访问内存,性能差
- 太多:指令编码困难,硬件复杂
32 个是一个平衡点:5 位就能编码(2^5 = 32)。
4.3 x0 的秘密
x0 是一个特殊的寄存器,永远是 0。
问题:为什么要浪费一个寄存器存储 0?
想想这些场景:
addi x1, x0, 10 # x1 = 0 + 10,相当于 li x1, 10
add x2, x3, x0 # x2 = x3 + 0,相当于 mv x2, x3
x0 让很多操作变得简单!这就是 RISC 的哲学:用简单的指令组合出复杂的功能。
4.4 实现寄存器
typedef struct {
uint32_t gpr[32]; // 通用寄存器
uint32_t pc; // 程序计数器
} CPU_state;
CPU_state cpu;
问题:这样实现有什么问题?
提示:x0 应该永远是 0,但这里可以被修改!
4.5 正确的实现
uint32_t reg_read(int idx) {
if (idx == 0) return 0; // x0 永远返回 0
return cpu.gpr[idx];
}
void reg_write(int idx, uint32_t val) {
if (idx == 0) return; // 写 x0 无效
cpu.gpr[idx] = val;
}
思考:为什么要用函数而不是直接访问数组?
这就是封装的好处:
- 保证 x0 的特殊性
- 方便添加调试信息
- 可以统计寄存器访问次数
4.6 实践练习
实现一个函数,打印所有寄存器的值:
void dump_registers() {
const char *abi_name[] = {
"zero", "ra", "sp", "gp", "tp",
"t0", "t1", "t2",
"s0", "s1",
"a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
"s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11",
"t3", "t4", "t5", "t6"
};
for (int i = 0; i < 32; i++) {
printf("x%d(%s) = 0x%08x\n", i, abi_name[i], reg_read(i));
}
printf("pc = 0x%08x\n", cpu.pc);
}
问题:你能说出 a0-a7 寄存器的用途吗?