ЛР8. Дополнение методички

Closes #18
This commit is contained in:
Andrei Solodovnikov
2024-07-04 16:44:45 +03:00
parent f6d2e86c8d
commit 00880bd3bd
7 changed files with 40 additions and 17 deletions

View File

@@ -2,11 +2,9 @@
Итогом шестой лабораторной работы стал практически завершенный процессор архитектуры RISC-V. Особенностью той реализации процессора было отсутствие поддержки инструкций `LB`, `LBU`, `SB`, `LH`, `LHU`, `SH`. Тому было две причины:
- в третьей лабораторной работе была реализована память данных, не поддерживавшая возможность обновления отдельных байт;
- подключенный к памяти данных сигнал `byte_enable_i` был аппаратно зафиксирован на значении `4'b1111`, но на самом деле этим сигналом должен кто-то управлять;
- необходимо подготовить считанные из памяти полуслова / байты для записи в регистровый файл.
В седьмой лабораторной работе была реализована новая память данных, в которой можно управлять записью в отдельные байты посредством управляющего сигнала `byte_enable`, каждый отдельный бит которого является разрешением записи в соответствующий байт слова. Необходимо управлять этим сигналом, используя интерфейс памяти процессора.
Для этих целей используется специальный модуль — **Блок загрузки и сохранения** (**Load and Store Unit**, **LSU**).
## Цель
@@ -41,9 +39,11 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр
На входной порт `core_addr_i` от процессора поступает адрес ячейки памяти, к которой будет произведено обращение. Намеренье процессора обратиться к памяти (и для чтения, и для записи) отражается выставлением сигнала `core_req_i` в единицу. Если процессор собирается записывать в память, то сигнал `core_we_i` выставляется в единицу, а сами данные, которые следует записать, поступают от него на вход `core_wd_i`. Если процессор собирается читать из памяти, то сигнал `core_we_i` находится в нуле, а считанные данные подаются для процессора на выход `core_rd_o`.
Инструкции `LOAD` и `STORE` в **RV32I** поддерживают обмен 8-битными, 16-битными или 32-битными значениями, однако в самом процессоре происходит работа только с 32-битными числами, поэтому загружая байты или полуслова из памяти их необходимо предварительно расширить до 32-битного значения. Для выбора разрядности на вход **LSU** подается сигнал `core_size_i`, принимающий следующие значения:
Инструкции `LOAD` и `STORE` в **RV32I** поддерживают обмен 8-битными, 16-битными или 32-битными значениями, однако в самом процессоре происходит работа только с 32-битными числами, поэтому загружая байты или полуслова из памяти их необходимо предварительно расширить до 32-битного значения. Расширять значения можно как нулями (если считанное число интерпретируется как беззнаковое), либо знаковым битом (если число интерпретируется как знаковое). Во время записи данных в память, они не расширяются, поскольку в основной памяти есть возможность записи отдельных байт. Таким образом, различать знаковые и беззнаковые числа необходимо только на этапе загрузки, но не сохранения.
| Название |Значение| Пояснение |
Для выбора разрядности и формата представления числа на вход **LSU** подается сигнал `core_size_i`, принимающий следующие значения (для удобства использования, данные значения определены в виде параметров в пакете `decoder_pkg`):
| Параметр |Значение| Пояснение |
|----------|--------|-------------------------------|
| LDST_B | 3'd0 |Знаковое 8-битное значение |
| LDST_H | 3'd1 |Знаковое 16-битное значение |
@@ -51,8 +51,6 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр
| LDST_BU | 3'd4 |Беззнаковое 8-битное значение |
| LDST_HU | 3'd5 |Беззнаковое 16-битное значение |
Формат представления числа (является оно **знаковым** или **беззнаковым**) имеет значение только для операций типа `LOAD`: если число знаковое, то производится расширение знака до 32 бит, а если беззнаковое расширение нулями.
Для операций типа `STORE` формат представления чисел не важен, для них `core_size_i` сможет принимать значение только от 0 до 2.
Выходной сигнал `core_stall_o` нужен для остановки программного счетчика. Ранее логика этого сигнала временно находилась в модуле `riscv_unit` — теперь она займет свое законное место в модуле **LSU**.
@@ -61,7 +59,7 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр
В параграфе описывается организация внешней памяти, и то, как к ней подключается **LSU**.
Память данных имеет 32-битную разрядность ячейки памяти и поддерживает побайтовую адресацию. Это значит, что существует возможность записи значения по одному байту в пределах одного слова (4-байтовой ячейки памяти). Для указания на необходимые байты интерфейс к памяти предусматривает использование 4-битного сигнала `mem_be_o`, подаваемого вместе с адресом слова `mem_addr_o`. Позиции битов 4-битного сигнала соответствуют позициям байтов в слове. Если конкретный бит `mem_be_o` равен 1, то соответствующий ему байт будет записан в память. Данные для записи подаются на выход `mem_wd_o`. На результат чтения из памяти состояние `mem_be_o` не влияет, так как чтение производится всегда по 32-бита.
Память данных имеет 32-битную разрядность ячейки памяти и поддерживает побайтовую адресацию. Это значит, что существует возможность обновить любой байт пределах одного слова (4-байтовой ячейки памяти), не изменяя слова целиком. Для указания на обновляемые байты интерфейс к памяти предусматривает использование 4-битного сигнала `mem_be_o`, подаваемого вместе с адресом слова `mem_addr_o`. Позиции битов 4-битного сигнала соответствуют позициям байтов в слове. Если конкретный бит `mem_be_o` равен 1, то соответствующий ему байт в памяти будет обновлен. Данные для записи подаются на выход `mem_wd_o`. На результат чтения из памяти состояние `mem_be_o` не влияет, так как чтение производится всегда по 32-бита.
После получения запроса на чтение/запись из ядра, **LSU** перенаправляет запрос в память данных, взаимодействие осуществляется следующими сигналами:
@@ -116,7 +114,7 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр
### mem_be_o
По запросу на запись (`core_req_i == 1`, `core_we_i == 1`), если `core_size_i` соответствует инструкции записи байта (`SB`), то в сигнале `mem_be_o` бит с индексом равным значению двух младших бит адреса `core_addr_i` должен быть равен единице.
Данный сигнал принимает ненулевые значения только по запросу на запись (`core_req_i == 1`, `core_we_i == 1`), во время которого происходит мультиплексирование сигнала `core_size_i`. Если `core_size_i` соответствует инструкции записи байта (`LDST_B`, 3'd0), то в сигнале `mem_be_o` бит с индексом равным значению двух младших бит адреса `core_addr_i` должен быть равен единице.
Допустим, пришел запрос на запись байта по адресу 18:
@@ -127,9 +125,13 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр
В данном случае, необходимо выставить единицу во втором (считая с нуля) бите сигнала `mem_be_o` (поскольку значение двух младших бит `core_addr_i` равно двум): `mem_be_o == 4'b0100`.
Если пришел запрос на запись полуслова (`SH`, `core_size_i == LDST_H`), то в сигнале `mem_be_o` необходимо выставить в единицу либо два старших, либо два младших бита (в зависимости от `core_addr[1]`)
Если пришел запрос на запись полуслова (`core_size_i == LDST_H`), то в сигнале `mem_be_o` необходимо выставить в единицу либо два старших, либо два младших бита (в зависимости от `core_addr[1]`: если `core_addr[1] == 1`, то в двух старших битах, если `core_addr[1] == 0`, то в двух младших).
Если пришел запрос на запись слова (`SW`, `core_size_i == LDST_W`), то в сигнале `mem_be_o` необходимо выставить в единицу все биты.
Если пришел запрос на запись слова (`core_size_i == LDST_W`), то в сигнале `mem_be_o` необходимо выставить в единицу все биты.
![../../.pic/Labs/lab_08_lsu/fig_02.wavedrom.svg](../../.pic/Labs/lab_08_lsu/fig_02.wavedrom.svg)
_Рисунок 2. Временна́я диаграмма запросов на запись со стороны ядра и сигнала mem\_be\_o._
### mem_wd_o
@@ -156,11 +158,15 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр
В случае записи слова (`core_size_i == LDST_W`), сигнал `mem_wd_o` будет повторять сигнал `core_wd_i`.
![../../.pic/Labs/lab_08_lsu/fig_03.wavedrom.svg](../../.pic/Labs/lab_08_lsu/fig_03.wavedrom.svg)
_Рисунок 3. Временна́я диаграмма запросов на запись со стороны ядра и сигнала mem\_wd\_o._
### core_rd_o
Сигнал `core_rd_o` — это сигнал, который будет содержать данные для записи в регистровый файл процессора во время инструкций загрузки из памяти (`LW`, `LH`, `LHU`, `LB`, `LBU`). Чтобы понять, как управлять этим сигналом, нужно понять, что происходит во время этих инструкций.
Предположим, по адресам `16-19` лежит слово `32'hA55A_1881`. Чтение по любому из адресов 16, 17, 18, 19 вернет это слово на входном сигнале `mem_rd_i`. В случае инструкции `LB` (`core_size_i == LDST_B`) по адресу 19 (чтение байта, который интерпретируется как знаковое число), в регистровый файл должно быть записано значение `32'hFFFF_FFA5`, поскольку по 19-ому адресу лежит байт `A5`, который затем будет знакорасширен. В случае той же самой инструкции, но по адресу 18, в регистровый файл будет записано значение `32'h0000_005A` (знакорасширенный байт `5A`, расположенный по 18ому адресу).
Предположим, по адресам `16-19` лежит слово `32'hA55A_1881` (см. _рис. 4_). Чтение по любому из адресов 16, 17, 18, 19 вернет это слово на входном сигнале `mem_rd_i`. В случае инструкции `LB` (`core_size_i == LDST_B`) по адресу 19 (чтение байта, который интерпретируется как знаковое число), в регистровый файл должно быть записано значение `32'hFFFF_FFA5`, поскольку по 19-ому адресу лежит байт `A5`, который затем будет знакорасширен. В случае той же самой инструкции, но по адресу 18, в регистровый файл будет записано значение `32'h0000_005A` (знакорасширенный байт `5A`, расположенный по 18ому адресу).
Получить нужный байт можно из входного сигнала `mem_rd_i`, но чтобы понять какие биты этого сигнала нас интересуют, необходимо посмотреть на входные сигналы `core_size_i` и `core_addr_i[1:0]`. `core_size_i` сообщит конкретный тип инструкции (сколько нужно взять байт из считанного слова), а `core_addr_i[1:0]` укажет номер начального байта, который нужно взять из `mem_rd_i`.
@@ -170,6 +176,10 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр
Для инструкций `LW` на выход `core_rd_o` пойдут данные `mem_rd_i` без изменений.
![../../.pic/Labs/lab_08_lsu/fig_04.wavedrom.svg](../../.pic/Labs/lab_08_lsu/fig_04.wavedrom.svg)
_Рисунок 4. Временна́я диаграмма запросов на чтение со стороны ядра и сигнала core\_rd\_o._
### core_stall_o
Сигнал `core_stall_o` запрещает менять значение программного счетчика на время обращения в память. Этот сигнал должен:
@@ -177,11 +187,11 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр
- стать равным единице в тот же такт, когда пришел сигнал `core_req_i`
- удерживать это значение до тех пор, пока не придет сигнал `mem_ready_i`, но не менее 1 такта (т.е. даже если сигнал `mem_ready_i` будет равен единице, `core_req_i` должен подняться хотя бы на 1 такт).
Для реализации подобного функционала вам потребуется вспомогательный регистр `stall_reg`, каждый такт записывающий значение выхода `core_stall_o` и таблица истинности для этого выхода, представленная на _рис. 2_.
Для реализации подобного функционала вам потребуется вспомогательный регистр `stall_reg`, каждый такт записывающий значение выхода `core_stall_o` и таблица истинности для этого выхода, представленная на _рис. 5_.
![../../.pic/Labs/lab_08_lsu/fig_02.png](../../.pic/Labs/lab_08_lsu/fig_02.png)
![../../.pic/Labs/lab_08_lsu/fig_05.png](../../.pic/Labs/lab_08_lsu/fig_05.png)
_Рисунок 2. Таблица истинности выхода `core_stall_o`._
_Рисунок 5. Таблица истинности выхода `core_stall_o`._
---
@@ -215,9 +225,9 @@ module riscv_lsu(
```
![../../.pic/Labs/lab_08_lsu/fig_03.drawio.svg](../../.pic/Labs/lab_08_lsu/fig_03.drawio.svg)
![../../.pic/Labs/lab_08_lsu/fig_05.drawio.svg](../../.pic/Labs/lab_08_lsu/fig_06.drawio.svg)
_Рисунок 3. Структурная схема модуля `riscv_lsu`._
_Рисунок 6. Структурная схема модуля `riscv_lsu`._
---
@@ -228,6 +238,7 @@ _Рисунок 3. Структурная схема модуля `riscv_lsu`._
1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `riscv_lsu.sv`.
2. Опишите в нем модуль `riscv_lsu` с таким же именем и портами, как указано в [задании](#задание).
1. При описании обратите внимание на то, что большая часть модуля является чисто комбинационной. В этом плане реализация модуля будет частично похожа на реализацию декодера.
1. При описании мультиплексоров, управляемых сигналом core_size_i посредством конструкции `case`, не забывайте описать блок `default`, иначе вы получите защелку!
2. Однако помимо комбинационной части, в модуле будет присутствовать и один регистр.
3. После описания модуля, его необходимо проверить с помощью тестового окружения.
1. Тестовое окружение находится [здесь](tb_lsu.sv).