LUI(load upper immediate)用于构建32位常量并使用U型格式。 LUI将U-immediate值放在目标寄存器rd的前20位,用零填充最低的12位。
我在手册中发现了这个,但如果我想将0xffffffff移动到寄存器,我需要的所有代码都是:
LUI x2, 0xfffff000
ADDI x2, x2, 0xfff
但是出现了问题,ADDI会扩展签名以立即将数据转换为签名号码,因此0xfff
将扩展为0xffffffff
。
它x2
到0xffffefff
但不是0xffffffff
什么是将32位立即移位到寄存器的好实现?
答案 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, 0xFFFFFFFF
为addi 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位立即数。 lui
和addi
可分别用于加载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
伪指令 在任何情况下,您始终可以使用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 重定位以查找出实际的目标符号以及相对引用的位置。)