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


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

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

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

    一、指令结构分类

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

    image-20220806154011351

    关于 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 .
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    读取立即数 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    SRAM 存放值:

    image-20220806234733822

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

    四、算数运算指令

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

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

    编写代码测试指令:

    	li t1, 12
    	addi t2, t1, 4
    
    • 1
    • 2

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

    20220815232131

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

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

    	add t2, t2, t1
    
    • 1

    运行结果:

    image-20220815232620063

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

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

    20220815233057

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

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

    image-20220815233726105

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

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

    20220815233812

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

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

    image-20220815233909290

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

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

    20220815233940

    五、移位指令

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

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

    将 0x000F0000 逻辑左移四位:

    20220817230828

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

    将 0x000F0000 逻辑左移8位:

    image-20220817231038242

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

    将 0x000F0000 逻辑右移4位:

    image-20220817231107749

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

    将 0x000F0000 逻辑右移8位:

    image-20220817231208927

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

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

    li t2, -128
    sra t1, t2, t3
    
    • 1
    • 2

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

    20220817231552

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

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

    20220817231608

    六、逻辑操作指令

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

    执行结果:

    image-20220818233534563

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

    执行结果:

    image-20220818233606046

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

    执行结果:

    image-20220818233709677

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

    执行结果:

    image-20220818233757980

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

    执行结果:

    image-20220818233854147

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

    执行结果:

    20220818233959

    七、跳转指令

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对应的汇编:

    	beq t1, t2, fun1
    	blt t1, t2, fun2
    	bge t1, t2, fun3
    
    • 1
    • 2
    • 3

    另一个版本

    if (t1 != t2) {
        fun1();
    }
    
    • 1
    • 2
    • 3

    对应的:

    	bne t1, t2, fun1
    
    • 1

    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)它保存全局中断使能,以及许多其他的状态,如图所示

    image-20220820141201982

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

    image-20220820183445569

    异常类型分为 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 中。
  • 相关阅读:
    manjaro gnome 记录 3 配置国内镜像源
    谷本系数/相似度的计算和分子指纹
    GO逃逸分析
    zookeeper 查询注册的 dubbo 服务
    PySCENIC(二):pyscenic单细胞转录组转录因子分析
    单目标应用:基于狐猴优化算法(Lemurs Optimizer,LO)的微电网优化调度MATLAB
    Spring (5)—声明式事务控制
    Oracle数据库:子查询、单行子查询,多行子查询,in,any,all语句,子查询的练习案例
    数据治理建设管理办法(参考)(粉丝福利)
    直流有刷电机开环调速基于STM32F302R8+X-NUCLEO-IHM07M1(一)
  • 原文地址:https://blog.csdn.net/qq_45396672/article/details/126453717