diff --git a/.pic/Labs/lab_12_periph/fig_02.png b/.pic/Labs/lab_12_periph/fig_02.png index 2ed48165..9383fa64 100644 Binary files a/.pic/Labs/lab_12_periph/fig_02.png and b/.pic/Labs/lab_12_periph/fig_02.png differ diff --git a/.pic/Labs/lab_12_periph/fig_02.xlsx b/.pic/Labs/lab_12_periph/fig_02.xlsx index 406047ec..1dfb2cac 100644 Binary files a/.pic/Labs/lab_12_periph/fig_02.xlsx and b/.pic/Labs/lab_12_periph/fig_02.xlsx differ diff --git a/.pic/Labs/lab_12_periph/fig_03.png b/.pic/Labs/lab_12_periph/fig_03.png new file mode 100644 index 00000000..2ad1be46 Binary files /dev/null and b/.pic/Labs/lab_12_periph/fig_03.png differ diff --git a/.pic/Labs/lab_12_periph/fig_04.png b/.pic/Labs/lab_12_periph/fig_04.png new file mode 100644 index 00000000..7098a5d4 Binary files /dev/null and b/.pic/Labs/lab_12_periph/fig_04.png differ diff --git a/.pic/Labs/lab_12_periph/fig_05.png b/.pic/Labs/lab_12_periph/fig_05.png new file mode 100644 index 00000000..72401c95 Binary files /dev/null and b/.pic/Labs/lab_12_periph/fig_05.png differ diff --git a/.pic/Labs/lab_12_periph/fig_06.png b/.pic/Labs/lab_12_periph/fig_06.png new file mode 100644 index 00000000..6a53cfe9 Binary files /dev/null and b/.pic/Labs/lab_12_periph/fig_06.png differ diff --git a/Labs/12. Peripheral units/README.md b/Labs/12. Peripheral units/README.md index 15888346..023cd827 100644 --- a/Labs/12. Peripheral units/README.md +++ b/Labs/12. Peripheral units/README.md @@ -44,6 +44,7 @@ 4. Семисегментные индикаторы 5. UART-приемник 6. UART-передатчик +7. Видеоадаптер В таком случае, если мы захотим обратиться в четвертый регистр семисегментных индикаторов, мы должны будем использовать адрес `0x03000004`. Старшие 8 бит (`0x03`) определяют выбранное периферийное устройство, оставшиеся 24 бита определяют конкретный адрес в адресном пространстве этого устройства. @@ -81,19 +82,30 @@ module riscv_unit( // Входы и выходы периферии 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 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` (когда сигнал равен нулю — это сброс, когда единице — не сброс). @@ -119,6 +131,8 @@ sys_clk_rst_gen divider(.ex_clk_i(clk_i),.ex_areset_n_i(resetn_i),.div_i(10),.sy ![Карта памяти](../../.pic/Labs/lab_12_periph/fig_02.png) +_Рисунок 2. Карта памяти периферийных устройств_ + Работа с картой осуществляется следующим образом. Под названием каждого периферийного устройства указана старшая часть адреса (чему должны быть равны старшие 8 бит адреса, чтобы было сформировано обращение к данному периферийному устройству). Например, для переключателей это значение равно `0x01`, для светодиодов `0x02` и т.п. В самом левом столбце указаны используемые/неиспользуемые адреса в адресном пространстве данного периферийного устройства. Например для переключателей есть только один используемый адрес: `0x000000`. Его функциональное назначение и разрешения на доступ указаны в столбце соответствующего периферийного устройства. Возвращаясь к адресу `0x000000` для переключателей мы видим следующее: @@ -134,6 +148,8 @@ sys_clk_rst_gen divider(.ex_clk_i(clk_i),.ex_areset_n_i(resetn_i),.div_i(10),.sy Подробное описание периферийных устройств их управления и назначение управляющих регистров будет дано после порядка выполнения задания. +Обратите внимание на то, что у всех модулей периферийных устройств есть выходной сигнал `ready_o`, который должен быть всегда равен единице. + --- ## Порядок выполнения задания @@ -143,13 +159,12 @@ sys_clk_rst_gen divider(.ex_clk_i(clk_i),.ex_areset_n_i(resetn_i),.div_i(10),.sy 3. Реализуйте модули контроллеров периферии. Имена модулей и их порты будут указаны в [описании контроллеров](#описание-контроллеров-периферийных-устройств). Пример разработки контроллера приведен [здесь](../../Basic%20Verilog%20structures/Controllers.md). 4. Обновите модуль `riscv_unit` в соответствии с разделом ["Дополнительные правки модуля riscv_unit"](#дополнительные-правки-модуля-riscv_unit). 1. Подключите в проект файл `sys_clk_rst_gen.sv`. - 2. Добавьте в модуль `riscv_unit` входы и выходы периферии. Необходимо добавить порты даже тех периферийных устройств, которые вы не будете реализовывать. + 2. Добавьте в модуль `riscv_unit` входы и выходы периферии. **Необходимо добавить порты даже тех периферийных устройств, которые вы не будете реализовывать**. 3. Создайте в начале описания модуля `riscv_unit` экземпляр модуля `sys_clk_rst_gen`, скопировав приведенный фрагмент кода. 4. Замените подключение тактового сигнала исходных подмодулей `riscv_unit` на появившийся сигнал `sysclk`. Убедитесь, что на модули имеющие сигнал сброса приходит сигнал `rst`. 5. Интегрируйте модули контроллеров периферии в процессорную систему по приведенной схеме руководствуясь старшими адресами контроллеров, представленными на карте памяти ([_рис. 2_](../../.pic/Labs/lab_12_periph/fig_02.png)). Это означает, что если вы реализуете контроллер светодиодов, на его вход `req_i` должна подаваться единица в случае если `mem_req_o == 1` и старшие 8 бит адреса равны `0x02`. 1. При интеграции вы должны подключить только модули-контроллеры вашего варианта. Контроллеры периферии других вариантов подключать не надо. 2. При этом во время интеграции, вы должны использовать старшую часть адреса, представленную в карте памяти для формирования сигнала `req_i` для ваших модулей-контроллеров. - 3. Даже если вы не используете какие-то входные/выходные сигналы в модуле `riscv_unit` (например по варианту вам не достался контроллер клавиатуры и поэтому вы не используете сигналы `kclk_i` и `kdata_i`), вы все равно должны их описать во входах и выходах модуля `riscv_unit`. 6. Проверьте работу процессорной системы с помощью моделирования. 1. Для каждой пары контроллеров периферии предложено две программы: с обновлением данных по опросу и по прерываниям. Запустите моделирование сначала для одной программы, затем для другой (для этого необходимо обновить файл, инициализирующий память инструкций). После проверки работоспособности процессора, сравните поведение сигналов LSU для этих программ. 7. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](nexys_a7_100t.xdc)), если тот еще не был подключен, либо замените его содержимое данными из файла к этой лабораторной работе. @@ -342,6 +357,8 @@ endmodule В случае, если произошел запрос на чтение по адресу `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`. В случае запроса на запись по адресу `0x24` со значением `1`, необходимо осуществить сброс регистров `scan_code` и `scan_code_is_unread` в `0`. @@ -505,6 +522,7 @@ module uart_rx_sb_ctrl( */ output logic interrupt_request_o, + input logic interrupt_return_i, /* Часть интерфейса модуля, отвечающая за подключение передающему, @@ -535,6 +553,7 @@ module uart_tx_sb_ctrl( input logic [31:0] write_data_i, input logic write_enable_i, output logic [31:0] read_data_o, + output logic ready_o, /* Часть интерфейса модуля, отвечающая за подключение передающему, @@ -551,21 +570,21 @@ module uart_tx_sb_ctrl( endmodule ``` -У обоих предоставленных модулей схожий прототип, различия заключаются лишь в направлениях некоторых сигналов: +У обоих предоставленных модулей схожий прототип, различия заключаются лишь в направлениях сигналов `data` и `valid`. -Управление сигналами этого модуля достаточно просто. +Взаимодействие модулей `uart_rx` и `uart_tx` с соответствующими модулями-контроллерами осуществляется следующим образом. -Сигналы `clk_i` и `rx_i`/`tx_i` подключаются напрямую к соответствующим сигналам модуля-контроллера. +Сигналы `clk_i` и `rx_i`/`tx_i` подключаются напрямую к соответствующим сигналам модулей-контроллеров. Сигнал `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` равен нулю. Иными словами, изменение настроек передачи возможно только в моменты, когда передача не происходит. Доступ на чтение этих регистров может осуществляться в любой момент времени. +Значения входных сигналов `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`. Сам регистр доступен для чтения по адресу `0x04`. Регистр `valid` подключается к выходу `interrupt_request_o`. Что позволяет узнать о пришедших данных и посредством прерывания. +В регистр `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` равен нулю. Доступ на чтение этого регистра может осуществляться в любой момент времени. @@ -595,3 +614,162 @@ endmodule |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-ти цветов. + +![../../.pic/Labs/lab_12_periph/fig_03.png](../../.pic/Labs/lab_12_periph/fig_03.png) + +_Рисунок 3. Пример вывода на экран символьной информации_ + +Для управления выводимым на экран содержимым, адресное пространство модуля разделено на следующие диапазоны: + +![../../.pic/Labs/lab_12_periph/fig_04.png](../../.pic/Labs/lab_12_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` + +Цветовая схема каждой позиции состоит из двух цветов: цвета фона и цвета символа. Оба эти цвета выбираются из палитры на 16 цветов, сгруппированной по 8 парам. В каждой паре цвет на своей полной яркости, и половинной яркости. Один из цветов — черный, что на полной, что на половинной яркости он одинаковый. Ниже приведены коды цветов их rgb-значения: + +![../../.pic/Labs/lab_12_periph/fig_05.png](../../.pic/Labs/lab_12_periph/fig_05.png) + +_Рисунок 5. Цветовая палитра vga-модуля_ + +Таким образом, для установки цветовой схемы, необходимо выбрать два цвета из палитры, склеить их (в старших разрядах идет цвет символа, в младших — цвет фона) и записать получившееся 8-битное значение по адресу выбранной позиции в диапазоне адресов цветовой схемы (color_map). + +К примеру, мы хотим установить черный фоновый цвет и белый цвет в качестве цвета символа для верхней левой позиции. В этом случае, мы должны записать значение `f0` (f(15) — код белого цвета, 0 — код черного цвета) по адресу `0x0000_1000` (нулевой адрес в диапазоне `color_map`). + +Для отрисовки символов, мы условно поделили экран на сетку `80х30`, и для каждой позиции в этой сетке определили фоновый и активный цвет. Чтобы модуль мог отрисовать символ на очередной позиции (которая занимает `16х8` пикселей), ему необходимо знать в какой цвет необходимо окрасить каждый пиксель для каждого ascii-кода. Для этого используется память шрифтов. + +Допустим, нам необходимо отрисовать символ `F` (ascii-код `0x46`). + +![../../.pic/Labs/lab_12_periph/fig_06.png](../../.pic/Labs/lab_12_periph/fig_06.png) +_Рисунок 6. Отрисовка символа `F` в разрешении 16х8 пикселей._ + +Данный символ состоит из 16 строчек по 8 пикселей. Каждый пиксель кодируется одним битом (горит/не горит, цвет символа/фоновый цвет). Каждая строчка кодируется одним байтом (8 бит на 8 пикселей). Таким образом, каждый сканкод требует 16 байт памяти. + +Данный модуль поддерживает 256 сканкодов. Следовательно, для хранения шрифта под каждый из 256 сканкодов требуется 16 * 256 = 4KiB памяти. + +Для хранения шрифтов в модуле отведен диапазон адресов `0x00010000-0x00010FFF`. В отличие от предыдущих диапазонов адресов, где каждый адрес был закреплен за соответствующей позицией символа в сетке `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 +); +``` + +Для управления данным модулем, необходимо написать модуль-контроллер со следующим прототипом: + +```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 ready_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]` в соответствии с диапазонами адресов (рис. 3): + +* `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`. +