Оглавление
§ LUI: Загрузка константы в регистр
37 "Load Upper Immediate"
------+-31--------------------------------------12-+-11--------7-+-6------0-+
LUI | imm[31:12] | rd | 011_0111 |
------+--------------------------------------------+-------------+----------+
Код:
regs[rd] = I & 0xFFFFF000;
Описание: из старших битов [31:12] загружается 20-битное значение в регистр номер rd. Это самая простая операция, которая просто копирует биты как есть, заполняя младшие 12 бит просто нулями. Можно сказать так, что загружается "страница" размером 4096 байт.
§ AUIPC: Добавление константы к PC
67 "Add Upper Immediate to PC"
------+-31--------------------------------------12-+-11--------7-+-6------0-+
AUIPC | imm[31:12] | rd | 110_0111 |
------+--------------------------------------------+-------------+----------+
Код:
regs[rd] = pc + (I & 0xFFFFF000);
Описание: вместо загрузки прямого значения в регистр, добавляет к текущему значению программного счетчика (PC) константу, которая формируется аналогично LUI, и сохраняет в регистр rd полученное значение. Такой метод позволяет переходить "на длинные" дистанции. В связке с JALR даёт полноценный длинный переход на 32-х битный адрес.
§ JAL: относительный переход
6F "Jump And Link"
------+-31--------------------------------------12-+-11--------7-+-6------0-+
JAL | imm[20,10:1,11,19:12] | rd | 110_1111 |
------+--------------------------------------------+-------------+----------+
Код:
regs[rd] = pc + 4
pc += SIGN(((I >> 31) << 20) | (((I >> 12) & 0xFF) << 12) | (((I >> 20) & 1) << 11) | (((I >> 21) & 0x3FF) << 1))
Описание: Относительный переход от текущего значения программного счётчика в пределах -1М..+1М, то есть, окно перехода будет 2Мб.
Макрос SIGN расширяет знак с 21-го бита до 32-х битного.
Инструкцию сложно понять только из-за того что в ней крайне неясная для восприятия архитектура константы. Реализация на верилоге будет выглядеть следующим образом.
wire [31:0] imm_jal = {{12{inst[31]}}, inst[19:12], inst[20], inst[30:21], 1'b0};
§ JALR: Косвенный переход по регистру
67 "Jump And Link Register"
------+-31------------------20-+-19---15-+-14---12-+-11--------7-+-6------0-+
JALR | imm[11:0] | rs1 | 000 | rd | 110_0111 |
------+------------------------+---------+---------+-------------+----------+
Код:
regs[rd] = pc + 4
pc = (regs[rs1] + SIGN12((I >> 20) & 0xFFF)) & ~1
Описание: Относительный переход по косвенному адресу. Берется значение из rs1 регистра, получается константа (12-бит), диапазон которой равен -2048..2047 (4к окно) и совершается переход на новый адрес, адрес же возврата (PC+4) записывается в регистр rd. Это аналог CALL, но если выбирать в качестве LINK-регистра регистр 0, то обратный адрес записан не будет. Есть одна важная деталь: младший бит PC обнуляется. Как говорят сами разработчики спецификации – это не баг, это фича.
Хинт: Чтобы нормально перейти на чистый 32-х битный адрес, используются комбинации, например:
LUI x2, 0xDEADC000
JALR x1, x2, 0xEEF
Это дает точный переход на PC=0xDEADBEEF путем сложения значения с регистром x2: DEADC000 + 0xFFFFFEEF. Так что при разработке такого рода абсолютных переходов надо делать поправку на учёт знака.
§ Bxxx: условные переходы
63 "Branch ..."
------+-31--------25-+-24---20-+-19---15-+-14---12-+-11--------7-+-6------0-+
BEQ | imm[12,10:5] | rs2 | rs1 | 000 | imm[4:1,11] | 110_0011 |
BNE | imm[12,10:5] | rs2 | rs1 | 001 | imm[4:1,11] | 110_0011 |
BLT | imm[12,10:5] | rs2 | rs1 | 100 | imm[4:1,11] | 110_0011 |
BGE | imm[12,10:5] | rs2 | rs1 | 101 | imm[4:1,11] | 110_0011 |
BLTU | imm[12,10:5] | rs2 | rs1 | 110 | imm[4:1,11] | 110_0011 |
BGEU | imm[12,10:5] | rs2 | rs1 | 111 | imm[4:1,11] | 110_0011 |
------+--------------+---------+---------+-funct3--+-------------+----------+
Код:
v1 = regs[rs1]
v2 = regs[rs2]
immba =
((inst >> 31) << 12) |
(((inst >> 7) & 0x01) << 11) |
(((inst >> 25) & 0x3F) << 5) |
(((inst >> 8) & 0x0F) << 1);
switch (funct3)
000: take = v1 == v2
001: take = v1 != v2
100: take = v1 < v2
101: take = v1 >= v2
110: take = v1 < v2
111: take = v1 >= v2
if (take) pc += SIGN13(immba)
Описание: Несмотря на большой код, работа инструкции очень проста. В процессоре RISC-V вообще нет флагов – ни Carry, ни Overflow, ни Sign. Это, казалось бы, странно, как вычислять тогда переполнения или переносы? Для этого существуют другие методы, и по сути эти методы представляют собой эмуляцию флагов. Но, несмотря на то, что флагов нет, они требуются далеко не всегда, чтобы их постоянно сохранять и проверять. Зачастую для перехода этого не нужно вовсе.
Инструкция на вход принимает 2 регистра – слева будет регистр номер rs1, а справа rs2. Они сравниваются и если сравнение было успешно, то происходит переход по относительному адресу в рамках от -4к до 4к (8к окно).
Интересное: Несмотря на то что в константе 12 бит, она вычисляется сразу с умножением на 2, потому что перейти по адресу, не кратному 2, нельзя.