RISC-V基础指令之addi与lui(生成一个更大的立即数,包含负数符号拓展的特殊情况)

这两条指令都是RISC-V体系结构中的整数指令,它们的功能和格式如下:

  • lui指令的全称是Load Upper Immediate,它的功能是把一个20位的立即数加载到寄存器的高20位,低12位为0。它的格式是:

    lui rd, imm

    其中,rd是目标寄存器,imm是20位的立即数。例如,lui x1, 0x12345会把0x12345000加载到x1寄存器中。

  • addi指令的全称是Add Immediate,它的功能是把一个寄存器的值和一个12位的立即数相加,并把结果存入另一个寄存器。它的格式是:

    addi rd, rs1, imm

    其中,rd是目标寄存器,rs1是源寄存器,imm是12位的立即数。例如,addi x2, x1, 0x678会把x1寄存器的值和0x678相加,并把结果存入x2寄存器中。

这两条指令可以组合起来生成一个大于20位的立即数。例如,如果想要生成一个32位的立即数0x12345678,就可以这样做:

lui x3, 0x12345
addi x3, x3, 0x678

lui指令和addi指令的立即数必须是12位和20位的。这是因为RISC-V指令集的设计原则之一是固定长度的指令,也就是说每条指令都是32位的。为了在32位的指令中表示操作码,寄存器编号,和立即数,就需要对每个部分分配一定的位数。lui指令和addi指令都属于I类型的指令,它们的格式如下:

31:20 19:15 14:12 11:7 6:0
imm rs1 funct3 rd opcode

可以看到,imm部分占用了12位,rs1和rd部分各占用了5位,funct3和opcode部分各占用了3位和7位。这样加起来刚好是32位。所以lui指令和addi指令的立即数不能超过12位。

但是,lui指令的立即数实际上是20位的,因为它会被左移12位后加载到寄存器的高20位。所以lui指令可以看作是把一个20位的立即数分成两个10位的部分,然后把高10位放在imm的高10位,低10位放在imm的低10位。例如,如果想要加载0x12345到寄存器的高20位,就可以这样写:

lui x1, 0x12345

这条指令实际上相当于:

lui x1, 0x012_345

其中,0x012是高10位,0x345是低10位。它们被合并成一个12位的立即数0x12345,并且左移12位后加载到寄存器x1中。

lui和addi指令都可以用来生成立即数,但是它们有一些限制:

  • lui指令可以把一个20位的立即数加载到寄存器的高20位,低12位为0。
  • addi指令可以把一个12位的立即数加到一个寄存器的值上,并把结果存入另一个寄存器。
  • 12位的立即数是有符号的,也就是说它可以表示正数或负数。如果它是负数,那么它的最高位(第11位)为1,并且在加法运算时会被符号扩展,也就是说它的高20位都会变成1。

因此,如果想要生成一个大于20位的立即数,就需要用lui和addi指令组合起来。例如,如果想要生成一个32位的立即数0xFEEDA987,就可以这样做:

  • 用lui指令把0xFEEDB加载到s2寄存器的高20位,低12位为0。这样s2寄存器的值就变成了0xFEEDB000。
  • 用addi指令把0x987加到s2寄存器的值上,并把结果存入s2寄存器。这样s2寄存器的值就变成了0xFEEDA987。

注意,这里有两个地方需要特别注意:

  • 为什么lui指令要加载0xFEEDB而不是0xFEEDA呢?因为0x987是一个负数,它在加法运算时会被符号扩展成0xFFFFF987。如果lui指令加载了0xFEEDA,那么加法运算后的结果就会变成0xFEED9987,而不是我们想要的0xFEEDA987。所以我们需要把lui指令加载的立即数增加1,这样加法运算后才能得到正确的结果。
  • 为什么0x987是一个负数呢?因为它是一个12位的有符号数,它的最高位(第11位)为1。在二进制补码表示法中,如果一个数的最高位为1,那么它就是一个负数。要求出它的绝对值,就需要对它取反并加1。例如,0x987取反后得到0xF678,再加1得到0xF679。所以0x987表示的负数就是−1657。

在这里,更详细地解释一下负号(即addi中的立即数第11位即最大位为1,也就是负号拓展后产生更大负数的情况)

假设我们想要生成一个32位的立即数0x12345678,我们可以用lui和addi指令组合起来,如下:

lui s0,0x12345 # 把0x12345加载到s0寄存器的高20位,低12位为0

addi s0,s0,0x678 # 把0x678加到s0寄存器的值上,并把结果存入s0寄存器

这样就可以得到我们想要的结果0x12345678。这是因为0x678是一个正数,它在加法运算时不会被符号扩展,也就是说它的高20位都是0。所以加法运算后的结果就是两个数的简单相加。

但是,如果我们想要生成一个32位的立即数0x1234F678,我们就不能用上面的方法了。因为0xF678是一个负数,它在加法运算时会被符号扩展,也就是说它的高20位都会变成1。所以如果我们用lui和addi指令组合起来,如下:

lui s0,0x1234F # 把0x1234F加载到s0寄存器的高20位,低12位为0

addi s0,s0,0xF678 # 把0xF678加到s0寄存器的值上,并把结果存入s0寄存器

那么我们得到的结果就不是我们想要的了。这是因为加法运算后的结果是:

s0 = 0x1234F000 + 0xFFFFF678 = 0x1234E678

可以看到,结果比我们想要的少了1000。这是因为符号扩展相当于把负数变成了更大的负数。所以我们需要把lui指令加载的立即数增加1,这样才能抵消符号扩展带来的影响。也就是说,我们应该这样写:

lui s0,0x12350 # 把0x12350加载到s0寄存器的高20位,低12位为0

addi s0,s0,0xF678 # 把0xF678加到s0寄存器的值上,并把结果存入s0寄存器

这样就可以得到我们想要的结果了。这是因为加法运算后的结果是:

s0 = 0x12350000 + 0xFFFFF678 = 0x1234F678

注意这里要理解好补码的意义,负数的补码,是能够和其相反数相加通过溢出从而使计算机内计算结果变为0的二进制码,所以之前的地方采取了+1的操作。

对补码更详细介绍转至计算机体系结构基础知识介绍之简单回顾原码反码补码_管二狗赶快去工作!的博客-CSDN博客

你可能感兴趣的:(RISC-V基础知识,risc-v)