Home > Blockchain >  Why can't I load a 64bit properly into a register?
Why can't I load a 64bit properly into a register?

Time:09-15

I'm trying to load a 64 bit into a register so I split it into two 32 bits and then combine it together at the end.

;Use lui and addi to store 0x1234587811223344 in x5

c:  EQU     0x1234587811223344
    lui     x6, (c & 0xffffffff) >> 12
    addi    x6, x6, c & 0xfff

    lui     x7, c >> 44
    addi    x7, x7, (c & 0xfff00000000) >> 32 
    slli    x7, x7, 32

    or  x5, x6, x7

So the first part works fine. It loads 0x11223000 into x6 and then adds the last 3 to become 0x11223344.

The second part is where it doesn't seem to for some reason. It loads 0x12345000 and then when it should add the last 3 of that 32bit sequence it changes the whole thing to 0x12344878 instead of 0x12345878.

Is there some error with the way I'm going about this?

CodePudding user response:

RISC V uses sign extension on all 12-bit immediates.

Various tools will accept or give different error messages when attempting to load a constant that doesn't fit in signed 12 bits, and, this can vary depending on the syntax used, e.g. hex vs. decimal notation.

It seems your assembler is allowing the value and then creating the instruction addi x7, x7, 0x878.  However during execution, the processor is going to internally create constant 0xffffffff878 and add that to the register, which of course, will have the effect of subtracting 1 from the upper 20 bits.

If you want to see this more up front in single step debugging, use addi x8, x0, 0x878, and you'll see the sign extension, then maybe followed by add x7, x7, x8 where you'll more explicitly see the effect of the addition on the upper portion.

Btw, breaking down an line of code into smaller pieces is a good tool to have in your debugging repertoire — here separating the forming of the immediate from the addition to something else.


Some assembler support li to form constants & la to form an address from labels — these are pseudo instructions that can expand into two actual machine instructions, while others support %hi() and %lo() functions.

In both cases, the assembler sees the full constant value, and what it does for us is, if the 12th-bit of the constant is set it will adjust the upper (20-bit) constant by 1 to mitigate the sign extension that will have a -1 effect on that upper part when both parts are used together.

Apparently, since you are using the individual instructions with hard-coded constants, you will need to do this adjustment yourself.

I would have preferred the assembler to give an error or at least a warning that the constant 0x878 doesn't fit in 12-bits signed...


MIPS uses 16-bit immediates, both for lui and for ori and addi.  However, ori uses zero extension, whereas addi uses sign extension.

So, you might think that the best approach would be to use lui with ori to avoid the sign extension and -1 offseting that then requires the 1 in the lui.  However, in programming we often want a sequence like lui followed by lw, which, between the two of them, also forms a 32-bit constant (while also accessing memory).  Yet, since lw performs sign extension on the immediate just like addi, the community (assemblers & linkers) have provided the 1 offsetting and chosen not to then also create a 2nd mechanism (that doesn't 1 the upper immediate) for use with ori and zero extending immediates.

  • Related