RISC-V汇编指令

本文详细介绍了RISC-V汇编指令集中的基本整数计算、控制转移、加载存储以及控制状态寄存器操作,包括ADDI、MV、SLTI等指令及其功能和实现原理。
摘要由CSDN通过智能技术生成

        本文仅作为riscv汇编指令集的学习记录。其中可能对指令集的记录并不完善,也可能出现错误疏漏之处,所以仅作为个人学习记录。

        对于RISC-V的指令集来说,其使用模块化的方式进行组织,每一个模块用一个英文字母表示,包括基本指令集I、整数乘除法指令集M、原子操作指令集A、单精度浮点型指令集F、双精度浮点型指令集D和压缩指令集C,其中特定的组合“IMAFD”被称为“通用”组合,用英文字母G表示。

        而指令集格式如下图所示:

         上图基本概括了riscv指令集的机器码格式。其中rs与rd表示通用寄存器(x0~x31);imm表示立即数;而opcode与funct域组合后,用于表示是哪个具体的汇编指令。

        下面将通过RV32的汇编指令集来进行学习,对于RV64的指令则不进行过多介绍。因为RV64也是在RV32的基础上进行扩展。

一、基本指令集 I

1.1 整数计算指令

        整数计算指令基本只使用2类汇编指令格式,即I类和R类。

        当使用I类指令格式时,进行寄存器-立即数操作;当使用R类指令格式时,进行寄存器-寄存器操作。对于这两种指令操作,其目标都是寄存器rd。

1.1.1 寄存器-立即数操作指令

ADDI汇编指令:(add imm)

        rd = imm + rs1;

        此指令将12bit位的有符号立即数扩展为32位有符号数,然后再与rs1寄存器中的值相加,最后将计算结果赋值给rd寄存器。(并且在计算过程中,如果出现32位数据溢出,将忽略溢出情况)

        对于ADDI指令来说,立即数只有12bit位,其值范围为-2048~2047。

MV汇编伪指令

        MV汇编伪指令就是利用ADDI来实现的。MV指令的作用是将某一个通用寄存器中的值复制到另一个通用寄存器,其实现原理为:

MV rd, rs1 ---> ADDI rd, rs1, 0

SLTI汇编指令:(set less than imm)

        rd = (rs1 < imm) ? 1 : 0;

        此指令作用是,如果rs1寄存器值小于经过有符号扩展后的12bit位的立即数时,将1赋值给rd寄存器;否则将0赋值给rd寄存器。

SLTIU汇编指令

        与SLTI指令相似,只不过此指令进行无符号立即数的比较,不会进行有符号数扩展。

NOP汇编指令

        此指令由ADDI指令实现,其原理为x0通用寄存器加0,并赋值给x0寄存器;此过程相当于没有进行任何有效的操作。

NOP ---> ADDI x0, x0, 0

SEQZ汇编伪指令:(set equal zero)

        此伪指令的实现原理为,使用SLTIU指令,并将立即数设置为1;此时只有当rs1寄存器值为0时,才能小于立即数,rd寄存器才会被赋值为1:

SEQZ rd, rs1 ---> SLTIU rd, rs1, 1

ANDI、ORI、XORI汇编指令

        这些指令是逻辑位操作,将有符号扩展后的12bit位立即数与rs1寄存器中的值进行按位逻辑与、逻辑或、逻辑异或操作,并将结果存到rd寄存器中。

NOT汇编伪指令

        此指令利用XORI汇编指令来实现按位取反的功能,其原理是将立即数设置为-1,那么经过扩展后的32位立即数的值为0xFFFF_FFFF;然后再让rs1寄存器值与0xFFFF_FFFF进行异或操作,就能达到按位取反的效果。

NOT rd, rs1 ---> XORI rd, rs1, -1

SLLI、SRLI、SRAI汇编指令

        SLLI:rd = rs1 << imm;(左移后,最低位补0)

        SRLI:rd = rs1 >> imm;(右移后,最高位补0)

        SRAI:rd = rs1 >> imm;(右移后,最高位补符号位)

        上述3个汇编指令分别代表了逻辑左移指令、逻辑右移指令和算术右移指令;并且因为这是32位riscv架构,所以imm只有5个bit位有效。(如果是64位架构,则应该有6个bit位有效)

        使用方法如下所示:

slli rd, rs1, 5

LUI汇编指令

        rd = imm << 12;

        此汇编指令用于将U型立即数赋值给rd寄存器的高20bit位,并将rd寄存器的低12bit位置0。

AUIPC汇编指令

        rd = pc + (imm << 12);

        此汇编指令用于构建pc的相对地址,而当执行AUIPC指令时,pc的值就是AUIPC这条汇编指令的地址;因此用此指令地址加上U型立即数即可。

1.1.2 寄存器-寄存器操作指令

        上述的汇编指令都与立即数操作的汇编指令原理一致,只不过是将输入参数中的立即数换成了通用目的寄存器(所以在进行操作时,不需要进行符号位扩展了,直接使用rs1和rs2寄存器中的值进行计算)。下面将仅对SUB等汇编指令进行介绍。

SUB汇编指令

        此指令用于将rs1寄存器中的值减去rs2寄存器中的值,并将计算结果存入rd寄存器中;同时忽略在计算过程中的溢出行为。

SNEZ汇编伪指令

        此指令用于判断传入的通用寄存器中值是否为0,如果值为非0,则rd寄存器置1;如果值为0,则rd寄存器置0。其实现原理如下所示:

SNEZ rd, rs2 ---> SLT rd, x0, rs2

1.2 控制转移指令

        RV32I提供了2种类型的控制转移指令:无条件跳转和条件分支。

1.2.1 无条件跳转指令

 JAL汇编指令

        此汇编指令采用J类型指令形式,所以立即数一共由20bit位,并且在实际使用时,立即数会左移1位,进行2字节对齐。同时由此可知,跳转的空间范围也就是在-1MB~+1MB之间;并且跳转的地址是采用pc加上符号扩展后的立即数的地址,使用的是pc相对地址,因此支持位置无关码。

        在调用此指令后,同时会将当前指令的下一条指令的地址存入rd寄存器种,也就是rd = pc+4。

J汇编伪指令

        实现原理如下:

J imm ---> JAL x0, imm

        值得注意的是,标准软件调用约定使用x1寄存器来进行存储跳转指令后的返回地址;当使用x0寄存器来进行存储调用返回地址时,就说明跳转之后,程序不会进行返回了。

 

 JALR汇编指令

        此汇编指令采用I类型指令形式,rd存放的依旧是函数调用后的返回地址;不同的是,跳转的目标地址不再是pc的偏移,而是rs1加上imm立即数后,形成的目标地址;这样jalr指令访问的就是32位的绝对地址。

        如果想要跳转到任意一个32位的绝对地址上去时,可以使用lui指令将addr的高20bit位赋值到一个通用寄存器中,然后再使用jalr指令,将addr的低12bit位加上这个通用寄存器,达到组合成一个32bit位的绝对目标跳转地址。

        注意:在使用上述组合跳转绝对地址时,有一个问题。就是在将32bit绝对地址进行拆分时,低12bit位是表示一个有符号的立即数,所以当低12bit立即数最高位为1时,jalr指令会将其当作一个负数相加,此时将产生不正确的结果。所以当低12bit立即数最高位为1时,高20bit立即数应该加1,来消除此影响。

1.2.2 条件分支指令

        所有的分支指令使用B 类型指令格式。 12 B 立即数以 2 字节的倍数编码符号偏移量。偏移量是符号扩展的,加到分支指令的地址上以给出目标地址。条件分支的范围是± 4KiB 。同时可以看出B类型指令格式没有rd寄存器,也就是说没有跳转后的返回地址。
BEQ汇编指令
        beq x5,x6,1f -> beq rs1,rs2,imm;
        比较rs1与rs2的值,如果相等,则跳转到新地址执行;并且具体编程时,常用1f、1b等标签来表示imm域的值,由链接器来解析标签,生成imm的值。
        其余指令基本类似,通过指令名称基本可以看出其作用,所以这里不再进行赘述。

1.3 加载和存储指令

        RV32I是一个“加载-存储”架构,只有加载和存储指令访问内存,而算数指令只操作CPU寄存器。RV32I提供一个32位的地址空间,按字节编址。EEI将定义该地址空间的哪一部分是哪个指令可以合法访问的(例如,一些地址可能是只读的,或者只支持按字访问)。以x0为目的寄存器的加载操作必定会引发某些异常,并引起其它的副作用,即使所加载的值被丢弃。并且现在riscv架构可以自行设置选择内存系统的大小端模式。

 

二、控制与状态寄存器指令

        RISC-V定义了一个独立的地址空间,包含与各硬件线程相关联的 4096 个控制与状态寄存器。这些控制与状态寄存器则需要特殊的指令来进行访问,这些指令称为csr指令,并且其为原子性的读-修改-写操作。

CSRRWAtomic Read/Write CSR
        指令原子性的交换 CSR 和整数寄存器中的值。 CSRRW指令读取在 CSR 中的旧值,将其零扩展到 XLEN 位,然后写入整数寄存器 rd 中。 rs1 寄存器 中的值将被写入 CSR 中。如果 rd x0 ,那么这条指令将不会读该 CSR ,且不会导致任何因为 CSR 读而出现的副作用。
CSRRSAtomic Read and Set Bits in CSR
        指令读取 CSR 的值,将其零扩展到 XLEN 位,然后写入整数寄存器 rd 中。整数寄存器 rs1 中的初始值被当做按位掩码指明了哪些 CSR 中的位被置为 1 rs1 中的任何为 1 的位,将导致 CSR 中对应位被置为 1 ,如果 CSR 中该位是可以写的话。CSR 中的其他位不受影响(虽然当 CSR 被写入时可能有些副作用)。
CSRRCAtomic Read and Clear Bits in CSR
        指令读取 CSR 的值,将其零扩展到 XLEN 位,然后写入整数寄存器 rd 中。整数寄存器 rs1 中的初始值被当做按位掩码指明了哪些 CSR 中的位被置为 0 rs1 中的任何为 1 的位,将导致 CSR 中对应位被置为 0 ,如果 CSR 中该位是可以写的话。CSR 中的其他位不受影响。
CSRRWI 指令、CSRRSI 指令、CSRRCI 指令
        分别于 CSRRW 指令、 CSRRS 指令、 CSRRC 指令 相似,除了它们是使用一个处于 rs1 字段的、零扩展到 XLEN 位的 5 位立即数( zimm[4:0] )而不是使用 rs1 整数寄存器的值。对于 CSRRSI 指令和 CSRRCI 指令,如果 zimm[4:0] 字段是零,那么这些指令将不会写 CSR ,因此应该不会产生任何由于写 CSR 产生的副作用。对于 CSRRWI 指令,如果 rd=x0 ,则这条指令将不会读 CSR ,且不会导致任何因为 CSR 读而出现的副作用。
值得注意的是,以下这些汇编伪指令:
        CSRR rd, csr 被编码为 CSRRS rd, csr, x0;(读取csr寄存器的值到rd寄存器)
        CSRW csr, rs1 被编码为 CSRRW x0, csr, rs1;(将rs1寄存器的值写入csr寄存器)
        

         以上介绍了riscv的两大类汇编指令模块,上述的汇编指令也基本涵盖了日常应用的80%。后续其他的riscv汇编指令就不再进行介绍了,需要深入了解更多的汇编指令则请参考riscv的架构手册。

  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,以下是一个简单的RISC-V汇编指令的冒泡排序的实现: ``` # RISC-V汇编指令的冒泡排序 .data array: .word 5, 2, 8, 3, 1 # 待排序的数组 n: .word 5 # 数组的长度 .text .globl main main: # 初始化寄存器 la x1, array # 数组地址存储在 x1 中 lw x2, n # 数组长度存储在 x2 中 li x3, 1 # 将 1 存储在 x3 中,用于比较 # 开始排序 loop: li x4, 0 # 将 0 存储在 x4 中,用于判断是否交换位置 li x5, 0 # 将 0 存储在 x5 中,用于遍历数组 loop1: beq x5, x2, end # 如果 x5 = x2,则跳出循环 lw x6, 0(x1) # 将 array[x5] 存储在 x6 中 lw x7, 4(x1) # 将 array[x5+1] 存储在 x7 中 blt x6, x7, skip # 如果 array[x5] < array[x5+1],则跳过交换操作 sw x7, 0(x1) # 将 array[x5+1] 存储到 array[x5] 中 sw x6, 4(x1) # 将 array[x5] 存储到 array[x5+1] 中 li x4, 1 # 标记已交换 skip: addi x5, x5, 1 # x5++ addi x1, x1, 4 # 指向下一个元素 j loop1 # 继续遍历 beq x4, x3, loop # 如果已经完成排序,则跳出循环 subi x2, x2, 1 # x2-- li x5, 0 # 重新开始遍历数组 la x1, array # 指向数组的第一个元素 j loop # 继续排序 end: # 排序完成,输出结果 li x10, 1 # 将 1 存储在 x10 中,用于输出整数 li x5, 0 # 将 0 存储在 x5 中,用于遍历数组 loop2: beq x5, x2, exit # 如果 x5 = x2,则跳出循环 lw a0, 0(x1) # 将 array[x5] 存储在 a0 中 li a7, 1 # 将系统调用号 1(输出整数)存储在 a7 中 ecall # 调用系统 addi x5, x5, 1 # x5++ addi x1, x1, 4 # 指向下一个元素 j loop2 # 继续遍历 exit: li a7, 10 # 将系统调用号 10(退出程序)存储在 a7 中 ecall # 调用系统,退出程序 ``` 这段代码实现了一个简单的冒泡排序,通过遍历数组并比较相邻元素的值,不断交换位置直到排序完成。在排序完成后,程序输出排序后的结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值