本期内容如下:
- RV32I 指令应用注意事项
- 指令立即数取值范围
- RISC-V汇编伪指令
一、RV32I 指令应用注意事项
RISC-V为了追求硬件设计上的简化,很多功能并没有单独实现,而是通过编译器来自动完成对未实现指令的自动转换,编译器会利用已有基本指令来完成对未有指令功能的扩充,由于RISC-V指令设计的巧妙,这种扩充,并不会损失太多CPU性能。
如:
X0寄存器,也被称为zero,是一个只读寄存器,返回值永远为0,写入的任何数据都将被丢弃。基本指令借助于zero可以扩展出许多新的指令。
addi x0 , x0, 0 就等价与其他处理器指令的nop操作;
sub a0,x0,a1 就等价于取负数指令neg;
以上设计思路可以大大简化硬件设计。
1.1 善用伪指令
在用机器指令进行编写代码时,初学者总觉的,RV32I提供的指令集不完善,有些功能都没有实现,如nop,neg,mv,not等,为此直接用有限的机器指令编写代码,总觉的蹩脚,不够直接。
如,我们使用的nop功能时,其实编写的机器指令是addi x0 , x0, 0,像这样编写代码实在痛苦,整个代码也比较难读。为此,gnu汇编器提供的伪指令就派上用途了,实际编写“空”操作代码时,直接使用伪指令nop,在编译时候,汇编器会自动帮助将nop转换为addi x0,x0,0,这样就方便多了。
1.2 立即数的取值范围
RV32I中存在大量的立即数操作数指令,由于不同指令格式的立即数占用bit不同,其所能取的数值也是有限的,如果立即数超出实际指令范围,编译器将会报错。
如我们希望加载一个32bit常量数据0x12345678到t0寄存器中,不采用伪指令li t0,0x12345678的情况下,我们是找不到任何一条机器指令可以直接一步完成32bit立即数加载的,为了实现这一功能,我们可能想到的方法为如下代码:
addi t0,zero,0x123 //加载0x12345678的高12bit到t0
slli t0,t0,12 //t0<<12bit =>t0=0x123000
addi t0,t0,0x456 //t0=t0+0x456=0x123456
slli t0,t0,8 //t0<<8bit =>t0=0x12345600
addi t0,t0,0x78 //t0=t0+0x78=0x12345678
当然为了实现上述功能,也可采用他方法,之所以举这个例子是为了让大家知道,编写汇编代码时,机器指令的立即数是有位宽限制的,addi t0,zero,0x123345678 显然是一个错误指令。
二、指令立即数取值范围
不同立即数操作指令,立即数取值范围参见图2。
三、RISC-V汇编伪指令
伪指令是汇编器提供的类似于机器指令的操作指令,伪指令在实际的处理器指令集中并没有被实现,编译器在编译汇编代码时,可以通过一条 或多条机器指令去实现伪指令对应的功能。这样做的目的,就是可以最大限度简化处理器指令集,降低处理器开发难度,使用伪指令可以更加快速方便的编写汇编代码。
RISC-V在原有机器指令的基础上,扩展了几十个伪指令,通过这些伪指令,我们可以非常方便的编写汇编代码,具体伪指令参见图3和图4。
为了理解和消化这些伪指令,读者需要耐心的练习相关操作。
这里需要强调的几条指令,中
3.1 加载立即数
伪指令
li rd, imm(32-bit)
等效机器指令
lui rd, imm(20-bit)
addi rd, rd, imm(12-bit)
3.2 加载地址
伪指令
la rd, label
等效机器指令
auipc rd, imm(20-bit)
addi rd, rd, imm(12-bit)
3.3 调用任意32-bit 绝对地址
lui x1, <high 20-bit>
jalr ra, x1, <low 12-bit> (pc)=32bit 地址,即,调用32bit绝对地址
3.4 跳转相对PC的32bit偏移地址
auipc x1, <high 20-bit>
jalr x0, x1, <low 12-bit>(pc)=(pc)+32bit地址,即,跳转到当前指令前后32bit偏移地址处
RISC-V汇编指令学习贵在练习,坚持一段时间后,这将更好地帮助我们理解RISC-V处理器架构。