这两条指令都是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的操作。