mirror of
https://github.com/MPSU/APS.git
synced 2025-09-15 17:20:10 +00:00
Предложения по лабораторной 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:
@@ -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
|
||||
|
||||

|
||||
@@ -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. После интеграции модуля, его необходимо проверить с помощью тестового окружения.
|
||||
|
Reference in New Issue
Block a user