Предложения по лабораторной 14 (#54)

* Предложения по дополнению и улучшению лабораторной 15

* Apply suggestions from code review

Co-authored-by: Andrei Solodovnikov <VoultBoy@yandex.ru>

* Предложения по изменению вводной

* Предложения по лабораторной 14

* Еще немного изменений

* Apply suggestions from code review

Co-authored-by: Andrei Solodovnikov <VoultBoy@yandex.ru>

* Apply suggestions from code review

Co-authored-by: Andrei Solodovnikov <VoultBoy@yandex.ru>

---------

Co-authored-by: Mikhail Popov <mikhail.popov@bsc.es>
Co-authored-by: Andrei Solodovnikov <VoultBoy@yandex.ru>
This commit is contained in:
Mikhail Popov
2024-01-30 14:50:20 +02:00
committed by GitHub
parent 7208184af8
commit 5affbc08b0

View File

@@ -1,21 +1,23 @@
# Лабораторная работа 14 "Программатор"
Чтобы выпустить микроконтроллер в "дикую природу", то есть, чтобы его можно было использовать не в лабораторных условиях, а независимо от всего этого дополнительного оборудования, необходимо придусмотреть механизм замены исполняемой программы.
## Цель
Реализация программатора — части микроконтроллера, обеспечивающего получение программы из внешнего мира.
Реализация программатора — части микроконтроллера, обеспечивающего получение исполняемой программы из внешних, по отношению к системе, устройств.
## Ход работы
1. Познакомиться с информацией о программаторах и загрузчиках
2. Изучить информацию о конечных автоматах и способах их реализации
3. Описать перезаписываемую память инструкций
4. Описать и проверить программатор
5. Интегрировать программатор в процессорную систему и проверить её.
6. Проверить работу системы в ПЛИС с помощью предоставленного скрипта по прошивки системы.
1. Познакомиться с информацией о программаторах и загрузчиках ([#теория](#теория))
2. Изучить информацию о конечных автоматах и способах их реализации ([#практика](#практика))
3. Описать перезаписываемую память инструкций ([#память инструкций](#перезаписываемая-память-инструкций))
4. Описать и проверить модуль программатора ([#программатор](#программатор))
5. Интегрировать программатор в процессорную систему и проверить её ([#интеграция](#интеграция-программатора-в-riscv_unit))
6. Проверить работу системы в ПЛИС с помощью предоставленного скрипта по прошивкe системы ([#проверка](#пример-загрузки-прогаммы))
## Теория
До этого момента, исполняемая процессором программа попадала в память инструкций через магический вызов `$readmemh`. Однако реальные микроконтроллеры не обладают такими возможностями. Программа из внешнего мира попадает в них посредством так называемого **программатора** — устройства, обеспечивающего запись программы в память микроконтроллера. Программатор записывает данные в постоянное запоминающее устройство (ПЗУ). Для того, чтобы программа попала из ПЗУ в память инструкций, после запуска контроллера сперва начинает исполняться **загрузчик** (**bootloader**) — небольшая программа, вшитая в память микроконтроллера на этапе изготовления, которая отвечает за первичную инициализацию и подготовку микроконтроллера к выполнению основной программы (включая её перенос из ПЗУ в память инструкций).
До этого момента исполняемая процессором программа попадала в память инструкций через магический вызов `$readmemh`. Однако, реальные микроконтроллеры не обладают такими возможностями. Программа из внешнего мира попадает в них посредством так называемого **программатора** — устройства, обеспечивающего запись программы в память микроконтроллера. Программатор записывает данные в постоянное запоминающее устройство (ПЗУ). Для того, чтобы программа попала из ПЗУ в память инструкцийОЗУ), после запуска контроллера сперва начинает исполняться **загрузчик** (**bootloader**) — небольшая программа, вшитая в память микроконтроллера на этапе изготовления. Загрузчик отвечает за первичную инициализацию различных регистров и подготовку микроконтроллера к выполнению основной программы, включая её перенос из ПЗУ в память инструкций.
Со временем появилось несколько уровней загрузчиков: сперва запускается **первичный загрузчик** (**first stage bootloader**, **fsbl**), после которого запускается **вторичный загрузчик** (часто в роли вторичного загрузчика исполняется программа под названием **u-boot**). Такая иерархия загрузчиков может потребоваться, например, в случае загрузки операционной системы (которая хранится в файловой системе). Код для работы с файловой системой может попросту не уместиться в первичный загрузчик. В этом случае, целью первичного загрузчика является лишь загрузить вторичный загрузчик, который в свою очередь уже будет способен взаимодействовать с файловой системой и загрузить операционную систему[[1]](https://stackoverflow.com/q/22455153).
@@ -35,14 +37,14 @@
Обычно, конечные автоматы описываются в виде направленного графа переходов между состояниями, где вершины графа — это состояния конечного автомата, а дуги — условия перехода из одного состояния в другое.
Простейшим примером конечного автомата может быть турникет. Когда приёмник турникета опускается подходящий жетон, тот разблокирует вращающуюся треногу. После попытки поворота треноги, та блокируется до следующего жетона.
Простейшим примером конечного автомата может быть турникет. Когда в приёмник турникета опускается подходящий жетон, тот разблокирует вращающуюся треногу. После попытки поворота треноги, та блокируется до следующего жетона.
Иными словами, у турникета есть:
- два состояния
- заблокирован (`locked`)
- разблокирован(`unlocked`)
- два входа
- два входа (события)
- жетон принят (`coin`)
- попытка поворота треноги (`push`)
- один выход
@@ -68,7 +70,7 @@ _Рисунок 1. Граф переходов конечного автомат
Для реализации регистра состояния конечного автомата будет удобно воспользоваться специальным типом языка **SystemVerilog**, который называется `enum` (**перечисление**).
Перечисления позволяют объявить объединенный набор именованных констант. В дальнейшем, объявленные имена можно использовать вместо перечисленных значений, им соответствующих. Если не указано иного, первому имени присваивается значение `0`, каждое последующее увеличивается на `1` относительно предыдущего значения.
Перечисления позволяют объявить объединенный набор именованных констант. В дальнейшем, объявленные имена можно использовать вместо перечисленных значений, им соответствующих, что повышает читабельность кода. Если не указано иного, первому имени присваивается значение `0`, каждое последующее увеличивается на `1` относительно предыдущего значения.
```SystemVerilog
module turnstile_fsm(
@@ -107,7 +109,7 @@ module turnstile_fsm(
_Рисунок 2. Вывод значений объекта `enum` на временную диаграмму._
Для описания регистра состояния часто бывает использовать отдельный комбинационный сигнал, который подается непосредственно на его вход (часто именуемый как `next_state`). Приведенный выше автомат турникета слишком простой, чтобы показать преимущества такого подхода. Предположим, что в момент перехода из состояния `locked` в состояние `unlocked` мы хотим, чтобы загоралась и сразу гасла зеленая лампочка. Без сигнала `next_state` подобный модуль можно было бы описать как:
Для описания регистра состояния часто используют отдельный комбинационный сигнал, который подается непосредственно на его вход (часто именуемый как `next_state`). Приведенный выше автомат турникета слишком простой, чтобы показать преимущества такого подхода. Предположим, что в момент перехода из состояния `locked` в состояние `unlocked` мы хотим, чтобы загоралась и сразу гасла зеленая лампочка. Без сигнала `next_state` подобный модуль можно было бы описать как:
```SystemVerilog
module turnstile_fsm(
@@ -198,7 +200,7 @@ module turnstile_fsm(
### Перезаписываемая память инструкций
Поскольку ранее из памяти инструкций можно было только считывать данные, но не записывать их в нее, программатор не сможет записать принятую из внешнего мира программа. Поэтому необходимо добавить в память инструкций порт на запись. Для того, чтобы различать реализации памяти инструкций, данный модуль будет назвать `rw_instr_mem` со следующим прототипом:
Поскольку ранее из памяти инструкций можно было только считывать данные, но не записывать их в нее, программатор не сможет записать принятую из внешнего мира программу. Поэтому необходимо добавить в память инструкций порт на запись. Для того, чтобы различать реализации памяти инструкций, данный модуль будет назвать `rw_instr_mem` со следующим прототипом:
```SystemVerilog
module rw_instr_mem(
@@ -216,7 +218,7 @@ module rw_instr_mem(
### Программатор
Необходимо реализовать модуль-программатор, использующий `uart` в качестве интерфейса для обмена данными с внешним миром, интерфейсами для записи в память инструкций и память данных.
Необходимо реализовать модуль программатора, использующий с одной "стороны" `uart` в качестве интерфейса для обмена данными с внешним миром, а с другой — интерфейсы для записи полученных данных в память инструкций и память данных.
#### Описание модуля
@@ -284,15 +286,6 @@ localparam INIT_MSG_SIZE = 40;
localparam FLASH_MSG_SIZE = 57;
localparam ACK_MSG_SIZE = 4;
logic [INIT_MSG_SIZE-1:0][7:0] init_msg;
// ascii-код строки "ready for flash staring from 0xflash_addr\n"
assign init_msg = { 8'h72, 8'h65, 8'h61, 8'h64, 8'h79, 8'h20, 8'h66, 8'h6F,
8'h72, 8'h20, 8'h66, 8'h6C, 8'h61, 8'h73, 8'h68, 8'h20,
8'h73, 8'h74, 8'h61, 8'h72, 8'h69, 8'h6E, 8'h67, 8'h20,
8'h66, 8'h72, 8'h6F, 8'h6D, 8'h20, 8'h30, 8'h78,
flash_addr_ascii, 8'h0a};
logic [7:0] [7:0] flash_size_ascii, flash_addr_ascii;
genvar i;
generate
@@ -303,8 +296,8 @@ generate
// его ascii-кодом, необходимо прибавить к нему число 8'h30
// (ascii-код символа '0').
// Если ниббл больше либо равен 10, он описывается буквами a-f. Для его
// представления в виде ascii-кода, необходимо прибавить число 8'h41
// (ascii-код символа 'a' - 10).
// представления в виде ascii-кода, необходимо прибавить число 8'h57
// (ascii-код символа 'a' - 8'h61).
assign flash_size_ascii[i*2] = flash_size[i][3:0] < 4'ha ? flash_size[i][3:0] + 8'h30 :
flash_size[i][3:0] + 8'h57;
assign flash_size_ascii[i*2+1] = flash_size[i][7:4] < 4'ha ? flash_size[i][7:4] + 8'h30 :
@@ -317,6 +310,14 @@ generate
end
endgenerate
logic [INIT_MSG_SIZE-1:0][7:0] init_msg;
// ascii-код строки "ready for flash staring from 0xflash_addr\n"
assign init_msg = { 8'h72, 8'h65, 8'h61, 8'h64, 8'h79, 8'h20, 8'h66, 8'h6F,
8'h72, 8'h20, 8'h66, 8'h6C, 8'h61, 8'h73, 8'h68, 8'h20,
8'h73, 8'h74, 8'h61, 8'h72, 8'h69, 8'h6E, 8'h67, 8'h20,
8'h66, 8'h72, 8'h6F, 8'h6D, 8'h20, 8'h30, 8'h78,
flash_addr_ascii, 8'h0a};
logic [FLASH_MSG_SIZE-1:0][7:0] flash_msg;
//ascii-код строки: "finished write 0xflash_size bytes starting from 0xflash_addr\n"
assign flash_msg = {8'h66, 8'h69, 8'h6E, 8'h69, 8'h73, 8'h68, 8'h65, 8'h64,
@@ -373,14 +374,14 @@ endmodule
- `flash_size_ascii`,
- `flash_addr_ascii`.
#### Реализация модуля
#### Реализация модуля программатора
Для реализации данного модуля, необходимо реализовать все объявленные выше сигналы, кроме сигналов:
- `rx_busy`, `rx_valid`, `rx_data`, `tx_busy` (т.к. те уже подключены к выходам модулей `uart_rx`/`uart_tx`),
- `flash_size_ascii`, `flash_addr_ascii`, `init_msg`, `flash_msg` (т.е. они уже реализованы в представленной выше логике).
- `rx_busy`, `rx_valid`, `rx_data`, `tx_busy` (т.к. те уже подключены к выходам модулей `uart_rx` и `uart_tx`),
- `flash_size_ascii`, `flash_addr_ascii`, `init_msg`, `flash_msg` (т.к. они уже реализованы в представленной выше логике).
Так же необходимо реализовать выходы модуля:
Так же необходимо реализовать выходы модуля программатора:
- `instr_addr_o`;
- `instr_wdata_o`;
@@ -414,15 +415,15 @@ endmodule
Во всех остальных ситуациях счетчик должен сохранять свое значение.
##### Реализация сигналов uart_tx
##### Реализация сигналов, подключаемых к uart_tx
Сигнал `tx_valid` должен быть равен единице только когда `tx_busy` равен нулю, и конечный автомат находится в одном из следующих состояний:
Сигнал `tx_valid` должен быть равен единице только когда `tx_busy` равен нулю, а конечный автомат находится в одном из следующих состояний:
- `INIT_MSG`,
- `SIZE_ACK`,
- `FLASH_ACK`
Иными словами, `tx_valid` равен единице, когда автомат находится в состоянии, отвечающем за передачу сообщений от программатора, и в данный момент программатор не отправляет очередной байт сообщения.
Иными словами, `tx_valid` равен единице, когда автомат находится в состоянии, отвечающем за передачу сообщений от программатора, но в данный момент программатор не отправляет очередной байт сообщения.
Сигнал `tx_data` должен нести очередной байт одного из передаваемых сообщений:
@@ -468,6 +469,8 @@ endmodule
- `data_addr_o` становится равен `flash_addr + flash_counter - 1`
- во всех остальных ситуациях `data_wdata_o` и `data_addr_o` сохраняют свое значение, а `data_write_enable_o` сбрасывается в ноль.
> Так как вышесказанное по сути является полным описанием работы программатора на русском языке, то фактически **задача сводится к переводу** текста описания программатора выше **с руского на verilog**
### Интеграция программатора в riscv_unit
![../../.pic/Labs/lab_14_programming_device/fig_04.drawio.png](../../.pic/Labs/lab_14_programming_device/fig_04.drawio.png)
@@ -476,14 +479,23 @@ endmodule
В случае, если использовалось периферийное устройство `uart_tx`, необходимо мультиплексировать его выход `tx_o` с одноименным выходом программатора аналогично тому, как это было сделано с сигналами интерфейса памяти данных.
### Пример загрузки прогаммы
Чтобы проверить работу программатора на практике необходимо подготовить скомпилированную программу подобно тому, как это делалось в лабораторной работе 13. Подключить интерфейс последовательного порта к компьютеру также, как это делалось в лабораторной работе 12, после чего необходимо запустить скрипт:
```bash
# Пример использования скрипта. Указывается программа для записи и последовательный порт, к которому подключается программатор
python3 flash.py ./path/to/program COM3
```
## Порядок выполнения задания
1. Напишите модуль перезаписываемой памяти инструкций. Данный модуль будет аналогичен памяти данных, только в нем не будет сигнала `mem_req_i`.
2. Создайте модуль bluster, используя предоставленный код.
2. Создайте модуль `bluster`, используя предоставленный код.
1. Опишите конечный автомат используя сигналы `state`, `next_state`, `send_fin`, `size_fin`, `flash_fin`, `next_round`.
2. [Реализуйте](#описание-модуля) логику сигналов `send_fin`, `size_fin`, `flash_fin`, `next_round`.
3. [Реализуйте](#реализация-конечного-автомата) логику счетчиков `size_counter`, `flash_counter`, `msg_counter`.
4. [Реализуйте](#реализация-сигналов-uart_tx) логику сигналов `tx_valid`, `tx_data`.
4. [Реализуйте](#реализация-сигналов-подключаемых-к-uart_tx) логику сигналов `tx_valid`, `tx_data`.
5. [Реализуйте](#реализация-оставшейся-части-логики) логику оставшихся сигналов.
3. После описания модуля, его необходимо проверить с помощью тестового окружения.
1. Тестовое окружение находится [здесь](tb_bluster.sv).
@@ -495,7 +507,7 @@ endmodule
2. Добавьте модуль программатор.
3. Подключите программатор к процессорной системе.
1. Интерфейс памяти инструкций подключается к порту записи обновленной памяти инструкций.
2. Интерфейс памяти данных мультиплексируется с интерфейсом памяти данных модуля LSU
2. Интерфейс памяти данных мультиплексируется с интерфейсом памяти данных модуля `LSU`.
3. Замените сигнал сброса модуля `riscv_core` сигналом `core_reset_o`.
4. В случае если у вас есть периферийное устройство `uart_tx` его выход `tx_o` необходимо мультиплексировать с выходом `tx_o` программатора аналогично тому, как был мультиплексирован интерфейс памяти данных.
5. После интеграции модуля, его необходимо проверить с помощью тестового окружения.