E5 Полнофункциональный мини-процессор RISC-V
Мы уже реализовали sCPU. Хотя реализация этого процессора действительно дала нам более глубокое понимание того, как работают процессоры, с практической точки зрения sCPU не может выполнять более сложные программы из-за ряда ограничений. По сути, эти ограничения в конечном счёте проистекают из простоты системы команд sISA, например:
- Регистр PC имеет разрядность всего 8 бит, а значит, программа может содержать не более 256 инструкций.
- GPR имеют разрядность всего 8 бит, из-за чего невозможно представить данные больше 255.
- Функциональность инструкций ограничена; например, невозможно выполнить операцию вычитания между двумя GPR, не говоря уже об умножении и делении.
Далее вы реализуете полнофункциональный процессор RISC-V, который сможет выполнять больше программ и даже потенциально сможет запускать игры для NES!
Система команд Mini RISC-V
RISC-V — это открытая архитектура системы команд, получившая широкое распространение за последнее десятилетие. Она использует философию модульного проектирования, разбивая инструкции на отдельные подмножества. Помимо базовой системы команд RV32I, существуют различные расширения — включая умножение/деление (M), операции с плавающей точкой (F) и атомарные операции (A). Разработчики могут по своему выбору реализовать ноль или более расширений в зависимости от требований приложения. Эта гибкость сделала её очень популярной среди разработчиков.
Базовый набор RV32I включает 42 инструкции. Для большинства вычислительных задач достаточно реализовать только RV32I. Чтобы ещё больше сократить трудозатраты на разработку, мы предлагаем minirv — подмножество инструкций «миниатюрный RISC-V». Minirv отбирает из RV32I 8 базовых инструкций, которые с помощью комбинаций инструкций способны функционально заменить все остальные инструкции RV32I. Следовательно, любую задачу, выполнимую с помощью RV32I, можно выполнить и с помощью minirv. Такой подход избавляет от необходимости реализовывать полный набор RV32I из 42 инструкций, при этом позволяя процессору выполнять относительно сложные программы.
Как и у любой настоящей архитектуры системы команд, детали спецификации RISC-V задокументированы в соответствующих официальных руководствах. Мы призываем всех выработать хорошую привычку читать официальные руководства. Поэтому вам нужно скачать riscv manual. Если это ваше первое знакомство с концепциями ISA и проектирования процессоров, вам может быть сложно понять каждую деталь в руководствах. Однако мы поможем вам найти ключевую информацию, связанную с RV32I, в этих документах.
Предварительно разберитесь в системе команд RISC-V с помощью RTFM
Посмотрите оглавление руководства RISC-V. В какой главе описывается RV32I? Попробуйте найти в этой главе соответствующее содержимое про RV32I и ответьте на следующие вопросы:
- Какова разрядность регистра PC?
- Сколько всего GPR? Какова разрядность каждого GPR?
- В чём отличия между
R[0]иR[0]в sISA? - Какова разрядность кодировки инструкции? Сколько базовых форматов у инструкций?
- В базовом формате инструкций сколько бит нужно для представления GPR? Почему?
- Каков конкретный формат инструкции
add? - Есть ещё одна базовая система команд под названием RV32E. В чём её отличие от RV32I?
После того как вы разберётесь в некоторых деталях системы команд RISC-V, мы можем представить спецификацию ISA под названием minirv, а именно:
- Начальное значение PC —
0. - Количество GPR такое же, как определено в RV32E.
- Поддерживаются следующие 8 инструкций:
add,addi,lui,lw,lbu,sw,sb,jalr - Остальные детали ISA такие же, как у RV32I.
Реализуйте симулятор системы команд для minirv
Процессор minirv всего с двумя инструкциями
Реализуем ли мы процессор с помощью Logisim или RTL, нам нужно продумывать детали на уровне цифровой схемы. Однако, поскольку вы, возможно, впервые сталкиваетесь с системой команд RISC-V, реализация процессора MiniRV сразу на уровне цифровой схемы может оказаться довольно сложной.
Поэтому мы сначала реализуем указанные выше инструкции в симуляторе системы команд. Реализовать эти инструкции в симуляторе обычно гораздо проще, поскольку нам нужно сосредоточиться только на том, как описать их поведение средствами языка C. Это поможет нам выработать правильное понимание того, как работают эти инструкции. Мы называем этот симулятор системы команд minirvEMU.
У minirv 8 инструкций, и мы сначала реализуем две из них: addi, jalr. Сначала рассмотрим инструкцию addi.
RTFM(2)
Обратитесь к руководству RISC-V, чтобы найти кодировку и соответствующее функциональное описание инструкции addi. В главе 34, "RV32/64G Instruction Set Listings", есть несколько таблиц инструкций, которые помогут вам найти кодировку инструкции addi.
Для процесса выборки инструкции вам нужно продумать изменение разрядности M и разрядности регистра PC. Однако понятие памяти существует как на уровне ISA, так и на уровне фактической реализации. Поэтому важным вопросом, который нужно решить, становится то, как реализовать модель памяти уровня ISA средствами кода C.
RTFM(3)
Чтобы разобраться в нескольких соглашениях RISC-V относительно памяти, вам нужно прочитать первый абзац раздела 1.4 руководства RISC-V, чтобы понять спецификации памяти на уровне ISA, особенно определение разрядности.
Для удобства обозначим разрядность памяти, определённую в руководстве RISC-V, как . Очевидно, что на уровне ISA регистр PC использует -битную адресацию. Однако в фактической реализации на C, если разрядность M, , отличается от , значение PC нельзя напрямую использовать как адрес для доступа к M. Поэтому вам нужно продумать, как решить эту проблему в реализации на C.
Для процесса декодирования сначала рассмотрим декодирование кода операции. Однако, поскольку система команд miniRV содержит лишь небольшое число инструкций, а кодировка кода операции относительно разреженная, мы можем напрямую сравнить поле кода операции инструкции с кодировкой инструкции addi, чтобы выполнить декодирование. Например, определить, является ли инструкция инструкцией addi, можно с помощью следующей операции:
// The following is pseudo code
is_addi = (inst[6:0] == ?) && (inst[14:12] == ?)
Здесь inst обозначает выбранную инструкцию, а ? нужно определить по результатам изучения руководства.
Для декодирования операндов важный момент, на который стоит обратить внимание, — непосредственное значение. Поскольку поле непосредственного значения в инструкции имеет относительно небольшую разрядность, но должно использоваться в вычислениях вместе с более широкими GPR, перед вычислением непосредственное значение нужно сначала расширить по знаку.
Для GPR идея проектирования похожа на предыдущую. Кроме того, в RISC-V функция R[0] довольно особая, и вам также нужно продумать, как правильно её реализовать.
Для процесса выполнения нам пока нужно реализовать только операцию сложения.
Для обновления PC, поскольку разрядность инструкций RISC-V отличается от sISA, вам также нужно продумать, как обновлять PC, чтобы он корректно указывал на следующую инструкцию.
RTFM(4)
Обратитесь к руководству RISC-V, чтобы найти кодировку и соответствующее функциональное описание инструкции jalr.
Реализуйте minirvEMU с двумя инструкциями
После того как вы разберётесь в функциях инструкций addi и jalr, попробуйте спроектировать minirvEMU, поддерживающий эти две инструкции RISC-V, опираясь на предыдущий опыт проектирования симуляторов sEMU.
Чтобы помочь вам провести простой тест процессора, мы подготовили следующую тестовую программу. В приведённых ниже инструкциях ассемблера GPR используют мнемоники ABI, то есть используются имена, которые лучше отражают их функции. Например, zero используется для обозначения GPR с номером 0. В инструкциях ассемблера также встречаются a0 и ra, и соответствующие номера GPR можно узнать, разобрав соответствующие кодировки инструкций.
00000000 <_start>:
0: 01400513 addi a0,zero,20
4: 010000e7 jalr ra,16(zero) # 10 <fun>
8: 00c000e7 jalr ra,12(zero) # c <halt>
0000000c <halt>:
c: 00c00067 jalr zero,12(zero) # c <halt>
00000010 <fun>:
10: 00a50513 addi a0,a0,10
14: 00008067 jalr zero,0(ra)
Попробуйте понять функцию этой программы через автомат состояний системы команд. После того как разберётесь, поместите программу в M и попробуйте запустить свой процессор, затем проверьте, соответствует ли результат работы процессора ожиданиям.
Протестируйте инструкцию addi
В приведённой выше тестовой программе непосредственные значения, используемые инструкциями addi, относительно малы. Чтобы проверить, корректна ли реализация расширения знака, вам нужно заставить minirvEMU выполнить несколько инструкций addi с отрицательными непосредственными значениями. Попробуйте написать несколько таких инструкций addi, поместить их в M и проверить, корректно ли работает ваша реализация.
Реализация полного minirvEMU
Далее рассмотрим, как реализовать оставшиеся 6 инструкций minirv. Изучив руководство (RTFM), вы обнаружите, что функция инструкции add очень похожа на инструкцию add в sISA, поэтому реализовать её не сложно. Что касается инструкции lui, она довольно похожа на инструкцию li в sISA, за исключением того, что нужно учитывать разные типы форматов непосредственных значений.
Реализуйте полный minirvEMU
Реализуйте инструкции add и lui. После реализации попробуйте написать несколько простых последовательностей инструкций и поместить их в M, чтобы предварительно проверить корректность вашей реализации.
Оставшиеся 4 инструкции — все инструкции обращения к памяти, и всем им нужен доступ к памяти. Операции обращения к памяти делятся на два типа: load (чтение памяти) и store (запись в память). В minirvEMU память — это M. Прежде чем продумывать, как реализовать эти четыре инструкции обращения к памяти, вам нужно сначала разобраться в соглашениях о памяти, определённых RISC-V, а также в конкретном поведении соответствующих инструкций обращения к памяти.
RTFM(5)
Обратитесь к руководству RISC-V, чтобы найти кодировки и соответствующие функциональные описания четырёх инструкций: lw, lbu, sw и sb. В руководстве также описано содержимое, связанное с EEI и невыровненным доступом к памяти — пока это не используется, так что можете это содержимое пропустить.
Реализация инструкции lw относительно проста. Вычислив адрес памяти, мы можем напрямую использовать его для чтения соответствующих данных из M. Как и в процессе выборки инструкции, рассмотренном ранее, фактическая спецификация памяти, представленная M, может отличаться от определения памяти на уровне ISA. Поэтому вам нужно продумать, как правильно индексировать M. Реализация инструкции sw похожа на реализацию lw, но вам также нужно учитывать различные форматы непосредственных значений, используемых инструкцией.
Невыровненный доступ к памяти можно не учитывать.
Если остаток от деления адреса памяти addr на разрядность данных w инструкции обращения к памяти равен 0, доступ считается выровненным. Для lw и sw w = 4. Поэтому, если addr % 4 == 0 (% обозначает операцию взятия остатка), доступ к памяти выровнен; иначе он невыровнен.
Для упрощения реализации мы можем считать, что два младших бита в двоичном представлении адресов памяти, вычисляемых lw и sw, всегда равны 0. Предоставленные нами тестовые программы гарантируют это свойство, поэтому не будет случая, когда обращение к данным пересекает границу двух слов памяти. Поэтому реализации не нужно обрабатывать невыровненный доступ к памяти.
Заинтересованные студенты могут почитать соответствующие разделы руководства RISC-V для подробностей.
Реализуйте полный minirvEMU (2)
Реализуйте инструкции lw и sw, затем напишите несколько простых последовательностей инструкций и поместите их в M. Одновременно поместите в M некоторые тестовые данные, чтобы предварительно проверить, корректно ли ведут себя инструкции обращения к памяти.
Инструкции lbu нужно прочитать всего один байт. Вам нужно выбрать соответствующий байт из прочитанных данных на основе конкретного адреса обращения к памяти и записать его обратно в регистр-приёмник.
Реализуйте полный minirvEMU (3)
Реализуйте инструкцию lbu и с помощью нескольких последовательностей инструкций предварительно проверьте корректность вашей реализации.
Подсказка: вы можете сначала поместить 4-байтное значение данных 0x12345678 в M и прочитать его с помощью инструкции lw (предполагая, что данные хранятся по адресу памяти a). Убедитесь, что результат равен 0x12345678. Затем с помощью нескольких инструкций lbu прочитайте данные по адресам памяти a, a+1, a+2 и a+3. Ожидается, что эти инструкции lbu прочитают соответственно 0x78 (по адресу a), 0x56, 0x34 и 0x12 (по адресу a+3).
Инструкция sb действует наоборот. Ей нужно записать всего один байт по целевому адресу.
Реализуйте полный minirvEMU (4)
Реализуйте инструкцию sb и с помощью нескольких последовательностей инструкций предварительно проверьте корректность вашей реализации.
Подсказка: вы можете сначала поместить 4-байтное значение данных 0x12345678 в M и прочитать его с помощью инструкции lw (предполагая, что данные расположены по адресу памяти a). Убедитесь, что результат чтения равен 0x12345678. Затем с помощью нескольких инструкций sb запишите следующие значения по адресам памяти a+3, a+2, a+1 и a+0 соответственно: 0x90 (по адресу a+3), 0xab, 0xcd и 0xef (по адресу a). Перед выполнением записи вы можете использовать инструкцию addi вместе с нулевым регистром, чтобы загрузить непосредственное значение в регистр-приёмник, добиваясь того же эффекта, что и инструкция li в sISA. Наконец, снова прочитайте обновлённые данные с помощью инструкции lw. Ожидаемый результат — 0x90abcdef.
Пусть программа сама решает, когда симулятору остановиться
Ранее мы позволяли minirvEMU выполняться до тех пор, пока программа не входила в ожидаемый бесконечный цикл, обозначающий завершение выполнения. Другой подход заключался в том, чтобы останавливать minirvEMU после выполнения фиксированного числа инструкций. Однако эти подходы не очень универсальны: вам нужно заранее знать, сколько инструкций выполнит программа до завершения, а затем вручную вписать эту информацию в код minirvEMU. Есть ли способ автоматически останавливать minirvEMU, когда программа завершает выполнение?
Поскольку Logisim — это программа с графическим интерфейсом, добавлять в неё собственные функции не очень удобно. Однако minirvEMU — это программа на C, поэтому мы можем заставить её автоматически определять, успешно ли завершилась программа. В частности, мы можем добавить инструкцию ebreak и определить её поведение как завершение программы. Чтобы реализовать автоматическое обнаружение завершения, мы определим, что при выполнении инструкции ebreak minirvEMU должна остановить выполнение всей программы и вывести соответствующую информацию.
Чтобы программа следовала этому соглашению, мы можем вручную вписать кодировку инструкции ebreak в нужное место в M. Конкретную кодировку инструкции ebreak уточните в руководстве (RTFM). Под «нужным местом» подразумевается позиция рядом с функцией halt() в программе или конец последовательности инструкций, которую вы пишете сами. Например, вписать инструкцию ebreak можно следующим способом:
M[? + 0] = ?;
M[? + 1] = ?;
// ......
Вам нужно найти правильный адрес памяти и кодировку инструкции ebreak, а затем заменить ? выше на соответствующие значения.
Реализуйте автоматическое обнаружение завершения программы
Согласно описанному выше соглашению, добавьте и реализуйте инструкцию ebreak в minirvEMU. Затем измените последовательность инструкций программы так, чтобы она выполняла инструкцию ebreak по завершении. Если ваша реализация верна, вы увидите, что программа автоматически завершается, а minirvEMU выводит соответствующую информацию о завершении.
Конечно, этот процесс всё ещё включает немало ручных операций. Однако, поскольку сейчас нам нужно запускать лишь небольшое число программ, накладные расходы на эти ручные шаги пока приемлемы. По мере роста числа программ, которые нам нужно запускать, нам понадобится найти способ реализовать полностью автоматический механизм обнаружения. Мы вскоре продолжим обсуждение этого вопроса.
Реализуйте NPC miniRV на RTL
После реализации minirvEMU у вас теперь должно быть чёткое понимание деталей системы команд MiniRV. Теперь настало время «обновить» прежний NPC из sCPU до процессора MiniRV.
Модульное проектирование на RTL
В отличие от sCPU, мы будем и дальше улучшать NPC, добавляя новые функции. Поэтому проект NPC нужно правильно поддерживать и готовить к будущим расширениям. Один из способов повысить сопровождаемость кода — модульное проектирование.
С точки зрения типов инструкций система команд miniRV охватывает несколько функций, включая сложение, конкатенацию битов, обращение к памяти и переходы. Основываясь на этих функциях и рабочем процессе процессора, мы можем разделить NPC на следующие модули:
- IFU (устройство выборки инструкций): отвечает за выборку инструкции из памяти согласно текущему PC.
- IDU (устройство декодирования инструкций): отвечает за декодирование текущей инструкции и подготовку данных и управляющих сигналов, необходимых для стадии выполнения.
- EXU (исполнительное устройство): отвечает за управление ALU согласно управляющим сигналам и выполнение вычислений над данными.
- LSU (устройство загрузки-записи): отвечает за управление обращением к памяти согласно управляющим сигналам, включая чтение данных из памяти и запись данных в память.
- WBU (устройство записи результата): отвечает за запись данных обратно в регистры и обновление PC.
Вам нужно самостоятельно спроектировать и организовать интерфейсы между этими модулями. Конечно, вы также можете сами решить, какие компоненты помещать в какой модуль, исходя из собственных проектных решений.
Одно исключение — память. Чтобы упростить тестирование, мы не планируем реализовывать память на RTL. Вместо этого она будет реализована на C++. Пока рассмотрим самый простой подход к реализации: вывод сигналов интерфейса обращения к памяти на верхний уровень и использование кода C++ для доступа к памяти.
while (???) {
...
top->inst = pmem_read(top->pc);
single_cycle();
...
}
Вы можете легко реализовать простую память с помощью кода C++.
NPC miniRV всего с двумя инструкциями
При реализации minirvEMU мы использовали средства языка C для описания процесса выполнения инструкций. Однако если мы хотим реализовать minirv NPC на RTL, нам нужно продумать, как реализовать процесс выполнения инструкций с помощью модулей схемы на аппаратном уровне. Поэтому у вас сначала должна появиться архитектурная диаграмма miniRV NPC — нарисованная на бумаге или просто существующая у вас в голове. Имея архитектурную диаграмму, описывать структуру схемы каждого модуля с помощью RTL-кода становится намного проще.
Сначала реализуем самую простую инструкцию: addi.
Реализуйте инструкцию addi в NPC
В частности, вам нужно обратить внимание на следующие моменты:
- В память можно поместить несколько двоичных кодировок инструкций
addi. (Вы можете использовать свойства регистра x0, чтобы писать инструкции с предсказуемым поведением.) - Поскольку инструкции перехода ещё не реализованы, NPC может выполнять инструкции только последовательно. Вы можете остановить симуляцию после того, как NPC выполнит определённое число инструкций.
- Вы можете проверить, корректно ли выполняется инструкция
addi, просмотрев временную диаграмму или выведя состояние регистров общего назначения в RTL-коде. - Что касается регистров общего назначения (GPR), их реализация в схеме по сути представляет собой память. Чтобы студенты, использующие Verilog, не писали неуместный код с поведенческим моделированием, мы предоставляем следующий неполный код для дополнения. (Содержимое блока always менять не нужно.)
module RegisterFile #(ADDR_WIDTH = 1, DATA_WIDTH = 1) (
input clk,
input [DATA_WIDTH-1:0] wdata,
input [ADDR_WIDTH-1:0] waddr,
input wen
);
reg [DATA_WIDTH-1:0] rf [2**ADDR_WIDTH-1:0];
always @(posedge clk) begin
if (wen) rf[waddr] <= wdata;
end
endmodule
- Вам также нужно продумать, как реализовать особое поведение нулевого регистра.
Не знаете, с чего начать?
Вы, вероятно, столкнётесь со следующими проблемами:
- Как правильно обращаться к памяти по значению PC?
- Как поместить инструкции
addiв память? - Как остановить симуляцию после выполнения всего нескольких инструкций?
- ...
При настройке каркаса Verilator мы уже напоминали всем:
Каждая деталь проекта имеет к вам отношение.
Всякий раз, когда вам кажется, что вы совсем не представляете, как действовать дальше, это, весьма вероятно, сигнал о том, что вы могли упустить что-то важное в предыдущем обучении.
Вместо того чтобы просто спрашивать у однокурсников, вам стоит сначала повторить предыдущие эксперименты, постараться разобраться в каждой детали и с помощью этих знаний найти ответы на вышеуказанные вопросы.
Реализуйте инструкцию `jalr` в NPC
После реализации инструкций addi и jalr запустите на NPC ту же тестовую программу из двух инструкций, которая ранее выполнялась на minirvEMU, и проверьте, соответствует ли результат выполнения NPC ожидаемому поведению.
Пусть программа сама решает, когда симуляции завершиться
Аналогично minirvEMU, мы можем реализовать похожий механизм в NPC: если программа выполняет инструкцию ebreak, она уведомит среду симуляции о необходимости завершить симуляцию.
Реализовать эту функцию не сложно. Сначала вам нужно добавить в NPC поддержку инструкции ebreak. Однако, чтобы NPC мог уведомлять среду симуляции при выполнении инструкции ebreak, вам также нужно реализовать механизм взаимодействия между RTL-кодом и кодом C++. Для этого взаимодействия мы будем использовать DPI-C, предоставляемый SystemVerilog.
Попробуйте механизм DPI-C
Прочитайте руководство Verilator, найдите соответствующие разделы про механизм DPI-C и попробуйте запустить примеры, приведённые в руководстве.
Реализуйте `ebreak` через DPI-C
Используйте механизм DPI-C в RTL-коде, чтобы уведомлять среду симуляции о необходимости завершить симуляцию, когда NPC выполняет инструкцию ebreak. После реализации поместите инструкцию ebreak на место функции halt() в приведённой выше программе для тестирования. Если ваша реализация верна, среде симуляции больше не нужно знать, когда программа завершит выполнение. Ей нужно лишь продолжать симуляцию, пока программа не выполнит инструкцию ebreak.
Если вы используете Chisel, вы можете использовать механизм BlackBox из Chisel, чтобы вызывать код Verilog, а затем позволить коду Verilog взаимодействовать со средой симуляции через DPI-C. Об использовании BlackBox см. соответствующую документацию.
Реализация полного NPC MiniRV
Вам нужно реализовать оставшиеся шесть инструкций MiniRV, а именно: add, lui, lw, lbu, sw, sb. Из них первые две — инструкции вычислений над целыми числами. Они очень похожи на инструкции add и li в sISA. Вы уже реализовали эти две инструкции sISA, так что реализовать их не составит труда.
Чтобы реализовать оставшиеся четыре инструкции обращения к памяти, нам нужно продумать несколько дополнительных вопросов. Инструкциям обращения к памяти нужен доступ к памяти. В отличие от выборки инструкции, инструкции обращения к памяти также могут нуждаться в записи данных в память. Простая реализация, которую мы использовали ранее, где интерфейс выборки инструкции был напрямую выведен на верхний уровень, не может корректно поддерживать инструкции обращения к памяти. Это потому, что сигналы интерфейса обращения к памяти зависят от текущей выбранной инструкции, а среда симуляции не осведомлена об этой зависимости и потому не может её корректно обработать. Чтобы решить эту проблему, мы можем использовать DPI-C для реализации обращения к памяти:
import "DPI-C" function int pmem_read(input int raddr);
import "DPI-C" function void pmem_write(
input int waddr, input int wdata, input byte wmask);
reg [31:0] rdata;
always @(*) begin
if (valid) begin // при наличии запроса на чтение/запись
rdata = pmem_read(raddr);
if (wen) begin // при наличии запроса на запись
pmem_write(waddr, wdata, wmask);
end
end
else begin
rdata = 0;
end
end
extern "C" int pmem_read(int raddr) {
// Always read 4 bytes from the address `raddr & ~0x3u` and return the result
}
extern "C" void pmem_write(int waddr, int wdata, char wmask) {
// Always write 4 bytes to the address `waddr & ~0x3u` using the write mask `wmask`
// Each bit in `wmask` represents the write enable mask for one byte of `wdata`.
// For example, `wmask = 0x3` means that only the lowest 2 bytes are written,
// while the other bytes in memory remain unchanged.
}
В этих двух функциях чтения/записи памяти мы имитируем поведение 32-битной шины: они поддерживают только 4-байтно выровненные обращения к памяти. Для операций чтения функция всегда возвращает данные, прочитанные с 4-байтно выровненного адреса. RTL-коду нужно выбрать нужную часть возвращённых данных в соответствии с адресом чтения. Такой подход призван свести к минимуму будущие изменения при реализации настоящего интерфейса шины. Вам нужно передавать правильные параметры в эти две функции при их вызове в коде Verilog и реализовать их функциональность в коде C++. Для выборки инструкции вам нужно убрать прежнюю реализацию, в которой сигналы выборки были напрямую выведены на верхний уровень, и вместо этого вызывать pmem_read() ещё раз для реализации выборки инструкции.
Реализуйте полный NPC miniRV
Добавьте в NPC оставшиеся шесть инструкций MiniRV и запустите тесты, которые вы ранее написали для minirvEMU. Определите, может ли программа успешно завершить выполнение.
