想象你走进一栋办公楼。普通员工手边都有一排小抽屉,用来随手放便签、电话号码和半成品文件——这就是「快捷工位区」。而在楼顶的机房,还有一间门上挂着三道锁的「总控室」,物业在那里管门禁、查火警、记电表,整栋楼谁也不能随便进出。RISC-V 处理器就是这栋楼:32 个通用寄存器,是每个执行单元手边的抽屉;CSR(控制与状态寄存器),则是楼顶总控室里那一排仪表和旋钮。程序员写的每一条汇编指令,本质上都是在调度「从 x5 抽屉取纸、算完放回 x7 抽屉」的日常流程;一旦发生中断或异常——好比火警突然响了——总控室会瞬间接管整栋楼。学会 RISC-V 汇编,就从读懂这栋楼的「抽屉编号」和「总控仪表」开始。
对 AI 从业者而言,寄存器与 CSR 是阅读 RISC-V 汇编、操作系统启动代码和性能分析工具的底层地图,无论做芯片验证、编译器后端还是嵌入式开发都绕不开这套「办公室地图」。
目录
可视化演示
展示32个通用寄存器ABI命名、CSR分类与特权级切换,切换模式查看RISC-V编程模型的不同层面。
本节配有交互动效(进入视口后自动加载,约 3KB)。
通用寄存器:32个寄存器各有ABI约定 x0恒零,x1为返回地址ra a0-a7传递参数,s0-s11保存跨调用 x0=0, x1=ra, x2=sp, x10-x17=a0-a7 ABI命名约定简化汇编编程
寄存器基础:32个抽屉的由来
办公楼的抽屉为什么刚好是 32 个?不是拍脑袋定的。物业在设计图纸时规定了:每张抽屉编号牌上只允许贴 5 个「灯位」,每个灯位要么亮要么灭,就足够把楼里所有抽屉唯一编号。亮灭两种状态正是数字电路最擅长的表示方式,于是抽屉数量被自然地定成了 32。
这 5 个灯位对应到处理器里,就是指令编码中的 5 个二进制位。RISC-V 的每一条指令都预留了若干 5 位字段(称为 rd、rs1、rs2),分别用来指明「结果放哪个抽屉」「从哪个抽屉取数」。5 位二进制能表示的编号范围是 00000 到 11111,正好覆盖 x0 到 x31 这 32 个通用寄存器。
再来看每个抽屉的「大小」。抽屉能装多少东西,由 XLEN 决定:RV32 的抽屉宽 32 位,RV64 的抽屉宽 64 位。位宽就像抽屉的深度,越深能放的数字越大。至此,「32 个抽屉、每个抽屉多深」的楼面布局已经清楚了——这是读懂后面所有汇编指令的地基。
- 底数 2处理器内部只用二进制计数,每个二进制位只有 0 和 1 两种状态,好比抽屉编号牌上的灯只有亮和灭。
- 指数 5RISC-V 指令编码里为寄存器编号预留了 5 个二进制位,分别对应指令的 rd/rs1/rs2 字段。
- 可数的抽屉数5 个位从全 0 到全 1 能组合出 32 种编号,所以 RISC-V 恰好有 32 个通用寄存器,从 x0 到 x31。
分工与调用:每个抽屉都有岗位
抽屉虽然长得一样,但物业在每张抽屉上贴了「岗位标签」,规定它主要用来干什么。这不是强迫你只能这么用,而是整个办公楼约定俗成的规矩:大家遵守同一套分工,文件流转才不会乱。32 个抽屉大致分成五类角色。
x0 是一个「焊死在地板上、永远空着」的抽屉,任何写入都会被丢弃,读它永远得到 0,是硬件电路专门做出来的「零号常量源」。x1 叫 ra(返回地址),相当于贴在抽屉上的「回公司路线纸条」——打电话(调用函数)前先把回来的路线存进去。x2 叫 sp(栈指针),记录当前办公楼层(栈顶)在哪里,函数嵌套调用时全靠它记账。x3 叫 gp(全局指针),x4 叫 tp(线程指针),分别贴着「公司公告栏」和「热点通知」的标签,软件层面各有用途。
剩下的抽屉按职责分成三队:a0~a7 是「公文传递格」,用来递送函数调用的实参和带回返回值;t0~t6 是「草稿格」,临时算点东西随手就用;s0~s11 是「长期档案格」,被叫去开会(调函数)之前,必须把自己要长期保存的东西放进这些格子,开完会再原样取回。数一遍,刚好:
有了这五类岗位,汇编代码就像一张张调度单:命令员工「把文件放进 a0 格、拨号前把路线存进 ra 格、临时算账用 t0 格」。也正是这套分工,让不同团队写的函数能无缝拼接——前提是每个人都遵守抽屉的岗位约定。
- x0 的 1 个x0 是焊死为 0 的抽屉,写入被丢弃、读取恒为 0,数抽屉总数时仍占一个名额。
- 特殊职能的 4 个x1(ra)、x2(sp)、x3(gp)、x4(tp)各有专属岗位,分别管返回地址、栈顶、全局数据和线程数据。
- 临时草稿的 7 个x5~x7(t0~t2)和 x28~x31(t3~t6)共 7 个抽屉,属于用完不用负责的草稿区。
- 长期保存的 12 个x8/fp(s0)、x9(s1)、x18~x27(s2~s11)共 12 个抽屉,被调函数必须「物归原主」。
- 参数传递的 8 个x10~x17(a0~a7)共 8 个抽屉,用来递送函数参数和带回返回值。
ABI命名:贴在抽屉上的标签
走进办公楼,你会发现每张抽屉贴着两个编号:门框上刻着一个「硬件号」x5、x6、x7,抽屉正面还贴着一张「花名标签」t0、t1、t2。它们指的是同一个抽屉,只是叫法不同。这套花名就是 ABI(Application Binary Interface,应用二进制接口)命名约定,它是软件世界为了方便人类阅读而发明的第二套编号系统。
为什么需要两套名字?因为机器码里只认编号,但人类看「x5 和 x10 相加,结果放 x7」远不如看「t0 加 a0,结果放 t2」直观。更重要的是,ABI 名字承载了岗位职责:看到 a 就知道是参数、看到 s 就知道要保护、看到 t 就知道可以随便用。汇编器(比如 GNU as)和编译器都认识这些花名,你写 add t0, a0, a1,它自动翻译成对 x5、x10、x11 的操作。
几个高频映射需要记牢:
临时寄存器 t 和保存寄存器 s 的分工,是 ABI 里最关键的「责任条款」:t 开头的抽屉,谁用谁负责,函数调用后值可能被改掉;s 开头的抽屉,被调函数有义务保持原样。写编译器后端或手写汇编时,遵守这套责任划分,才能保证函数之间不互相踩脚。
- t0 对 x5ABI 给硬件编号起别名,t0 就是 x5,属于临时草稿区的第一个抽屉。
- s0/fp 对 x8x8 既是长期保存的 s0,也可以当作帧指针 fp 使用,一个抽屉贴两张标签。
- a0 对 x10x10 是第 1 个参数寄存器,函数调用时第一个实参从这格递进。
CSR档案:楼顶的总控室
办公楼的一楼是员工办公区,摆满了 32 个通用抽屉;而楼顶机房还有一排完全不同的「控制仪表」——CSR。它们不占用任何 x 编号,存在于独立的地址空间,就像总控室和员工区是两套门牌系统。普通员工平时根本见不到这些仪表,只有总控室的管理员才允许拧动它们。
CSR 的名字透露着它的职能:m-status 类管「工作模式」、m-isa 类管「这栋楼支持哪些楼层功能」、m-epc/m-cauuse 类管「异常和中断的记录」。处理器用 12 位地址来区分这些仪表,所以理论上总控室最多能安装 4096 个 CSR:
想操作总控室的仪表,普通抽屉指令不够用,需要用专门的 CSR 指令:csrrw rd, csr, rs1 是把抽屉里的值写进仪表、同时把仪表旧值读回抽屉;csrrs 则是对仪表做「按位或」式的置位,常用于打开某个开关。读总控、写总控、改开关,都有专属的「钥匙」,这也引出了下一节的门禁问题。
- 底数 2CSR 的地址同样按二进制编址,每个地址位只有 0 或 1 两种取值。
- 指数 12RISC-V 为 CSR 地址预留了 12 位,访问 CSR 的指令会把这 12 位地址直接写在指令中。
- 可容纳的仪表数12 位地址从 0x000 到 0xFFF 共 4096 个位置,按用途分成用户、监督、机器三大类区间。
特权模式:三种门禁卡
总控室不是谁都能进的。办公楼发放三种门禁卡,权限从低到高排列:普通员工的「用户卡」、部门经理的「监督卡」、物业的「机器卡」。RISC-V 把它们叫做用户模式(U)、监督模式(S)和机器模式(M)。处理器当前拿着哪张卡,决定它能碰哪些 CSR、能执行哪些特权指令。
这三种模式在 mstatus CSR 里用两个二进制位编码,就像门禁卡上印着的二位等级码。芯片上电复位时默认拿到最高权限的机器模式卡,让启动代码先把整栋楼的应急预案布置好;之后操作系统内核运行在监督模式,普通应用程序则被发配到用户模式,权限最低。
门禁卡是可以切换的。发生中断时,处理器从低模式「升卡」进入更高级模式处理事务;处理完毕,靠 mstatus 里的 MPP 字段记着「刚才持有哪张卡」,再用一条 mret 指令把卡降回原等级。权限隔离的意义在于:普通程序即使写 bug 乱改抽屉,也碰不到总控室的开关,整栋楼不会因为一个员工的失误而瘫痪。
- 用户模式 U编码 00,普通员工的权限,只能执行常规指令,碰不到总控室任何旋钮。
- 监督模式 S编码 01,部门经理的权限,通常承载操作系统内核,可访问监督类 CSR。
- 机器模式 M编码 11,物业管理员的总权限,上电默认进入,所有 CSR 都可读写。
中断异常:火警应急流程
想象办公楼突然响起火警:不管员工手头在算什么,都得立刻停下,把「刚才自己在几楼几号工位」记在总控室的登记本上,然后跑去指定集合点听候物业指挥。处理器里的中断和异常(统称 trap),就是这套火警流程。
处理器被拆分为三步:第一步,把当前 PC(正在执行的指令地址)存进 mepc,相当于登记「你来处在哪」,这样火警解除后能原路返回;第二步,把事故类型写进 mcause,相当于记录「是火灾、停电还是有人误按警报」;第三步,把 PC 改成 mtvec 指向的地址——mtvec 就是应急预案手册的存放位置,那里写着中断服务程序(handler)的第一条指令。
处理完火警,服务程序最后执行 mret(机器模式返回)指令:处理器根据 mstatus.MPP 恢复原来的特权卡,再从 mepc 取回之前的地址继续干活。这整套「登记→记录→跳转→返回」的流程,是操作系统管理设备、处理非法访问、管理任务调度的底层机制,读内核代码时几乎到处都能撞见它。
- 现场登记硬件把当前 PC 值保存进 mepc,相当于火警登记本上写下「你刚才在几楼几号工位」。
- 记录原因mcause 记录这次 trap 的类型编号,是中断还是异常、具体是哪一种,服务程序靠它分类处理。
- 跳转预案入口处理器把 PC 改为 mtvec 指向的地址,从那里开始执行中断服务程序。
性能计数:物业的统计报表
物业在总控室装了两种电表:一种是「总走字」表 cycle,累计整栋楼从通电到现在跳过的时钟周期数;另一种是「办结业务单」计数器 instret,记录处理器完成(退休)的指令总数。它们都是 CSR,程序通过读取它们来了解自己到底花了多少时间。
这两块表还有个配套的「观看权限开关」叫 mcounteren。默认情况下,低权限模式读不到某些计数 CSR;总控室管理员(机器模式)可以通过配置 mcounteren,决定是否开放给部门经理或普通员工查看。想跑性能剖析,就得先在启动代码里把开关打开。
把两个表读数相除,就得到每个性能分析几乎必看的核心指标——CPI(Cycles Per Instruction,每条指令的平均时钟周期数):
在 AI 推理加速芯片上做算子调优时,工程师最常干的事就是:读两次 cycle 相减得到区间耗时,再读 instret 看这段代码执行了多少条指令。如果 CPI 明显高于理论值,说明流水线在等待访存或依赖,接下来就该查缓存、调循环展开了。电表不会说谎,数据会告诉你优化方向。
- 总时钟 cyclecycle 统计开机以来累计的时钟周期数,相当于大楼总电表的走字数。
- 完成指令数 instretinstret 统计已退休(完整执行完)的指令条数,相当于办结的业务单数量。
- 比值 CPI两者相除得到平均每条指令消耗的时钟周期数,是衡量处理器效率的核心指标。
汇编实操:调度单上手
把前面的知识合并成一张「调度单」。写 RISC-V 汇编,本质上就是给处理器下命令:从哪个抽屉取数、做什么运算、结果放回哪个抽屉,以及要不要去仓库(内存)搬货。最常用的两组指令是数据搬运和算术运算。
lw rd, offset(rs1) 意为「从 rs1 抽屉记录的地址(仓库货架号)出发,偏移 offset 个位置,把货搬到 rd 抽屉」;sw 则相反,把抽屉里的数存回仓库。这就是「仓库与抽屉」之间的物流指令。而 addi rd, rs1, imm 是一条典型的立即数运算指令:imm 是直接写在指令里的常数,相当于调度单上直接写「加 4」,而不是「去 4 号抽屉取一个 4 出来」:
掌握这几张「调度单」后,就可以读真实的 RISC-V 汇编了:li x5, 3 是把 3 直接装进抽屉,add x7, x5, x6 是两抽屉相加,lw x5, 8(x6) 是从仓库取货。再把上一节说的 csrr x10, mcause 加进来——从总控室仪表读个值放进普通抽屉——恭喜你,已经能看懂启动代码和中断服务程序的核心骨架了。办公楼的日常,无非就是抽屉、仓库、总控室这三者之间的不停流转。
- 读源寄存器先从 rs1 指定的抽屉取出操作数,这是运算的数据来源。
- 加上立即数imm 是编码在指令里的常数,不需要额外的抽屉来存放它。
- 写入目标寄存器计算结果写进 rd 指定的抽屉,一次调度完成。
AI 应用场景
AI芯片启动固件开发(Bring-up)
阶段:AI芯片软件栈搭建(板级启动与 BSP 移植)
解决的问题:新流片的 RISC-V 内核上电后,第一个要跑的就是启动汇编。传统方式下,工程师面对一屋子「不知名」的寄存器,不知道该先配谁;懂 CSR 之后,启动流程就变成了「先把栈顶设好、再把中断入口挂上、最后开门禁」的三步例行公事。
具体案例:某 AI 加速 SoC 的启动汇编中,先执行 li sp, 0x80000000 把栈指针抽屉设定到片上 SRAM 顶部,再 la t0, trap_handler; csrw mtvec, t0 把火警预案入口注册进总控室,最后 csrsi mstatus, 0x8 打开全局中断开关,CPU 才安心开始跑主程序。
RTOS 任务切换与中断上下文保存
阶段:系统软件适配(实时操作系统内核移植)
解决的问题:AI 推理设备上常跑轻量级 RTOS,任务切换时要把当前任务的「抽屉现场」完整保存下来。不懂 ABI 的保存约定,程序员可能用临时寄存器 t 去保存现场,结果被下一个函数随手改掉,任务恢复时数据错乱。
具体案例:RISC-V 核上的 RTOS 调度器用汇编实现上下文切换:先把 ra(返回路线)、s0~s11(长期档案抽屉)逐个压栈,再切到新任务的栈并逐一弹回抽屉;中断处理入口通过读 mcause 判断是时钟中断还是外部中断,从而决定是否触发调度。
AI推理算子性能剖析与优化
阶段:AI推理性能调优(算子级优化与 profiling)
解决的问题:卷积算子跑得慢,但慢在取数还是慢在计算?传统方式靠猜。用性能计数 CSR 的 cycle 和 instret 算 CPI,能立刻定位是流水线等待访存,还是计算单元本身瓶颈。
具体案例:AI 编译器团队在 RISC-V 核上剖析矩阵乘算子:在函数入口 csrr t0, cycle、出口再读一次,相减得到这段代码的时钟周期数;同时读 instret 得指令条数,算出 CPI=2.3,高于理论值 1.0,于是定位到 cache miss 过多,改用分块访存后 CPI 降到 1.1,算子提速约 40%。
Python 演示
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""从 32 位机器码中拆出 RISC-V 寄存器字段,并翻译成 ABI 名字。""" ABI = ["zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0/fp", "s1", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"] def decode_rtype(inst): """R 型指令: add/sub/and 等, 含 rs1/rs2/rd 三个寄存器字段。""" rd = (inst >> 7) & 0x1F rs1 = (inst >> 15) & 0x1F rs2 = (inst >> 20) & 0x1F return rd, rs1, rs2 def decode_itype(inst): """I 型指令: addi/lw 等, 含 rs1/rd 和 12 位立即数。""" rd = (inst >> 7) & 0x1F rs1 = (inst >> 15) & 0x1F imm = (inst >> 20) & 0xFFF if imm & 0x800: imm -= 0x1000 return rd, rs1, imm def show(inst, hint="?"): opcode = inst & 0x7F if opcode == 0x33: rd, rs1, rs2 = decode_rtype(inst) print("%s: add x%d(%s), x%d(%s), x%d(%s)" % (hint, rd, ABI[rd], rs1, ABI[rs1], rs2, ABI[rs2])) elif opcode == 0x03: rd, rs1, imm = decode_itype(inst) print("%s: lw x%d(%s), %d(x%d(%s))" % (hint, rd, ABI[rd], imm, rs1, ABI[rs1])) else: print("%s: 未知 opcode 0x%02X" % (hint, opcode)) def main(): show(0x00B503B3, "add x7, x10, x11") show(0x00832283, "lw x5, 8(x6)") show(0x0000007B, "非法指令") if __name__ == "__main__": main()
t0 其实就是 x5、为什么一个 5 位字段能索引到 32 个抽屉。ABI = [...]一整张「抽屉标签对照表」,让硬件编号 x0~x31 与软件名字一一对应,后面打印全靠它。rd = (inst >> 7) & 0x1F右移 7 位再屏蔽低 5 位,从 32 位机器码中取出目标寄存器编号,5 位编码正是 32 个抽屉的索引。rs1 = (inst >> 15) & 0x1F取出第一个源寄存器字段,对应「从哪个抽屉取数」。imm = (inst >> 20) & 0xFFF取出 I 型指令的 12 位立即数,它是编码在指令里的「直接写好的常数」。if imm & 0x800: imm -= 0x1000判断立即数最高位,做符号扩展,把负数立即数恢复成真实值。if opcode == 0x33:0x33 是 R 型算术指令的特征码,add/sub 等运算都从这里分流处理。
常见误区
误区我以为 x0 和别的寄存器一样,只是上电时值恰好为 0,之后也能把数据写进去、再读出来。
正解x0 是硬编码接地的「死抽屉」:任何写入都被硬件直接丢弃,读它永远得到 0。它不是「初始值为 0 的变量」,而是「一个焊死在地上、永远写不进的空抽屉」。正因为这一点,addi x0, x0, 0 就是一条什么都不干的无操作指令,beq x0, x0, label 则是一个无条件跳转。
为何常见:很多教程只写「x0 恒为零」,初学者很自然地把「恒为零」类比成普通变量「初值为零」,忽略了写入被丢弃这层硬件语义。
误区我以为 t0 就是「第 0 个临时寄存器」,对应的硬件编号是 x0;a0 对应的自然是 x1。
正解ABI 名和硬件编号是两套独立的编号体系。t0 的真实编号是 x5,a0 的真实编号是 x10,而真正的 x0 叫作 zero。写汇编时混用问题不大(汇编器会换算),但读 objdump 反汇编输出、或者写链接脚本时,如果脑子里没有这张对照表,很容易把操作数看错。
为何常见:计算机里处处从 0 开始计数,直觉会引导人做「t0↔x0、a0↔x1」的错位联想,而 ABI 表恰恰把参数和临时寄存器安排在中间位置。
误区CSR 不也是寄存器吗?那我写的用户程序应该也能直接读写 mstatus、mepc 这些总控仪表吧。
正解CSR 是特权资源,受模式门禁严格保护。用户模式(U)下访问机器模式的 CSR 会直接触发非法指令异常;即使监督模式想读某些性能计数 CSR,也需要 mcounteren 先放行。门禁不是软件约定,而是硬件电路的强制检查。
为何常见:通用寄存器和 CSR 都叫「寄存器」,名字相似让初学者默认它们权限相同,忘了 CSR 的完整名字里还有「控制」两个字。
误区处理完中断,像调用函数一样 `jr ra` 跳回原来的地方不就行了?反正机器码里也是跳转。
正解trap 的返回必须使用 mret(或 sret)。mret 不只是把 PC 恢复到 mepc,还会根据 mstatus.MPP 把特权模式切回原来的等级、重新打开被屏蔽的中断开关。用普通跳转返回,处理器会停留在错误特权级上,后续程序一访问受保护资源就崩溃。
为何常见:学函数调用时养成的「用 ra 返回」习惯太深刻,忽略了 trap 与函数调用是两套机制:函数调用没有切换特权模式,trap 却要跨权限处理。
误区程序跳转就得改 PC,那 PC 应该也算 RISC-V 的一个「寄存器」吧,可能是 x32。
正解PC(程序计数器)是独立的专用逻辑,不属于 32 个通用寄存器。它不在寄存器文件里、没有 ABI 名字、不能被 lw/add 等指令直接寻址,只能通过 jal/beq/call 等控制流指令间接改变。32 个抽屉是「数据工位」,PC 是「当前读到哪一页书的书签」。
为何常见:x86 体系里 IP/EIP 常和通用寄存器一起出现在文档中,把这种思维惯性搬到 RISC-V 上就容易多算一个寄存器,忘了 RISC-V 刻意把 PC 独立出来以简化硬件。
面试题
Q1. x0 寄存器在 RISC-V 中有何特殊之处?
- A.永远读回 0 且写入无效← 正确答案
- B.可保存任意临时计算结果
- C.用于存储当前程序计数器
- D.只能在机器模式下访问
答案:A。x0 是硬编码为 0 的「死抽屉」,写入被丢弃、读取恒为 0,是 RISC-V 用来实现清零、比较和无操作指令的常备工具。
Q2. RISC-V 的 ABI 名 t0 对应哪个硬件寄存器?
- A.x5 号通用寄存器← 正确答案
- B.x6 号通用寄存器
- C.x7 号通用寄存器
- D.x8 号通用寄存器
答案:A。ABI 名与硬件编号是两套体系:t0 对应 x5,t1 对应 x6,t2 对应 x7。x8 的 ABI 名是 s0/fp,别和 t 系列弄混。
Q3. 函数调用返回时,处理器依靠哪个寄存器找回调用点?
- A.ra(x1)保存返回地址← 正确答案
- B.sp(x2)指向当前栈顶
- C.gp(x3)指向全局数据区
- D.tp(x4)指向线程局部数据
答案:A。ra(return address)的职责就是保存「回公司路线」。函数调用指令 jal 会自动把下一条指令地址写进 ra,返回时再用 ret 跳回。
Q4. 下列哪些属于 RISC-V 标准的特权模式?
- A.用户模式 U(User)← 正确答案
- B.监督模式 S(Supervisor)← 正确答案
- C.机器模式 M(Machine)← 正确答案
- D.调试模式 D(Debug)
答案:ABC。RISC-V 特权架构规定 U/S/M 三种模式,D 选项的调试模式不是标准特权等级。门禁卡只有三张,上电默认拿最高级的 M 卡。
Q5. 中断或异常发生后,处理器应跳转到哪个 CSR 指向的服务入口?
- A.mtvec 指向的应急入口← 正确答案
- B.mstatus 配置的工作模式
- C.mepc 记录的返回现场
- D.mcause 记录的异常原因
答案:A。mtvec 好比火警预案手册的存放位置,trap 发生后处理器把 PC 改成 mtvec 的值去执行中断服务程序。
Q6. 进入 trap 时,被打断的 PC 值被硬件保存在哪里?
- A.mepc 中便于处理完返回← 正确答案
- B.mtvec 中用作跳转入口
- C.mcause 中记录异常种类
- D.mscratch 中作临时暂存
答案:A。mepc 就是「火警登记本」,专门记录来处。处理完执行 mret 时,处理器从 mepc 取回地址继续干活;mcause 则只负责记原因。
Q7. 32 个通用寄存器最少需要几位二进制索引?
- A.4 位索引最多编出 16 个
- B.5 位索引最多编出 32 个← 正确答案
- C.6 位索引最多编出 64 个
- D.7 位索引最多编出 128 个
答案:B。5 个二进制位从 00000 到 11111 正好 32 种组合,所以 RISC-V 用 5 位字段(rd/rs1/rs2)索引寄存器,既够用又不浪费。
Q8. 关于 mstatus.MPP 字段的说法,正确的是?
- A.记录进入 trap 前的特权模式← 正确答案
- B.mret 返回时会用它恢复等级← 正确答案
- C.用户态程序可直接改写它
- D.它记录异常发生的具体原因
答案:AB。MPP 相当于门禁卡上印的「原持有等级」,trap 进入时硬件写入、mret 返回时用于恢复。它位于机器模式 CSR 中,用户态根本无权访问,异常原因则记录在 mcause 中。
Q9. cycle 与 instret 两个性能计数 CSR 的比值表示什么?
- A.每条指令平均的时钟周期数← 正确答案
- B.每秒钟执行的指令条数
- C.缓存缺失发生次数的比例
- D.总中断次数除以总指令数
答案:A。CPI = cycle / instret,就像「总步数除以办成事数」得到平均每件事花几步。它是判断处理器流水线是否高效的核心指标。
Q10. 关于 callee-saved 寄存器的说法,正确的是?
- A.被调函数需保持 s0-s11 原值← 正确答案
- B.调用者不必预先保存 s 系列← 正确答案
- C.t0-t6 也属于 callee-saved
- D.t0-t6 由调用者负责保存← 正确答案
答案:ABD。s0~s11 是「长期档案格」,被调函数有义务原样归还;t0~t6 是「草稿纸」,谁用谁负责,属于 caller-saved。调用者调用前不用保护 s,但不保护 t 就可能被改掉。