RISC-V 和 Cortex-M 都属于精简指令计算机(RISC),使用 Load/Store 架构,在执行计算之前,需要将数据 Load 到寄存器,计算完成后再把寄存器的数据 Store 到内存,不能像 8051 或 x86 那样直接对内存中的数据进行运算。指令长度为 32 位或 16 位,RISC-V 标准指令都是 32 位的,RVC 扩展使用 16 位指令,Cortex-M0/M0+ 主要是 16 位的 Thumb 1 指令,有少量 32 位的 Thumb 2 指令,Cortex-M3/M4/M7 包含了大量 32 位的 Thumb 2 指令。默认情况下,都是小端在先。
这里对比的是 32 位的 RISC-V( RV32) 和 Cortex-M,都是 32 位才有可比性。
Cortex-M 有条件码,要进行条件跳转前,首先执行比较指令设置条件码,然后分支指令根据条件码进行跳转。RISC-V 采用比较跳转指令,即在一条指令中实现比较和跳转这两项工作,没有条件码。
RISC-V 默认为有符号数,如果要作为无符号数处理,那么要加后缀 U;Cortex-M 默认为无符号数,如果要作为有符号处理,那么要加后缀 S。例如,加载小于32位的数,RISC-V 默认执行符号扩展,无符号扩展要加U后缀,LB 指令加载一个字节数据并进行符号扩展,LBU 指令加载一个字节数据并进行零扩展;Cortex-M 默认执行零扩展,有符号扩展要加S后缀,LDRB 指令加载一个字节数据并进行零扩展,LDRSB 指令加载一个字节数据并进行符号扩展。
RISC-V 的立即数基本上都是有符号数,将扩展成32位的有符号数后参与运算,CSR 指令的立即数是零扩展的;Cortex-M 的立即数基本上是无符号数,零扩展成32位数(除了B/BL指令执行符号扩展)。
C 语言中的数,默认也是有符号数,比如 int 就是有符号整数,如果要表示无符号数,一定要加上 unsigned ,常量不加后缀是有符号数,加后缀U表示是无符号数。RISC-V 和 C 语言是一致的。
RISC-V 的立即数基本上都是 12 位的,包括运算指令、内存访问指令、跳转指令等均是 12 位立即数( LUI 、AUIPC、JAL 使用 20 位立即数,CSR 使用 5 位立即数)。Cortex-M0 的立即数有 3位、5位、7位、8位、11位、24位。
RISC-V 用指令组合常量,一般是一条 LUI 指令加上一条 ADDI 指令,LUI 指令加载高20位立即数,ADDI 累加低12位立即数,刚好是32位立即数;Cortex-M 从常量池加载常量,编译器将常量存储在指令的下方,然后使用 PC 间接寻址加载该常量。理论上讲,RISC-V 也可以将变量存储在常量池,然后通过 PC 间接寻址加载,但是这将多占用4个字节空间,因为 RISC-V 的 PC 寄存器不是作为通用寄存器存在的,首先要使用 AUIPC 指令获取当前 PC 的值。
RISC-V 有 32 个寄存器,x0 为硬绑定为零的寄存器,此外还有一个 PC 寄存器,总共有 33 个寄存器。Cortex-M 有 16 个寄存器,其中 R15 为 PC 寄存器。
RISC-V 的 PC 寄存器不是作为通用寄存器存在的;Cortex-M 的 PC 寄存器即通用寄存器 R15。RISC-V 要获取 PC 寄存器的值,首先要执行指令 AUIPC a0, 0
。
LR 寄存器保存函数调用的返回地址。RISC-V 可以使用 x1 ~ x31 中的任意一个作为 LR 寄存器使用,但是函数调用规约约定使用 x1 作为 LR 寄存器,这是软件层面的规约,当然,如果使用 RVC 扩展,那么 RVC 指令是使用 x1 作为 LR 寄存器的,这就硬件层面的规定了。Cortex-M 的 LR 寄存器即通用寄存器 R14。
SP 寄存器保存当前堆栈指针。RISC-V 可以使用 x1 ~ x31 中的任意一个作为 SP 寄存器使用,但是函数调用规约约定使用 x2 作为 SP 寄存器,这是软件层面的规约,当然,如果使用 RVC 扩展,那么 RVC 指令是使用 x2 作为 SP 寄存器的,这就硬件层面的规定了。Cortex-M 的 SP 寄存器即通用寄存器 R13。
Cortex-M0指令除了 MOV、ADD、CMP 之外,其他指令中的寄存器均为低寄存器,即R0~R7。
RISC-V 使用 CSR 访问系统特殊功能寄存器,CSR 是原子的,即一条指令完成读修改写操作,还有一些特殊功能寄存器是内存映射的,比如 mtime 寄存器。Cortex-M 使用 MRS、MSR 访问特殊功能寄存器,多数特殊功能寄存器是内存映射的。
RISC-V 没有多寄存器加载/存储指令。Cortex-M 有多寄存器加载/存储指令。RISC-V 的 Load/Store 只有一种格式:lw rd, imm12(rs1)
(偏移量是有符号数,不移位,可以支持不对齐的访问)。Cortex-M0 的 Load/Store 有两种格式: LDR
和 LDR
(偏移量是无符号数,且根据 Load/Store 的数据大小进行移位,Cortex-M0 总是对齐访问,不对齐的访问将导致 Hardfault)。
RISC-V 没有多寄存器的 Load/Store 指令,所以压栈/出栈一个寄存器就是一条指令,而Cortex-M 有多寄存器的 Push/Pop 指令,一般情况下进入函数一条 Push,退出函数一条Pop,虽然在执行速度上没有什么区别,但是指令空间上 RISC-V 比 Cortex-M 占用更多,而 RISC-V 的设计则是简化了执行单元的设计。多寄存器的 Load/Store 碰上中断的情况分析对比,RISC-V 不支持 LDM/STM 大大简化了处理器设计。
Cortex-M 提供了带进位的加法指令 ADDC 来实现 64 位的加法;RISC-V 没有提供 ADDC,RISC-V 使用 SLT 指令处理 64 位的加法运算。Cortex-M 执行 64 位加法只需要 ADDS 指令加 ADDC 指令,RISC-V 执行 64 位加法需要三条 ADD 指令加 SLT 指令。分别编译下面这个 64 位加法函数:
// add64.c
#include
uint64_t add64u(uint64_t a, uint64_t b)
{
return a + b;
}
编译出 RISC-V 指令:
riscv-none-embed-gcc -O2 -Wall -march=rv32i -mabi=ilp32 -S add64.c
add64u:
mv a5,a0
add a0,a0,a2
sltu a5,a0,a5
add a1,a1,a3
add a1,a5,a1
ret
编译出 Cortex-M0 指令:
arm-none-eabi-gcc -O2 -Wall -mcpu=cortex-m0 -S add64.c
add64u:
adds r0, r0, r2
adcs r1, r1, r3
bx lr
Cortex-M0 比 RISC-V 少 3 条指令!如果有大量的 64 位运算,那么 RISC-V 就上 RV64 了吧。Cortex-M 目前没有 64 位的。
RISC-V’s WFI
The WFI instruction can also be executed when interrupts are disabled. The operation of WFI must
be unaffected by the global interrupt bits in mstatus (MIE/SIE/UIE) and the delegation registers
[m|s|u]ideleg (i.e., the hart must resume if a locally enabled interrupt becomes pending, even if
it has been delegated to a less-privileged mode), but should honor the individual interrupt enables
(e.g, MTIE) (i.e., implementations should avoid resuming the hart if the interrupt is pending but
not individually enabled). WFI is also required to resume execution for locally enabled interrupts
pending at any privilege level, regardless of the global interrupt enable at each privilege level.