RISC-V使用LUI和ADDI构建32位常量

时间:2018-06-07 13:24:01

标签: riscv

  

LUI(load upper immediate)用于构建32位常量并使用U型格式。 LUI将U-immediate值放在目标寄存器rd的前20位,用零填充最低的12位。

我在手册中发现了这个,但如果我想将0xffffffff移动到寄存器,我需要的所有代码都是:

LUI x2, 0xfffff000
ADDI x2, x2, 0xfff

但是出现了问题,ADDI会扩展签名以立即将数据转换为签名号码,因此0xfff将扩展为0xffffffff

x20xffffefff但不是0xffffffff

什么是将32位立即移位到寄存器的好实现?

4 个答案:

答案 0 :(得分:5)

RISC-V汇编程序支持伪指令li x2, 0xFFFFFFFF

N是带符号的2&#32补码整数。

li x2,N的常见案例实现是:

    # sign extend low 12 bits
    M=(N << 20) >> 20

    # Upper 20 bits
    K=((N-M) >> 12) <<12

    # Load upper 20 bits
    LUI x2,K

    # Add lower bits
    ADDI x2,x2,M

当然,加载短暂的li可以使用

   addi x2,x0,imm

因此,li x2, 0xFFFFFFFFaddi x2,x0,-1

答案 1 :(得分:3)

我打算说&#34;使用ORI代替ADDI&#34;但后来我阅读了指令集手册,结果发现它也不起作用,因为即使是逻辑运算,所有低12的立即数操作数都会进行符号扩展。

AFAICT您必须以预期用于设置低12位的指令的效果的方式偏置您放入高20位的值。因此,如果您希望在前20位中得到值X,并且您将使用ADDI设置低12位,那么低12位在最左侧位置有1,您必须LUI (X+1)而不是LUI X。类似地,如果您要使用XORI设置低12位,而低12位在最左侧位置有1,则必须执行LUI (~X)(即X的按位反转)而不是LUI X

但是在你做任何这些之前,我都要看看你的汇编程序是否已经有某种“立即加载”#34;伪操作或宏将为您处理此问题。如果它没有,那么看看你是否可以写一个: - )

RISC处理器需要程序员(或者更常见的是来自编译器)的这种额外努力并不罕见。这个想法是&#34;保持硬件简单,以便它可以快速进行,如果这使得构建软件变得更加困难并不重要。

答案 2 :(得分:0)

TL; DR:要加载到x2中的32位常量是0xffffffff,它对应于 -1 。由于 -1 的范围为 [-2048,2047] ,因此可以用一条指令addi x2, zero, -1加载此常量。您还可以使用li伪指令:li, x2, -1,汇编程序将其翻译为addi x2, zero, -1

使用lui + addi序列加载32位常量

通常,我们需要一个lui + addi序列(两条指令)来将32位常量加载到寄存器中。 lui指令编码20位立即数,而addi指令编码12位立即数。 luiaddi可分别用于加载32位常量的高20位和低12位。

N 是我们要加载到寄存器中的32位常量: N≡n 31 ... n 0 。然后,我们可以将该常量拆分为高20位和低12位, N U N L 分别为: N U ≡n 31 ... n 12 N L ≡n 11 ... n 0

原则上,我们在lui N L 的立即数中编码 N U addi中的立即数。但是,如果addi中12位立即数的最高有效位是 1 ,则很难处理,因为在{{ 1}}指令 符号扩展 到32位。在这种情况下,addi指令不是 N L ,而是 N L -4096 代替- -4096 (或-2 12 )是当高20位为 1时的结果数 s和低12位是 0 s。

为补偿不必要的术语 -4096 ,我们可以在addi的立即数中添加 1 – {{1}中的立即数的LSB }对应于#12 位–因此,在此立即数中添加 1 会导致在目标寄存器中添加 4096 ,从而抵消了 -4096 术语。

使用一条lui指令加载32位常量

上面解释的问题是由于lui中的立即数经历了符号扩展。扩展addi立即开始的符号决定可能是允许加载 -2048 2047 之间的小整数-整数/ em>(包括两者)-包含一条addi指令。例如,如果addi中的立即数是零扩展而不是符号扩展,则不可能将诸如 -1 这样的频繁常数加载到仅需一条指令的寄存器。


使用addi 伪指令

加载32位常量

在任何情况下,您始终可以使用addi伪指令来加载32位常量,而不必担心要加载的常量的值是多少。该伪指令可以将任何32位数字加载到寄存器中,因此,与手动编写li + li序列相比,它更易于使用且出错率更低。

如果数字适合lui的立即数字段( [-2048,2047] ),则汇编程序会将addi伪指令转换为{{1 }}指令,否则,addi将被翻译成li + addi序列,并且上面解释的复杂性将由汇编器自动处理。

答案 3 :(得分:0)

在实践中,如果可能,只需使用 li 伪指令让汇编器优化为一条指令(单个 lui 或单个 addi),如果没有,则为您进行数学计算。

   li    t0, 0x12345678
   li    t1, 123
   li    t2, -1
   li    t3, 0xffffffff    # same as -1 in 32-bit 2's complement
   li    t4, 1<<17

我用空格分隔了每个“组”。只有第一个(进入 t0)需要两条指令。

$ clang -c -target riscv32 rv.s         # on my x86-64 Arch GNU/Linux desktop
$ llvm-objdump -d rv.o
...
00000000 <.text>:
       0: 01 00         nop
       2: 01 00         nop

       4: b7 52 34 12   lui     t0, 74565
       8: 93 82 82 67   addi    t0, t0, 1656

       c: 13 03 b0 07   addi    t1, zero, 123

      10: fd 53         addi    t2, zero, -1

      12: 7d 5e         addi    t3, zero, -1

      14: b7 0e 02 00   lui     t4, 32

如果您想手动完成,大多数 RISC-V(或至少 GAS / clang)的汇编器都有 %lo%hi“宏”,所以你可以lui dst, %hi(value) / addi dst, dst, %lo(value)

   lui   x9, %hi(0x12345678)
   addi  x9, x9, %lo(0x12345678)

   lui   x10, %hi(0xFFFFFFFF)
   addi  x10, x10, %lo(0xFFFFFFFF)

用clang组装,再用llvm-objdump反汇编:

      18: b7 54 34 12   lui     s1, 74565
      1c: 93 84 84 67   addi    s1, s1, 1656

      20: 37 05 00 00   lui     a0, 0
      24: 7d 15         addi    a0, a0, -1

请注意,lui a0, 0 是对指令的愚蠢浪费,这是由于天真地在 0xffffffff 上使用 hi/lo 而没有意识到整个事情适合符号扩展的 12 位立即数。


手动 %hi/%lo 有很好的用例,尤其是地址,您有一个对齐的“锚”点,然后想要加载或存储到某个标签:< /p>

   lui   t0, %hi(symbol)

   lw    t1, %lo(symbol)(t0)
   lw    t2, %lo(symbol2)(t0)
   addi  t3, t0, %lo(symbol3)   # also put an address in a register
   ...
   sw    t1, %lo(symbol)(t0)

因此,不要浪费指令为每个符号做一个单独的 lui,如果你知道它们在同一个 2k 对齐的块中,你可以在汇编程序的帮助下相对于一个基数引用它们。或者实际上是一个 4k 对齐的块,中间有“锚点”,因为 %lo 可以是负数。

带有 auipc 的 PC 相关版本同样高效,但看起来有点不同:What do %pcrel_hi and %pcrel_lo actually do? - %pcrel_lo 实际上引用了 %pcrel_hi 重定位以查找出实际的目标符号以及相对引用的位置。)