# Лабораторная работа 13 "Периферийные устройства"

На прошлой лабораторной работе вы реализовали свой собственный RISC-V процессор. Однако пока что он находится "в вакууме" и никак не связан с внешним миром. Для исправления этого недостатка вами будет реализована системная шина, через которую к процессору смогут подключаться различные периферийные устройства.

## Цель

Интегрировать периферийные устройства в процессорную систему.

---

## Допуск к лабораторной работе

Для успешного выполнения лабораторной работы, вам необходимо:

* ознакомиться с [примером описания модуля-контроллера](../../Basic%20Verilog%20structures/Controllers.md);
* ознакомиться с [описанием](#описание-контроллеров-периферийных-устройств) контроллеров периферийных устройств.

## Ход работы

1. Изучить теорию об адресном пространстве
2. Получить индивидуальный вариант со своим набором периферийных устройств
3. Интегрировать контроллеры периферийных устройств в адресное пространство вашей системы
4. Собрать финальную схему вашей системы
5. Проверить работу системы в ПЛИС с помощью демонстрационного ПО, загружаемого в память инструкций

---

## Теория

Помимо процессора и памяти, третьим ключевым элементом вычислительной системы является система ввода/вывода, обеспечивающая обмен информации между ядром вычислительной машины и периферийными устройствами[1, стр. 364].

Любое периферийное устройство со стороны вычислительной машины видится как набор ячеек памяти (регистров). С помощью чтения и записи этих регистров происходит обмен информации с периферийным устройством, и управление им. Например, датчик температуры может быть реализован самыми разными способами, но для процессора он в любом случае ячейка памяти, из которой он считывает число – температуру.

Система ввода/вывода может быть организована одним из двух способов: с **выделенным адресным пространством** устройств ввода/вывода, или с **совместным адресным пространством**. В первом случае система ввода/вывода имеет отдельную шину для подключения к процессору (и отдельные инструкции для обращения к периферии), во втором – шина для памяти и системы ввода/вывода общая (а обращение к периферии осуществляется теми же инструкциями, что и обращение к памяти).

### Адресное пространство

Архитектура RISC-V подразумевает использование совместного адресного пространства — это значит, что в лабораторной работе будет использована единая шина для подключения памяти и регистров управления периферийными устройствами. При обращении по одному диапазону адресов процессор будет попадать в память, при обращении по другим – взаимодействовать с регистрами управления/статуса периферийного устройства. Например, можно разделить 32-битное адресное пространство на 256 частей, отдав старшие 8 бит адреса под указание конкретного периферийного устройства. Тогда каждое из периферийных устройств получит 24-битное адресное пространство (16 MiB). Допустим, мы распределили эти части адресного пространства в следующем порядке (от младшего диапазона адресов к старшему):

0. Память данных
1. Переключатели
2. Светодиоды
3. Клавиатура PS/2
4. Семисегментные индикаторы
5. UART-приемник
6. UART-передатчик
7. Видеоадаптер

В таком случае, если мы захотим обратиться к первому (счет идет с нуля) байту семисегментных индикаторов, мы должны будем использовать адрес `0x04000001`. Старшие 8 бит (`0x04`) определяют выбранное периферийное устройство, оставшиеся 24 бита определяют конкретный адрес в адресном пространстве этого устройства.

На рисунке ниже представлен способ подключения процессора к памяти инструкций и данных, а также 255 периферийным устройствам.

![../../.pic/Labs/lab_13_periph/fig_01.drawio.svg](../../.pic/Labs/lab_13_periph/fig_01.drawio.svg)

_Рисунок 1. Итоговая структура процессорной системы._

### Активация выбранного устройства

В зависимости от интерфейса используемой шины, периферийные устройства либо знают какой диапазон адресов им выделен (например, в интерфейсе I²C), либо нет (интерфейс APB). В первом случае, устройство понимает что к нему обратились непосредственно по адресу в данном обращении, во втором случае — по специальному сигналу.

На _рис. 1_ используется второй вариант — устройство понимает, что к нему обратились по специальному сигналу `req_i`. Данный сигнал формируется из двух частей: сигнала `req` исходящего из процессорного ядра (сигнал о том, обращение в память вообще происходит) и специального сигнала-селектора исходящего из 256-разрядной шины. Формирование значения на этой шине происходит с помощью [унитарного](https://ru.wikipedia.org/wiki/Унитарный_код) ([one-hot](https://en.wikipedia.org/wiki/One-hot)) кодирования. Процесс кодирования достаточно прост. В любой момент времени на выходной шине должен быть **ровно один** бит, равный единице. Индекс этого бита совпадает со значением старших восьми бит адреса. Поскольку для восьмибитного значения существует 256 комбинаций значений, именно такая разрядность будет на выходе кодировщика. Это означает, что в данной системе можно связать процессор с 256 устройствами (одним из которых будет память данных).

Реализация такого кодирования предельно проста:

* Нулевой сигнал этой шины будет равен единице только если `data_addr_o[31:24] = 8'd0`.
* Первый бит этой шины будет равен единице только если `data_addr_o[31:24] = 8'd1`.
* ...
* Двести пятьдесят пятый бит шины будет равен единице только если `data_addr_o[31:24] = 8'd255`.

Для реализации такого кодирования достаточно выполнить сдвиг влево константы `255'd1` на значение `data_addr_o[31:24]`.

### Дополнительные правки модуля riscv_unit

Ранее, для того чтобы ваши модули могли работать в ПЛИС, вам предоставлялся специальный модуль верхнего уровня, который выполнял всю работу по связи с периферией через входы и выходы ПЛИС. Поскольку в текущей лабораторной вы завершаете свою процессорную систему, она сама должна оказаться модулем верхнего уровня, а значит здесь вы должны и выполнить всё подключение к периферии.

Для этого необходимо добавить в модуль `riscv_unit` дополнительные входы и выходы, которые подключены посредством файла ограничений ([nexys_a7_100t.xdc](nexys_a7_100t.xdc)) к входам и выходам ПЛИС.

```SystemVerilog
module riscv_unit(
  input  logic        clk_i,
  input  logic        resetn_i,

  // Входы и выходы периферии
  input  logic [15:0] sw_i,       // Переключатели

  output logic [15:0] led_o,      // Светодиоды

  input  logic        kclk_i,     // Тактирующий сигнал клавиатуры
  input  logic        kdata_i,    // Сигнал данных клавиатуры

  output logic [ 6:0] hex_led_o,  // Вывод семисегментных индикаторов
  output logic [ 7:0] hex_sel_o,  // Селектор семисегментных индикаторов

  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_b_o,    // синий канал vga
  output logic        vga_hs_o,   // линия горизонтальной синхронизации vga
  output logic        vga_vs_o    // линия вертикальной синхронизации vga

);
//...
endmodule
```

Эти порты нужно подключить к одноименным портам ваших контроллеров периферии (**речь идет только о реализуемых вами контроллерах, остальные порты должны остаться неподключенными**). Иными словами, в описании модуля должны быть все указанные входы и выходы. Но использовать вам нужно только порты, связанные с теми периферийными устройствами, реализацию которых вам необходимо подключить к процессорной системе в рамках индивидуального задания.

Обратите внимание на то, что изменился сигнал сброса (`resetn_i`). Буква `n` на конце означает, что сброс работает по уровню `0` (когда сигнал равен нулю — это сброс, когда единице — не сброс).

Помимо прочего, необходимо подключить к вашему модулю `блок делителя частоты`. Поскольку в данном курсе лабораторных работ вы выполняли реализацию однотактного процессора, инструкция должна пройти через все ваши блоки за один такт. Из-за этого критический путь вашей схемы не позволит использовать тактовый сигнал частотой в `100 МГц`, от которого работает отладочный стенд. Поэтому, необходимо создать отдельный сигнал с пониженной тактовой частотой, от которого будет работать ваша схема.

Для этого необходимо:

1. Подключить файл [`sys_clk_rst_gen.sv`](sys_clk_rst_gen.sv) в ваш проект.
2. Подключить этот модуль в начале описания модуля `riscv_unit` следующим образом:

```SystemVerilog
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_](../../.pic/Labs/lab_13_periph/fig_01.drawio.svg). На карте приведено шесть периферийных устройств, вам необходимо взять только два из них. Какие именно — сообщит преподаватель.

![Карта памяти](../../.pic/Labs/lab_13_periph/fig_02.png)

_Рисунок 2. Карта памяти периферийных устройств._

Работа с картой осуществляется следующим образом. Под названием каждого периферийного устройства указана старшая часть адреса (чему должны быть равны старшие 8 бит адреса, чтобы было сформировано обращение к данному периферийному устройству). Например, для переключателей это значение равно `0x01`, для светодиодов `0x02` и т.п.
В самом левом столбце указаны используемые/неиспользуемые адреса в адресном пространстве данного периферийного устройства. Например для переключателей есть только один используемый адрес: `0x000000`. Его функциональное назначение и разрешения на доступ указаны в столбце соответствующего периферийного устройства. Возвращаясь к адресу `0x000000`, для переключателей мы видим следующее:

* **(R)** означает что разрешен доступ только на чтение (операция записи по этому адресу должна игнорироваться вашим контроллером).
* **"Выставленное на переключателях значение"** означает ровно то, что и означает. Если процессор выполняет операцию чтения по адресу `0x01000000` (`0x01` [старшая часть адреса переключателей] + `0x000000` [младшая часть адреса для получения выставленного на переключателях значения]), то контроллер должен выставить на выходной сигнал `RD` значение на переключателях (о том как получить это значение будет рассказано чуть позже).

Рассмотрим еще один пример. При обращении по адресу `0x02000024` (`0x02` (старшая часть адреса контроллера светодиодов) + `0x000024` (младшая часть адреса для доступа на запись к регистру сброса) ) должна произойти запись в регистр сброса, который должен сбросить значения в регистре управления зажигаемых светодиодов и регистре управления режимом "моргания" светодиодов (подробнее о том как должны работать эти регистры будет ниже).

Таким образом, каждый контроллер периферийного устройства должен выполнять две вещи:

1. При получении сигнала `req_i`, записать в регистр или вернуть значение из регистра, ассоциированного с переданным адресом (адрес передается с обнуленной старшей частью). Если регистра, ассоциированного с таким адресом нет (например, для переключателей не ассоциировано ни одного адреса кроме `0x000000`), игнорировать эту операцию.
2. Выполнять управление периферийным устройством с помощью управляющих регистров.

Подробное описание периферийных устройств их управления и назначение управляющих регистров будет дано после порядка выполнения задания.

---

## Порядок выполнения задания

1. Внимательно ознакомьтесь с [примером описания модуля контроллера](../../Basic%20Verilog%20structures/Controllers.md).
2. Внимательно ознакомьтесь со спецификацией контроллеров периферии своего варианта. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
3. Реализуйте модули контроллеров периферии. Имена модулей и их порты будут указаны в [описании контроллеров](#описание-контроллеров-периферийных-устройств). Пример разработки контроллера приведен [здесь](../../Basic%20Verilog%20structures/Controllers.md).
   1. Готовые модули периферии, управление которыми должны осуществлять модули-контроллеры хранятся в папке `peripheral modules`.
4. Обновите модуль `riscv_unit` в соответствии с разделом ["Дополнительные правки модуля riscv_unit"](#дополнительные-правки-модуля-riscv_unit).
   1. Подключите в проект файл `sys_clk_rst_gen.sv`.
   2. Добавьте в модуль `riscv_unit` входы и выходы периферии. **Необходимо добавить порты даже тех периферийных устройств, которые вы не будете реализовывать**.
   3. Создайте в начале описания модуля `riscv_unit` экземпляр модуля `sys_clk_rst_gen`, скопировав приведенный фрагмент кода.
   4. Замените подключение тактового сигнала исходных подмодулей `riscv_unit` на появившийся сигнал `sysclk`. Убедитесь, что на модули, имеющие сигнал сброса, приходит сигнал `rst`.
5. Интегрируйте модули контроллеров периферии в процессорную систему по приведенной схеме руководствуясь старшими адресами контроллеров, представленными на карте памяти ([_рис. 2_](../../.pic/Labs/lab_13_periph/fig_02.png)). Это означает, что если вы реализуете контроллер светодиодов, на его вход `req_i` должна подаваться единица в случае, если `mem_req_o == 1` и старшие 8 бит адреса равны `0x02`.
   1. При интеграции вы должны подключить только модули-контроллеры вашего варианта. Контроллеры периферии других вариантов подключать не надо.
   2. При этом во время интеграции, вы должны использовать старшую часть адреса, представленную в карте памяти для формирования сигнала `req_i` для ваших модулей-контроллеров.
6. Проверьте работу процессорной системы с помощью моделирования.
   1. Для каждой пары контроллеров в папке `firmware/mem_files` представлены файлы, инициализирующие память инструкций. Обратите внимание, что для пары "PS2-VGA" также необходим файл, инициализирующий память данных (в модуле `ext_mem` необходимо прописать блок `$readmemh`).
   2. Исходный код программ с адресами и результирующими инструкциями находится в папке `firmware/software`.
   3. При моделировании светодиодов лучше уменьшить значение, до которого считает счетчик в режиме "моргания" в 10 раз, чтобы уменьшить время моделирования. Перед генерацией битстрима это значение будет необходимо восстановить.
   4. Для проверки тестбенч имитирует генерацию данных периферийных устройств ввода. При реализации контроллера клавиатуры или uart_rx рекомендуется ознакомиться с тем, какие именно данные тестбенч подает на вход.
   5. Для того, чтобы понять, что устройство работает должным образом, в первую очередь необходимо убедиться, что контроллер устройства ввода успешно осуществил прием данных (сгенерированные тестбенчем данные оказались в соответствующем регистре контроллера периферийного устройства) и сгенерировал запрос на прерывание.
   6. После чего, необходимо убедиться, что процессор среагировал на данное прерывание, и в процессе его обработки в контроллер устройства вывода были поданы выходные данные.
   7. Для того, чтобы лучше понимать как именно процессор будет обрабатывать прерывание, рекомендуется ознакомиться с исходным кодом исполняемой программы, расположенным в папке `firmware/software`.
7. Переходить к следующему пункту можно только после того, как вы полностью убедились в работоспособности модуля на этапе моделирования (увидели корректные значения на выходных сигналах периферии, либо (если по сигналам периферии сложно судить о работоспособности), значениям в контрольных/статусных регистрах модуля-контроллера этой периферии). Генерация битстрима будет занимать у вас долгое время, а итогом вы получите результат: заработало / не заработало, без какой-либо дополнительной информации, поэтому без прочного фундамента на моделировании далеко уехать у вас не выйдет.
   <!-- 1. Для каждой пары контроллеров периферии предложено две программы: с обновлением данных по опросу и по прерываниям. Запустите моделирование сначала для одной программы, затем для другой (для этого необходимо обновить файл, инициализирующий память инструкций). После проверки работоспособности процессора, сравните поведение сигналов LSU для этих программ. -->
8. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](nexys_a7_100t.xdc)), если тот еще не был подключен, либо замените его содержимое данными из файла к этой лабораторной работе.
9. Проверьте работу вашей процессорной системы на отладочном стенде с ПЛИС.
   1. Обратите внимание, что в данной лабораторной уже не будет модуля верхнего уровня `nexys_...`, так как ваш модуль процессорной системы уже полностью самостоятелен и взаимодействует непосредственно с ножками ПЛИС через модули, управляемые контроллерами периферии.

---

## Описание контроллеров периферийных устройств

Для того, чтобы избежать избыточности в контексте описания контроллеров периферийных устройств будет использоваться два термина:

1. Под "**запросом на запись** по адресу `0xАДРЕС`" будет пониматься совокупность следующих условий:
   1. Происходит восходящий фронт `clk_i`.
   2. На входе `req_i` выставлено значение `1`.
   3. На входе `write_enable_i` выставлено значение `1`.
   4. На входе `addr_i` выставлено значение `0xАДРЕС`
2. Под "**запросом на чтение** по адресу `0xАДРЕС`" будет пониматься совокупность следующих условий:
   1. На входе `req_i` выставлено значение `1`.
   2. На входе `write_enable_i` выставлено значение `0`.
   3. На входе `addr_i` выставлено значение `0xАДРЕС`

Обратите внимание на то, что **запрос на чтение** должен обрабатываться **синхронно** (выходные данные должны выдаваться по положительному фронту `clk_i`).

При описании поддерживаемых режимов доступа по данному адресу используется следующее обозначение:

* R — доступ **только на чтение**;
* W — доступ **только на запись**;
* RW — доступ на **чтение и запись**.

В случае отсутствия **запроса на чтение**, на выходе `read_data_o` не должно меняться значение (тоже самое было сделано в процессе разработки памяти данных).

Если пришел **запрос на запись** или **чтение**, это еще не значит, что контроллер должен его выполнить. В случае, если запрос происходит по адресу, не поддерживающему этот запрос (например **запрос на запись** по адресу, поддерживающему только чтение), данный запрос должен игнорироваться. В случае **запроса на чтение** по недоступному адресу, на выходе `read_data_o` должно остаться прежнее значение.

В случае осуществления записи по принятому запросу, необходимо записать данные с сигнала `write_data_i` в регистр, ассоциированный с адресом `addr_i` (если разрядность регистра меньше разрядности сигнала `write_data_i`, старшие биты записываемых данных отбрасываются).

В случае осуществления чтения по принятому запросу, необходимо по положительному фронту `clk_i` выставить данные с сигнала, ассоциированного с адресом `addr_i` на выходной сигнал `read_data_o` (если разрядность сигнала меньше разрядности выходного сигнала `read_data_o`, возвращаемые данные должны дополниться нулями в старших битах).

### Переключатели

Переключатели являются простейшим устройством ввода на отладочном стенде `Nexys A7`. Соответственно и контроллер, осуществляющий доступ процессора к ним так же будет очень простым. Рассмотрим прототип модуля, который вам необходимо реализовать:

```SystemVerilog
module sw_sb_ctrl(
/*
    Часть интерфейса модуля, отвечающая за подключение к системной шине
*/
  input  logic        clk_i,
  input  logic        rst_i,
  input  logic        req_i,
  input  logic        write_enable_i,
  input  logic [31:0] addr_i,
  input  logic [31:0] write_data_i,  // не используется, добавлен для
                                     // совместимости с системной шиной
  output logic [31:0] read_data_o,

/*
    Часть интерфейса модуля, отвечающая за отправку запросов на прерывание
    процессорного ядра
*/

  output logic        interrupt_request_o,
  input  logic        interrupt_return_i,

/*
    Часть интерфейса модуля, отвечающая за подключение к периферии
*/
  input logic [15:0]  sw_i
);

endmodule
```

По сути, логика работы контроллера сводится к тому, выдавать на шину `read_data_o` данные со входа `sw_i` каждый раз, когда приходит **запрос на чтение** по нулевому адресу. Поскольку разрядность `sw_i` в два раза меньше разрядности выхода `read_data_o` его старшие биты необходимо дополнить нулями.

Адресное пространство контроллера:

|Адрес|Режим доступа|           Функциональное назначение             |
|-----|-------------|-------------------------------------------------|
|0x00 | R           | Чтение значения, выставленного на переключателях|

При этом, будучи устройством ввода, данный модуль может генерировать прерывание, чтобы сообщить процессору о том, что данные на переключателях были изменены. Если на очередном такте `clk_i` данные на входе `sw_i` изменились (т.е. отличаются от тех, что были на предыдущем такте), модуль должен выставить значение `1` на выходе `interrupt_request_o` и удерживать его до получения сигнала о завершении обработки прерывания `interrupt_return_i`.

Для отслеживания изменений на входе `sw_i` между тактами синхроимпульса вам потребуется вспомогательный регистр, каждый такт сохраняющий значение `sw_i`. При реализации данного регистра, не забывайте о том, что его необходимо сбрасывать посредством сигнала `rst_i`.

### Светодиоды

Как и переключатели, светодиоды являются простейшим устройством вывода. Поэтому, чтобы задание было интересней, для их управления был добавлен регистр, управляющий режимом вывода данных на светодиоды.
Рассмотрим прототип модуля, который вам необходимо реализовать:

```SystemVerilog
module led_sb_ctrl(
/*
    Часть интерфейса модуля, отвечающая за подключение к системной шине
*/
  input  logic        clk_i,
  input  logic        rst_i,
  input  logic        req_i,
  input  logic        write_enable_i,
  input  logic [31:0] addr_i,
  input  logic [31:0] write_data_i,
  output logic [31:0] read_data_o,

/*
    Часть интерфейса модуля, отвечающая за подключение к периферии
*/
  output logic [15:0] led_o
);

logic [15:0]  led_val;
logic         led_mode;

endmodule
```

Данный модуль должен выводить на выходной сигнал `led_o` данные с регистра `led_val`. Запись и чтение регистра `led_val` осуществляется по адресу `0x00`. Запись любого значения, превышающего `2¹⁶-1` должна игнорироваться.

Регистр `led_mode` отвечает за режим вывода данных на светодиоды. Когда этот регистр равен единице, светодиоды должны "моргать" выводимым значением. Под морганием подразумевается вывод значения из регистра `led_val` на выход `led_o` на одну секунду (загорится часть светодиодов, соответствующие которым биты шины `led_o` равны единице), после чего на одну секунду выход `led_o` необходимо подать нули. Запись и чтение регистра `led_mode` осуществляется по адресу `0x04`. Запись любого значения, отличного от `0` и `1` в регистр `led_mode` должна игнорироваться.

Отсчет времени можно реализовать простейшим счетчиком, каждый такт увеличивающимся на 1 и сбрасывающимся по достижении определенного значения, чтобы продолжить считать с нуля. Зная тактовую частоту, нетрудно определить до скольки должен считать счетчик. При тактовой частоте в 10 МГц происходит 10 миллионов тактов в секунду. Это означает, что при такой тактовой частоте через секунду счетчик будет равен `10⁷-1` (счет идет с нуля). Тем не менее удобней будет считать не до `10⁷-1` (что было бы достаточно очевидным и тоже правильным решением), а до `2*10⁷-1`. В этом случае старший бит счетчика каждую секунду будет инвертировать свое значение, что может быть использовано при реализации логики "моргания".

Важно отметить, что счетчик должен работать только при `led_mode == 1`, в противном случае счетчик должен быть равен нулю.

Обратите внимание на то, что адрес `0x24` является адресом сброса. В случае **запроса на запись** по этому адресу значения `1`. вы должны сбросить регистры `led_val`, `led_mode` и все вспомогательные регистры, которые вы создали. Для реализации сброса вы можете как создать отдельный регистр `led_rst`, в который будет происходить запись, а сам сброс будет происходить по появлению единицы в этом регистре (в этом случае необходимо не забыть сбрасывать и этот регистр), так и создать обычный провод, формирующий единицу в случае выполнения всех указанных условий (условий **запроса на запись**, адреса сброса и значения записываемых данных равному единице).

Адресное пространство контроллера:

|Адрес|Режим доступа|Допустимые значения|                       Функциональное назначение                                   |
|-----|-------------|-------------------|-----------------------------------------------------------------------------------|
|0x00 | RW          | [0:65535]         | Чтение и запись в регистр `led_val` отвечающий за вывод данных на светодиоды      |
|0x04 | RW          | [0:1]             | Чтение и запись в регистр `led_mode`, отвечающий за режим "моргания" светодиодами |
|0x24 | W           |  1                | Запись сигнала сброса                                                             |

### Клавиатура PS/2

Клавиатура [PS/2](https://ru.wikipedia.org/wiki/PS/2_(порт)) осуществляет передачу [скан-кодов](https://ru.wikipedia.org/wiki/Скан-код), нажатых на этой клавиатуре клавиш.

В рамках данной лабораторной работы вам будет дан готовый модуль, осуществляющий прием данных с клавиатуры. От вас требуется написать лишь модуль, осуществляющий контроль предоставленным модулем. У готового модуля будет следующий прототип:

```SystemVerilog
module PS2Receiver(
    input        clk_i,          // Сигнал тактирования процессора и вашего модуля-контроллера
    input        kclk_i,         // Тактовый сигнал, приходящий с клавиатуры
    input        kdata_i,        // Сигнал данных, приходящий с клавиатуры
    output [7:0] keycode_o,      // Сигнал полученного с клавиатуры скан-кода клавиши
    output       keycode_valid_o // Сигнал готовности данных на выходе keycodeout
    );
endmodule
```

Вам необходимо реализовать модуль-контроллер со следующим прототипом:

```SystemVerilog
module ps2_sb_ctrl(
/*
    Часть интерфейса модуля, отвечающая за подключение к системной шине
*/
  input  logic         clk_i,
  input  logic         rst_i,
  input  logic [31:0]  addr_i,
  input  logic         req_i,
  input  logic [31:0]  write_data_i,
  input  logic         write_enable_i,
  output logic [31:0]  read_data_o,

/*
    Часть интерфейса модуля, отвечающая за отправку запросов на прерывание
    процессорного ядра
*/

  output logic        interrupt_request_o,
  input  logic        interrupt_return_i,

/*
    Часть интерфейса модуля, отвечающая за подключение к модулю,
    осуществляющему прием данных с клавиатуры
*/
  input  logic kclk_i,
  input  logic kdata_i
);

logic [7:0] scan_code;
logic       scan_code_is_unread;

endmodule
```

В первую очередь, вы должны создать экземпляр модуля `PS2Receiver` внутри вашего модуля-контроллера, соединив соответствующие входы. Для подключения к выходам необходимо создать дополнительные провода.

По каждому восходящему фронту сигнала `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` уже записывается новое, еще непрочитанное значение).

Обнуление регистра `scan_code_is_unread` должно происходить и в случае получения сигнала `interrupt_return_i` (однако опять же, если в этот момент приходит сигнал `keycode_valid_o`, обнулять регистр не нужно).

В случае **запроса на чтение** по адресу `0x04` необходимо вернуть значение регистра `scan_code_is_unread`.

В случае **запроса на запись** значения `1` по адресу `0x24`, необходимо осуществить сброс регистров `scan_code` и `scan_code_is_unread` в `0`.

Регистр `scan_code_is_unread` необходимо подключить к выходу `interrupt_request_o`. Таким образом процессор может узнавать о нажатых клавишах как посредством программного опроса путем чтения значения регистра`scan_code_is_unread`, так и посредством прерываний через сигнал`interrupt_request_o`.

Адресное пространство контроллера:

|Адрес|Режим доступа|Допустимые значения|                       Функциональное назначение                                                                   |
|-----|-------------|-------------------|-------------------------------------------------------------------------------------------------------------------|
|0x00 | R           | [0:255]           | Чтение из регистра `scan_code`, хранящего скан-код нажатой клавиши                                                |
|0x04 | R           | [0:1]             | Чтение из регистра `scan_code_is_unread`, сообщающего о том, что есть непрочитанные данные в регистре `scan_code` |
|0x24 | W           |  1                | Запись сигнала сброса                                                                                             |

### Семисегментные индикаторы

Семисегментные индикаторы позволяют выводить арабские цифры и первые шесть букв латинского алфавита, тем самым позволяя отображать шестнадцатеричные цифры. На отладочном стенде `Nexys A7` размещено восемь семисегментных индикаторов. Для вывода цифр на эти индикаторы, вам будет предоставлен модуль `hex_digits`, вам нужно лишь написать модуль, осуществляющий контроль над ним. Прототип модуля `hex_digits` следующий:

```SystemVerilog
module hex_digits(
  input  logic       clk_i,
  input  logic       rst_i,
  input  logic [3:0] hex0_i,    // Цифра, выводимой на нулевой (самый правый) индикатор
  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] hex5_i,    // Цифра, выводимая на пятый индикатор
  input  logic [3:0] hex6_i,    // Цифра, выводимая на шестой индикатор
  input  logic [3:0] hex7_i,    // Цифра, выводимая на седьмой индикатор
  input  logic [7:0] bitmask_i, // Битовая маска для включения/отключения
                                // отдельных индикаторов

  output logic [6:0] hex_led_o, // Сигнал, контролирующий каждый отдельный
                                // светодиод индикатора
  output logic [7:0] hex_sel_o  // Сигнал, указывающий на какой индикатор
                                // выставляется hex_led
);
endmodule
```

Для того, чтобы вывести шестнадцатеричную цифру на любой из индикаторов, необходимо выставить двоичное представление этой цифры на соответствующий вход `hex0-hex7`.

За включение/отключение индикаторов отвечает входной сигнал `bitmask_i`, состоящий из 8 бит, каждый из которых включает/отключает соответствующий индикатор. Например, при `bitmask_i == 8'b0000_0101`, включены будут нулевой и второй индикаторы, остальные будут погашены.

Выходные сигналы `hex_led` и `hex_sel` необходимо просто подключить к соответствующим выходным сигналам модуля-контроллера. Они пойдут на выходы ПЛИС, соединенные с семисегментными индикаторами.

Для управления данным модулем, необходимо написать модуль-контроллер со следующим прототипом:

```SystemVerilog
module hex_sb_ctrl(
/*
    Часть интерфейса модуля, отвечающая за подключение к системной шине
*/
  input  logic        clk_i,
  input  logic [31:0] addr_i,
  input  logic        req_i,
  input  logic [31:0] write_data_i,
  input  logic        write_enable_i,
  output logic [31:0] read_data_o,

/*
    Часть интерфейса модуля, отвечающая за подключение к модулю,
    осуществляющему вывод цифр на семисегментные индикаторы
*/
  output logic [6:0] hex_led,
  output logic [7:0] hex_sel
);

  logic [3:0] hex0, hex1, hex2, hex3, hex4, hex5, hex6, hex7;
  logic [7:0] bitmask;
endmodule
```

Регистры `hex0-hex7` отвечают за вывод цифры на соответствующий семисегментный индикатор. Регистр `bitmask` отвечает за включение/отключение семисегментных индикаторов. Когда в регистре `bitmask` бит, индекс которого совпадает с номером индикатора равен единице — тот включен и выводит число, совпадающее со значением в соответствующем регистре `hex0-hex7`. Когда бит равен нулю — этот индикатор гаснет.

Доступ на чтение/запись регистров `hex0-hex7` осуществляется по адресам `0x00-0x1c` (см. таблицу адресного пространства).

Доступ на чтение/запись регистра `bitmask` осуществляется по адресу `0x20`.

При **запросе на запись** единицы по адресу `0x24` необходимо выполнить сброс всех регистров. При этом регистр `bitmask` должен сброситься в значение `0xFF`.

Адресное пространство контроллера:

|Адрес|Режим доступа|Допустимые значения|                 Функциональное назначение               |
|-----|-------------|-------------------|---------------------------------------------------------|
|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                | Запись сигнала сброса                                   |

### UART

[UART](https://ru.wikipedia.org/wiki/Универсальный_асинхронный_приёмопередатчик) — это последовательный интерфейс, использующий для приема и передачи данных по одной независимой линии с поддержкой контроля целостности данных.

Для того, чтобы передача данных была успешно осуществлена, приемник и передатчик на обоих концах одного провода должны договориться о параметрах передачи:

* её скорости (бодрейт);
* контроля целостности данных (использование бита четности/нечетности/отсутствие контроля);
* длины стопового бита.

Вам будут предоставлены модули, осуществляющие прием и передачу данных по этому интерфейсу, от вас лишь требуется написать модули, осуществляющие управление предоставленными модулями.

```SystemVerilog
module uart_rx (
  input  logic            clk_i,      // Тактирующий сигнал
  input  logic            rst_i,      // Сигнал сброса
  input  logic            rx_i,       // Сигнал линии, подключенной к выводу ПЛИС,
                                      // по которой будут приниматься данные
  output logic            busy_o,     // Сигнал о том, что модуль занят приемом данных
  input  logic [16:0]     baudrate_i, // Настройка скорости передачи данных
  input  logic            parity_en_i,// Настройка контроля целостности через бит четности
  input  logic            stopbit_i,  // Настройка длины стопового бита
  output logic [7:0]      rx_data_o,  // Принятые данные
  output logic            rx_valid_o  // Сигнал о том, что прием данных завершен

);
endmodule
```

```SystemVerilog
module uart_tx (
  input  logic            clk_i,      // Тактирующий сигнал
  input  logic            rst_i,      // Сигнал сброса
  output logic            tx_o,       // Сигнал линии, подключенной к выводу ПЛИС,
                                      // по которой будут отправляться данные
  output logic            busy_o,     // Сигнал о том, что модуль занят передачей данных
  input  logic [16:0]     baudrate_i, // Настройка скорости передачи данных
  input  logic            parity_en_i,// Настройка контроля целостности через бит четности
  input  logic            stopbit_i,  // Настройка длины стопового бита
  input  logic [7:0]      tx_data_i,  // Отправляемые данные
  input  logic            tx_valid_i  // Сигнал о старте передачи данных
);
endmodule
```

Для управления этими модулями вам необходимо написать два модуля-контроллера со следующими прототипами

```SystemVerilog
module uart_rx_sb_ctrl(
/*
    Часть интерфейса модуля, отвечающая за подключение к системной шине
*/
  input  logic          clk_i,
  input  logic          rst_i,
  input  logic [31:0]   addr_i,
  input  logic          req_i,
  input  logic [31:0]   write_data_i,
  input  logic          write_enable_i,
  output logic [31:0]   read_data_o,

/*
    Часть интерфейса модуля, отвечающая за отправку запросов на прерывание
    процессорного ядра
*/

  output logic        interrupt_request_o,
  input  logic        interrupt_return_i,

/*
    Часть интерфейса модуля, отвечающая за подключение передающему,
    входные данные по UART
*/
  input  logic          rx_i
);

  logic busy;
  logic [16:0] baudrate;
  logic parity_en;
  logic stopbit;
  logic [7:0]  data;
  logic valid;

endmodule
```

```SystemVerilog
module uart_tx_sb_ctrl(
/*
    Часть интерфейса модуля, отвечающая за подключение к системной шине
*/
  input  logic          clk_i,
  input  logic          rst_i,
  input  logic [31:0]   addr_i,
  input  logic          req_i,
  input  logic [31:0]   write_data_i,
  input  logic          write_enable_i,
  output logic [31:0]   read_data_o,

/*
    Часть интерфейса модуля, отвечающая за подключение передающему,
    выходные данные по UART
*/
  output logic          tx_o
);

  logic busy;
  logic [16:0] baudrate;
  logic parity_en;
  logic stopbit;
  logic [7:0]  data;

endmodule
```

У обоих предоставленных модулей схожий прототип, различия заключаются лишь в направлениях сигналов `data` и `valid`.

Взаимодействие модулей `uart_rx` и `uart_tx` с соответствующими модулями-контроллерами осуществляется следующим образом.

Сигналы `clk_i` и `rx_i`/`tx_o` подключаются напрямую к соответствующим сигналам модулей-контроллеров.

Сигнал `rst_i` модулей `uart_rx` / `uart_tx` должен быть равен единице при **запросе на запись** единицы по адресу `0x24`, а также в случае, когда сигнал `rst_i` модуля-контроллера равен единице.

Выходной сигнал `busy_o` на каждом такте `clk_i` должен записываться в регистр `busy`, доступ на чтение к которому осуществляется по адресу `0x08`.

Значения входных сигналов `baudrate_i`, `parity_en_i`, `stopbit_i` берутся из соответствующих регистров, доступ на запись к которым осуществляется по адресам `0x0C`, `0x10`, `0x14` соответственно, но только в моменты, когда выходной сигнал `busy_o` равен нулю. Иными словами, изменение настроек передачи возможно только в моменты, когда передача не происходит. Доступ на чтение этих регистров может осуществляться в любой момент времени.

В регистр `data` модуля `uart_rx_sb_ctrl` записывается значение одноименного выхода модуля `uart_rx` в моменты положительного фронта `clk_i`, когда сигнал `rx_valid_o` равен единице. Доступ на чтение этого регистра осуществляется по адресу `0x00`.

В регистр `valid` модуля `uart_rx_sb_ctrl` записывается единица по положительному фронту clk_i, когда выход `rx_valid_o` равен единице. Данный регистр сбрасывается в ноль при выполнении **запроса на чтение** по адресу `0x00`, а также при получении сигнала `interrupt_return_i`. Сам регистр доступен для чтения по адресу `0x04`. Регистр `valid` подключается к выходу `interrupt_request_o`. Что позволяет узнать о пришедших данных и посредством прерывания.

На вход `tx_data_i` модуля `uart_tx` подаются данные из регистра `data` модуля `uart_tx_sb_ctrl`. Доступ на запись в этот регистр происходит по адресу `0x00` в моменты положительного фронта `clk_i`, когда сигнал `busy_o` равен нулю. Доступ на чтение этого регистра может осуществляться в любой момент времени.

На вход `tx_valid_i` модуля `uart_tx` подается единица в момент выполнения **запроса на запись** по адресу `0x00` (при сигнале `busy` равном нулю). В остальное время на вход этого сигнала подается `0`.

В случае **запроса на запись** значения `1` по адресу `0x24` (адресу сброса), все регистры модуля-контроллера должны сброситься. При этом регистр `baudrate` должен принять значение `9600`, регистр `parity` должен принять значение `1`, регистр, `stopbit` должен принять значение `1`. Остальные регистры должны принять значение `0`.

Адресное пространство контроллера `uart_rx_sb_ctrl`:

|Адрес|Режим доступа|Допустимые значения|                 Функциональное назначение                                                               |
|-----|-------------|-------------------|---------------------------------------------------------------------------------------------------------|
|0x00 | R           | [0:255]           | Чтение из регистра `data`, хранящего значение принятых данных                                           |
|0x04 | R           | [0:1]             | Чтение из регистра `valid`, сообщающего о том, что есть непрочитанные данные в регистре `data`          |
|0x08 | R           | [0:1]             | Чтение из регистра `busy`, сообщающего о том, что модуль находится в процессе приема данных             |
|0x0C | RW          | [0:65535]         | Чтение/запись регистра `baudrate`, отвечающего за скорость передачи данных                              |
|0x10 | RW          | [0:1]             | Чтение/запись регистра `parity`, отвечающего за включение отключение проверки данных через бит четности |
|0x14 | RW          | [0:1]             | Чтение/запись регистра `stopbit`, отвечающего за длину стопового бита                                   |
|0x24 | W           |  1                | Запись сигнала сброса                                                                                   |

Адресное пространство контроллера `uart_tx_sb_ctrl`:

|Адрес|Режим доступа|Допустимые значения|                 Функциональное назначение                                                               |
|-----|-------------|-------------------|---------------------------------------------------------------------------------------------------------|
|0x00 | RW          | [0:255]           | Чтение и запись регистра `data`, хранящего значение отправляемых данных                                 |
|0x08 | R           | [0:1]             | Чтение из регистра `busy`, сообщающего о том, что модуль находится в процессе передачи данных           |
|0x0C | RW          | [0:65535]         | Чтение/запись регистра `baudrate`, отвечающего за скорость передачи данных                              |
|0x10 | RW          | [0:1]             | Чтение/запись регистра `parity`, отвечающего за включение отключение проверки данных через бит четности |
|0x14 | RW          | [0:1]             | Чтение/запись регистра `stopbit`, отвечающего за длину стопового бита                                   |
|0x24 | W           |  1                | Запись сигнала сброса                                                                                   |

### Видеоадаптер

Видеоадаптер позволяет выводить информацию на экран через интерфейс **VGA**. Предоставляемый в данной лабораторной работе vga-модуль способен выводить `80х30` символов (разрешение символа `8x16`). Таким образом, итоговое разрешение экрана, поддерживаемого vga-модулем будет `80*8 x 30*16 = 640x480`. VGA-модуль поддерживает управление цветовой схемой для каждого поля символа в сетке `80х30`. Это значит, что каждый символ (и фон символа) может быть отрисован отдельным цветом из диапазона 16-ти цветов.

![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)._

Для управления выводимым на экран содержимым, адресное пространство модуля разделено на следующие диапазоны:

![../../.pic/Labs/lab_13_periph/fig_04.png](../../.pic/Labs/lab_13_periph/fig_04.png)

_Рисунок 4. Карта памяти vga-модуля._

Для того, чтобы вывести символ на экран, необходимо использовать адрес этого символа на сетке `80x30` (диапазон адресов `char_map`). К примеру, мы хотим вывести символ в верхнем левом углу. Это нулевой символ в диапазоне адресов `char_map`. Поскольку данный диапазон начинается с адреса `0x0000_0000`, запись по этому адресу приведет к отображению символа, соответствующего [ASCII-коду](https://www.asciitable.com/), пришедшему на `write_data_i`.

Если мы хотим вывести нулевой (левый) символ в первой строке (счет ведется с нуля), то необходимо произвести запись по адресу `1*80 + 0 = 80 = 0x0000_0050`.

Вывод символа в правом нижнем углу осуществляется записью по адресу `0x0000_095F` (80*30-1)

Установка цветовой схемы осуществляется по тем же самым адресам, к которым прибавлено значение `0x0000_1000`:

* верхний левый символ — `0x0000_1000`
* нулевой символ первой строки — `0x0000_1050`
* нижний правый символ — `0x0000_195F`

Цветовая схема каждой позиции состоит из двух цветов: цвета фона и цвета символа. Оба эти цвета выбираются из палитры 8 цветов, каждый из которых содержит два оттенка: цвет на полной яркости и цвет на уменьшенной яркости (см. рис. 5). Один из цветов — черный, оба его оттенка представляют собой один и тот же цвет. Ниже приведены коды цветов их rgb-значения:

![../../.pic/Labs/lab_13_periph/fig_05.png](../../.pic/Labs/lab_13_periph/fig_05.png)

_Рисунок 5. Цветовая палитра vga-модуля._

Код цвета формируется следующим образом: старший бит определяет яркость оттенка цвета. Оставшиеся 3 бита кодируют используемый канал:

* 0 бит кодирует использование синего канала;
* 1 бит кодирует использование зеленого канала;
* 2 бит кодирует использование красного канала.

Таким образом, для установки цветовой схемы, необходимо выбрать два цвета из палитры, склеить их (в старших разрядах идет цвет символа, в младших — цвет фона) и записать получившееся 8-битное значение по адресу выбранной позиции в диапазоне адресов цветовой схемы (color_map).

К примеру, мы хотим установить черный фоновый цвет и белый цвет в качестве цвета символа для верхней левой позиции. В этом случае, мы должны записать значение `f0` (f(15) — код белого цвета, 0 — код черного цвета) по адресу `0x0000_1000` (нулевой адрес в диапазоне `color_map`).

Для отрисовки символов, мы условно поделили экран на сетку `80х30`, и для каждой позиции в этой сетке определили фоновый и активный цвет. Чтобы модуль мог отрисовать символ на очередной позиции (которая занимает `16х8` пикселей), ему необходимо знать в какой цвет необходимо окрасить каждый пиксель для каждого ascii-кода. Для этого используется память шрифтов.

Допустим, нам необходимо отрисовать символ `F` (ascii-код `0x46`).

![../../.pic/Labs/lab_13_periph/fig_06.png](../../.pic/Labs/lab_13_periph/fig_06.png)

_Рисунок 6. Отрисовка символа `F` в разрешении 16х8 пикселей._

Данный символ состоит из 16 строчек по 8 пикселей. Каждый пиксель кодируется одним битом (горит/не горит, цвет символа/фоновый цвет). Каждая строчка кодируется одним байтом (8 бит на 8 пикселей). Таким образом, каждый сканкод требует 16 байт памяти.

Данный модуль поддерживает 256 сканкодов. Следовательно, для хранения шрифта под каждый из 256 сканкодов требуется 16 * 256 = 4KiB памяти.

Для хранения шрифтов в модуле отведен диапазон адресов `0x00002000-0x00002FFF`. В отличие от предыдущих диапазонов адресов, где каждый адрес был закреплен за соответствующей позицией символа в сетке `80x30`, адреса данного диапазона распределены следующим образом:

* 0-ой байт — нулевая (верхняя) строчка символа с кодом 0;
* 1-ый байт — первая строчка символа с кодом 0;
* ...
* 15-ый байт — пятнадцатая (нижняя) строчка символа с кодом 0;
* 16-ый байт — нулевая (верхняя) строчка символа с кодом 1;
* ...
* 4095-ый байт — пятнадцатая (нижняя) строчка символа с кодом 255.

Прототип vga-модуля следующий:

```SystemVerilog
module vgachargen (
  input  logic          clk_i,            // системный синхроимпульс
  input  logic          clk100m_i,        // клок с частотой 100МГц
  input  logic          rst_i,            // сигнал сброса

  /*
      Интерфейс записи выводимого символа
  */
  input  logic [ 9:0]   char_map_addr_i,  // адрес позиции выводимого символа
  input  logic          char_map_we_i,    // сигнал разрешения записи кода
  input  logic [ 3:0]   char_map_be_i,    // сигнал выбора байтов для записи
  input  logic [31:0]   char_map_wdata_i, // ascii-код выводимого символа
  output logic [31:0]   char_map_rdata_o, // сигнал чтения кода символа

  /*
      Интерфейс установки цветовой схемы
  */
  input  logic [ 9:0]   col_map_addr_i,   // адрес позиции устанавливаемой схемы
  input  logic          col_map_we_i,     // сигнал разрешения записи схемы
  input  logic [ 3:0]   col_map_be_i,     // сигнал выбора байтов для записи
  input  logic [31:0]   col_map_wdata_i,  // код устанавливаемой цветовой схемы
  output logic [31:0]   col_map_rdata_o,  // сигнал чтения кода схемы

  /*
      Интерфейс установки шрифта
  */
  input  logic [ 9:0]   char_tiff_addr_i, // адрес позиции устанавливаемого шрифта
  input  logic          char_tiff_we_i,   // сигнал разрешения записи шрифта
  input  logic [ 3:0]   char_tiff_be_i,   // сигнал выбора байтов для записи
  input  logic [31:0]   char_tiff_wdata_i,// отображаемые пиксели в текущей позиции шрифта
  output logic [31:0]   char_tiff_rdata_o,// сигнал чтения пикселей шрифта


  output logic [3:0]    vga_r_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
);
```

Файлы модуля:

* peripheral modules/vhachargen.sv
* peripheral modules/vhachargen_pkg.sv
* firmware/mem_files/lab_12_ps2_vga_instr.mem — этим файлом необходимо проинициализировать память инструкций
* firmware/mem_files/lab_12_ps2ascii_data.mem — этим файлом необходимо проинициализировать память данных
* firmware/mem_files/lab_12_vga_ch_map.mem
* firmware/mem_files/lab_12_vga_ch_t_ro.mem
* firmware/mem_files/lab_12_vga_ch_t_rw.mem
* firmware/mem_files/lab_12_vga_col_map.mem

Вам необходимо добавить в проект все эти файлы.

Для управления данным модулем, необходимо написать модуль-контроллер со следующим прототипом:

```SystemVerilog
module vga_sb_ctrl (
  input  logic        clk_i,
  input  logic        rst_i,
  input  logic        clk100m_i,
  input  logic        req_i,
  input  logic        write_enable_i,
  input  logic [3:0]  mem_be_i,
  input  logic [31:0] addr_i,
  input  logic [31:0] write_data_i,
  output logic [31:0] read_data_o,

  output logic [3:0]  vga_r_o,
  output logic [3:0]  vga_g_o,
  output logic [3:0]  vga_b_o,
  output logic        vga_hs_o,
  output logic        vga_vs_o
);
```

Реализация данного модуля исключительно простая. В первую очередь необходимо подключить одноименные сигналы напрямую:

* `clk_i`,
* `rst_i`,
* `clk100m_i`,
* `vga_r_o`,
* `vga_g_o`,
* `vga_b_o`,
* `vga_hs_o`,
* `vga_vs_o`

Кроме того, необходимо:

1. подключить напрямую сигнал `write_data_i` ко входам:
   1. `char_map_wdata_i`,
   2. `col_map_wdata_i`,
   3. `char_tff_wdata_i`,
2. подключить биты `addr_i[11:2]` ко входам:
   1. `char_map_addr_i`,
   2. `col_map_addr_i`,
   3. `char_tiff_addr_i`,
3. сигнал `mem_be_i` подключить ко входам:
   1. `char_map_be_i`,
   2. `col_map_be_i`,
   3. `char_tiff_be_i`.

Остается только разобраться с сигналами `write_enable_i` и `read_data_o`.

Оба эти сигнала мультиплексируются / демультиплексируются с помощью одного и того же управляющего сигнала: `addr_i[13:12]` в соответствии с диапазонами адресов (рис. 4):

* `addr_i[13:12] == 2'b00` — сигнал `write_enable_i` поступает на вход `char_map_we_i`, выход `char_map_rdata_o` записывается в выходной регистр `read_data_o`;
* `addr_i[13:12] == 2'b01` — сигнал `write_enable_i` поступает на вход `col_map_we_i`, выход `col_map_rdata_o` записывается в выходной регистр `read_data_o`;
* `addr_i[13:12] == 2'b10` — сигнал `write_enable_i` поступает на вход `char_tiff_we_i`, выход `char_tiff_rdata_o` записывается в выходной регистр `read_data_o`.

## Список использованной литературы

1. С.А. Орлов, Б.Я. Цилькер / Организация ЭВМ и систем: Учебник для вузов. 2-е изд. / СПб.: Питер, 2011.
2. [Rebelstar](https://en.wikipedia.org/wiki/Rebelstar)
