ЛР12. Обновление методички

This commit is contained in:
Andrei Solodovnikov
2023-09-22 11:02:58 +03:00
parent 62e8e1a93e
commit e16cb09099
13 changed files with 152 additions and 115 deletions

View File

@@ -1,4 +1,4 @@
# Лабораторная работа 7 "Периферийные устройства"
# Лабораторная работа 12 "Периферийные устройства"
На прошлой лабораторной работе вы реализовали свой собственный RISC-V процессор. Однако пока что он находится "в вакууме" и никак не связан с внешним миром. Для исправления этого недостатка вами будет реализована системная шина, через которую к процессору смогут подключаться различные периферийные устройства.
@@ -35,15 +35,15 @@
### Адресное пространство
Архитектура RISC-V подразумевает использование совместного адресного пространства, это значит, что в лабораторной работе будет использована единая шина для подключения памяти и регистров управления периферийными устройствами. При обращении по одному диапазону адресов процессор будет
попадать в память, при обращении по другим взаимодействовать с регистрами управления/статуса периферийного устройства. Например, можно разделить 32-битное адресное пространство на 256 частей, отдав старшие 8 бит адреса под указание конкретного периферийного устройства. Тогда каждое из периферийных устройств получит 24-битное адресное пространство (16 MiB). Допустим, мы распределили эти части адресного пространства в следующем порядке (от младшего диапазона адресов к старшему):
Архитектура RISC-V подразумевает использование совместного адресного пространства, это значит, что в лабораторной работе будет использована единая шина для подключения памяти и регистров управления периферийными устройствами. При обращении по одному диапазону адресов процессор будет попадать в память, при обращении по другим взаимодействовать с регистрами управления/статуса периферийного устройства. Например, можно разделить 32-битное адресное пространство на 256 частей, отдав старшие 8 бит адреса под указание конкретного периферийного устройства. Тогда каждое из периферийных устройств получит 24-битное адресное пространство (16 MiB). Допустим, мы распределили эти части адресного пространства в следующем порядке (от младшего диапазона адресов к старшему):
0. Переключатели
1. Светодиоды
2. Клавиатура PS/2
3. Семисегментные индикаторы
4. UART-приемник
5. UART-передатчик
0. Память данных
1. Переключатели
2. Светодиоды
3. Клавиатура PS/2
4. Семисегментные индикаторы
5. UART-приемник
6. UART-передатчик
В таком случае, если мы захотим обратиться в четвертый регистр семисегментных индикаторов, мы должны будем использовать адрес `0x03000004`. Старшие 8 бит (`0x03`) определяют выбранное периферийное устройство, оставшиеся 24 бита определяют конкретный адрес в адресном пространстве этого устройства.
@@ -51,11 +51,13 @@
![../../.pic/Labs/lab_12_periph/fig_01.drawio.png](../../.pic/Labs/lab_12_periph/fig_01.drawio.png)
_Рисунок 1. Итоговая структура процессорной системы_
### Активация выбранного устройства
В зависимости от интерфейса используемой шины, периферийные устройства либо знают какой диапазон адресов им выделен (например, в интерфейсе I²C), либо нет (интерфейс APB). В первом случае, устройство понимает что к нему обратились непосредственно по адресу в данном обращении, во втором случае — по специальному сигналу.
На приведенной выше схеме используется второй вариант — устройство понимает, что к нему обратились по специальному сигналу `req_i`. Данный сигнал формируется из двух частей: сигнала `req` исходящего из процессорного ядра (сигнал о том, обращение в память вообще происходит) и специального сигнала-селектора исходящего из 256-разрядной шины. Формирование значения на этой шине происходит с помощью [унитарного](https://ru.wikipedia.org/wiki/Унитарный_код) ([one-hot](https://en.wikipedia.org/wiki/One-hot)) кодирования. Процесс кодирования достаточно прост. В любой момент времени на выходной шине должен быть **ровно один** бит, равный единице. Индекс этого бита совпадает со значением старших восьми бит адреса. Поскольку для восьмибитного значения существует 256 комбинаций значений, именно такая разрядность будет на выходе кодировщика. Это означает, что в данной системе можно связать процессор с 256 устройствами (одним из которых будет память данных).
На _рис. 1_ используется второй вариант — устройство понимает, что к нему обратились по специальному сигналу `req_i`. Данный сигнал формируется из двух частей: сигнала `req` исходящего из процессорного ядра (сигнал о том, обращение в память вообще происходит) и специального сигнала-селектора исходящего из 256-разрядной шины. Формирование значения на этой шине происходит с помощью [унитарного](https://ru.wikipedia.org/wiki/Унитарный_код) ([one-hot](https://en.wikipedia.org/wiki/One-hot)) кодирования. Процесс кодирования достаточно прост. В любой момент времени на выходной шине должен быть **ровно один** бит, равный единице. Индекс этого бита совпадает со значением старших восьми бит адреса. Поскольку для восьмибитного значения существует 256 комбинаций значений, именно такая разрядность будет на выходе кодировщика. Это означает, что в данной системе можно связать процессор с 256 устройствами (одним из которых будет память данных).
Реализация такого кодирования предельно проста:
@@ -64,7 +66,7 @@
* ...
* Двести пятьдесят пятый бит шины будет равен единице только если `data_addr_o[31:24] = 8'd255`.
Для реализации такого кодирования достаточно выполнить сдвиг влево `255'd1` на значение `data_addr_o[31:24]`.
Для реализации такого кодирования достаточно выполнить сдвиг влево константы `255'd1` на значение `data_addr_o[31:24]`.
### Дополнительные правки модуля riscv_unit
@@ -91,7 +93,7 @@ module riscv_unit(
endmodule
```
Эти входы порты подключены к одноименным портам ваших контроллеров периферии (речь идет только о реализуемых вами контроллерах, остальные порты должны остаться неподключенными).
Эти порты подключены к одноименным портам ваших контроллеров периферии (речь идет только о реализуемых вами контроллерах, остальные порты должны остаться неподключенными).
Обратите внимание на то, что изменился сигнал сброса (`resetn_i`). Буква `n` на конце означает, что сброс работает по уровню `0` (когда сигнал равен нулю — это сброс, когда единице — не сброс).
@@ -99,8 +101,8 @@ endmodule
Для этого необходимо:
1. Подключить файл [`sys_clk_rst_gen.v`](sys_clk_rst_gen.v) в ваш проект.
2. Подключить этот модуль внутри `riscv_unit` следующим образом:
1. Подключить файл [`sys_clk_rst_gen.sv`](sys_clk_rst_gen.sv) в ваш проект.
2. Подключить этот модуль в начале описания модуля `riscv_unit` следующим образом:
```SystemVerilog
logic sysclk, rst;
@@ -113,19 +115,19 @@ sys_clk_rst_gen divider(.ex_clk_i(clk_i),.ex_areset_n_i(resetn_i),.div_i(10),.sy
## Задание
В рамках данной лабораторной работы необходимо реализовать модули-контроллеры двух периферийных устройств, реализующих управление в соответствии с приведенной ниже картой памяти и встроить их в процессорную систему, используя используя схему приведенную выше. На карте приведено шесть периферийных устройств, вам необходимо взять только два из них в зависимости от вашего варианта индивидуального задания (ИЗ) по следующему правилу:
В рамках данной лабораторной работы необходимо реализовать модули-контроллеры двух периферийных устройств, реализующих управление в соответствии с приведенной ниже картой памяти и встроить их в процессорную систему, используя используя [_рис. 1_](../../.pic/Labs/lab_12_periph/fig_01.drawio.png). На карте приведено шесть периферийных устройств, вам необходимо взять только два из них в зависимости от вашего варианта индивидуального задания (ИЗ) по следующему правилу:
1. Те кому достались варианты ИЗ 1-5 должны реализовать контроллеры переключателей и светодиодов.
2. Те кому достались варианты ИЗ 6-15 должны реализовать контроллеры клавиатуры PS/2 и семисегментных индикаторов.
3. Те кому достались варианты BP 16-29 должны реализовать контроллеры приемника и передатчика UART.
![Карта памяти](../../.pic/Labs/riscv_periph_memory_map.png)
![Карта памяти](../../.pic/Labs/lab_12_periph/fig_02.png)
Работа с картой осуществляется следующим образом. Под названием каждого периферийного устройства указана старшая часть адреса (чему должны быть равны старшие 8 бит адреса, чтобы было сформировано обращение к данному периферийному устройству). Например, для переключателей это значение равно `0x01`, для светодиодов `0x02` и т.п.
В самом левом столбце указаны используемые/неиспользуемые адреса в адресном пространстве данного периферийного устройства. Например для переключателей есть только один используемый адрес: `0x000000`. Его функциональное назначение и разрешения на доступ указаны в столбце соответствующего периферийного устройства. Возвращаясь к адресу `0x000000` для переключателей мы видим следующее:
* (R) означает что разрешен доступ только на чтение (операция записи по этому адресу должна игнорироваться вашим контроллером).
* "Выставленное на переключателях значение" означает ровно то что и означает. Если процессор выполняет операцию чтения по адресу 0x01000000 (`0x01` (старшая часть адреса переключателей) + `0x000000` (младшая часть адреса для получения выставленного на переключателях значения) ), то контроллер должен выставить на выходной сигнал RD значение на переключателях (о том как получить это значение будет рассказано чуть позже).
* **(R)** означает что разрешен доступ только на чтение (операция записи по этому адресу должна игнорироваться вашим контроллером).
* **"Выставленное на переключателях значение"** означает ровно то что и означает. Если процессор выполняет операцию чтения по адресу `0x01000000` (`0x01` (старшая часть адреса переключателей) + `0x000000` (младшая часть адреса для получения выставленного на переключателях значения) ), то контроллер должен выставить на выходной сигнал `RD` значение на переключателях (о том как получить это значение будет рассказано чуть позже).
Рассмотрим еще один пример. При обращении по адресу `0x02000024` (`0x02` (старшая часть адреса контроллера светодиодов) + `0x000024` (младшая часть адреса для доступа на запись к регистру сброса) ) должна произойти запись в регистр сброса, который должен сбросить значения в регистре управления зажигаемых светодиодов и регистре управления режимом "моргания" светодиодов (подробнее о том как должны работать эти регистры будет ниже).
@@ -145,14 +147,15 @@ sys_clk_rst_gen divider(.ex_clk_i(clk_i),.ex_areset_n_i(resetn_i),.div_i(10),.sy
3. Реализуйте модули контроллеров периферии. Имена модулей и их порты будут указаны в [описании контроллеров](#описание-контроллеров-периферийных-устройств). Пример разработки контроллера приведен [здесь](../../Basic%20Verilog%20structures/Controllers.md).
4. Обновите модуль `riscv_unit` в соответствии с разделом ["Дополнительные правки модуля riscv_unit"](#дополнительные-правки-модуля-riscv_unit).
1. Подключите в проект файл `sys_clk_rst_gen.sv`.
2. Добавьте в модуль `riscv_unit` входы и выходы периферии.
3. Подключите к модулю `riscv_unit` модуль `sys_clk_rst_gen` скопировав приведенный фрагмент кода.
2. Добавьте в модуль `riscv_unit` входы и выходы периферии. Необходимо добавить порты даже тех периферийных устройств, которые вы не будете реализовывать.
3. Создайте в начале описания модуля `riscv_unit` экземпляр модуля `sys_clk_rst_gen`, скопировав приведенный фрагмент кода.
4. Замените подключение тактового сигнала исходных подмодулей `riscv_unit` на появившийся сигнал `sysclk`. Убедитесь, что на модули имеющие сигнал сброса приходит сигнал `rst`.
5. Интегрируйте модули контроллеров периферии в процессорную систему по приведенной схеме руководствуясь старшими адресами контроллеров, представленными на карте памяти. Это означает, что если вы реализуете контроллер светодиодов, на его входов `req_i` должна подаваться единица в случае если `mem_req_o == 1` и старшие 8 бит адреса равны `0x02`.
5. Интегрируйте модули контроллеров периферии в процессорную систему по приведенной схеме руководствуясь старшими адресами контроллеров, представленными на карте памяти ([_рис. 2_](../../.pic/Labs/lab_12_periph/fig_02.png)). Это означает, что если вы реализуете контроллер светодиодов, на его вход `req_i` должна подаваться единица в случае если `mem_req_o == 1` и старшие 8 бит адреса равны `0x02`.
1. При интеграции вы должны подключить только модули-контроллеры вашего варианта. Контроллеры периферии других вариантов подключать не надо.
2. При этом во время интеграции, вы должны использовать старшую часть адреса, представленную в карте памяти для формирования сигнала `req_i` для ваших модулей-контроллеров.
3. Даже если вы не используете какие-то входные/выходные сигналы в модуле `riscv_unit` (например по варианту вам не достался контроллер клавиатуры и поэтому вы не используете сигналы `kclk_i` и `kdata_i`), вы все равно должны их описать во входах и выходах модуля `riscv_unit`.
6. Проинициализируйте память инструкций с помощью предоставленной для каждой пары контроллеров программы.
6. Проверьте работу процессорной системы с помощью моделирования.
1. Для каждой пары контроллеров периферии предложено две программы: с обновлением данных по опросу и по прерываниям. Запустите моделирование сначала для одной программы, затем для другой (для этого необходимо обновить файл, инициализирующий память инструкций). После проверки работоспособности процессора, сравните поведение сигналов LSU для этих программ.
7. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](nexys_a7_100t.xdc)), если тот еще не был подключен, либо замените его содержимое данными из файла к этой лабораторной работе.
8. Проверьте работу вашей процессорной системы с помощью отладочного стенда с ПЛИС и (при соответствующем варианте) клавиатуры/рабочего компьютера.
1. Обратите внимание, что в данной лабораторной уже не будет модуля верхнего уровня `nexys_...`, так как ваш модуль процессорной системы уже полностью самостоятелен и взаимодействует непосредственно с ножками ПЛИС через модули, управляемые контроллерами периферии.
@@ -183,7 +186,7 @@ sys_clk_rst_gen divider(.ex_clk_i(clk_i),.ex_areset_n_i(resetn_i),.div_i(10),.sy
В случае отсутствия **запроса на чтения**, на выход `read_data_o` должно подаваться значение `32'hfa11_1eaf`. Это никак не повлияет на работу процессора, но будет удобно в процессе отладки на временной диаграмме (тоже самое было сделано в процессе разработки памяти данных).
Если пришел **запрос на запись** или **чтение**, это еще не значит, что контроллер должен его выполнить. В случае, если запрос происходит по адресу, не поддерживающему этот запрос (например **запрос на запись** по адресу поддерживающему только чтение или наоборот), данный запрос должен игнорироваться, а на выходе `read_data_o` должно появиться значение `32'hdead_beef`.
Если пришел **запрос на запись** или **чтение**, это еще не значит, что контроллер должен его выполнить. В случае, если запрос происходит по адресу, не поддерживающему этот запрос (например **запрос на запись** по адресу поддерживающему только чтение), данный запрос должен игнорироваться. В случае **запроса на чтение** по недоступному адресу, на выходе `read_data_o` должно появиться значение `32'hdead_beef`.
К примеру, в случае запроса на чтение по адресу `0x0100004` (четвертый байт в адресном пространстве периферийного устройства "переключатели"), на выходе `read_data_o` должно оказаться значение `32'hdead_beef`. В случае отсутствия запроса на чтение (`req_i == 0` или `write_enable_i == 1`), на выходе `read_data_o` контроллера переключателей должно оказаться значение `32'hfa11_1eaf`.
@@ -201,7 +204,7 @@ module sw_sb_ctrl(
Часть интерфейса модуля, отвечающая за подключение к системной шине
*/
input logic clk_i,
input logic rst_i
input logic rst_i,
input logic req_i,
input logic write_enable_i,
input logic [31:0] addr_i,
@@ -237,7 +240,7 @@ module led_sb_ctrl(
Часть интерфейса модуля, отвечающая за подключение к системной шине
*/
input logic clk_i,
input logic rst_i
input logic rst_i,
input logic req_i,
input logic write_enable_i,
input logic [31:0] addr_i,
@@ -318,7 +321,7 @@ logic scan_code_is_unread;
endmodule
```
В первую очередь, вы должны инстанциировать модуль `PS2Receiver` внутри вашего модуля-контроллера, соединив соответствующие входы. Для подключения к выходам необходимо создать дополнительные провода.
В первую очередь, вы должны создать экземпляр модуля `PS2Receiver` внутри вашего модуля-контроллера, соединив соответствующие входы. Для подключения к выходам необходимо создать дополнительные провода.
По каждому восходящему фронту сигнала `clk_i` вы должны проверять выход `keycode_valid_o` и если тот равен единице, записать значение с выхода `keycode_o` в регистр `scan_code`. При этом значение регистра `scan_code_is_unread` необходимо выставить в единицу.
@@ -355,7 +358,7 @@ module hex_digits(
input logic [7:0] bitmask_i, // Битовая маска для включения/отключения
// отдельных индикаторов
output logic [6:0] hex_led_o // Сигнал, контролирующий каждый отдельный
output logic [6:0] hex_led_o, // Сигнал, контролирующий каждый отдельный
// светодиод индикатора
output logic [7:0] hex_sel_o // Сигнал, указывающий на какой индикатор
// выставляется hex_led
@@ -408,14 +411,14 @@ endmodule
|Адрес|Режим доступа|Допустимые значения| Функциональное назначение |
|-----|-------------|-------------------|---------------------------------------------------------|
|0x00 | RW | [0:31] | Регистр, хранящий значение, выводимое на hex0 |
|0x04 | RW | [0:31] | Регистр, хранящий значение, выводимое на hex1 |
|0x08 | RW | [0:31] | Регистр, хранящий значение, выводимое на hex2 |
|0x0C | RW | [0:31] | Регистр, хранящий значение, выводимое на hex3 |
|0x10 | RW | [0:31] | Регистр, хранящий значение, выводимое на hex4 |
|0x14 | RW | [0:31] | Регистр, хранящий значение, выводимое на hex5 |
|0x18 | RW | [0:31] | Регистр, хранящий значение, выводимое на hex6 |
|0x1C | RW | [0:31] | Регистр, хранящий значение, выводимое на hex7 |
|0x00 | RW | [0:15] | Регистр, хранящий значение, выводимое на hex0 |
|0x04 | RW | [0:15] | Регистр, хранящий значение, выводимое на hex1 |
|0x08 | RW | [0:15] | Регистр, хранящий значение, выводимое на hex2 |
|0x0C | RW | [0:15] | Регистр, хранящий значение, выводимое на hex3 |
|0x10 | RW | [0:15] | Регистр, хранящий значение, выводимое на hex4 |
|0x14 | RW | [0:15] | Регистр, хранящий значение, выводимое на hex5 |
|0x18 | RW | [0:15] | Регистр, хранящий значение, выводимое на hex6 |
|0x1C | RW | [0:15] | Регистр, хранящий значение, выводимое на hex7 |
|0x20 | RW | [0:255] | Регистр, управляющий включением/отключением индикаторов |
|0x24 | W | 1 | Запись сигнала сброса |
@@ -472,7 +475,7 @@ module uart_rx_sb_ctrl(
Часть интерфейса модуля, отвечающая за подключение к системной шине
*/
input logic clk_i,
input logic rst_i
input logic rst_i,
input logic [31:0] addr_i,
input logic req_i,
input logic [31:0] write_data_i,
@@ -502,7 +505,7 @@ module uart_tx_sb_ctrl(
Часть интерфейса модуля, отвечающая за подключение к системной шине
*/
input logic clk_i,
input logic rst_i
input logic rst_i,
input logic [31:0] addr_i,
input logic req_i,
input logic [31:0] write_data_i,