WIP: APS cumulative update (#98)

* WIP: APS cumulative update

* Update How FPGA works.md

* Перенос раздела "Последовательностная логика" в отдельный док

* Исправление картинки

* Исправление оформления индексов

* Переработка раздела Vivado Basics

* Добавление картинки в руководство по созданию проекта

* Исправление ссылок в анализе rtl

* Обновление изображения в sequential logic

* Исправление ссылок в bug hunting

* Исправление ссылок

* Рефактор руководства по прошивке ПЛИС

* Mass update

* Update fig_10

* Restore fig_02
This commit is contained in:
Andrei Solodovnikov
2024-09-02 10:20:08 +03:00
committed by GitHub
parent 78bb01ef95
commit a28002e681
195 changed files with 3640 additions and 2664 deletions

View File

@@ -1,4 +1,4 @@
# Лабораторная работа 13 "Периферийные устройства"
# Лабораторная работа 13 "Периферийные устройства"
В [ЛР№11](../11.%20Interrupt%20integration/) вы закончили реализовывать свой собственный RISC-V процессор. Однако пока что он находится "в вакууме" и никак не связан с внешним миром. Для исправления этого недостатка вами будет реализована системная шина, через которую к процессору смогут подключаться различные периферийные устройства.
@@ -6,28 +6,21 @@
Интегрировать периферийные устройства в процессорную систему.
---
## Материалы для подготовки к лабораторной работе
## Материал для подготовки к лабораторной работе
Для успешного выполнения лабораторной работы, вам необходимо:
- ознакомиться с [примером описания модуля-контроллера](../../Basic%20Verilog%20structures/Controllers.md);
- ознакомиться с [описанием](#описание-контроллеров-периферийных-устройств) контроллеров периферийных устройств.
- [Пример описания модуля-контроллера](../../Basic%20Verilog%20structures/Controllers.md);
## Ход работы
1. Изучить теорию об адресном пространстве
2. Получить индивидуальный вариант со своим набором периферийных устройств
3. Интегрировать контроллеры периферийных устройств в адресное пространство вашей системы
4. Собрать финальную схему вашей системы
5. Проверить работу системы в ПЛИС с помощью демонстрационного ПО, загружаемого в память инструкций
---
1. Изучить теорию об адресном пространстве.
2. Получить индивидуальный вариант со своим набором периферийных устройств.
3. Интегрировать контроллеры периферийных устройств в адресное пространство вашей системы.
4. Собрать финальную схему вашей системы.
5. Проверить работу системы в ПЛИС с помощью демонстрационного ПО, загружаемого в память инструкций.
## Теория
Помимо процессора и памяти, третьим ключевым элементом вычислительной системы является система ввода/вывода, обеспечивающая обмен информации между ядром вычислительной машины и периферийными устройствами[1, стр.364].
Помимо процессора и памяти, третьим ключевым элементом вычислительной системы является система ввода/вывода, обеспечивающая обмен информации между ядром вычислительной машины и периферийными устройствами [1, стр.364].
Любое периферийное устройство со стороны вычислительной машины видится как набор ячеек памяти (регистров). С помощью чтения и записи этих регистров происходит обмен информации с периферийным устройством, и управление им. Например, датчик температуры может быть реализован самыми разными способами, но для процессора он в любом случае ячейка памяти, из которой он считывает число температуру.
@@ -42,7 +35,7 @@
2. Светодиоды
3. Клавиатура PS/2
4. Семисегментные индикаторы
5. UART-приемник
5. UART-приёмник
6. UART-передатчик
7. Видеоадаптер
@@ -54,6 +47,8 @@
_Рисунок 1. Итоговая структура процессорной системы._
Обратите внимание на то, что на вход `mem_ready_i` модуля `lsu` подаётся единица. Вообще говоря, каждый модуль-контроллер периферийного устройства должен содержать выходной сигнал `ready_o`, который должен мультиплексироваться с остальными подобно тому, как мультиплексируются сигналы read_data_o. На вход `lsu` должен подаваться выход мультиплексора. Однако, поскольку все модули достаточно просты, чтобы, как и у памяти данных, выходной сигнал `ready_o` был всегда равен единице (а также для упрощения _рис. 1_), эти сигналы были убраны из микроархитектуры. В случае, если вы решите добавить в процессорную систему периферийное устройство, сигнал `ready_o` которого не будет равен константной единице, логику управления входом `mem_ready_i` модуля `lsu` будет необходимо обновить описанным выше способом.
### Активация выбранного устройства
В зависимости от интерфейса используемой шины, периферийные устройства либо знают какой диапазон адресов им выделен (например, в интерфейсе I²C), либо нет (интерфейс APB). В первом случае, устройство понимает что к нему обратились непосредственно по адресу в данном обращении, во втором случае — по специальному сигналу.
@@ -69,14 +64,14 @@ _Рисунок 1. Итоговая структура процессорной
Для реализации такого кодирования достаточно выполнить сдвиг влево константы `255'd1` на значение `data_addr_o[31:24]`.
### Дополнительные правки модуля riscv_unit
### Дополнительные правки модуля processor_system
Ранее, для того чтобы ваши модули могли работать в ПЛИС, вам предоставлялся специальный модуль верхнего уровня, который выполнял всю работу по связи с периферией через входы и выходы ПЛИС. Поскольку в текущей лабораторной вы завершаете свою процессорную систему, она сама должна оказаться модулем верхнего уровня, а значит здесь вы должны и выполнить всё подключение к периферии.
Ранее, для того чтобы ваши модули могли работать в ПЛИС, вам предоставлялся специальный модуль верхнего уровня, который выполнял всю работу по связи с периферией через входы и выходы ПЛИС. Поскольку в текущей лабораторной вы завершаете свою процессорную систему, она сама должна оказаться модулем верхнего уровня, а значит здесь вам необходимо и выполнить всё подключение к периферии.
Для этого необходимо добавить в модуль `riscv_unit` дополнительные входы и выходы, которые подключены посредством файла ограничений ([nexys_a7_100t.xdc](nexys_a7_100t.xdc)) к входам и выходам ПЛИС.
Для этого необходимо добавить в модуль `processor_system` дополнительные входы и выходы, которые подключены посредством файла ограничений к входам и выходам ПЛИС (см. документ "[Как работает ПЛИС](../../Introduction/How%20FPGA%20works.md)").
```SystemVerilog
module riscv_unit(
```Verilog
module processor_system(
input logic clk_i,
input logic resetn_i,
@@ -91,11 +86,11 @@ module riscv_unit(
output logic [ 6:0] hex_led_o, // Вывод семисегментных индикаторов
output logic [ 7:0] hex_sel_o, // Селектор семисегментных индикаторов
input logic rx_i, // Линия приема по UART
input logic rx_i, // Линия приёма по UART
output logic tx_o, // Линия передачи по UART
output logic [3:0] vga_r_o, // Красный канал vga
output logic [3:0] vga_g_o, // Зеленый канал vga
output logic [3:0] vga_g_o, // Зелёный канал vga
output logic [3:0] vga_b_o, // Синий канал vga
output logic vga_hs_o, // Линия горизонтальной синхронизации vga
output logic vga_vs_o // Линия вертикальной синхронизации vga
@@ -105,7 +100,7 @@ module riscv_unit(
endmodule
```
Эти порты нужно подключить к одноименным портам ваших контроллеров периферии (**речь идет только о реализуемых вами контроллерах, остальные порты должны остаться неподключенными**). Иными словами, в описании модуля должны быть все указанные входы и выходы. Но использовать вам нужно только порты, связанные с теми периферийными устройствами, реализацию которых вам необходимо подключить к процессорной системе в рамках индивидуального задания.
Эти порты нужно подключить к одноименным портам ваших контроллеров периферии (**речь идёт только о реализуемых вами контроллерах, остальные порты должны остаться неподключенными**). Иными словами, в описании модуля должны быть все указанные входы и выходы. Но использовать вам нужно только порты, связанные с теми периферийными устройствами, реализацию которых вам необходимо подключить к процессорной системе в рамках индивидуального задания.
Обратите внимание на то, что изменился сигнал сброса (`resetn_i`). Буква `n` на конце означает, что сброс работает по уровню `0` (в таком случае говорят, что **активный уровень** данного сигнала `0`: когда сигнал равен нулю — это сброс, когда единице — не сброс).
@@ -114,36 +109,36 @@ endmodule
Для этого необходимо:
1. Подключить файл [`sys_clk_rst_gen.sv`](sys_clk_rst_gen.sv) в ваш проект.
2. Создать экземпляр этого модуля в начале описания модуля `riscv_unit` следующим образом:
2. Создать экземпляр этого модуля в начале описания модуля `processor_system` следующим образом:
```SystemVerilog
```Verilog
logic sysclk, rst;
sys_clk_rst_gen divider(.ex_clk_i(clk_i),.ex_areset_n_i(resetn_i),.div_i(5),.sys_clk_o(sysclk), .sys_reset_o(rst));
```
3. После вставки данных строк в начало описания модуля `riscv_unit` вы получите тактовый сигнал `sysclk` с частотой в 10 МГц и сигнал сброса `rst` с активным уровнем `1` (как и в предыдущих лабораторных). Все ваши внутренние модули (`riscv_core`, `data_mem` и контроллеры периферии) должны работать от тактового сигнала `sysclk`. На модули, имеющие входной сигнал сброса (`rst_i`) необходимо подать ваш сигнал `rst`.
_Листинг 1. Пример создания экземпляра блока делителя частоты._
---
3. После вставки данных строк в начало описания модуля `processor_system` вы получите тактовый сигнал `sysclk` с частотой в 10 МГц и сигнал сброса `rst` с активным уровнем `1` (как и в предыдущих лабораторных). Все ваши внутренние модули (`processor_core`, `data_mem` и контроллеры периферии) должны работать от тактового сигнала `sysclk`. На модули, имеющие входной сигнал сброса (`rst_i`) необходимо подать ваш сигнал `rst`.
## Задание
В рамках данной лабораторной работы необходимо реализовать модули-контроллеры двух периферийных устройств, реализующих управление в соответствии с приведенной на _рис. 2_ картой памяти и встроить их в процессорную систему, используя [_рис. 1_](../../.pic/Labs/lab_13_periph/fig_01.drawio.svg). На карте приведено семь периферийных устройств, вам необходимо взять только два из них. Какие именно — сообщит преподаватель.
В рамках данной лабораторной работы необходимо реализовать модули-контроллеры двух периферийных устройств, реализующих управление в соответствии с приведенной в _таблице 1_ картой памяти и встроить их в процессорную систему, используя [_рис. 1_](../../.pic/Labs/lab_13_periph/fig_01.drawio.svg). На карте приведено семь периферийных устройств, вам необходимо взять только два из них. Какие именно — сообщит преподаватель.
![Карта памяти](../../.pic/Labs/lab_13_periph/fig_02.png)
![../../.pic/Labs/lab_13_periph/tab_01.png](../../.pic/Labs/lab_13_periph/tab_01.png)
_Рисунок 2. Карта памяти периферийных устройств._
_Таблица 1. Карта памяти периферийных устройств._
Работа с картой осуществляется следующим образом. Под названием каждого периферийного устройства указана старшая часть адреса (чему должны быть равны старшие 8 бит адреса, чтобы было сформировано обращение к данному периферийному устройству). Например, для переключателей это значение равно `0x01`, для светодиодов `0x02` и т.п.
В самом левом столбце указаны используемые/неиспользуемые адреса в адресном пространстве данного периферийного устройства. Например для переключателей есть только один используемый адрес: `0x000000`. Его функциональное назначение и разрешения на доступ указаны в столбце соответствующего периферийного устройства. Возвращаясь к адресу `0x000000`, для переключателей мы видим следующее:
- **(R)** означает что разрешен доступ только на чтение (операция записи по этому адресу должна игнорироваться вашим контроллером).
- **"Выставленное на переключателях значение"** означает ровно то, что и означает. Если процессор выполняет операцию чтения по адресу `0x01000000` (`0x01` [старшая часть адреса переключателей] + `0x000000` [младшая часть адреса для получения выставленного на переключателях значения]), то контроллер должен выставить на выходной сигнал `RD` значение на переключателях (о том как получить это значение будет рассказано чуть позже).
- **(R)** означает что разрешён доступ только на чтение (операция записи по этому адресу должна игнорироваться вашим контроллером).
- **"Выставленное на переключателях значение"** означает ровно то, что и означает. Если процессор выполняет операцию чтения по адресу `0x01000000` (`0x01` [старшая часть адреса переключателей] + `0x000000` [младшая часть адреса для получения выставленного на переключателях значения]), то контроллер должен выставить на выходной сигнал `RD` значение на переключателях (о том, как получить это значение будет рассказано чуть позже).
Рассмотрим еще один пример. При обращении по адресу `0x02000024` (`0x02` (старшая часть адреса контроллера светодиодов) + `0x000024` (младшая часть адреса для доступа на запись к регистру сброса) ) должна произойти запись в регистр сброса, который должен сбросить значения в регистре управления зажигаемых светодиодов и регистре управления режимом "моргания" светодиодов (подробнее о том как должны работать эти регистры будет ниже).
Рассмотрим ещё один пример. При обращении по адресу `0x02000024` (`0x02` [старшая часть адреса контроллера светодиодов] + `0x000024` [младшая часть адреса для доступа на запись к регистру сброса] ) должна произойти запись в регистр сброса, который должен сбросить значения в регистре управления зажигаемых светодиодов и регистре управления режимом "моргания" светодиодов (подробнее о том как должны работать эти регистры будет ниже).
Таким образом, каждый контроллер периферийного устройства должен выполнять две вещи:
1. При получении сигнала `req_i`, записать в регистр или вернуть значение из регистра, ассоциированного с переданным адресом (адрес передается с обнуленной старшей частью). Если регистра, ассоциированного с таким адресом нет (например, для переключателей не ассоциировано ни одного адреса кроме `0x000000`), игнорировать эту операцию.
1. При получении сигнала `req_i`, записать в регистр или вернуть значение из регистра, ассоциированного с переданным адресом (адрес передаётся с обнуленной старшей частью). Если регистра, ассоциированного с таким адресом нет (например, для переключателей не ассоциировано ни одного адреса кроме `0x000000`), игнорировать эту операцию.
2. Выполнять управление периферийным устройством с помощью управляющих регистров.
Подробное описание периферийных устройств их управления и назначение управляющих регистров описано **после порядка выполнения задания**.
@@ -152,41 +147,38 @@ _Рисунок 2. Карта памяти периферийных устрой
## Порядок выполнения задания
1. Внимательно ознакомьтесь с [примером описания модуля контроллера](../../Basic%20Verilog%20structures/Controllers.md).
2. Внимательно ознакомьтесь со спецификацией контроллеров периферии своего варианта. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
3. Добавьте в проект пакет [`peripheral_pkg`](peripheral_pkg.sv). Данный пакет содержит старшие части адресов периферии в виде параметров, а также вспомогательные вызовы, используемые тестбенчем.
1. Ознакомьтесь с [примером описания модуля контроллера](../../Basic%20Verilog%20structures/Controllers.md).
2. Ознакомьтесь со спецификацией контроллеров периферии своего варианта. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
3. Добавьте в проект пакет [`peripheral_pkg`](peripheral_pkg.sv). Данный пакет содержит старшие части адресов периферии в виде параметров, а также вспомогательные вызовы, используемые верификационным окружением.
4. Реализуйте модули контроллеров периферии. Имена модулей и их порты будут указаны в [описании контроллеров](#описание-контроллеров-периферийных-устройств). Пример разработки контроллера приведен в [примере описания модуля контроллера](../../Basic%20Verilog%20structures/Controllers.md).
1. Готовые модули периферии, управление которыми должны осуществлять модули-контроллеры хранятся в папке `peripheral modules`.
5. Обновите модуль `riscv_unit` в соответствии с разделом ["Дополнительные правки модуля riscv_unit"](#дополнительные-правки-модуля-riscv_unit).
5. Обновите модуль `processor_system` в соответствии с разделом ["Дополнительные правки модуля processor_system"](#дополнительные-правки-модуля-processor_system).
1. Подключите в проект файл `sys_clk_rst_gen.sv`.
2. Добавьте в модуль `riscv_unit` входы и выходы периферии, а так же замените вход `rst_i` входом `resetn_i`. **Необходимо добавить порты даже тех периферийных устройств, которые вы не будете реализовывать**.
3. Создайте в начале описания модуля `riscv_unit` экземпляр модуля `sys_clk_rst_gen`, скопировав приведенный фрагмент кода.
4. Замените подключение тактового сигнала исходных подмодулей `riscv_unit` на появившийся сигнал `sysclk`. Убедитесь, что на модули, имеющие сигнал сброса, приходит сигнал `rst`.
6. Интегрируйте модули контроллеров периферии в процессорную систему по приведенной схеме руководствуясь старшими адресами контроллеров, представленными на карте памяти ([_рис. 2_](../../.pic/Labs/lab_13_periph/fig_02.png)). Это означает, что если вы реализуете контроллер светодиодов, на его вход `req_i` должна подаваться единица в случае, если `mem_req_o == 1` и старшие 8 бит адреса равны `0x02`.
1. При интеграции вы должны подключить только модули-контроллеры вашего варианта. Контроллеры периферии других вариантов подключать не надо.
2. Во время интеграции, вы должны использовать старшую часть адреса, представленную в карте памяти для формирования сигнала `req_i` для ваших модулей-контроллеров.
2. Добавьте в модуль `processor_system` входы и выходы периферии, а также замените вход `rst_i` входом `resetn_i`. **Необходимо добавить порты даже тех периферийных устройств, которые вы не будете реализовывать**.
3. Создайте в начале описания модуля `processor_system` экземпляр модуля `sys_clk_rst_gen`, скопировав фрагмент кода, приведённый в _листинге 1_.
4. Замените подключение тактового сигнала исходных подмодулей `processor_system` на появившийся сигнал `sysclk`. Убедитесь, что на модули, имеющие сигнал сброса, приходит сигнал `rst`.
6. Интегрируйте модули контроллеров периферии в процессорную систему по схеме представленной на _рис. 1_, руководствуясь старшими адресами контроллеров, представленными на карте памяти ([_таблицы 1_](../../.pic/Labs/lab_13_periph/fig_02.png)). Это означает, что если вы реализуете контроллер светодиодов, на его вход `req_i` должна подаваться единица в случае, если `mem_req_o == 1` и старшие 8 бит адреса равны `0x02`.
1. При интеграции вам необходимо подключить только модули-контроллеры вашего варианта. Контроллеры периферии других вариантов подключать не надо.
2. Во время интеграции, вам необходимо использовать старшую часть адреса, представленную в карте памяти для формирования сигнала `req_i` для ваших модулей-контроллеров.
7. Проверьте работу процессорной системы с помощью моделирования.
1. Для моделирования используйте тестбенч `lab_13_tb_system`.
2. Для каждой пары контроллеров в папке `firmware/mem_files` представлены файлы, инициализирующие память инструкций. Обратите внимание, что для пары "PS2-VGA" также необходим файл, инициализирующий память данных (в модуле `data_mem` необходимо добавить вызов инициализирующей функции `$readmemh` в блоке `initial`).
2. Для каждой пары контроллеров в папке `firmware/mem_files` представлены файлы, инициализирующие память инструкций. Содержимым одного из файлов, соответствующих паре периферийных устройств вашего варианта необходимо заменить содержимое файла `program.mem` в `Design Sources` проекта. Обратите внимание, что для пары "PS2-VGA" также необходим файл, инициализирующий память данных (в модуле `data_mem` необходимо добавить вызов инициализирующей функции `$readmemh` в блоке `initial`).
3. Для проверки тестбенч имитирует генерацию данных периферийных устройств ввода. Перед проверкой желательно найти в тестбенче `initial`-блок своего устройства ввода (`sw_block`, `ps2_block`, `uart_block`) — по этому блоку будет понятно, какие данные будет передавать устройство ввода. Именно эти данные в итоге должны оказаться на шине `mem_rd_i`.
4. Для того, чтобы понять, что устройство работает должным образом, в первую очередь необходимо убедиться, что контроллер устройства ввода успешно осуществил прием данных (сгенерированные тестбенчем данные оказались в соответствующем регистре контроллера периферийного устройства) и выполнил запрос на прерывание.
5. После чего, необходимо убедиться, что процессор среагировал на данное прерывание, и в процессе его обработки в контроллер устройства вывода были поданы выходные данные.
6. Для того, чтобы лучше понимать как именно процессор будет обрабатывать прерывание, рекомендуется ознакомиться с исходным кодом исполняемой программы, расположенным в папке `firmware/software`.
1. Общая логика программ для всех периферий сводится к ожиданию в бесконечном цикле прерывания от устройства ввода, после чего в процессе обработки прерывания процессор загружает данные от устройства ввода и (возможно преобразовав их) выдает их на устройство вывода.
1. Общая логика программ для всех периферий сводится к ожиданию в бесконечном цикле прерывания от устройства ввода, после чего в процессе обработки прерывания процессор загружает данные от устройства ввода и (возможно преобразовав их) выдаёт их на устройство вывода.
2. В случае правильной работы программы на временной диаграмме это будет отображено следующим образом: сразу после поступления прерывания от устройства ввода, на системной шине начинается операция чтения из устройства ввода (это легко определить по старшей части адреса, к которому обращается процессор), после чего выполняются операции записи в устройство вывода (аналогично, обращение к устройству вывода можно определить по адресу, к которому обращается процессор).
7. При моделировании светодиодов лучше уменьшить значение, до которого считает счетчик в режиме "моргания" в 1000 раз, чтобы сократить время моделирования до очередного переключения светодиодов. Перед генерацией битстрима это значение будет необходимо восстановить, иначе моргание станет слишком быстрым и его нельзя будет воспринять невооруженным взглядом.
7. При моделировании светодиодов лучше уменьшить значение, до которого считает счётчик в режиме "моргания" в 1000 раз, чтобы сократить время моделирования до очередного переключения светодиодов. Перед генерацией битстрима это значение будет необходимо восстановить, иначе моргание станет слишком быстрым и его нельзя будет воспринять невооружённым взглядом.
8. Переходить к следующему пункту можно только после того, как вы полностью убедились в работоспособности модуля на этапе моделирования (увидели корректные значения на выходных сигналах периферии, либо (если по сигналам периферии сложно судить о работоспособности), значениям в контрольных/статусных регистрах модуля-контроллера этой периферии). Генерация битстрима будет занимать у вас долгое время, а итогом вы получите результат: заработало / не заработало, без какой-либо дополнительной информации, поэтому без прочного фундамента на моделировании далеко уехать у вас не выйдет.
<!-- 1. Для каждой пары контроллеров периферии предложено две программы: с обновлением данных по опросу и по прерываниям. Запустите моделирование сначала для одной программы, затем для другой (для этого необходимо обновить файл, инициализирующий память инструкций). После проверки работоспособности процессора, сравните поведение сигналов LSU для этих программ. -->
9. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](nexys_a7_100t.xdc)), если тот еще не был подключен, либо замените его содержимое данными из файла к этой лабораторной работе.
9. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](nexys_a7_100t.xdc)), если тот ещё не был подключён, либо замените его содержимое данными из файла к этой лабораторной работе.
10. Проверьте работу вашей процессорной системы на отладочном стенде с ПЛИС.
1. Обратите внимание, что в данной лабораторной уже не будет модуля верхнего уровня `nexys_...`, так как ваш модуль процессорной системы уже полностью самостоятелен и взаимодействует непосредственно с ножками ПЛИС через модули, управляемые контроллерами периферии.
2. Для проверки периферии переключателей и светодиодов будет достаточно одного лишь отладочного стенда. Для проверки всей остальной периферии может могут потребоваться: компьютер (для uart_rx / uart_tx), клавиатура (для контролера клавиатуры) и VGA-монитор для VGA-контроллера.
1. Чтобы проверить работоспособность контроллеров UART, необходимо запустить на компьютере программу Putty, в настройках программы указать настройки, которыми будет сконфигурирован программой ваш контроллер (либо указать значения, которыми сбрасываются регистры, если программа ничего не настраивает) и COM-порт, через который компьютер будет общаться с контроллером. Определить нужный COM-порт на операционной системе Windows можно через "Диспетчер устройств", который можно открыть через меню пуск.
В данном окне необходимо найти вкладку "Порты (COM и LPT)", раскрыть ее, а затем подключить отладочный стенд через USB-порт (если тот еще не был подключен). В списке появится новое устройство, а в скобках будет указан нужный COM-порт.
2. Не смотря на то, что описанный контроллер клавиатуры позволяет управлять клавиатурой с интерфейсом PS/2, некоторые платы (например Nexys A7) позволяют подключать вместо них клавиатуры с USB-интерфейсом. Дело в том, что PS/2 уже давно устарел и найти клавиатуры с таким интерфейсом — задача непростая. Однако протокол передачи по этому интерфейсу очень удобен для образовательны целей, поэтому некоторые производители просто ставят на платы переходник с USB на PS/2, позволяя объединить простоту разработки с удобством использования.
---
1. Обратите внимание, что в данной лабораторной уже не будет модуля верхнего уровня `nexys_...`, так как ваш модуль процессорной системы уже полностью самостоятелен и взаимодействует непосредственно с ножками ПЛИС через модули, управляемые контроллерами периферии.
2. Для проверки периферии переключателей и светодиодов будет достаточно одного лишь отладочного стенда. Для проверки всей остальной периферии может могут потребоваться: компьютер (для uart_rx / uart_tx), клавиатура (для контроллера клавиатуры) и VGA-монитор для VGA-контроллера.
1. Чтобы проверить работоспособность контроллеров UART, необходимо запустить на компьютере программу Putty, в настройках программы указать настройки, которыми будет сконфигурирован программой ваш контроллер (либо указать значения, которыми сбрасываются регистры, если программа ничего не настраивает) и COM-порт, через который компьютер будет общаться с контроллером. Определить нужный COM-порт на операционной системе Windows можно через "Диспетчер устройств", который можно открыть через меню пуск.
В данном окне необходимо найти вкладку "Порты (COM и LPT)", раскрыть её, а затем подключить отладочный стенд через USB-порт (если тот ещё не был подключён). В списке появится новое устройство, а в скобках будет указан нужный COM-порт.
2. Несмотря на то, что описанный контроллер клавиатуры позволяет управлять клавиатурой с интерфейсом PS/2, некоторые платы (например, Nexys A7) позволяют подключать вместо них клавиатуры с USB-интерфейсом. Дело в том, что PS/2 уже давно устарел и найти клавиатуры с таким интерфейсом — задача непростая. Однако протокол передачи по этому интерфейсу очень удобен для образовательных целей, поэтому некоторые производители просто ставят на платы переходник с USB на PS/2, позволяя объединить простоту разработки с удобством использования.
## Описание контроллеров периферийных устройств
@@ -203,7 +195,7 @@ _Рисунок 2. Карта памяти периферийных устрой
3. На входе `write_enable_i` выставлено значение `0`.
4. На входе `addr_i` выставлено значение `0xАДРЕС`
Обратите внимание на то, что **запрос на чтение** должен обрабатываться **синхронно** (выходные данные должны выдаваться по положительному фронту `clk_i`) так же как был реализован порт на чтение памяти данных в [ЛР№6](../06.%20Main%20memory/).
Обратите внимание на то, что **запрос на чтение** должен обрабатываться **синхронно** (выходные данные должны выдаваться по положительному фронту `clk_i`) так же, как был реализован порт на чтение памяти данных в [ЛР№6](../06.%20Main%20memory/).
При описании поддерживаемых режимов доступа по данному адресу используются следующее обозначения:
@@ -213,7 +205,7 @@ _Рисунок 2. Карта памяти периферийных устрой
В случае отсутствия **запроса на чтение**, на выходе `read_data_o` не должно меняться значение (тоже самое было сделано в процессе разработки памяти данных).
Если пришел **запрос на запись** или **чтение**, это еще не значит, что контроллер должен его выполнить. В случае, если запрос происходит по адресу, не поддерживающему этот запрос (например **запрос на запись** по адресу, поддерживающему только чтение), данный запрос должен игнорироваться. В случае **запроса на чтение** по недоступному адресу, на выходе `read_data_o` должно остаться прежнее значение.
Если пришёл **запрос на запись** или **чтение**, это ещё не значит, что контроллер должен его выполнить. В случае, если запрос происходит по адресу, не поддерживающему этот запрос (например **запрос на запись** по адресу, поддерживающему только чтение), данный запрос должен игнорироваться. В случае **запроса на чтение** по недоступному адресу, на выходе `read_data_o` должно остаться прежнее значение.
В случае осуществления записи по принятому запросу, необходимо записать данные с сигнала `write_data_i` в регистр, ассоциированный с адресом `addr_i` (если разрядность регистра меньше разрядности сигнала `write_data_i`, старшие биты записываемых данных отбрасываются).
@@ -223,7 +215,7 @@ _Рисунок 2. Карта памяти периферийных устрой
Переключатели являются простейшим устройством ввода на отладочном стенде `Nexys A7`. Соответственно и контроллер, осуществляющий доступ процессора к ним так же будет очень простым. Рассмотрим прототип модуля, который вам необходимо реализовать:
```SystemVerilog
```Verilog
module sw_sb_ctrl(
/*
Часть интерфейса модуля, отвечающая за подключение к системной шине
@@ -256,12 +248,14 @@ endmodule
По сути, логика работы контроллера сводится к тому, выдавать на шину `read_data_o` данные со входа `sw_i` каждый раз, когда приходит **запрос на чтение** по нулевому адресу. Поскольку разрядность `sw_i` в два раза меньше разрядности выхода `read_data_o` его старшие биты необходимо дополнить нулями.
Адресное пространство контроллера:
Адресное пространство контроллера представлено в аблице 2_.
|Адрес|Режим доступа| Функциональное назначение |
|-----|-------------|-------------------------------------------------|
|0x00 | R | Чтение значения, выставленного на переключателях|
_Таблица 2. Адресное пространство контроллера переключателей._
При этом, будучи устройством ввода, данный модуль может генерировать прерывание, чтобы сообщить процессору о том, что данные на переключателях были изменены. Если на очередном такте `clk_i` данные на входе `sw_i` изменились (т.е. отличаются от тех, что были на предыдущем такте), модуль должен выставить значение `1` на выходе `interrupt_request_o` и удерживать его до получения сигнала о завершении обработки прерывания `interrupt_return_i`.
Для отслеживания изменений на входе `sw_i` между тактами синхроимпульса вам потребуется вспомогательный регистр, каждый такт сохраняющий значение `sw_i`. При реализации данного регистра, не забывайте о том, что его необходимо сбрасывать посредством сигнала `rst_i`.
@@ -271,7 +265,7 @@ endmodule
Как и переключатели, светодиоды являются простейшим устройством вывода. Поэтому, чтобы задание было интересней, для их управления был добавлен регистр, управляющий режимом вывода данных на светодиоды.
Рассмотрим прототип модуля, который вам необходимо реализовать:
```SystemVerilog
```Verilog
module led_sb_ctrl(
/*
Часть интерфейса модуля, отвечающая за подключение к системной шине
@@ -300,13 +294,13 @@ endmodule
Регистр `led_mode` отвечает за режим вывода данных на светодиоды. Когда этот регистр равен единице, светодиоды должны "моргать" выводимым значением. Под морганием подразумевается вывод значения из регистра `led_val` на выход `led_o` на одну секунду (загорится часть светодиодов, соответствующие которым биты шины `led_o` равны единице), после чего на одну секунду выход `led_o` необходимо подать нули. Запись и чтение регистра `led_mode` осуществляется по адресу `0x04`.
Отсчет времени можно реализовать простейшим счетчиком, каждый такт увеличивающимся на 1 и сбрасывающимся по достижении определенного значения, чтобы продолжить считать с нуля. Зная тактовую частоту, нетрудно определить до скольки должен считать счетчик. При тактовой частоте в 10 МГц происходит 10 миллионов тактов в секунду. Это означает, что при такой тактовой частоте через секунду счетчик будет равен `10⁷-1` (счет идет с нуля). Тем не менее удобней будет считать не до `10⁷-1` (что было бы достаточно очевидным и тоже правильным решением), а до `2*10⁷-1`. В этом случае старший бит счетчика каждую секунду будет инвертировать свое значение, что может быть использовано при реализации логики "моргания".
Отсчёт времени можно реализовать простейшим счётчиком, каждый такт увеличивающимся на 1 и сбрасывающимся по достижении определенного значения, чтобы продолжить считать с нуля. Зная тактовую частоту, нетрудно определить до скольки должен считать счётчик. При тактовой частоте в 10 МГц происходит 10 миллионов тактов в секунду. Это означает, что при такой тактовой частоте через секунду счётчик будет равен `10⁷-1` (счёт идёт с нуля). Тем не менее удобней будет считать не до `10⁷-1` (что было бы достаточно очевидным и тоже правильным решением), а до `2*10⁷-1`. В этом случае старший бит счётчика каждую секунду будет инвертировать своё значение, что может быть использовано при реализации логики "моргания".
Важно отметить, что счетчик должен работать только при `led_mode == 1`, в противном случае счетчик должен быть равен нулю.
Важно отметить, что счётчик должен работать только при `led_mode == 1`, в противном случае счётчик должен быть равен нулю.
Обратите внимание на то, что адрес `0x24` является адресом сброса. В случае **запроса на запись** по этому адресу значения `1`. вы должны сбросить регистры `led_val`, `led_mode` и все вспомогательные регистры, которые вы создали. Для реализации сброса вы можете как создать отдельный регистр `led_rst`, в который будет происходить запись, а сам сброс будет происходить по появлению единицы в этом регистре (в этом случае необходимо не забыть сбрасывать и этот регистр тоже), так и создать обычный провод, формирующий единицу в случае выполнения всех указанных условий (условий **запроса на запись**, адреса сброса и значения записываемых данных равному единице).
Обратите внимание на то, что адрес `0x24` является адресом сброса. В случае **запроса на запись** по этому адресу значения `1`. вам необходимо сбросить регистры `led_val`, `led_mode` и все вспомогательные регистры, которые вы создали. Для реализации сброса вы можете как создать отдельный регистр `led_rst`, в который будет происходить запись, а сам сброс будет происходить по появлению единицы в этом регистре (в этом случае необходимо не забыть сбрасывать и этот регистр тоже), так и создать обычный провод, формирующий единицу в случае выполнения всех указанных условий (условий **запроса на запись**, адреса сброса и значения записываемых данных равному единице).
Адресное пространство контроллера:
Адресное пространство контроллера представлено в аблице 3_.
|Адрес|Режим доступа|Допустимые значения| Функциональное назначение |
|-----|-------------|-------------------|-----------------------------------------------------------------------------------|
@@ -314,13 +308,15 @@ endmodule
|0x04 | RW | [0:1] | Чтение и запись в регистр `led_mode`, отвечающий за режим "моргания" светодиодами |
|0x24 | W | 1 | Запись сигнала сброса |
Таблица 3. Адресное пространство контроллера светодиодов.
### Клавиатура PS/2
Клавиатура [PS/2](https://ru.wikipedia.org/wiki/PS/2_(порт)) осуществляет передачу [скан-кодов](https://ru.wikipedia.org/wiki/Скан-код), нажатых на этой клавиатуре клавиш.
В рамках данной лабораторной работы вам будет дан готовый модуль, осуществляющий прием данных с клавиатуры. От вас требуется написать лишь модуль, осуществляющий контроль предоставленным модулем. У готового модуля будет следующий прототип:
В рамках данной лабораторной работы вам будет дан готовый модуль, осуществляющий приём данных с клавиатуры. От вас требуется написать лишь модуль, осуществляющий контроль предоставленным модулем. У готового модуля будет следующий прототип:
```SystemVerilog
```Verilog
module PS2Receiver(
input clk_i, // Сигнал тактирования
input rst_i, // Сигнал сброса
@@ -334,7 +330,7 @@ endmodule
Вам необходимо реализовать модуль-контроллер со следующим прототипом:
```SystemVerilog
```Verilog
module ps2_sb_ctrl(
/*
Часть интерфейса модуля, отвечающая за подключение к системной шине
@@ -357,7 +353,7 @@ module ps2_sb_ctrl(
/*
Часть интерфейса модуля, отвечающая за подключение к модулю,
осуществляющему прием данных с клавиатуры
осуществляющему приём данных с клавиатуры
*/
input logic kclk_i,
input logic kdata_i
@@ -369,11 +365,11 @@ logic scan_code_is_unread;
endmodule
```
В первую очередь, вы должны создать экземпляр модуля `PS2Receiver` внутри вашего модуля-контроллера, соединив соответствующие входы. Для подключения к выходам необходимо создать дополнительные провода.
В первую очередь, вам необходимо создать экземпляр модуля `PS2Receiver` внутри вашего модуля-контроллера, соединив соответствующие входы. Для подключения к выходам необходимо создать дополнительные провода.
По каждому восходящему фронту сигнала `clk_i` вы должны проверять выход `keycode_valid_o` и, если тот равен единице, записать значение с выхода `keycode_o` в регистр `scan_code`. При этом значение регистра `scan_code_is_unread` необходимо выставить в единицу.
По каждому восходящему фронту сигнала `clk_i` вам необходимо проверять выход `keycode_valid_o` и, если тот равен единице, записать значение с выхода `keycode_o` в регистр `scan_code`. При этом значение регистра `scan_code_is_unread` необходимо выставить в единицу.
В случае, если произошел **запрос на чтение** по адресу `0x00`, необходимо выставить на выход `read_data_o` значение регистра `scan_code` (дополнив старшие биты нулями), при этом значение регистра `scan_code_is_unread` необходимо обнулить. В случае, если одновременно с **запросом на чтение** пришел сигнал `keycode_valid_o`, регистр `scan_code_is_unread` обнулять не нужно (в этот момент в регистр `scan_code` уже записывается новое, еще непрочитанное значение).
В случае, если произошёл **запрос на чтение** по адресу `0x00`, необходимо выставить на выход `read_data_o` значение регистра `scan_code` (дополнив старшие биты нулями), при этом значение регистра `scan_code_is_unread` необходимо обнулить. В случае, если одновременно с **запросом на чтение** пришёл сигнал `keycode_valid_o`, регистр `scan_code_is_unread` обнулять не нужно (в этот момент в регистр `scan_code` уже записывается новое, ещё непрочитанное значение).
Обнуление регистра `scan_code_is_unread` должно происходить и в случае получения сигнала `interrupt_return_i` (однако опять же, если в этот момент приходит сигнал `keycode_valid_o`, обнулять регистр не нужно).
@@ -381,9 +377,9 @@ endmodule
В случае **запроса на запись** значения `1` по адресу `0x24`, необходимо осуществить сброс регистров `scan_code` и `scan_code_is_unread` в `0`.
Регистр `scan_code_is_unread` необходимо подключить к выходу `interrupt_request_o`. Таким образом процессор может узнавать о нажатых клавишах как посредством программного опроса путем чтения значения регистра`scan_code_is_unread`, так и посредством прерываний через сигнал`interrupt_request_o`.
Регистр `scan_code_is_unread` необходимо подключить к выходу `interrupt_request_o`. Таким образом процессор может узнавать о нажатых клавишах как посредством программного опроса путём чтения значения регистра `scan_code_is_unread`, так и посредством прерываний через сигнал `interrupt_request_o`.
Адресное пространство контроллера:
Адресное пространство контроллера представлено в аблице 4_.
|Адрес|Режим доступа|Допустимые значения| Функциональное назначение |
|-----|-------------|-------------------|-------------------------------------------------------------------------------------------------------------------|
@@ -391,11 +387,13 @@ endmodule
|0x04 | R | [0:1] | Чтение из регистра `scan_code_is_unread`, сообщающего о том, что есть непрочитанные данные в регистре `scan_code` |
|0x24 | W | 1 | Запись сигнала сброса |
_Таблица 4. Адресное пространство контроллера клавиатуры._
### Семисегментные индикаторы
Семисегментные индикаторы позволяют выводить арабские цифры и первые шесть букв латинского алфавита, тем самым позволяя отображать шестнадцатеричные цифры. На отладочном стенде `Nexys A7` размещено восемь семисегментных индикаторов. Для вывода цифр на эти индикаторы, вам будет предоставлен модуль `hex_digits`, вам нужно лишь написать модуль, осуществляющий контроль над ним. Прототип модуля `hex_digits` следующий:
```SystemVerilog
```Verilog
module hex_digits(
input logic clk_i,
input logic rst_i,
@@ -403,7 +401,7 @@ module hex_digits(
input logic [3:0] hex1_i, // Цифра, выводимая на первый индикатор
input logic [3:0] hex2_i, // Цифра, выводимая на второй индикатор
input logic [3:0] hex3_i, // Цифра, выводимая на третий индикатор
input logic [3:0] hex4_i, // Цифра, выводимая на четвертый индикатор
input logic [3:0] hex4_i, // Цифра, выводимая на четвёртый индикатор
input logic [3:0] hex5_i, // Цифра, выводимая на пятый индикатор
input logic [3:0] hex6_i, // Цифра, выводимая на шестой индикатор
input logic [3:0] hex7_i, // Цифра, выводимая на седьмой индикатор
@@ -422,11 +420,11 @@ endmodule
За включение/отключение индикаторов отвечает входной сигнал `bitmask_i`, состоящий из 8 бит, каждый из которых включает/отключает соответствующий индикатор. Например, при `bitmask_i == 8'b0000_0101`, включены будут нулевой и второй индикаторы, остальные будут погашены.
Выходные сигналы `hex_led` и `hex_sel` необходимо просто подключить к соответствующим выходным сигналам модуля-контроллера. Они пойдут на выходы ПЛИС, соединенные с семисегментными индикаторами.
Выходные сигналы `hex_led` и `hex_sel` необходимо просто подключить к соответствующим выходным сигналам модуля-контроллера. Они пойдут на выходы ПЛИС, соединённые с семисегментными индикаторами.
Для управления данным модулем, необходимо написать модуль-контроллер со следующим прототипом:
```SystemVerilog
```Verilog
module hex_sb_ctrl(
/*
Часть интерфейса модуля, отвечающая за подключение к системной шине
@@ -451,7 +449,7 @@ module hex_sb_ctrl(
endmodule
```
Регистры `hex0-hex7` отвечают за вывод цифры на соответствующий семисегментный индикатор. Регистр `bitmask` отвечает за включение/отключение семисегментных индикаторов. Когда в регистре `bitmask` бит, индекс которого совпадает с номером индикатора равен единице — тот включен и выводит число, совпадающее со значением в соответствующем регистре `hex0-hex7`. Когда бит равен нулю — этот индикатор гаснет.
Регистры `hex0-hex7` отвечают за вывод цифры на соответствующий семисегментный индикатор. Регистр `bitmask` отвечает за включение/отключение семисегментных индикаторов. Когда в регистре `bitmask` бит, индекс которого совпадает с номером индикатора равен единице — тот включён и выводит число, совпадающее со значением в соответствующем регистре `hex0-hex7`. Когда бит равен нулю — этот индикатор гаснет.
Доступ на чтение/запись регистров `hex0-hex7` осуществляется по адресам `0x00-0x1c` (см. таблицу адресного пространства).
@@ -459,7 +457,7 @@ endmodule
При **запросе на запись** единицы по адресу `0x24` необходимо выполнить сброс всех регистров. При этом регистр `bitmask` должен сброситься в значение `0xFF` (т.е. после сброса все семисегментные индикаторы должны загореться с цифрой `0`).
Адресное пространство контроллера:
Адресное пространство контроллера представлено в аблице 5_.
|Адрес|Режим доступа|Допустимые значения| Функциональное назначение |
|-----|-------------|-------------------|---------------------------------------------------------|
@@ -474,44 +472,46 @@ endmodule
|0x20 | RW | [0:255] | Регистр, управляющий включением/отключением индикаторов |
|0x24 | W | 1 | Запись сигнала сброса |
_Таблица 5. Адресное пространство контроллера семисегментных индикаторов._
### UART
[UART](https://ru.wikipedia.org/wiki/Универсальный_асинхронный_приёмопередатчик) — это последовательный интерфейс, использующий для приема и передачи данных по одной независимой линии с поддержкой контроля целостности данных.
[UART](https://ru.wikipedia.org/wiki/Универсальный_асинхронный_приёмопередатчик) — это последовательный интерфейс, использующий для приёма и передачи данных по одной независимой линии с поддержкой контроля целостности данных.
Для того, чтобы передача данных была успешно осуществлена, приемник и передатчик на обоих концах одного провода должны договориться о параметрах передачи:
Для того, чтобы передача данных была успешно осуществлена, приёмник и передатчик на обоих концах одного провода должны договориться о параметрах передачи:
- её скорости (бодрейт);
- контроля целостности данных (использовать или нет [бит четности](https://en.wikipedia.org/wiki/Parity_bit));
- контроля целостности данных (использовать или нет [бит чётности](https://en.wikipedia.org/wiki/Parity_bit));
- длины стопового бита.
Вам будут предоставлены модули, осуществляющие прием и передачу данных по этому интерфейсу, от вас лишь требуется написать модули, осуществляющие управление предоставленными модулями.
Вам будут предоставлены модули, осуществляющие приём и передачу данных по этому интерфейсу, от вас лишь требуется написать модули, осуществляющие управление предоставленными модулями.
```SystemVerilog
```Verilog
module uart_rx (
input logic clk_i, // Тактирующий сигнал
input logic rst_i, // Сигнал сброса
input logic rx_i, // Сигнал линии, подключенной к выводу ПЛИС,
input logic rx_i, // Сигнал линии, подключённой к выводу ПЛИС,
// по которой будут приниматься данные
output logic busy_o, // Сигнал о том, что модуль занят приемом данных
output logic busy_o, // Сигнал о том, что модуль занят приёмом данных
input logic [16:0] baudrate_i, // Настройка скорости передачи данных
input logic parity_en_i,// Настройка контроля целостности через бит четности
input logic parity_en_i,// Настройка контроля целостности через бит чётности
input logic [1:0] stopbit_i, // Настройка длины стопового бита
output logic [7:0] rx_data_o, // Принятые данные
output logic rx_valid_o // Сигнал о том, что прием данных завершен
output logic rx_valid_o // Сигнал о том, что прием данных завершён
);
endmodule
```
```SystemVerilog
```Verilog
module uart_tx (
input logic clk_i, // Тактирующий сигнал
input logic rst_i, // Сигнал сброса
output logic tx_o, // Сигнал линии, подключенной к выводу ПЛИС,
output logic tx_o, // Сигнал линии, подключённой к выводу ПЛИС,
// по которой будут отправляться данные
output logic busy_o, // Сигнал о том, что модуль занят передачей данных
input logic [16:0] baudrate_i, // Настройка скорости передачи данных
input logic parity_en_i,// Настройка контроля целостности через бит четности
input logic parity_en_i,// Настройка контроля целостности через бит чётности
input logic [1:0] stopbit_i, // Настройка длины стопового бита
input logic [7:0] tx_data_i, // Отправляемые данные
input logic tx_valid_i // Сигнал о старте передачи данных
@@ -521,7 +521,7 @@ endmodule
Для управления этими модулями вам необходимо написать два модуля-контроллера со следующими прототипами
```SystemVerilog
```Verilog
module uart_rx_sb_ctrl(
/*
Часть интерфейса модуля, отвечающая за подключение к системной шине
@@ -559,7 +559,7 @@ module uart_rx_sb_ctrl(
endmodule
```
```SystemVerilog
```Verilog
module uart_tx_sb_ctrl(
/*
Часть интерфейса модуля, отвечающая за подключение к системной шине
@@ -606,13 +606,13 @@ endmodule
Доступ на запись в регистр `data` модуля `uart_tx_sb_ctrl` происходит по адресу `0x00` в моменты положительного фронта `clk_i`, когда сигнал `busy_o` равен нулю. Доступ на чтение этого регистра может осуществляться в любой момент времени.
На вход `tx_data_i` модуля `uart_tx` непрерывно подается младший байт входа `write_data_i`.
На вход `tx_data_i` модуля `uart_tx` непрерывно подаётся младший байт входа `write_data_i`.
На вход `tx_valid_i` модуля `uart_tx` подается единица в момент выполнения **запроса на запись** по адресу `0x00` (при сигнале `busy` равном нулю). В остальное время на вход этого сигнала подается `0`.
На вход `tx_valid_i` модуля `uart_tx` подаётся единица в момент выполнения **запроса на запись** по адресу `0x00` (при сигнале `busy` равном нулю). В остальное время на вход этого сигнала подаётся `0`.
В случае **запроса на запись** значения `1` по адресу `0x24` (адресу сброса), все регистры модуля-контроллера должны сброситься. При этом регистр `baudrate` должен принять значение `9600`, регистр, `stopbit` должен принять значение `1`. Остальные регистры должны принять значение `0`.
Адресное пространство контроллера `uart_rx_sb_ctrl`:
Адресное пространство контроллера `uart_rx_sb_ctrl` представлено в аблице 6_.
|Адрес|Режим доступа|Допустимые значения| Функциональное назначение |
|-----|-------------|-------------------|------------------------------------------------------------------------------------------------------------|
@@ -620,22 +620,26 @@ endmodule
|0x04 | R | [0:1] | Чтение из регистра `valid`, сообщающего о том, что есть непрочитанные данные в регистре `data` |
|0x08 | R | [0:1] | Чтение из регистра `busy`, сообщающего о том, что модуль находится в процессе приема данных |
|0x0C | RW | [0:131072] | Чтение/запись регистра `baudrate`, отвечающего за скорость передачи данных |
|0x10 | RW | [0:1] | Чтение/запись регистра `parity_en`, отвечающего за включение отключение проверки данных через бит четности |
|0x10 | RW | [0:1] | Чтение/запись регистра `parity_en`, отвечающего за включение отключение проверки данных через бит чётности |
|0x14 | RW | [1:2] | Чтение/запись регистра `stopbit`, хранящего длину стопового бита |
|0x24 | W | 1 | Запись сигнала сброса |
Адресное пространство контроллера `uart_tx_sb_ctrl`:
_Таблица 6. Адресное пространство приёмника UART._
Адресное пространство контроллера `uart_tx_sb_ctrl` представлено в аблице 7_.
|Адрес|Режим доступа|Допустимые значения| Функциональное назначение |
|-----|-------------|-------------------|------------------------------------------------------------------------------------------------------------|
|0x00 | RW | [0:255] | Чтение и запись регистра `data`, хранящего значение отправляемых данных |
|0x08 | R | [0:1] | Чтение из регистра `busy`, сообщающего о том, что модуль находится в процессе передачи данных |
|0x0C | RW | [0:131072] | Чтение/запись регистра `baudrate`, отвечающего за скорость передачи данных |
|0x10 | RW | [0:1] | Чтение/запись регистра `parity_en`, отвечающего за включение отключение проверки данных через бит четности |
|0x10 | RW | [0:1] | Чтение/запись регистра `parity_en`, отвечающего за включение отключение проверки данных через бит чётности |
|0x14 | RW | [1:2] | Чтение/запись регистра `stopbit`, хранящего длину стопового бита |
|0x24 | W | 1 | Запись сигнала сброса |
В случае установки регистра `parity_en` в значение `1`, модуль uart_tx будет дополнять посылку битом четности (который вычисляется как исключающее ИЛИ по всем битам передаваемого байта). Модуль `uart_rx` же будет выполнять проверку этого бита с тем, что он рассчитает самостоятельно. Однако в случае появления ошибки, внешне его поведение никак не изменится (поскольку выход `err_o` данного модуля закомментирован ради простоты системы).
_Таблица 7. Адресное пространство передатчика UART._
В случае установки регистра `parity_en` в значение `1`, модуль uart_tx будет дополнять посылку битом чётности (который вычисляется как исключающее ИЛИ по всем битам передаваемого байта). Модуль `uart_rx` же будет выполнять проверку этого бита с тем, что он рассчитает самостоятельно. Однако в случае появления ошибки, внешне его поведение никак не изменится (поскольку выход `err_o` данного модуля закомментирован ради простоты системы).
### Видеоадаптер
@@ -643,17 +647,17 @@ endmodule
![https://upload.wikimedia.org/wikipedia/commons/c/cf/RebelstarII.png](https://upload.wikimedia.org/wikipedia/commons/c/cf/RebelstarII.png)
_Рисунок 3. Пример игры с использованием символьной графики[[2]](https://en.wikipedia.org/wiki/Rebelstar)._
_Рисунок 2. Пример игры с использованием символьной графики[[2]](https://en.wikipedia.org/wiki/Rebelstar)._
Для управления выводимым на экран содержимым, адресное пространство модуля разделено на следующие диапазоны:
Для управления выводимым на экран содержимым, адресное пространство модуля разделено на диапазоны, представленные в аблице 8_.
![../../.pic/Labs/lab_13_periph/fig_04.png](../../.pic/Labs/lab_13_periph/fig_04.png)
![../../.pic/Labs/lab_13_periph/tab_08.png](../../.pic/Labs/lab_13_periph/tab_08.png)
_Рисунок 4. Карта памяти vga-модуля._
_Таблица 8. Адресное пространство контроллера VGA._
Для того, чтобы вывести символ на экран, необходимо использовать адрес этого символа на сетке `80x30` (диапазон адресов `char_map`). К примеру, мы хотим вывести символ в верхнем левом углу (т.е. нулевой символ нулевой строки). Это нулевой символ в диапазоне адресов `char_map`. Поскольку данный диапазон начинается с адреса `0x0000_0000`, запись по этому адресу приведет к отображению символа, соответствующего [ASCII-коду](https://www.asciitable.com/), пришедшему на `write_data_i`.
Для того, чтобы вывести символ на экран, необходимо использовать адрес этого символа на сетке `80x30` (диапазон адресов `char_map`). К примеру, мы хотим вывести символ в верхнем левом углу (т.е. нулевой символ нулевой строки). Это нулевой символ в диапазоне адресов `char_map`. Поскольку данный диапазон начинается с адреса `0x0000_0000`, запись по этому адресу приведёт к отображению символа, соответствующего [ASCII-коду](https://www.asciitable.com/), пришедшему на `write_data_i`.
Если мы хотим вывести нулевой (левый) символ в первой строке (счет ведется с нуля), то необходимо произвести запись по адресу `1*80+0=80=0x0000_0050`.
Если мы хотим вывести нулевой (левый) символ в первой строке (счёт ведётся с нуля), то необходимо произвести запись по адресу `1*80+0=80=0x0000_0050`.
Вывод символа в правом нижнем углу осуществляется записью по адресу `0x0000_095F` (80*30-1)
@@ -665,17 +669,17 @@ _Рисунок 4. Карта памяти vga-модуля._
Цветовая схема каждой позиции состоит из двух цветов: цвета фона и цвета символа. Оба эти цвета выбираются из палитры 8 цветов, каждый из которых содержит два оттенка: цвет на полной яркости и цвет на уменьшенной яркости (см. _рис. 5_). Один из цветов — черный, оба его оттенка представляют собой один и тот же цвет. На _рис. 5_ приведены коды цветов их rgb-значения:
![../../.pic/Labs/lab_13_periph/fig_05.png](../../.pic/Labs/lab_13_periph/fig_05.png)
![../../.pic/Labs/lab_13_periph/fig_03.png](../../.pic/Labs/lab_13_periph/fig_03.png)
_Рисунок 5. Цветовая палитра vga-модуля._
_Рисунок 3. Цветовая палитра vga-модуля._
Код цвета формируется следующим образом: старший бит определяет яркость оттенка цвета. Оставшиеся 3 бита кодируют используемый канал:
- 0 бит кодирует использование синего канала;
- 1 бит кодирует использование зеленого канала;
- 1 бит кодирует использование зелёного канала;
- 2 бит кодирует использование красного канала.
Таким образом, для установки цветовой схемы, необходимо выбрать два цвета из палитры, склеить их (в старших разрядах идет цвет символа, в младших — цвет фона) и записать получившееся 8-битное значение по адресу выбранной позиции в диапазоне адресов цветовой схемы (color_map).
Таким образом, для установки цветовой схемы, необходимо выбрать два цвета из палитры, склеить их (в старших разрядах идёт цвет символа, в младших — цвет фона) и записать получившееся 8-битное значение по адресу выбранной позиции в диапазоне адресов цветовой схемы (color_map).
К примеру, мы хотим установить черный фоновый цвет и белый цвет в качестве цвета символа для верхней левой позиции. В этом случае, мы должны записать значение `f0` (f(15) — код белого цвета, 0 — код черного цвета) по адресу `0x0000_1000` (нулевой адрес в диапазоне `color_map`).
@@ -683,15 +687,15 @@ _Рисунок 5. Цветовая палитра vga-модуля._
Допустим, нам необходимо отрисовать символ `F` (ascii-код `0x46`).
![../../.pic/Labs/lab_13_periph/fig_06.png](../../.pic/Labs/lab_13_periph/fig_06.png)
![../../.pic/Labs/lab_13_periph/fig_04.png](../../.pic/Labs/lab_13_periph/fig_04.png)
_Рисунок 6. Отрисовка символа `F` в разрешении 16х8 пикселей._
_Рисунок 4. Отрисовка символа `F` в разрешении 16х8 пикселей._
Данный символ состоит из 16 строчек по 8 пикселей. Каждый пиксель кодируется одним битом (горит/не горит, цвет символа/фоновый цвет). Каждая строчка кодируется одним байтом (8 бит на 8 пикселей). Таким образом, каждый сканкод требует 16 байт памяти.
Данный модуль поддерживает 256 сканкодов. Следовательно, для хранения шрифта под каждый из 256 сканкодов требуется 16 * 256 = 4KiB памяти.
Для хранения шрифтов в модуле отведен диапазон адресов `0x00002000-0x00002FFF`. В отличие от предыдущих диапазонов адресов, где каждый адрес был закреплен за соответствующей позицией символа в сетке `80x30`, адреса данного диапазона распределены следующим образом:
Для хранения шрифтов в модуле отведён диапазон адресов `0x00002000-0x00002FFF`. В отличие от предыдущих диапазонов адресов, где каждый адрес был закреплён за соответствующей позицией символа в сетке `80x30`, адреса данного диапазона распределены следующим образом:
- 0-ой байт — нулевая (верхняя) строчка символа с кодом 0;
- 1-ый байт — первая строчка символа с кодом 0;
@@ -705,7 +709,7 @@ _Рисунок 6. Отрисовка символа `F` в разрешении
Прототип vga-модуля следующий:
```SystemVerilog
```Verilog
module vgachargen (
input logic clk_i, // системный синхроимпульс
input logic clk100m_i, // клок с частотой 100МГц
@@ -763,7 +767,7 @@ module vgachargen (
Для управления данным модулем, необходимо написать модуль-контроллер со следующим прототипом:
```SystemVerilog
```Verilog
module vga_sb_ctrl (
input logic clk_i,
input logic rst_i,
@@ -814,20 +818,20 @@ module vga_sb_ctrl (
Все эти сигналы мультиплексируются / демультиплексируются с помощью одного и того же управляющего сигнала: `addr_i[13:12]` в соответствии с диапазонами адресов (рис. 4):
- `addr_i[13:12] == 2'b00`
- `req_i` подается на вход `char_map_req_i`,
- `req_i` подаётся на вход `char_map_req_i`,
- `write_enable_i` поступает на вход `char_map_we_i`,
- `char_map_rdata_o` подается на выход `read_data_o`;
- `char_map_rdata_o` подаётся на выход `read_data_o`;
- `addr_i[13:12] == 2'b01`
- `req_i` поступает на вход `col_map_req_i`,
- `write_enable_i` поступает на вход `col_map_we_i`,
- `col_map_rdata_o` подается на выход `read_data_o`;
- `col_map_rdata_o` подаётся на выход `read_data_o`;
- `addr_i[13:12] == 2'b10`
- `req_i` поступает на вход `char_tiff_req_i`,
- `write_enable_i` поступает на вход `char_tiff_we_i`,
- `char_tiff_rdata_o` подается на выход `read_data_o`.
- `char_tiff_rdata_o` подаётся на выход `read_data_o`.
> [!Important]
> Обратите внимание на то, что контроллер vga является единственным контроллером, для которого не нужно реализовывать регистр перед выходом read_data_o для реализации синхронного чтения. Данная особенность обусловлена тем, что внутри модуля `vgachargen` уже находится блочная память с синхронным портом на чтение. Добавление еще одного регистра приведет к тому, данные будут "опаздывать" на один такт. Таким образом, данные на выход `read_data_o` необходимо подавать с помощью чисто комбинационной логики.
> Обратите внимание на то, что контроллер vga является единственным контроллером, для которого не нужно реализовывать регистр перед выходом read_data_o для реализации синхронного чтения. Данная особенность обусловлена тем, что внутри модуля `vgachargen` уже находится блочная память с синхронным портом на чтение. Добавление ещё одного регистра приведёт к тому, данные будут "опаздывать" на один такт. Таким образом, данные на выход `read_data_o` необходимо подавать с помощью чисто комбинационной логики.
## Список использованной литературы