吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2279|回复: 15
上一主题 下一主题
收起左侧

[C&C++ 原创] [长期更新] RV32IMAFDQ指令集模拟器 包含交互式汇编级调试器

  [复制链接]
跳转到指定楼层
楼主
Jar36 发表于 2026-7-24 16:10 回帖奖励
本帖最后由 Jar36 于 2026-8-3 20:08 编辑

【原创C&C++】RISCVemu:60/60通过riscv-tests的RV32IMA指令集模拟器,含完整交互式调试器【长期更新项目】

0xfe. 已完成FDQ扩展 见回帖和v0.3tag

(希望uint8够用)(笑)

0x00. 为什么写这个模拟器?

RISC-V 作为开源指令集架构,近年来在学术界和工业界都备受关注。对于学习计算机体系结构的人来说,亲手实现一个能跑的模拟器,比读一百遍手册都管用。

RISCVemu 是我用 C23 写的一个 RV32IMA(U) 指令集模拟器,目标是:

  • 干净:代码结构清晰,模块化设计,易于阅读和修改
  • 可测试:完整通过 riscv-tests 全部 60 个用例
  • 可用:自带交互式调试器,支持单步、断点、反汇编、寄存器/内存读写
  • 长期更新: 还在肝F D Q V扩展中
  • 最后: 跑一下OpenSBI和Linux

项目地址:https://github.com/Phi-Lia093/RISCVemu


0x01. 整体架构

┌─────────────────────────────────────────────────────────────┐
│                        RISCVemu                            │
├─────────────┬─────────────┬─────────────┬─────────────────┤
│   emu.c     │   exec.c    │   mem.c     │   debugger.c    │
│  (主循环)    │ (指令执行)   │ (内存管理)   │   (调试器)      │
├─────────────┴─────────────┴─────────────┴─────────────────┤
│                    disasm.c (反汇编器)                      │
├─────────────────────────────────────────────────────────────┤
│              extension/ (M/A/Zicsr/Zifencei)               │
└─────────────────────────────────────────────────────────────┘

核心数据结构 machine_state

struct machine_state
{
    uint32_t gpr[32];        // 32个通用寄存器
    uint32_t pc;             // 程序计数器
    uint8_t *main_memory;    // 4GB 内存空间(按需分配)
    int terminated;          // 终止标志
#ifdef CONFIG_ENABLE_A_EXTENSION
    struct hashmap mmu_flags; // A扩展用到的内存标志
#endif
#ifdef CONFIG_ENABLE_DEBUGGER
    int single_step;         // 单步模式
    uint32_t breakpoint;     // 断点地址
    uint32_t breakpoint_enabled;
#endif
    uint32_t privilege;      // 特权级(目前固定为U模式)
};

C23 特性应用

  • __maybe_unused / __always_inline:更清晰的编译器提示
  • constexpr 风格的宏配置
  • static_assert 用于编译时检查(虽然这里没用但项目里有用到)

0x02. 指令解码与执行引擎

核心设计思路:按 opcode 分发,再按 funct3/funct7 细分。

void exec(uint32_t ins)
{
    uint32_t opcode = get_opcode(ins);
    uint32_t funct3 = get_funct3(ins);
    uint32_t funct7 = get_funct7(ins);
    uint32_t rs1 = get_rs1(ins);
    uint32_t rs2 = get_rs2(ins);
    uint32_t rd = get_rd(ins);

    switch (opcode)
    {
    // R格式:算术/逻辑运算
    case 0b0110011:
    {
        // M扩展指令(乘除)
        if (unlikely(funct7 == 0b0000001))
        {
            m_ins_optable[funct3][funct7](rs2, rs1, rd);
        }
        else
        {
            switch (funct3)
            {
            case 0: // ADD / SUB
                if (likely(funct7 == 0b0000000))
                    insi_r_add(rs2, rs1, rd);
                else if (funct7 == 0b0100000)
                    insi_r_sub(rs2, rs1, rd);
                // ...
            // ...
            }
        }
        break;
    }
    // I格式:立即数运算 / Load
    case 0b0010011: // 算术立即数
    {
        uint32_t imm = sign_extend_12((ins >> 20) & 0xFFF);
        // ...
        break;
    }
    // ... 更多 opcode
    }
}

M扩展指令表:用二维函数指针数组实现,简洁高效。

static void (*m_ins_optable[8][128])(uint32_t, uint32_t, uint32_t) = {
    [0] = { [0b0000001] = &insm_r_mul },
    [1] = { [0b0000001] = &insm_r_mulh },
    [2] = { [0b0000001] = &insm_r_mulsu },
    // ...
};

A扩展(原子操作):支持 LR/SC 和全部 AMO 指令。

case 0b0101111: // AMO
{
    if (likely(funct3 == 0x2))
    {
        uint32_t funct5 = (ins >> 27) & 0x1F;
        switch (funct5)
        {
        case 0x00: insa_r_amoadd_w(rs1, rs2, rd); break;
        case 0x01: insa_r_amoswap_w(rs1, rs2, rd); break;
        case 0x02: insa_r_lr_w(rs1, rd); break;
        case 0x03: insa_r_sc_w(rs1, rs2, rd); break;
        // ... 全部 AMO 操作
        }
    }
    break;
}

性能优化exec()函数大小内联7.1KB 保证可以置入L1-I缓存 perf实测miss率<1.7% ; 使用严谨的热度排序 保证分支预测miss率最小


0x03. 内存管理:按需分配,支持原子操作标志

内存管理采用按需分配策略,并非一开始就分配完整的 4GB 空间:

void init_mem(void)
{
    g_state.main_memory = (uint8_t *)calloc(MEM_SIZE, sizeof(uint8_t));
    // MEM_SIZE = 0x100000000 (4GB) 但 calloc 是虚拟的,物理页按需分配
#ifdef CONFIG_ENABLE_A_EXTENSION
    hashmap_init(&g_state.mmu_flags);
#endif
}

UART 模拟:写入 0x10000000 的数据会作为 ASCII 字符直接输出到 stdout。这个特性让 printf 可以直接工作:

// 在 mem_write32 中:
if (addr == UART_BASE) {
    putchar((char)value & 0xFF);
    return;
}

0x04. 交互式调试器

调试器是让这个模拟器可用的关键。支持的命令包括单步、断点、寄存器修改、反汇编等。

命令分发核心逻辑

void tick_debugger(void)
{
    // 显示当前指令
    uint32_t ins = mem_read32_unsigned(g_state.pc);
    if (show_disasm) {
        char *disasm_str = disasm(ins);
        printf("0x%08x:  %08x  %s\n", g_state.pc, ins, disasm_str);
    }

    while (1) {
        printf("DEBUG> ");
        fgets(line, sizeof(line), stdin);
        // 解析命令...
        switch (cmd) {
        case 's': case 'n': cmd_single_step(); return;
        case 'c': cmd_continue(); return;
        case 'r': cmd_registers(); break;
        case 'R': cmd_set_register(args); break;
        case 'u': cmd_disasm(args); break;
        case 'b': cmd_breakpoint_set(args); break;
        // ...
        }
    }
}

寄存器设置支持数字或名称,比如 R a0 0x1234R 10 0x1234 都可以。

断点实现:简单地在每次执行前检查 g_state.pc == breakpoint


0x05. 测试与验证

测试结果:运行 make run_tests,全部 60 个测试通过:

==========================================
  TEST COMPLETED!
  TOTAL: 60
  PASSED: 60
  FAILED: 0
  SKIPPED: 0
==========================================

测试覆盖:

  • **rv32ui-p-***:基础整数指令(add、sub、lui、auipc、分支、跳转、加载/存储等)
  • **rv32um-p-***:M扩展(mul、div、rem 等)
  • **rv32ua-p-***:A扩展(LR/SC、全部 AMO 指令)

其中 rv32ui-p-ma_data 涉及特权级/中断处理,但模拟器依然处理得当。


0x06. 运行效果

正常运行示例程序

15:58:43 [INFO] RISC-V Emulator starting...
15:58:43 [INFO] Program: run.bin
15:58:43 [INFO] Base address: 0x1000
15:58:43 [INFO] loaded 8383 bytes to 0x1000
15:58:43 [INFO] starting execution at PC=0x1000
Hello, World!
15:58:43 [INFO] ECALL instruction executed, Terminating...
15:58:43 [INFO] Execution terminated. Return value: 0x325

调试模式

0x00001000:  ff010113  addi sp, sp, -16
DEBUG> s
0x00001004:  00112623  sw ra, 12(sp)
DEBUG> r
PC=0x00001004
REGISTERS:
 zero=0x00000000    ra=0x00000000    sp=0xfffffff0    gp=0x00000000 
   tp=0x00000000    t0=0x00000000    t1=0x00000000    t2=0x00000000 
   s0=0x00000000    s1=0x00000000    a0=0x00000000    a1=0x00000000 
   ...
DEBUG> b 0x100c
Breakpoint set at 0x0000100c
DEBUG> c
15:59:34 [INFO] breakpoint hit at PC=0x100c
0x0000100c:  01010413  addi s0, sp, 16

0x07. 构建与使用

依赖

  • C23 兼容编译器(GCC 13+ / Clang 16+)
  • CMake 3.10+
  • (测试用)RISC-V GNU Toolchain

构建

git clone --recursive https://github.com/你的用户名/RISCVemu
cd RISCVemu
mkdir build && cd build
cmake .. && make

运行

./RISCVemu run.bin 0x1000        # 正常运行
./RISCVemu run.bin 0x1000 -d     # 调试模式
./RISCVemu run.bin 0x1000 -d -b 0x1200  # 带断点启动

CMake 配置选项(可在 ccmake .. 中调整):

选项 说明 默认
CONFIG_ENABLE_DEBUGGER 调试器 ON
CONFIG_ENABLE_M_EXTENSION M扩展 ON
CONFIG_ENABLE_A_EXTENSION A扩展 ON
CONFIG_ENABLE_ZICSR_EXTENSION CSR指令 ON
CONFIG_ENABLE_UART_DEVICE UART模拟 ON

还有很多扩展欢迎尝试


0xff. 总结

RISCVemu 是一个干净、可测试、功能完整的 RV32IMA 模拟器,适合以下场景:

  • 学习 RISC-V 指令集
  • 学习模拟器开发
  • 作为嵌入式开发的测试平台

如果觉得这个项目对你有帮助,欢迎到 GitHub 上给个 Star ⭐

项目地址:https://github.com/Phi-Lia093/RISCVemu


参考资料


本模拟器仅供学习和研究使用,欢迎 Issue 和 PR!

免费评分

参与人数 16吾爱币 +16 热心值 +16 收起 理由
allspark + 1 + 1 用心讨论,共获提升!
Kahlil + 1 + 1 谢谢@Thanks!
fengbolee + 1 + 1 我很赞同!
wapj20260721 + 1 + 1 用心讨论,共获提升!
gouzi123 + 1 + 1 我很赞同!
52pojieplayer + 1 谢谢@Thanks!
unsignedv1 + 1 + 1 我很赞同!
laotzudao0 + 2 + 1 我很赞同!
Conight + 1 + 1 鼓励转贴优秀软件安全工具和文档!
vergil0528 + 1 + 1 谢谢@Thanks!
yixi + 1 + 1 谢谢@Thanks!
buluo533 + 1 + 1 用心讨论,共获提升!
c271687388 + 1 + 1 谢谢@Thanks!
ioyr5995 + 1 + 1 热心回复!
shiyue2925 + 1 + 1 谢谢@Thanks!
ope087 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| Jar36 发表于 2026-8-3 20:15 |楼主
已完成FDQ扩展 84/84 通过riscv-tests
[Plain Text] 纯文本查看 复制代码
 
Running tests
==========================================
  RISC-V emulator batch test
  emulator executable: /home/philia093/RISCVemu/build/RISCVemu
  test directory: /home/philia093/RISCVemu/build/tests/tmp
  source test directory: /home/philia093/RISCVemu/tests
  base address: 0x80000000
  summary log: build/test_results.log
  full logs: build/test_logs/
==========================================

[rv32mi-p-zicntr] RUNNING... &#9989; PASS
[rv32ua-p-amoadd_w] RUNNING... &#9989; PASS
[rv32ua-p-amoand_w] RUNNING... &#9989; PASS
[rv32ua-p-amomaxu_w] RUNNING... &#9989; PASS
[rv32ua-p-amomax_w] RUNNING... &#9989; PASS
[rv32ua-p-amominu_w] RUNNING... &#9989; PASS
[rv32ua-p-amomin_w] RUNNING... &#9989; PASS
[rv32ua-p-amoor_w] RUNNING... &#9989; PASS
[rv32ua-p-amoswap_w] RUNNING... &#9989; PASS
[rv32ua-p-amoxor_w] RUNNING... &#9989; PASS
[rv32ua-p-lrsc] RUNNING... &#9989; PASS
[rv32ud-p-fadd] RUNNING... &#9989; PASS
[rv32ud-p-fclass] RUNNING... &#9989; PASS
[rv32ud-p-fcmp] RUNNING... &#9989; PASS
[rv32ud-p-fcvt] RUNNING... &#9989; PASS
[rv32ud-p-fcvt_w] RUNNING... &#9989; PASS
[rv32ud-p-fdiv] RUNNING... &#9989; PASS
[rv32ud-p-fmadd] RUNNING... &#9989; PASS
[rv32ud-p-fmin] RUNNING... &#9989; PASS
[rv32ud-p-ldst] RUNNING... &#9989; PASS
[rv32ud-p-recoding] RUNNING... &#9989; PASS
[rv32uf-p-fadd] RUNNING... &#9989; PASS
[rv32uf-p-fclass] RUNNING... &#9989; PASS
[rv32uf-p-fcmp] RUNNING... &#9989; PASS
[rv32uf-p-fcvt] RUNNING... &#9989; PASS
[rv32uf-p-fcvt_w] RUNNING... &#9989; PASS
[rv32uf-p-fdiv] RUNNING... &#9989; PASS
[rv32uf-p-fmadd] RUNNING... &#9989; PASS
[rv32uf-p-fmin] RUNNING... &#9989; PASS
[rv32uf-p-ldst] RUNNING... &#9989; PASS
[rv32uf-p-move] RUNNING... &#9989; PASS
[rv32uf-p-recoding] RUNNING... &#9989; PASS
[rv32ui-p-add] RUNNING... &#9989; PASS
[rv32ui-p-addi] RUNNING... &#9989; PASS
[rv32ui-p-and] RUNNING... &#9989; PASS
[rv32ui-p-andi] RUNNING... &#9989; PASS
[rv32ui-p-auipc] RUNNING... &#9989; PASS
[rv32ui-p-beq] RUNNING... &#9989; PASS
[rv32ui-p-bge] RUNNING... &#9989; PASS
[rv32ui-p-bgeu] RUNNING... &#9989; PASS
[rv32ui-p-blt] RUNNING... &#9989; PASS
[rv32ui-p-bltu] RUNNING... &#9989; PASS
[rv32ui-p-bne] RUNNING... &#9989; PASS
[rv32ui-p-fence_i] RUNNING... &#9989; PASS
[rv32ui-p-jal] RUNNING... &#9989; PASS
[rv32ui-p-jalr] RUNNING... &#9989; PASS
[rv32ui-p-lb] RUNNING... &#9989; PASS
[rv32ui-p-lbu] RUNNING... &#9989; PASS
[rv32ui-p-ld_st] RUNNING... &#9989; PASS
[rv32ui-p-lh] RUNNING... &#9989; PASS
[rv32ui-p-lhu] RUNNING... &#9989; PASS
[rv32ui-p-lui] RUNNING... &#9989; PASS
[rv32ui-p-lw] RUNNING... &#9989; PASS
[rv32ui-p-ma_data] RUNNING... &#9989; PASS
[rv32ui-p-or] RUNNING... &#9989; PASS
[rv32ui-p-ori] RUNNING... &#9989; PASS
[rv32ui-p-sb] RUNNING... &#9989; PASS
[rv32ui-p-sh] RUNNING... &#9989; PASS
[rv32ui-p-simple] RUNNING... &#9989; PASS
[rv32ui-p-sll] RUNNING... &#9989; PASS
[rv32ui-p-slli] RUNNING... &#9989; PASS
[rv32ui-p-slt] RUNNING... &#9989; PASS
[rv32ui-p-slti] RUNNING... &#9989; PASS
[rv32ui-p-sltiu] RUNNING... &#9989; PASS
[rv32ui-p-sltu] RUNNING... &#9989; PASS
[rv32ui-p-sra] RUNNING... &#9989; PASS
[rv32ui-p-srai] RUNNING... &#9989; PASS
[rv32ui-p-srl] RUNNING... &#9989; PASS
[rv32ui-p-srli] RUNNING... &#9989; PASS
[rv32ui-p-st_ld] RUNNING... &#9989; PASS
[rv32ui-p-sub] RUNNING... &#9989; PASS
[rv32ui-p-sw] RUNNING... &#9989; PASS
[rv32ui-p-xor] RUNNING... &#9989; PASS
[rv32ui-p-xori] RUNNING... &#9989; PASS
[rv32um-p-div] RUNNING... &#9989; PASS
[rv32um-p-divu] RUNNING... &#9989; PASS
[rv32um-p-mul] RUNNING... &#9989; PASS
[rv32um-p-mulh] RUNNING... &#9989; PASS
[rv32um-p-mulhsu] RUNNING... &#9989; PASS
[rv32um-p-mulhu] RUNNING... &#9989; PASS
[rv32um-p-rem] RUNNING... &#9989; PASS
[rv32um-p-remu] RUNNING... &#9989; PASS
[rv32uzicond-p-czero_eqz] RUNNING... &#9989; PASS
[rv32uzicond-p-czero_nez] RUNNING... &#9989; PASS

==========================================
  TEST COMPLETED!
  TOTAL: 84
  PASSED: 84
  FAILED: 0
  SKIPPED: 0
  SUMMARY LOG: build/test_results.log
  FULL LOGS: build/test_logs/
==========================================

浮点还是有点调试难度的 我使用了Berkeley IEEE Binary Floating-Point Library 现在如果要编译请自行下载和放入LD路径(有许可证问题)
浮点主要的坑是NaN Inf等非规则操作的反馈问题 不能完全相信berkeley的结果 有时候需要自己CAST
比如
[C] 纯文本查看 复制代码
static uint32_t
rv_fmin_s(uint32_t a, uint32_t b)
{
    int a_snan = is_signaling_nan_s(a);
    int b_snan = is_signaling_nan_s(b);
    int a_qnan = is_nan_s(a) && !a_snan;
    int b_qnan = is_nan_s(b) && !b_snan;
    if (a_snan || b_snan)
    {
        fcsr |= NV;
    }
    if ((a_snan || a_qnan) && (b_snan || b_qnan))
    {
        return RISCV_CANONICAL_NAN_S;
    }
    if (a_snan || a_qnan)
    {
        return b;
    }
    if (b_snan || b_qnan)
    {
        return a;
    }
    float32_t fa = { a }, fb = { b };
    if (f32_eq(fa, fb))
    {
        return ((a >> 31) & 1) ? a : b;
    }
    return f32_lt(fa, fb) ? a : b;
}

就和标准IEEE规范不一样 2个NaN才NV 一个是返回另一个
FLT.S and FLE.S perform what the IEEE 754-2008 standard refers to as signaling comparisons: that is, they
set the invalid operation exception flag if either input is NaN. FEQ.S performs a quiet comparison: it only
sets the invalid operation exception flag if either input is a signaling NaN. For all three instructions, the
result is 0 if either operand is NaN.

总之我的FDQ扩展(用AI)写了一些注释 我自己是没有注释习惯的 大家可以配合
The RISC-V Instruction Set
Manual, Volume I
Unprivileged Architecture
Version 20260120: Official Release

食用 下一个更新会是一些Za Zb的小扩展(不想写Sv32 拖延症中)
沙发
MingYuMeng 发表于 2026-7-25 09:55
3#
绵绵青山 发表于 2026-7-25 10:24
4#
loalin 发表于 2026-7-25 12:36
让我先啃两口,自己做模拟器还是程序员的一个梦想,不断学习中
5#
mjhwzwg6 发表于 2026-7-28 01:28
让我先啃两口,自己做模拟器还是程序员的一个梦想,不断学习中
6#
c271687388 发表于 2026-7-28 10:42
感谢大佬分享!!!
7#
turmasi1234 发表于 2026-7-28 12:43
跟着楼主慢慢学,一起成长
8#
Iamx688110 发表于 2026-7-28 12:52
让我试试
9#
linkens 发表于 2026-7-30 19:09
感谢分享
10#
HACING 发表于 2026-7-31 15:15
我虽然确实看不懂一点,但是楼主肯定是非常厉害的,也感谢楼主的分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-8-18 20:35

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表