# Лабораторная работа №7 "Тракт данных"

Микроархитектуру можно разделить на две части: тракт данных и устройство управления. По тракту данных перемещаются данные (из памяти инструкций, регистрового файла, АЛУ, памяти данных, мультиплексоров), а устройство управления (в нашем случае — декодер инструкций) получает текущую инструкцию из тракта и в ответ говорит ему как именно её выполнить, то есть управляет тем, как эти данные будут проходить через тракт данных.

## Цель

Описать на языке **SystemVerilog** процессор с архитектурой **RISC-V**, реализовав его тракт данных, используя разработанные ранее блоки, и подключив к нему устройство управления. Итогом текущей лабораторной работы станет процессор RISC-V, который пока что сможет работать с памятью данных лишь посредством 32-битных слов (то есть БЕЗ инструкций, связанных с байтами и полусловами: `lh`, `lhu`, `lb`, `lbu`, `sh`, `sb`).

## Ход работы

1. Изучить микроархитектурную реализацию однотактного процессора RISC-V (без поддержки команд загрузки/сохранения байт/полуслов)
2. Реализовать тракт данных с подключенным к нему устройством управления([#задание](#Задание))
3. Подготовить программу по индивидуальному заданию и загрузить ее в память инструкций
4. Сравнить результат работы процессора на модели в **Vivado** и в симуляторе программы ассемблера

## Микроархитектура процессорной системы

### processor_core

Рассмотрим микроархитектуру процессорного ядра `processor_core`. Микроархитектура данного модуля представлена на _рис. 1_.

```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` из [ЛР№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` используются для вычисления адресов и значений. Поэтому все эти константы должны быть подключены к АЛУ. А значит теперь, для выбора значения для операндов требуются мультиплексоры, определяющие, что именно будет подаваться на АЛУ.

> [!IMPORTANT]
> Обратите внимание на константу `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` (_рисунок 2_). Этот регистр и будет управлять разрешением на запись в программный счётчик. Поскольку мы используем блочную память, расположенную прямо в ПЛИС, доступ к ней осуществляется за 1 такт, а значит, что при обращении в память, нам необходимо "отключить" программный счётчик ровно на 1 такт. Если бы использовалась действительно "внешняя" память (например память типа DDR3), то вместо этого регистра появилась бы другая логика, выставляющая на вход ядра `stall_i` единицу пока идет обращение в память.

## Задание

1. Реализовать ядро процессора RISC-V по предложенной микроархитектуре (`processor_core`).
2. Подключить к нему память инструкций и память данных в модуле `processor_system`.
3. Проверить работу системы, с помощью тестовой программы из _листинга 1_.
4. Написать собственную программу на ассемблере RISC-V по индивидуальному заданию, которое было выбрано в ЛР№4, и проверить выполнение программы на вашей процессорной системе.

Напишем простую программу, которая использует все типы инструкций для проверки нашего процессора. Сначала напишем программу на ассемблере:

```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. После проверки работы процессора программой из _листинга 1_, вам необходимо написать собственную программу по варианту индивидуального задания, полученного в ЛР№4.
   1. Для того, чтобы перевести код из ассемблера RISC-V в двоичный код, вы можете воспользоваться онлайн-компилятором, например: https://venus.kvakil.me. Напишите код во вкладке "Editor", затем, перейдя на вкладку "Simulator", нажмите кнопку "Dump". Двоичный код будет скопирован в буфер обмена в том формате, который подходит для инициализации памяти в Vivado, замените этим кодом содержимое файла program.mem в вашем проекте. С помощью кнопок "Step" и "Run" вы можете отладить вашу программу в онлайн-симуляторе, прежде чем перейти к запуску этой программы на собственной процессорной системе. В ЛР№14 вам будет рассказано, как скомпилировать программу самостоятельно, без помощи сторонних сервисов.
7. Проверьте работоспособность вашей цифровой схемы в ПЛИС.

---

<details>
  <summary>Прочти меня, когда выполнишь.</summary>
  Поздравляю, ты сделал(а) свой первый взрослый процессор! Теперь ты можешь говорить:

 >Я способен(на) на всё! Я сам(а) полностью, с нуля, сделал(а) процессор с архитектурой RISC-V! Что? Не знаешь, что такое архитектура? Пф, щегол! Подрастешь – узнаешь

</details>

## Список источников

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).
