Files
APS/Labs/06. Datapath/README.md
Andrei Solodovnikov f4c0960704 Initial commit
2023-09-07 17:06:55 +03:00

152 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Лабораторная работа 6 "Тракт данных"
Микроархитектуру можно разделить на две части: тракт данных и устройство управления. По тракту данных перемещаются данные (из памяти инструкций, регистрового файла, АЛУ, памяти данных, мультиплексоров), а устройство управления (основной дешифратор команд) получает текущую инструкцию из тракта и в ответ говорит ему как именно выполнить эту инструкцию, то есть управляет тем, как эти данные будут через тракт данных проходить.
## Цель
Описать на языке **SystemVerilog** процессор с архитектурой **RISC-V**, реализовав его тракт данных, использую ранее разработанные блоки, и подключив к нему устройство управления. В рамках этого трека лабораторных работ требуется реализовать только поддержку обработки слов (то есть БЕЗ инструкций связанных с байтами и полусловами: `lh`, `lhu`, `lb`, `lbu`, `sh`, `sb`).
## Ход работы
1. Изучить микроархитектурную реализацию однотактного процессора RISC-V (без поддержки команд загрузки/сохранения байт/полуслов)
2. Реализовать тракт данных с подключенным к нему устройством управления([#задание](#задание))
3. Подготовить программу по индивидуальному заданию и загрузить ее в память инструкций
4. Сравнить результат работы процессора на модели в **Vivado** и в симуляторе программы ассемблера
## Микроархитектура RISC-V
### riscv_core
Рассмотрим микроархитектуру процессорного ядра `riscv_core`.
![../../.pic/Labs/lab_06_dp/fig_01.drawio.png](../../.pic/Labs/lab_06_dp/fig_01.drawio.png)
В отличие от реализованного ранее процессора с архитектурой Cybercobra, в данном модуле отсутствует память (она подключается извне, а значит у этого модуля должны быть сигналы интерфейса памяти).
Кроме того, в данной микроархитектуре используется пять различных видов констант (соответствующих определенным типам инструкций).
Константы `I`,`U`,`S` используются для вычисления адресов и значений. Поэтому все эти константы должны быть подключены к АЛУ. А значит теперь, для выбора значения для операндов требуются мультиплексоры, определяющие что именно будет подаваться на АЛУ.
Обратите внимание на константу `imm_U`. В отличие от всех остальных констант, она не знакорасширяется, вместо этого к ней приклеивается справа 12 нулевых бит.
Программный счетчик (`PC`) теперь также изменяется более сложным образом. Поскольку появился еще один вид безусловного перехода (`jalr`), программный счетчик может не просто увеличиться на значение константы из инструкции, но и получить совершенно новое значение в виде суммы константы и значения из регистрового файла (см. на самый левый мультиплексор схемы).
Поскольку обращение во внешнюю память требует времени, необходимо останавливать программный счетчик, чтобы до конца обращения в память не начались исполняться последующие инструкции. Для этого у программного счетчика появился управляющий сигнал `stall_i`. Программный счетчик может меняться только когда этот сигнал равен нулю (иными словами, инверсия этого сигнала является сигналом `enable` для регистра `PC`).
### riscv_unit
После реализации процессорного ядра, к нему необходимо подключить память. Это происходит в модуле `riscv_unit`.
![../../.pic/Labs/lab_06_dp/fig_02.drawio.png](../../.pic/Labs/lab_06_dp/fig_02.drawio.png)
Обратите внимание на регистр `stall`. Этот регистр и будет управлять разрешением на запись в программный счетчик `PC`. Поскольку мы используем блочную память, расположенную прямо в ПЛИС, доступ к ней осуществляется за 1 такт, а значит, что при обращении в память, нам необходимо "отключить" программный счетчик ровно на 1 такт. Если бы использовалась действительно "внешняя" память (например чип DDR3), то вместо этого регистра появилась бы другая логика, выставляющая на вход ядра `stall_i` единицу пока идет обращение в память.
## Задание
Реализовать ядро процессора `riscv_core` архитектуры RISC-V по предложенной микроархитектуре. Подключить к нему память инструкций и память данных в модуле `riscv_unit`. Проверить работу процессора с помощью программы, написанной на ассемблере RISC-V по индивидуальному заданию, которое использовалось для написания программы для процессора архитектуры CYBERcobra.
### Как инициализировать память инструкций новой программой
Поскольку теперь ваш процессор почти полностью соответствует спецификации RISC-V, вы можете пользоваться существующими компиляторами, а значит, теперь для написании программы можно воспользоваться языком ассемблера RISC-V (помните, что пока вы не поддерживаете инструкции `lh`, `lhu`, `lb`, `lbu`, `sh`, `sb`).
Обычно ассемблеры выдают код собранной программы в виде шестнадцатеричных строк. При записи программы в файл инициализации, вы должны убрать префикс `0x`, если таковой имеется, поскольку системная функция инициализации памяти `$readmemh` и так уже настроена читать в шестнадцатеричном формате.
Кроме того, поскольку каждая ячейка памяти занимает 8 бит, необходимо разбить строки инструкции на отдельные байты. Однако после того как вы это сделаете, нарушится порядок байт. Микроархитектурная реализация процессора построена с использованием порядка байт под названием **Little endian**. Это означает, что старший байт инструкции должен располагаться по старшему адресу, младший байт инструкции — по младшему (привязка к **Little endian** вытекает из двух модулей: памяти инструкций и декодера инструкций). Проблема заключается в том, что функция `$readmemh` загружает байты, начиная с младших адресов.
Предположим, мы описываем содержимое памяти инструкций и у нас есть очередная инструкция `0xDEADBEEF` (`jal`). Если она должна быть размещена в памяти, начиная с адреса `4`, то байт `EF` должен находиться по 4-ому адресу, байт `BE` — по пятому и т.п. Допустим, мы разделили байты инструкций символами переноса строк (и что строки в файле нумеруются с нуля). Тогда соответствие между строкой, байтом инструкции и адресом в памяти, где этот байт должен быть расположен будет следующим:
![../../.pic/Labs/lab_06_dp/fig_02.excel.png](../../.pic/Labs/lab_06_dp/fig_02.excel.png)
Если после разделения инструкции переносами, мы не изменим порядок байт в файле, при считывании файла САПР будет инициализировать память наоборот: ячейка с младшим адресом будет проинициализирована строкой с младшим номером. Если оставить все как есть, процессор считает из памяти инструкцию `0xEFBEADDE` (вместо jal получаем нелегальную инструкцию, т.к. младшие 2 бита не равны 1).
Чтобы данные легли в память в нужном порядке, **необходимо изменить порядок следования байт в текстовом файле**. Современные текстовые редакторы поддерживают режим множественных курсоров, что позволяет довольно быстро выполнить данную процедуру.
<details>
<summary> Пример такого редактирования </summary>
В VSCode дополнительные курсоры создаются либо через `alt+ЛКМ`, либо через `alt+ctrl+UP`, `alt+ctrl+DOWN`. Vivado так же поддерживает множественные курсоры (проведя мышью с зажатой ЛКМ вдоль нужных строк при зажатой клавише `Ctrl`).
![Пример создания и использования множественных курсоров](../../../technical/Other/Pic/multicursor_edit_example.gif)
</details>
Напишем простую программу, которая использует все типы инструкций для проверки нашего процессора. Сначала напишем программу на ассемблере:
```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, jlr
1C: jalr x15, 0x0(x6)
20: slli x7, x5, 31
24: srai x8, x7, 1
28: srli x9, x8, 29
2C: sw x1, 0x0(x5)
30: lw x10, 0x0(x5)
34: lui x11, 0xFFF80
38: auipc x12, 0x00004
3C: bne x3, x4, 0x04
40: jal x13, 0x00004
44: jalr x14, 0x0(x13)
48: jalr x15, 0x4(x15)
```
Теперь в соответствии с кодировкой инструкций переведем программу в машинные коды:
```text
00: 011101011100 00000 000 00001 0010011
04: 100010100111 00000 000 00010 0010011
08: 0000000 00001 00010 000 00011 0110011
0C: 0000000 00001 00010 111 00100 0110011
10: 0100000 00011 00100 000 00101 0110011
14: 0000001 00011 00100 000 00110 0110011
18: 00000011000000000000 01111 1101111
1C: 000000000000 00110 000 01111 1100111
20: 0000000 11111 00101 001 00111 0010011
24: 0100000 00001 00111 101 01000 0010011
28: 0000000 11101 01000 101 01001 0010011
2C: 0000000 00001 00101 010 00000 0100011
30: 000000000000 00101 010 01010 0000011
34: 11111111111110000000 01011 0110111
38: 00000000000000000100 01100 0010111
3C: 0000000 00011 00100 001 00100 1100011
40: 00000000010000000000 01101 1101111
44: 000000000000 01101 000 01110 1100111
48: 000000000100 01111 000 01111 1100111
```
Данная программа, представленная в шестнадцатиричном формате находится в файле [program.txt](program.txt).
## Порядок выполнения задания
1. Внимательно ознакомьтесь микроархитектурной реализацией. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
2. Реализуйте модуль `riscv_core`. Для этого:
1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemSystemVerilog`-файл `riscv_core.sv`.
2. Опишите в нем модуль процессор `riscv_core` с таким же именем и портами, как указано в [задании](#задание).
1. Процесс реализации модуля очень похож на процесс описания модуля cybercobra, однако теперь появляется:
1. декодер
2. дополнительные мультиплексоры и знакорасширители.
3. Создайте в проекте новый `SystemSystemVerilog`-файл `riscv_unit.sv` и опишите в нем модуль `riscv_unit`, объединяющий ядро процессора (`riscv_core`) с памятями инструкция и данных.
3. После описания модуля, его необходимо проверить с помощью тестового окружения.
1. Тестовое окружение находится [`здесь`](tb_riscv_unit.sv).
2. Программа, которой необходимо проинициализировать память инструкций находится [`здесь`](program.txt).
3. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md).
4. Перед запуском симуляции убедитесь, что выбран правильный модуль верхнего уровня.
5. **Во время симуляции убедитесь, что в логе есть сообщение о завершении теста!**
6. Вполне возможно, что после первого запуска вы столкнетесь с сообщениями о множестве ошибок. Вам необходимо [исследовать](../../Vivado%20Basics/Debug%20manual.md) эти ошибки на временной диаграмме и исправить их в вашем модуле.
4. Добавьте в проект модуль верхнего уровня ([nexys_riscv_unit.sv](board%20files/nexys_riscv_unit.sv)), соединяющий основной ваш процессор с периферией в ПЛИС. Описание работы модуля находится [здесь](board%20files).
5. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](board%20files/nexys_a7_100t.xdc)), если тот еще не был подключен, либо замените его содержимое данными из файла к этой лабораторной работе.
6. Проверьте работу процессора в ПЛИС.
---
<details>
<summary>Прочти меня, когда выполнишь.</summary>
Поздравляю, ты сделал(а) свой первый взрослый процессор! Теперь ты можешь говорить:
>Я способен(на) на всё! Я сам(а) полностью, с нуля, сделал(а) процессор с архитектурой RISC-V! Что? Не знаешь, что такое архитектура? Пф, щегол! Подрастешь узнаешь
</details>