恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
PA1 收尾讲义精读:从状态机、SDB、RTFM 到提交实验报告本文结合 ysyx-workbench/nemu 的实际代码,讲解 PA1 最后一讲《如何阅读手册》中的必答题、实验报告要求,以及它们
首页
资讯中心
/
PA1 收尾讲义精读:从状态机、SDB、RTFM 到提交实验报告本文结合 ysyx-workbench/nemu 的实际代码,讲解 PA1 最后一讲《如何阅读手册》中的必答题、实验报告要求,以及它们
PA1 收尾讲义精读:从状态机、SDB、RTFM 到提交实验报告本文结合 ysyx-workbench/nemu 的实际代码,讲解 PA1 最后一讲《如何阅读手册》中的必答题、实验报告要求,以及它们
发布时间:2026/10/1 14:03:28
本文结合ysyx-workbench/nemu的实际代码讲解 PA1 最后一讲《如何阅读手册》中的必答题、实验报告要求以及它们和 NEMU 源码的对应关系。摘要PA1 最后一讲并非单纯介绍“如何看手册”而是要求你掌握四种能力用状态机理解程序运行用数据说明基础设施的价值通过目录、搜索和筛选快速查阅 ISA 手册用命令行统计代码并理解-Wall -Werror。这些内容最终都要写进学号.pdf实验报告并通过make submit提交。关键词NEMU SDB 状态机 RTFM riscv32 -wWall -Werror PA1一、必答题一程序是状态机讲义要求画出计算1 2 ... 100的程序状态机。先写成 riscv32 风格li t0, 0 # sum li t1, 0 # i loop: addi t1, t1, 1 # i i 1 add t0, t0, t1 # sum sum i blt t1, 100, loop # if (i 100) goto loop也可以用 C 描述int sum 0; for (int i 1; i 100; i) { sum i; }程序状态可以表示为(PC, sum, i)状态转移过程初始 (loop, 0, 0) 第一次循环 (loop, 0, 0) - (addi, 0, 0) - (add, 0, 1) - (blt, 1, 1) - (loop, 1, 1) 第二次循环 (loop, 1, 1) - (addi, 1, 1) - (add, 1, 2) - (blt, 3, 2) - (loop, 3, 2)最后sum 5050 i 100二、状态机如何对应你的 NEMU 代码你的 NEMU 中CPU 执行循环在nemu/src/cpu/cpu-exec.c核心代码static void exec_once(Decode *s, vaddr_t pc) { s-pc pc; s-snpc pc; isa_exec_once(s); cpu.pc s-dnpc; }它对应讲义里的从 PC 取出指令 执行指令 更新 PC执行循环static void execute(uint64_t n) { Decode s; for (; n 0; n--) { if (nemu_state.state ! NEMU_RUNNING) { break; } exec_once(s, cpu.pc); g_nr_guest_inst; trace_and_difftest(s, cpu.pc); IFDEF(CONFIG_DEVICE, device_update()); } }因此一次 exec_once() 一次状态转移状态由这些部分组成cpu.pc cpu.gpr[32] pmem你在nemu/src/isa/riscv32/include/isa-def.h中定义的typedef struct { word_t gpr[32]; vaddr_t pc; word_t mstatus; word_t mtvec; word_t mepc; word_t mcause; } riscv32_CPU_state;就是状态机中的 CPU 状态。三、必答题二基础设施能节省多少时间讲义给出的假设编译 NEMU 500 次 其中 90% 用于调试 每次调试需要获取 20 个信息 每个信息 GDB 需要 30 秒计算调试次数 500 × 90% 450 信息数量 450 × 20 9000 GDB 总时间 9000 × 30 秒 270000 秒 270000 秒 4500 分钟 75 小时如果使用 SDB每个信息只需要10 秒总时间9000 × 10 秒 90000 秒 90000 秒 1500 分钟 25 小时因此节省75 小时 - 25 小时 50 小时也就是大约2 天多这些时间来自 SDB 提供的si 单步执行 info r 查看寄存器 x 扫描内存 p 表达式求值 w 监视点 b 断点对应的代码在nemu/src/monitor/sdb/sdb.c nemu/src/monitor/sdb/expr.c nemu/src/monitor/sdb/watchpoint.c例如单步执行static int cmd_si(char *args) { int step 1; if (args ! NULL) { if (sscanf(args, %d, step) ! 1) { printf(Usage: si [N]\n); return 0; } } if (step 0) { printf(N must be positive\n); return 0; } cpu_exec(step); return 0; }查看寄存器void isa_reg_display() { int i; printf(Reg\tHex\t\tDec\n); printf(-----------------------------\n); for (i 0; i 32; i) { printf(%s\t0x%08x\t%u\n, regs[i], cpu.gpr[i], cpu.gpr[i]); } printf(pc\t0x%08x\t%u\n, cpu.pc, cpu.pc); }扫描内存static int cmd_x(char *args) { ... for (int i 0; i num; i) { word_t data vaddr_read(addr i * 4, 4); printf(0x%08x: 0x%08x\n, addr i * 4, data); } }这些命令正是讲义所说“基础设施”的具体体现。四、必答题三RTFM1. riscv32 有哪几种指令格式RISC-V 有六种基本指令格式类型用途R寄存器运算I立即数运算、load、jalrSstoreB分支Ului、auipcJjal你的inst.c中就有enum { TYPE_I, TYPE_U, TYPE_S, TYPE_J, TYPE_B, TYPE_R, TYPE_N, };具体译码INSTPAT(??????? ????? ????? 000 ????? 00100 11, addi, I, R(rd) src1 imm); INSTPAT(0000000 ????? ????? 000 ????? 01100 11, add, R, R(rd) src1 src2);2. LUI 指令的行为LUI的作用是rd imm[31:12] 12也就是把 20 位立即数放到寄存器高 20 位低 12 位补 0。如果 NEMU 实现了LUI应该类似R(rd) imm;其中imm是已经扩展到 32 位、低 12 位为 0 的立即数。3. mstatus 寄存器的结构你的代码中有typedef struct { word_t gpr[32]; vaddr_t pc; word_t mstatus; word_t mtvec; word_t mepc; word_t mcause; } riscv32_CPU_state;mstatus中的常见字段包括位名称含义3MIE全局中断使能7MPIE异常前中断使能12:11MPP异常前特权级17MPRV存储器特权级18SUM允许 S 模式访问 U 页面19MXR允许执行可读页面你的abstract-machine/am/src/riscv/riscv.h中有#define MSTATUS_MXR (1 19) #define MSTATUS_SUM (1 18)在trap.S中还使用了li a0, (1 17) or t1, t1, a0 csrw mstatus, t1这里设置的正是MPRV。4. selector 的说明selector是 x86 段选择子相关的概念不是 riscv32 的主要概念。如果你使用的是 riscv32手册里不会像 x86 那样介绍“selector”。这正是讲义想强调的一点不同 ISA 的手册内容和术语并不相同。所以在实验报告中应该明确写出你使用的 ISA本实验使用 riscv32。五、必答题四统计 nemu 目录代码行数1. 统计全部.c和.h行数find nemu -type f \( -name *.c -o -name *.h \) -print0 \ | xargs -0 cat \ | wc -l2. 去掉空行后的行数find nemu -type f \( -name *.c -o -name *.h \) -print0 \ | xargs -0 cat \ | grep -v ^[[:space:]]*$ \ | wc -l3. 写入 Makefile可以添加count: echo nemu .c/.h lines: find nemu -type f \( -name *.c -o -name *.h \) -print0 \ | xargs -0 cat \ | wc -l count-no-blank: echo nemu .c/.h non-blank lines: find nemu -type f \( -name *.c -o -name *.h \) -print0 \ | xargs -0 cat \ | grep -v ^[[:space:]]*$ \ | wc -l然后执行make count make count-no-blank六、和pa0分支比较写了多少代码pa0分支记录的是做 PA1 之前的状态所以可以比较git diff --stat pa0 -- nemu或者只统计增删行数git diff --numstat pa0 -- nemu \ | awk {add $1; del $2} END {print added , add, deleted , del}也可以查看短统计git diff --shortstat pa0 -- nemu在实验报告中可以写成PA1 相对 pa0 新增代码xxx 行 删除代码xxx 行 净增加xxx 行七、必答题五解释-Wall和-Werror文件nemu/scripts/build.mk其中包含CFLAGS -Wall -Werror这里的含义是-Wall开启 GCC 的大量常见警告例如未使用变量未初始化变量类型不匹配函数声明不一致不可达代码隐式函数声明。-Werror把警告当成错误。也就是说只要出现警告编译就失败。例如你的monitor.c中曾经出现error: unused variable img_size [-Werrorunused-variable]原因就是long img_size load_img();在某些配置下没有使用。如果只有-Wall它只是警告加上-Werror它就会直接阻止编译。为什么要这样1. 让问题尽早暴露 2. 避免代码质量逐渐下降 3. 防止小型警告发展成难以定位的 bug 4. 统一代码规范。八、实验报告与make submit讲义要求把实验报告命名为 学号.pdf 放到 ysyx-workbench 根目录 执行 make submit顶层 Makefile 中还要检查STUID ... STUNAME ...如果还是示例值STUID ysyx_22040000 STUNAME 张三tracer 提交信息就会使用错误身份。九、PA1 最终检查清单提交前确认1. 状态机图已经画好 2. 基础设施时间计算已经完成 3. riscv32 手册问题已经回答 4. nemu 代码行数统计命令已经记录 5. pa0 分支代码差异已经统计 6. -Wall / -Werror 已经解释 7. 实验报告命名为 学号.pdf 8. 顶层 Makefile 的 STUID/STUNAME 正确 9. make submit 成功执行十、总结PA1 最后一讲的核心可以概括为状态机 NEMU 的 exec_once/execute 就是状态转移 基础设施 SDB 的 si/info/x/p/w 能节省大量调试时间 RTFM 通过目录、搜索、筛选定位手册内容 代码统计 find xargs wc grep 编译选项 -Wall 打开警告 -Werror 把警告变成错误 实验提交 报告命名为 学号.pdf 执行 make submit最终要形成的能力是能运行程序 能观察状态 能查阅手册 能统计工程 能提交实验这才算完整完成 PA1。2:03