第2章 指令集速览

📅 2026/8/6 9:36:32
第2章 指令集速览
本章核心问题ARM 工程师如何在 3 天内上手 RISC-V 指令集哪些指令可以直接迁移哪些必须换思路阅读建议配合附录 A 的ARM→RISC-V 指令映射表使用。本章讲原理和差异附录 A 是可查的速查表。2.0 引子用 ARM 汇编写的第一个 RISC-V假设你要实现一个功能把内存地址0x2000_0000的 4 字节读出来加 1再写回。ARM 版本Cortex-M 风格LDR r0, 0x20000000 ; 地址 LDR r1, [r0] ; 读值 ADD r1, r1, #1 ; 加 1 STR r1, [r0] ; 写回RISC-V 版本li x10, 0x20000000 ; 地址伪指令展开为 luiaddi lw x11, 0(x10) ; 读值 addi x11, x11, 1 ; 加 1 sw x11, 0(x10) ; 写回看起来很像但有三处关键差异偏移写法ARM 用[r0]RISC-V 必须显式写0(x10)立即数加载ARM 用LDR r0, 立即数RISC-V 用li伪指令无 SUBI加 1 用addi减 1 也是addi x11, x11, -1如果你的第一反应是这也没多难嘛——恭喜你已经有了正确的直觉。RISC-V 指令集确实简洁难点不在指令本身而在没有的东西无 PUSH、无标志位、无条件执行。2.1 RV32I 基础指令2.1.1 什么是 RV32IRV32I 是 RISC-V 的基础整数指令集32 位所有 RISC-V 实现必须支持。地位相当于ARM 的 Cortex-M 核心指令集x86 的基础指令集特性说明名称RV32I32 位/ RV64I64 位状态v2.2 冻结永久稳定指令数约 47 条含 CSR纯用户态约 37 条哲学极简、正交、可教学2.1.2 六大类指令速览类别指令数代表指令ARM 对应算术8add/sub/addiADD/SUB/ADDS逻辑6and/or/xor/andi/ori/xoriAND/ORR/EOR移位6sll/srl/sra/slli/srli/sraiLSL/LSR/ASR加载/存储12lw/sw/lb/lbu/lh/lhu/sb/shLDR/STR系列分支6beq/bne/blt/bltu/bge/bgeuBEQ/BNE等跳转4jal/jalrB/BL/BX/BLX2.1.3 与 ARM 的核心差异无标志位这是 ARM 工程师最大的认知转变。RISC-V 没有 CPSR/NZCV 标志位。功能ARM 做法RISC-V 做法比较CMP r0, r1改 NZCVsub x0, x10, x11结果丢弃到 x0条件分支BEQ依赖上次比较beq直接比较两个寄存器进位ADC/SBC依赖 C 标志无进位标志需手动处理溢出V标志需mulh/比较判断无标志位的影响指令更简洁不需要区分ADD/ADDS分支必须直接比较beq x10, x11而非cmp; beq多精度运算64 位在 32 位机上更复杂工程视角无标志位让指令译码更简单、乱序执行更容易。这是 RISC-V 面向高性能设计的哲学——把复杂度从硬件转移到编译器。2.2 寄存器与立即数2.2.1 32 个通用寄存器寄存器ABI 名用途ARM 对应x0zero恒为零RISC-V 独有—x1ra返回地址r14 (LR)x2sp栈指针r13 (SP)x3gp全局指针—x4tp线程指针—x5-x7t0-t2临时寄存器r12 (IP)x8s0/fp保存/帧指针r4-r11x9s1保存r4-r11x10-x17a0-a7参数/返回值r0-r3x18-x27s2-s11保存r4-r11x28-x31t3-t6临时—2.2.2 x0 恒零的妙用x0 永远为 0写入被忽略。这个设计看似简单但非常巧妙# 生成 0 addi x10, x0, 0 # x10 0 # 比较结果丢弃 sub x0, x10, x11 # 比较 x10 和 x11 # 跳转无链接相当于 B jal x0, label # 返回地址写 x0丢弃等同无条件跳转2.2.3 立即数编码12 位有符号RISC-V 的立即数操作是12 位有符号-2048 到 2047。这限制了单条指令的立即数范围ARMRISC-V说明MOV r0, #1000addi x10, x0, 100012 位内直接MOV r0, #0x12345lui x10, 0x2addi x10, x10, 0x345大立即数需两条MOV r0, #0x12345678li x10, 0x12345678伪指令编译器展开⚠️重要提醒li是伪指令编译器会自动展开为luiaddi必要时加额外指令处理符号位。不要在性能关键代码里频繁使用大立即数。2.3 分支与跳转2.3.1 六种条件分支指令条件无符号版ARM 对应beq rs1, rs2, offset相等—BEQbne rs1, rs2, offset不等—BNEblt rs1, rs2, offset小于有符号bltuBLT/BLObge rs1, rs2, offset大于等于有符号bgeuBGE/BHS关键差异RISC-V 分支直接比较两个寄存器不依赖标志位。# ARM: 比较分支依赖 NZCV CMP r0, r1 BEQ label # RISC-V: 直接比较 beq x10, x11, label2.3.2 跳转指令指令用途ARM 对应jal rd, offset跳转 保存返回地址到 rdBLjalr rd, offset(rs1)间接跳转 保存返回地址BLX/BXjal x0, label无条件跳转返回地址丢弃Bjalr x0, 0(x1)函数返回ret伪指令BX lr函数调用的标准模式# 调用函数返回地址存 ra x1 jal ra, my_function # 函数返回 ret # jalr x0, 0(x1)2.3.3 PC 相对寻址RISC-V没有 PC 寄存器不像 ARM 的 r15。要访问 PC 相对数据用auipc# 加载当前 PC 附近的字符串 auipc x10, 0 # x10 PC 0PC 高位 addi x10, x10, offset # 加低位偏移2.4 加载存储寻址2.4.1 唯一的寻址模式RISC-V 的加载/存储只有一种寻址模式寄存器 立即数偏移。ARM 寻址RISC-V 处理指令数LDR r0, [r1]lw x10, 0(x11)1LDR r0, [r1, #4]lw x10, 4(x11)1LDR r0, [r1, r2]addlw2LDR r0, [r1], #4后索引lwaddi2LDR r0, [r1, #4]!前索引addilw2LDMIA多寄存器多个lwN2.4.2 数据宽度与符号扩展RISC-V 显式区分零扩展和符号扩展指令宽度扩展lb8 位符号扩展lbu8 位零扩展lh16 位符号扩展lhu16 位零扩展lw32 位—对比 ARMARM 用LDRB零扩展/LDRSB符号扩展区分概念类似但 RISC-V 命名更直观。2.4.3 压栈没有 PUSH/POP 怎么办ARM 有PUSH/POPRISC-V没有。需要手动管理栈指针# ARM: 保存 4 个寄存器 PUSH {r4-r7, lr} # RISC-V: 手动压栈 addi sp, sp, -20 # 分配 20 字节5 个寄存器 × 4 字节 sw x1, 16(sp) # 保存 ra sw x8, 12(sp) # 保存 s0 sw x9, 8(sp) # 保存 s1 sw x10, 4(sp) # 保存 a0 sw x11, 0(sp) # 保存 a1# ARM: 恢复 POP {r4-r7, pc} # RISC-V: 手动弹栈 lw x1, 16(sp) # 恢复 ra lw x8, 12(sp) # 恢复 s0 lw x9, 8(sp) # 恢复 s1 lw x10, 4(sp) # 恢复 a0 lw x11, 0(sp) # 恢复 a1 addi sp, sp, 20 # 释放栈帧 ret工程视角编译器GCC/LLVM会自动处理压栈弹栈你几乎不需要手写。但理解这个过程对调试、阅读反汇编、手写汇编至关重要。2.5 扩展指令集2.5.1 标准扩展一览RISC-V 通过基础 扩展模式灵活组合。核心扩展扩展名称功能冻结状态M乘法除法mul/div/rem✅A原子操作lr/sc/amoswap✅F单精度浮点浮点运算✅D双精度浮点双精度浮点✅C压缩指令16 位指令代码密度✅Zb位操作andn/clz/rori✅ 1.0V向量向量运算AI/DSP✅ 1.0K标量密码密码学加速✅ 1.0H虚拟化Hypervisor✅ 稳定2.5.2 M 扩展乘除指令指令功能ARM 对应mul低 32 位乘积MULmulh有符号高 32 位SMULL部分mulhu无符号高 32 位UMULLmulhsu混合符号高 32 位—div有符号除法SDIVdivu无符号除法UDIVrem有符号余数—remu无符号余数—2.5.3 C 扩展压缩指令重要C 扩展是代码密度的关键相当于 ARM 的 Thumb。典型用法# 标准指令4 字节 addi sp, sp, -16 # 压缩指令2 字节 c.addi sp, -16C 扩展的关键指令压缩指令展开说明c.addiaddi立即数加法c.liaddi rd, x0, imm加载立即数c.lw/c.swlw/sw栈内加载/存储c.j/c.jrjal/jalr跳转c.beqz/c.bnez比较 x0零分支工程视角启用 C 扩展通常可减少20-30% 代码体积对 Flash 有限的 IoT 芯片至关重要。平头哥玄铁、Telink 等嵌入式核都支持 C 扩展。2.5.4 Zb 位操作扩展新指令功能性能提升andn/orn/xnor取反操作~2xclz/ctz/cpop位计数~5xrol/ror循环移位~2xmin/max极值~2xsext.b/sext.h符号扩展省 1 条⚠️重要提醒Zb 扩展虽好但不是所有核都支持。使用前必须确认目标核的扩展集否则代码无法编译/运行。2.6 汇编实战双平台对照2.6.1 冒泡排序经典教学示例ARM 版本 冒泡排序 r0 数组基址, r1 长度 bubble_sort: PUSH {r4-r7, lr} MOV r4, r0 数组基址 MOV r5, r1 长度 SUB r6, r5, #1 外层循环次数 n-1 outer: MOV r7, r4 内层起点 MOV r2, #0 交换标志 inner: LDR r3, [r7] arr[i] LDR r12, [r7, #4] arr[i1] CMP r3, r12 比较 BLE no_swap 若有序跳过 STR r12, [r7] 交换 STR r3, [r7, #4] MOV r2, #1 有交换 no_swap: ADD r7, r7, #4 下一元素 CMP r7, r6, LSL #2 是否到末尾 BNE inner CMP r2, #0 无交换则提前结束 BEQ done SUBS r6, r6, #1 BNE outer done: POP {r4-r7, pc}RISC-V 版本# 冒泡排序 x10 数组基址, x11 长度 bubble_sort: addi sp, sp, -24 sw x1, 20(sp) # 保存 ra sw x8, 16(sp) # 保存 s0 sw x9, 12(sp) # 保存 s1 sw x10, 8(sp) # 保存 a0 sw x11, 4(sp) # 保存 a1 addi x8, x10, 0 # s0 数组基址 addi x9, x11, 0 # s1 长度 addi x28, x9, -1 # t3 n-1外层次数 outer: addi x29, x8, 0 # t4 内层起点 li x30, 0 # t5 交换标志 inner: lw x5, 0(x29) # arr[i] lw x6, 4(x29) # arr[i1] blt x6, x5, swap # arr[i1] arr[i] 则交换 j no_swap swap: sw x6, 0(x29) # 交换 sw x5, 4(x29) li x30, 1 # 有交换 no_swap: addi x29, x29, 4 # 下一元素 bltu x29, x28, inner # 未到末尾继续用 bltu 与基址偏移比较 beqz x30, done # 无交换提前结束 addi x28, x28, -4 # 外层递减 bgez x28, outer done: lw x1, 20(sp) # 恢复 ra lw x8, 16(sp) # 恢复 s0 lw x9, 12(sp) # 恢复 s1 lw x10, 8(sp) # 恢复 a0 lw x11, 4(sp) # 恢复 a1 addi sp, sp, 24 ret双平台差异点总结差异ARMRISC-V压栈PUSH {r4-r7, lr}一条手动 5 条比较分支CMPBLE两条blt一条寄存器名r0-r15x0-x31 ABI 名循环控制SUBS带标志位addibgez无标志位返回POP {..., pc}ret2.6.2 双平台 FIFO 环形缓冲区C 代码 内联汇编核心逻辑C// 环形缓冲区读返回 0空1成功intfifo_pop(volatileuint32_t*buf,uint32_t*head,uint32_t*tail,uint32_tsize,uint32_t*out){if(*head*tail)return0;// 空*outbuf[*tail];*tail(*tail1)%size;return1;}ARM 内联汇编关键路径staticinlineuint32_tread_fifo_arm(uint32_t*buf,uint32_ttail){uint32_tval;__asmvolatile(ldr %0, [%1, %2]:r(val):r(buf),r(tail));returnval;}RISC-V 内联汇编staticinlineuint32_tread_fifo_riscv(uint32_t*buf,uint32_ttail){uint32_tval;__asmvolatile(lw %0, 0(%1):r(val):r(buftail));returnval;}工程视角现代编译器对 RISC-V 支持已经非常成熟GCC/LLVM 都是 upstream。99% 的代码用 C 写内联汇编只在最热路径使用。手写汇编的必要性远低于十年前。2.7 本章小结2.7.1 六条要点RV32I 只有 37 条用户态指令——比 ARM 精简得多3 天可上手无标志位是最核心差异——比较直接进分支不依赖 NZCV无 PUSH/POP——手动管理栈编译器自动处理寻址模式唯一——只有寄存器 偏移多寄存器/前后索引需组合立即数 12 位有符号——大立即数用luiaddi或li伪指令扩展通过 Profile 组合——C 扩展省 20-30% 代码Zb 提升位操作性能2.7.2 常见坑清单坑现象解决subi不存在编译报错用addi负立即数忘记 x0 恒零写 x0 想清零addi x10, x0, 0偏移必须写 0lw x10, (x11)报错lw x10, 0(x11)大立即数溢出编译错误用li伪指令忘记保存 ra函数返回崩溃调用前jal会覆盖 raZb 指令不支持编译/运行错误确认目标核扩展集2.7.3 下章预告第 3 章进入寄存器与调用约定为什么 ARM 程序搬到 RISC-V 直接崩溃AAPCS 和 RISC-V ABI 到底差在哪x0 恒零、无 PC 寄存器、8 个参数寄存器——这些设计如何影响你的 C 代码和汇编参考引用规范引用RISC-V Unprivileged Spec v2.220191213— RV32I 基础指令RISC-V 标准扩展规范M/A/F/D/CRISC-V Zb 位操作扩展规范 1.0RISC-V V 向量扩展规范 1.0参考资料《RISC-V 架构与嵌入式开发快速入门》胡振波RISC-V Reader中文版平头哥玄铁系列汇编手册⚠️重要提醒不同核的扩展支持差异大如 Zb 非全支持使用扩展指令前必须查阅目标核的 ISA 手册。