赞
踩
本文主要介绍RISC-V指令集,简单总结一些重点信息,其中参考了《RISC-V 手册》和《计算机组成与设计 硬件与软件接口 RISC V 版》,如果学习过程中有问题,欢迎指正。
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
提示:以下是本篇文章正文内容,下面案例可供参考
RISC-V要成为一个指令集,他必须要符合一下这些特征。
要能适应包括从最袖珍的嵌入式控制器,到最快的高性能计算机等各种规模的处理器
应该能兼容各种流行的软件栈和编程语言
应该适应所有实现技术,包括现场可编程门阵列(FPGA)、专用集成电路
(ASIC)、全定制芯片,甚至未来的设备技术。
应该对所有微体系结构样式都有效
应该支持广泛的专业化,成为定制加速器的基础
应该是稳定的,基础的指令集架构不应该改变
RISC-V相对于以前传统的指令集,它的优点在于:
RISC-V的命名规范:RV[###][abc……xyz]。例如:RV32IMA,RV64GC
RISC-V的模块化是以一个名为RV32I的基础ISA作为核心模块,RV32I是固定的,永远不会改变,但根据应用程序的需要可以选择扩展模块。
扩展模块指令集:
这种模块化特性使得RISC-V具有了袖珍化、低能耗的特点,而这对于嵌入式应用可能至关重要。惯例是把代表扩展的字母附加到指令集名称之后作为指示。例如,RV32IMFD将乘法(RV32M),单精度浮点(RV32F)和双精度浮点(RV32D)的扩展添加到了基础指令集(RV32I)中。
用一个公式简单表达,即:
RISC ISA = 1 个基本整数指令集 + 多个可选的扩展指令集
RISC-V有六种基本指令格式:
以RV32为例
如下图所示:
R类型指令将32位划分成6个区域:
R类型的全部指令
例子:
add x18,x19,x10
I类型指令将32位划分成5个区域:
I类型指令可以用于:
格式上只有一个字段不同于r格式,rs2和funct7替换为12位符号立即数(immediate字)imm[11:0]。immediate字段为补码值,所以它可以表示从-2"到2"-1之间的整数。当I型格式用于加载指令时,immediate字段表示一个字节偏移量,所以加载双字指令可以取相对于基址寄存器rd中基地址偏移±(2"或2048)字节(±(2或256)个双字)的任何双字。
指令格式展示:
寄存器-立即数的运算
加载指令
同样格式的I类型指令也可以用作加载指令
I类型的指令算术展示:
注:其中画圈表示位移方向0表示向左移,1表示向右移
举例:
addi x15,x1,-50
加载:
lw x14, 8(x2)
S类型指令将32位划分成6个区域:
S型格式的12位 immediate字段分成了两个字段,低5位和高7位。RISC-V体系结构设计师选择这种设计是因为它能够在所有指令格式中保持rs1和 rs2字段在相同的位置。保持尽可能相似的指令格式降低了硬件的复杂性。同样,opcode和 funct3字段也总是保持同样的大小并在同一个位置。
指令格式展示:
S类型的指令算术展示:
注:sb = Store byte,sh = Store halfword, sw = Store word
举例
sw x14, 8(x2) #先将x2寄存器偏移8bit,再存到x14寄存器。
B类型指令将32位划分成6个区域:
B类型指令属于有条件的分支。RV32I 可以比较两个寄存器并根据比较结果上进行分支跳转。比较可以是:相等(beq),不相等 (bne),大于等于(bge),或小于(blt)。最后两种比较有符号比较,RV32I 也提供相应的无符号版本比较的:bgeu 和 bltu。剩下的两个比较关系(大于和小于等于)可以通过简单地交换两个操作数,即可完成比较。因为 x < y 表示 y > x 且 x ≥ y表示 y ≤ x。
RISC-V汇编语言包含两个决策类指令,类似于带go to的if语句。
beq rs1, rs2, Ll
该指令表示如果寄存器rs1中的值等于寄存器rs2中的值,则转到标签为L1的语句执行。助记符beq代表相等则分支。
bne rs1, rs2, Ll
该指令表示如果寄存器rs1中的值不等于寄存器rs2中的值,则转到标签为L1的语句执行。助记符bne代表不等则分支。
这两条指令通常称作条件分支指令。
注:PC 相对寻址在后续补充
举例
beq x19,x10, offset = 16 bytes
U类型指令将32位划分成3个区域:
U类型指令包含两种指令
之前的类型指令中,已经有了12位的立即数作为表示常量。虽然常量通常很短并且适合12位,但有时它们也会更大。RISC-V指令系统包括指令 load upper immediate(取立即数高位,lui),用于将高20位常数加载到寄存器的第31位到第12位。将寄存器的低12位用0填充。该指令会与ADDI指令一起使用,目的是将低12位写入目标寄存器,以实现对32位的寄存器数值设置。
LUI x10 0x87654 # x10 = 0x87654000
ADDI x10,x10,0x321 # x10 = 0x87654321
注:由于ADDI的12位立即数最高位是符号位。例如:
如何利用LUI加载0xDEADBEEF
LUI x10, 0xDEADB # x10 = 0xDEADB000
ADDI x10, x10, 0xEEF # x10 = 0xDEADAEEF
由上可知 0xDEADAEEF不等于0xDEADBEEF
所以当ADDI 的12位立即数为负,也就是最高位bit位为1时,得到而结果是高20为减1再和低12位拼接起来。(减1是和补码有关)
解决方法:当数值的低12位的符号为1时,将利用lui装入的高20为的立即数值预先加1,而ADDI写入的低12位不变。如下
LUI x10, 0xDEADB # x10 = 0xDEADB000
ADDI x10, x10, 0xEEF # x10 = 0xDEADAEEF
在汇编语言中还可以使用li伪指令,直接装入32位的数值,但时它在编译时还是会编译成LUI和ADDI这两条指令。
li x10, 0xDEADBEEF
AUIPC指令加载一个20bit的的立即数,取值范围为0-1048575,但是该指令rd中保存的数据是(pc)+(立即数<<12)。用于PC相对寻址。
Label: AUIPC x10, 0 # Puts address of label in x10
J类型指令将32位划分成3个区域:
跳转并链接指令(jal)具有双重功能。若将下一条指令 PC + 4 的地址保存到目标寄存器中,通常是返回地址寄存器 ra(见下图 寄存器),便可以用它来实现过程调用。像分支一样,jal 将其 20 位分支地址乘以 2,进行符号扩展后再添加到 PC 上,便得到了跳转地址。
注: 如果使用零寄存器(x0)替换 ra 作为目标寄存器,则可以实现无条件跳转,因为 x0 不能更改。
跳转和链接指令的寄存器版本(jalr)同样是多用途的。它可以调用地址是动态计算出来的函数,或者也可以实现调用返回(只需 ra 作为源寄存器,零寄存器(x0)作为目的寄存器)。Switch 和 case 语句的地址跳转,也可以使用 jalr 指令,目的寄存器设为 x0。
RV32I有32个通用寄存器,以及一个PC寄存器。其中有一个通过硬件设置的值恒为 0 的 x0 寄存器,
RISC-V分支指令寻址,在之前的B类型指令就是其中之一。
这种格式可以表示从-4096到4094的分支地址,以2的倍数表示。B型格式包括一个7位操作码、一个3位功能码、两个5位的寄存器操作数( rs1和 rs2)和一个12位地址立即数。该地址使用特殊的编码方式,简化了数据通路设计,但使组装变得复杂。下面这条指令
bne x10,x11,2000 // if x10 != xll,go to location 2000 = 0111 1101 0000
其中条件分支的操作码是11001112,而bne 的funct3码是0012
RISC-V的无条件跳转–链接指令(jal),是分支寻址的另一种方法,也是唯一使用J型格式的指令。该指令由一个7位操作码、一个5位目标寄存器操作数(rd)和一个20位地址立即数组成。链接地址,即jal之后的指令的地址,被写入rd 中。
如果程序的地址必须适合这个20位字段,则意味着没有程序可能大于220,所以另一种方法是指定一个与分支地址偏移量相加的寄存器,这样就允许程序大到264,并且仍然能够使用条件分支指令,以便分支指令可以按如下来计算:
程序计数器=寄存器内容+分支地址偏移量
这样就允许程序大到2^64,并且仍然能够使用条件分支指令
注:加载和存储对字节、半字、字或双字的访问有不同的版本。
在做RISC-V指令过程中,参考了《RISC-V版本的计算机组成与设计
硬件/软件接口》、《RISC-V 手册》、浙江大学mooc《计算机组成与设计:RISC-V》以及RISC-V官文提供文件。不清楚方面各种参考相互比较学习,收获颇多。我会坚持写完这一个系列。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。