Предполагается, что многопоточный вариант софт-ядра, позволит эффективнее встраивать его в FPGA-проекты в качестве управляющего, контролирующего блока. Выделенные теневые регистры состояний позволят упростить переключение контента между программными задачами и, дополнительно, упростят блок обработки прерываний. Следование концепции архитектуры RISC-V в некоторых моментах упрощает написание и поддержку программного кода на языках высокого уровня хотя в практике относительно небольших, или сильно специфических проектов выгоднее работать в рамках виртуальных языковых машин, или разрабатывать DSL. . В предложенной реализации микроархитектуры контексты потоков сохраняются в наборах т.н. теневых регистров, отображаемых в момент выполнения потока на регистры общего назначения x0-x31 и программный счетчик (PC). Предлагаемая концепция управления потоками выполнения предназначена, прежде всего, для софт-процессоров, и для систем с одним уровнем привилегий – машинным – уровень микроконтроллерных встраиваемых систем. Все потоки предполагаются равноправными и работающими в едином адресном пространстве. Защита данных потоков и контроль за доступом к общим переменным выносится на уровень программного обеспечения. Рассмотрение ведётся для минимального набора инструкций I+Zicsr (целочисленные операции плюс работа с регистрами специального назначения (CSR). Для управления и настройки параметров потоков предлагается задействовать набор CSR-регистров.
Введение
На работу над многопоточным вариантом вдохновили процессоры семейства Xcore компании XMOS, специализирующейся на процессорах/микроконтроллерах для встраиваемых систем. Решения XMOS традиционно включают в себя элементы, которые традиционно требовали бы использования компонентов другого класса [1]. Там, где традиционно можно использовать микроконтроллер для управления конструкцией, DSP для обработки сигналов и, возможно, CPLD для подключения к сложному цифровому интерфейсу, процессоры XMOS могут выполнять эти три задачи в одном устройстве, используя единый программный процесс на основе программного обеспечения. Их характерные черты – аппаратная многопоточность, возможность масштабирования количества ядер/процессоров в системе, гибкие программно-конфигурируемые порты ввода-вывода. В ранних версиях своих процессоров XMOS применяли свои RISC ядра, были серии с комбинацией RISC-ядер и ARM-ядра. В 2023м году компания анонсировала вариант процессора с ядрами RISC-V (рис.1).
![Рис. 1. Двухядерный процессор XMOS с восьмипоточными RISC-V ядрами [2]. Рис. 1. Двухядерный процессор XMOS с восьмипоточными RISC-V ядрами [2].](https://habrastorage.org/r/w1560/getpro/habr/upload_files/1ca/de9/9f7/1cade99f72c4eb6d879f3293c08ded56.png)
Вторым побуждающим мотивом экспериментов является желание в решениях софт-ядер для FPGA уйти от необходимости введения в состав софт-процессора контроллера прерываний.
Разработка многопоточной микроархитетуры софт-процессора актуальна для систем на базе FPGA по следующим причинам:
- упрощение обработки множества асинхронных событий за счет нескольких потоков без необходимости программного переключения контекста;
-упрощение контроллера прерываний;
- упрощение программного кода и уменьшение его размера.
Одной из задач работы была задача сохранить совместимость многопоточной микроархитектуры с архитектурой RISC-V.
В спецификации архитектуры RISC-V заявлена поддержка многопоточности в виде т.н. аппаратно-поддерживаемых потоков — хартов (hart). Собственно, это и было реализовано в рассматриваемой микроархитектуре. Для каждого из потоков(хартов) предусмотрено хранение состояния потока в массиве теневых регистров (основные регистры x0-x31 и счетчик программ) — каждый набор регистров хранит архитектурное состояние потока.
Общими ресурсами являются исполнительные логические блоки (периферийные устройства, АЛУ), память (программ и данных). Также общими для всех потоков является блок регистров специального назначения (CSR) — регистры состояния, таймеры, счетчики производительности и тп...
"Эволюция" из многотактного поэтапного конвейера
Попробуем несколько модифицировать многотактный вариант процессора с мелким разбитием этапов выполнения команд, добавив ему поддержку многопоточности в виде «теневых» копий основных архитектурных регистров. В терминологии RISC-V аппаратно-поддерживаемый поток называется хартом (hart) [2-4]. Общая структура ядра напоминает цепочку связанных сегментов – рис.2

Ключевая идея – для каждого из хартов хранение состояния потока(нити) в массиве теневых регистров. При этом каждая из копий регистров хранит архитектурное состояние нити. Общими ресурсами остаются логические блоки, память. Также имеет смысл сохранить общим для всех хартов блок регистров специальных функций (как правило, это системные таймеры, счетчики производительности и тп).
Блок CSR-регистров в таком варианте должен будет иметь два раздельных адресных входа – на чтение его данных и на запись данных в него (из-за того, что он оставлен общим для всех хартов и операции чтения/записи осуществляются на разных этапах):
module rv_csr #( parameter DATA_WIDTH=32, parameter ADDR_WIDTH=12, parameter CSR_size = 32 ) ( input clk, input [(ADDR_WIDTH-1):0] csr_addr_in, input [(DATA_WIDTH-1):0] csr_in, input [(ADDR_WIDTH-1):0] csr_addr_out, output reg [(DATA_WIDTH-1):0] csr_out, input csr_wr, input en ); // csr register file reg [ADDR_WIDTH-1:0] csr_reg[0:CSR_size-1]; always @ (posedge clk) begin case (csr_addr_in) 32'h0 : begin if (csr_wr&en) begin csr_reg[0] <= csr_in; end end default: begin csr_reg[1] <= 32'h555; end endcase case (csr_addr_out) 32'h0 : begin csr_out <= csr_reg[0]; end default: begin csr_out<=32'hAAA; end endcase end endmodule
Аналогично файл-регистр также должен иметь возможность записывать и считывать данные регистров, соотнесенных различным хартам – фактически определяем массив или, если угодно, стек файл-регистров. (для отладочных целей и бОльшей наглядности в коде далее использован вариант файл-регистра с дополнительными выходами для отслеживания состояния отдельных регистров).
module rv_reg_file #( parameter DATA_WIDTH=32, parameter ADDR_WIDTH=5 ) ( input clk, input [(ADDR_WIDTH-1):0] rs1, input [(ADDR_WIDTH-1):0] rs2, input [(ADDR_WIDTH-1):0] rd, output reg [(DATA_WIDTH-1):0] Rs1_out, output reg [(DATA_WIDTH-1):0] Rs2_out, input [(DATA_WIDTH-1):0] Rd_input, input [2:0] hart_in, // hard to read out data from input [2:0] hart_out, // hard to write data to input we, input en, output reg [(DATA_WIDTH-1):0] x1_out, output reg [(DATA_WIDTH-1):0] x2_out, output reg [(DATA_WIDTH-1):0] x3_out, output reg [(DATA_WIDTH-1):0] x4_out, output reg [(DATA_WIDTH-1):0] x5_out ); // RAM array reg [DATA_WIDTH-1:0] ram[0:2**ADDR_WIDTH-1][0:7]; wire rd_nonzero; wire rs1_nonzero; wire rs2_nonzero; assign rd_nonzero = |rd; assign rs1_nonzero = |rs1; assign rs2_nonzero = |rs2; always @ (posedge clk) begin if (en & we & rd_nonzero) ram[rd][hart_in] <= Rd_input; end always @ (*) begin Rs1_out <= rs1_nonzero ? ram[rs1][hart_out] : 32'h0; Rs2_out <= rs2_nonzero ? ram[rs2][hart_out] : 32'h0; x1_out <= ram[1][hart_out]; x2_out <= ram[2][hart_out]; x3_out <= ram[3][hart_out]; x4_out <= ram[4][hart_out]; x5_out <= ram[5][hart_out]; end // */ endmodule
Программный счетчик также будет массивом регистров.
module rv_pc #( parameter WIDTH=32 ) ( input clk, input rst_n, input en, input pc_load, input [WIDTH-1:0] pc_next, output [WIDTH-1:0] pc, output reg [WIDTH-1:0] pc_plus, input [2:0] hart_in, input [2:0] hart_out ); reg [WIDTH-1:0] pc_reg[0:7]; // read initial PCs data values initial $readmemh("pc.txt",pc_reg); always@(posedge clk or negedge rst_n) begin if (en) begin if(pc_load==1'b1) pc_reg[hart_in] <= pc_next; else pc_reg[hart_in] <= pc_reg[hart_in] + 3'h4; end end assign pc = pc_reg[hart_out]; always @ * begin pc_plus <= pc_reg[hart_out] + 3'h4; end endmodule
В файле «pc.txt» - начальные значения регистров.
Как и ожидалось, «ленивый» вариант преобразования многотактного процессора в многопоточный простой заменой файл-регистра и счетчика не работоспособен, требуются некоторый механизм управления.
Для управления выборкой данных хартов введём группу регистров – hart-reg, объединенных в линейку. Выходные сигналы – номер харта и бит активности харта. При сбросе регистры инициируются нулями и биты активности хартов сброшены.
На линейку в процессе функционирования должна подаваться последовательность хартов с кодами активности. Сама последовательность может, например, хранится в отдельной небольшой памяти – назовем ее hart_table. Hart_table адресуется циклическим счетчиком.
Для текущего примера создадим простенький «системный таймер» разрядностью 64-бита (на самом деле можно любой приемлемой разрядности, в данном случае 64 бита выбрано по аналогии с традиционными таймерами-счетчиками в реализациях RISC-V архитектуры). Младшие разряды счетчика будут адресовать память таблицы хартов. Сам счетчик потом можно будет использовать в качестве счетчика тактов работы с момента старта системы, например.
Биты активности харта также должны будут использованы для управления записью в такие модули процессорного ядра, как программный счетчик, файл-регистр, запись в память (если харт не активен – запись не будет произведена). Также инверсный сигнал активности харта подается на вход «сброса конвейера» АЛУ (да, для данной реализации будет взята версия АЛУ конвейеризированного процессорного ядра).
Уровни «разделяются» промежуточными регистрами (обычные регистры хранения), которые фиксируют данные (регистры R0 – R6). Ряд сигналов просто «пробрасываются» по этапам (напрямую между регистрами) в том случае, если они не задействованы в них, но требуются на последующих – всё как и было в простом многотактном варианте.

Итоговые микроархитектурные блоки многопоточного варианта процессора:
rv_pc – программный счетчик (массив из нескольких регистров);
rv_mem – блок памяти (программная и оперативная;
rv_desh – дешифратор команд (слова-инструкции) с входом «сброса конвейера»;
rv_imm – формирователь непосредственного значения из слова-инструкции;
rv_reg_file – файл-регистр (массив из нескольких файл-регистров);
rv_ops_mux – коммутатор операндов для АЛУ;
rv_cmp – формирователь сигнала разрешения перехода;
rv_alu_v – АЛУ;
rv_rez_mux – коммутатор результатов;
rv_csr – блок регистров специального назначения;
rv_r_reg – регистры хранения результатов промежуточных этапов;
rv_hart_reg – регистры хранения номеров и состояния активности хартов;
rv_timer – системный таймер;
rv_hart_table – память хранения списка и состояния хартов.
Описание памяти таблицы хартов – в текущем варианте – память на 8 записей, выходная разрядность 4 бита, старший бит – бит активности харта, младшие три – номер харта:
module rv_hart_table #( parameter DATA_WIDTH=4, parameter ADDR_WIDTH=3 ) ( input clk, input [(ADDR_WIDTH-1):0] h_addr, output reg [(DATA_WIDTH-1):0] h_out, input we, input en ); // ROM array reg [DATA_WIDTH-1:0] rom [0:2**ADDR_WIDTH-1] ; // read ROM content from file initial $readmemh("hart_table.txt",rom); always @ (*) begin h_out <= rom[h_addr]; end endmodule //Системный таймер: module rv_timer #( parameter DATA_WIDTH=64 ) ( input clk, input rst, output [(DATA_WIDTH-1):0] timer ); reg [(DATA_WIDTH-1):0] cnt; always@(posedge clk or negedge rst) begin if(!rst) cnt <= 64'h0; else cnt <= cnt + 1'b1; end assign timer = cnt; endmodule

Соединение линейки регистров для хранения хартов:
wire [3:0] hart1_out; rv_hart_reg H1( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart0_out), .h_out(hart1_out) ); wire [3:0] hart2_out; rv_hart_reg H2( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart1_out), .h_out(hart2_out) ); wire [3:0] hart3_out; rv_hart_reg H3( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart2_out), .h_out(hart3_out) ); wire [3:0] hart4_out; rv_hart_reg H4( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart3_out), .h_out(hart4_out) ); wire [3:0] hart5_out; rv_hart_reg H5( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart4_out), .h_out(hart5_out) ); wire [3:0] hart6_out; rv_hart_reg H6( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart5_out), .h_out(hart6_out) );
Тестирование многопоточного ядра
Небольшая тестовая программа - в программе заданы несколько коротких бесконечных цикла, работающих с регистрами х1, х2. Регистр х1 инициируется определенным значением, регистр х2 будет использован, как регистр связи. При простом линейном исполнении программа зациклится на метке l1
Address Code Basic Line Source 0x00400000 0x000010b7 lui x1,1 1 lui x1,0x1 0x00400004 0x00c0d093 srli x1,x1,12 2 srli x1,x1,12 0x00400008 0x00108093 addi x1,x1,1 4 addi x1, x1, 1 0x0040000c 0xffdff16f jal x2,0xfffffffc 5 jal x2,l1 0x00400010 0x000990b7 lui x1,0x00000099 6 lui x1,0x99 0x00400014 0x000020b7 lui x1,2 8 lui x1,0x2 0x00400018 0x00c0d093 srli x1,x1,12 9 srli x1,x1,12 0x0040001c 0x00208093 addi x1,x1,2 11 addi x1, x1, 2 0x00400020 0xffdff16f jal x2,0xfffffffc 12 jal x2,l2 0x00400024 0x000990b7 lui x1,0x00000099 13 lui x1,0x99 0x00400028 0x000030b7 lui x1,3 15 lui x1,0x3 0x0040002c 0x00c0d093 srli x1,x1,12 16 srli x1,x1,12 0x00400030 0x00308093 addi x1,x1,3 18 addi x1, x1, 3 0x00400034 0xffdff16f jal x2,0xfffffffc 19 jal x2,l3 0x00400038 0x000990b7 lui x1,0x00000099 20 lui x1,0x99 0x0040003c 0x000040b7 lui x1,4 22 lui x1,0x4 0x00400040 0x00c0d093 srli x1,x1,12 23 srli x1,x1,12 0x00400044 0x00408093 addi x1,x1,4 25 addi x1, x1, 4 0x00400048 0xffdff16f jal x2,0xfffffffc 26 jal x2,l4 0x0040004c 0x000990b7 lui x1,0x00000099 27 lui x1,0x99 0x00400050 0x000050b7 lui x1,5 29 lui x1,0x5 0x00400054 0x00c0d093 srli x1,x1,12 30 srli x1,x1,12 0x00400058 0x00508093 addi x1,x1,5 32 addi x1, x1, 5 0x0040005c 0xffdff16f jal x2,0xfffffffc 33 jal x2,l5 0x00400060 0x000990b7 lui x1,0x00000099 34 lui x1,0x99 0x00400064 0x000060b7 lui x1,6 36 lui x1,0x6 0x00400068 0x00c0d093 srli x1,x1,12 37 srli x1,x1,12 0x0040006c 0x00608093 addi x1,x1,6 39 addi x1, x1, 6 0x00400070 0xffdff16f jal x2,0xfffffffc 40 jal x2,l6 0x00400074 0x000990b7 lui x1,0x00000099 41 lui x1,0x99 0x00400078 0x000070b7 lui x1,7 43 lui x1,0x7 0x0040007c 0x00c0d093 srli x1,x1,12 44 srli x1,x1,12 0x00400080 0x00708093 addi x1,x1,7 46 addi x1, x1, 7 0x00400084 0xffdff16f jal x2,0xfffffffc 47 jal x2,l7 0x00400088 0x000990b7 lui x1,0x00000099 48 lui x1,0x99 0x0040008c 0x000080b7 lui x1,8 50 lui x1,0x8 0x00400090 0x00c0d093 srli x1,x1,12 51 srli x1,x1,12 0x00400094 0x00808093 addi x1,x1,8 53 addi x1, x1, 8 0x00400098 0xffdff16f jal x2,0xfffffffc 54 jal x2,l8 0x0040009c 0x000990b7 lui x1,0x00000099 55 lui x1,0x99
Для исполнения на многопоточном процессоре массив программных счетчиков инициируется значениями адресов начала каждого из циклов – в данном примере – адреса:
0x0, 0x14, 0x28, 0x3С, 0x50, 0x64, 0x78, 0x8C.
Таблица хартов – перечисление номеров хартов с 0 по 7 в 4-битном представлении. Например, если активен должен быть харт с номером 0, то в таблице он будет представлен цифрой 8 (4b1000), если с номером 3, то 0xB (4b1011) и так далее.
PC.txt | Hart_table.txt |
@00 0 | @00 8 |

На диаграмме представлены значения:
сигналы сброса и тактовый;
номера хартов (с битом активности);
значение программного счетчика и выбранное из памяти слово инструкции;
для АЛУ - опкод команды, операнды и результат;
для файл-регистра текущие выходы и вход с разрешением записи;
тестовые выходы первых 4 регистров файл-регистра с указанием, к какому харту они относятся.
Для регистра Х1 0-го активного харта видна сначала его загрузка значением 0х1000, потом сдвиг его до 0х1, аналогично для 1-го активного харта видна его загрузка значением 0х2000, потом сдвиг его до 0х2.
Предложенный вариант многопоточной микроархитектуры RISC-V позволит осуществлять совместную работу нескольких независимых, или созависимых программ в рамках одного аппаратного ядра. Все программные потоки в предложенном варианте работают с общей памятью, поскольку в аппаратной части не предусмотрено мер по защите данных от одновременной модификации-записи, забота об их сохранности и корректном доступе ложится на программиста. Микроархитектурные блоки системного таймера и таблицы хартов для соответствия архитектуре RISC-V необходимо ввести в состав блоков регистров специального назначения (CSR) – появится возможность их программной модификации штатными средствами, и будет формальное соответствие канонической архитектуре.
Дальнейшее развитие микроархитектуры будет заключаться в развитии механизмов управвления хартами, как событийными (аналог прерываний), так и программными – остановка/запуск харта самим собой или другими хартами.
Для управления наборами теневых регисров вводится группа регистров – hart-reg, объединенных в линейку. Выходные сигналы этих регистров – номер харта и бит активности харта. При сбросе регистры инициируются нулями и биты активности хартов сброшены.
На линейку регистров hart-reg в процессе функционирования подается последовательность хартов с кодами активности. Сама последовательность потоков/хранится в отдельной небольшой памяти –hart_table. Hart_table адресуется циклическим счетчиком.
Роль циклического счетчика играют младшие разряды системного таймера.
Биты активности харта также используются для управления записью в такие модули процессорного ядра, как программный счетчик, файл-регистр, запись в память (если харт не активен – запись не будет произведена).
Перечень потоков хранится в модуле rv_hart_table – память хранения списка и состояния хартов.
Управление аппаратными потоками в архитектуре RISC-V
По спецификации архитектуры RISC-V и данным открытых источников, управление аппаратными потоками выносится за рамки архитектуры. В большинстве реализаций управление потоками выносится на уровень прикладного или системного программного обеспечения (в зависимости от типа и назначения процессора).
Специфицированы только несколько регистров специального назначения (регистры, приведенные ниже соответствуют машинному уровню доступа, для систем с различным уровнем привилегий эти регистры также отображаются на адреса доступные непривилегированному коду):
mhartid (только чтение)– Hart ID Register – идентификатор аппаратного потока, который выполняет текущий код (для однопоточных процессоров mhartid = 0);
mstatus (чтение/запись) – Machine Status Register – регистр содержит текущее состояние и управляет текущим состоянием аппаратного потока.
Однако, ни в одном из этих регистров механизмы прямого управление состоянием потока (выполняется/не выполняется) не предусмотрены, по крайней мере для ядер микроконтроллерного класса. Способы взаимодействия потоком между собой также будут зависеть от конкретной реализации, и, как правило, взаимодействия потоков регулируются средствами операционных систем.
Возможные способы взаимодействия потоков в системах с одним аппаратным уровнем привилегий:
через общую память (часто для исключения гонок по данным/переменным, вероятно, лучше задействовать команды А-расширения системы команд, при его поддержке), иначе контроль выносится на программный уровень;
через прерывания/исключения;
через регистры специального назначения (CSR-регистры) – подобный вариант не встречался, но, формально, не запрещен в микроархитектурных реализациях.
Варианты перехода потока между активным и пассивным состояниями.
Программные (синхронные) события:
самостоятельно (поток имеет возможность перевести самого себя в состояние “не активен” – например, изменяя переменную, бит в выделенном CSR-регистре);
по команде другого потока (аналогично, или через общую переменную, или через бит CSR-регистра);
Асинхронные события — реакция на:
по событию периферийных устройств – таймеры, счётчики, приемопередающие устройства (UART, SPI, I2C, I2S и тп.);
по событиям обмена данными между потоками (чтение/запись).
Фактически, задача сводится к созданию некоторого подобия контроллера прерываний, которая упрощается тем, что можно следовать соглашениям, что за обработку тех или иных событий будет отвечать строго определенный аппаратный поток.
Помимо признака активности, поток характеризуется значением программного счетчика – т.е. тем кодом, который поток призван исполнять. Значениями программных счетчиков потоков также можно управлять – через адресное пространство памяти, или через CSR-регистры.
Стратегически правильнее будет вынести функции управления потоками на уровень CSR-регистров – в вариантах архитектуры с поддержкой привилегированных команд доступ к ним чаще всего ограничивается средствами операционных систем/системных мониторов.
Для максимального исключения конфликтов с существующими реализациями и расширениями системы команд в качестве базовых адресов CSR-регистров “дальние адреса” – например, 0x3800 и далее.
Регистры управления hart-ами
Адрес | Обозначение | Описание |
0x3800 - … | Hart_table | Таблица потоков, находящихся в “жесткой ротации” – те наборы теневых регистров, выполение которых разрешено или планируется в ходе работы – задаёт порядок выполнения потоков. Текущий поток отображается также в регистре текущего потока (hartid) (младшая часть), бит активности потока отображается в регистре состояния потока (hart_state) |
0x3820 - … | PC_table | Таблица значений программных счетчиков потоков в «жесткой ротации» |
0х3840 - ... | Sensivity_list | Набор битовых полей, показывающих как/посредством чего поток может изменять свой бит активности |
Биты слов в полях Sensiviti_list кодируют возможные для потока условия изменения его состояния:
поток управляет своим состоянием сам;
поток чувствителен к состоянию и сигналам системных и периферийных устройств - таймеры, интерфейсы;
поток реагирует на уровни линий ввода-вывода.
Заключение
Все потоки предполагаются равноправными и работающими в едином адресном пространстве. Защита данных потоков и контроль за доступом к общим переменным потоков выносится на уровень программного обеспечения. Предлагаемый вариант управления потоками требует лишь минимального набора инструкций I+Zicsr (целочисленные операции плюс работа с регистрами специального назначения (CSR), и не конфликтует с устоявшимися стандартами и расширениями архитектуры RISC-V.
https://riscv-alliance.ru/material/risc-v-dlya-fpga-arhitektura-mikroarhitekturnye-realizaczii/
1. Multicore microcontrollers of the XCORE family from XMOS [Электронный ресурс].– Режим доступа: https://cyberleninka.ru/article/n/multiyadernye-mikrokontrollery-semeystva-xcore-ot-xmos
2. Using RISC-V to define SoCs in software _ XMOS [Электронный ресурс].– Режим доступа: https://www.xmos.com/using-risc-v-to-define-socs-in-software/
3. The RISC-V Instruction Set Manual. Volume I: User-Level ISA Document Version 2.2. Editors: Andrew Waterman1, Krste Asanov
4. Сomputer architecture - What is a hardware thread in RISC-V_ - Electrical Engineering Stack Exchange [Электронный ресурс].– Режим доступа: https://electronics.stackexchange.com/questions/580645/what-is-a-hardware-thread-in-risc-v
5. Trusted Hart for Mobile RISC-V Security [Электронный ресурс].– Режим доступа: https://www.computer.org/csdl/proceedings-article/trustcom/2022/942500b587/1LFM9tik1IQ
6. RISC-V Assembly Language Programmer Manual Part I developed by: SHAKTI Development Team @ iitm ’20 shakti.org.inRISCV Green Card_v. [Электронный ресурс].– Режим доступа: https://www.cl.cam.ac.uk/teaching/1617/ECAD+Arch/files/docs/RISCVGreenCardv8-20151013.pdf
7. Stephen Smith. Programming in the RISC-V assembly language / translated from English by A. V. Logunov; edited by A. Yu. Romanov. – M.: DMK Press – 276 p
8. Adding custom instructions compilation support, to RISCV toolchain. [Электронный ресурс].– Режим доступа: https://medium.com/@viveksgt/adding-custom-instructions-compilation-support-to-riscv-toolchain-78ce1b6efcf4
9. Создание RISC-V ядер [Электронный ресурс].– Режим доступа: https://riscv-alliance.ru/material/sozdanie-risc-v-yader/
10. RISC-V для FPGA – архитектура, микроархитектурные реализации [Электронный ресурс].– Режим доступа: https://riscv-alliance.ru/material/risc-v-dlya-fpga-arhitektura-mikroarhitekturnye-realizaczii/
