mirror of
https://github.com/MPSU/APS.git
synced 2026-06-13 12:33:33 +00:00
English version draft
Assisted-by: Claude:claude-4.6-sonnet
This commit is contained in:
@@ -1,76 +1,76 @@
|
||||
# Лабораторная работа №15 "Программатор"
|
||||
# Lab 15. Programmer
|
||||
|
||||
Чтобы выпустить микроконтроллер в "дикую природу", то есть, чтобы его можно было использовать не в лабораторных условиях, а независимо от всего этого дополнительного оборудования, необходимо предусмотреть механизм замены исполняемой программы.
|
||||
To release a microcontroller "into the wild" — that is, to use it independently of all this additional equipment and outside laboratory conditions — a mechanism for replacing the executable program must be provided.
|
||||
|
||||
## Цель
|
||||
## Objective
|
||||
|
||||
Реализация программатора — части микроконтроллера, обеспечивающего получение исполняемой программы из внешних, по отношению к системе, устройств.
|
||||
Implementation of a programmer — a part of the microcontroller that enables receiving an executable program from devices external to the system.
|
||||
|
||||
## Ход работы
|
||||
## Workflow
|
||||
|
||||
1. Познакомиться с информацией о программаторах и загрузчиках ([#теория](#Теория))
|
||||
2. Изучить информацию о конечных автоматах и способах их реализации ([#практика](#Практика))
|
||||
3. Описать перезаписываемую память инструкций ([#память инструкций](#Перезаписываемая-память-инструкций))
|
||||
4. Описать и проверить модуль программатора ([#программатор](#Программатор))
|
||||
5. Интегрировать программатор в процессорную систему и проверить её ([#интеграция](#Интеграция-программатора-в-processor_system))
|
||||
6. Проверить работу системы в ПЛИС с помощью предоставленного скрипта, инициализирующего память системы ([#проверка](#Пример-загрузки-программы))
|
||||
1. Learn about programmers and bootloaders ([#theory](#Theory))
|
||||
2. Study finite-state machines and their implementation approaches ([#practice](#Practice))
|
||||
3. Describe the rewritable instruction memory ([#instruction memory](#Rewritable-Instruction-Memory))
|
||||
4. Describe and verify the programmer module ([#programmer](#Programmer))
|
||||
5. Integrate the programmer into the processor system and verify it ([#integration](#Integrating-the-Programmer-into-processor_system))
|
||||
6. Verify the system operation on an FPGA using the provided memory initialization script ([#verification](#Program-Loading-Example))
|
||||
|
||||
## Теория
|
||||
## Theory
|
||||
|
||||
До этого момента исполняемая процессором программа попадала в память инструкций через магический вызов `$readmemh`. Однако, реальные микроконтроллеры не обладают такими возможностями. Программа из внешнего мира попадает в них посредством так называемого **программатора** — устройства, обеспечивающего запись программы в память микроконтроллера. Программатор записывает данные в постоянное запоминающее устройство (ПЗУ). Для того, чтобы программа попала из ПЗУ в память инструкций (в ОЗУ), после запуска микроконтроллера сперва начинает исполняться **загрузчик** (**bootloader**) — небольшая программа, "вшитая" в память микроконтроллера на этапе изготовления. Загрузчик отвечает за первичную инициализацию различных регистров и подготовку микроконтроллера к выполнению основной программы, включая её перенос из ПЗУ в память инструкций.
|
||||
Until now, the program executed by the processor was loaded into instruction memory via the `$readmemh` magic call. However, real microcontrollers do not have such capabilities. A program from the outside world reaches them through a so-called **programmer** — a device that writes the program into the microcontroller's memory. The programmer writes data to non-volatile memory (ROM). For the program to transfer from ROM to instruction memory (RAM), after the microcontroller starts, a **bootloader** executes first — a small program embedded in the microcontroller's memory at the time of manufacture. The bootloader is responsible for initial initialization of various registers and preparing the microcontroller to execute the main program, including transferring it from ROM to instruction memory.
|
||||
|
||||
Со временем появилось несколько уровней загрузчиков: сперва запускается **первичный загрузчик** (**first stage bootloader**, **fsbl**), после которого запускается **вторичный загрузчик** (часто в роли вторичного загрузчика исполняется программа под названием **u-boot**). Такая иерархия загрузчиков может потребоваться, например, в случае загрузки операционной системы (которая хранится в файловой системе). Код для работы с файловой системой может попросту не уместиться в первичный загрузчик. В этом случае, целью первичного загрузчика является лишь загрузить вторичный загрузчик, который в свою очередь уже будет способен взаимодействовать с файловой системой и загрузить операционную систему.
|
||||
Over time, several bootloader stages emerged: the **first stage bootloader** (**fsbl**) runs first, followed by a **second stage bootloader** (the program commonly known as **u-boot** often serves this role). Such a bootloader hierarchy may be required, for example, when loading an operating system (which is stored in a file system). The code for working with the file system may simply not fit in the first stage bootloader. In that case, the sole purpose of the first stage bootloader is to load the second stage bootloader, which in turn is capable of interacting with the file system and loading the operating system.
|
||||
|
||||
Кроме того, код вторичного загрузчика может быть изменен, поскольку программируется вместе с основной программой. Первичный же загрузчик не всегда может быть изменен.
|
||||
In addition, the second stage bootloader code can be modified, since it is programmed together with the main program. The first stage bootloader, however, cannot always be modified.
|
||||
|
||||
В рамках данной лабораторной работы мы немного упростим процесс передачи программы: вместо записи в ПЗУ, программатор будет записывать её сразу в память инструкций, минуя загрузчик.
|
||||
In this lab we will simplify the program transfer process slightly: instead of writing to ROM, the programmer will write directly to instruction memory, bypassing the bootloader.
|
||||
|
||||
## Практика
|
||||
## Practice
|
||||
|
||||
### Конечные автоматы (Finite-State Machines, FSM)
|
||||
### Finite-State Machines (FSM)
|
||||
|
||||
Программатор будет представлен в виде модуля с [конечным автоматом](https://en.wikipedia.org/wiki/Finite-state_machine). Конечный автомат представляет собой устройство, состоящее из:
|
||||
The programmer will be implemented as a module with a [finite-state machine](https://en.wikipedia.org/wiki/Finite-state_machine). A finite-state machine consists of:
|
||||
|
||||
- элемента памяти (так называемого **регистра состояния**);
|
||||
- логики, обеспечивающей изменение значения **регистра состояния** (логики перехода между состояниями) в зависимости от его текущего состояния и входных сигналов;
|
||||
- логики, отвечающей за выходы конечного автомата.
|
||||
- a memory element (the so-called **state register**);
|
||||
- logic that drives changes to the **state register** value (state transition logic) depending on the current state and input signals;
|
||||
- logic responsible for the FSM outputs.
|
||||
|
||||
Обычно, конечные автоматы описываются в виде направленного графа переходов между состояниями, где вершины графа — это состояния конечного автомата, а рёбра (дуги) — условия перехода из одного состояния в другое.
|
||||
Finite-state machines are typically described as directed state transition graphs, where graph vertices are the FSM states and edges (arcs) are the conditions for transitioning from one state to another.
|
||||
|
||||
Простейшим примером конечного автомата может быть турникет. Когда в приёмник турникета опускается подходящий жетон, тот разблокирует вращающуюся треногу. После попытки поворота треноги, та блокируется до следующего жетона.
|
||||
A turnstile is the simplest example of a finite-state machine. When a valid token is inserted into the turnstile's slot, it unlocks the rotating arm. After an attempt to push through the arm, it locks again until the next token.
|
||||
|
||||
Иными словами, у турникета есть:
|
||||
In other words, the turnstile has:
|
||||
|
||||
- два состояния
|
||||
- заблокирован (`locked`)
|
||||
- разблокирован(`unlocked`)
|
||||
- два входа (события)
|
||||
- жетон принят (`coin`)
|
||||
- попытка поворота треноги (`push`)
|
||||
- один выход
|
||||
- блокировка треноги
|
||||
- two states
|
||||
- locked (`locked`)
|
||||
- unlocked (`unlocked`)
|
||||
- two inputs (events)
|
||||
- token accepted (`coin`)
|
||||
- attempt to push the arm (`push`)
|
||||
- one output
|
||||
- arm lock
|
||||
|
||||
Для описания двух состояний нам будет достаточно однобитного регистра. Для взаимодействия с регистром, нам потребуются также сигнал синхронизации и сброса.
|
||||
A single-bit register is sufficient to represent the two states. A clock signal and a reset signal are also required to interact with the register.
|
||||
|
||||
Опишем данный автомат в виде графа переходов:
|
||||
Let us describe this FSM as a state transition graph:
|
||||
|
||||

|
||||
|
||||
_Рисунок 1. Граф переходов конечного автомата для турникета[[1]](https://en.wikipedia.org/wiki/Finite-state_machine)._
|
||||
_Figure 1. State transition graph of the turnstile FSM [[1]](https://en.wikipedia.org/wiki/Finite-state_machine)._
|
||||
|
||||
Черной точкой со стрелкой в вершину `Locked` обозначен сигнал сброса. Иными словами, при сбросе турникет всегда переходит в заблокированное состояние.
|
||||
The filled circle with an arrow pointing to the `Locked` vertex represents the reset signal. In other words, on reset the turnstile always transitions to the locked state.
|
||||
|
||||
Как мы видим, повторное опускание жетона в разблокированном состоянии приводит к сохранению этого состояния (но турникет не запоминает, что было опущено 2 жетона, и после первого же прохода станет заблокирован). В случае попытки поворота треноги в заблокированном состоянии, автомат так и останется в заблокированном состоянии.
|
||||
As we can see, inserting another token while in the unlocked state keeps the state unchanged (but the turnstile does not remember that two tokens were inserted, and will lock again after the first pass). Attempting to push through the arm while in the locked state keeps the FSM in the locked state.
|
||||
|
||||
Так же необходимо оговорить приоритет переходов: в первую очередь проверяется попытка поворота треноги, в случае если такой попытки не было, проверяется опускание монетки. Такой приоритет можно было бы указать и на графе, показав на рёбрах что переход в состояние unlocked возможен только при отсутствии сигнала `push`.
|
||||
It is also necessary to define transition priority: first the arm push attempt is checked, and only if no such attempt occurred is the token insertion checked. This priority could also be indicated on the graph by annotating the edges to show that the transition to the unlocked state is only possible when the `push` signal is absent.
|
||||
|
||||
### Реализация конечных автоматов в SystemVerilog
|
||||
### Implementing Finite-State Machines in SystemVerilog
|
||||
|
||||
Глядя на описание составляющих конечного автомата, вы могли задаться вопросом: чем автомат отличается от последовательностной логики, ведь она состоит из тех же компонент. Ответом будет: ничем. Конечные автоматы являются математической абстракцией над функцией последовательностной логики[[2]](https://www.allaboutcircuits.com/textbook/digital/chpt-11/finite-state-machines/). Иными словами — конечный автомат, это просто другой способ представления последовательностной логики, а значит вы уже умеете их реализовывать.
|
||||
Looking at the components of a finite-state machine, you may have wondered: how does an FSM differ from sequential logic, since they are composed of the same elements? The answer is: not at all. Finite-state machines are a mathematical abstraction over the sequential logic function [[2]](https://www.allaboutcircuits.com/textbook/digital/chpt-11/finite-state-machines/). In other words, a finite-state machine is simply a different way of representing sequential logic — and you already know how to implement sequential logic.
|
||||
|
||||
Для реализации регистра состояния конечного автомата будет удобно воспользоваться специальным типом языка **SystemVerilog**, который называется `enum` (**перечисление**).
|
||||
To implement the state register of an FSM, it is convenient to use a special **SystemVerilog** type called `enum` (**enumeration**).
|
||||
|
||||
Перечисления позволяют объявить объединенный набор именованных констант. В дальнейшем, объявленные имена можно использовать вместо перечисленных значений, им соответствующих, что повышает читаемость кода. Если не указано иного, первому имени присваивается значение `0`, каждое последующее увеличивается на `1` относительно предыдущего значения.
|
||||
Enumerations allow declaring a unified set of named constants. The declared names can then be used in place of the enumerated values they correspond to, which improves code readability. Unless specified otherwise, the first name is assigned the value `0`, and each subsequent name increments by `1` relative to the previous value.
|
||||
|
||||
```Verilog
|
||||
module turnstile_fsm(
|
||||
@@ -104,15 +104,15 @@ module turnstile_fsm(
|
||||
endmodule
|
||||
```
|
||||
|
||||
_Листинг 1. Пример реализации конечного автомата для турникета._
|
||||
_Listing 1. Example implementation of a turnstile FSM._
|
||||
|
||||
Кроме того, при должной поддержке со стороны инструментов моделирования, значения объектов перечислений могут выводиться на временную диаграмму в виде перечисленных имен:
|
||||
Furthermore, with appropriate tool support, enum object values can be displayed on a waveform using their named labels:
|
||||
|
||||

|
||||
|
||||
_Рисунок 2. Вывод значений объекта `enum` на временную диаграмму._
|
||||
_Figure 2. Displaying `enum` object values on a waveform._
|
||||
|
||||
Для описания регистра состояния часто используют отдельный комбинационный сигнал, который подаётся непосредственно на его вход (часто именуемый как `next_state`). Приведённый выше автомат турникета слишком простой, чтобы показать преимущества такого подхода. Предположим, что в момент перехода из состояния `locked` в состояние `unlocked` мы хотим, чтобы загоралась и сразу гасла зелёная лампочка. Без сигнала `next_state` подобный модуль можно было бы описать как:
|
||||
A separate combinational signal fed directly to the state register input (commonly called `next_state`) is often used when describing the state register. The turnstile FSM above is too simple to demonstrate the advantages of this approach. Suppose that on the transition from the `locked` state to the `unlocked` state we want a green light to flash on and immediately off. Without the `next_state` signal, such a module could be described as:
|
||||
|
||||
```Verilog
|
||||
module turnstile_fsm(
|
||||
@@ -128,7 +128,7 @@ module turnstile_fsm(
|
||||
|
||||
assign is_locked = state == LOCKED;
|
||||
|
||||
// (!push) && coin — условие перехода в состояние UNLOCKED
|
||||
// (!push) && coin — condition for transitioning to UNLOCKED state
|
||||
assign green_light = (state == LOCKED) && (!push) && coin;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
@@ -150,9 +150,9 @@ module turnstile_fsm(
|
||||
endmodule
|
||||
```
|
||||
|
||||
_Листинг 2. Пример реализации конечного автомата для усложнённого турникета._
|
||||
_Listing 2. Example implementation of an extended turnstile FSM._
|
||||
|
||||
Используя сигнал `next_state`, автомат мог бы быть переписан следующим образом:
|
||||
Using the `next_state` signal, the FSM could be rewritten as follows:
|
||||
|
||||
```Verilog
|
||||
module turnstile_fsm(
|
||||
@@ -193,23 +193,23 @@ module turnstile_fsm(
|
||||
endmodule
|
||||
```
|
||||
|
||||
_Листинг 3. Пример реализации конечного автомата для усложнённого турникета с использованием сигнала next\_state._
|
||||
_Listing 3. Example implementation of an extended turnstile FSM using the next\_state signal._
|
||||
|
||||
На первый взгляд может показаться, что так даже сложнее. Во-первых, появился дополнительный сигнал. Во-вторых, появился ещё один `always`-блок. Однако представьте на секунду, что условиями перехода будет что-то сложнее, чем 1-битный входной сигнал. И что от этих условий будет зависеть не один выходной сигнал, а множество как выходных сигналов, так и внутренних элементов памяти помимо регистра состояний. В этом случае, сигнал `next_state` позволит избежать дублирования множества условий.
|
||||
At first glance this may seem more complex. First, an additional signal appeared. Second, an extra `always` block was added. However, imagine for a moment that the transition conditions are something more complex than a 1-bit input signal. And that those conditions govern not just one output signal but many output signals as well as internal memory elements beyond the state register. In that case, the `next_state` signal avoids duplicating many conditions.
|
||||
|
||||
Важно отметить, что объектам типа `enum` можно присваивать только перечисленные константы и объекты того же типа. Иными словами, `state` можно присваивать значения `LOCKED`/`UNLOCKED` и `next_state`, но нельзя, к примеру, присвоить `1'b0`.
|
||||
It is important to note that `enum` objects can only be assigned enumerated constants or objects of the same type. In other words, `state` can be assigned `LOCKED`/`UNLOCKED` or `next_state`, but cannot be assigned, for example, `1'b0`.
|
||||
|
||||
## Задание
|
||||
## Assignment
|
||||
|
||||
Для выполнения данной лабораторной работы необходимо:
|
||||
To complete this lab, you must:
|
||||
|
||||
- описать перезаписываемую память инструкций;
|
||||
- описать модуль-программатор;
|
||||
- заменить в `processor_system` память инструкций на новую, и интегрировать программатор.
|
||||
- describe the rewritable instruction memory;
|
||||
- describe the programmer module;
|
||||
- replace the instruction memory in `processor_system` with the new one and integrate the programmer.
|
||||
|
||||
### Перезаписываемая память инструкций
|
||||
### Rewritable Instruction Memory
|
||||
|
||||
Поскольку ранее из памяти инструкций можно было только считывать данные, но не записывать их в неё, программатор не сможет записать принятую из внешнего мира программу. Поэтому необходимо добавить в память инструкций порт на запись. Для того, чтобы различать реализации памяти инструкций, данный модуль будет называться `rw_instr_mem`:
|
||||
Since the instruction memory previously only supported read operations and not write operations, the programmer will be unable to write the program received from the outside world into it. Therefore, a write port must be added to the instruction memory. To distinguish this implementation of the instruction memory from the previous one, this module will be named `rw_instr_mem`:
|
||||
|
||||
```Verilog
|
||||
module rw_instr_mem
|
||||
@@ -238,39 +238,39 @@ import memory_pkg::INSTR_MEM_SIZE_WORDS;
|
||||
endmodule
|
||||
```
|
||||
|
||||
_Листинг 4. Модуль rw\_instr\_mem._
|
||||
_Listing 4. The rw\_instr\_mem module._
|
||||
|
||||
### Программатор
|
||||
### Programmer
|
||||
|
||||
Необходимо реализовать модуль программатора, использующий с одной "стороны" `uart` в качестве интерфейса для обмена данными с внешним миром, а с другой — интерфейсы для записи полученных данных в память инструкций и память данных.
|
||||
You need to implement a programmer module that uses `uart` on one "side" as the interface for exchanging data with the outside world, and on the other side uses interfaces for writing the received data to instruction memory and data memory.
|
||||
|
||||
#### Описание модуля
|
||||
#### Module Description
|
||||
|
||||
В основе работы модуля лежит конечный автомат со следующим графом перехода между состояниями:
|
||||
The module is based on a finite-state machine with the following state transition graph:
|
||||
|
||||

|
||||
|
||||
_Рисунок 3. Граф перехода между состояниями программатора._
|
||||
_Figure 3. State transition graph of the programmer._
|
||||
|
||||
Данный автомат реализует следующий алгоритм:
|
||||
This FSM implements the following algorithm:
|
||||
|
||||
1. Получение команды ("запись очередного блока" / "программирование завершено"). Данная команда представляет собой адрес записи очередного блока, и в случае, если адрес равен 0xFFFFFFFF, это означает команду "программирование завершено".
|
||||
1. В случае получения команды "программирование завершено", модуль завершает свою работу, снимая сигнал сброса с процессора.
|
||||
2. В случае получения команды "запись очередного блока" происходит переход к п. 2.
|
||||
2. Модуль отправляет сообщение о готовности принимать размер очередного блока.
|
||||
3. Выполняется передача размера очередного блока.
|
||||
4. Модуль подтверждает получение размера очередного блока и повторяет его значение.
|
||||
5. Выполняется передача очередного блока, который записывается, начиная с адреса, принятого в п.1.
|
||||
6. Получив заданное в п.3 количество байт очередного блока, модуль сообщает о завершении записи и переходит к ожиданию очередной команды в п.1.
|
||||
1. Receive a command ("write the next block" / "programming complete"). This command is the write address of the next block; if the address equals `0xFFFFFFFF`, it means the "programming complete" command.
|
||||
1. Upon receiving the "programming complete" command, the module finishes its operation by de-asserting the processor reset signal.
|
||||
2. Upon receiving the "write the next block" command, proceed to step 2.
|
||||
2. The module sends a message indicating it is ready to receive the size of the next block.
|
||||
3. The size of the next block is transmitted.
|
||||
4. The module acknowledges receipt of the block size and echoes its value.
|
||||
5. The next block is transmitted and written starting at the address received in step 1.
|
||||
6. After receiving the number of bytes of the next block specified in step 3, the module reports that writing is complete and returns to waiting for the next command in step 1.
|
||||
|
||||
На графе перехода автомата обозначены следующие условия:
|
||||
The following conditions are indicated on the FSM transition graph:
|
||||
|
||||
- `send_fin = ( msg_counter == 0) && !tx_busy` — условие завершения передачи модулем сообщения по `uart_tx`;
|
||||
- `size_fin = ( size_counter == 0) && !rx_busy` — условие завершения приема модулем размера будущей посылки;
|
||||
- `flash_fin = (flash_counter == 0) && !rx_busy` — условие завершения приема модулем блока записываемых данных;
|
||||
- `next_round = (flash_addr !='1) && !rx_busy` — условие записи блока данных через системную шину;
|
||||
- `send_fin = ( msg_counter == 0) && !tx_busy` — condition for the module to finish transmitting a message via `uart_tx`;
|
||||
- `size_fin = ( size_counter == 0) && !rx_busy` — condition for the module to finish receiving the size of the upcoming block;
|
||||
- `flash_fin = (flash_counter == 0) && !rx_busy` — condition for the module to finish receiving the block of data to be written;
|
||||
- `next_round = (flash_addr !='1) && !rx_busy` — condition for writing a data block via the system bus.
|
||||
|
||||
Ниже представлен прототип модуля с частично реализованной логикой:
|
||||
Below is the module prototype with partially implemented logic:
|
||||
|
||||
```Verilog
|
||||
module bluster
|
||||
@@ -322,26 +322,24 @@ assign flash_fin = (flash_counter == 0) && !rx_busy;
|
||||
assign next_round = (flash_addr != '1) && !rx_busy;
|
||||
|
||||
logic [7:0] [7:0] flash_size_ascii, flash_addr_ascii;
|
||||
// Блок generate позволяет создавать структуры модуля цикличным или условным
|
||||
// образом. В данном случае, при описании непрерывных присваиваний была
|
||||
// обнаружена закономерность, позволяющая описать четверки присваиваний в более
|
||||
// общем виде, который был описан в виде цикла.
|
||||
// Важно понимать, данный цикл лишь автоматизирует описание присваиваний и во
|
||||
// время синтеза схемы развернется в четыре четверки непрерывных присваиваний.
|
||||
// The generate block allows creating module structures in a loop or conditionally.
|
||||
// In this case, a pattern was found in the continuous assignments that allows
|
||||
// describing groups of four assignments in a more general form as a loop.
|
||||
// Important: this loop only automates the description of assignments and will
|
||||
// be unrolled into four groups of four continuous assignments during synthesis.
|
||||
genvar i;
|
||||
generate
|
||||
for(i=0; i < 4; i=i+1) begin
|
||||
// Данная логика преобразовывает сигналы flash_size и flash_addr,
|
||||
// которые представляют собой "сырые" двоичные числа в ASCII-символы[1]
|
||||
// This logic converts flash_size and flash_addr signals,
|
||||
// which are raw binary numbers, into ASCII characters [1]
|
||||
|
||||
// Разделяем каждый байт flash_size и flash_addr на два ниббла.
|
||||
// Ниббл — это 4 бита. Каждый ниббл можно описать 16-битной цифрой.
|
||||
// Если ниббл меньше 10 (4'ha), он описывается цифрами 0-9. Чтобы представить
|
||||
// его ascii-кодом, необходимо прибавить к нему число 8'h30
|
||||
// (ascii-код символа '0').
|
||||
// Если ниббл больше либо равен 10, он описывается буквами a-f. Для его
|
||||
// представления в виде ascii-кода, необходимо прибавить число 8'h57
|
||||
// (это уменьшенный на 10 ascii-код символа 'a' = 8'h61).
|
||||
// Each byte of flash_size and flash_addr is split into two nibbles.
|
||||
// A nibble is 4 bits. Each nibble can represent a hexadecimal digit.
|
||||
// If a nibble is less than 10 (4'ha), it is represented by digits 0-9.
|
||||
// To get its ASCII code, add 8'h30 (ASCII code of '0').
|
||||
// If a nibble is greater than or equal to 10, it is represented by letters a-f.
|
||||
// To get its ASCII code, add 8'h57
|
||||
// (which is the ASCII code of 'a' = 8'h61, minus 10).
|
||||
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 :
|
||||
@@ -355,7 +353,7 @@ generate
|
||||
endgenerate
|
||||
|
||||
logic [INIT_MSG_SIZE-1:0][7:0] init_msg;
|
||||
// ascii-код строки "ready for flash starting from 0xflash_addr\n"
|
||||
// ASCII code of the string "ready for flash starting 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'h74, 8'h69, 8'h6E, 8'h67,
|
||||
@@ -363,7 +361,7 @@ assign init_msg = { 8'h72, 8'h65, 8'h61, 8'h64, 8'h79, 8'h20, 8'h66, 8'h6F,
|
||||
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"
|
||||
// ASCII code of the string: "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,
|
||||
8'h20, 8'h77, 8'h72, 8'h69, 8'h74, 8'h65, 8'h20, 8'h30,
|
||||
8'h78, flash_size_ascii, 8'h20, 8'h62, 8'h79,
|
||||
@@ -399,35 +397,35 @@ uart_tx tx(
|
||||
endmodule
|
||||
```
|
||||
|
||||
_Листинг 5. Готовая часть программатора._
|
||||
_Listing 5. The pre-implemented portion of the programmer._
|
||||
|
||||
Здесь уже объявлены:
|
||||
The following are already declared:
|
||||
|
||||
- `enum`-сигналы `state` и `next_state`;
|
||||
- сигналы, `send_fin`, `size_fin`, `flash_fin`, `next_round`, используемые в качестве условий переходов между состояниями;
|
||||
- счетчики `msg_counter`, `size_counter`, `flash_counter`, необходимые для реализации условий переходов;
|
||||
- сигналы, необходимые для подключения модулей `uart_rx` и `uart_tx`:
|
||||
- `enum` signals `state` and `next_state`;
|
||||
- signals `send_fin`, `size_fin`, `flash_fin`, `next_round`, used as state transition conditions;
|
||||
- counters `msg_counter`, `size_counter`, `flash_counter`, required to implement the transition conditions;
|
||||
- signals required for connecting the `uart_rx` and `uart_tx` modules:
|
||||
- `rx_busy`,
|
||||
- `rx_valid`,
|
||||
- `tx_busy`,
|
||||
- `tx_valid`,
|
||||
- `rx_data`,
|
||||
- `tx_data`;
|
||||
- модули `uart_rx`, `uart_tx`;
|
||||
- сигналы `init_msg`, `flash_msg`, хранящие ascii-код ответов программатора, а также логику и сигналы, необходимые для реализации этих ответов:
|
||||
- the `uart_rx` and `uart_tx` module instances;
|
||||
- signals `init_msg` and `flash_msg`, storing the ASCII codes of the programmer's response messages, along with the logic and signals required to implement those responses:
|
||||
- `flash_size`,
|
||||
- `flash_addr`,
|
||||
- `flash_size_ascii`,
|
||||
- `flash_addr_ascii`.
|
||||
|
||||
#### Реализация модуля программатора
|
||||
#### Programmer Module Implementation
|
||||
|
||||
Для реализации данного модуля, необходимо реализовать все объявленные выше сигналы, кроме сигналов:
|
||||
To implement this module, all signals declared above must be implemented, except for:
|
||||
|
||||
- `rx_busy`, `rx_valid`, `rx_data`, `tx_busy` (т.к. те уже подключены к выходам модулей `uart_rx` и `uart_tx`),
|
||||
- `send_fin`, `size_fin`, `flash_fin`, `next_round`, `flash_size_ascii`, `flash_addr_ascii`, `init_msg`, `flash_msg` (т.к. они уже реализованы в представленной выше логике).
|
||||
- `rx_busy`, `rx_valid`, `rx_data`, `tx_busy` (since they are already connected to the outputs of the `uart_rx` and `uart_tx` modules),
|
||||
- `send_fin`, `size_fin`, `flash_fin`, `next_round`, `flash_size_ascii`, `flash_addr_ascii`, `init_msg`, `flash_msg` (since they are already implemented in the logic shown above).
|
||||
|
||||
Так же необходимо реализовать выходы модуля программатора:
|
||||
The following programmer module outputs must also be implemented:
|
||||
|
||||
- `instr_addr_o`;
|
||||
- `instr_wdata_o`;
|
||||
@@ -437,109 +435,109 @@ _Листинг 5. Готовая часть программатора._
|
||||
- `data_we_o`;
|
||||
- `core_reset_o`.
|
||||
|
||||
##### Реализация конечного автомата
|
||||
##### FSM Implementation
|
||||
|
||||
Для реализации сигналов `state`, `next_state` используйте граф переходов между состояниями, представленный на _рис. 3_. В случае, если не выполняется ни одно из условий перехода, автомат должен остаться в текущем состоянии.
|
||||
To implement the `state` and `next_state` signals, use the state transition graph shown in _Fig. 3_. If none of the transition conditions are satisfied, the FSM must remain in the current state.
|
||||
|
||||
Для работы логики переходов, необходимо реализовать счетчики `size_counter`, `flash_counter`, `msg_counter`.
|
||||
To support the transition logic, counters `size_counter`, `flash_counter`, and `msg_counter` must be implemented.
|
||||
|
||||
`size_counter` должен сбрасываться в значение `4`, а также принимать это значение во всех состояниях кроме: `RCV_SIZE`, `RCV_NEXT_COMMAND`. В данных двух состояниях счётчик должен декрементироваться в случае, если `rx_valid` равен единице.
|
||||
`size_counter` must reset to `4` and hold that value in all states except `RCV_SIZE` and `RCV_NEXT_COMMAND`. In those two states the counter must decrement whenever `rx_valid` is asserted.
|
||||
|
||||
`flash_counter` должен сбрасываться в значение `flash_size`, а также принимать это значение во всех состояниях кроме `FLASH`. В этом состоянии счётчик должен декрементироваться в случае, если `rx_valid` равен единице.
|
||||
`flash_counter` must reset to `flash_size` and hold that value in all states except `FLASH`. In that state the counter must decrement whenever `rx_valid` is asserted.
|
||||
|
||||
`msg_counter` должен сбрасываться в значение `INIT_MSG_SIZE-1` (в _Листинге 5_ объявлены параметры `INIT_MSG_SIZE`, `FLASH_MSG_SIZE` и `ACK_MSG_SIZE`).
|
||||
`msg_counter` must reset to `INIT_MSG_SIZE-1` (the parameters `INIT_MSG_SIZE`, `FLASH_MSG_SIZE`, and `ACK_MSG_SIZE` are declared in _Listing 5_).
|
||||
|
||||
`msg_counter` должен инициализироваться следующим образом:
|
||||
`msg_counter` must be initialized as follows:
|
||||
|
||||
- в состоянии `FLASH` должен принимать значение `FLASH_MSG_SIZE-1`,
|
||||
- в состоянии `RCV_SIZE` должен принимать значение `ACK_MSG_SIZE-1`,
|
||||
- в состоянии `RCV_NEXT_COMMAND` должен принимать значение `INIT_MSG_SIZE-1`.
|
||||
- in the `FLASH` state it must take the value `FLASH_MSG_SIZE-1`,
|
||||
- in the `RCV_SIZE` state it must take the value `ACK_MSG_SIZE-1`,
|
||||
- in the `RCV_NEXT_COMMAND` state it must take the value `INIT_MSG_SIZE-1`.
|
||||
|
||||
В состояниях: `INIT_MSG`, `SIZE_ACK`, `FLASH_ACK` `msg_counter` должен декрементироваться в случае, если `tx_valid` равен единице.
|
||||
In the `INIT_MSG`, `SIZE_ACK`, and `FLASH_ACK` states, `msg_counter` must decrement whenever `tx_valid` is asserted.
|
||||
|
||||
Во всех остальных ситуациях `msg_counter` должен сохранять свое значение.
|
||||
In all other situations `msg_counter` must retain its current value.
|
||||
|
||||
##### Реализация сигналов, подключаемых к uart_tx
|
||||
##### Implementing Signals Connected to uart_tx
|
||||
|
||||
Сигнал `tx_valid` должен быть равен единице только когда `tx_busy` равен нулю, а конечный автомат находится в одном из следующих состояний:
|
||||
The `tx_valid` signal must be asserted only when `tx_busy` is de-asserted and the FSM is in one of the following states:
|
||||
|
||||
- `INIT_MSG`,
|
||||
- `SIZE_ACK`,
|
||||
- `FLASH_ACK`
|
||||
|
||||
Иными словами, `tx_valid` равен единице, когда автомат находится в состоянии, отвечающем за передачу сообщений от программатора, но в данный момент программатор не отправляет очередной байт сообщения.
|
||||
In other words, `tx_valid` is asserted when the FSM is in a state responsible for transmitting messages from the programmer, but the programmer is not currently sending the next byte of the message.
|
||||
|
||||
Сигнал `tx_data` должен нести очередной байт одного из передаваемых сообщений:
|
||||
The `tx_data` signal must carry the next byte of one of the transmitted messages:
|
||||
|
||||
- в состоянии `INIT_MSG` передаётся очередной байт сообщения `init_msg`
|
||||
- в состоянии `SIZE_ACK` передаётся очередной байт сообщения `flash_size`
|
||||
- в состоянии `FLASH_ACK` передаётся очередной байт сообщения `flash_msg`.
|
||||
- in the `INIT_MSG` state the next byte of the `init_msg` message is transmitted,
|
||||
- in the `SIZE_ACK` state the next byte of the `flash_size` message is transmitted,
|
||||
- in the `FLASH_ACK` state the next byte of the `flash_msg` message is transmitted.
|
||||
|
||||
В остальных состояниях он равен нулю. Для отсчёта байт используется счётчик `msg_counter`.
|
||||
In all other states it equals zero. The `msg_counter` counter is used to track which byte to send.
|
||||
|
||||
##### Реализация интерфейсов памяти инструкций и данных
|
||||
##### Implementing Instruction Memory and Data Memory Interfaces
|
||||
|
||||
Почему программатору необходимо два интерфейса? Дело в том, что в процессорной системе используется две шины: шина инструкций и шина данных. Чтобы не переусложнять логику системы дополнительным мультиплексированием, программатор также будет реализовывать два отдельных интерфейса. При этом необходимо различать, когда выполняется программирование памяти инструкций, а когда — памяти данных. Поскольку обе эти памяти имеют независимые адресные пространства, адреса по которым может вестись программирование могут быть неотличимы. Однако с этой же проблемой мы сталкивались и в ЛР№14 во время описания скрипта компоновщика. Тогда было решено дать секции данных специальный заведомо большой адрес загрузки. Это же решение отлично ложится и в логику программатора: если мы будет использовать при программировании системы заведомо большие адреса загрузки, по их значению мы сможем понимать назначение текущего блока данных: если адрес записи этого блока больше либо равен размеру памяти инструкций в байтах — этот блок не предназначен для памяти инструкций и будет отправлен на запись по интерфейсу памяти данных, в противном случае — наоборот.
|
||||
Why does the programmer need two interfaces? The processor system uses two buses: the instruction bus and the data bus. To avoid overcomplicating the system logic with additional multiplexing, the programmer will implement two separate interfaces as well. It is necessary to distinguish when instruction memory is being programmed from when data memory is being programmed. Since both memories have independent address spaces, the addresses used for programming may be indistinguishable. However, we faced this same problem in Lab #14 when describing the linker script. At that time, the decision was made to assign the data section a deliberately large load address. This same solution fits naturally into the programmer's logic: if we use deliberately large load addresses when programming the system, we can determine the purpose of the current data block from its value — if the write address of the block is greater than or equal to the size of the instruction memory in bytes, the block is not intended for instruction memory and will be sent to the data memory interface; otherwise, it goes to the instruction memory interface.
|
||||
|
||||
Сигналы памяти инструкций (регистры `instr_addr_o`, `instr_wdata_o`, `instr_we_o`):
|
||||
Instruction memory signals (registers `instr_addr_o`, `instr_wdata_o`, `instr_we_o`):
|
||||
|
||||
- по сигналу сброса — сбрасываются в ноль
|
||||
- в случае состояния `FLASH` и пришедшего сигнала `rx_valid`, если значение `flash_addr` **меньше** размера памяти инструкций в байтах:
|
||||
- `instr_wdata_o` принимает значение `{instr_wdata_o[23:0], rx_data}` (справа вдвигается очередной пришедший байт)
|
||||
- `instr_we_o` становится равен `(flash_counter[1:0] == 2'b01)`
|
||||
- `instr_addr_o` становится равен `flash_addr + flash_counter - 1`
|
||||
- во всех остальных ситуациях `instr_wdata_o` и `instr_addr_o` сохраняют свое значение, а `instr_we_o` сбрасывается в ноль.
|
||||
- on reset — reset to zero;
|
||||
- in the `FLASH` state when `rx_valid` is received, if the value of `flash_addr` is **less than** the instruction memory size in bytes:
|
||||
- `instr_wdata_o` takes the value `{instr_wdata_o[23:0], rx_data}` (the newly received byte is shifted in from the right),
|
||||
- `instr_we_o` is set to `(flash_counter[1:0] == 2'b01)`,
|
||||
- `instr_addr_o` is set to `flash_addr + flash_counter - 1`;
|
||||
- in all other situations `instr_wdata_o` and `instr_addr_o` retain their values, and `instr_we_o` is reset to zero.
|
||||
|
||||
Сигналы памяти данных (`data_addr_o`, `data_wdata_o`, `data_we_o`):
|
||||
Data memory signals (`data_addr_o`, `data_wdata_o`, `data_we_o`):
|
||||
|
||||
- по сигналу сброса — сбрасываются в ноль
|
||||
- в случае состояния `FLASH` и пришедшего сигнала `rx_valid`, если значение `flash_addr` **больше либо равно** размеру памяти инструкций в байтах:
|
||||
- `data_wdata_o` принимает значение `{data_wdata_o[23:0], rx_data}` (справа вдвигается очередной пришедший байт)
|
||||
- `data_we_o` становится равен `(flash_counter[1:0] == 2'b01)`
|
||||
- `data_addr_o` становится равен `flash_addr + flash_counter - 1`
|
||||
- во всех остальных ситуациях `data_wdata_o` и `data_addr_o` сохраняют свое значение, а `data_we_o` сбрасывается в ноль.
|
||||
- on reset — reset to zero;
|
||||
- in the `FLASH` state when `rx_valid` is received, if the value of `flash_addr` is **greater than or equal to** the instruction memory size in bytes:
|
||||
- `data_wdata_o` takes the value `{data_wdata_o[23:0], rx_data}` (the newly received byte is shifted in from the right),
|
||||
- `data_we_o` is set to `(flash_counter[1:0] == 2'b01)`,
|
||||
- `data_addr_o` is set to `flash_addr + flash_counter - 1`;
|
||||
- in all other situations `data_wdata_o` and `data_addr_o` retain their values, and `data_we_o` is reset to zero.
|
||||
|
||||
##### Реализация оставшейся части логики
|
||||
##### Implementing the Remaining Logic
|
||||
|
||||
Регистр `flash_size` работает следующим образом:
|
||||
The `flash_size` register works as follows:
|
||||
|
||||
- по сигналу сброса — сбрасывается в 0;
|
||||
- в состоянии `RCV_SIZE` при `rx_valid` равном единице становится равен `{flash_size[2:0], rx_data}` (сдвигается на 1 байт влево и на освободившееся место ставится очередной пришедший байт);
|
||||
- в остальных ситуациях сохраняет свое значение.
|
||||
- on reset — reset to 0;
|
||||
- in the `RCV_SIZE` state when `rx_valid` is asserted, it takes the value `{flash_size[2:0], rx_data}` (shifted one byte to the left, with the newly received byte placed in the vacated position);
|
||||
- in all other situations it retains its current value.
|
||||
|
||||
Регистр `flash_addr` почти полностью повторяет поведение `flash_size`:
|
||||
The `flash_addr` register nearly mirrors the behavior of `flash_size`:
|
||||
|
||||
- по сигналу сброса — сбрасывается в 0;
|
||||
- в состоянии `RCV_NEXT_COMMAND` при `rx_valid` равном единице становится равен `{flash_addr[2:0], rx_data}` (сдвигается на 1 байт влево и на освободившееся место ставится очередной пришедший байт);
|
||||
- в остальных ситуациях сохраняет свое значение.
|
||||
- on reset — reset to 0;
|
||||
- in the `RCV_NEXT_COMMAND` state when `rx_valid` is asserted, it takes the value `{flash_addr[2:0], rx_data}` (shifted one byte to the left, with the newly received byte placed in the vacated position);
|
||||
- in all other situations it retains its current value.
|
||||
|
||||
Сигнал `core_reset_o` равен единице в случае, если состояние конечного автомата не `FINISH`.
|
||||
The `core_reset_o` signal is asserted when the FSM state is not `FINISH`.
|
||||
|
||||
> Так как вышесказанное по сути является полным описанием работы программатора на русском языке, то фактически **задача сводится к переводу** текста описания программатора **с русского на SystemVerilog**.
|
||||
> Since the above is essentially a complete description of the programmer's behavior in plain language, the **task effectively reduces to translating** the programmer description **from English into SystemVerilog**.
|
||||
|
||||
### Интеграция программатора в processor_system
|
||||
### Integrating the Programmer into processor_system
|
||||
|
||||

|
||||
|
||||
_Рисунок 4. Интеграция программатора в `processor_system`._
|
||||
_Figure 4. Integration of the programmer into `processor_system`._
|
||||
|
||||
В первую очередь, необходимо заменить память инструкций и добавить новый модуль. После чего подключить программатор к памяти инструкций и мультиплексировать выход интерфейса памяти данных программатора с интерфейсом памяти данных LSU. Сигнал сброса процессора необходимо заменить на выход `core_reset_o`.
|
||||
First, replace the instruction memory and add the new module. Then connect the programmer to the instruction memory and multiplex the programmer's data memory interface output with the LSU data memory interface. Replace the processor reset signal with the `core_reset_o` output.
|
||||
|
||||
В случае, если использовалось периферийное устройство `uart_tx`, необходимо мультиплексировать его выход `tx_o` с одноименным выходом программатора аналогично тому, как это было сделано с сигналами интерфейса памяти данных.
|
||||
If the `uart_tx` peripheral was used, its `tx_o` output must be multiplexed with the programmer's `tx_o` output in the same way as was done for the data memory interface signals.
|
||||
|
||||
### Пример загрузки программы
|
||||
### Program Loading Example
|
||||
|
||||
Чтобы проверить работу программатора на практике необходимо подготовить скомпилированную программу подобно тому, как это делалось в ЛР№14 (или взять готовые .mem-файлы вашего варианта из ЛР№13). Однако, в отличие от ЛР№14, удалять первую строчку из файла, инициализирующего память данных не надо — теперь адрес загрузки будет использоваться в процессе загрузки.
|
||||
To verify the programmer in practice, prepare a compiled program as was done in Lab #14 (or use the ready-made `.mem` files from your Lab #13 variant). However, unlike Lab #14, do not delete the first line from the data memory initialization file — the load address will now be used during the loading process.
|
||||
|
||||
Необходимо подключить отладочный стенд к последовательному порту компьютера (в случае платы Nexys A7 — достаточно просто подключить плату usb-кабелем, как это делалось на протяжении всех лабораторных для прошивки). Необходимо будет узнать COM-порт, по которому отладочный стенд подключен к компьютеру. Определить нужный COM-порт на операционной системе Windows можно через "Диспетчер устройств", который можно открыть через меню пуск. В данном окне необходимо найти вкладку "Порты (COM и LPT)", раскрыть её, а затем подключить отладочный стенд через USB-порт (если тот еще не был подключен). В списке появится новое устройство, а в скобках будет указан нужный COM-порт.
|
||||
Connect the debug board to the computer's serial port (for the Nexys A7 board it is sufficient to simply connect the board with a USB cable, as was done throughout the labs for FPGA programming). You will need to find the COM port to which the debug board is connected. On Windows, the required COM port can be found through Device Manager, which can be opened from the Start menu. In that window, find the "Ports (COM & LPT)" section, expand it, and then connect the debug board via USB (if not already connected). A new device will appear in the list, with the COM port number shown in parentheses.
|
||||
|
||||
Подключив отладочный стенд к последовательному порту компьютера и сконфигурировав ПЛИС вашим проектом, остается проинициализировать память. Сделать это можно с помощью предоставленного скрипта, пример запуска которого приведен в листинге 6.
|
||||
After connecting the debug board to the computer's serial port and configuring the FPGA with your project, initialize the memory using the provided script. An example of running the script is shown in Listing 6.
|
||||
|
||||
```bash
|
||||
# Пример использования скрипта. Сперва указываются опциональные аргументы
|
||||
# (инициализация памяти данных и различных областей памяти vga-контроллера),
|
||||
# Затем идут обязательные аргументы: файл для прошивки памяти инструкций и
|
||||
# COM-порт.
|
||||
# Example script usage. Optional arguments are listed first
|
||||
# (data memory initialization and various VGA controller memory regions),
|
||||
# followed by the required arguments: the file for instruction memory
|
||||
# initialization and the COM port.
|
||||
$ python flash.py --help
|
||||
usage: flash.py [-h] [-d DATA] [-c COLOR] [-s SYMBOLS] [-t TIFF] instr comport
|
||||
|
||||
@@ -560,42 +558,42 @@ python3 flash.py -d /path/to/data.mem -c /path/to/col_map.mem \
|
||||
-s /path/to/char_map.mem -t /path/to/tiff_map.mem /path/to/program COM
|
||||
```
|
||||
|
||||
_Листинг 6. Пример использования скрипта для инициализации памяти._
|
||||
_Listing 6. Example of using the memory initialization script._
|
||||
|
||||
## Порядок выполнения задания
|
||||
### Steps
|
||||
|
||||
1. Опишите модуль `rw_instr_mem`, используя код, представленный в _листинге 4_.
|
||||
2. Добавьте пакет [`bluster_pkg`](bluster_pkg.sv), содержащий объявления параметров и вспомогательных вызовов, используемых модулем и тестбенчем.
|
||||
3. Опишите модуль `bluster`, используя код, представленный в _листинге 5_. Завершите описание этого модуля.
|
||||
1. Опишите конечный автомат используя сигналы `state`, `next_state`, `send_fin`, `size_fin`, `flash_fin`, `next_round`.
|
||||
2. [Реализуйте](#Реализация-конечного-автомата) логику счетчиков `size_counter`, `flash_counter`, `msg_counter`.
|
||||
3. [Реализуйте](#Реализация-сигналов-подключаемых-к-uart_tx) логику сигналов `tx_valid`, `tx_data`.
|
||||
4. [Реализуйте](#Реализация-интерфейсов-памяти-инструкций-и-данных) интерфейсы памяти инструкций и данных.
|
||||
5. [Реализуйте](#Реализация-оставшейся-части-логики) логику оставшихся сигналов.
|
||||
4. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_15.tb_bluster.sv`](lab_15.tb_bluster.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их.
|
||||
1. Перед запуском моделирования убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`.
|
||||
2. Для работы тестбенча потребуется пакет [`peripheral_pkg`](../13.%20Peripheral%20units/peripheral_pkg.sv) из ЛР№13, а также файлы [`lab_15_char.mem`](lab_15_char.mem), [`lab_15_data.mem`](lab_15_data.mem), [`lab_15_instr.mem`](lab_15_instr.mem) из папки [mem_files](mem_files).
|
||||
5. Интегрируйте программатор в модуль `processor_system`.
|
||||
1. В модуле `processor_system` замените память инструкцией модулем `rw_instr_mem`.
|
||||
2. Добавьте в модуль `processor_system` экземпляр модуля-программатора.
|
||||
1. Интерфейс памяти инструкций подключается к порту записи модуля `rw_instr_mem`.
|
||||
2. Интерфейс памяти данных мультиплексируется с интерфейсом памяти данных модуля `LSU`.
|
||||
3. Замените сигнал сброса модуля `processor_core` сигналом `core_reset_o`.
|
||||
4. В случае если у вас есть периферийное устройство `uart_tx` его выход `tx_o` необходимо мультиплексировать с выходом `tx_o` программатора аналогично тому, как был мультиплексирован интерфейс памяти данных.
|
||||
6. Проверьте процессорную систему после интеграции программатора с помощью верификационного окружения, представленного в файле [`lab_15.tb_processor_system.sv`](lab_15.tb_processor_system.sv).
|
||||
1. Данный тестбенч необходимо обновить под свой вариант. Найдите строки со вспомогательным вызовом `program_region`, первыми аргументами которого являются "YOUR_INSTR_MEM_FILE" и "YOUR_DATA_MEM_FILE". Обновите эти строки под имена файлов, которыми вы инициализировали свои память инструкций и данных в ЛР№13. Если память данных вы не инициализировали, можете удалить/закомментировать соответствующий вызов. При необходимости вы можете добавить столько вызовов, сколько вам потребуется.
|
||||
2. В .mem-файлах, которыми вы будете инициализировать вашу память необходимо сделать доработку. Вам необходимо указать адрес ячейки памяти, с которой необходимо начать инициализировать память. Это делается путём добавления в начало файла строки вида: `@hex_address`. Пример `@FA000000`. Строка обязательно должна начинаться с символа `@`, а адрес обязательно должен быть в шестнадцатеричном виде. Для памяти инструкций нужен нулевой адрес, а значит нужно использовать строку `@00000000`. Для памяти данных необходимо использовать адрес, превышающий размер памяти инструкций, но не попадающий в адресное пространство других периферийных устройств (старший байт адреса должен быть равен нулю). Поскольку система использует байтовую адресацию, адрес ячеек будет в 4 раза меньше адреса по которому обратился бы процессор. Это значит, что если бы вы хотели проинициализировать память VGA-контроллера, вам нужно было бы использовать не адрес `@07000000`, а `@01C00000` (`01C00000 * 4 = 07000000`). Таким образом, для памяти данных оптимальным адресом инициализации будет `@00200000`, поскольку эта ячейка с адресом `00200000` соответствует адресу `00800000` — этот адрес не накладывается на адресное пространство других периферийных устройств, но при этом заведомо больше возможного размера памяти инструкций. Примеры использования начальных адресов вы можете посмотреть в файлах [`lab_15_char.mem`](lab_15_char.mem), [`lab_15_data.mem`](lab_15_data.mem), [`lab_15_instr.mem`](lab_15_instr.mem) из папки [mem_files](mem_files).
|
||||
3. Тестбенч будет ожидать завершения инициализации памяти, после чего сформирует те же тестовые воздействия, что и в тестбенче к ЛР№13. А значит, если вы использовали для инициализации те же самые файлы, поведение вашей системы после инициализации не должно отличаться от поведения на симуляции в ЛР№13.
|
||||
4. Перед запуском моделирования убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`.
|
||||
7. Переходить к следующему пункту можно только после того, как вы полностью убедились в работоспособности системы на этапе моделирования (увидели, что в память инструкций и данных были записаны корректные данные, после чего процессор стал обрабатывать прерывания от устройства ввода). Генерация битстрима будет занимать у вас долгое время, а итогом вы получите результат: заработало/не заработало, без какой-либо дополнительной информации, поэтому без прочного фундамента на моделировании далеко уехать у вас не выйдет.
|
||||
8. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](../13.%20Peripheral%20units/nexys_a7_100t.xdc)), если тот ещё не был подключён, либо замените его содержимое данными из файла, представленного в ЛР№13.
|
||||
9. Проверьте работу вашей процессорной системы на отладочном стенде с ПЛИС.
|
||||
1. Для инициализации памяти процессорной системы используется скрипт [flash.py](flash.py).
|
||||
2. Перед инициализацией необходимо подключить отладочный стенд к последовательному порту компьютера и узнать номер этого порта (см. [пример загрузки программы](#Пример-загрузки-программы)).
|
||||
3. Формат файлов для инициализации памяти с помощью скрипта аналогичен формату, использовавшемуся в [тестбенче](lab_15_tb_bluster.sv). Это значит что первой строчкой всех файлов должна быть строка, содержащая адрес ячейки памяти, с которой должна начаться инициализация (см. п. 6.2).
|
||||
10. В текущем исполнении инициализировать память системы можно только 1 раз с момента сброса, что может оказаться не очень удобным при отладке программ. Подумайте, как можно модифицировать конечный автомат программатора таким образом, чтобы получить возможность в неограниченном количестве инициализаций памяти без повторного сброса всей системы.
|
||||
1. Describe the `rw_instr_mem` module using the code presented in _Listing 4_.
|
||||
2. Add the [`bluster_pkg`](bluster_pkg.sv) package, which contains parameter declarations and helper tasks used by the module and the testbench.
|
||||
3. Describe the `bluster` module using the code presented in _Listing 5_. Complete the description of this module.
|
||||
1. Implement the FSM using signals `state`, `next_state`, `send_fin`, `size_fin`, `flash_fin`, `next_round`.
|
||||
2. [Implement](#FSM-Implementation) the counter logic for `size_counter`, `flash_counter`, `msg_counter`.
|
||||
3. [Implement](#Implementing-Signals-Connected-to-uart_tx) the logic for signals `tx_valid`, `tx_data`.
|
||||
4. [Implement](#Implementing-Instruction-Memory-and-Data-Memory-Interfaces) the instruction memory and data memory interfaces.
|
||||
5. [Implement](#Implementing-the-Remaining-Logic) the logic for the remaining signals.
|
||||
4. Verify the module using the testbench provided in [`lab_15.tb_bluster.sv`](lab_15.tb_bluster.sv). If error messages appear in the TCL console, [find](../../Vivado%20Basics/05.%20Bug%20hunting.md) and fix them.
|
||||
1. Before running the simulation, make sure the correct top-level module is selected in `Simulation Sources`.
|
||||
2. The testbench requires the [`peripheral_pkg`](../13.%20Peripheral%20units/peripheral_pkg.sv) package from Lab #13, as well as the files [`lab_15_char.mem`](lab_15_char.mem), [`lab_15_data.mem`](lab_15_data.mem), and [`lab_15_instr.mem`](lab_15_instr.mem) from the [mem_files](mem_files) folder.
|
||||
5. Integrate the programmer into the `processor_system` module.
|
||||
1. In the `processor_system` module, replace the instruction memory with the `rw_instr_mem` module.
|
||||
2. Add an instance of the programmer module to `processor_system`.
|
||||
1. The instruction memory interface connects to the write port of `rw_instr_mem`.
|
||||
2. The data memory interface is multiplexed with the `LSU` data memory interface.
|
||||
3. Replace the `processor_core` reset signal with the `core_reset_o` signal.
|
||||
4. If the `uart_tx` peripheral is present, its `tx_o` output must be multiplexed with the programmer's `tx_o` output in the same way as the data memory interface was multiplexed.
|
||||
6. Verify the processor system after integrating the programmer using the testbench provided in [`lab_15.tb_processor_system.sv`](lab_15.tb_processor_system.sv).
|
||||
1. This testbench must be updated for your variant. Find the lines containing the helper call `program_region` whose first arguments are `"YOUR_INSTR_MEM_FILE"` and `"YOUR_DATA_MEM_FILE"`. Update these lines with the names of the files used to initialize your instruction memory and data memory in Lab #13. If you did not initialize the data memory, you may delete or comment out the corresponding call. You may add as many calls as needed.
|
||||
2. The `.mem` files you use to initialize your memory require an update. You must specify the memory cell address at which initialization should begin. This is done by adding a line of the form `@hex_address` at the beginning of the file. Example: `@FA000000`. The line must begin with the `@` character, and the address must be in hexadecimal. For instruction memory, the zero address is required, so use `@00000000`. For data memory, use an address that exceeds the instruction memory size but does not fall within the address space of other peripherals (the most significant byte of the address must be zero). Since the system uses byte addressing, the cell address will be 4 times smaller than the address the processor would use. This means that if you wanted to initialize the VGA controller memory, you would need to use `@01C00000` instead of `@07000000` (`01C00000 * 4 = 07000000`). Therefore, the optimal initialization address for data memory is `@00200000`, since the cell at address `00200000` corresponds to address `00800000` — this address does not overlap with the address space of other peripherals and is guaranteed to be larger than any possible instruction memory size. Examples of start address usage can be found in the files [`lab_15_char.mem`](lab_15_char.mem), [`lab_15_data.mem`](lab_15_data.mem), and [`lab_15_instr.mem`](lab_15_instr.mem) in the [mem_files](mem_files) folder.
|
||||
3. The testbench will wait for memory initialization to complete, after which it will apply the same test stimuli as the testbench from Lab #13. Therefore, if you used the same initialization files, the system behavior after initialization should not differ from the simulation behavior in Lab #13.
|
||||
4. Before running the simulation, make sure the correct top-level module is selected in `Simulation Sources`.
|
||||
7. Proceed to the next step only after you have fully verified that the system works correctly during simulation (confirmed that correct data was written to instruction and data memories, and that the processor then began handling interrupts from the input device). Bitstream generation will take a significant amount of time, and the outcome will simply be "worked / didn't work" with no additional information, so without a solid simulation foundation you will not get far.
|
||||
8. Add the constraints file ([nexys_a7_100t.xdc](../13.%20Peripheral%20units/nexys_a7_100t.xdc)) to the project if it has not been added yet, or replace its contents with the data from the file provided in Lab #13.
|
||||
9. Verify the operation of your processor system on the FPGA debug board.
|
||||
1. Use the [flash.py](flash.py) script to initialize the processor system memory.
|
||||
2. Before initialization, connect the debug board to the computer's serial port and find the port number (see [Program Loading Example](#Program-Loading-Example)).
|
||||
3. The file format for memory initialization via the script is the same as the format used in the [testbench](lab_15_tb_bluster.sv). This means the first line of all files must be a line containing the memory cell address at which initialization should begin (see step 6.2).
|
||||
10. In the current implementation, memory can only be initialized once since the last reset, which may be inconvenient when debugging programs. Think about how the programmer's FSM could be modified to allow an unlimited number of memory initializations without resetting the entire system.
|
||||
|
||||
## Список источников
|
||||
## References
|
||||
|
||||
1. [Finite-state machine](https://en.wikipedia.org/wiki/Finite-state_machine).
|
||||
2. [All about circuits — Finite State Machines](https://www.allaboutcircuits.com/textbook/digital/chpt-11/finite-state-machines/)
|
||||
|
||||
Reference in New Issue
Block a user