1. 搭建RISC-V指令集模拟器框架

任务目标

实现一个可运行的RISC-V RV32I指令集模拟器核心框架,支持指令的取指、解码、执行以及寄存器堆状态更新。本案例聚焦于算术运算指令(ADD/SUB/ADDI)与分支指令(BEQ)的最小闭环,为后续扩展其他指令类别和微架构设计提供可验证的参考模型。

环境依赖

仅需标准C++17编译器(g++或clang++),无第三方库依赖。建议Linux或macOS环境。

核心实现

模拟器采用经典的冯·诺依曼结构:内存中顺序存放指令,PC指向当前指令地址。解码阶段根据opcode和funct3/funct7字段识别指令类型,执行阶段完成计算并更新寄存器堆。分支指令通过比较两个源寄存器决定是否跳转。

#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>

class RV32ISim {
public:
    static constexpr uint32_t REG_COUNT = 32;
    static constexpr uint32_t MEM_SIZE = 4096;
    static constexpr uint32_t BASE_ADDR = 0x1000;

    RV32ISim() : regs(REG_COUNT, 0), mem(MEM_SIZE, 0), pc(BASE_ADDR) {
        regs[0] = 0;  // x0 恒为0
    }

    // 加载程序到内存(从BASE_ADDR开始)
    void loadProgram(const std::vector<uint32_t>& instructions) {
        for (size_t i = 0; i < instructions.size(); ++i) {
            uint32_t addr = BASE_ADDR + i * 4;
            if (addr + 4 > BASE_ADDR + MEM_SIZE) break;
            mem[addr - BASE_ADDR] = instructions[i];
        }
    }

    // 执行一条指令,返回是否继续执行
    bool step() {
        if (pc < BASE_ADDR || pc + 4 > BASE_ADDR + MEM_SIZE) return false;
        
        uint32_t instr = mem[pc - BASE_ADDR];
        uint32_t opcode = instr & 0x7f;
        uint32_t rd = (instr >> 7) & 0x1f;
        uint32_t funct3 = (instr >> 12) & 0x7;
        uint32_t rs1 = (instr >> 15) & 0x1f;
        uint32_t rs2 = (instr >> 20) & 0x1f;
        uint32_t funct7 = (instr >> 25) & 0x7f;

        uint32_t next_pc = pc + 4;

        switch (opcode) {
            case 0x33: {  // R-type
                if (funct3 == 0 && funct7 == 0) {
                    // ADD
                    regs[rd] = regs[rs1] + regs[rs2];
                } else if (funct3 == 0 && funct7 == 0x20) {
                    // SUB
                    regs[rd] = regs[rs1] - regs[rs2];
                } else {
                    printf("不支持的R-type指令: funct3=%x funct7=%x\n", funct3, funct7);
                    return false;
                }
                break;
            }
            case 0x13: {  // I-type (ADDI)
                if (funct3 == 0) {
                    int32_t imm = static_cast<int32_t>(instr) >> 20;  // 符号扩展
                    regs[rd] = regs[rs1] + imm;
                } else {
                    printf("不支持的I-type指令: funct3=%x\n", funct3);
                    return false;
                }
                break;
            }
            case 0x63: {  // B-type (BEQ)
                if (funct3 == 0) {
                    int32_t imm = ((instr >> 31) & 1) << 12 |
                                  ((instr >> 7) & 1) << 11 |
                                  ((instr >> 25) & 0x3f) << 5 |
                                  ((instr >> 8) & 0xf) << 1;
                    if (imm & 0x1000) imm |= 0xffffe000;  // 符号扩展
                    if (regs[rs1] == regs[rs2]) {
                        next_pc = pc + imm;
                    }
                } else {
                    printf("不支持的B-type指令: funct3=%x\n", funct3);
                    return false;
                }
                break;
            }
            default:
                printf("不支持的opcode: %x\n", opcode);
                return false;
        }

        regs[0] = 0;  // 确保x0始终为0
        pc = next_pc;
        return true;
    }

    void dumpRegs() {
        for (int i = 0; i < 8; ++i) {
            printf("x%d=%d  x%d=%d  x%d=%d  x%d=%d\n",
                   i, regs[i], i+8, regs[i+8], i+16, regs[i+16], i+24, regs[i+24]);
        }
    }

    uint32_t getPC() const { return pc; }

private:
    std::vector<uint32_t> regs;
    std::vector<uint32_t> mem;
    uint32_t pc;
};

int main() {
    // 测试程序:x1=5, x2=3, x3=x1+x2, x4=x1-x2, x5=x3+10, 若x3==8则跳转
    std::vector<uint32_t> program = {
        0x00500093,  // addi x1, x0, 5    (x1 = 5)
        0x00300113,  // addi x2, x0, 3    (x2 = 3)
        0x002081b3,  // add  x3, x1, x2   (x3 = 8)
        0x40208233,  // sub  x4, x1, x2   (x4 = 2)
        0x00a18293,  // addi x5, x3, 10   (x5 = 18)
        0x00318463,  // beq  x3, x3, 8    (跳转+8,跳过下一条)
        0x00100313,  // addi x6, x0, 1    (不应执行)
        0x00000393,  // addi x7, x0, 0    (x7 = 0)
    };

    RV32ISim sim;
    sim.loadProgram(program);

    int steps = 0;
    while (steps < 20 && sim.step()) {
        steps++;
    }

    printf("\n执行 %d 条指令后寄存器状态:\n", steps);
    sim.dumpRegs();
    printf("PC = 0x%x\n", sim.getPC());

    // 验证关键结果
    bool pass = true;
    // 由于无法直接读取寄存器,通过dumpRegs人工验证
    printf("\n预期: x3=8, x4=2, x5=18, x6=0(未执行), x7=0\n");
    printf("若输出与预期一致,则测试通过。\n");
    return 0;
}

编译与运行

g++ -std=c++17 -o rv32i_sim rv32i_sim.cpp
./rv32i_sim

验证方法

程序执行7条指令后停止(第8条BEQ跳转跳过第7条,第8条执行后PC指向内存末尾)。观察输出寄存器值:

  • x3 应为8(ADD结果)
  • x4 应为2(SUB结果)
  • x5 应为18(ADDI结果)
  • x6 应为0(BEQ跳转使其未执行)
  • x7 应为0(跳转目标指令)

若输出与上述一致,说明指令解码、执行、寄存器更新和分支跳转逻辑均正确,模拟器框架验证通过。

常见问题

  • 立即数符号扩展:ADDI和BEQ的立即数均为有符号数,需正确进行符号扩展,否则负数运算和反向跳转会出错。
  • x0寄存器:硬件上x0恒为0,每次指令执行后需强制清零,防止被写入。
  • PC更新时机:分支指令需先计算目标地址再更新PC,顺序执行时PC+4,本实现通过next_pc变量统一处理。