Эта Лекция раскрывает основы непривилегированных целочисленных инструкций архитектуры RISC-V и содержит базовую информацию для написания программ на языке Assembler.
После освоения этой лекции вы должны быть способны:
Набор непривилегированных инструкций архитектуры RISC-V описывает четыре базовые набора инструкций:
Числа 32, 64, и 128 характеризуют размер регистра и адресного пространства. Например, 32-х битный регистр может хранить данные размером до 2^32 бит. Набор инструкций RV32I содержит всего 40 инструкций.
Любой процессор RISC-V поддерживает как минимум один базовый набор инструкций. Например, обозначение RV32I означает, что процессор поддерживает 32-х битный целочисленный набор команд. Распространены дальнейшие расширения базового набора инструкций. Тип расширения, который поддерживает процессор, указывается в качестве суффикса к базовой аббревиатуре набора инструкций. Распространены следующие расширения (таблица 3.1):
| Сокращение | Стандартное расширение |
|---|---|
| M | целочисленные умножение и деление integer multiplication and division |
| A | атомарные инструкции atomic instructions |
| F | числа с плавающей запятой одинарной точности single-precision floating point |
| D | числа с плавающей запятой двойной точности double-precision floating point |
| Q | числа с плавающей запятой четверной точности quad-precision floating point |
| C | сжатые инструкции compressed instructions |
| V | векторные операции vector operations |
Примеры:
Базовый набор инструкций определяет наличие 32 регистров и программного счётчика. Регистры именуются от x0 до x31. Размер регистра соответствует размеру базовых инструкций, например, для процессора RV32I размер каждого из регистров будет составлять 32 бита. Регистр x0 хранит значение 0, которое нельзя поменять.
Расширения предоставляют большее число регистров. Например, расширения, связанные с вычислениями с плавающей запятой, предоставляют дополнительные регистры работы с дробной арифметикой.
Использование регистров языками более высокого уровня, чем Assembler, определяется бинарным интерфейсом для приложений (application binary interface, ABI). Этот интерфейс определяет то, как регистры должны использоваться в процессе трансляции программы компилятором из описания на высокоуровневом языке в машинный код. ABI предписывает использование дополнительных имён регистров и их назначение ( Табл.3.2):
| Регистр | Имя ABI | Описание |
|---|---|---|
| x0 | zero | Константный ноль Zero constant |
| x1 | ra | Адрес возврата Return address |
| x2 | sp | Указатель на область стека Stack pointer |
| x3 | gp | Глобальный указатель Global pointer |
| x4 | tp | Указатель потока Thread pointer |
| x5-x7 | t0-t2 | Временные значения Temporaries |
| x8 | s0 / fp | Сохраняемый регистр / Указатель на фрейм Saved / Frame pointer |
| x9 | s1 | Сохраняемый регистр Saved register |
| x10-x11 | a0-a1 | Аргументы функций и возвращаемые значения Function args. / return values |
| x12-x17 | a2-a7 | Аргументы функций Function arguments |
| x18-x27 | s2-s11 | Сохраняемые регистры Saved registers |
| x28-x31 | t3-t6 | Временные значения Temporaries |
| Pc | - | Программный счётчик Program counter |
Архитектура RISC-V предполагает выполнение арифметических операций через регистры: для модификации данных, находящихся в памяти, необходимо загрузить данные из памяти в один из регистров, выполнить операцию над данными, и переместить результат из регистра обратно в память.
Для понимания принципа использования инструкций, рассмотрим простой пример, в котором используется только одна инструкция: addi rd, rs1, imm, которая добавляет константное целое imm (в диапазоне 12-ти битного знакового: -2048…2047) к содержимому регистра rs1 и записывает результат в регистр rd. Другими словами выполняется операция rd = rs1 + imm. После получения инструкции программный счётчик инкрементируется и будет содержать адрес следующей инструкции, что можно условно записать как pc = cp + 4, поскольку размер инструкции в машинном коде равен 4 байтам.
Инструкция addi x1, x0, 1 добавляет число 1 к значению 0, которое хранится в регистре x0 (в регистре x0 всегда хранится значение 0) и пишет результат, равный 1, в регистр x1. Машинный код инструкции addi x1, x0, 1 - это (в шестнадцатеричном представлении) 0x00100093. Префикс 0x перед числом показывает, что число задано в шестнадцатеричной системе счисления. Далее мы увидим, как именно работает кодирование чисел. По аналогии, инструкция addi x1, x1, 1 добавляет значение 1 к содержимому регистра x1 и записывает результат обратно в регистр x1. Машинный код этой инструкции - 0x00108093.
Теперь представим, что эти машинные коды располагаются в памяти начиная с адреса 0x0. Поскольку каждая инструкция занимает 4 байта, первая инструкция располагается по адресу 0x0, а вторая - по адресу 0x4. Программный счётчик указывает на начало исполняемого кода, следовательно, он равен значению 0x0:
Адрес Машинный код Инструкции 0x0 0x00100093 addi x1, x0, 1 0x4 0x00108093 addi x1, x1, 1
После запуска этих двух строк кода микропроцессор начинает выполнение с адреса 0x0. Как уже было объяснено выше, в результате выполнения первой строки в регистре x1 будет получено число 1, при этом программный счётчик будет инкрементирован до значения 0x4. Выполнение второй строки приведёт к увеличению значения регистра x1 на 1. Так как его значение было равно 1 в результате предыдущей операции, регистр после выполнения второй строки примет значение, равное 2.
На рис.3.1 показан пример того, как этот исходный код запускается в симуляторе Ripes.
(рис 3.1) Запуск рассмотренного примера в симуляторе Ripes
Базовые целочисленные инструкции в микропроцессорах архитектуры RISC-V разделены на пять типов, каждый из которых имеет свой тип кодирования. Типы соответствуют задачам, выполняемым машинной инструкцией: R-type (register, регистр), I-type (immediate, непосредственное значение), S-type (store, хранение), B-type (branch, ветвление), U-type (upper immediate, верхнее непосредственное значение), и J-type (jump, переход). Таблица 3.3 ниже показывает принцип кодирования:
Рассмотрим предыдущий пример. Машинный код инструкции addi x1, x0, 1 - это значение 0x00100093. Побайтное представление кода может быть получено из таблицы выше, зная, что код операции равен 0x13, значение funct3 равно 0x0, и что код типа I (целочисленная операция).
Таким образом, машинный код инструкции addi x1, x0, 1 в побитовом представлении формируется следующим образом:
Поскольку каждые 4 бита могут быть объединены в шестнадцатеричное число, машинный код получается равным 0x00100093.
Приведённая ниже таблица 3.4 содержит инструкции, которые используют непосредственные данные. Программный счётчик обновляется после выполнения каждой инструкции и не приводится.
| Инструкция | Тип | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| addi | ADD Immediate | I | 0010011 | 0x0 | rd = rs1 + imm |
| xori | XOR Immediate | I | 0010011 | 0x4 | rd = rs1 ^ imm |
| ori | OR Immediate | I | 0010011 | 0x4 | rd = rs1 | imm |
| andi | AND Immediate | I | 0010011 | 0x7 | rd = rs1 imm |
| slli | Shift Left Logical Imm. | I | 0010011 | 0x1 | imm[11:5]=0x00, rd = rs1 << imm[4:0] |
| srli | Shift Right Logical Imm. | I | 0010011 | 0x5 | imm[11:5]=0x00, rd = rs1 << imm[4:0] |
| srai | Shift Right Arith. Imm. | I | 0010011 | 0x5 | imm[11:5]=0x20, rd = rs1 >> imm[4:0] |
| slti | Set Less Than Imm. | I | 0010011 | 0x2 | rd = (rs1 < imm)? 0:1 |
| sltiu | Set Less Than Imm. Un. | I | 0010011 | 0x3 | rd = (rs1 < imm)? 0:1 |
Как уже было показано выше, инструкции этого типа берут непосредственное значение imm, в качестве источника используется регистр rs1, в качестве регистра назначения - rd.
Поскольку инструкция addi rd, rs1, imm использует знаковое непосредственное значение imm, она может сработать как для сложения (если imm меняется от 0 до 2047), так и для вычитания (imm меняется от -2048 до -1).
Инструкции xori, ori и andi rd, rs1, imm выполняют побитовые операции xor (исключающее или), or (логическое сложение) и and (логическое умножение). Например, пусть в регистре x1 лежит значение 0x9, которому в побитовом представлении соответствует число 0b1001, тогда инструкция xori x1, x1, 0x3 выполнит побитовое исключающее или над содержимым регистра x1 и числом 0x3, что можно представить следующим образом:
x1 = x1 xor 0x3 = 0b1001 xor 0b0011 = 0b1010 = 0xa
Инструкции slli, srli, и srai выполняют операции побитового сдвига с использованием непосредственного значения, задающего шаг сдвига. В качестве такого непосредственного значения может выступать положительное пятибитное число, представляющее значения от 0 до 31.
Код операции логического (побитового) сдвига влево синтаксически описывается следующим образом: slli rd, rs1, imm, и в процессе выполнения сдвигает каждый бит регистра, задаваемого как rs1, на imm позиций влево. Результат при этом записывается в регистр rd. При каждом сдвиге на 1 бит младший значащий бит устанавливается в 0, а значение старшего значащего бита теряется.
По аналогии с этим выполняется команда побитового сдвига вправо: srli rd, rs1, imm. Значение регистра rs1 побитового сдвигается вправо, результат записывается в регистр, задаваемый как rd. При этом в старший значащий бит при каждом сдвиге записывается значение 0, а значение младшего значащего бита теряется.
Результат выполнения команды srai в плане заполнение старшего бита отличается для арифметического сдвига вправо для команды srai rd, rs1, imm. Информация о старшем значащем бите для этой операции сохраняется. Если до сдвига он был равен 0, старший бит заполняется 0 после сдвига. Если он был равен 1, он заполняется 1. Такой сдвиг является знакорасширяющим для значений в rs1, которые записаны в дополнительном коде.
Рассмотрим пример. Пусть значение регистра x1 равно 0x9, двоичное представление которого записывается как 0b1001. Тогда в результате выполнения инструкции slli x1, x1, 0x2 выполнится логический сдвиг содержимого регистра x1, который можно описать следующим кодом:
x1 = x1 << 0x2 = 0b1001 << 2 = 0b100100 = 0x24
Оставшиеся две инструкции slti rd, rs1, imm и sltiu rd, rs1, imm сравнивают содержимое регистра-источника с непосредственным значением и устанавливают значение регистра-приёмника в 0 в случае, если значение в регистре-источнике меньше непосредственного значения imm. Отличие между этими двумя командами в том, что команда slti использует значения в дополнительном коде и сравнение происходит со знаком.
Следующая категория R-формата включает в себя арифметические и логические операции, которые используют три регистра: два регистра-источника и регистр-приёмник. Большинство этих инструкций по своему действию аналогичны рассмотренным выше. К этим инструкциям относятся (табл.3.5):
| Инструкция | Тип | Формат | Код | funct3 | funct7 | Описание |
|---|---|---|---|---|---|---|
| Add | ADD | R | 0110011 | 0x0 | 0x00 | rd = rs1 + rs2 |
| Sub | SUB | R | 0110011 | 0x0 | 0x20 | rd = rs1 - rs2 |
| Xor | XOR | R | 0110011 | 0x4 | 0x00 | rd = rs1 ^ rs2 |
| or | OR | R | 0110011 | 0x6 | 0x00 | rd = rs1 | rs2 |
| and | AND | R | 0110011 | 0x7 | 0x00 | rd = rs1 rs2 |
| sll | Shift Left Logical | R | 0110011 | 0x1 | 0x00 | rd = rs1 << rs2 |
| srl | Shift Right Logical | R | 0110011 | 0x5 | 0x00 | rd = rs1 >> rs2 |
| sra | Set Right Arith. | R | 0110011 | 0x5 | 0x20 | rd = rs1 >> rs2 |
| slt | Set Less Than | R | 0110011 | 0x2 | 0x00 | Rd = (rs1 < rs2)? 0:1 |
| sltu | Set Less Than Un. | R | 0110011 | 0x3 | 0x00 | rd = (rs1 < rs2)? 0:1 |
Рассмотрим дополнение предыдущего примера с учётом новых инструкций:
Адрес Машинный код Инструкции 0x0 0x00100093 addi x1, x0, 1 0x4 0x00108093 addi x1, x1, 1 0x8 0x00108133 add x2, x1, x1
В последней строке примера содержимое регистра x1 добавляется к самому себе, результат записывается в регистр x2. В результате выполнения всех операций в регистре x2 содержится число 4.
Все эти инструкции в отдельном рассмотрении не нуждаются, поскольку являются функциональными аналогами операций из предыдущего раздела. Исключением является инструкция sub, но, как нам кажется, её функциональность является очевидной и в рассмотрении не нуждается.
Для того, чтобы получить доступ к памяти, реализован ряд инструкций, которые относятся к I-формату и предназначены для загрузки данных из памяти в регистры, а также ряд инструкций, относящихся к S-формату (S-Format, Save) для сохранения данных из регистров в память. Инструкции указаны в таблице 3.6:
| Инструкция | Имя | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| lb | Load Byte | I | 0000011 | 0x0 | rd = M[rs1+imm][7:0] |
| lh | Load Half | I | 0000011 | 0x1 | rd = M[rs1+imm][15:0] |
| lw | Load Word | I | 0000011 | 0x2 | rd = M[rs1+imm][31:0] |
| lbu | Load Byte Un. | I | 0000011 | 0x0 | Rd = M[rs1+imm][7:0] |
| lhu | Load Half Un. | I | 0010011 | 0x0 | rd = M[rs1+imm][15:0] |
| sb | Store Byte | I | 0010011 | 0x0 | M[rs1+imm][7:0] = rs2[7:0] |
| sh | Store Half | I | 0100011 | 0x1 | M[rs1+imm][15:0] = rs2[15:0] |
| sw | Store Word | I | 0100011 | 0x2 | M[rs1+imm][31:0] = rs2[31:0] |
Рассмотрим следующий пример. Предполагается, что в памяти по адресу 0x10000000находится значение 0x02.
Адрес Машинный код Значение 0x0 0x00100093 addi x1, x0, 0x1 0x4 0x01c09093 slli x1, x1, 28 0x8 0x00008103 lb x2, 0(x1)
Первые две инструкции записывают в регистр x1 значение 0x10000000. Для этого в регистр x1 записывается число 1, после чего сдвигается влево на 28 позиций, в результате чего в регистр x1 формируется значение 0x10000000. Третья инструкция загружает байт из памяти по адресу, задаваемому регистром x1.
Синтаксис вызова этой команды следующий: lb rd, imm(rs1). Это можно развернуть в следующее представление: rd = M[rs1+imm][7:0]. Значение одного байта, задаваемого конструкций bits[7:0], загружается в регистра, определяемый как rd (register destination) из памяти по адресу, взятому из регистра, определяемого как rs1 (Register Source), к которому добавляется непосредственное значение смещения imm.
Из приведённого примера видно, что задавать адрес в регистре с использованием описанных инструкций довольно неудобно. Для исправления этого был разработан ряд инструкций U-формата (U-Format, Upper) (Табл.3.7):
| Инструкция | Имя | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| lui | Load Upper Imm. | U | 0110111 | - | rd = imm << 12 |
| auipc | Add Upper Imm. to PC | U | 0010111 | - | rd = PC + (imm << 12) |
Инструкция lui rd, imm задаёт значение регистра, определяемого как rd, равным непосредственному значению imm, сдвинутому на 12 бит влево. А инструкция auipc rd, imm устанавливает значение регистра, определяемого как rd, в результат сложения значения регистра программного счётчика PC и непосредственного значения imm, сдвинутого влево на 12 бит.
Разберём следующий пример:
Адрес Машинный код Значение Комментарий 0x0 0x100000b7 lui x1, 0x10000 Загружает 0x10000000 в регистр x1 0x4 0x10000117 auipc x2, 0x10000 Загружает 0x10000004 в регистр x2
Инструкция lui x1, 0x10000 задаёт регистру x1 значение 0x10000 << 12, таким образом, x1 = 0x10000000, поскольку сдвиг влево на 12 бит "добавляет" три нуля в шестнадцатеричном представлении. Инструкция auipc x2, 0x10000 устанавливает содержимое регистра x2 равным сумме текущего значения PC (счётчика команд), которое в данный момент равно 0x04 с прибавленным значением 0x10000 << 12. Таким образом, значение регистра x2 после выполнения этого кода будет равно 0x10000004.
Каждая из предыдущих рассмотренных инструкций устанавливает программный счётчик PC на адрес следующей инструкции, расположенной в памяти: pc = pc + 4. Для реализации возможности контроля за процессом исполнения используются инструкции B-формата (B-Format, Branching), которые реализуют механизм ветвления. Они используются для условного перехода к инструкции, адрес которой указывается непосредственно в составе соответствующих команд в случае, если условие выполняется (Табл.3.8):
| Инструкция | Тип | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| beq | Branch == | B | 1100011 | 0x0 | if (rs1 == rs2) pc+=imm |
| bne | Branch != | B | 1100011 | 0x1 | If (rs1 != rs2) pc+=imm |
| blt | Branch < | B | 1100011 | 0x4 | if (rs1 < rs2) pc+=imm |
| bge | Branch >= | B | 1100011 | 0x5 | if (rs1 >= rs2) pc+=imm |
| bltu | Branch < Un. | B | 1100011 | 0x6 | if (rs1 < rs2) pc+=imm |
| bleu | Branch >= Un. | B | 1100011 | 0x7 | if (rs1 >= rs2) pc+=imm |
Инструкция ветвления сравнивает значения регистров rs1 и rs2 способом, определяемым самой инструкцией. Если условие истинно, программный счётчик увеличивается на непосредственное значение imm. В выражении blt rs1, rs2, imm проверяется, что rs1 меньше, чем rs2, и если это так, программный счётчик PC изменяется следующим образом: pc = pc + imm. В противном случае он принимает значение адреса следующей инструкции в памяти (pc = pc +4).
Инструкции blt и bge учитывают знак числа, инструкции bltu и bleu - нет, воспринимают числа как беззнаковые.
Рассмотрим пример:
Адрес Машинный код Значение Комментарий 0x00 0xffb00093 addi x1, x0, -5 x1 = -5 0x04 0x00500113 addi x2, x0, 5 x2 = 5 0x08 0x0020c463 blt x1, x2, 0x8 if (x1 < x2) pc = pc + 8 0x0c 0x00100193 addi x3, x0, 1 skipped if (x1 < x2): x3 = 1 0x10 0x00200193 addi x3, x0, 2 x3 = 2
Инструкция по адресу 0x08 проверяет условие, что x1 < x2, то есть -5 < 5. Поскольку выражение истинно, программный счётчик устанавливается в значение 0x10 и инструкция по адресу 0x0c пропускается (не выполняется). Если бы инструкция по адресу 0x08 была bltu, отрицательное значение, хранящиеся в регистре x1 (-5) интерпретировалось бы как положительное значение, равное 0xFFFFFFFB (представление положительного числа, в двоичном коде идентичного отрицательному числу -5), а значит условие бы не выполнилось.
Помимо инструкций для условного перехода, есть инструкции для безусловного перехода: jalr и jal. Эти инструкции позволяют переходить на требуемый адрес путём модификации программного счётчика PC, однако при этом сохраняют адрес возврата, который равен PC+4 на момент вызова инструкции. Такой подход позволяет вернуться в то место, откуда произошёл переход. Именно так реализуется модульность разрабатываемых программ.
Инструкции записываются в разных форматах (Табл.3.9):
| Инструкция | Тип | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| jal | Jump and Link | J | 1101111 | - | rd = PC+4; PC += imm |
| jalr | Jump and Link Register | I | 1100111 | 0x0 | rd = PC+4; PC = rs + imm |
Инструкция jal rd, imm добавляет непосредственное значение к программному счётчику PC, PC += imm, и пишет адрес инструкции, следующей за jal в регистр, задаваемый как rd. Инструкция jalr rd, rs, imm устанавливает программный счётчик в значение, равное результату сложения регистра, задаваемого как rs, и непосредственного значения imm, pc = rs + imm, и пишет адрес возврата в регистр, задаваемый как rd.
Рассмотрим пример:
Адрес Машинный код Значение Комментарий
0x00 0x00c000ef jal x1,0xc set pc to 0x0 + 0xc = 0xc,
x1 = pc + 4 = 0x4
0x04 0x00000013 addi x0, x0, 0 no operation
0x08 0x00010093 addi x1, x2, 0 set x1 = x2
0x0c 0x00008167 jalr x2, x1, 0 set pc to x1,
x2 = pc + 4 = 0x10
0x10 0x00100093 addi x1, x0, 1 x1 = 1
Программа работает следующим образом. Она начинается с инструкции jal x1, 0xc, которая расположена по адресу 0x00. В результате своего выполнения адрес возврата пишется в регистр x1 (следующая инструкция за инструкцией по адресу 0x00 размещается по адресу 0x04, именно это число помещается в регистр x1) и осуществляется переход по адресу 0x0c. По адресу 0x0c размещается инструкция jalr x2, x1, 0, которая пишет уже свой адрес возврата (значение 0x10) в регистр x2 и осуществляет переход по адресу, который лежит в регистре x1 (там размещается адрес возврата для первого выполненного перехода, это адрес 0x04). Программа продолжает выполняться и исполняется инструкция, расположенная по адресу 0x04, которая ничего не делает. Следующая за ней инструкция, расположенная по адресу 0x08) записывает значение 0x10 (адрес возврата для команды jalr) в регистр x1. После этого, вторая итерация выполнения команды jalr по адресу 0xc передаёт управление по адресу 0x10 (значение регистра x1). Итого, программный счётчик PC последовательного принимает следующие значения: 0x00, 0x0c, 0x04, 0x08, 0x0c, и 0x10.
Две инструкции, ecall и ebreak задаются в I-формате (I-format, immediate), и обе используются для доступа к системным функциям. Системные функции обычно требуют привилегированного доступа и наличия инструкций, которые лежат вне пользовательского режима доступа.
| Инструкция | Тип | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| ecall | Environment Call | I | 1110011 | 0x0 | imm = 0, rd = rs1 = 0, передать управление системе |
| ebreak | Environment Break | I | 1110011 | 0x0 | imm = 1, rd = rs1 = 0, передать управление отладчику |
Инструкция ecall запрашивает системную сервисную операцию, так называемый системный вызов. Согласно бинарному интерфейсу для приложений ABI, идентификатор и дополнительные параметры предоставляются через предварительно настроенные регистры, при этом значение регистра x17 задаёт тип системного вызова. В следующем примере показан вызов функции выхода в ОС Linux. Системный вызов exit говорит ОС Linux о том, что ОС должна закончить выполнение программы и выгрузить программу из памяти.
Адрес Машинный код Значение Комментарий
0x00 0x05d00893 addi x17, x0, 93 set x17 = 93,
Linux system call exit
0x04 0x00000073 ecall call system
Инструкция ebreak используется отладчиком для отладки программ. Отладчик временно подменяет инструкцией ebreak код в том месте, где программа должна остановиться. Ситуация, когда вызов инструкции ebreak реализуется программистом вручную, нетипична.
Наконец, инструкция fence используется для синхронизации доступа к памяти между различными процессорами, использующими общую область памяти. Инструкция fence разделяет программный код на код, идущий до неё (предшествующий набор, predecessor set) и инструкции после неё (последующий набор, successor set). Команда полезна для систем, использующих более одного процессорного ядра RISC-V.
Инструкция fence заботится о том, чтобы каждая инструкция из предшествующего ей набора была выполнена исполняющим ядром (и так для каждого ядра) перед тем, как будет выполнена первая инструкция из последующего за ней набора. Инструкция fence очень важна для реализации синхронизации процессов в операционных системах, в том числе в таких механизмах, как механизмы блокировки. Однако эти механизмы выходят за пределы рассматриваемого курса и упоминаются лишь для полноты картины. Обычная инструкция fence имеет следующий I-формат (Табл.3.11):
| Инструкция | Тип | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| fence | Fence | I | 0001111 | 0x0 | rd, rs1 зарезервированы. Для всех типов доступа к памяти imm = 0b000011111111. |
В качестве примера расширений, рассмотрим M-расширение, реализующее операции умножения и деления. Инструкции приведены в таблице 3.12:
| Инструкция | Тип | Формат | Код | funct3 | funct7 | instruction |
|---|---|---|---|---|---|---|
| mul | Multiply | R | 0110011 | 0x0 | 0x01 | rd = (rs1 * rs2)[31:0] |
| mulh | Multiply High | R | 0110011 | 0x1 | 0x01 | rd = (rs1 * rs2)[63:32] |
| mulsu | Multiply High Sign/Uns. | R | 0110011 | 0x3 | 0x01 | rd = (rs1 * rs2)[63:32] |
| mulu | Multiply Unsigned | R | 0110011 | 0x3 | 0x01 | rd = (rs1 * rs2)[63:32] |
| div | Divide | R | 0110011 | 0x4 | 0x01 | rd = rs1 / rs2 |
| divu | Divide Unsigned | R | 0110011 | 0x5 | 0x01 | rd = rs1 / rs2 |
| rem | Remainder | R | 0110011 | 0x6 | 0x01 | rd = rs1 % rs2 |
| remu | Remainder Unsigned | R | 0110011 | 0x7 | 0x01 | rd = rs1 % rs2 |
Поскольку перемножение двух (потенциально больших) чисел может превысить размер регистра (для RV32 этот размер составляет 32 бита), младшая часть результата умножения возвращается с использованием инструкции mul, а старшая часть - с использованием инструкций mulh, mulsu, mulu в зависимости от того, какая комбинация чисел в плане знака используется. Например:
Адрес Машинный код Значение Комментарий
0x00 0x000100b7 lui x1, 0x10 x1 = 0x00010000
0x04 0x00108093 addi x1, x1, 1 x1 = 0x00010001
0x08 0x00080137 lui x2, 0x80 x2 = 0x00080000
0x0c 0x022081b3 mul x3, x1, x2 x3 = 0x00080000,
lower 32 bits of mult.
0x10 0x02209233 mulh x4, x1, x2 x4 = 0x00000008,
higher 32 bits of mult.
Деление на нуль, результат которого не определён, записывает в регистр-приёмник значение -1 (или, другими словами, выставляет все биты в 1). Это значит, что перед выполнением операции деления программисту необходимо самостоятельно проверять делитель и использовать операторы ветвления для обработки исключительных ситуаций.
В целом, для архитектуры RV64 набор инструкций такой же, как и для архитектуры RV32. Однако для RV64 регистры имеют размер в 64 бита, а не в 32, и операции выполняются над 64-битными числами. Более того, для RV64 реализуется ряд инструкций, специфичных именно для 64-битной архитектуры, такие инструкции имеют в конце букву 'w'. Такие инструкции работают только с 32-битными числами, причём используются младшие 32 бита то 64-битных чисел. Примерами таких инструкций являются addiw, addw, sllw, mulw.
Эта Лекция раскрывает основы непривилегированных целочисленных инструкций архитектуры RISC-V и содержит базовую информацию для написания программ на языке Assembler.
После освоения этой лекции вы должны быть способны:
Набор непривилегированных инструкций архитектуры RISC-V описывает четыре базовые набора инструкций:
Числа 32, 64, и 128 характеризуют размер регистра и адресного пространства. Например, 32-х битный регистр может хранить данные размером до 2^32 бит. Набор инструкций RV32I содержит всего 40 инструкций.
Любой процессор RISC-V поддерживает как минимум один базовый набор инструкций. Например, обозначение RV32I означает, что процессор поддерживает 32-х битный целочисленный набор команд. Распространены дальнейшие расширения базового набора инструкций. Тип расширения, который поддерживает процессор, указывается в качестве суффикса к базовой аббревиатуре набора инструкций. Распространены следующие расширения (таблица 3.1):
| Сокращение | Стандартное расширение |
|---|---|
| M | целочисленные умножение и деление integer multiplication and division |
| A | атомарные инструкции atomic instructions |
| F | числа с плавающей запятой одинарной точности single-precision floating point |
| D | числа с плавающей запятой двойной точности double-precision floating point |
| Q | числа с плавающей запятой четверной точности quad-precision floating point |
| C | сжатые инструкции compressed instructions |
| V | векторные операции vector operations |
Примеры:
Базовый набор инструкций определяет наличие 32 регистров и программного счётчика. Регистры именуются от x0 до x31. Размер регистра соответствует размеру базовых инструкций, например, для процессора RV32I размер каждого из регистров будет составлять 32 бита. Регистр x0 хранит значение 0, которое нельзя поменять.
Расширения предоставляют большее число регистров. Например, расширения, связанные с вычислениями с плавающей запятой, предоставляют дополнительные регистры работы с дробной арифметикой.
Использование регистров языками более высокого уровня, чем Assembler, определяется бинарным интерфейсом для приложений (application binary interface, ABI). Этот интерфейс определяет то, как регистры должны использоваться в процессе трансляции программы компилятором из описания на высокоуровневом языке в машинный код. ABI предписывает использование дополнительных имён регистров и их назначение ( Табл.3.2):
| Регистр | Имя ABI | Описание |
|---|---|---|
| x0 | zero | Константный ноль Zero constant |
| x1 | ra | Адрес возврата Return address |
| x2 | sp | Указатель на область стека Stack pointer |
| x3 | gp | Глобальный указатель Global pointer |
| x4 | tp | Указатель потока Thread pointer |
| x5-x7 | t0-t2 | Временные значения Temporaries |
| x8 | s0 / fp | Сохраняемый регистр / Указатель на фрейм Saved / Frame pointer |
| x9 | s1 | Сохраняемый регистр Saved register |
| x10-x11 | a0-a1 | Аргументы функций и возвращаемые значения Function args. / return values |
| x12-x17 | a2-a7 | Аргументы функций Function arguments |
| x18-x27 | s2-s11 | Сохраняемые регистры Saved registers |
| x28-x31 | t3-t6 | Временные значения Temporaries |
| Pc | - | Программный счётчик Program counter |
Архитектура RISC-V предполагает выполнение арифметических операций через регистры: для модификации данных, находящихся в памяти, необходимо загрузить данные из памяти в один из регистров, выполнить операцию над данными, и переместить результат из регистра обратно в память.
Для понимания принципа использования инструкций, рассмотрим простой пример, в котором используется только одна инструкция: addi rd, rs1, imm, которая добавляет константное целое imm (в диапазоне 12-ти битного знакового: -2048…2047) к содержимому регистра rs1 и записывает результат в регистр rd. Другими словами выполняется операция rd = rs1 + imm. После получения инструкции программный счётчик инкрементируется и будет содержать адрес следующей инструкции, что можно условно записать как pc = cp + 4, поскольку размер инструкции в машинном коде равен 4 байтам.
Инструкция addi x1, x0, 1 добавляет число 1 к значению 0, которое хранится в регистре x0 (в регистре x0 всегда хранится значение 0) и пишет результат, равный 1, в регистр x1. Машинный код инструкции addi x1, x0, 1 - это (в шестнадцатеричном представлении) 0x00100093. Префикс 0x перед числом показывает, что число задано в шестнадцатеричной системе счисления. Далее мы увидим, как именно работает кодирование чисел. По аналогии, инструкция addi x1, x1, 1 добавляет значение 1 к содержимому регистра x1 и записывает результат обратно в регистр x1. Машинный код этой инструкции - 0x00108093.
Теперь представим, что эти машинные коды располагаются в памяти начиная с адреса 0x0. Поскольку каждая инструкция занимает 4 байта, первая инструкция располагается по адресу 0x0, а вторая - по адресу 0x4. Программный счётчик указывает на начало исполняемого кода, следовательно, он равен значению 0x0:
Адрес Машинный код Инструкции 0x0 0x00100093 addi x1, x0, 1 0x4 0x00108093 addi x1, x1, 1
После запуска этих двух строк кода микропроцессор начинает выполнение с адреса 0x0. Как уже было объяснено выше, в результате выполнения первой строки в регистре x1 будет получено число 1, при этом программный счётчик будет инкрементирован до значения 0x4. Выполнение второй строки приведёт к увеличению значения регистра x1 на 1. Так как его значение было равно 1 в результате предыдущей операции, регистр после выполнения второй строки примет значение, равное 2.
На рис.3.1 показан пример того, как этот исходный код запускается в симуляторе Ripes.
(рис 3.1) Запуск рассмотренного примера в симуляторе Ripes
Базовые целочисленные инструкции в микропроцессорах архитектуры RISC-V разделены на пять типов, каждый из которых имеет свой тип кодирования. Типы соответствуют задачам, выполняемым машинной инструкцией: R-type (register, регистр), I-type (immediate, непосредственное значение), S-type (store, хранение), B-type (branch, ветвление), U-type (upper immediate, верхнее непосредственное значение), и J-type (jump, переход). Таблица 3.3 ниже показывает принцип кодирования:
Рассмотрим предыдущий пример. Машинный код инструкции addi x1, x0, 1 - это значение 0x00100093. Побайтное представление кода может быть получено из таблицы выше, зная, что код операции равен 0x13, значение funct3 равно 0x0, и что код типа I (целочисленная операция).
Таким образом, машинный код инструкции addi x1, x0, 1 в побитовом представлении формируется следующим образом:
Поскольку каждые 4 бита могут быть объединены в шестнадцатеричное число, машинный код получается равным 0x00100093.
Приведённая ниже таблица 3.4 содержит инструкции, которые используют непосредственные данные. Программный счётчик обновляется после выполнения каждой инструкции и не приводится.
| Инструкция | Тип | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| addi | ADD Immediate | I | 0010011 | 0x0 | rd = rs1 + imm |
| xori | XOR Immediate | I | 0010011 | 0x4 | rd = rs1 ^ imm |
| ori | OR Immediate | I | 0010011 | 0x4 | rd = rs1 | imm |
| andi | AND Immediate | I | 0010011 | 0x7 | rd = rs1 imm |
| slli | Shift Left Logical Imm. | I | 0010011 | 0x1 | imm[11:5]=0x00, rd = rs1 << imm[4:0] |
| srli | Shift Right Logical Imm. | I | 0010011 | 0x5 | imm[11:5]=0x00, rd = rs1 << imm[4:0] |
| srai | Shift Right Arith. Imm. | I | 0010011 | 0x5 | imm[11:5]=0x20, rd = rs1 >> imm[4:0] |
| slti | Set Less Than Imm. | I | 0010011 | 0x2 | rd = (rs1 < imm)? 0:1 |
| sltiu | Set Less Than Imm. Un. | I | 0010011 | 0x3 | rd = (rs1 < imm)? 0:1 |
Как уже было показано выше, инструкции этого типа берут непосредственное значение imm, в качестве источника используется регистр rs1, в качестве регистра назначения - rd.
Поскольку инструкция addi rd, rs1, imm использует знаковое непосредственное значение imm, она может сработать как для сложения (если imm меняется от 0 до 2047), так и для вычитания (imm меняется от -2048 до -1).
Инструкции xori, ori и andi rd, rs1, imm выполняют побитовые операции xor (исключающее или), or (логическое сложение) и and (логическое умножение). Например, пусть в регистре x1 лежит значение 0x9, которому в побитовом представлении соответствует число 0b1001, тогда инструкция xori x1, x1, 0x3 выполнит побитовое исключающее или над содержимым регистра x1 и числом 0x3, что можно представить следующим образом:
x1 = x1 xor 0x3 = 0b1001 xor 0b0011 = 0b1010 = 0xa
Инструкции slli, srli, и srai выполняют операции побитового сдвига с использованием непосредственного значения, задающего шаг сдвига. В качестве такого непосредственного значения может выступать положительное пятибитное число, представляющее значения от 0 до 31.
Код операции логического (побитового) сдвига влево синтаксически описывается следующим образом: slli rd, rs1, imm, и в процессе выполнения сдвигает каждый бит регистра, задаваемого как rs1, на imm позиций влево. Результат при этом записывается в регистр rd. При каждом сдвиге на 1 бит младший значащий бит устанавливается в 0, а значение старшего значащего бита теряется.
По аналогии с этим выполняется команда побитового сдвига вправо: srli rd, rs1, imm. Значение регистра rs1 побитового сдвигается вправо, результат записывается в регистр, задаваемый как rd. При этом в старший значащий бит при каждом сдвиге записывается значение 0, а значение младшего значащего бита теряется.
Результат выполнения команды srai в плане заполнение старшего бита отличается для арифметического сдвига вправо для команды srai rd, rs1, imm. Информация о старшем значащем бите для этой операции сохраняется. Если до сдвига он был равен 0, старший бит заполняется 0 после сдвига. Если он был равен 1, он заполняется 1. Такой сдвиг является знакорасширяющим для значений в rs1, которые записаны в дополнительном коде.
Рассмотрим пример. Пусть значение регистра x1 равно 0x9, двоичное представление которого записывается как 0b1001. Тогда в результате выполнения инструкции slli x1, x1, 0x2 выполнится логический сдвиг содержимого регистра x1, который можно описать следующим кодом:
x1 = x1 << 0x2 = 0b1001 << 2 = 0b100100 = 0x24
Оставшиеся две инструкции slti rd, rs1, imm и sltiu rd, rs1, imm сравнивают содержимое регистра-источника с непосредственным значением и устанавливают значение регистра-приёмника в 0 в случае, если значение в регистре-источнике меньше непосредственного значения imm. Отличие между этими двумя командами в том, что команда slti использует значения в дополнительном коде и сравнение происходит со знаком.
Следующая категория R-формата включает в себя арифметические и логические операции, которые используют три регистра: два регистра-источника и регистр-приёмник. Большинство этих инструкций по своему действию аналогичны рассмотренным выше. К этим инструкциям относятся (табл.3.5):
| Инструкция | Тип | Формат | Код | funct3 | funct7 | Описание |
|---|---|---|---|---|---|---|
| Add | ADD | R | 0110011 | 0x0 | 0x00 | rd = rs1 + rs2 |
| Sub | SUB | R | 0110011 | 0x0 | 0x20 | rd = rs1 - rs2 |
| Xor | XOR | R | 0110011 | 0x4 | 0x00 | rd = rs1 ^ rs2 |
| or | OR | R | 0110011 | 0x6 | 0x00 | rd = rs1 | rs2 |
| and | AND | R | 0110011 | 0x7 | 0x00 | rd = rs1 rs2 |
| sll | Shift Left Logical | R | 0110011 | 0x1 | 0x00 | rd = rs1 << rs2 |
| srl | Shift Right Logical | R | 0110011 | 0x5 | 0x00 | rd = rs1 >> rs2 |
| sra | Set Right Arith. | R | 0110011 | 0x5 | 0x20 | rd = rs1 >> rs2 |
| slt | Set Less Than | R | 0110011 | 0x2 | 0x00 | Rd = (rs1 < rs2)? 0:1 |
| sltu | Set Less Than Un. | R | 0110011 | 0x3 | 0x00 | rd = (rs1 < rs2)? 0:1 |
Рассмотрим дополнение предыдущего примера с учётом новых инструкций:
Адрес Машинный код Инструкции 0x0 0x00100093 addi x1, x0, 1 0x4 0x00108093 addi x1, x1, 1 0x8 0x00108133 add x2, x1, x1
В последней строке примера содержимое регистра x1 добавляется к самому себе, результат записывается в регистр x2. В результате выполнения всех операций в регистре x2 содержится число 4.
Все эти инструкции в отдельном рассмотрении не нуждаются, поскольку являются функциональными аналогами операций из предыдущего раздела. Исключением является инструкция sub, но, как нам кажется, её функциональность является очевидной и в рассмотрении не нуждается.
Для того, чтобы получить доступ к памяти, реализован ряд инструкций, которые относятся к I-формату и предназначены для загрузки данных из памяти в регистры, а также ряд инструкций, относящихся к S-формату (S-Format, Save) для сохранения данных из регистров в память. Инструкции указаны в таблице 3.6:
| Инструкция | Имя | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| lb | Load Byte | I | 0000011 | 0x0 | rd = M[rs1+imm][7:0] |
| lh | Load Half | I | 0000011 | 0x1 | rd = M[rs1+imm][15:0] |
| lw | Load Word | I | 0000011 | 0x2 | rd = M[rs1+imm][31:0] |
| lbu | Load Byte Un. | I | 0000011 | 0x0 | Rd = M[rs1+imm][7:0] |
| lhu | Load Half Un. | I | 0010011 | 0x0 | rd = M[rs1+imm][15:0] |
| sb | Store Byte | I | 0010011 | 0x0 | M[rs1+imm][7:0] = rs2[7:0] |
| sh | Store Half | I | 0100011 | 0x1 | M[rs1+imm][15:0] = rs2[15:0] |
| sw | Store Word | I | 0100011 | 0x2 | M[rs1+imm][31:0] = rs2[31:0] |
Рассмотрим следующий пример. Предполагается, что в памяти по адресу 0x10000000находится значение 0x02.
Адрес Машинный код Значение 0x0 0x00100093 addi x1, x0, 0x1 0x4 0x01c09093 slli x1, x1, 28 0x8 0x00008103 lb x2, 0(x1)
Первые две инструкции записывают в регистр x1 значение 0x10000000. Для этого в регистр x1 записывается число 1, после чего сдвигается влево на 28 позиций, в результате чего в регистр x1 формируется значение 0x10000000. Третья инструкция загружает байт из памяти по адресу, задаваемому регистром x1.
Синтаксис вызова этой команды следующий: lb rd, imm(rs1). Это можно развернуть в следующее представление: rd = M[rs1+imm][7:0]. Значение одного байта, задаваемого конструкций bits[7:0], загружается в регистра, определяемый как rd (register destination) из памяти по адресу, взятому из регистра, определяемого как rs1 (Register Source), к которому добавляется непосредственное значение смещения imm.
Из приведённого примера видно, что задавать адрес в регистре с использованием описанных инструкций довольно неудобно. Для исправления этого был разработан ряд инструкций U-формата (U-Format, Upper) (Табл.3.7):
| Инструкция | Имя | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| lui | Load Upper Imm. | U | 0110111 | - | rd = imm << 12 |
| auipc | Add Upper Imm. to PC | U | 0010111 | - | rd = PC + (imm << 12) |
Инструкция lui rd, imm задаёт значение регистра, определяемого как rd, равным непосредственному значению imm, сдвинутому на 12 бит влево. А инструкция auipc rd, imm устанавливает значение регистра, определяемого как rd, в результат сложения значения регистра программного счётчика PC и непосредственного значения imm, сдвинутого влево на 12 бит.
Разберём следующий пример:
Адрес Машинный код Значение Комментарий 0x0 0x100000b7 lui x1, 0x10000 Загружает 0x10000000 в регистр x1 0x4 0x10000117 auipc x2, 0x10000 Загружает 0x10000004 в регистр x2
Инструкция lui x1, 0x10000 задаёт регистру x1 значение 0x10000 << 12, таким образом, x1 = 0x10000000, поскольку сдвиг влево на 12 бит "добавляет" три нуля в шестнадцатеричном представлении. Инструкция auipc x2, 0x10000 устанавливает содержимое регистра x2 равным сумме текущего значения PC (счётчика команд), которое в данный момент равно 0x04 с прибавленным значением 0x10000 << 12. Таким образом, значение регистра x2 после выполнения этого кода будет равно 0x10000004.
Каждая из предыдущих рассмотренных инструкций устанавливает программный счётчик PC на адрес следующей инструкции, расположенной в памяти: pc = pc + 4. Для реализации возможности контроля за процессом исполнения используются инструкции B-формата (B-Format, Branching), которые реализуют механизм ветвления. Они используются для условного перехода к инструкции, адрес которой указывается непосредственно в составе соответствующих команд в случае, если условие выполняется (Табл.3.8):
| Инструкция | Тип | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| beq | Branch == | B | 1100011 | 0x0 | if (rs1 == rs2) pc+=imm |
| bne | Branch != | B | 1100011 | 0x1 | If (rs1 != rs2) pc+=imm |
| blt | Branch < | B | 1100011 | 0x4 | if (rs1 < rs2) pc+=imm |
| bge | Branch >= | B | 1100011 | 0x5 | if (rs1 >= rs2) pc+=imm |
| bltu | Branch < Un. | B | 1100011 | 0x6 | if (rs1 < rs2) pc+=imm |
| bleu | Branch >= Un. | B | 1100011 | 0x7 | if (rs1 >= rs2) pc+=imm |
Инструкция ветвления сравнивает значения регистров rs1 и rs2 способом, определяемым самой инструкцией. Если условие истинно, программный счётчик увеличивается на непосредственное значение imm. В выражении blt rs1, rs2, imm проверяется, что rs1 меньше, чем rs2, и если это так, программный счётчик PC изменяется следующим образом: pc = pc + imm. В противном случае он принимает значение адреса следующей инструкции в памяти (pc = pc +4).
Инструкции blt и bge учитывают знак числа, инструкции bltu и bleu - нет, воспринимают числа как беззнаковые.
Рассмотрим пример:
Адрес Машинный код Значение Комментарий 0x00 0xffb00093 addi x1, x0, -5 x1 = -5 0x04 0x00500113 addi x2, x0, 5 x2 = 5 0x08 0x0020c463 blt x1, x2, 0x8 if (x1 < x2) pc = pc + 8 0x0c 0x00100193 addi x3, x0, 1 skipped if (x1 < x2): x3 = 1 0x10 0x00200193 addi x3, x0, 2 x3 = 2
Инструкция по адресу 0x08 проверяет условие, что x1 < x2, то есть -5 < 5. Поскольку выражение истинно, программный счётчик устанавливается в значение 0x10 и инструкция по адресу 0x0c пропускается (не выполняется). Если бы инструкция по адресу 0x08 была bltu, отрицательное значение, хранящиеся в регистре x1 (-5) интерпретировалось бы как положительное значение, равное 0xFFFFFFFB (представление положительного числа, в двоичном коде идентичного отрицательному числу -5), а значит условие бы не выполнилось.
Помимо инструкций для условного перехода, есть инструкции для безусловного перехода: jalr и jal. Эти инструкции позволяют переходить на требуемый адрес путём модификации программного счётчика PC, однако при этом сохраняют адрес возврата, который равен PC+4 на момент вызова инструкции. Такой подход позволяет вернуться в то место, откуда произошёл переход. Именно так реализуется модульность разрабатываемых программ.
Инструкции записываются в разных форматах (Табл.3.9):
| Инструкция | Тип | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| jal | Jump and Link | J | 1101111 | - | rd = PC+4; PC += imm |
| jalr | Jump and Link Register | I | 1100111 | 0x0 | rd = PC+4; PC = rs + imm |
Инструкция jal rd, imm добавляет непосредственное значение к программному счётчику PC, PC += imm, и пишет адрес инструкции, следующей за jal в регистр, задаваемый как rd. Инструкция jalr rd, rs, imm устанавливает программный счётчик в значение, равное результату сложения регистра, задаваемого как rs, и непосредственного значения imm, pc = rs + imm, и пишет адрес возврата в регистр, задаваемый как rd.
Рассмотрим пример:
Адрес Машинный код Значение Комментарий
0x00 0x00c000ef jal x1,0xc set pc to 0x0 + 0xc = 0xc,
x1 = pc + 4 = 0x4
0x04 0x00000013 addi x0, x0, 0 no operation
0x08 0x00010093 addi x1, x2, 0 set x1 = x2
0x0c 0x00008167 jalr x2, x1, 0 set pc to x1,
x2 = pc + 4 = 0x10
0x10 0x00100093 addi x1, x0, 1 x1 = 1
Программа работает следующим образом. Она начинается с инструкции jal x1, 0xc, которая расположена по адресу 0x00. В результате своего выполнения адрес возврата пишется в регистр x1 (следующая инструкция за инструкцией по адресу 0x00 размещается по адресу 0x04, именно это число помещается в регистр x1) и осуществляется переход по адресу 0x0c. По адресу 0x0c размещается инструкция jalr x2, x1, 0, которая пишет уже свой адрес возврата (значение 0x10) в регистр x2 и осуществляет переход по адресу, который лежит в регистре x1 (там размещается адрес возврата для первого выполненного перехода, это адрес 0x04). Программа продолжает выполняться и исполняется инструкция, расположенная по адресу 0x04, которая ничего не делает. Следующая за ней инструкция, расположенная по адресу 0x08) записывает значение 0x10 (адрес возврата для команды jalr) в регистр x1. После этого, вторая итерация выполнения команды jalr по адресу 0xc передаёт управление по адресу 0x10 (значение регистра x1). Итого, программный счётчик PC последовательного принимает следующие значения: 0x00, 0x0c, 0x04, 0x08, 0x0c, и 0x10.
Две инструкции, ecall и ebreak задаются в I-формате (I-format, immediate), и обе используются для доступа к системным функциям. Системные функции обычно требуют привилегированного доступа и наличия инструкций, которые лежат вне пользовательского режима доступа.
| Инструкция | Тип | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| ecall | Environment Call | I | 1110011 | 0x0 | imm = 0, rd = rs1 = 0, передать управление системе |
| ebreak | Environment Break | I | 1110011 | 0x0 | imm = 1, rd = rs1 = 0, передать управление отладчику |
Инструкция ecall запрашивает системную сервисную операцию, так называемый системный вызов. Согласно бинарному интерфейсу для приложений ABI, идентификатор и дополнительные параметры предоставляются через предварительно настроенные регистры, при этом значение регистра x17 задаёт тип системного вызова. В следующем примере показан вызов функции выхода в ОС Linux. Системный вызов exit говорит ОС Linux о том, что ОС должна закончить выполнение программы и выгрузить программу из памяти.
Адрес Машинный код Значение Комментарий
0x00 0x05d00893 addi x17, x0, 93 set x17 = 93,
Linux system call exit
0x04 0x00000073 ecall call system
Инструкция ebreak используется отладчиком для отладки программ. Отладчик временно подменяет инструкцией ebreak код в том месте, где программа должна остановиться. Ситуация, когда вызов инструкции ebreak реализуется программистом вручную, нетипична.
Наконец, инструкция fence используется для синхронизации доступа к памяти между различными процессорами, использующими общую область памяти. Инструкция fence разделяет программный код на код, идущий до неё (предшествующий набор, predecessor set) и инструкции после неё (последующий набор, successor set). Команда полезна для систем, использующих более одного процессорного ядра RISC-V.
Инструкция fence заботится о том, чтобы каждая инструкция из предшествующего ей набора была выполнена исполняющим ядром (и так для каждого ядра) перед тем, как будет выполнена первая инструкция из последующего за ней набора. Инструкция fence очень важна для реализации синхронизации процессов в операционных системах, в том числе в таких механизмах, как механизмы блокировки. Однако эти механизмы выходят за пределы рассматриваемого курса и упоминаются лишь для полноты картины. Обычная инструкция fence имеет следующий I-формат (Табл.3.11):
| Инструкция | Тип | Формат | Код | funct3 | Описание |
|---|---|---|---|---|---|
| fence | Fence | I | 0001111 | 0x0 | rd, rs1 зарезервированы. Для всех типов доступа к памяти imm = 0b000011111111. |
В качестве примера расширений, рассмотрим M-расширение, реализующее операции умножения и деления. Инструкции приведены в таблице 3.12:
| Инструкция | Тип | Формат | Код | funct3 | funct7 | instruction |
|---|---|---|---|---|---|---|
| mul | Multiply | R | 0110011 | 0x0 | 0x01 | rd = (rs1 * rs2)[31:0] |
| mulh | Multiply High | R | 0110011 | 0x1 | 0x01 | rd = (rs1 * rs2)[63:32] |
| mulsu | Multiply High Sign/Uns. | R | 0110011 | 0x3 | 0x01 | rd = (rs1 * rs2)[63:32] |
| mulu | Multiply Unsigned | R | 0110011 | 0x3 | 0x01 | rd = (rs1 * rs2)[63:32] |
| div | Divide | R | 0110011 | 0x4 | 0x01 | rd = rs1 / rs2 |
| divu | Divide Unsigned | R | 0110011 | 0x5 | 0x01 | rd = rs1 / rs2 |
| rem | Remainder | R | 0110011 | 0x6 | 0x01 | rd = rs1 % rs2 |
| remu | Remainder Unsigned | R | 0110011 | 0x7 | 0x01 | rd = rs1 % rs2 |
Поскольку перемножение двух (потенциально больших) чисел может превысить размер регистра (для RV32 этот размер составляет 32 бита), младшая часть результата умножения возвращается с использованием инструкции mul, а старшая часть - с использованием инструкций mulh, mulsu, mulu в зависимости от того, какая комбинация чисел в плане знака используется. Например:
Адрес Машинный код Значение Комментарий
0x00 0x000100b7 lui x1, 0x10 x1 = 0x00010000
0x04 0x00108093 addi x1, x1, 1 x1 = 0x00010001
0x08 0x00080137 lui x2, 0x80 x2 = 0x00080000
0x0c 0x022081b3 mul x3, x1, x2 x3 = 0x00080000,
lower 32 bits of mult.
0x10 0x02209233 mulh x4, x1, x2 x4 = 0x00000008,
higher 32 bits of mult.
Деление на нуль, результат которого не определён, записывает в регистр-приёмник значение -1 (или, другими словами, выставляет все биты в 1). Это значит, что перед выполнением операции деления программисту необходимо самостоятельно проверять делитель и использовать операторы ветвления для обработки исключительных ситуаций.
В целом, для архитектуры RV64 набор инструкций такой же, как и для архитектуры RV32. Однако для RV64 регистры имеют размер в 64 бита, а не в 32, и операции выполняются над 64-битными числами. Более того, для RV64 реализуется ряд инструкций, специфичных именно для 64-битной архитектуры, такие инструкции имеют в конце букву 'w'. Такие инструкции работают только с 32-битными числами, причём используются младшие 32 бита то 64-битных чисел. Примерами таких инструкций являются addiw, addw, sllw, mulw.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.