Цели этого руководства:
- Изучите структуру и синтаксис файла описания двоичного формата.
- Узнайте, как описание двоичного формата совпадает с описанием ISA.
- Напишите двоичные описания подмножества инструкций RiscV RV32I.
Обзор
Кодирование двоичных инструкций RiscV
Кодирование двоичных инструкций — это стандартный способ кодирования инструкций для выполнения на микропроцессоре. Обычно они хранятся в исполняемом файле, обычно в формате ELF. Инструкции могут иметь фиксированную или переменную ширину.
Обычно в инструкциях используется небольшой набор форматов кодирования, причем каждый формат настраивается для типа кодируемых инструкций. Например, инструкции регистр-регистр могут использовать один формат, который максимизирует количество доступных кодов операций, в то время как инструкции непосредственного регистра используют другой, который компенсирует количество доступных кодов операций для увеличения размера непосредственного кода, который может быть закодирован. Инструкции ветвления и перехода почти всегда используют форматы, которые максимизируют размер непосредственного действия, чтобы поддерживать ветвления с большими смещениями.
Форматы инструкций, которые мы хотим декодировать в нашем симуляторе RiscV, следующие:
Формат R-Type, используемый для инструкций регистр-регистр:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
функция7 | rs2 | rs1 | функция3 | р-д | код операции |
Формат I-Type, используемый для инструкций немедленного регистра, инструкций загрузки и инструкции jalr
, 12-битный немедленный формат.
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
имм12 | rs1 | функция3 | р-д | код операции |
Специализированный формат I-Type, используемый для сдвига с немедленными инструкциями, 5-битный немедленный:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
функция7 | uimm5 | rs1 | функция3 | р-д | код операции |
Формат U-Type, используемый для длинных немедленных инструкций ( lui
, auipc
), 20-битный немедленный:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | р-д | код операции |
Формат B-типа, используемый для условных переходов, 12-битный немедленный.
31 | 30..25 | 24..20 | 19..15 | 14..12 | 11..8 | 7 | 6..0 |
---|---|---|---|---|---|---|---|
1 | 6 | 5 | 5 | 3 | 4 | 1 | 7 |
имм | имм | rs2 | rs1 | функция3 | имм | имм | код операции |
Формат J-Type, используемый для инструкции jal
, 20-битный немедленный.
31 | 30..21 | 20 | 19..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
1 | 10 | 1 | 8 | 5 | 7 |
имм | имм | имм | имм | р-д | код операции |
Формат S-типа, используемый для инструкций сохранения, 12-битный немедленный.
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
имм | rs2 | rs1 | функция3 | имм | код операции |
Как видно из этих форматов, все эти инструкции имеют длину 32 бита, а младшие 7 бит в каждом формате — это поле кода операции. Также обратите внимание, что хотя несколько форматов имеют одинаковый размер непосредственных значений, их биты берутся из разных частей инструкции. Как мы увидим, формат спецификации двоичного декодера способен это выразить.
Описание двоичной кодировки
Двоичное кодирование инструкции выражается в файле описания двоичного формата ( .bin_fmt
). Он описывает двоичное кодирование инструкций в ISA, позволяющее создать декодер инструкций двоичного формата. Сгенерированный декодер определяет код операции, извлекает значение операнда и непосредственных полей, чтобы предоставить информацию, необходимую декодеру, не зависящему от кодирования ISA, описанному в предыдущем руководстве.
В этом уроке мы напишем файл описания двоичной кодировки для подмножества инструкций RiscV32I, необходимых для имитации инструкций, используемых в небольшой программе «Hello World». Более подробную информацию о RiscV ISA см. в спецификациях Risc-V {.external}.
Начните с открытия файла: riscv_bin_decoder/riscv32i.bin_fmt
.
Содержимое файла разбито на несколько разделов.
Во-первых, это определение decoder
.
decoder RiscV32I {
// The namespace in which code will be generated.
namespace mpact::sim::codelab;
// The name (including any namespace qualifiers) of the opcode enum type.
opcode_enum = "OpcodeEnum";
// Include files specific to this decoder.
includes {
#include "riscv_isa_decoder/solution/riscv32i_decoder.h"
}
// Instruction groups for which to generate decode functions.
RiscVInst32;
};
В нашем определении декодера указывается имя нашего декодера RiscV32I
, а также четыре дополнительных фрагмента информации. Первый — namespace
, определяющий пространство имен, в котором будет размещен сгенерированный код. Во-вторых, opcode_enum
, который определяет, как тип перечисления кода операции, сгенерированный декодером ISA, должен ссылаться в сгенерированном коде. В-третьих, includes {}
указывает включаемые файлы, необходимые для кода, сгенерированного для этого декодера. В нашем случае это файл, созданный декодером ISA из предыдущего руководства. Дополнительные включаемые файлы могут быть указаны в определении includes {}
глобальной областью действия. Это полезно, если определено несколько декодеров, и все они должны включать одни и те же файлы. Четвертый — список названий групп инструкций, составляющих инструкции, для которых генерируется декодер. В нашем случае он только один: RiscVInst32
.
Далее идут три определения формата. Они представляют собой различные форматы команд для 32-битного командного слова, используемого инструкциями, уже определенными в файле.
// The generic RiscV 32 bit instruction format.
format Inst32Format[32] {
fields:
unsigned bits[25];
unsigned opcode[7];
};
// RiscV 32 bit instruction format used by a number of instructions
// needing a 12 bit immediate, including CSR instructions.
format IType[32] : Inst32Format {
fields:
signed imm12[12];
unsigned rs1[5];
unsigned func3[3];
unsigned rd[5];
unsigned opcode[7];
};
// RiscV instruction format used by fence instructions.
format Fence[32] : Inst32Format {
fields:
unsigned fm[4];
unsigned pred[4];
unsigned succ[4];
unsigned rs1[5];
unsigned func3[3];
unsigned rd[5];
unsigned opcode[7];
};
Первый определяет 32-битный формат инструкций под названием Inst32Format
, который имеет два поля: bits
(шириной 25 бит) и opcode
(шириной 7 бит). Каждое поле является unsigned
, что означает, что значение будет расширено до нуля, когда оно будет извлечено и помещено в целочисленный тип C++. Сумма ширин битовых полей должна равняться ширине формата. В случае разногласий инструмент выдаст ошибку. Этот формат не является производным от какого-либо другого формата, поэтому считается форматом верхнего уровня .
Второй определяет 32-битный формат инструкций с именем IType
, который является производным от Inst32Format
, что делает эти два формата родственными . Формат содержит 5 полей: imm12
, rs1
, func3
, rd
и opcode
. Поле imm12
signed
, что означает, что значение будет расширено знаком, когда значение будет извлечено и помещено в целочисленный тип C++. Обратите внимание, что IType.opcode
имеет один и тот же знаковый/беззнаковый атрибут и ссылается на те же биты командного слова, что и Inst32Format.opcode
.
Третий формат — это пользовательский формат, который используется только инструкцией fence
, которая уже указана, и о которой нам не нужно беспокоиться в этом руководстве.
Ключевой момент: повторно используйте имена полей в разных связанных форматах, если они представляют одни и те же биты и имеют один и тот же знаковый/беззнаковый атрибут.
После определений формата в riscv32i.bin_fmt
идет определение группы команд. Все инструкции в группе инструкций должны иметь одинаковую разрядность и использовать формат, производный (возможно, косвенно) от одного и того же формата инструкций верхнего уровня. Когда ISA может иметь инструкции разной длины, для каждой длины используется отдельная группа инструкций. Кроме того, если целевое декодирование ISA зависит от режима выполнения, например инструкций Arm vs. Thumb, для каждого режима требуется отдельная группа инструкций. Анализатор bin_fmt
генерирует двоичный декодер для каждой группы команд.
instruction group RiscV32I[32] "OpcodeEnum" : Inst32Format {
fence : Fence : func3 == 0b000, opcode == 0b000'1111;
csrs : IType : func3 == 0b010, rs1 != 0, opcode == 0b111'0011;
csrw_nr : IType : func3 == 0b001, rd == 0, opcode == 0b111'0011;
csrs_nw : IType : func3 == 0b010, rs1 == 0, opcode == 0b111'0011;
};
Группа инструкций определяет имя RiscV32I
, ширину [32]
, имя типа перечисления кода операции для использования "OpcodeEnum"
и базовый формат инструкции. Тип перечисления кода операции должен быть таким же, как и тип перечисления декодера команд, независимого от формата, описанного в руководстве по декодеру ISA.
Описание кодировки каждой инструкции состоит из 3 частей:
- Имя кода операции, которое должно быть таким же, как и в описании декодера инструкций, чтобы они могли работать вместе.
- Формат инструкции, используемый для кода операции. Это формат, который используется для удовлетворения ссылок на битовые поля в финальной части.
- Список ограничений битовых полей, разделенных запятыми,
==
,!=
,<
,<=
,>
и>=
, все которые должны быть истинными, чтобы код операции успешно соответствовал командному слову.
Анализатор .bin_fmt
использует всю эту информацию для создания декодера, который:
- Предоставляет функции извлечения (со знаком/без знака), соответствующие каждому битовому полю в каждом формате. Функции экстрактора размещаются в пространствах имен, названных в змеином регистре имени формата. Например, функции извлечения формата
IType
размещаются в пространстве именi_type
. Каждая функция-экстрактор объявленаinline
, принимает самый узкий типuint_t
, который содержит ширину формата, и возвращает самый узкийint_t
(для знаков) иuint_t
(для беззнака), который содержит ширину извлеченного поля. Например:
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
- Функция декодирования для каждой группы команд. Он возвращает значение типа
OpcodeEnum
и принимает самый узкий типuint_t
, который содержит ширину формата группы инструкций.
Выполнить первоначальную сборку
Измените каталог на riscv_bin_decoder
и создайте проект с помощью следующей команды:
$ cd riscv_bin_decoder
$ bazel build :all
Теперь измените каталог обратно на корень репозитория, а затем давайте посмотрим на сгенерированные исходные коды. Для этого измените каталог на bazel-out/k8-fastbuild/bin/riscv_bin_decoder
(при условии, что вы находитесь на хосте x86 — для других хостов k8-fastbuild будет другой строкой).
$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_bin_decoder
-
riscv32i_bin_decoder.h
-
riscv32i_bin_decoder.cc
Сгенерированный файл заголовка (.h)
Откройте riscv32i_bin_decoder.h
. Первая часть файла содержит стандартные шаблонные элементы защиты, включаемые файлы и объявления пространств имен. После этого во internal
пространстве имен есть шаблонная вспомогательная функция. Эта функция используется для извлечения битовых полей из форматов, длина которых слишком велика для 64-битного целого числа C++.
#ifndef RISCV32I_BIN_DECODER_H
#define RISCV32I_BIN_DECODER_H
#include <iostream>
#include <cstdint>
#include "third_party/absl/functional/any_invocable.h"
#include "learning/brain/research/mpact/sim/codelab/riscv_isa_decoder/solution/riscv32i_decoder.h"
namespace mpact {
namespace sim {
namespace codelab {
namespace internal {
template <typename T>
static inline T ExtractBits(const uint8_t *data, int data_size,
int bit_index, int width) {
if (width == 0) return 0;
int byte_pos = bit_index >> 3;
int end_byte = (bit_index + width - 1) >> 3;
int start_bit = bit_index & 0x7;
// If it is only from one byte, extract and return.
if (byte_pos == end_byte) {
uint8_t mask = 0xff >> start_bit;
return (mask & data[byte_pos]) >> (8 - start_bit - width);
}
// Extract from the first byte.
T val = 0;
val = data[byte_pos++] & 0xff >> start_bit;
int remainder = width - (8 - start_bit);
while (remainder >= 8) {
val = (val << 8) | data[byte_pos++];
remainder -= 8;
}
// Extract any remaining bits.
if (remainder > 0) {
val <<= remainder;
int shift = 8 - remainder;
uint8_t mask = 0b1111'1111 << shift;
val |= (data[byte_pos] & mask) >> shift;
}
return val;
}
} // namespace internal
После начального раздела приведен набор из трех пространств имен, по одному для каждого объявления format
в файле riscv32i.bin_fmt
:
namespace fence {
...
} // namespace fence
namespace i_type {
...
} // namespace i_type
namespace inst32_format {
...
} // namespace inst32_format
В каждом из этих пространств имен определена inline
функция извлечения битового поля для каждого битового поля в этом формате. Кроме того, базовый формат дублирует функции извлечения из форматов-потомков, для которых 1) имена полей встречаются только в одном имени поля или 2) для которых имена полей относятся к полю того же типа (знаковое/беззнаковое и битовые позиции) в каждого формата, в котором они встречаются. Это позволяет извлекать битовые поля, описывающие одни и те же биты, с помощью функций в пространстве имен формата верхнего уровня.
Функции в пространстве имен i_type
показаны ниже:
namespace i_type {
inline uint8_t ExtractFunc3(uint32_t value) {
return (value >> 12) & 0x7;
}
inline int16_t ExtractImm12(uint32_t value) {
int16_t result = ( (value >> 20) & 0xfff) << 4;
result = result >> 4;
return result;
}
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
inline uint8_t ExtractRd(uint32_t value) {
return (value >> 7) & 0x1f;
}
inline uint8_t ExtractRs1(uint32_t value) {
return (value >> 15) & 0x1f;
}
} // namespace i_type
Наконец, объявляется функция декодера для группы команд RiscVInst32
. В качестве значения командного слова он принимает 32-битное беззнаковое значение и возвращает соответствующий член класса перечисления OpcodeEnum
или OpcodeEnum::kNone
если совпадений нет.
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);
Созданный исходный файл (.cc)
Теперь откройте riscv32i_bin_decoder.cc
. Первая часть файла содержит объявления #include
и пространства имен, за которыми следуют объявления функций декодера:
#include "riscv32i_bin_decoder.h"
namespace mpact {
namespace sim {
namespace codelab {
OpcodeEnum DecodeRiscVInst32None(uint32_t);
OpcodeEnum DecodeRiscVInst32_0(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_3(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_3c(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_5c(uint32_t inst_word);
DecodeRiscVInst32None
используется для пустых действий декодирования, т. е. тех, которые возвращают OpcodeEnum::kNone
. Остальные три функции составляют сгенерированный декодер. Общий декодер работает по иерархическому принципу. Набор битов в командном слове вычисляется для различения инструкций или групп инструкций на верхнем уровне. Биты не обязательно должны быть смежными. Количество битов определяет размер таблицы поиска, заполненной функциями декодера второго уровня. Это видно в следующем разделе файла:
absl::AnyInvocable<OpcodeEnum(uint32_t)> parse_group_RiscVInst32_0[kParseGroupRiscVInst32_0_Size] = {
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32None, &DecodeRiscVInst32_0_3,
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
...
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32_0_3c, &DecodeRiscVInst32None,
...
};
Наконец, определяются функции декодера:
OpcodeEnum DecodeRiscVInst32None(uint32_t) {
return OpcodeEnum::kNone;
}
OpcodeEnum DecodeRiscVInst32_0(uint32_t inst_word) {
if ((inst_word & 0x4003) != 0x3) return OpcodeEnum::kNone;
uint32_t index;
index = (inst_word >> 2) & 0x1f;
index |= (inst_word >> 7) & 0x60;
return parse_group_RiscVInst32_0[index](inst_word);
}
OpcodeEnum DecodeRiscVInst32_0_3(uint32_t inst_word) {
return OpcodeEnum::kFence;
}
OpcodeEnum DecodeRiscVInst32_0_3c(uint32_t inst_word) {
if ((inst_word & 0xf80) != 0x0) return OpcodeEnum::kNone;
return OpcodeEnum::kCsrwNr;
}
OpcodeEnum DecodeRiscVInst32_0_5c(uint32_t inst_word) {
uint32_t rs1_value = (inst_word >> 15) & 0x1f;
if (rs1_value != 0x0)
return OpcodeEnum::kCsrs;
if (rs1_value == 0x0)
return OpcodeEnum::kCsrsNw;
return OpcodeEnum::kNone;
}
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word) {
OpcodeEnum opcode;
opcode = DecodeRiscVInst32_0(inst_word);
return opcode;
}
В этом случае, когда определены только 4 инструкции, имеется только один уровень декодирования и очень разреженная таблица поиска. По мере добавления инструкций структура декодера будет меняться, а количество уровней в иерархии таблицы декодера может увеличиваться.
Добавить инструкции регистр-регистр ALU
Теперь пришло время добавить несколько новых инструкций в файл riscv32i.bin_fmt
. Первая группа инструкций — это инструкции ALU «регистр-регистр», такие как add
, and
и т. д. В RiscV32 все они используют формат двоичных инструкций R-типа:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
функция7 | rs2 | rs1 | функция3 | р-д | код операции |
Первое, что нам нужно сделать, это добавить формат. Откройте riscv32i.bin_fmt
в своем любимом редакторе. Сразу после Inst32Format
можно добавить формат под названием RType
, который является производным от Inst32Format
. Все битовые поля в RType
unsigned
. Используйте имена, разрядность и порядок (слева направо) из таблицы выше, чтобы определить формат. Если вам нужна подсказка или вы хотите увидеть полное решение, нажмите здесь .
Далее нам нужно добавить инструкции. Инструкции:
-
add
- целое число add. -
and
- побитовое и. -
or
- побитовое или. -
sll
— логический сдвиг влево. -
sltu
— установить меньше, без знака. -
sub
- вычитание целого числа. -
xor
— побитовое исключающее ИЛИ.
Их кодировки:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | имя кода операции |
---|---|---|---|---|---|---|
000 0000 | rs2 | rs1 | 000 | р-д | 011 0011 | добавлять |
000 0000 | rs2 | rs1 | 111 | р-д | 011 0011 | и |
000 0000 | rs2 | rs1 | 110 | р-д | 011 0011 | или |
000 0000 | rs2 | rs1 | 001 | р-д | 011 0011 | слл |
000 0000 | rs2 | rs1 | 011 | р-д | 011 0011 | sltu |
010 0000 | rs2 | rs1 | 000 | р-д | 011 0011 | суб |
000 0000 | rs2 | rs1 | 100 | р-д | 011 0011 | исключающее ИЛИ |
функция7 | функция3 | код операции |
Добавьте эти определения инструкций перед другими инструкциями в группе инструкций RiscVInst32
. Двоичные строки указываются с начальным префиксом 0b
(аналогично 0x
для шестнадцатеричных чисел). Чтобы облегчить чтение длинных строк двоичных цифр, вы также можете вставить одинарную кавычку '
в качестве разделителя цифр там, где считаете нужным.
Каждое из этих определений инструкций будет иметь три ограничения, а именно на func7
, func3
и opcode
. Для всех, кроме sub
, ограничение func7
будет:
func7 == 0b000'0000
Ограничение func3
варьируется в большинстве инструкций. Для add
и sub
это:
func3 == 0b000
Ограничение opcode
одинаково для каждой из этих инструкций:
opcode == 0b011'0011
Не забывайте завершать каждую строку точкой с запятой ;
.
Готовое решение здесь .
Теперь создайте свой проект, как и раньше, и откройте сгенерированный файл riscv32i_bin_decoder.cc
. Вы увидите, что для обработки новых инструкций были сгенерированы дополнительные функции декодера. По большей части они похожи на те, что были сгенерированы ранее, но посмотрите на DecodeRiscVInst32_0_c
, который используется для add
/ sub
:
OpcodeEnum DecodeRiscVInst32_0_c(uint32_t inst_word) {
static constexpr OpcodeEnum opcodes[2] = {
OpcodeEnum::kAdd,
OpcodeEnum::kSub,
};
if ((inst_word & 0xbe000000) != 0x0) return OpcodeEnum::kNone;
uint32_t index;
index = (inst_word >> 30) & 0x1;
return opcodes[index];
}
В этой функции генерируется статическая таблица декодирования, и из командного слова извлекается искомое значение для выбора соответствующего индекса. Это добавляет второй уровень в иерархию декодера инструкций, но поскольку опкод можно искать непосредственно в таблице без дальнейших сравнений, он встроен в эту функцию, а не требует вызова другой функции.
Добавьте инструкции ALU с немедленными действиями
Следующий набор инструкций, который мы добавим, — это инструкции ALU, которые используют непосредственное значение вместо одного из регистров. Существует три группы этих инструкций (в зависимости от поля немедленного действия): немедленные инструкции I-типа с 12-битным немедленным знаком, специализированные немедленные инструкции I-типа для сдвигов и немедленные инструкции U-типа с 20-битным немедленным полем. беззнаковое немедленное значение. Форматы показаны ниже:
Непосредственный формат I-Type:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
имм12 | rs1 | функция3 | р-д | код операции |
Специализированный непосредственный формат I-Type:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
функция7 | uimm5 | rs1 | функция3 | р-д | код операции |
Немедленный формат U-Type:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | р-д | код операции |
Формат I-Type уже существует в riscv32i.bin_fmt
, поэтому добавлять этот формат нет необходимости.
Если мы сравним специализированный формат I-Type с форматом R-Type, который мы определили в предыдущем упражнении, мы увидим, что единственная разница состоит в том, что поля rs2
переименованы в uimm5
. Вместо добавления совершенно нового формата мы можем расширить формат R-Type. Мы не можем добавить еще одно поле, так как это увеличит ширину формата, но мы можем добавить наложение . Оверлей — это псевдоним набора битов в формате, который можно использовать для объединения нескольких подпоследовательностей формата в отдельный именованный объект. Побочным эффектом является то, что сгенерированный код теперь будет включать в себя функцию извлечения для наложения в дополнение к функциям для полей. В этом случае, когда и rs2
, и uimm5
не имеют знака, это не имеет большого значения, кроме как указать, что поле используется как непосредственное. Чтобы добавить наложение с именем uimm5
в формат R-Type, добавьте следующее после последнего поля:
overlays:
unsigned uimm5[5] = rs2;
Единственный новый формат, который нам нужно добавить, — это формат U-Type. Прежде чем добавить формат, давайте рассмотрим две инструкции, использующие этот формат: auipc
и lui
. Оба они сдвигают 20-битное непосредственное значение влево на 12 перед его использованием либо для добавления к нему компьютера ( auipc
), либо для записи его непосредственно в регистр ( lui
). Используя наложение, мы можем предоставить предварительную версию немедленного процесса, немного переместив вычисления от выполнения инструкций к их декодированию. Сначала добавьте формат согласно полям, указанным в таблице выше. Затем мы можем добавить следующее наложение:
overlays:
unsigned uimm32[32] = uimm20, 0b0000'0000'0000;
Синтаксис наложения позволяет нам объединять не только поля, но и литералы. В этом случае мы объединяем его с 12 нулями, фактически сдвигая его влево на 12.
Инструкции I-Type, которые нам нужно добавить:
-
addi
— Добавить немедленно. -
andi
— Побитовый и с немедленным. -
ori
— Побитовый или с немедленным. -
xori
— побитовое исключающее ИЛИ с немедленным выполнением.
Их кодировки:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | имя_опкода_операции |
---|---|---|---|---|---|
имм12 | rs1 | 000 | р-д | 001 0011 | Адди |
имм12 | rs1 | 111 | р-д | 001 0011 | Энди |
имм12 | rs1 | 110 | р-д | 001 0011 | Ори |
имм12 | rs1 | 100 | р-д | 001 0011 | ксори |
функция3 | код операции |
Инструкции R-Type (специализированный I-Type), которые нам нужно добавить:
-
slli
- Сдвиг влево логический на немедленный. -
srai
— Сдвинуть арифметику вправо на немедленную. -
srli
— логический сдвиг вправо на немедленный.
Их кодировки:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | имя кода операции |
---|---|---|---|---|---|---|
000 0000 | uimm5 | rs1 | 001 | р-д | 001 0011 | слли |
010 0000 | uimm5 | rs1 | 101 | р-д | 001 0011 | срай |
000 0000 | uimm5 | rs1 | 101 | р-д | 001 0011 | срли |
функция7 | функция3 | код операции |
Инструкции U-Type, которые нам нужно добавить:
-
auipc
— Добавить верхнюю версию на компьютер. -
lui
- Немедленная загрузка верхнего уровня.
Их кодировки:
31..12 | 11..7 | 6..0 | имя кода операции |
---|---|---|---|
uimm20 | р-д | 001 0111 | АУИПК |
uimm20 | р-д | 011 0111 | Луи |
код операции |
Внесите изменения, а затем создайте. Проверьте сгенерированный вывод. Как и раньше, вы можете проверить свою работу по riscv32i.bin_fmt .
Добавьте инструкции ветвления и перехода и соединения.
Следующий набор инструкций, который необходимо определить, — это инструкции условного перехода, инструкция перехода и связи и инструкция регистра перехода и связи.
Все добавляемые нами условные ветки используют кодировку B-Type.
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | функция3 | imm5 | код операции |
Хотя кодировка B-Type по структуре идентична кодировке R-Type, мы решили использовать новый тип формата, чтобы он соответствовал документации RiscV. Но вы также могли бы просто добавить наложение, чтобы немедленно получить соответствующее смещение ветки, используя поля func7
и rd
кодировки R-Type.
Добавление формата BType
с указанными выше полями необходимо, но недостаточно. Как видите, непосредственная команда разделена на два поля инструкций. Более того, инструкции ветвления не рассматривают это как простое объединение двух полей. Вместо этого каждое поле дополнительно секционируется, и эти разделы объединяются в другом порядке. Наконец, это значение сдвигается на единицу влево, чтобы получить 16-битное выровненное смещение.
Последовательность битов в командном слове, используемая для формирования немедленной команды, следующая: 31, 7, 30..25, 11..8. Это соответствует следующим ссылкам на подполя, где индекс или диапазон указывают биты в поле, пронумерованные справа налево, т. е. imm7[6]
относится к старшему биту imm7
, а imm5[0]
относится к младшему биту imm5
.
imm7[6], imm5[0], imm7[5..0], imm5[4..1]
Если сделать эти манипуляции с битами частью самих инструкций ветвления, есть два больших недостатка. Во-первых, он связывает реализацию семантической функции с деталями представления двоичной инструкции. Во-вторых, это увеличивает накладные расходы во время выполнения. Ответ заключается в том, чтобы добавить наложение к формату BType
, включая конечный «0», чтобы учесть сдвиг влево.
overlays:
signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;
Обратите внимание, что наложение подписано, поэтому оно будет автоматически расширено знаком при извлечении из командного слова.
Команда перехода и соединения (немедленная) использует кодировку J-типа:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
имм20 | р-д | код операции |
Этот формат также легко добавить, но, опять же, непосредственный формат, используемый инструкцией, не так прост, как кажется. Битовые последовательности, используемые для формирования полного непосредственного значения: 31, 19..12, 20, 30..21, а последнее немедленное значение сдвигается влево на единицу для выравнивания половины слова. Решение состоит в том, чтобы добавить к формату еще одно наложение (21 бит для учета сдвига влево):
overlays:
signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;
Как видите, синтаксис наложений поддерживает указание нескольких диапазонов в поле в сокращенном формате. Кроме того, если имя поля не используется, номера битов относятся к самому командному слову, поэтому приведенное выше с таким же успехом можно записать так:
signed j_imm[21] = [31, 19..12, 20, 30..21], 0b0;
Наконец, переход и соединение (регистр) использует формат I-типа, который использовался ранее.
Непосредственный формат I-Type:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
имм12 | rs1 | функция3 | р-д | код операции |
На этот раз в формат вносить изменения не требуется.
Инструкции ветвления, которые нам нужно добавить:
-
beq
— Разветвление, если оно равно. -
bge
— Разветвление, если оно больше или равно. -
bgeu
— переход, если он больше или равен беззнаковому. -
blt
— Разветвление, если меньше. -
bltu
— переход, если число меньше беззнакового. -
bne
— Разветвление, если не равно.
Они кодируются следующим образом:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | имя кода операции |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 110 0011 | бек |
imm7 | rs2 | rs1 | 101 | imm5 | 110 0011 | бге |
imm7 | rs2 | rs1 | 111 | imm5 | 110 0011 | бгеу |
imm7 | rs2 | rs1 | 100 | imm5 | 110 0011 | блт |
imm7 | rs2 | rs1 | 110 | imm5 | 110 0011 | блту |
imm7 | rs2 | rs1 | 001 | imm5 | 110 0011 | бне |
функция3 | код операции |
Инструкция jal
закодирована следующим образом:
31..12 | 11..7 | 6..0 | имя кода операции |
---|---|---|---|
имм20 | р-д | 110 1111 | Джал |
код операции |
Инструкция jalr
закодирована следующим образом:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | имя_опкода_операции |
---|---|---|---|---|---|
имм12 | rs1 | 000 | р-д | 110 0111 | джалр |
функция3 | код операции |
Внесите изменения, а затем создайте. Проверьте сгенерированный вывод. Как и раньше, вы можете проверить свою работу по riscv32i.bin_fmt .
Добавить инструкции по магазину
Инструкции сохранения используют кодировку S-типа, которая идентична кодировке B-типа, используемой в инструкциях ветвления, за исключением композиции непосредственного. Мы решили добавить формат SType
, чтобы он соответствовал документации RiscV.
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | функция3 | imm5 | код операции |
В случае формата SType
немедленное значение, к счастью, представляет собой прямую конкатенацию двух непосредственных полей, поэтому спецификация наложения проста:
overlays:
signed s_imm[12] = imm7, imm5;
Обратите внимание, что при объединении целых полей спецификаторы битового диапазона не требуются.
Инструкции магазина закодированы следующим образом:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | имя кода операции |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 010 0011 | сб |
imm7 | rs2 | rs1 | 001 | imm5 | 010 0011 | ш |
imm7 | rs2 | rs1 | 010 | imm5 | 010 0011 | SW |
функция3 | код операции |
Внесите изменения, а затем создайте. Проверьте сгенерированный вывод. Как и раньше, вы можете проверить свою работу по riscv32i.bin_fmt .
Добавить инструкции по загрузке
Инструкции загрузки используют формат I-Type. Никаких изменений там делать не нужно.
Кодировки:
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | имя_опкода_операции |
---|---|---|---|---|---|
имм12 | rs1 | 000 | р-д | 000 0011 | фунт |
имм12 | rs1 | 100 | р-д | 000 0011 | лбу |
имм12 | rs1 | 001 | р-д | 000 0011 | левый |
имм12 | rs1 | 101 | р-д | 000 0011 | лху |
имм12 | rs1 | 010 | р-д | 000 0011 | лв |
функция3 | код операции |
Внесите изменения, а затем создайте. Проверьте сгенерированный вывод. Как и раньше, вы можете проверить свою работу по riscv32i.bin_fmt .
На этом урок завершается, мы надеемся, что он был полезен.