English version draft

Assisted-by: Claude:claude-4.6-sonnet
This commit is contained in:
Andrei Solodovnikov
2026-04-12 13:53:25 +03:00
parent 63260f434e
commit f3fcd27387
74 changed files with 5133 additions and 5875 deletions

View File

@@ -1,172 +1,172 @@
# Лабораторная работа №4 "Простейшее программируемое устройство"
# Lab 4. Primitive Programmable Device
В этой лабораторной работе, на основе ранее разработанных блоков памяти и АЛУ, вы соберете простой учебный процессор с архитектурой `CYBERcobra 3000 Pro 2.1`. Это нужно для более глубокого понимания принципов работы программно-управляемых устройств, чтобы проще было понять архитектуру RISC-V в будущем.
In this lab, using the previously developed memory blocks and ALU, you will build a simple educational processor with the `CYBERcobra 3000 Pro 2.1` architecture. This is needed for a deeper understanding of the principles of how program-controlled devices work, so that understanding the RISC-V architecture in the future will be easier.
## Материалы для подготовки к лабораторной работе
## Preparation Materials
В дополнение к [материалам](../../Basic%20Verilog%20structures/), изученным в ходе предыдущих работ, вам рекомендуется ознакомиться с:
In addition to the [materials](../../Basic%20Verilog%20structures/) studied in previous labs, you are recommended to review:
- Оператором конкатенации ([Concatenation.md](../../Basic%20Verilog%20structures/Concatenation.md)).
- The concatenation operator ([Concatenation.md](../../Basic%20Verilog%20structures/Concatenation.md)).
## Цель
## Goal
Реализовать простейшее программируемое устройство.
Implement a primitive programmable device.
## Ход работы
## Workflow
1. Изучить принцип работы процессоров (соответствующий раздел [#теории](#Теория-про-программируемое-устройство))
2. Познакомиться с архитектурой и микроархитектурой `CYBERcobra 3000 Pro 2.1` (раздел про эту [#архитектуру](#Архитектура-cybercobra-3000-pro-21-и-её-микроархитектура))
3. Изучить необходимые для описания процессора конструкции SystemVerilog (раздел [#инструменты](#Инструменты-для-реализации-процессора))
4. Реализовать процессор с архитектурой `CYBERcobra 3000 Pro 2.1` ([#задание по разработке аппаратуры](#Задание-по-реализации-процессора))
5. Проверить работу процессора в ПЛИС.
1. Study the operating principles of processors (see the corresponding [#theory](#Theory-on-programmable-devices) section)
2. Learn about the architecture and microarchitecture of `CYBERcobra 3000 Pro 2.1` (see the [#architecture](#CYBERcobra-3000-Pro-21-Architecture-and-Microarchitecture) section)
3. Study the SystemVerilog constructs required to describe the processor (see the [#tools](#Tools-for-Processor-Implementation) section)
4. Implement the processor with the `CYBERcobra 3000 Pro 2.1` architecture ([#hardware design task](#Processor-Implementation-Task))
5. Verify the processor operation on the FPGA.
Доп. задание, выполняемое дома:
Additional task (to be completed at home):
6. Написать программу для процессора и убедиться на моделировании в корректности её выполнения ([Индивидуальное задание](Индивидуальное%20задание)).
6. Write a program for the processor and verify its correct execution in simulation ([Individual Assignment](Индивидуальное%20задание)).
## Теория про программируемое устройство
## Theory on Programmable Devices
В обобщенном виде, процессор включает в себя память, АЛУ, устройство управления и интерфейсную логику для организации ввода/вывода. Также, в процессоре есть специальный регистр `PC` (**Program Counter** счётчик команд), который хранит в себе число адрес ячейки памяти, где хранится инструкция, которую нужно выполнить. Инструкция тоже представляет собой число, в котором закодировано `что нужно сделать` и `с чем это нужно сделать`.
In general terms, a processor includes memory, an ALU, a control unit, and interface logic for organizing input/output. A processor also has a special register `PC` (**Program Counter**), which holds a number — the address of the memory cell containing the instruction to be executed. An instruction is also a number that encodes `what needs to be done` and `what it needs to be done with`.
Алгоритм работы процессора следующий:
The processor operates according to the following algorithm:
1. из памяти считывается инструкция по адресу `PC`;
2. устройство управления декодирует полученную инструкцию (то есть определяет какую операцию нужно сделать, где взять операнды и куда разместить результат);
3. декодировав инструкцию, устройство управления выдает всем блокам процессора (АЛУ, регистровый файл, мультиплексоры) соответствующие управляющие сигналы, тем самым выполняя эту инструкцию;
4. изменяется значение `PC`;
5. цикл повторяется с `п.1`.
1. an instruction is fetched from memory at address `PC`;
2. the control unit decodes the received instruction (i.e., determines what operation needs to be performed, where to get the operands, and where to place the result);
3. having decoded the instruction, the control unit issues corresponding control signals to all processor blocks (ALU, register file, multiplexers), thereby executing the instruction;
4. the value of `PC` is updated;
5. the cycle repeats from step 1.
Любая инструкция приводит к изменению состояния памяти. В случае процессора, рассматриваемого в данной лабораторной работе, есть два класса инструкций: одни изменяют содержимое регистрового файла — это инструкции записи. Другие изменяют значение `PC` — это инструкции перехода. В первом случае используются вычислительные инструкции и инструкции загрузки данных из других источников. Во втором случае используются инструкции перехода.
Every instruction leads to a change in the memory state. In the case of the processor considered in this lab, there are two classes of instructions: those that modify the register file — these are write instructions. Others change the value of `PC` — these are branch instructions (conditional and unconditional). The first class includes computational instructions and instructions for loading data from other sources. The second class includes branch instructions.
Если процессор обрабатывает вычислительную инструкцию, то `PC` перейдет к следующей по порядку инструкции. В ЛР№3 мы реализовали память инструкций с [побайтовой адресацией](../03.%20Register%20file%20and%20memory/#1-Память-инструкций). Это означает, что каждый байт памяти имеет свой собственный адрес. Поскольку длина инструкции составляет `4 байта`, для перехода к следующей инструкции `PC` должен быть увеличен на `4` (`PC = PC + 4`). При этом, регистровый файл сохранит результат некоторой операции на АЛУ или данные с порта входных данных.
If the processor is executing a computational instruction, `PC` advances to the next instruction in sequence. In Lab 3, we implemented an instruction memory with [byte addressing](../03.%20Register%20file%20and%20memory/#1-Память-инструкций). This means each byte of memory has its own address. Since an instruction is `4 bytes` long, `PC` must be incremented by `4` to advance to the next instruction (`PC = PC + 4`). In this case, the register file saves the result of some ALU operation or data from the input data port.
Если же обрабатывается инструкция перехода, то возможно два варианта. В случае безусловного или успешного условного перехода, значение `PC` увеличится на значение константы, закодированной внутри инструкции `PC = PC + const*4` (иными словами, `const` говорит о том, через сколько инструкций перепрыгнет `PC`, `const` может быть и отрицательной). В случае же неуспешного условного перехода `PC`, как и после вычислительных команд, просто перейдет к следующей инструкции, то есть `PC = PC + 4`.
If a branch instruction is being executed, there are two possible outcomes. In the case of an unconditional or successful conditional branch, `PC` is incremented by the value of the constant encoded within the instruction: `PC = PC + const*4` (in other words, `const` specifies how many instructions `PC` will jump over; `const` can also be negative). In the case of an unsuccessful conditional branch, `PC`, as with computational instructions, simply advances to the next instruction: `PC = PC + 4`.
> Строго говоря `PC` меняется при выполнении любой инструкции (кроме случая `const = 0`, то есть перехода инструкции на саму себя `PC = PC + 0*4`). Разница в том, на какое значение `PC` изменится. В вычислительных инструкциях это всегда адрес следующей инструкции, программа не управляет `PC`, он "сам знает", что ему делать. В инструкциях перехода программа и контекст определяют, что произойдет с `PC`.
> Strictly speaking, `PC` changes on every instruction (except when `const = 0`, meaning a self-loop `PC = PC + 0*4`). The difference lies in what value `PC` changes to. For computational instructions, it is always the address of the next instruction — the program does not control `PC`, it "knows on its own" what to do. For branch instructions, the program and context determine what happens to `PC`.
## Архитектура CYBERcobra 3000 Pro 2.1 и её микроархитектура
## CYBERcobra 3000 Pro 2.1 Architecture and Microarchitecture
![../../.pic/Labs/lab_04_cybercobra/logoCC3000.svg](../../.pic/Labs/lab_04_cybercobra/logoCC3000.svg)
В качестве первого разрабатываемого программируемого устройства предлагается использовать архитектуру специального назначения `CYBERcobra 3000 Pro 2.1` (далее "CYBERcobra"), которая была разработана в **МИЭТ**. Главным достоинством данной архитектуры является простота её понимания и реализации. Главным её минусом является неоптимальность ввиду неэффективной реализации кодирования инструкций, что приводит к наличию неиспользуемых битов в программах. Но это неважно, так как основная цель разработки процессора с архитектурой `CYBERcobra` — это более глубокое понимание принципов работы программируемых устройств, которое поможет при разработке более сложного процессора с архитектурой **RISC-V**.
As the first programmable device to be developed, the special-purpose architecture `CYBERcobra 3000 Pro 2.1` (hereinafter "CYBERcobra"), developed at **MIET**, is proposed. The main advantage of this architecture is the simplicity of understanding and implementing it. Its main drawback is inefficiency due to a suboptimal instruction encoding scheme, which results in unused bits in programs. However, this is not important, since the primary goal of developing a processor with the `CYBERcobra` architecture is to gain a deeper understanding of the principles of programmable devices, which will help when developing a more complex processor with the **RISC-V** architecture.
Простота архитектуры `CYBERcobra` проявляется, в том числе, за счёт отсутствия памяти данных. Это значит, что данные c которыми работает программа могут храниться только в регистровом файле. Также в таком процессоре почти полностью отсутствует устройство управления (формально оно существует, но состоит только из проводов и пары логических вентилей).
The simplicity of the `CYBERcobra` architecture is partly due to the absence of a data memory. This means that data the program works with can only be stored in the register file. The control unit is also nearly absent in such a processor (formally it exists, but consists only of wires and a couple of logic gates).
Архитектурой предусмотрена поддержка 19 инструкций (5 типов команд):
The architecture supports 19 instructions (5 instruction types):
Первые два типа содержат 16 инструкций, которые выполняются на АЛУ:
The first two types contain 16 instructions executed on the ALU:
- 10 вычислительных
- 6 операций сравнения для условного перехода
- 10 computational
- 6 comparison operations for conditional branching
Кроме того, есть инструкции:
In addition, there are instructions for:
- безусловного перехода
- загрузки константы
- загрузки данных с внешнего устройства.
- unconditional branch
- loading a constant
- loading data from an external device.
К классу инструкций записи, то есть тех, которые меняют значение регистрового файла, можно отнести: 10 вычислительных, загрузки константы и загрузки данных с внешнего устройства. К классу инструкций перехода: 6 операций сравнения для условного перехода и безусловный переход.
The write instruction class (those that modify the register file) includes: 10 computational instructions, load constant, and load data from external device. The branch instruction class includes: 6 comparison operations for conditional branching and unconditional branch.
### Последовательное считывание инструкций
### Sequential Instruction Fetch
Будем рассматривать архитектуру (функциональные возможности процессора) и микроархитектуру (реализацию процессора) одновременно, прослеживая рассуждения их разработчика.
We will consider the architecture (the functional capabilities of the processor) and the microarchitecture (the implementation of the processor) simultaneously, following the reasoning of their designer.
Для начала реализуем базовый функционал, подключив счётчик команд `PC` к памяти инструкций `instr_mem` и сумматору, прибавляющему 4 к `PC`. Выход сумматора подключим ко входу `PC`.
First, let us implement the basic functionality by connecting the program counter `PC` to the instruction memory `instr_mem` and to an adder that adds 4 to `PC`. The output of the adder is connected to the input of `PC`.
Каждый раз, когда будет происходить тактовый импульс (переключение `clk_i` из 0 в 1), значение `PC` будет увеличиваться на `4`, тем самым указывая на следующую инструкцию. Последовательное считывание программы из памяти готово.
Each time a clock edge occurs (transition of `clk_i` from 0 to 1), the value of `PC` increments by `4`, thereby pointing to the next instruction. Sequential program fetch from memory is now complete.
Так как операции будут выполняться только над данными в регистровом файле, то его можно сразу подключить к АЛУ, соединив порты чтения `read_data1_o` и `read_data2_o` со входами операндов АЛУ, а результат операции АЛУ подключив к порту на запись `write_data_i`. Полученный результат изображен на _рис. 0_.
Since operations will only be performed on data in the register file, it can be immediately connected to the ALU by wiring the read ports `read_data1_o` and `read_data2_o` to the ALU operand inputs, and connecting the ALU operation result to the write port `write_data_i`. The resulting schematic is shown in _Fig. 0_.
> Для того чтобы номера таблиц и рисунков лучше соотносились друг с другом и сопутствующим текстом, первая схема разрабатываемой микроархитектуры будет обозначена как _Рисунок 0_. Все последующие схемы будут совпадать по нумерации с таблицами, обозначающими способ кодирования реализуемого типа инструкций.
> To make figure and table numbering correspond better to each other and to the accompanying text, the first schematic of the microarchitecture under development is labeled _Figure 0_. All subsequent schematics will share numbering with the tables that describe the encoding of the corresponding instruction type.
![../../.pic/Labs/lab_04_cybercobra/ppd_0.drawio.svg](../../.pic/Labs/lab_04_cybercobra/ppd_0.drawio.svg)
_Рисунок 0. Размещение на схеме основных блоков._
_Figure 0. Placement of the main blocks on the schematic._
Для компактности схемы названия портов регистрового файла сокращены (`RA1` обозначает `read_addr1_i` и т.п.).
For compactness, register file port names are abbreviated (`RA1` stands for `read_addr1_i`, etc.).
### Кодирование вычислительных инструкций
### Computational Instruction Encoding
Чтобы добавить поддержку каких-либо инструкций, необходимо договориться **как** они будут кодироваться (эта часть относится к вопросам архитектуры). Вычислительные инструкции требуют следующую информацию:
To add support for any instructions, it is necessary to agree on **how** they will be encoded (this part relates to architecture). Computational instructions require the following information:
1. по каким адресам регистрового файла лежат операнды?
2. по какому адресу будет сохранен результат?
3. какая операция должна быть выполнена?
1. at which register file addresses are the operands located?
2. at which address will the result be saved?
3. what operation needs to be performed?
Для этого в инструкции были выбраны следующие поля: 5 бит (`[27:23]`) для кодирования операции на АЛУ, два раза по 5 бит для кодирования адресов операндов в регистровом файле (`[22:18]` и `[17:13]`) и 5 бит для кодирования адреса результата (`[4:0]`). _Таблица 1_ демонстрирует деление 32-битной инструкции на поля `alu_op`, `RA1`, `RA2` и `WA`.
For this purpose, the following fields are selected in the instruction: 5 bits (`[27:23]`) for encoding the ALU operation, two groups of 5 bits for encoding operand addresses in the register file (`[22:18]` and `[17:13]`), and 5 bits for encoding the result address (`[4:0]`). _Table 1_ shows the division of the 32-bit instruction into the fields `alu_op`, `RA1`, `RA2`, and `WA`.
![../../.pic/Labs/lab_04_cybercobra/ppd_code_1.png](../../.pic/Labs/lab_04_cybercobra/ppd_code_1.png)
_Таблица 1. Кодирование вычислительных инструкций в архитектуре CYBERcobra._
_Table 1. Computational instruction encoding in the CYBERcobra architecture._
``` C
reg_file[WA] ← reg_file[RA1] {alu_op} reg_file[RA2]
```
Запись выше является некоторой формализацией выполняемой функции, которая как бы отвечает на вопрос "а что, собственно, будет сделано?". В регистр по адресу WA (`reg_file[WA]`) будет записан (``) результат операции alu_op (`{alu_op}`) между регистрами по адресам RA1 (`reg_file[RA1]`) и RA2 (`reg_file[RA1]`).
The expression above is a formalization of the function being performed, answering the question "what exactly will be done?". The result of the operation alu_op (`{alu_op}`) between the registers at addresses RA1 (`reg_file[RA1]`) and RA2 (`reg_file[RA2]`) will be written (``) into the register at address WA (`reg_file[WA]`).
### Реализация вычислительных инструкций
### Implementing Computational Instructions
Чтобы процессор правильно реагировал на эти инструкции, требуется подключить ко входам адреса регистрового файла и управляющему входу АЛУ соответствующие биты выхода `read_data_o` памяти инструкции (**Instruction Memory**). Допустим, программный счётчик указывает на ячейку памяти, в которой хранится следующая 32-битная инструкция:
For the processor to respond correctly to these instructions, the corresponding bits of the instruction memory (`Instruction Memory`) `read_data_o` output must be connected to the register file address inputs and the ALU control input. Suppose the program counter points to a memory cell containing the following 32-bit instruction:
```text
0000|00111 |00100|01000|00000000|11100
|alu_op| RA1 | RA2 | | WA
```
В этом случае, будет выполнена операция:
In this case, the following operation will be performed:
```text
reg_file[28] = reg_file[4] | reg_file[8]
reg_file[28] = reg_file[4] | reg_file[8]
```
Здесь:
Here:
- `alu_op = 00111`, что соответствует операции **логического ИЛИ** (см ЛР№2);
- `WA = 11100`, то есть запись произойдёт в 28-ой регистр;
- `RA1 = 00100` и `RA2 = 01000` — это значит что данные для АЛУ будут браться из 4-го и 8-го регистров соответственно.
- `alu_op = 00111`, which corresponds to the **bitwise OR** operation (see Lab 2);
- `WA = 11100`, meaning the result will be written to register 28;
- `RA1 = 00100` and `RA2 = 01000` — this means the ALU operands will be taken from registers 4 and 8, respectively.
_Рис. 1_ иллюстрирует фрагмент микроархитектуры, поддерживающий вычислительные операции на АЛУ. Поскольку другие инструкции пока что не поддерживаются, то вход `WE` регистрового файла просто равен `1` (это временно).
_Fig. 1_ illustrates the microarchitecture fragment supporting ALU computational operations. Since other instructions are not yet supported, the `WE` input of the register file is simply set to `1` (this is temporary).
![../../.pic/Labs/lab_04_cybercobra/ppd_1.drawio.svg](../../.pic/Labs/lab_04_cybercobra/ppd_1.drawio.svg)
_Рисунок 1. Подключение АЛУ и регистрового файла для реализации вычислительных инструкций._
_Figure 1. Connecting the ALU and register file to implement computational instructions._
### Реализация загрузки константы в регистровый файл
### Implementing Constant Load into the Register File
Обрабатываемая информация как-то должна попадать в регистровый файл, для этого добавим инструкцию загрузки константы по адресу `WA`. Чтобы аппаратура могла различать, когда ей нужно выполнять операцию на АЛУ, а когда загружать константу, назначим один бит инструкции определяющим "что именно будет записано в регистровый файл": результат с АЛУ или константа из инструкции. За это будет отвечать 28-ой бит инструкции `WS` (**Write Source**). Если `WS == 1`, значит выполняется вычислительная инструкция, а если `WS == 0`, значит нужно загрузить константу в регистровый файл.
The data being processed must somehow enter the register file, so let us add an instruction to load a constant at address `WA`. For the hardware to distinguish between executing an ALU operation and loading a constant, one bit of the instruction is designated to indicate "what exactly will be written to the register file": the result from the ALU or a constant from the instruction. Bit 28 of the instruction, `WS` (**Write Source**), handles this. If `WS == 1`, a computational instruction is being executed; if `WS == 0`, a constant must be loaded into the register file.
Сама константа имеет разрядность **23 бита** ([27:5] биты инструкции) и должна быть **знакорасширена** до 32-х бит, то есть к 23-битной константе нужно приклеить слева 9 раз 23-ий знаковый бит константы (см. [оператор конкатенации](../../Basic%20Verilog%20structures/Concatenation.md)).
The constant itself has a width of **23 bits** (bits `[27:5]` of the instruction) and must be **sign-extended** to 32 bits, meaning the 23-bit sign bit must be replicated 9 times to the left (see the [concatenation operator](../../Basic%20Verilog%20structures/Concatenation.md)).
Пример: если [27:5] биты инструкции равны:
Example: if bits `[27:5]` of the instruction equal:
```text
10100000111100101110111
```
то после знакорасширения константа примет вид:
then after sign extension the constant becomes:
```text
11111111110100000111100101110111
```
(если бы старший бит был равен нулю, то константа заполнилась бы слева нулями, а не единицами).
(if the most significant bit were zero, the constant would be filled with zeros on the left instead of ones).
Нет ничего страшного в том, что биты константы попадают на те же поля, что и `alu_op`, `RA1` и `RA2` — когда выполняется инструкция загрузки константы не важно, что будет выдавать АЛУ в этот момент (ведь благодаря мультиплексору на вход регистрового файла приходит константа). А значит не важно и что приходит в этот момент на АЛУ в качестве операндов и кода операции. _Таблица 2_ демонстрирует деление 32-битной инструкции на поля `alu_op`, `RA1`, `RA2`, `WA`, `WS` и `rf_const`, **с перекрытием полей**.
There is nothing wrong with the constant bits overlapping the same fields as `alu_op`, `RA1`, and `RA2` — when a constant load instruction is being executed, it does not matter what the ALU outputs at that moment (since the multiplexer routes the constant to the register file input). Therefore, it does not matter what arrives at the ALU as operands or operation code. _Table 2_ shows the division of the 32-bit instruction into the fields `alu_op`, `RA1`, `RA2`, `WA`, `WS`, and `rf_const`, **with overlapping fields**.
![../../.pic/Labs/lab_04_cybercobra/ppd_code_2.png](../../.pic/Labs/lab_04_cybercobra/ppd_code_2.png)
_Таблица 2. Добавление кодирования источника записи и 23-битной константы._
_Table 2. Adding write source encoding and a 23-bit constant._
``` C
reg_file[WA] ← rf_const
```
На _рис. 2_ приводится фрагмент микроархитектуры, поддерживающий вычислительные операции на АЛУ и загрузку констант из инструкции в регистровый файл.
_Fig. 2_ shows the microarchitecture fragment supporting ALU computational operations and loading constants from the instruction into the register file.
Так как вход записи уже занят результатом операции АЛУ, его потребуется мультиплексировать со значением константы из инструкции, которая предварительно **знакорасширяется** в блоке `SE`. На входе `WD` регистрового файла появляется мультиплексор, управляемый 28-м битом инструкции, который и определяет, что будет записано: константа или результат вычисления на АЛУ.
Since the write input is already occupied by the ALU operation result, it must be multiplexed with the constant value from the instruction, which is first **sign-extended** in the `SE` block. A multiplexer controlled by bit 28 of the instruction appears at the `WD` input of the register file and determines what will be written: the constant or the ALU result.
Например, в такой реализации следующая 32-битная инструкция поместит константу `-1` в регистр по адресу `5`:
For example, in this implementation, the following 32-bit instruction places the constant `-1` into the register at address `5`:
```text
000 0 11111111111111111111111 00101
@@ -175,45 +175,45 @@ _Таблица 2. Добавление кодирования источник
![../../.pic/Labs/lab_04_cybercobra/ppd_2.drawio.svg](../../.pic/Labs/lab_04_cybercobra/ppd_2.drawio.svg)
_Рисунок 2. Добавление константы из инструкции в качестве источников записи в регистровый файл._
_Figure 2. Adding a constant from the instruction as a write source for the register file._
### Реализация загрузки в регистровый файл данных с внешних устройств
### Implementing External Device Data Load into the Register File
Чтобы процессор мог взаимодействовать с внешним миром добавим возможность загрузки данных с внешних устройств в регистр по адресу `WA`. Появляется третий тип инструкции, который определяет третий источник ввода для регистрового файла. Одного бита `WS` для выбора одного из трех источников будет недостаточно, поэтому расширим это поле до 2 бит. Теперь, когда `WS == 0` будет загружаться константа, когда `WS == 1` будет загружаться результат вычисления АЛУ, а при `WS == 2` будут загружаться данные с внешних устройств. Остальные поля (кроме `WA`) в данной инструкции не используются.
To allow the processor to interact with the outside world, let us add the ability to load data from external devices into the register at address `WA`. A third instruction type appears, defining a third input source for the register file. A single `WS` bit is not enough to select among three sources, so the field is extended to 2 bits. Now, when `WS == 0`, a constant is loaded; when `WS == 1`, the ALU computation result is loaded; and when `WS == 2`, data from external devices is loaded. All other fields (except `WA`) are unused in this instruction.
![../../.pic/Labs/lab_04_cybercobra/ppd_code_3.png](../../.pic/Labs/lab_04_cybercobra/ppd_code_3.png)
_Таблица 3. Кодирование в инструкции большего числа источников записи._
_Table 3. Encoding a larger number of write sources in the instruction._
``` C
reg_file[WA] ← sw_i
```
На _рис. 3_ приводится фрагмент микроархитектуры, поддерживающий вычислительные операции на АЛУ, загрузку констант из инструкции в регистровый файл и загрузку данных с внешних устройств.
_Fig. 3_ shows the microarchitecture fragment supporting ALU computational operations, constant loads from the instruction into the register file, and data loads from external devices.
По аналогии с загрузкой констант увеличиваем входной мультиплексор до 4 входов и подключаем к нему управляющие сигналы `[29:28]` биты инструкции. Последний вход используется, чтобы разрешить неопределённость на выходе при `WS == 3`(`default`-вход, см. [мультиплексор](../../Basic%20Verilog%20structures/Multiplexors.md)).
By analogy with constant loading, the input multiplexer is expanded to 4 inputs and connected to the control signals — bits `[29:28]` of the instruction. The last input is used to resolve the output ambiguity when `WS == 3` (the `default` input; see [multiplexer](../../Basic%20Verilog%20structures/Multiplexors.md)).
Выход модуля `out_o` подключается к первому порту на чтение регистрового файла. Значение на выходе `out_o` будет определяться содержимым ячейки памяти по адресу `RA1`.
The `out_o` output of the module is connected to the first read port of the register file. The value at the `out_o` output is determined by the contents of the register file cell at address `RA1`.
![../../.pic/Labs/lab_04_cybercobra/ppd_3.drawio.svg](../../.pic/Labs/lab_04_cybercobra/ppd_3.drawio.svg)
_Рисунок 3. Подключение к схеме источников ввода и вывода._
_Figure 3. Connecting input and output sources to the schematic._
### Реализация условного перехода
### Implementing Conditional Branch
С реализованным набором инструкций полученное устройство нельзя назвать процессором пока что это продвинутый калькулятор. Добавим поддержку инструкции условного перехода, при выполнении которой программа будет перепрыгивать через заданное количество команд. Чтобы аппаратура отличала эту инструкцию от других будем использовать 30-ый бит `B` (`branch`). Если `B == 1`, значит это инструкция условного перехода и, если условие перехода выполняется, к `PC` надо прибавить константу. Если `B == 0`, значит это какая-то другая инструкция и к `PC` надо прибавить 4.
With the current instruction set, the resulting device cannot be called a processor — at this point it is an advanced calculator. Let us add support for a conditional branch instruction, which causes the program to skip over a specified number of instructions. To distinguish this instruction from others, bit 30 `B` (`branch`) is used. If `B == 1`, this is a conditional branch instruction and, if the branch condition is met, the constant must be added to `PC`. If `B == 0`, this is some other instruction and 4 must be added to `PC`.
![../../.pic/Labs/lab_04_cybercobra/ppd_code_4.png](../../.pic/Labs/lab_04_cybercobra/ppd_code_4.png)
_Таблица 4.Кодирование условного перехода._
_Table 4. Conditional branch encoding._
Для вычисления результата условного перехода, нам необходимо выполнить операцию на АЛУ и посмотреть на сигнал `flag`. Если он равен 1, переход выполняется, в противном случае — не выполняется. А значит, нам нужны операнды `A`, `B` и `alu_op`. Кроме того, нам необходимо указать насколько мы сместимся относительно текущего значения `PC` (константу смещения, `offset`). Для передачи этой константы лучше всего подойдут незадействованные биты инструкции `[12:5]`.
To evaluate the result of a conditional branch, we need to perform an ALU operation and check the `flag` signal. If it equals 1, the branch is taken; otherwise it is not. This requires operands `A`, `B`, and `alu_op`. In addition, we need to specify by how much to offset relative to the current value of `PC` (the offset constant). Unused instruction bits `[12:5]` are best suited for passing this constant.
Обратим внимание на то, что `PC` 32-битный и должен быть всегда кратен четырем (`PC` не может указывать кроме как в начало инструкции, а каждая инструкция длиной в 32 бита). Кратные четырем двоичные числа всегда будут иметь в конце два нуля (так же, как и кратные ста десятичные числа). Поэтому для более эффективного использования битов константы смещения, эти два нуля будут неявно подразумеваться при её описании. При этом, перед увеличением программного счётчика на значение константы смещения, эти два нуля нужно будет к ней приклеить справа. Кроме того, чтобы разрядность константы совпадала с разрядностью `PC`, необходимо знакорасширить её до 32 бит.
Note that `PC` is 32 bits wide and must always be a multiple of four (`PC` can only point to the start of an instruction, and each instruction is 32 bits long). Binary numbers that are multiples of four always end in two zeros (just as decimal numbers that are multiples of one hundred). Therefore, to make more efficient use of the offset constant bits, these two zeros are implicitly assumed in the encoding. Before adding the offset constant to the program counter, these two zeros must be appended to the right. Additionally, to match the bit width of `PC`, the constant must be sign-extended to 32 bits.
Предположим, мы хотим переместиться на две инструкции вперед. Это означает, что программный счётчик должен будет увеличиться на 8 ([2 инструкции] * [4 байта — размер одной инструкции в памяти]). Умножение константы смещения на 4 произойдет путем добавления к ней двух нулей справа, поэтому в поле `offset` мы просто записываем число инструкций, на которое мы переместим программный счётчик (на две): `0b00000010`.
Suppose we want to jump forward by two instructions. This means the program counter must increase by 8 ([2 instructions] × [4 bytes — size of one instruction in memory]). Multiplying the offset constant by 4 happens by appending two zeros to the right, so in the `offset` field we simply write the number of instructions to jump (two): `0b00000010`.
Данный Си-подобный псевдокод (далее мы назовем его псевдоассемблером) демонстрирует кодирование инструкций с новым полем `B`:
The following C-like pseudocode (referred to hereafter as pseudo-assembly) demonstrates instruction encoding with the new `B` field:
``` C
if (reg_file[RA1] {alu_op} reg_file[RA2])
@@ -222,94 +222,94 @@ _Таблица 4.Кодирование условного перехода._
PC ← PC + 4
```
Так как второй вход сумматора счётчика команд занят числом 4, то для реализации условного перехода этот вход надо мультиплексировать с константой. Мультиплексор при этом управляется 30-ым битом `B`, который и определяет, что будет прибавляться к `PC`.
Since the second input of the program counter adder is already occupied by the value 4, this input must be multiplexed with the constant to implement a conditional branch. The multiplexer is controlled by bit 30 `B`, which determines what is added to `PC`.
Сигнальные линии, которые управляют АЛУ и подают на его входы операнды уже существуют. Поэтому на схему необходимо добавить только логику управления мультиплексором на входе сумматора счётчика команд. Эта логика работает следующим образом:
The signal lines that control the ALU and supply its operands already exist. Therefore, only the control logic for the multiplexer at the program counter adder input needs to be added to the schematic. This logic operates as follows:
1. если сейчас инструкция условного перехода
2. и если условие перехода выполнилось,
1. if the current instruction is a conditional branch
2. and if the branch condition is satisfied,
то к `PC` прибавляется знакорасширенная константа, умноженная на 4. В противном случае, к `PC` прибавляется 4.
then the sign-extended constant multiplied by 4 is added to `PC`. Otherwise, 4 is added to `PC`.
Так как теперь не любая инструкция приводит к записи в регистровый файл, появляется необходимость управлять входом `WE` так, чтобы при операциях условного перехода запись в регистровый файл не производилась. Это можно сделать, подав на WE значение `!B` (запись происходит, если сейчас **не операция условного перехода**).
Since not every instruction now leads to a write to the register file, the `WE` input must be controlled so that no write to the register file occurs during conditional branch operations. This can be done by driving `WE` with `!B` (a write occurs only when the current instruction is **not a conditional branch**).
![../../.pic/Labs/lab_04_cybercobra/ppd_4.drawio.svg](../../.pic/Labs/lab_04_cybercobra/ppd_4.drawio.svg)
_Рисунок 4. Реализация условного перехода._
_Figure 4. Implementing the conditional branch._
### Реализация безусловного перехода
### Implementing Unconditional Branch
Осталось добавить поддержку инструкции безусловного перехода, для идентификации которой используется оставшийся 31-ый бит `J`(jump). Если бит `J == 1`, то это безусловный переход, и мы прибавляем к `PC` знакорасширенную константу смещения, умноженную на 4 (как это делали и в условном переходе).
All that remains is to add support for the unconditional branch instruction, identified by the remaining bit 31 `J` (jump). If bit `J == 1`, this is an unconditional branch, and we add the sign-extended offset constant multiplied by 4 to `PC` (exactly as done for conditional branch).
![../../.pic/Labs/lab_04_cybercobra/ppd_code_5.png](../../.pic/Labs/lab_04_cybercobra/ppd_code_5.png)
_Таблица 5. Кодирование безусловного перехода._
_Table 5. Unconditional branch encoding._
``` C
PC ← PC + const*4
```
Для реализации безусловного перехода, нам необходимо добавить дополнительную логику управления мультиплексором перед сумматором. Итоговая логика его работы звучит так:
To implement the unconditional branch, additional control logic for the multiplexer before the adder must be added. The final logic operates as follows:
1. если сейчас инструкция безусловного перехода, _или_
2. если сейчас инструкция условного перехода _и_ условие перехода выполнилось,
1. if the current instruction is an unconditional branch, _or_
2. if the current instruction is a conditional branch _and_ the branch condition is satisfied,
то к `PC` прибавляется знакорасширенная константа, умноженная на 4. В противном случае, к `PC` прибавляется 4.
then the sign-extended constant multiplied by 4 is added to `PC`. Otherwise, 4 is added to `PC`.
Кроме того, при безусловном переходе в регистровый файл также ничего не пишется. А значит, необходимо обновить логику работы сигнала разрешения записи `WE`, который будет равен 0 если сейчас инструкция условного или безусловного перехода.
In addition, during an unconditional branch, nothing is written to the register file either. Therefore, the write-enable signal `WE` logic must be updated: `WE` equals 0 if the current instruction is a conditional or unconditional branch.
На _рис. 5_ приводится итоговый вариант микроархитектуры процессора `CYBERcobra`.
_Fig. 5_ shows the final microarchitecture of the `CYBERcobra` processor.
![../../.pic/Labs/lab_04_cybercobra/ppd_5.drawio.svg](../../.pic/Labs/lab_04_cybercobra/ppd_5.drawio.svg)
_Рисунок 5. Реализация безусловного перехода._
_Figure 5. Implementing the unconditional branch._
### Финальный обзор
### Final Overview
Итого, архитектура `CYBERcobra` поддерживает 5 типов инструкций, которые кодируются следующим образом (символами `x` помечены биты, которые не задействованы в данной инструкции):
In total, the `CYBERcobra` architecture supports 5 instruction types, encoded as follows (bits marked `x` are unused in the given instruction):
1. 10 вычислительных инструкций `0 0 01 alu_op RA1 RA2 xxxx xxxx WA`
2. Инструкция загрузки константы `0 0 00 const WA`
3. Инструкция загрузки из внешних устройств `0 0 10 xxx xxxx xxxx xxxx xxxx xxxx WA`
4. Безусловный переход `1 x xx xxx xxxx xxxx xxxx offset xxxxx`
5. 6 инструкций условного перехода `0 1 xx alu_op RA1 RA2 offset xxxxx`
1. 10 computational instructions: `0 0 01 alu_op RA1 RA2 xxxx xxxx WA`
2. Load constant instruction: `0 0 00 const WA`
3. Load from external devices instruction: `0 0 10 xxx xxxx xxxx xxxx xxxx xxxx WA`
4. Unconditional branch: `1 x xx xxx xxxx xxxx xxxx offset xxxxx`
5. 6 conditional branch instructions: `0 1 xx alu_op RA1 RA2 offset xxxxx`
При кодировании инструкций используются следующие поля:
The following fields are used when encoding instructions:
- J 1-битный сигнал, указывающий на выполнение безусловного перехода;
- B 1-битный сигнал, указывающий на выполнение условного перехода;
- WS 2-битный сигнал, указывающий источник данных для записи в регистровый файл:
- 0 константа из инструкции;
- 1 результат с АЛУ;
- 2 внешние данные;
- 3 не используется;
- alu_op 5-битный сигнал кода операции АЛУ (в соответствии с таблицей из лабораторной по АЛУ);
- RA1 и RA2 5-битные адреса операндов из регистрового файла;
- offset 8-битная константа для условного / безусловного перехода;
- const — 23-битная константа для загрузки в регистровый файл;
- WA 5-битный адрес регистра, в который будет записан результат.
- J 1-bit signal indicating an unconditional branch;
- B 1-bit signal indicating a conditional branch;
- WS 2-bit signal indicating the data source for writing to the register file:
- 0 — constant from the instruction;
- 1 — result from the ALU;
- 2 — external data;
- 3 — unused;
- alu_op 5-bit ALU operation code (as defined in the ALU lab table);
- RA1 and RA2 5-bit addresses of operands in the register file;
- offset 8-bit constant for conditional/unconditional branch;
- const — 23-bit constant for loading into the register file;
- WA 5-bit address of the register where the result will be written.
Напишем простую программу для этого процессора, которая циклично увеличивает значение первого регистра на 1 до тех пор, пока его значение не превысит число, введенное на переключателях. Сначала напишем программу на псевдоассемблере (используя предложенную мнемонику):
Let us write a simple program for this processor that cyclically increments the value of the first register by 1 until it exceeds the number entered on the switches. First, the program is written in pseudo-assembly (using the proposed mnemonics):
``` C
reg_file[1] ← -1 // загрузить константу -1 в регистр 1
reg_file[2] ← sw_i // загрузить значение с входа sw_i в регистр 2
reg_file[3] ← 1 // загрузить константу 1 в регистр 3
reg_file[1] ← -1 // load constant -1 into register 1
reg_file[2] ← sw_i // load value from input sw_i into register 2
reg_file[3] ← 1 // load constant 1 into register 3
reg_file[1] ← reg_file[1] + reg_file[3] // сложить регистр 1 с регистром 3 и
// поместить результат в регистр 1
reg_file[1] ← reg_file[1] + reg_file[3] // add register 1 and register 3 and
// store the result in register 1
if (reg_file[1] < reg_file[2]) // если значение в регистре 1 меньше
// значения в регистре 2,
PC ← PC + (-1) // возврат на 1 инструкцию назад
if (reg_file[1] < reg_file[2]) // if the value in register 1 is less than
// the value in register 2,
PC ← PC + (-1) // go back 1 instruction
out_o = reg_file[1], PC ← PC + 0 // бесконечное повторение этой инструкции
// с выводом на out_o значения в регистре 1
out_o = reg_file[1], PC ← PC + 0 // infinite repetition of this instruction
// with output of register 1 value on out_o
```
_Листинг 1. Пример программы для CYBERcobra._
_Listing 1. Example program for CYBERcobra._
Теперь в соответствии с кодировкой инструкций переведем программу в машинные коды:
Now, according to the instruction encoding, the program is translated into machine codes:
```text
0 0 00 11111111111111111111111 00001
@@ -319,26 +319,26 @@ _Листинг 1. Пример программы для CYBERcobra._
0 1 00 11110 00001 00010 11111111 00000
1 0 00 00000 00001 00000 00000000 00000
```
_Листинг 2. Представление листинга 1 в виде машинных кодов._
_Listing 2. Listing 1 represented in machine codes._
Полученную программу можно помещать в память программ и выполнять на процессоре.
The resulting program can be placed in program memory and executed on the processor.
## Инструменты для реализации процессора
## Tools for Processor Implementation
Так как все модули процессора написаны, основная часть кода описания процессора будет связана с подключением этих модулей друг к другу. Подробнее о подключении модулей сказано в [Modules.md](../../Basic%20Verilog%20structures/Modules.md).
Since all processor modules have been written, the main part of the processor description code will involve connecting these modules to each other. More details about module instantiation are given in [Modules.md](../../Basic%20Verilog%20structures/Modules.md).
Для реализации блоков знакорасширения с умножением на 4 подходит использование оператора конкатенации ([Concatenation.md](../../Basic%20Verilog%20structures/Concatenation.md)).
The concatenation operator ([Concatenation.md](../../Basic%20Verilog%20structures/Concatenation.md)) is suitable for implementing sign-extension blocks with multiplication by 4.
## Задание по реализации процессора
## Assignment
Разработать процессор `CYBERcobra` (см. [_рис. 5_](../../.pic/Labs/lab_04_cybercobra/ppd_5.drawio.svg)), объединив ранее разработанные модули:
Develop the `CYBERcobra` processor (see [_Fig. 5_](../../.pic/Labs/lab_04_cybercobra/ppd_5.drawio.svg)) by combining the previously developed modules:
- Память инструкций (проинициализированную в двоичном формате файлом [`program.mem`](program.mem))
- Регистровый файл
- Арифметико-логическое устройство
- 32-битный сумматор
- Instruction memory (initialized in binary format with the file [`program.mem`](program.mem))
- Register file
- Arithmetic Logic Unit
- 32-bit adder
Кроме того, необходимо описать регистр счётчика команд и логику его работы, в соответствии с ранее представленной микроархитектурой.
In addition, the program counter register and its operating logic must be described in accordance with the microarchitecture presented above.
```Verilog
module CYBERcobra (
@@ -351,32 +351,32 @@ module CYBERcobra (
endmodule
```
## Порядок выполнения задания
## Steps
1. Добавьте в `Design Sources` проекта файл [program.mem](program.mem), содержащий программу из Листинга 1.
2. Опишите модуль `CYBERcobra` с таким же именем и портами, как указано в задании (обратите внимание на регистр имени модуля).
1. В первую очередь, необходимо создать счётчик команд и все вспомогательные провода. При создании, **следите за разрядностью**.
2. Затем, необходимо создать экземпляры модулей: памяти инструкции, АЛУ, регистрового файла и сумматора. При подключении сигналов сумматора, надо **обязательно** подать нулевое значение на входной бит переноса. Выходной бит переноса подключать не обязательно. Объекту памяти инструкций нужно дать имя `imem`.
3. После этого, необходимо описать оставшуюся логику:
1. Программного счётчика. Счётчик должен сбрасываться, когда сигнал _rst_i == 1_.
2. Сигнала управления мультиплексором, выбирающим слагаемое для программного счётчика
3. Сигнала разрешения записи в регистровый файл
4. Мультиплексор, выбирающий слагаемое для программного счётчика
5. Мультиплексор, выбирающий источник записи в регистровый файл.
3. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_04.tb_cybercobra.sv`](lab_04.tb_cybercobra.sv).
1. Перед запуском моделирования убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`.
2. В этот раз в конце не будет сообщения о том, работает ли ваше устройство или в нём есть ошибки. Вам необходимо самостоятельно проверить работу модуля, перенеся его внутренние сигналы на временную диаграмму, и [изучив](../../Vivado%20Basics/05.%20Bug%20hunting.md) их поведение.
3. По сути, проверка сводится к потактовому изучению временной диаграммы, во время которого вам нужно циклично ответить на следующие вопросы (после чего необходимо сравнить предсказанный ответ со значением сигналов на временной диаграмме):
1. Какое сейчас значение программного счётчика?
2. Какая инструкция должна быть считана при данном значении программного счётчика?
3. Как должно обновиться содержимое регистрового файла в результате выполнения этой инструкции: должно ли записаться какое-либо значение? Если да, то какое и по какому адресу?
4. Как должен измениться программный счётчик после выполнения этой инструкции?
4. Проверьте работоспособность вашей цифровой схемы в ПЛИС.
1. Add the file [program.mem](program.mem), containing the program from Listing 1, to the `Design Sources` of the project.
2. Describe the `CYBERcobra` module with the same name and ports as specified in the task (pay attention to the case of the module name).
1. First, create the program counter and all auxiliary wires. When doing so, **pay attention to bit widths**.
2. Then, instantiate the modules: instruction memory, ALU, register file, and adder. When connecting the adder signals, you **must** drive the carry-in with zero. The carry-out does not need to be connected. The instruction memory instance must be named `imem`.
3. After that, describe the remaining logic:
1. Program counter. The counter must reset when _rst_i == 1_.
2. Control signal for the multiplexer that selects the addend for the program counter.
3. Write-enable signal for the register file.
4. Multiplexer that selects the addend for the program counter.
5. Multiplexer that selects the write source for the register file.
3. Verify the module using the testbench provided in the file [`lab_04.tb_cybercobra.sv`](lab_04.tb_cybercobra.sv).
1. Before running the simulation, make sure the correct top-level module is selected in `Simulation Sources`.
2. This time there will be no message at the end indicating whether the device works correctly or contains errors. You must verify the module operation independently by adding its internal signals to the waveform and [examining](../../Vivado%20Basics/05.%20Bug%20hunting.md) their behavior.
3. Essentially, verification comes down to a cycle-by-cycle study of the waveform, during which you must repeatedly answer the following questions (and then compare the predicted answer with the signal values on the waveform):
1. What is the current value of the program counter?
2. Which instruction should be fetched at this program counter value?
3. How should the register file contents be updated as a result of executing this instruction: should any value be written? If so, what value and at which address?
4. How should the program counter change after executing this instruction?
4. Verify the operation of your digital circuit on the FPGA.
---
После выполнения задания по реализации процессора, необходимо также выполнить [индивидуальное задание](Индивидуальное%20задание) по написанию двоичной программы под созданный вами процессор.
After completing the processor implementation task, you must also complete the [individual assignment](Индивидуальное%20задание) of writing a binary program for the processor you have created.
---
Дерзайте!
Good luck!

View File

@@ -1,67 +1,67 @@
# Проверка работы CYBERcobra на ПЛИС
# Testing CYBERcobra on FPGA
После того, как вы проверили на моделировании дизайн, вам необходимо проверить его работу на прототипе в ПЛИС.
After verifying the design in simulation, you need to test it on an FPGA prototype.
Инструкция по реализации прототипа описана [здесь](../../../Vivado%20Basics/07.%20Program%20and%20debug.md).
Instructions for implementing the prototype are described [here](../../../Vivado%20Basics/07.%20Program%20and%20debug.md).
На _рис. 1_ представлена схема прототипа в ПЛИС.
_Fig. 1_ shows the prototype schematic on the FPGA.
![../../../.pic/Labs/board%20files/nexys_cobra_structure.drawio.svg](../../../.pic/Labs/board%20files/nexys_cobra_structure.drawio.svg)
_Рисунок 1. Структурная схема модуля `nexys_CYBERcobra`._
_Figure 1. Block diagram of the `nexys_CYBERcobra` module._
Прототип позволяет потактово исполнять программу, прошитую в память инструкций. Также прототип отображает операцию исполняемую в данный момент.
The prototype allows you to execute the program stored in the instruction memory one clock cycle at a time. It also displays the operation currently being executed by the processor.
> [!NOTE]
> Объект модуля `instr_mem` в модуле `CYBERcobra` **должен** называться `imem`. Т.е. строка создания сущности модуля должна выглядеть следующим образом: `instr_mem imem(...)`.
> The instance of the `instr_mem` module inside `CYBERcobra` **must** be named `imem`. That is, the instantiation line must look like this: `instr_mem imem(...)`.
## Описание используемой периферии
## Peripheral Description
- ### Переключатели.
- ### Switches
Значение с переключателей `SW[15:0]` подаются напрямую на порт `sw_i` модуля дизайна.
The values of switches `SW[15:0]` are passed directly to the `sw_i` port of the design module.
- ### Кнопки
- ### Buttons
- `BTND`при нажатии создает тактовый импульс, поступающий на порт тактирования `clk_i` модуля дизайна.
- `CPU_RESET`соединен со входом `rst_i` модуля дизайна. Поскольку в модуле `CYBERcobra` используется синхронный сброс (то есть сигнал сброса учитывается только во время восходящего фронта тактового сигнала), то для сброса модуля `CYBERcobra` и вложенных в него модулей необходимо при зажатой кнопке сброса еще нажать кнопку тактирования.
- `BTND`pressing this button generates a clock pulse delivered to the `clk_i` port of the design module.
- `CPU_RESET`connected to the `rst_i` input of the design module. Since `CYBERcobra` uses synchronous reset (i.e., the reset signal is only recognized on a rising clock edge), resetting the `CYBERcobra` module and its sub-modules requires holding down the reset button and then pressing the clock button.
- ### Светодиоды
- ### LEDs
Светодиоды `LED[15:0]` отображают младшие 16 бит значения, выставленного в данный момент на порте `out_o` модуля дизайна.
LEDs `LED[15:0]` display the lower 16 bits of the value currently present on the `out_o` port of the design module.
- ### Семисегментные индикаторы
- ### Seven-Segment Displays
Семисегментные индикаторы разбиты на 3 блока (см. _рис. 1_):
The seven-segment displays are divided into 3 blocks (see _Fig. 1_):
- `out`отображают младшие 8 бит значения, выставленного в данный момент на порте `out_o` модуля дизайна, в виде шестнадцатеричного числа.
- `PC`отображают в виде шестнадцатеричного числа младшие 8 бит программного счетчика, который подается на вход `addr_i` модуля памяти инструкций.
- `operation`отображают [операцию](#Операции-отображаемые-прототипом), исполняемую процессором на текущем такте.
- `out`displays the lower 8 bits of the value on the `out_o` port of the design module as a hexadecimal number.
- `PC`displays the lower 8 bits of the program counter, which is driven to the `addr_i` input of the instruction memory module, as a hexadecimal number.
- `operation`displays the [operation](#Operations-displayed-by-the-prototype) currently being executed by the processor.
## Операции, отображаемые прототипом
## Operations Displayed by the Prototype
Соответствие типа инструкции отображаемой операции:
Mapping of instruction types to displayed operations:
1. Вычислительные — соответствует опкодам вычислительных операций АЛУ.
1. Инструкция загрузки константы`LI` (от **l**oad **i**mmediate).
1. Инструкция загрузки из внешних устройств`IN` (от **in**put).
1. Безусловный переход`JUMP`.
1. Инструкций условного перехода — соответствует опкодам операций сравнения АЛУ.
1. Computational instructions — corresponds to the opcodes of ALU computational operations.
1. Load-immediate instruction`LI` (from **l**oad **i**mmediate).
1. Load-from-external-device instruction`IN` (from **in**put).
1. Unconditional jump`JUMP`.
1. Conditional branch instructions — corresponds to the opcodes of ALU comparison operations.
Во время исполнения вычислительных инструкций и инструкций условного перехода могут встретиться нелегальные операции (отображается как `ILL`, от **ill**egal). Операция считается нелегальной в следующих случаях:
During the execution of computational instructions and conditional branch instructions, illegal operations may be encountered (displayed as `ILL`, from **ill**egal). An operation is considered illegal in the following cases:
- Если в поле инструкции, отвечающем за операция АЛУ, указана битовая последовательность, не входящая в диапазон допустимых значений.
- Если инструкция является вычислительной, но в поле инструкции, отвечающем за операция АЛУ, указана битовая последовательность, соответствующая операции, вычисляющей флаг. И обратный случай.
- If the ALU operation field of the instruction contains a bit pattern that is not within the range of valid values.
- If the instruction is a computational instruction but the ALU operation field contains a bit pattern corresponding to a flag-computing operation, or vice versa.
Инструкция `0 0 11 xxxxxxxxxxxxxxxxxxxxxxxxxxxx` отображается как `NOP` (от **n**o **op**eration).
The instruction `0 0 11 xxxxxxxxxxxxxxxxxxxxxxxxxxxx` is displayed as `NOP` (from **n**o **op**eration).
Соответствие операции ее отображению на семисегментных индикаторах представлено на _рис. 2_:
The mapping of operations to their representation on the seven-segment displays is shown in _Fig. 2_:
!['../../../.pic/Labs/board%20files/nexys_cobra_operations.drawio.svg'](../../../.pic/Labs/board%20files/nexys_cobra_operations.drawio.svg)
_Рисунок 2. Соответствие операции ее отображению на семисегментных индикаторах._
_Figure 2. Mapping of operations to their representation on the seven-segment displays._
## Демонстрационная программа
## Demo Program
В качестве демонстрационной программы, предлагается использовать [program.mem](../program.mem). Описание ее работы можно прочитать в разделе [#финальный обзор](../#Финальный-обзор).
The recommended demo program is [program.mem](../program.mem). A description of how it works can be found in the [#final-overview](../#Final-overview) section.

View File

@@ -1,35 +1,35 @@
# Написание программы под процессор CYBERcobra
# Writing a Program for the CYBERcobra Processor
Чтобы максимально "прочувствовать" принцип работы созданного вами процессора, вам необходимо написать один из [вариантов программ](#Индивидуальные-задания). Вариант выдаётся преподавателем.
To fully grasp the principles behind the processor you have built, you need to write one of the [individual assignments](#individual-assignments). The variant is assigned by the instructor.
Порядок выполнения задания следующий:
The procedure is as follows:
1. В первую очередь необходимо ознакомиться с заданием и изучить пример, приведённый в конце задания. Если возникли вопросы по заданию или примеру, свяжитесь с преподавателем. Чем лучше вы понимаете, что от вас ожидают, тем проще будет выполнить задание.
2. Составьте алгоритм работы программы (буквально возьмите листочек, и нарисуйте блок-схему). Прежде чем вы погрузитесь в увлекательное приключение с ноликами и единицами, вам нужно составить четкую карту вашего путешествия.
3. Проверьте вашу блок-схему на данных из примера. Если все сошлось, проверьте вашу блок-схему на других данных. Не забывайте про краевые случаи (отрицательные числа, деление на ноль, переполнения и прочее — для каждого задания они могут быть разными).
4. После того как вы убедились в работоспособности алгоритма на всех возможных данных, наступает время претворить его в виде двоичной программы.
5. Программа описывается в текстовом файле. Для удобства был написан специальный конвертер, который будет принимать на вход текстовый файл с комментариями и двоичным кодом, разделённым пробелами, а на выход выдавать текстовый файл, которым можно будет проинициализировать память инструкций. Подробнее о конвертере смотрите в параграфе [cyberconverter](#cyberconverter). Пример текстового файла, который сможет принять конвертер:
1. First, read the assignment carefully and study the example provided at the end. If you have any questions about the assignment or the example, contact the instructor. The better you understand what is expected of you, the easier it will be to complete the task.
2. Design the program algorithm (literally take a sheet of paper and draw a flowchart). Before you dive into the exciting adventure of zeros and ones, you need to draw a clear map of your journey.
3. Verify your flowchart against the data from the example. If everything matches, check your flowchart against other data. Do not forget about edge cases (negative numbers, division by zero, overflows, etc. — these may differ for each assignment).
4. Once you have confirmed that the algorithm works for all possible inputs, it is time to implement it as a binary program.
5. The program is described in a text file. For convenience, a special converter has been written that takes a text file containing comments and binary code separated by spaces as input, and produces a text file that can be used to initialize the instruction memory. For more details on the converter, see the [cyberconverter](#cyberconverter) section. An example of a text file that the converter can accept:
```text
//J B WS ALUop RA1 RA2 const WA
0 0 00 11111111111111111111111 00001 // загрузить константу -1 в регистр 1
0 0 10 00000000000000000000000 00010 // загрузить значение с входа sw_i в регистр 2
0 0 00 00000000000000000000001 00011 // загрузить константу 1 регистр 3
0 0 01 00000 00001 00011 00000000 00001 // сложить регистр 1 с регистром 3 и поместить результат в регистр 1
0 1 00 11110 00001 00010 11111111 00000 // если значение в регистре 1 меньше значения в регистре 2, возврат на 1 инструкцию назад
1 0 00 00000 00001 00000 00000000 00000 // бесконечное повторение этой инструкции с выводом на out_o значения в регистре 1
0 0 00 11111111111111111111111 00001 // load constant -1 into register 1
0 0 10 00000000000000000000000 00010 // load value from sw_i input into register 2
0 0 00 00000000000000000000001 00011 // load constant 1 into register 3
0 0 01 00000 00001 00011 00000000 00001 // add register 1 and register 3, store result in register 1
0 1 00 11110 00001 00010 11111111 00000 // if the value in register 1 is less than the value in register 2, go back 1 instruction
1 0 00 00000 00001 00000 00000000 00000 // repeat this instruction indefinitely, outputting the value of register 1 to out_o
```
6. При реализации условных переходов следует иметь в виду пару правил:
1. Блок ветвления (аналог `if/else`) состоит из двух наборов инструкций:
1. инструкции блока `if` (если условие выполнилось)
2. инструкции блока `else` (если условие не выполнилось)
6. When implementing conditional branches, keep the following rules in mind:
1. A branch block (analogous to `if/else`) consists of two sets of instructions:
1. instructions of the `if` block (executed when the condition is true)
2. instructions of the `else` block (executed when the condition is false)
При этом сразу за инструкцией ветвления описываются инструкции блока `else` (т.к. в случае невыполнения условия перехода, `PC` перейдёт к следующей инструкции).
Для того, чтобы после выполнения инструкций блока `else` не начались исполняться инструкции блока `if`, в конце блока этих инструкций необходимо добавить безусловный переход на инструкцию, следующую за инструкциями блока `if`.
2. Если вы реализуете не ветвление (аналог блока `if/else`), а только проверку условия для выполнения какого-то блока инструкций (аналог блока `if` без блока `else`), вы должны помнить, что блок `else` всё равно есть, просто в этом блоке нет инструкций. Однако вы, как и в прошлом правиле, должны добавить безусловный переход на инструкцию, следующую за инструкциями блока `if`.
Этого можно избежать, если инвертировать ваше условие. В этом случае, если ваше инвертированное условие выполнится, вы сможете сразу пропустить нужное количество инструкций и начать исполнять инструкцию за пределами вашего блока `if`. Если инвертированное условие не выполнится (т.е. выполнится исходное условие), `PC` перейдёт к следующей инструкции, где и будут находиться ваши инструкции блока `if`.
Звучит достаточно запутанно, поэтому давайте рассмотрим пару примеров. Сначала мы запишем нашу идею на языке Си, а затем перенесём её в двоичный код под архитектуру CYBERcobra:
The instructions of the `else` block immediately follow the branch instruction (because when the branch condition is not taken, `PC` moves to the next instruction).
To prevent the `if` block instructions from being executed after the `else` block completes, an unconditional jump to the instruction following the `if` block must be added at the end of the `else` block.
2. If you are implementing not a branch (analogous to `if/else`) but only a condition check for a block of instructions (analogous to an `if` block without an `else`), remember that the `else` block still exists — it simply contains no instructions. However, as in the previous rule, you must still add an unconditional jump to the instruction following the `if` block.
This can be avoided by inverting your condition. In that case, if the inverted condition is true, you can immediately skip the required number of instructions and begin executing the instruction beyond your `if` block. If the inverted condition is false (i.e., the original condition is true), `PC` will move to the next instruction, where your `if` block instructions will be located.
This sounds somewhat confusing, so let us look at a couple of examples. We will first write the idea in C, then translate it into binary code for the CYBERcobra architecture:
```C
if(reg[1]==reg[5])
@@ -40,25 +40,25 @@
else
{
reg[2] = 7;
goto if_end; // Поскольку в памяти программы блок else будет идти
// сразу за инструкцией условного перехода, необходимо
// добавить в конце инструкцию безусловного перехода,
// чтобы не начать исполнять инструкции блока if.
goto if_end; // Since in program memory the else block follows
// immediately after the conditional branch instruction,
// an unconditional jump must be added at the end
// to avoid executing the if block instructions.
}
if_end:
```
Мы хотим проверить на равенство значения в регистровом файле по адресам `1` и `5`. Если это так, записать значения `10` и `15` по адресам `2` и `3` соответственно. В противном случае, записать значение `7` по адресу `2`.
Это можно реализовать следующей двоичной программой:
We want to check whether the values in the register file at addresses `1` and `5` are equal. If so, write values `10` and `15` to addresses `2` and `3` respectively. Otherwise, write value `7` to address `2`.
This can be implemented with the following binary program:
```text
//J B WS ALUop RA1 RA2 const WA
0 1 00 11000 00001 00101 00000011 00000 // Если регистры 1 и 5 равны,
// перемести PC на 3 инструкции вперёд
// (перешагни через две
// инструкции блока else)
0 1 00 11000 00001 00101 00000011 00000 // If registers 1 and 5 are equal,
// move PC 3 instructions forward
// (skip over two
// instructions of the else block)
//---------------------------------------
// блок else
// else block
//---------------------------------------
0 0 00 00000000000000000000111 00010 // reg[2] = 7
1 0 00 00000 00000 00000 00000011 00000 // goto if_end
@@ -66,18 +66,18 @@
//---------------------------------------
// блок if
// if block
//---------------------------------------
0 0 00 00000000000000000001010 00010 // reg[2] = 10
0 0 00 00000000000000000001111 00011 // reg[3] = 15
//---------------------------------------
0 0 00 00000000000000000000000 00000 // некая инструкция с меткой if_end
// куда будет перемещен PC после
// выполнения блока else
0 0 00 00000000000000000000000 00000 // some instruction at the if_end label,
// where PC will be moved after
// the else block executes
```
Рассмотрим второй пример, где нет блока `else`:
Let us consider the second example, where there is no `else` block:
```C
if(reg[1] == reg[5])
@@ -87,7 +87,7 @@
}
```
Как упоминалось ранее, можно реализовать этот условный переход по той же схеме (тогда пример программы на Си примет вид):
As mentioned earlier, this conditional branch can be implemented using the same scheme (in which case the C program looks like):
```C
if(reg[1] == reg[5])
@@ -102,7 +102,7 @@
if_end:
```
А можно инвертировать условие:
Alternatively, the condition can be inverted:
```C
if(reg[1] != reg[5])
@@ -116,202 +116,202 @@
}
```
В этом случае, нет нужды делать безусловный переход на инструкцию, следующую за инструкциями блока `if`, т.к. там нет никаких инструкций.
Такое условие можно реализовать следующей двоичной программой:
In this case, there is no need to add an unconditional jump to the instruction following the `if` block, since there are no instructions there.
This condition can be implemented with the following binary program:
```text
//J B WS ALUop RA1 RA2 const WA
0 1 00 11001 00010 00101 00000011 00000 // Если регистры 2 и 5 НЕ РАВНЫ,
// перемести PC на 3 инструкции вперед
// (перешагни через две
// инструкции блока else)
0 1 00 11001 00010 00101 00000011 00000 // If registers 2 and 5 are NOT EQUAL,
// move PC 3 instructions forward
// (skip over two
// instructions of the else block)
//---------------------------------------
// блок else
// else block
//---------------------------------------
0 0 00 00000000000000000001010 00010 // reg[2] = 7
0 0 00 00000000000000000001111 00011 // reg[3] = 15
//---------------------------------------
```
7. В двоичном программировании, реализация циклов лучше всего делается аналогом `do while` в Си (если вы уверены, что первая итерация цикла гарантированно пройдёт условие выхода из цикла). В этом случае, вы сперва описываете тело цикла, а затем через условный переход возвращаетесь обратно к началу тела цикла. Если условие не выполнилось, вы автоматически выйдете из цикла.
8. Для того, чтобы в конце выполнения программы было легко увидеть результат выполнения, в конец программы необходимо добавить инструкцию безусловного перехода, поле `const` которой равно нулю. В этом случае, будет выполняться `PC=PC+0` что приведёт к повторению этой инструкции снова и снова. При этом в поле `RA1` необходимо указать адрес регистра, где хранится результат. На временной диаграмме это отобразится так, что в какой-то момент все сигналы процессора "замрут", а на выходе `out_o` окажется результат, вычисленный вашей программой.
9. После того, как вы написали программу, её необходимо проверить. Для этого сперва необходимо преобразовать её к формату, принимаемому памятью инструкций с помощью программы [`cyberconverter`](#cyberconverter). При необходимости, заменить данные в файле, инициализирующем память инструкций актуальными данными.
10. Если ваша программа использует данные с внешних устройств, нужно выставить проверяемое вами значение в модуле `testbench` на вход `sw_i` в месте подключения модуля `CYBERcobra`.
11. Проверка работы программы осуществляется аналогично проверке модуля `CYBERcobra` — вы достаете внутренние сигналы модуля, и смотрите за поведением сигналов: `PC`, `read_data` памяти инструкций, `flag` АЛУ, содержимым регистрового файла. Проверяете, что в конце на выходе `out_o` размещено корректное значение.
7. In binary programming, loops are best implemented as the equivalent of `do while` in C (if you are certain that the first iteration will always satisfy the loop exit condition). In this case, you first describe the loop body, then use a conditional branch to return to the beginning of the loop body. If the condition is not satisfied, execution will automatically exit the loop.
8. To make it easy to see the result at the end of program execution, add an unconditional jump instruction at the end of the program with the `const` field equal to zero. This will execute `PC=PC+0`, causing that instruction to repeat indefinitely. Set the `RA1` field to the address of the register holding the result. On the waveform, this will appear as all processor signals "freezing" at some point, while the output `out_o` holds the result computed by your program.
9. After writing the program, it must be verified. To do this, first convert it to the format accepted by the instruction memory using the [`cyberconverter`](#cyberconverter) tool. If necessary, replace the data in the instruction memory initialization file with the updated data.
10. If your program uses data from external devices, set the value you want to test on the `sw_i` input of the `CYBERcobra` module in the `testbench`.
11. Program verification is performed in the same way as verifying the `CYBERcobra` module — you expose the internal signals of the module and observe the behavior of: `PC`, `read_data` of the instruction memory, `flag` of the ALU, and the contents of the register file. Verify that at the end of execution, the output `out_o` holds the correct value.
## cyberconverter
[cyberconverter](cyberconverter.cpp) — это программа, которая преобразует текстовый файл с инструкциями архитектуры CYBERcobra в текстовый файл, который сможет принять память инструкций.
[cyberconverter](cyberconverter.cpp) is a program that converts a text file containing CYBERcobra architecture instructions into a text file that can be used to initialize the instruction memory.
cyberconverter может обрабатывать файлы, содержащие комментарии (начинающиеся с `//`), пробелы и пустые строки, а также наборы символов `0` и `1`. Комментарии, пробелы и пустые строки удаляются, после чего оставшиеся строки из 32 нулей и единиц конвертируются в шестнадцатеричные значения и записываются в выходной файл.
cyberconverter can process files containing comments (starting with `//`), spaces and blank lines, as well as sequences of `0` and `1` characters. Comments, spaces, and blank lines are removed, after which the remaining 32-character binary strings are converted to hexadecimal values and written to the output file.
cyberconverter принимает до двух аргументов. Порядок запуска следующий:
cyberconverter accepts up to two arguments. The usage is as follows:
1. Вызов справки:
1. Display help:
```bash
cyberconverter -h
cyberconverter --help
```
2. Преобразование программы, записанной в файле `test.txt`, с записью результата в файл `program.mem`:
2. Convert a program stored in `test.txt` and write the result to `program.mem`:
```bash
cyberconverter test.txt program.mem
```
3. Если не указан второй аргумент, результат будет записан в файл:
`<имя_исходногоайла>_converted.<расширение исходного файла>`:
3. If the second argument is not specified, the result will be written to:
`<source_filename>_converted.<source_file_extension>`:
```bash
cyberconverter test.txt
```
Результат будет записан в файл `test_converted.txt`.
The result will be written to `test_converted.txt`.
4. Если программа будет запущена без аргументов, то исходным файлом будет считаться файл `program.mem`.
4. If the program is run without any arguments, the source file is assumed to be `program.mem`.
В случае отсутствия исходного файла, наличия неподдерживаемых символов или неверной длины инструкции будет выведено сообщение об ошибке.
If the source file is missing, contains unsupported characters, or has an incorrect instruction length, an error message will be displayed.
## Индивидуальные задания
## Individual Assignments
В приведённых ниже заданиях под `a` будет подразумеваться некоторое число, заданное в программе (например, в программе прописано `a=10`), под `sw_i` — вход с внешних устройств. "Вывести в `out_o`" — означает, что в конце программы необходимо реализовать бесконечный цикл, с указанием в `RA1` адреса регистра, хранящего результат (см. пункт 8 параграфа "[Написание программы под процессор CYBERcobra](#Написание-программы-под-процессор-cybercobra)").
In the assignments below, `a` refers to a number defined in the program (for example, the program sets `a=10`), and `sw_i` refers to the external device input. "Output to `out_o`" means that at the end of the program you must implement an infinite loop with `RA1` set to the address of the register holding the result (see step 8 of the "[Writing a Program for the CYBERcobra Processor](#writing-a-program-for-the-cybercobra-processor)" section).
В случае, если задание используется для написания программы на ассемблере, `sw_i` будет обозначать ещё одно число, заданное в программе (как и `a`), а под "Вывести в `out_o`" — запись результата в регистр `x10` (в назначение этого регистра входит возврат результата функции) в конце выполнения программы.
If the assignment is used for writing an assembly program, `sw_i` denotes another number defined in the program (like `a`), and "Output to `out_o`" means writing the result to register `x10` (whose designated purpose is to return a function result) at the end of program execution.
1. Вычислить [циклический сдвиг](https://ru.wikipedia.org/wiki/Битовый_сдвиг#Циклический_сдвиг) вправо `a >> sw_i`.
Пример: `a = 0...01011`, `sw_i = 0...010`.
Результат вычислений: `out_o = 110...010`.
1. Compute the [circular shift](https://ru.wikipedia.org/wiki/Битовый_сдвиг#Циклический_сдвиг) right `a >> sw_i`.
Example: `a = 0...01011`, `sw_i = 0...010`.
Result: `out_o = 110...010`.
2. Вычислить `a - sw_i` без использования операции вычитания.
Пример: `sw_i = 0...011`, `a = 0...0100`.
Результат вычислений: `out_o = 0...001`.
2. Compute `a - sw_i` without using the subtraction operation.
Example: `sw_i = 0...011`, `a = 0...0100`.
Result: `out_o = 0...001`.
3. Вычислить [циклический сдвиг](https://ru.wikipedia.org/wiki/Битовый_сдвиг#Циклический_сдвиг) влево `a << sw_i`.
Пример: `a = 10...01011`, `sw_i = 0...10`.
Результат вычислений: `out_o = 0...0101110`.
3. Compute the [circular shift](https://ru.wikipedia.org/wiki/Битовый_сдвиг#Циклический_сдвиг) left `a << sw_i`.
Example: `a = 10...01011`, `sw_i = 0...10`.
Result: `out_o = 0...0101110`.
4. Поменять местами `[7:0]` и `[15:8]` биты числа `sw_i`. Вывести результат в `out_o`.
Пример: `sw_i = 0...010100000_1110010`.
Результат вычислений: `out_o = 0...011100101_10100000`.
4. Swap bits `[7:0]` and `[15:8]` of the number `sw_i`. Output the result to `out_o`.
Example: `sw_i = 0...010100000_1110010`.
Result: `out_o = 0...011100101_10100000`.
5. Вычислить приблизительное значение длины вектора `(a;sw_i)`. Вычисляется как `max + min/2`, где `max` и `min` — это большее и меньшее из чисел `a` и `sw_i` соответственно.
Пример: `a = 0...011`, `sw_i = 0...0100`.
Результат вычислений: `out_o = 0...0101`.
5. Compute the approximate length of the vector `(a; sw_i)`. It is computed as `max + min/2`, where `max` and `min` are the larger and smaller of `a` and `sw_i` respectively.
Example: `a = 0...011`, `sw_i = 0...0100`.
Result: `out_o = 0...0101`.
---
6. Вычислить `a * sw_i` посредством суммы `sw_i` значений `a`. Вывести результат в `out_o`.
Пример: `a = 5`, `sw_i = 4`. `5 * 4 == 5 + 5 + 5 + 5 = 20`.
Результат вычислений: `out_o = 0...010100`.
6. Compute `a * sw_i` as a sum of `sw_i` copies of `a`. Output the result to `out_o`.
Example: `a = 5`, `sw_i = 4`. `5 * 4 == 5 + 5 + 5 + 5 = 20`.
Result: `out_o = 0...010100`.
7. Если `sw_i[1:0] == 00`, то в `out_o` выводится `a`, если `sw_i[1:0] == 01`, то `b`, если `sw_i[1:0] == 10`, то `c`, если `sw_i[1:0] == 11`, то `d`.
Пример: `a = 0...00`, `b = 0...010`, `c = 0...011`, `d = 0...001`, `sw_i[1:0] = 01`.
Результат вычислений: `out_o = 0...010`.
7. If `sw_i[1:0] == 00`, output `a` to `out_o`; if `sw_i[1:0] == 01`, output `b`; if `sw_i[1:0] == 10`, output `c`; if `sw_i[1:0] == 11`, output `d`.
Example: `a = 0...00`, `b = 0...010`, `c = 0...011`, `d = 0...001`, `sw_i[1:0] = 01`.
Result: `out_o = 0...010`.
8. Посчитать длину окружности при заданном радиусе `sw_i`, считая, что `pi = 3`. Вывести результат в `out_o`.
Пример: `sw_i = 0...010`.
Результат вычислений: `out_o = 0...01100`.
8. Compute the circumference for a given radius `sw_i`, assuming `pi = 3`. Output the result to `out_o`.
Example: `sw_i = 0...010`.
Result: `out_o = 0...01100`.
9. Если `sw_i` является степенью двойки, то вывести `out_o = 0...01`, в противном случае, `out_o = 0...0`.
Пример 1: `sw_i = 0...0100`. Результат вычислений: `out_o = 0...01`.
Пример 2: `sw_i = 0...0110`. Результат вычислений: `out_o = 0...00`.
9. If `sw_i` is a power of two, output `out_o = 0...01`; otherwise output `out_o = 0...0`.
Example 1: `sw_i = 0...0100`. Result: `out_o = 0...01`.
Example 2: `sw_i = 0...0110`. Result: `out_o = 0...00`.
10. Найти количество нулей в двоичном представлении числа `sw_i`. Вывести результат в `out_o`.
Пример: `sw_i = 1...10110_0010`.
Результат вычислений: `out_o = 0...0101`.
10. Count the number of zeros in the binary representation of `sw_i`. Output the result to `out_o`.
Example: `sw_i = 1...10110_0010`.
Result: `out_o = 0...0101`.
11. Найти наибольший двоичный разряд числа `sw_i`, значение которого равно `1`. Если такого нет, вывести `32`. Вывести результат в `out_o`.
Пример: `sw_i = 0...0110`.
Результат вычислений: `out_o = 0...010`.
11. Find the highest binary bit of `sw_i` whose value is `1`. If no such bit exists, output `32`. Output the result to `out_o`.
Example: `sw_i = 0...0110`.
Result: `out_o = 0...010`.
12. Сформировать число, состоящее из чётных двоичных разрядов числа `sw_i`. Вывести результат в `out_o`.
Пример: `sw_i = 0...011_1011_1000`.
Результат вычислений `out_o = 0...01_0100`.
12. Form a number consisting of the even-indexed binary bits of `sw_i`. Output the result to `out_o`.
Example: `sw_i = 0...011_1011_1000`.
Result: `out_o = 0...01_0100`.
13. Найти количество единиц в двоичном представлении числа `sw_i` (обратите внимание, `sw_i` знаковое число). Вывести результат в `out_o`.
Пример: `sw_i = 0...0101_0110`.
Результат вычислений: `out_o = 0...0100`.
13. Count the number of ones in the binary representation of `sw_i` (note: `sw_i` is a signed number). Output the result to `out_o`.
Example: `sw_i = 0...0101_0110`.
Result: `out_o = 0...0100`.
14. Найти количество двоичных разрядов, в которых различаются числа `sw_i` и `a`. Вывести результат в `out_o`.
Пример: `sw_i = 0...0110`, `a = 0...01110`.
Результат вычислений: `out_o = 0...01`.
14. Count the number of binary bits in which `sw_i` and `a` differ. Output the result to `out_o`.
Example: `sw_i = 0...0110`, `a = 0...01110`.
Result: `out_o = 0...01`.
15. Вывести в `out_o` подряд все единицы входного числа `sw_i`.
Пример: `sw_i = 0...01011011011`.
Результат вычислений:`out_o` = `0...01111111`.
15. Output all the one-bits of `sw_i` packed consecutively to `out_o`.
Example: `sw_i = 0...01011011011`.
Result: `out_o = 0...01111111`.
16. Вывести в `out_o` значение выражения: `out = (k*a + b) + c`, где `k` — это `sw_i[15:12]`, `a` — это `sw_i[11:8]`, `b` — это `sw_i[7:4]`, `c` — это `sw_i[3:0]`.
Пример: `sw_i = 0...0011_0010_0100_0011`.
Результат вычислений: `out_o = 0...01101`.
16. Output to `out_o` the value of the expression: `out = (k*a + b) + c`, where `k` is `sw_i[15:12]`, `a` is `sw_i[11:8]`, `b` is `sw_i[7:4]`, `c` is `sw_i[3:0]`.
Example: `sw_i = 0...0011_0010_0100_0011`.
Result: `out_o = 0...01101`.
---
17. Найти остаток от деления `sw_i` на `a`. Вывести результат в `out_o`.
Пример: `sw_i = 0...0101`, `a = 0...010`.
Результат вычислений: `out_o = 0...01`.
17. Find the remainder of dividing `sw_i` by `a`. Output the result to `out_o`.
Example: `sw_i = 0...0101`, `a = 0...010`.
Result: `out_o = 0...01`.
18. Найти и вывести в `out_o` количество вхождений `a[2:0]` в `sw_i` без пересечений.
Пример: `a[2:0] = 010`, `sw_i = 0...01101_0101`.
Результат вычислений: `out_o = 0...01`.
18. Find and output to `out_o` the number of non-overlapping occurrences of `a[2:0]` in `sw_i`.
Example: `a[2:0] = 010`, `sw_i = 0...01101_0101`.
Result: `out_o = 0...01`.
19. Определить, сколько раз встречается `11` в двоичном представлении `sw_i` без пересечений. Вывести результат в `out_o`.
Пример: `sw_i = 0...01110`.
Результат вычислений: `out_o = 0...01`.
19. Determine how many times the pattern `11` occurs in the binary representation of `sw_i` without overlaps. Output the result to `out_o`.
Example: `sw_i = 0...01110`.
Result: `out_o = 0...01`.
20. Вывести в `out_o` результат целочисленного деления `a/sw_i`.
Пример: `sw_i = 0...010`, `a = 0...0111`.
Результат вычислений: `out_o = 0...011`.
20. Output to `out_o` the result of integer division `a/sw_i`.
Example: `sw_i = 0...010`, `a = 0...0111`.
Result: `out_o = 0...011`.
21. Вывести в `out_o` сумму `sw_i[3:0]` + `sw_i[7:4]` + `sw_i[11:8]` + `sw_i[15:12]`.
Пример: `sw_i[15:0] = 0001_0010_0011_0000`.
Результат вычислений: `out_o = 0...0110`.
21. Output to `out_o` the sum `sw_i[3:0]` + `sw_i[7:4]` + `sw_i[11:8]` + `sw_i[15:12]`.
Example: `sw_i[15:0] = 0001_0010_0011_0000`.
Result: `out_o = 0...0110`.
22. В числе `sw_i` заменить справа-налево каждое `00` на `11`. Вывести результат в `out_o`.
Пример: `sw_i = 1...101000`.
Результат вычислений: `out_o = 1...101011`.
22. In the number `sw_i`, replace each occurrence of `00` with `11` scanning from right to left. Output the result to `out_o`.
Example: `sw_i = 1...101000`.
Result: `out_o = 1...101011`.
---
23. Поменять местами чётные биты числа `sw_i` с нечётными битами этого числа (то есть соседние биты поменять местами). Вывести результат в `out_o`.
Пример: `sw_i = 0...01010_0111`.
Результат вычислений: `out_o = 0...0101_1011`.
23. Swap the even-indexed bits of `sw_i` with the odd-indexed bits (i.e., swap each pair of adjacent bits). Output the result to `out_o`.
Example: `sw_i = 0...01010_0111`.
Result: `out_o = 0...0101_1011`.
24. Инвертировать первые `sw_i` бит числа `a`. Вывести результат в `out_o`.
Пример: `sw_i = 0...011`, `a = 0...01010_0011`.
Результат вычислений: `out_o = 0...01010_0100`.
24. Invert the first `sw_i` bits of the number `a`. Output the result to `out_o`.
Example: `sw_i = 0...011`, `a = 0...01010_0011`.
Result: `out_o = 0...01010_0100`.
25. Вывести n-ый член [последовательности Фибоначчи](https://ru.wikipedia.org/wiki/Числа_Фибоначчи) Fn. n = `sw_i`. Вывести результат в `out_o`.
Пример: `sw_i = 0...0100`.
Результат вычислений: `out_o = 0...010`.
25. Output the n-th term of the [Fibonacci sequence](https://ru.wikipedia.org/wiki/Числа_Фибоначчи) Fn, where n = `sw_i`. Output the result to `out_o`.
Example: `sw_i = 0...0100`.
Result: `out_o = 0...010`.
26. Поменять в числе `a` разряды `i = sw_i[4:0]` и `j = sw_i[9:5]`. Вывести результат в `out_o`.
Пример: `a = 0...01001`, `sw_i[9:0] = 00000_00001`. Значит, в числе `а` необходимо поменять местами `а[0]` и `a[1]`.
Результат вычислений: `out_o = 0...01010`.
26. In the number `a`, swap bits `i = sw_i[4:0]` and `j = sw_i[9:5]`. Output the result to `out_o`.
Example: `a = 0...01001`, `sw_i[9:0] = 00000_00001`. This means bits `a[0]` and `a[1]` must be swapped.
Result: `out_o = 0...01010`.
---
27. Вычислить `a * sw_i` с использованием операций сложений и сдвига ("в столбик"). Вывести результат в `out_o`.
[Пример](https://en.wikipedia.org/wiki/Binary_multiplier#Binary_long_multiplication): `a = 0...01011`, `sw_i[9:0] = 0...01110`.
Результат вычислений: `out_o = 0...010011010`.
27. Compute `a * sw_i` using addition and shift operations (long multiplication). Output the result to `out_o`.
[Example](https://en.wikipedia.org/wiki/Binary_multiplier#Binary_long_multiplication): `a = 0...01011`, `sw_i[9:0] = 0...01110`.
Result: `out_o = 0...010011010`.
```
1011 (11 в двоичном виде)
x 1110 (14 в двоичном виде)
1011 (11 in binary)
x 1110 (14 in binary)
======
0000 (это 1011 x 0)
1011 (это 1011 x 1, сдвинутое на 1 влево)
1011 (это 1011 x 1, сдвинутое на 2 влево)
+ 1011 (это 1011 x 1, сдвинутое на 2 влево)
0000 (this is 1011 x 0)
1011 (this is 1011 x 1, shifted left by 1)
1011 (this is 1011 x 1, shifted left by 2)
+ 1011 (this is 1011 x 1, shifted left by 3)
=========
10011010 (154 в двоичном виде)
10011010 (154 in binary)
```
28. Вывести в `out_o` n-ый член [арифметической прогрессии](https://ru.wikipedia.org/wiki/Арифметическая_прогрессия) aN, где `a1 = a`, `d = sw_i[15:8]`, `n = sw_i[7:0]` (d и n неотрицательные).
Пример: `sw_i[15:8] = 0000_0010`, `sw_i[7:0] = 0000_0011`, `a = 0...01`.
Результат вычислений: `out_o = 0...0101`.
28. Output to `out_o` the n-th term of the [arithmetic progression](https://ru.wikipedia.org/wiki/Арифметическая_прогрессия) aN, where `a1 = a`, `d = sw_i[15:8]`, `n = sw_i[7:0]` (d and n are non-negative).
Example: `sw_i[15:8] = 0000_0010`, `sw_i[7:0] = 0000_0011`, `a = 0...01`.
Result: `out_o = 0...0101`.
<!-- 25. *Зажечь все светодиоды на 50% яркости ([подсказка](http://wiki.amperka.ru/конспект-arduino:шим)) -->
<!-- 25. *Turn on all LEDs at 50% brightness ([hint](http://wiki.amperka.ru/конспект-arduino:шим)) -->
29. Удалить все вхождения `sw_i[2:0]` из `a` со сдвигом вправо (заполняя удалённые области).
Пример: `a = 0...010011010`, `sw_i[2:0] = 101`.
Результат вычислений: `out_o = 0...010010`
29. Remove all occurrences of `sw_i[2:0]` from `a` with a right shift (filling in the removed areas).
Example: `a = 0...010011010`, `sw_i[2:0] = 101`.
Result: `out_o = 0...010010`