第2章 RISC-V基础整数指令集RV32I,V2.1

本章描述了RV32I基础整数指令集的2.0版本。
RV32I设计目标
RV32I被设计成足以形成编译器目标并支持现代操作系统环境。ISA的设计也是为了减少最小实现所需要的硬件。
RV32I包含40条独立的指令,便可以模拟其他任何ISA扩展。(除了原子扩展"A",它需要额外的硬件支持原子性)。在实践中,包括机器模式特权级架构在内的硬件实现还需要6条CSR指令。

2.1 RV32I的程序员模型

图2.1显示了RV32I的非特权级状态。对于RV32I,共32个寄存器,每个位宽32比特,即XLEN=32比特。
寄存器0是硬件连线的,所有位都是零。通用寄存器1~31存储的值被各种指令解释为布尔值集合、二进制补码表示的有符号二进制整数、或无符号二进制整数。

还有一个额外的非特权寄存器PC,用于保存当前指令的存储地址。
 图2.1 RV32I整数指令寄存器
在RV32I中,没有专用的堆栈指针寄存器SP或者返回地址链接寄存器LR。指令编码允许任何寄存器用于此目的。
然而,标准软件调用约定使用X1作为LR,使用X2作为SP,使用X5作为备用LR。硬件可能会使用X1或X5来加速函数调用和返回,见JAL和JALR指令描述。
16比特压缩指令倾向固定使用X1作为LR,X2作为SP。使用其他寄存器的软件也可以正常运行,但可能需要更大的代码尺寸。

可用的架构寄存器数量对代码大小、性能和能耗有很大影响。虽然16个寄存器对于编译代码的整数ISA来说足够了,但是16位的16个寄存器不能编码一个完整的ISA,不支持3地址格式的指令,虽然2地址格式的是可以的,但是它会增加指令数量并降低效率。我们希望避免使用中间指令大小(例如Xtensa的24位指令)来简化基础硬件实现。更多整数寄存器也有助于提高高性能代码的性能,可以广泛使用循环展开,软件流水线和缓存平铺等。

2.2 基础指令格式

在RV32I中,有四种指令格式(R/I/S/U)。如图2.2所示,所有指令格式长度都是32位。在内存中,必须按照4字节边界对齐,即IALIGN=32。
在无条件跳转指令或者分支转移指令中,如果目的地址不是四字节对齐的,则触发指令地址未对齐异常。这个异常会在分支或者跳转指令中报告,而不是目的指令。跳转指令未发生时,并不会产生指令地址未对齐异常。

注意:

  1. 在16bit的ISA中,指令地址按照2字节边界对齐。
  2. 在分支或跳转指令中产生的地址未对齐异常有助于调试和简化硬件设计,这是唯一产生指令地址未对齐异常的地方。

解码一个预留指令的行为是未定义的。有些平台也许要求产生非法指令异常。

在所有格式中,RV32I保证源寄存器(rs1, rs2)和目的寄存器(rd)在相同位置,以此来简化编码。
除了在CSR指令中使用的5比特立即数,立即数都是有符号扩展,然后被打包到指令最左边的可用位置来减少硬件复杂度。特别地,立即数符号位总是在指令的第31位来加速有符号扩展电路的速度。

解码寄存器说明符在实现的关键路径上,在设计和实现计算机处理器的时候,解码指令中的寄存器标识是一个重要任务,它要正确识别指令中的寄存器号和他们的操作数,如果解码复杂需要多个步骤,那么整个流水线的吞吐量就会受到影响,从而导致性能下降。
为了避免在解码过程中出现性能瓶颈,RISC-V指令架构选择了一种简化的方式,将寄存器号放在相同位置,无论指令格式是什么,处理器都可以在相同位置找到源寄存器和目的寄存器,这样以来,解码阶段的逻辑可以更加简单高效,因为不需要针对不同格式的指令进行不同的解码。
图2.2 RV32I指令编码格式注意:

  1. 在实践中,立即数要么很小,要么很大,需要全部的XLEN位。我们通常选择不对称的立即分割(常规指令采用12位立即数,并增加一种专用的加载上限指令,来增加操作码可用的操作码空间。
  2. 立即数扩展总是符号扩展,因为我们没有看到MIPS ISA使用零扩展的好处,我们尽可能保持ISA的简洁性。

2.3 立即数编码变体

关于立即数变体,有两种,新增格式(B/J)。如下图所示
立即数变体S和B格式区别:用途不同:B格式指令主要用于条件分支操作。S格式指令主要用于将数据存储到内存中。 B格式指令不像常规那样将立即数字段的所有位在硬件中左移1位,而是将中间位imm[10:1]和符号位保持固定位置,而S格式的最低位imm[0]则被编码为B格式的高阶位imm[11]。B格式的12位立即数表示分支偏移量,以半字(2字节)为单位的偏移量。S和J使用了相同的指令格式,节约了编码空间。B格式的立即数解码在硬件更为简洁和高效,有助于提高处理器性能和效率。
类似的,U和J格式区别:20位立即数向左移动12位后形成U格式立即数,向左移动1位形成J格式立即数。

图2.4显示了每种基本格式的立即数,并做了标记来显示指令地址的哪些位产生立即数的每一位。
立即数类型符号扩展是立即数指令最关键的操作之一(特别当XLEN>32时),在RISC-V中,所有指令的立即数符号位总是保持在指令的31位,这样做的目的是允许符号扩展和指令解码同时进行,这样设计可以简化硬件实现并提高性能
虽然更复杂的实现可能会有单独的加法器用于分支跳转计算,如此不会从保持立即数位置不变中受益。但我们仍旧想减少最小实现的硬件成本。通过旋转B和J格式中立即数而不是使用动态硬件选择器来乘以2,我们减少了硬件信号的传播和立即数选择器的成本。被打乱的立即数编码为静态编译器增加了可以忽略不计的时间,对于动态生成的指令可能会有一些小的额外开销,但是最常见的短向前跳转都有直接的立即编码方式。
【这段话主要讨论RISC-V中处理立即数的一些优化策略,意在提高硬件实现的效率和性能。通过保持立即数的位置一致以及采用旋转编码方式,RISC-V指令集能够在简化硬件设计的同时,保持较高的性能水平】。

2.4 整数计算指令

大多数整数计算指令操作数位宽为XLEN,并保存在整数寄存器文件中。整数计算指令被编码为I格式(寄存器-立即数操作)或者R格式(寄存器-寄存器操作)的指令。目的寄存器都是rd,对于这两种格式指令,没有整数计算指令会产生异常。
注意:
在基础指令集中,我们没有包含特定指令用于支持整数算数运算溢出检查,因为许多溢出检查可以很便宜地使用RISC-V分支来实现。例如:
1)无符号加法运算溢出检查,只需要在加法分支后添加一条分支指令来实现。add t0, t1, t2; bltu t0, t1, overflow.
2) 对于有符号加法运算溢出检查,如果操作数符号是已知的,溢出检查只需要一条指令来实现:addi t0,t1,+imm; blt t0,t1,overflow.
3)对于通用符号加法溢出检查,三个额外指令在加法后面是需要的,利用观察当且仅当一个操作数为负数时,和应该小于其中一个操作数。add t0, t1, t2; slti t3, t2, 0; slt t4, t0, t1 bne t3, t4, overflow.
在64I指令集中,检查32位符号加法可以进一步被优化,通过比较ADD和ADDW结果。

整数 寄存器-立即数 指令

addi把12位有符号立即数加到寄存器rs1中,算数溢出是被忽略的,并且计算结果的低XLEN位保存到寄存器rd中。addi rd, rs1, 0被用来实现mv rd, rs1汇编伪指令.
register-Imm
slti(set less than immediate)。如果寄存器rs1(前提是有符号数)小于有符号立即数,则将数字1放在寄存器rd中,否则将0写入寄存器rd中。stiu rd, rs1, 1指令如果rs1=0,则rd=1,否则rd=0。汇编伪指令seqz rd, rs。
andi、ori、xori是对寄存器rs1和符号扩展的12位立即数执行按位与、或、异或的逻辑运算并将结果放入rd中。注意,xori rd, rs1, -1执行寄存器rs1的按位逻辑反转(汇编伪指令not rd, rs)

以常量移位操作被编码为I格式的指令,被移动的操作数保存在rs1中,移动位被编码到立即数的低5位。
移位类型被编码到bit30位。

SLLI是逻辑左移(零被移入地位)
移位SRLI是逻辑右移(零被移入高位)
SRAI是算数右移(原符号位复制到高位)

==lui(load upper immediate)==被用来构架32比特常量,使用能够U类型编码。lui将u立即数值保存在目的寄存器rd的高20位,低12位填充零。
load==auipc(add upper immediate to PC)==被用来构建PC相对地址,也是U类型格式指令。auipc利用20位的立即数构建一个32位偏移量,低12位填充零,最后把这个偏移量加auipc指令的地址,把最终的结果放在rd寄存器中。
注意:AUIPC指令支持两个指令序列来访问相对PC的任意偏移地址,来进行控制流程传输和数据访问。1)auipc和jalr中的12位立即数的组合可以将控制转移到任何32位PC相对地址,
2)而auipc加12位立即数偏移在常规加载和存储指令中可以访问任意32位PC相对偏移的数据地址。
3)此外,当前pc的值可以通过设置立即数为0来获得,尽管jalr+4指令可以被用来获得pc值,但在简单的微处理器中有可能导致Pipeline断裂,或者负载微处理器架构污染BTB结构。

整数 寄存器-寄存器 指令

RV32I定义了几个R格式的算数操作,所有操作都是将rs1和rs2作为源操作数,并将结果写入rd中。func7和func3位域选择操作类型。
R-R-R

add加法,sub减法;
slt和sltu比较操作: 有符号无符号比较,并将结果写入rd中,rs1<rs2, rd=1, rs1>=rs2, rd=0;
and or xor逻辑操作: 与,或,非
sll srl sra移位操作: rs2的低5位表示移动位数,rs1为被移动数,rd存储结果。

NOP指令
NOP指令不改变架构任何可见状态,除了PC和任何应用性能计数器有增长,如下图所示,NOP被编码为
addi x0, x0, 0
NOP
NOP指令在编写代码时被用来对代码段进行对齐,使其在微架构上对齐到有重要意义的地址边界。
或者为代码修改流出空间,虽然NOP的编码有多种方法,但是我们定义了一个规范的NOP编码,以允许
微架构优化和可读性更好的反汇编输出。

ADDI被选择用于nop编码,因为这是最有可能在一些列系统中使用最少资源的。特别地,该指令只读一个寄存器。此外,ADDI可能是超标量计算中常见操作。特别地,地址生成单元可以用于执行addi。而寄存器加或逻辑移位都需要额外的资源。

2.5 控制转换指令

RV32I提供两类控制转换指令:1)无条件跳转;2)有条件跳转。控制转换指令在架构上没有任何可见的时延。

无条件跳转

1)跳转和链接JAL指令使用J类型格式,J-immediate是一个有符号偏移(以2的倍数字节)。
偏移量与跳转指令的地址相加生成目的地址,跳转范围±1MB。JAL指令同时将跳转指令的后一条(PC+4)指令地址存入寄存器rd。标准软件调佣习惯使用x1作为返回地址寄存器LR,X5作为备用LR。

备用链接寄存器支持调用毫秒级例程(例如,保存和恢复在压缩代码中的寄存器),同时保留常规返回地址寄存器。X5倍选择为备用链接返回寄存器,被编码为只有1个bit的不同(对比标准链接寄存器)。

2)普通跳转指令(伪汇编J)被编码为rd=x0的JAL。
JAL

3)间接跳转指令(JALR)使用I类型格式,目的地址由12位符号扩展立即数加上寄存器rs1组成,然后把最低位设置为0。跟在跳转指令后的指令地址会被写入寄存器rd中。寄存器rd0被用作目的寄存器,如果结果需要。
目的寄存器

无条件跳转指令都是用PC相对地址来支持地址独立代码(PIC程序)。JALR指令被定义为能用两个指令跳转到32位绝对地址范围。首先,一个LUI指令可以先加载rs1寄存器,保存目的地址高20位。然后,JALR指令可以加在低位。类似的,AUIPC辅助JALR指令可以跳转到任意32位的PC相对地址范围。
在计算JALR目的地址时清除最低位,可以略微简化硬件,并用来存储额外信息。当r1=x0时,JALR可以被用于执行单条指令路径调用(跳转范围±2KB),可以用于执行快速调用一个小的运行时库。或者,ABI可以专用一个寄存器来指向地址空间的其他地方库。

如果目的地址不是四字节边界对齐,JAL和JALR指令会产生一个目的地址未对齐异常。

返回地址预测堆栈是高性能取指单元(IFU)的常规特征,但是需要对过程调用和返回的精准预测才能生效。对于RISC-V,指令使用的是通过寄存器号隐式编码,JAL指令应该将返回地址压入RAS(返回地址栈),只有当rd=x1/x5的时候。JALR指令应当根据表2.1进行压栈出栈操作。
rd
NOTE:
当两个不同链接寄存器(X1,X5)被给出作为rs1和rd时,然后RAS被弹出和压入来支持子路径。如果rs1和rd是相同链接寄存器(x1或x5),RAS只能压栈来使能序列融合:lui ra imm20; jalr ra, imm12(ra); 2) auipc ra, imm20; jalr ra, imm12(ra)。

条件分支

比较与条件分支的结合
所有分支指令使用B指令格式,12bit的B-imm被编码为符号偏移,且是2字节倍数。
偏移加跳转指令地址来获取目的地址。跳转范围±4KB。
分支跳转
分支指令比较两个寄存器。BEQ和BNE表示如果rs1=rs2,或rs1≠rs2,跳转到某分支。
BLT和BLTU表示如果rs1<rs2跳转到指定分支。
BGE和BGEU表示如果rs1>rs2跳转到指定分支。

2.6加载和存储指令

RV32I是一个加载-存储架构,只有加载和存储指令(访问内存)和算数指令可以运行在CPU寄存器上。RV32I提供了一个32位地址空间(按字节寻址)EEI将会定义地址空间的什么部分可以被什么指令合法访问。(例如,有些地址是只读的,或者只支持按字宽度访问)。以X0作为目的地址一定会引发异常
load和store

EEI将定义存储系统是大端还是小端。在RISC-V中,字节序是固定不变的。

如果一个字节的数被存储到某个地址,然后从那个地址按照字节加载,可以返回存储的值;
在一个小端配置中,多字节存储时,寄存器低字节数据存储到低地址内存位置,高字节存储在内存高位置处
在一个大端配置中,多字节存储时,寄存器高字节数据存储在低地址内存位置,低字节数据存储到高地址内存位置。

加载和存储指令实现在寄存器和内存之间传递数据, 加载指令被编码为I类型格式,存储指令被编码为S格式。
有效的地址获取是通过把寄存器RS1加上符号扩展的12位立即数来实现的。
加载指令从内存RS1+imm12位偏移处拷贝数据到寄存器中。
存储指令将寄存器RS2中数据存储到RS1+imm12的内存中;

LW指令从内存加载一个32-bit宽的数据到寄存器rd中;
LH指令从内存加载一个16-bit宽数据,然后符号扩展到32-bit数据,最后存储到内存中;
LB和LBU指令从内存加载一个8-bit宽数据,然后被扩展为32-bit数据,最后存储到内存中;
SW,SH,SB指令存储将寄存器rs2中的32-bit,16-bit和8bit数据存储到内存中。

抛开EEI,加载和存储的有效地址如果自然对齐将不会产生地址未对齐异常,然而加载和存储使用的地址通常和数据对齐。
EEI也许保证未对齐加载和存储被全面支持,如此软件运行过程中,不会经历受限的或者致命的地址未对齐的陷阱。在这种情况下,硬件来处理未对齐加载和存储,或者通过一个不可见的陷阱进入可执行环境处理,或者可能是硬件和不可见的陷阱组合来处理(具体根据地址值)。
EEI也许不保证未对齐的加载和存储不可见。在这种情况下,加载和存储不对齐的指令或许执行成功,也许产生异常。异常具体可能是地址未对齐,也可能是access-fault访问异常。对于一次内存访问,如果不应该模拟不对齐访问,例如对内存区域访问有副作用,则可以引发一个access-fault访问异常,而不是地址对齐异常。
当EEI不能保证未对齐的加载和存储被隐形处理时,EEI必须定义异常(地址未对齐引发)进入受限陷阱(允许软件运行在内部可执行环境来处理陷阱)或者致命陷阱(终止执行)。

未对齐访问偶尔是必须的,在移植遗留代码时,或者使用任何形式被打包的SIMD扩展或处理外部打包的数据结构时,这非常有助于提高程序的性能。我们允许EEI通过常规的存储加载指令选择支持未对齐访问,来达到或者减少不对齐硬件的作用。一种选项是在基本ISA中,不支持未对齐访问。然后提供一些独立的ISA来支持不对齐访问,或者是特殊指令集来帮助软件处理未对齐问题。或者采用新的硬件地址寻址模式来支持不对齐访问。特殊指令增加了ISA的复杂度,并且经常需要添加新的处理器状态(例如,SPARC VIS有对齐地址偏移寄存器)或者复杂化现有的处理器状态。(例如,MIPS LWL/LSR部分寄存器写)。另外,对于面向循环的打包SIMD代码,操作数不对齐的额外开销促使软件提供多种形式的循环(基于操作数对齐方式),这使得代码生成变得复杂,并增加了循环启动开销,新的不对齐硬件寻址模式在指令编码中占用大量可观的空间,或者需要非常简单的寻址模式(例如,寄存器间接寻址)。

即使当不对齐存储加载成功时,这些访问也可能运行的非常慢,具体速度取决于实现(例如,通过不可见的陷阱实现)。此外,自然对齐存储和加载可以保证以原子方式执行,而未对齐的可能做不到,因此需要额外的同步操作来保证原子性。

我们没有强制不对齐访问的原子性。因此,可执行环境可以实现使用不可见机器陷阱和一个软件处理程序来处理部分或全部的不对齐访问。如果硬件支持不对齐访问,软件可以使用常见的加载和存储指令来利用这一点,硬件可以根据运行地址是否对齐,自动优化访问

2.7 内存排序指令

fence
FENCE指令可被其他RISC核等观察到, 用于调整设备I/O和内存访问顺序,设备输入(I),设备输出(O),内存读®,内存写(W)都可以被重新排序成任何组合。在FENCE指令之后的指令操作不能被其他RISC核看到,直到FENCE指令之前的操作执行完成。

第14章对内存模型有一个精准的描述。

EEI定义什么IO操作是可能的,特别地,被存储和加载指令访问的内存地址被处理或者排序为设备输入或设备输出操作,而不是内存读和写。例如,内存映射IO设备通常被处理为不可缓冲存储加载操作并且排序通过I/O位而不是R/W位。指令集扩展也许描述新的I/O指令,这些也使用FENCE指令的I和O位排序。
fence
FENCE的fm位域定义了fence语义,FENCE.TSO被定义为fm=1000的fence。在前身集中的所有操作都要在后继集中的操作之前。
当前身=RW,后继=RW,FENCE.TSO排序所有所有在前身集中的存储操作在后继集的所有内存操作。
【这段理解不准确】
FENCE.SO编码作为可选扩展加载到原始基础指令编码中,基本定义要求忽略任何集合bit,并将FENCE指令作为全局的,因此,这是一个向后兼容的扩展。

FENCE指令中未使用的域rs1和rd被预留给更精准粒度的围栏指令,为了将来的扩展。为了向前兼容,基本实现会忽略这些位,标准软件使用零来填充这些位置。类似,表2.2中许多fm和前后集设置也为将来预留。基本实现会把预留的当做正常的fm=0来处理。

我们选择松散内存模型可实现从简单的机器实现,未来的加速器和协处理的更高性能。
我们从内存RW排序中分离出IO排序,来避免在一个设备驱动不必要的串行化操作。也支持可替代的非内存路径来控制新增的协处理和IO设备。简单实现也许额外的忽略前身和后继域,总是执行一个保守的fence操作。

2.8 环境调用和断点

SYSTEM指令被用来访问系统功能,那些可能需要特权才能访问的功能,被编码为I类型指令格式。这个可以
被分为两类,一类是原子的读-修改-写控制和状态寄存器CSR指令,另一类是潜在的特权级指令。
CSR指令会在第九章讨论,这里讨论基本非特权级指令。

system指令被定义成允许更简单的实现,总是陷入到单个软件陷阱处理程序中,更复杂的实现也许
执行了更多地system指令。
他

这两条指令产生一个精准的请求陷入支持可执行环境。
ECALL指令用来向执行环境发起服务请求,EEI会定义服务的参数和如何传递,但通常这些被定义
在整数寄存器文件中。
EBREAK指令用于返回控制,返回到一个调试环境。

ecall和ebreak之前被命名为scall和sbreak指令,相同功能和编码方式,但是重新命名来反映它们可以用于更多的场景,不仅仅是操作系统调用或调试。另一个使用EBREAK方式是支持"半主机",可执行环境包含一个调试器,围绕ebreak构架一个服务可以替代系统调用接口。因为RV32I只提供一个ebreak指令,半主机使用一个特殊序列的指令来区分半主机ebreak和插入到调试器中的ebreak。

ebrak
NOTE:这三条指令必须是32位宽指令。

2.9 HINT指令

RV32I为HINT指令预留了大量编码空间,这些指令用于向微处理器架构传递性能暗示。
HINT被编码为rd=x0的整形计算指令。因此像NOP指令,不改变架构可见状态,除了推进PC和任何可用性能计数器。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值