RISC-V 指令学习笔记(基于CH32V103)

Posted Top嵌入式

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RISC-V 指令学习笔记(基于CH32V103)相关的知识,希望对你有一定的参考价值。

文章目录

RISC-V 指令学习笔记(基于CH32V103)

最近学习 RISC-V 指令,参考书籍、博客:

学习过程中使用 CH32V103 RV32 架构的单片机进行一些指令的使用复现,加强记忆!

一、指令结构分类

以 32 位 RV 架构 CPU 为例子,从 CPU 级别看来,各种指令就是 32 位的一串数字,这 32 位的数字按照存储数据的位结构,具体可以分为 6 类:

关于 6 种指令的说明,参考博客内的总结:

  • R-typed

    R-typed 指令是最常用的运算指令,具有三个寄存器地址,每个都用 5bit 的数表示。指令的操作由 7 位的 opcode、7 位的 funct7 以及 3 位的 funct3 共同决定的。R-typed 是不包含立即数的所有整数计算指令,一般表示寄存器-寄存器操作的指令。

  • I-typed

    I-typed 具有两个寄存器地址和一个立即数,其中一个是源寄存器 rs1,一个是目的寄存器 rd,指令的高 12 位是立即数。指令的操作仅由 7 位的 opcode 和 3 位的funct3两者决定。值得注意的是,在执行运算时需要先把 12 位立即数扩展到 32 位之后再进行运算。I-typed 指令相当于将 R-typed 指令格式中的一个操作数改为立即数。一般表示短立即数和访存 load 操作的指令。

  • S-typed

    S-typed 的指令功能由 7 位 opcode 和 3 位 funct3 决定,指令中包含两个源寄存器和指令的imm[31:25]和 imm[11:7]构成的一个12位的立即数,在执行指令运算时需要把12 位立即数扩展到 32 位,然后再进行运算,S-typed 一般表示访存 store 操作指令,如存储字(sw)、半字(sh)、字节(sb)等指令。

  • B-typed

    B-typed 的指令操作由 7 位 opcode 和 3 位 funct3 决定,指令中具有两个源寄存器和一个 12 位的立即数,该立即数构成是指令的第32位是 imm[12]、第7位是imm[11]、25 到 30 是 imm[10:5]、8 到 11 位是 imm[4:1],同样的,在执行运算时需要把12 位立即数扩展到 32 位,然后再进行运算。B-typed 一般表示条件跳转操作指令,如相等(beq)、不相等(bne)、大于等于(bge)以及小于(blt)等跳转指令。

  • U-typed

    U-typed 的指令操作仅由 7 位 opcode 决定,指令中包括一个目的寄存器 rd 和高20 位表示的 20 位立即数。U-typed 一般表示长立即数操作指令,例如 lui 指令,将立即数左移 12 位,并将低 12 位置零,结果写回目的寄存器中。

  • J-typed

    J-typed 的指令操作由 7 位 opcode 决定,与 U-typed 一样只有一个目的寄存器 rd和一个 20 位的立即数,但是 20 位的立即数组成不同,即指令的 31 位是 imm[20]、 12 到 19 位是 imm[19:12]、20 位是 imm[11]、21 到 30 位是 imm[10:1],J-typed 一般表示无条件跳转指令,如 jal 指令。

二、寄存器功能

了解了 RV32 的指令结构分类后,我们来看一下 RV32 架构的寄存器分配情况,RV32 有 32 个寄存器,这 32 个寄存器的定义如下:其中 ABI 是寄存器的二进制接口的名称,可以在汇编中使用。

寄存器编号寄存器 ABI 名称寄存器功能
x0zero全0寄存器
x1ra跳转返回指针
x2sp栈指针
x3gp全局指针
x4tp线程指针
x5t0临时存储器
x6t1临时存储器
x7t2临时存储器
x8s0/fp存储寄存器,框架指针
x9s1存储寄存器
x10a0函数参数寄存器(可用于返回值)
x11a1函数参数寄存器(可用于返回值)
x12a2函数参数寄存器
x13a3函数参数寄存器
x14a4函数参数寄存器
x15a5函数参数寄存器
x16a6函数参数寄存器
x17a7函数参数寄存器
x18s2存储寄存器
x19s3存储寄存器
x20s4存储寄存器
x21s5存储寄存器
x22s6存储寄存器
x23s7存储寄存器
x24s8存储寄存器
x25s9存储寄存器
x26s10存储寄存器
x27s11存储寄存器
x28t3临时存储器
x29t4临时存储器
x30t5临时存储器
x31t6临时存储器

除了上面的寄存器外,还有个 pc 指针指向程序运行地址!了解完指令结构和架构寄存器的分工后,我们了解一下指令的功能分类~

三、加载存储指令

加载存储指令用于对寄存器进行数据的加载和保存操作,主要有以下几个

  • lb rd,offset(rs1):从地址为寄存器rs1的值加offset的主存中读一个字节,符号扩展后存入rd
  • lh rd,offset(rs1):从地址为寄存器rs1的值加offset的主存中读半个字,符号扩展后存入rd
  • lw rd,offset(rs1):从地址为寄存器rs1的值加offset的主存中读一个字,符号扩展后存入rd
  • lbu rd,offset(rs1):从地址为寄存器rs1的值加offset的主存中读一个无符号的字节,零扩展后存入rd
  • lhu rd,offset(rs1):从地址为寄存器rs1的值加offset的主存中读半个无符号的字,零扩展后存入rd
  • lwu rd,offset(rs1):从地址为寄存器rs1的值加offset的主存中读一个无符号的字,零扩展后存入rd
  • sb rs1,offset(rs2):把寄存器rs1的值存入地址为寄存器rs2的值加offset的主存中,保留最右端的8位
  • sh rs1,offset(rs2):把寄存器rs1的值存入地址为寄存器rs2的值加offset的主存中,保留最右端的16位
  • sw rs1,offset(rs2):把寄存器rs1的值存入地址为寄存器rs2的值加offset的主存中,保留最右端的32位

编程示例:

my_test:
	li t0, 0x20000000
	li t4, 0x12345678
	sw t4, 0x0(t0)
	lb t1, 0x0(t0)
	lh t2, 0x0(t0)
	lw t3, 0(t0)
	sb t4, 4(t0)
	sh t4, 8(t0)
	sw t4, 12(t0)
	j .

读取立即数 0x20000000 地址 (SRAM 地址 )到 t0,把 0x12345678 赋值给 t4 ,把 t4 的值保存到以 t0 值位地址的位置上,依次调用 lb、lh、lw 来读取,然后在调用 sb sh sw 将数据存放到 SRAM 上。

li 是立即数操作伪指令,因为立即数的操作是一些指令的合成,因为立即数操作使用频次较高,所以编译器将其缩减为 li,关于伪指令我在末尾会提到。

实验结果如下:

寄存器值:

Name : t0
	Hex:0x20000000
	Decimal:536870912
	Octal:04000000000
	Binary:100000000000000000000000000000
	Default:536870912

Name : t1
	Hex:0x78
	Decimal:120
	Octal:0170
	Binary:1111000
	Default:120

Name : t2
	Hex:0x5678
	Decimal:22136
	Octal:053170
	Binary:101011001111000
	Default:22136

Name : t3
	Hex:0x12345678
	Decimal:305419896
	Octal:02215053170
	Binary:10010001101000101011001111000
	Default:305419896

SRAM 存放值:

因为 CH32 是大端存储,高字节在低地址,所以每个 byte 存储的数据位置相反,关于大端小端可以参考我之前的文章:内存大小端

四、算数运算指令

首先是寄存运算功能:加减乘除

  • add rd,rs1,rs2:将寄存器rs1与rs2的值相加并写入寄存器rd

编写代码测试指令:

	li t1, 12
	addi t2, t1, 4

给 t1 寄存器赋值 12,加上立即数 4 到 t2,编译运行查看结果

  • addi rd,rs1,imm:将寄存器rs1的值与立即数imm相加并存入寄存器rd

在将 t2 寄存器的值和 t1 寄存器相加,结果保存到 t2:

	add t2, t2, t1

运行结果:

  • sub rd,rs1,rs2:将寄存器rs1与rs2的值相减并写入寄存器rd
	li t3, 4
	sub t2, t2, t3

然后给 t3 赋值 4,使用 t2 寄存器的值减去 t3,结果保存到 t2,运行结果:

  • mul rd,rs1,rs2:将寄存器rs1与rs2的值相乘并写入寄存器rd
	mul t1, t1, t3

将 t1 和 t3 的值相乘存储到 t1,运行现象:

  • div rd,rs1,rs2:将寄存器rs1除以寄存器rs2的值,向零舍入并写入寄存器rd
	div t4, t1, t3

再将 t1 除以 t3,结果保存到 t4,运行现象:

  • rem rd, rs1, rs2:寄存器 rs1 除以寄存器 rs2 的值,向 0 舍入,都视为 2 的补码,余数写入 rd 寄存器
	rem t4, t1, t3

将 t1 除以 t3 取余数,运行现象:

  • neg rd, rs2:把寄存器 rs2 的值的二进制补码写入 rd 寄存器
	li t1, 0xF1
	neg t2, t1

将 t1 的补码保存到 t2,运行结果:

五、移位指令

  • sll rd,rs1,rs2:将寄存器rs1的值左移寄存器rs2的值这么多位,并写入寄存器rd

    li t2, 0x000F0000
    li t3, 4
    sll t1, t2, t3

将 0x000F0000 逻辑左移四位:

  • slli rd,rs1,imm:将寄存器rs1的值左移立即数imm的值这么多位,并写入寄存器rd
slli t1, t2, 8

将 0x000F0000 逻辑左移8位:

  • srl rd,rs1,rs2:将寄存器rs1的值逻辑右移寄存器rs2的值这么多位,并写入寄存器rd
srl t1, t2, t3

将 0x000F0000 逻辑右移4位:

  • srli rd,rs1,imm:将寄存器rs1的值逻辑右移立即数imm的值这么多位,并写入寄存器rd
srli t1, t2, 8

将 0x000F0000 逻辑右移8位:

  • sra rd,rs1,rs2:将寄存器rs1的值算数右移寄存器rs2的值这么多位,并写入寄存器rd

算数偏移,往左相对于乘以 2,往右相对于除以 2,该方式下会将符号位带入计算

li t2, -128
sra t1, t2, t3

逻辑右移 4 位,相对于除以 16,符号位不变,得到 -8:

  • srai rd,rs1,imm:将寄存器rs1的值算数右移立即数imm的值这么多位,并写入寄存器rd
srai t1, t2, 8

逻辑右移 8 位,相对于除以 128,符号位不变,得到 -1:

六、逻辑操作指令

  • and rd,rs1,rs2:将寄存器rs1与rs2的值按位与并写入寄存器rd
	li t1, 0b0A # 0000 1010
	li t2, 0x06 # 0000 0110
	and t3, t1, t2

执行结果:

  • andi rd,rs1,imm:将寄存器rs1的值与立即数imm的值按位与并写入寄存器rd
	andi t3, t1, 0x0F

执行结果:

  • or rd,rs1,rs2:将寄存器rs1与rs2的值按位或并写入寄存器rd
	or t3, t1, t2

执行结果:

  • ori rd,rs1,imm:将寄存器rs1的值与立即数imm的值按位或并写入寄存器rd
	ori t3, t1, 0x0F

执行结果:

  • xor rd,rs1,rs2:将寄存器rs1与rs2的值按位异或(相异为1,相同为0)并写入寄存器rd
	xor t3, t1, t2

执行结果:

  • xori rd,rs1,imm:将寄存器rs1的值与立即数imm的值按位异或并写入寄存器rd
	xori t3, t1, 0x0F

执行结果:

七、跳转指令

7.1 条件跳转

条件跳转是满足设置条件的情况下进行跳转:

  • beq rs1,rs2,lable:若rs1的值等于rs2的值,程序跳转到lable处继续执行

  • bne rs1,rs2,lable:若rs1的值不等于rs2的值,程序跳转到lable处继续执行

  • blt rs1,rs2,lable:若rs1的值小于rs2的值,程序跳转到lable处继续执行

  • bge rs1,rs2,lable:若rs1的值大于等于rs2的值,程序跳转到lable处继续执行

  • bltu rs1,rs2,lable:blt 无符号版

  • bgeu rs1,rs2,lable:bge 无符号版

写一段 c 语言方便理解:

if (t1 == t2) 
    fun1();
 else if (t1 < t2) 
    fun2();
 else if (t1 >= t2) 
    fun3();

对应的汇编:

	beq t1, t2, fun1
	blt t1, t2, fun2
	bge t1, t2, fun3

另一个版本

if (t1 != t2) 
    fun1();

对应的:

	bne t1, t2, fun1

7.2 无条件跳转

无条件跳转没有设置条件,可直接进行跳转

  • j label:程序直接跳转到lable处继续执行
  • jal rd,label:把下一条指令的地址保存在rd中(通常用x1),然后跳转到label处继续执行
  • jalr rd,offset(rs):把下一条指令的地址存到rd中,然后跳转到rs+offset地址处的指令继续执行。若 rd 为全 0 寄存器,则相当于 j

jal 和 jalr 常用于函数跳转和返回

八、比较判断

  • slt rd,rs1,rs2:若rs1的值小于rs2,rd置为1,否则置为0
  • slti rd,rs1,imm:若rs1的值小于立即数imm,rd置为1,否则置为0
  • sltu rd,rs1,rs2:若rs1的值小于rs1的值,rd置为1,否则置为0
  • sltiu rd,rs1,imm:若rs1的值小于立即数imm,rd置为1,否则置为0

九、CSR 操作指令

RISC - V 中有 8 个重要的 CSR 寄存器,寄存器如下:

  1. mtvec(Machine Trap Vector)它保存发生异常时处理器需要跳转到的地址
  2. mepc(Machine Exception PC)它指向发生异常的指令
  3. mcause(Machine Exception Cause)它指示发生异常的种类
  4. mie(Machine Interrupt Enable)它指出处理器目前能处理和必须忽略的中断
  5. mip(Machine Interrupt Pending)它列出目前正准备处理的中断
  6. mtval(Machine Trap Value)它保存了陷入(trap)的附加信息:地址例外中出错
    的地址、发生非法指令例外的指令本身,对于其他异常,它的值为 0
  7. mscratch(Machine Scratch)它暂时存放一个字大小的数据
  8. mstatus(Machine Status)它保存全局中断使能,以及许多其他的状态,如图所示

这些寄存器控制着中断的使能,同时可以用于异常的捕获,当异常发生时:异常指令的 PC 被保存在 mepc 中,PC 被设置为 mtvec(对于同步异常,mepc 指向导致异常的指令;对于中断,它指向中断处理后应该恢复执行的位置。)根据异常来源设置 mcause,并将 mtval 设置为出错的地址或者其它适用于特定异常的信息字,在机器模式(M 模式,对硬件有 %100 的控制权限)下,异常信息字设置如下:

异常类型分为 5 种:

  • 访问错误异常 当物理内存的地址不支持访问类型时发生(例如尝试写入 ROM)
  • 断点异常 在执行 ebreak 指令,或者地址或数据与调试触发器匹配时发生
  • 环境调用异常 在执行 ecall 指令时发生
  • 非法指令异常 在译码阶段发现无效操作码时发生
  • 非对齐地址异常 在有效地址不能被访问大小整除时发生

异常发生时异常指令的 PC 被保存在 mepc 中,PC 被设置为 mtvec(对于同步异常,mepc
指向导致异常的指令;对于中断,它指向中断处理后应该恢复执行的位置。)根据异常来源设置 mcause,并将 mtval 设置为出错的地址或者其它适用于特定异常的信息字。把控制状态寄存器 mstatus 中的 MIE 位置零以禁用中断,并把先前的 MIE 值保留到 MPIE 中。发生异常之前的权限模式保留在 mstatus 的 MPP 域中,再把权限模式更改为 M。

相关的寄存器操作使用如下指令完成,csr 就是上面相关寄存器:

  • csrrw rd, csr, rs:是读后写控制状态寄存器,先将 csr 的值记为 t,把 rs 寄存器的值写入 csr,再将 t 写入 rd 中;
  • csrrwi rd, csr, zimm:是立即数读后写控制状态寄存器,将 csr 的值写入 rd 中,再将立即数写入 csr 中;
  • csrrs rd, csr, rs1:是读后 置位 控制状态寄存器,先将 csr 的值记为 t,让 t 和 rs1 取或并写入 csr,再将 t 写入 rd 中;
  • csrrsi rd, csr, zimm:是立即数读后 置位 控制状态寄存器,先将 csr 的值记为 t,把 t 和立即数 zimm 取或并写入 csr,再将 t 写入 rd 中;
  • csrrc rd, csr, rs1:是读后 清除位 控制状态寄存器,先将 csr 的值记 为 t,把 t 和 rs1 位与并写入 csr,再将 t 写入 rd 中;
  • csrrci rd, csr, zimm:是立即数读后 清除位 控制状态寄存器,csr 的值记为 t,把 t 和立即数 zimm 位与并写入 csr,再将 t 写入 rd 中。

以上是关于RISC-V 指令学习笔记(基于CH32V103)的主要内容,如果未能解决你的问题,请参考以下文章

RISC-V 指令学习笔记(基于CH32V103)

RISC-V MCU移植RTOS系列教程

沁恒CH32学习——RISC-V架构学习笔记

第十六届全国大学智能汽车竞赛全向组沁恒芯片申请统计情况

基于CH32V307的智能垃圾桶及其信息系统

RISC-V学习笔记