# Лабораторная работа №7 "Тракт данных" Микроархитектуру можно разделить на две части: тракт данных и устройство управления. По тракту данных перемещаются данные (из памяти инструкций, регистрового файла, АЛУ, памяти данных, мультиплексоров), а устройство управления (в нашем случае — декодер инструкций) получает текущую инструкцию из тракта и в ответ говорит ему как именно её выполнить, то есть управляет тем, как эти данные будут через проходить тракт данных. ## Цель Описать на языке **SystemVerilog** процессор с архитектурой **RISC-V**, реализовав его тракт данных, используя разработанные ранее блоки, и подключив к нему устройство управления. Итогом текущей лабораторной работы станет процессор RISC-V, который пока что сможет работать с памятью данных лишь посредством 32-битных слов (то есть БЕЗ инструкций, связанных с байтами и полусловами: `lh`, `lhu`, `lb`, `lbu`, `sh`, `sb`). ## Ход работы 1. Изучить микроархитектурную реализацию однотактного процессора RISC-V (без поддержки команд загрузки/сохранения байт/полуслов) 2. Реализовать тракт данных с подключенным к нему устройством управления([#задание](#задание)) 3. Подготовить программу по индивидуальному заданию и загрузить ее в память инструкций 4. Сравнить результат работы процессора на модели в **Vivado** и в симуляторе программы ассемблера ## Микроархитектура RISC-V ### processor_core Рассмотрим микроархитектуру процессорного ядра `processor_core`. Данный модуль обладает следующим прототипом и микроархитектурой: ```Verilog module processor_core ( input logic clk_i, input logic rst_i, input logic stall_i, input logic [31:0] instr_i, input logic [31:0] mem_rd_i, output logic [31:0] instr_addr_o, output logic [31:0] mem_addr_o, output logic [ 2:0] mem_size_o, output logic mem_req_o, output logic mem_we_o, output logic [31:0] mem_wd_o ); endmodule ``` ![../../.pic/Labs/lab_07_dp/fig_01.drawio.svg](../../.pic/Labs/lab_07_dp/fig_01.drawio.svg) _Рисунок 1. Микроархитектура ядра процессора RISC-V._ Предложенная микроархитектура имеет схожую структуру c процессором `CYBERcobra 3000 Pro 2.0` из [ЛР№4](../04.%20Primitive%20programmable%20device/), с некоторыми изменениями. В первую очередь изменились входы и выходы процессора: - память инструкций вынесена наружу процессора, таким образом, у процессора появляются входы и выходы: `instr_addr_o` и `instr_i`; - помимо прочего, у модуля появились сигналы интерфейса памяти данных, реализованной в [ЛР№6](../06.%20Main%20memory/): - `mem_addr_o` — адрес внешней памяти; - `mem_req_o` — запрос на обращение во внешнюю память; - `mem_size_o` — размер данных при обращении в память; - `mem_we_o` — сигнал разрешения записи во внешнюю память; - `mem_wd_o` — данные для записи во внешнюю память; - `mem_rd_i` — считанные из внешней памяти данные; Эти сигналы используются при выполнении инструкций загрузки (сохранения) информации из (в) памяти данных. - еще у процессора появился вход `stall_i`, приостанавливающий обновление программного счётчика. Кроме того, в данной микроархитектуре используется пять различных видов констант (соответствующих определенным типам инструкций). Константы `I`, `U`, `S` используются для вычисления адресов и значений. Поэтому все эти константы должны быть подключены к АЛУ. А значит теперь, для выбора значения для операндов требуются мультиплексоры, определяющие, что именно будет подаваться на АЛУ. Обратите внимание на константу `imm_U`. В отличие от всех остальных констант, она не знакорасширяется, вместо этого к ней "приклеивается" справа 12 нулевых бит. Константы `B` и `J` используются для условного и безусловного перехода (в киберкобре для этого использовалась одна константа `offset`). Программный счётчик (`PC`) теперь также изменяется более сложным образом. Поскольку появился еще один вид безусловного перехода (`jalr`), программный счётчик может не просто увеличиться на значение константы из инструкции, но и получить совершенно новое значение в виде суммы константы и значения из регистрового файла (см. на самый левый мультиплексор _рис. 1_). Обратите внимание, что младший бит этой суммы должен быть обнулен — таково требование спецификации [[1](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf), стр. 28]. Поскольку обращение во внешнюю память требует времени, необходимо приостанавливать программный счётчик, чтобы до конца обращения в память не начались исполняться последующие инструкции. Для этого у программного счётчика появился управляющий сигнал `stall_i`. Программный счётчик может меняться только когда этот сигнал равен нулю (иными словами, инверсия этого сигнала является сигналом `enable` для регистра `PC`). ### processor_system После реализации процессорного ядра, к нему необходимо подключить память. Это происходит в модуле `processor_system`. ```Verilog module processor_system( input logic clk_i, input logic rst_i ); endmodule ``` ![../../.pic/Labs/lab_07_dp/fig_02.drawio.svg](../../.pic/Labs/lab_07_dp/fig_02.drawio.svg) _Рисунок 2. Микроархитектура процессорной системы._ Обратите внимание на регистр `stall`. Этот регистр и будет управлять разрешением на запись в программный счётчик `PC`. Поскольку мы используем блочную память, расположенную прямо в ПЛИС, доступ к ней осуществляется за 1 такт, а значит, что при обращении в память, нам необходимо "отключить" программный счётчик ровно на 1 такт. Если бы использовалась действительно "внешняя" память (например чип DDR3), то вместо этого регистра появилась бы другая логика, выставляющая на вход ядра `stall_i` единицу пока идет обращение в память. ## Задание Реализовать ядро процессора `processor_core` архитектуры RISC-V по предложенной микроархитектуре. Подключить к нему память инструкций и память данных в модуле `processor_system`. Проверить работу процессора с помощью программы, написанной на ассемблере RISC-V по индивидуальному заданию, которое использовалось для написания программы для процессора архитектуры CYBERcobra. Напишем простую программу, которая использует все типы инструкций для проверки нашего процессора. Сначала напишем программу на ассемблере: ```assembly 00: addi x1, x0, 0x75С 04: addi x2, x0, 0x8A7 08: add x3, x1, x2 0C: and x4, x1, x2 10: sub x5, x4, x3 14: mul x6, x3, x4 // неподдерживаемая инструкция 18: jal x15, 0x00050 // прыжок на адрес 0x68 1C: jalr x15, 0x0(x6) 20: slli x7, x5, 31 24: srai x8, x7, 1 28: srli x9, x8, 29 2C: lui x10, 0xfadec 30: addi x10, x10,-1346 34: sw x10, 0x0(x4) 38: sh x10, 0x6(x4) 3C: sb x10, 0xb(x4) 40: lw x11, 0x0(x4) 44: lh x12, 0x0(x4) 48: lb x13, 0x0(x4) 4С: lhu x14, 0x0(x4) 50: lbu x15, 0x0(x4) 54: auipc x16, 0x00004 58: bne x3, x4, 0x08 // перескок через 5С: // нелегальную нулевую инструкцию 60: jal x17, 0x00004 64: jalr x14, 0x0(x17) 68: jalr x18, 0x4(x15) ``` _Листинг 1. Пример программы на ассемблере._ Теперь в соответствии с кодировкой инструкций переведем программу в машинные коды: ```text 00: 011101011100 00000 000 00001 0010011 (0x75C00093) 04: 100010100111 00000 000 00010 0010011 (0x8A700113) 08: 0000000 00010 00001 000 00011 0110011 (0x002081B3) 0C: 0000000 00010 00001 111 00100 0110011 (0x0020F233) 10: 0100000 00011 00100 000 00101 0110011 (0x403202B3) 14: 0000001 00100 00011 000 00110 0110011 (0x02418333) 18: 00000101000000000000 01111 1101111 (0x050007EF) 1C: 000000000000 00110 000 01111 1100111 (0x000307E7) 20: 0000000 11111 00101 001 00111 0010011 (0x01F29393) 24: 0100000 00001 00111 101 01000 0010011 (0x4013D413) 28: 0000000 11101 01000 101 01001 0010011 (0x01D45493) 2C: 11111010110111101100 01010 0110111 (0xFADEC537) 30: 101010111110 01010 000 01010 0010011 (0xABE50513) 34: 0000000 01010 00100 010 00000 0100011 (0x00A22023) 38: 0000000 01010 00100 001 00110 0100011 (0x00A21323) 3C: 0000000 01010 00100 000 01011 0100011 (0x00A205A3) 40: 000000000000 00100 010 01011 0000011 (0x00022583) 44: 000000000000 00100 001 01100 0000011 (0x00021603) 48: 000000000000 00100 000 01101 0000011 (0x00020683) 4C: 000000000000 00100 101 01110 0000011 (0x00025703) 50: 000000000000 00100 100 01111 0000011 (0x00024783) 54: 00000000000000000100 10000 0010111 (0x00004817) 58: 0000000 00011 00100 001 01000 1100011 (0x00321463) 5C: 00000000 00000000 00000000 00000000 (0x00000000) 60: 00000000010000000000 10001 1101111 (0x004008EF) 64: 000000000000 10001 000 01110 1100111 (0x00088767) 68: 000000000100 01111 000 10010 1100111 (0x00478967) ``` _Листинг 2. Программа из Листинга 1, представленная в машинных кодах._ Данная программа, представленная в шестнадцатеричном формате находится в файле [program.mem](program.mem). ## Порядок выполнения задания 1. Внимательно ознакомьтесь микроархитектурной реализацией процессорного ядра. В случае возникновения вопросов, проконсультируйтесь с преподавателем. 2. Замените файл `program.mem` в `Design Sources` проекта новым файлом [program.mem](program.mem), приложенном в данной лабораторной работе. Данный файл содержит программу из _листинга 1_. 3. Опишите модуль процессорного ядра с таким же именем и портами, как указано в задании. 1. Процесс реализации модуля очень похож на процесс описания модуля cybercobra, однако теперь появляется: 1. декодер 2. дополнительные мультиплексоры и знакорасширители. 2. Сперва рекомендуется создать все провода, которые будут подключены к входам и выходам каждого модуля на схеме. 3. Затем необходимо создать экземпляры модулей. 4. Также необходимо создать 32-разрядные константы I, U, S, B и J-типа и программный счётчик. 5. После необходимо описать логику, управляющую созданными в п. 3.2 проводами. 6. В конце останется описать логику работы программного счётчика. 4. Опишите модуль процессорной системы, объединяющий ядро процессора (`processor_core`) с памятями инструкция и данных. 1. Опишите модуль с таким же именем и портами, как указано в задании. 2. **При создании объекта модуля `processor_core` в модуле `processor_system` вы должны использовать имя сущности `core` (т.е. создать объект в виде: `processor_core core(...`)**. 5. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_07.tb_processor_system.sv`](lab_07.tb_processor_system.sv). 1. Перед запуском симуляции убедитесь, что выбран правильный модуль верхнего уровня в `Simulation Sources`. 2. Как и в случае с проверкой процессора архитектуры CYBERcobra, вам не будет сказано пройден тест или нет. Вам необходимо самостоятельно, такт за тактом проверить что процессор правильно выполняет описанные в _Листинге 1_ инструкции (см. порядок выполнения задания ЛР№4). Для этого, необходимо сперва самостоятельно рассчитать что именно должна сделать данная инструкция, а потом проверить что процессор сделал именно это. 6. Проверьте работоспособность вашей цифровой схемы в ПЛИС. ---
Прочти меня, когда выполнишь. Поздравляю, ты сделал(а) свой первый взрослый процессор! Теперь ты можешь говорить: >Я способен(на) на всё! Я сам(а) полностью, с нуля, сделал(а) процессор с архитектурой RISC-V! Что? Не знаешь, что такое архитектура? Пф, щегол! Подрастешь – узнаешь
## Список источников 1. [The RISC-V Instruction Set Manual Volume I: Unprivileged ISA](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf).