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:2019:1514:1211:76:0
immrs1funct3rdopcode

可以看到,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博客

### 回答1: 以下是使用RISC-V汇编语言实现将10个整数中的正数乘以2,负数除以2的代码: ``` lui t0, %hi(x10010000) # 将x10010000的高20位存入$t0 addi t0, t0, %lo(x10010000) # 将x10010000的低12位加入$t0中 li t1, 10 # 将10存入$t1中 li t2, 0 # 将0存入$t2中 Loop: # 开始循环 lw t3, 0(t0) # 将存储在$t0指向地址的整数存入$t3中 addi t0, t0, 4 # 将$t0指向下一个整数存储单元 blt t3, t2, Negative # 如果$t3小于0,跳转到Negative标签 slli t3, t3, 1 # 如果$t3大于等于0,将其左移1位,即乘以2 sw t3, -4(t0) # 将$t3存入原存储单元中的地址 Negative: bge t3, t2, Loop # 如果$t3大于等于0,跳转回Loop标签 srai t3, t3, 1 # 如果$t3小于0,将其右移1位,即除以2 sw t3, -4(t0) # 将$t3存入原存储单元中的地址 addi t1, t1, -1 # $t1减1 bgtz t1, Loop # 如果$t1大于0,跳转回Loop标签 ``` 以上代码中,我们使用了循环和条件分支指令,以及左移和右移指令实现了将正数乘以2,负数除以2的功能。其中,blt和bge指令用于判断$t3的正负性,slli和srai指令用于进行左移和右移操作。 ### 回答2: 使用RISC-V汇编语言,将10个整数中的正数乘以2,负数除以2,并存回原存储单元之中。 首先,我们需要定义一些变量来存储地址和值。假设从x10010000开始的一段连续存储单元存储了这10个整数,我们可以使用寄存器t0来存储起始地址,使用寄存器t1来遍历每个整数,并使用寄存器t2来存储当前整数的值。 ``` .data .align 2 .text .global main main: # 设置起始地址 lui t0, %hi(numbers) addi t0, t0, %lo(numbers) # 设置循环计数器 li t1, 0 loop: # 将当前整数的值加载到t2中 lw t2, 0(t0) # 判断整数的符号,如果是正数则乘以2,是负数则除以2 bltz t2, negative slli t2, t2, 1 j store negative: srai t2, t2, 1 store: # 存回原存储单元 sw t2, 0(t0) # 移动到下一个整数的地址 addi t0, t0, 4 # 增加循环计数器 addi t1, t1, 1 # 检查是否处理完所有整数 bne t1, 10, loop # 结束程序 li a7, 10 ecall .data numbers: .word 1, 2, 3, 4, 5, -5, -6, 3, 2, 1 ``` 上述代码首先定义了起始地址为x1001 0000,并设定了一个循环计数器t1用于遍历整数。在循环中,我们首先加载当前整数的值到寄存器t2中,然后判断符号是否为正数,如果是正数则乘以2,如果是负数则除以2。最后再存回原存储单元。 ### 回答3: 在RISC-V汇编语言中,可以使用循环和条件语句来实现将10个整数中的正数乘以2,负数除以2,并将结果存回原存储单元。 首先,需要定义一个指针(如寄存器t0)指向存储整数的起始地址x1001 0000。使用_load_指令将第一个整数加载到一个寄存器(如寄存器t1),并递增指针(addi指令将t0加上4,即一个整数占用4个字节)。 接下来,可以使用条件语句if-else判断整数是正数还是负数。如果整数大于0,就将整数左移一位(sll指令)乘以2,并将结果存储回原存储单元(sw指令);如果整数小于0,将整数右移一位(sra指令)除以2,并存储回原存储单元。 然后,判断是否已经处理完10个整数。可以使用一个计数器(如寄存器t2),每处理一个整数就将计数器加1。如果计数器小于10,说明还有剩余整数需要处理,则进行下一次循环;如果计数器等于10,说明所有整数已经处理完毕,则结束循环。 最后,将整个代码段放入一个循环中,直到处理完10个整数为止。 以下是该算法的伪代码: ``` # 将存储整数的起始地址存储到寄存器t0 li t0, x10010000 # 初始化计数器 li t2, 0 loop: # 加载一个整数到寄存器t1 lw t1, 0(t0) # 增加指针 addi t0, t0, 4 # 判断整数的正负并操作 blt t1, zero, negative sll t1, t1, 1 # 正数乘以2 j store negative: sra t1, t1, 1 # 负数除以2 store: # 存储结果回原存储单元 sw t1, 0(t0) # 增加计数器 addi t2, t2, 1 # 判断是否处理完10个整数 blt t2, 10, loop # 程序结束 ``` 通过以上算法,可以将给定的10个整数中的正数乘以2,负数除以2,并将结果存回原存储单元。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

D了一天bug忘了编译

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值