В базовом наборе команд RIS C -V каждая инструкция кодируется в 32 бита. Это означает, что пространство для непосредственных операндов ограничено несколькими битами. Таким образом, чтобы получить большую константу в регистр (который с RV32G / RV64G также имеет ширину 32 или 64 бита), вам нужно разделить его и переместить части с несколькими инструкциями, то есть 2 с RV32G и до 8 с RV64G.
С 32-разрядной RIS C -V (RV32G) можно загружать более крупные константы с помощью немедленной загрузки сверху (lui
) и добавления немедленного (* 1008) *) инструкция. Непосредственный операнд lui
имеет ширину 20 бит, а addi
допускает непосредственный операнд 12 бит. Таким образом, они достаточны для загрузки констант, которые используют до 32 бит.
lui
знак расширяет его непосредственный операнд и сдвигает его влево на 12 бит и загружает результат в регистр назначения. Отсюда и его имя. addi
также расширяет свой непосредственный операнд перед добавлением.
Таким образом, для RV32G, чтобы загрузить большую константу с lui
, за которым следует addi
, нужно взять старшие 20 бит, логически сдвиньте их вправо на 12 бит, так что 12-битное смещение влево на lui
отменяется. Затем следует маскировка младших 12 бит, чтобы получить операнд для addi
.
Этого достаточно, если addi
не подписывает-расширяет свой непосредственный операнд. Если это так, поскольку старший бит установлен в 1, мы должны увеличить операнд lui
так, чтобы лишние знаковые биты снова обнулялись при сложении.
Скажем, мы обозначаем старшую часть нашей константы x
с h
, нижняя часть с l
, так как RIS C -V реализует два дополнения и арифметику c обертки при переполнении регистра, мы можем использовать модульную арифметику c, чтобы увидеть, что:
h + l = x # taking register widths into account:
=> (h + l) % 2**32 = x % 2**32 # in case l is sign extended:
=> (h + l + e + c) % 2**32 = x % 2**32 # replace e with the additional sign bits:
<=> (h + l + 4294963200 + c) % 2**32 = x % 2**32 # eliminate c:
<=> (h + l + 4294963200 + 4096) % 2**32 = x % 2**32
<=> (h + l) % 2**32 + (4294963200 + 4096) % 2**32 = x % 2**32
<=> (h + l) % 2**32 + 0 = x % 2**32
Таким образом, мы должны добавить 1 к непосредственному операнду lui
(который равен 4096 после смещения влево на 12 битов) тогда и только тогда, когда непосредственный операнд addi
является расширенным знаком.
В вашем примере сборки >>
обозначает сдвиг вправо, <<
сдвиг влево и &
логическое и. Они используются для реализации описанного расщепления и арифметики c, например, в
lui t0, (CON1>>12) + ((CON1 & 0x0800)>>11)
addi t0, t0, CON1&0xFFF
, где CON1 & 0x0800
маскирует 12-битный бит, то есть знаковый бит непосредственного операнда addi
. Если он установлен, то ((CON1 & 0x0800)>>11)
оценивается как 1 и, таким образом, удаляет лишние знаковые биты, добавленные следующей инструкцией addi
. CON1&0xFFF
маскирует младшие 12 бит.
В стандартной сборке RIS C -V всего этого утомительного управления битами можно избежать, просто используя псевдо немедленную загрузку (li
) -инструкция, например:
li t1, 6245
, которую ассемблер автоматически переводит в оптимальную последовательность команд (проверьте, например, с помощью objdump):
lui t1, 0x2
addi t1, t1,-1947
В качестве альтернативы, с GNU в качестве ассемблера также имеются директивы для разбиения операнда на верхнюю и нижнюю части:
lui a1, %hi(6245)
addi a1, a1, %lo(6245)
, что, возможно, также более читабельно, чем беспорядок в вашем фрагменте.
Это также работает с символами в GNU, как, например, :
.set CON2, 6245
li a1, 6245
lui a2, %hi(CON2)
addi a2, a2, %lo(CON2)
li a3, CON2
# => a1 == a2 == a3