Initial commit

This commit is contained in:
Andrei Solodovnikov
2023-09-07 17:04:37 +03:00
commit f4c0960704
334 changed files with 36105 additions and 0 deletions

View File

@@ -0,0 +1,351 @@
# Лабораторная работа 4 "Примитивное программируемое устройство"
В этой лабораторной работе на основе ранее разработанных блоков памяти и АЛУ ты соберешь простой учебный процессор с архитектурой `CYBERcobra 3000 Pro 2.1`. Это нужно для более глубокого понимания принципов работы программируемых устройств, чтобы проще было понять архитектуру RISC-V в будущем.
## Допуск к лабораторной работе
Для выполнения этой лабораторной работы, необходимо в полной мере освоить следующие элементы синтаксиса языка verilog:
1. Описание модулей, их создание внутри других модулей и оператор непрерывного присваивания `assign` ([Modules.md](../../Basic%20Verilog%20structures/Modules.md)).
2. Описание мультиплексоров: с помощью `тернарного оператора`, блоков `case` и `if/else`. Знать особенности использования этих блоков и особенности синтеза комбинационной логики внутри блока `always` ([Multiplexors.md](../../Basic%20Verilog%20structures/Multiplexors.md)).
3. Описание регистров ([Registers.md](../../Basic%20Verilog%20structures/Registers.md)).
4. Оператор конкатенации ([Concatenation.md](../../Basic%20Verilog%20structures/Concatenation.md)).
5. Отладку проекта по временной диаграмме ([Debug manual.md](../../Vivado%20Basics/Debug%20manual.md)).
## Цель
Реализовать простейшее программируемое устройство с архитектурой `CYBERcobra 3000 Pro 2.1`
## Ход работы
1. Изучить принцип работы процессоров (соответствующий раздел [#теории](#теория-про-программируемое-устройство))
2. Познакомиться с архитектурой и микроархитектурой `CYBERcobra 3000 Pro 2.1` (раздел про эту [#архитектуру](#архитектура-cybercobra-3000-pro-21-и-ее-микроархитектура))
3. Изучить необходимые для описания процессора конструкции verilog (раздел [#инструменты](#инструменты-для-реализации-процессора))
4. Реализовать процессор с архитектурой `CYBERcobra 3000 Pro 2.1` ([#задание по разработке аппаратуры](#задание-по-реализации-процессора))
5. Проверить работу процессора в ПЛИС.
Доп. задание, выполняемое дома:
6. Написать программу для процессора и на модели убедиться в корректности ее выполнения ([#задание по разработке программы](#задание-по-проверке-процессора))
## Теория про программируемое устройство
В обобщенном виде, процессор включает в себя память, АЛУ, устройство управления и интерфейсную логику для организации ввода/вывода. Также, в процессоре есть специальный регистр `PC` (**Program Counter** счетчик команд), который хранит в себе число адрес ячейки памяти, в которой лежит инструкция, которую нужно выполнить. Инструкция тоже представляет собой число, в котором закодировано `что нужно сделать` и `с чем это нужно сделать`.
Алгоритм работы процессора следующий:
1. Из памяти считывается инструкция по адресу `PC`
2. Устройство управления дешифрует полученную инструкцию (то есть определяет какую операцию нужно сделать, где взять операнды и куда разместить результат)
3. Декодировав инструкцию, устройство управления выдает всем блокам процессора (АЛУ, регистровый файл, мультиплексоры) соответствующие управляющие сигналы, тем самым выполняя эту инструкцию.
4. Изменяется значение `PC`.
5. Цикл повторяется с `п.1`.
Любая инструкция приводит к изменению состояния памяти. В случае процессора с архитектурой `CYBERcobra 3000 Pro 2.1` есть два класса инструкций: одни изменяют содержимое регистрового файла — это инструкции записи. Другие изменяют значение `PC` — это инструкции перехода. В первом случае используются вычислительные инструкции и инструкции загрузки данных из других источников. Во-втором случае используются инструкции перехода.
Если процессор обрабатывает вычислительную инструкцию, то `PC` перейдет к следующей по порядку инструкции. На лабораторной работе, посвященной памяти, мы сделали память инструкций с [побайтовой адресацией](../03.%20Register%20file%20and%20memory/README.md#1-память-инструкций). Это означает, что каждый байт памяти имеет свой собственный адрес. Поскольку длина инструкции составляет `4 байта`, для перехода к следующей инструкции `PC` должен быть увеличен на `4` (`PC = PC + 4`). При этом, регистровый файл сохранит результат некоторой операции на АЛУ или данные со входного порта.
Если же обрабатывается инструкция перехода, то возможно два варианта. В случае безусловного или успешного условного перехода, значение `PC` увеличится на значение константы закодированной внутри инструкции `PC = PC + const*4` (иными словами, `const` говорит о том, через сколько инструкций перепрыгнет `PC`, `const` может быть и отрицательной). В случае же неуспешного условного перехода `PC`, как и после вычислительных команд, просто перейдет к следующей инструкции, то есть `PC = PC + 4`.
> Строго говоря `PC` меняется при выполнении любой инструкции (кроме случая `const = 0`, то есть перехода инструкции на саму себя `PC = PC + 0*4`). Разница в том, на какое значение `PC` изменится. В вычислительных инструкциях это всегда адрес следующей инструкции, программа не управляет `PC`, он "сам знает", что ему делать. В инструкциях перехода программа и контекст определяют, что произойдет с `PC`.
## Архитектура CYBERcobra 3000 Pro 2.1 и ее микроархитектура
В качестве первого разрабатываемого программируемого устройства предлагается использовать архитектуру специального назначения `CYBERcobra 3000 Pro 2.1`, которая была разработана в **МИЭТ**. Главным достоинством данной архитектуры является простота ее понимания и реализации. Главным ее минусом является неоптимальность ввиду неэффективной реализации кодирования инструкций, что приводит к наличию неиспользуемых битов в программах. Но это неважно, так как основная цель разработки процессора с архитектурой `CYBERcobra 3000 Pro 2.1` — это более глубокое понимание принципов работы программируемых устройства, которое поможет при разработке более сложного процессора с архитектурой **RISC-V**.
![../../.pic/Labs/lab_04_cybercobra/logoCC3000.svg](../../.pic/Labs/lab_04_cybercobra/logoCC3000.svg)
Простота архитектуры `CYBERcobra 3000 Pro 2.1` проявляется, в том числе, за счет отсутствия памяти данных. Это значит, что данные c которыми работает программа могут храниться только в регистровом файле. Также в таком процессоре почти полностью отсутствует устройство управления (формально оно существует, но состоит только из проводов и пары логических вентилей).
Архитектурой предусмотрена поддержка 19 инструкций (5 типов команд):
Первые два типа содержат 16 инструкций, которые выполняются на АЛУ:
- 10 вычислительных
- 6 операций сравнения для условного перехода
Кроме того, есть инструкции:
- безусловного перехода
- загрузки константы
- загрузки данных с внешнего устройства.
К классу инструкций записи, то есть тех, которые меняют значение регистрового файла, можно отнести: 10 вычислительных, загрузки константы и загрузки данных с внешнего устройства. К классу инструкций перехода: 6 операций сравнения для условного перехода и безусловный переход.
### Последовательное считывание инструкций
Будем рассматривать архитектуру (функции процессора) и микроархитектуру (реализация процессора) одновременно, прослеживая рассуждения их разработчика.
Для начала реализуем базовый функционал, подключив счетчик команд `PC` к памяти инструкций `instr_mem` и сумматору, прибавляющему 4 к `PC`. Выход сумматора подключим ко входу `PC`.
Каждый раз, когда будет происходить тактовый импульс (переключение `clk_i` из 0 в 1), значение `PC` будет увеличиваться на `4`, тем самым указывая на следующую инструкцию. Последовательное считывание программы из памяти готово.
Так как операции будут выполняться только над данными в регистровом файле, то его можно сразу подключить к АЛУ, соединив порты чтения `read_data1_o` и `read_data2_o` со входами операндов АЛУ, а результат операции АЛУ подключив к порту на запись `write_data_i`. Полученный результат изображен на картинке ниже.
![../../.pic/Labs/lab_04_cybercobra/ppd_1.drawio.png](../../.pic/Labs/lab_04_cybercobra/ppd_1.drawio.png)
Для компактности схемы, названия портов регистрового файла сокращено (`RA1` обозначает `read_addr1_i` и т.п.).
### Кодирование вычислительных инструкций
Чтобы добавить поддержку каких-либо инструкций, необходимо договориться **как** они будут кодироваться (эта часть относится к вопросам архитектуры). Вычислительные инструкции требуют следующую информацию:
1. по каким адресам регистрового файла лежат операнды?
2. по какому адресу будет сохранен результат?
3. какая операция должна быть выполнена?
Для этого в инструкции были выбраны следующие поля: 5 бит (`[27:23]`) для кодирования операции на АЛУ, два раза по 5 бит для кодирования адресов операндов в регистровом файле (`[22:18]` и `[17:13]`) и 5 бит для кодирования адреса результата (`[4:0]`). Ниже демонстрируется деление 32-битной инструкции на поля `alu_op`, `RA1`, `RA2` и `WA`.
![../../.pic/Labs/lab_04_cybercobra/ppd_code_1.png](../../.pic/Labs/lab_04_cybercobra/ppd_code_1.png)
``` 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]`).
### Реализация вычислительных инструкций
Чтобы процессор правильно реагировал на эти инструкции, требуется подключить ко входам адреса регистрового файла и управляющему входу АЛУ соответствующие биты выхода `read_data` памяти инструкции (**Instruction Memory**). В таком случае, когда `PC` будет указывать на ячейку памяти, в которой лежит, например, следующая 32-битная инструкция:
```text
0000 00111 00100 01000 00000000 11100
|alu_op| RA1 | RA2 | | WA
```
будет выполнена операция `reg_file[28] = reg_file[4] | reg_file[8]`, потому что `alu_op = 00111`, что соответствует операции **логического ИЛИ**, `WA = 11100`, то есть 28-ой регистр, `RA1 = 00100` (4-ый регистр) и `RA2 = 01000` (8-ой регистр). Ниже иллюстрируется фрагмент микроархитектуры, поддерживающий вычислительные операции на АЛУ. Так как пока что другие инструкции не поддерживаются, то вход `WE` регистрового файла всегда равен `1` (это временно).
![../../.pic/Labs/lab_04_cybercobra/ppd_2.drawio.png](../../.pic/Labs/lab_04_cybercobra/ppd_2.drawio.png)
### Реализация загрузки константы в регистровый файл
Информация как-то должна попадать в регистровый файл, для этого добавим инструкцию загрузки константы по адресу `WA`. Чтобы аппаратура могла различать, когда ей нужно выполнять операцию на АЛУ, а когда загружать константу, назначим один бит инструкции определяющим "что именно будет записано в регистровый файл": результат с АЛУ или константа из инструкции. За это будет отвечать 28-ой бит инструкции `WS` (**Write Source**). Если `WS == 1`, значит выполняется вычислительная инструкция, а если `WS == 0`, значит нужно загрузить константу в регистровый файл.
Сама константа имеет разрядность **23 бита** ([27:5] биты инструкции) и должна быть **знакорасширена** до 32-х бит, то есть к 23-битной константе нужно приклеить слева 9 раз 23-ий знаковый бит константы (см. [оператор конкатенации](../../Basic%20Verilog%20structures/Concatenation.md)).
Пример: если [27:5] биты инструкции равны:
```text
10100000111100101110111
```
то после знакорасширения константа примет вид:
```text
11111111110100000111100101110111
```
(если бы старший бит был равен нулю, то константа заполнилась бы слева нулями, а не единицами).
Нет ничего страшного в том, что биты константы попадают на те же поля, что и `alu_op`, `RA1` и `RA2`, потому что когда выполняется инструкция загрузки константы не важно что будет выдавать АЛУ в этот момент (ведь благодаря мультиплексору на вход регистрового файла приходит константа). А значит не важно и что приходит в этот момент на АЛУ в качестве операндов и кода операции. Ниже демонстрируется деления 32-битной инструкции на поля `alu_op`, `RA1`, `RA2`, `WA`, `WS` и `const`, **с перекрытием полей**.
![../../.pic/Labs/lab_04_cybercobra/ppd_code_2.png](../../.pic/Labs/lab_04_cybercobra/ppd_code_2.png)
``` C
reg_file[WA] ← const
```
Так как вход записи уже занят результатом операции АЛУ, его потребуется мультиплексировать со значением константы из инструкции, которая предварительно **знакорасширяется** в блоке `SE`. На входе `WD` регистрового файла появляется мультиплексор, управляемый 28-м битом инструкции, который и определяет что будет записано: константа или результат вычисления на АЛУ.
Например, в такой реализации следующая 32-битная инструкция поместит константу `-1` в регистр по адресу `5`:
```text
000 0 11111111111111111111111 00101
|WS| RF_const | WA |
```
Далее приводится фрагмент микроархитектуры, поддерживающий вычислительные операции на АЛУ и загрузку констант из инструкции в регистровый файл.
![../../.pic/Labs/lab_04_cybercobra/ppd_3.drawio.png](../../.pic/Labs/lab_04_cybercobra/ppd_3.drawio.png)
### Реализация загрузки в регистровый файл данных с внешних устройств
Чтобы процессор мог взаимодействовать с внешним миром добавим возможность загрузки данных с внешних устройств в регистр по адресу `WA`. Появляется третий тип инструкции, который определяет третий источник ввода для регистрового файла. Одного бита `WS` для выбора одного из трех источников будет недостаточно, поэтому расширим это поле до 2 бит. Теперь, когда `WS == 0` будет загружаться константа, когда `WS == 1` будет загружаться результат вычисления АЛУ, а при `WS == 2` будут загружаться данные с внешних устройств. Остальные поля в данной инструкции не используются.
![../../.pic/Labs/lab_04_cybercobra/ppd_code_3.png](../../.pic/Labs/lab_04_cybercobra/ppd_code_3.png)
``` C
reg_file[WA] ← sw_i
```
По аналогии с загрузкой констант увеличиваем входной мультиплексор до 4 входов и подключаем к нему управляющие сигналы `[29:28]` биты инструкции. Последний вход используется, чтобы разрешить неопределенность на выходе при `WS == 3`(`default`-вход, см. [мультиплексор](../../Basic%20Verilog%20structures/Multiplexors.md)).
Выход OUT подключается к первому порту на чтение регистрового файла. Значение на выходе OUT будет определяться содержимым ячейки памяти по адресу `RA1`. Ниже приводится фрагмент микроархитектуры, поддерживающий вычислительные операции на АЛУ, загрузку констант из инструкции в регистровый файл и загрузку данных с внешних устройств.
![../../.pic/Labs/lab_04_cybercobra/ppd_4.drawio.png](../../.pic/Labs/lab_04_cybercobra/ppd_4.drawio.png)
### Реализация условного перехода
С реализованным набором инструкций полученное устройство нельзя назвать процессором пока что это продвинутый калькулятор. Добавим поддержку инструкции условного перехода, при выполнении которой программа будет перепрыгивать через заданное количество команд. Чтобы аппаратура отличала эту инструкцию от других будем использовать 30-ый бит `B` (`branch`). Если `B == 1`, значит это инструкция условного перехода и, если условие перехода выполняется, к `PC` надо прибавить константу. Если `B == 0`, значит это какая-то другая инструкция и к `PC` надо прибавить четыре.
![../../.pic/Labs/lab_04_cybercobra/ppd_code_4.png](../../.pic/Labs/lab_04_cybercobra/ppd_code_4.png)
Для вычисления результата условного перехода, нам необходимо выполнить операцию на АЛУ и посмотреть на сигнал `flag`. Если он равен 1, переход выполняется, в противном случае — не выполняется. А значит, нам нужны операнды `A`, `B`, и `alu_op`. Кроме того, нам необходимо указать насколько мы сместимся относительно текущего значения `PC` (константу смещения, `offset`). Для передачи этой константы лучше всего подойдут незадействованные биты инструкции `[12:5]`.
Обратим внимание на то, что `PC` 32-битный и должен быть всегда кратен четырем (`PC` не может указывать кроме как в начало инструкции, а каждая инструкция длиной в 32 бита). Чтобы константа смещения указывала на число инструкций, а не число байт, необходимо увеличить её в 4 раза. Это можно сделать, если приклеить к ней справа два нулевых бита (так же как в десятичной системе можно умножить число на 10<sup>2</sup>=100 если дописать справа от него два нуля). Кроме того, чтобы разрядность константы совпадала с разрядностью `PC`, необходимо знакорасширить её до 32 бит.
Приведенный ниже Си-подобный псевдо-код (далее мы назовем его псевдоассемблером) демонстрирует кодирование инструкций с новым полем `B`:
``` C
if (reg_file[RA1] {alu_op} reg_file[RA2])
PC ← PC + const * 4
else
PC ← PC + 4
```
Так как второй вход сумматора счетчика команд занят числом 4, то для реализации условного перехода этот вход надо мультиплексировать с константой. Мультиплексор при этом управляется 30-ым битом `B`, который и определяет, что будет прибавляться к `PC`.
Сигнальные линии, которые управляют АЛУ и подают на его входы операнды уже существуют. Поэтому на схему необходимо добавить только логику управления мультиплексором на входе сумматора счетчика команд так. Эта логика работает следующим образом:
1. Если сейчас инструкция условного перехода
2. И если условие перехода выполнилось
то к `PC` прибавляется знакорасширенная константа, умноженная на 4. В противном случае, к `PC` прибавляется 4.
Так как теперь не любая инструкция приводит к записи в регистровый файл, появляется необходимость управлять входом `WE` так, чтобы при операциях условного перехода запись в регистровый файл не производилась. Это можно сделать, подав на WE значение `!B` (запись происходит, если сейчас **не операция условного перехода**)
![../../.pic/Labs/lab_04_cybercobra/ppd_5.drawio.png](../../.pic/Labs/lab_04_cybercobra/ppd_5.drawio.png)
### Реализация безусловного перехода
Осталось добавить поддержку инструкции безусловного перехода, для идентификации которой используется оставшийся 31-ый бит `J`(jump). Если бит `J == 1`, то это безусловный переход, и мы прибавляем к `PC` знакорасширенную константу смещения, умноженную на 4 (как это делали и в условном переходе).
![../../.pic/Labs/lab_04_cybercobra/ppd_code_5.png](../../.pic/Labs/lab_04_cybercobra/ppd_code_5.png)
``` C
PC ← PC + const*4
```
Для реализации безусловного перехода, нам необходимо добавить дополнительную логику управления мультиплексором перед сумматором. Итоговая логика его работы звучит так:
1. Если сейчас инструкция безусловного перехода
2. ИЛИ если сейчас инструкция условного перехода
3. И если условие перехода выполнилось
Кроме того, при безусловном переходе в регистровый файл так же ничего не пишется. А значит, необходимо обновить логику работы сигнала разрешения записи `WE`, который будет равен 0 если сейчас инструкция условного или безусловного перехода.
Ниже приводится итоговый вариант микроархитектуры процессора `CYBERcobra 3000 Pro 2.1`
![../../.pic/Labs/lab_04_cybercobra/ppd_6.drawio.png](../../.pic/Labs/lab_04_cybercobra/ppd_6.drawio.png)
### Финальный обзор
Итого, архитектура `CYBERcobra 3000 Pro 2.1` поддерживает 5 типов инструкций, которые кодируются следующим образом (иксами помечены биты, которые не задействованы в данной инструкции):
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 0 xx xxx xxxx xxxx xxxx const xxxxx`
5. 6 инструкций условного перехода `0 1 xx alu_op RA1 RA2 const x xxxx`
При кодировании инструкций используются следующие поля:
- J однобитный сигнал указывающий на выполнение безусловного перехода
- B однобитный сигнал указывающий на выполнение условного перехода
- WS двухбитный сигнал указывающий источник данных для записи в регистровый файл:
- 0 константа из инструкции
- 1 результат с АЛУ
- 2 внешние данные
- 3 не используется
- alu_op 5-битный сигнал кода операции АЛУ (в соответствии с таблицей из лабораторной по АЛУ)
- RA1 и RA2 5-битные адреса операндов из регистрового файла
- offset 8-битная константа для условного / безусловного перехода
- const — 23-битная константа для загрузки в регистровый файл
- WA 5-битный адрес регистра, в который будет записан результат
Напишем простую программу для этого процессора, которая в бесконечном цикле увеличивает значение первого регистра на 1. Сначала напишем программу на псевдоассемблере (используя предложенную мнемонику):
``` C
reg_file[1] ← -1 // загрузить константу `-1` регистр 1
reg_file[2] ← sw_i // загрузить значение с входа sw_i в регистр 2
reg_file[3] ← 1 // загрузить константу `1` регистр 3
reg_file[1] ← reg_file[1] + reg_file[3] // сложить регистр 1 с регистром 3 и
// поместить результат в регистр 1
if (reg_file[1] < reg_file[2]) // если значение в регистре 1 меньше
// значения в регистре 2,
PC ← PC 1 // возврат на 1 инструкцию назад
out_o = reg_file[1], PC ← PC + 0 // бесконечное повторение этой инструкции
// с выводом на out_o значения в регистре 1
```
Теперь в соответствии с кодировкой инструкций переведем программу в машинные коды:
```text
0 0 00 11111111111111111111111 00001
0 0 10 00000000000000000000000 00010
0 0 00 00000000000000000000001 00011
0 0 01 00000 00001 00011 00000000 00001
0 1 00 11110 00001 00010 11111111 00000
1 0 00 00000 00001 00000 00000000 00000
```
Полученную программу можно помещать в память программ и выполнять на процессоре.
## Инструменты для реализации процессора
Так как все модули процессора написаны, основная часть кода описания процессора будет связана с подключением этих модулей друг к другу. Подробнее о подключении модулей сказано в [Modules.md](../../Basic%20Verilog%20structures/Modules.md).
Для реализации блоков знакорасширения с умножением на 4 подходит использование оператора конкатенации ([Concatenation.md](../../Basic%20Verilog%20structures/Modules.md)).
## Задание по реализации процессора
Разработать процессор `CYBERcobra`, объединив ранее разработанные модули:
- Память инструкций (проинициализированную в двоичном формате файлом `example.txt`)
- Регистровый файл
- Арифметико-логическое устройство
- 32-битный сумматор
Кроме того, необходимо описать регистр счетчика команд и логику его работы, в соответствии с ранее представленной микроархитектурой.
```SystemVerilog
module CYВЕRcоbrа (
inрut logic clk_i,
inрut logic rst_i,
inрut logic [15:0] sw_i,
оutрut logic [31:0] out_o
);
// тут твой код, о котором говорится чуть выше
endmodule
```
![../../.pic/Labs/lab_04_cybercobra/ppd_6.drawio.png](../../.pic/Labs/lab_04_cybercobra/ppd_6.drawio.png)
## Порядок выполнения задания
1. Внимательно ознакомьтесь с [заданием](#задание-по-реализации-процессора). В случае возникновения вопросов, проконсультируйтесь с преподавателем.
2. Реализуйте модуль `CYBERcobra`. Для этого:
1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `cybercobra.sv`.
2. Опишите в нем модуль процессора с таким же именем и портами, как указано в [задании](#задание-по-реализации-процессора) (обратите внимание на регистр имени модуля).
1. В первую очередь, необходимо создать счетчик команд и все вспомогательные провода. При создании, **следите за разрядностью**.
2. Затем, необходимо создать экземпляры модулей: памяти инструкции, АЛУ, регистрового файла и сумматора. При подключении сигналов сумматора, надо **обязательно** надо подать нулевое значение на входной бит переноса. Выходной бит переноса подключать не обязательно.
3. После этого, необходимо описать оставшуюся логику:
1. Программного счетчика
2. Сигнала управления мультиплексором, выбирающим слагаемое для программного счетчика
3. Сигнала разрешения записи в регистровый файл
4. Мультиплексор, выбирающий слагаемое для программного счетчика
5. Мультиплексор, выбирающий источник записи в регистровый файл.
3. После описания модуля, его необходимо проверить с помощью [`тестового окружения`](../../Basic%20Verilog%20structures/Testbench.md).
1. Тестовое окружение находится [`здесь`](tb_cybercobra.sv).
2. Программа, которой необходимо проинициализировать память инструкций находится [`здесь`](example.txt). Алгоритм работы программы приведен в разделе [`Финальный обзор`](#финальный-обзор).
3. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md).
4. Перед запуском симуляции убедитесь, что выбран правильный модуль верхнего уровня.
5. **Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!**
6. В этот раз, в конце не будет сообщения о том, работает ли ваше устройство или в нем есть ошибки. Вы должны самостоятельно проверить работу модуля, перенеся его внутренние сигналы на временную диаграмму, и [проверив](../../Vivado%20Basics/Debug%20manual.md) логику их работы.
4. Добавьте в проект модуль верхнего уровня ([nexys_cybercobra_demo.sv](board%20files/nexys_cybercobra_demo.sv)), соединяющий процессор с периферией в ПЛИС. Описание работы модуля находится [здесь](board%20files)
5. Замените содержимое файла, инициализирующего память инструкций новой программой, которая размещена [`здесь`](board%20files/demo.txt).
6. Убедитесь, что у файла, инициализирующего память инструкций выставлен тип `Memory Initialization Files`, а не `Memory File`.
7. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](board%20files/nexys_a7_100t.xdc)), если тот еще не был подключен, либо замените его содержимое данными из файла к этой лабораторной работе.
8. Проверьте работу процессора в ПЛИС.
---
После выполнения задания по реализации процессора, необходимо также выполнить [индивидуальное задание](Индивидуальное%20задание) по написанию двоичной программы под созданный вами процессор.
---
Дерзайте!

View File

@@ -0,0 +1,11 @@
# Проверка работы CYBERcobra на ПЛИС
Если вы не понимаете, что лежит в этой папке, или если надо вспомнить, как прошить ПЛИС, можно воспользоваться [`этой инструкцией`](../../../Vivado%20Basics/Program%20nexys%20a7.md)
Файл [`nexys_cybercobra_demo.v`](nexys_cybercobra_demo.v), который нужно запускать с [`демонстрационным файлом инструкций`](demo.txt), является демонстрацией возможностей кобры, реализующий лишь декодирование выходных значений в формат для отображения на семисегментных индикаторах, а вся логика работы реализована инструкциями в текстовом файле.
Сначала выводится приветствие `≡ALOHA≡`, меняя положение восьми правых переключателей, последовательно нажимая на кнопку `BTND` (на рисунке выделена синим цветом), можно включать или выключать `один` из выбранных сегментов. Кнопка `CPU RESET` (на рисунке выделена красным цветом) возвращает все исходное состояние. Попробуйте погасить все слово, а потом снова его зажечь.
Файл [`nexys_cybercobra.v`](nexys_cybercobra.v), который нужно запускать с `вашим файлом инструкций`, так же реализует лишь декодирование выходных значений в формат для отображения на семисегментных индикаторах, но кнопка `BTND` задает тактирующий сигнал, следовательно, нажимая на нее, вы можете пошагово переходить по инструкциям, контролируя правильность работы устройства, для удобства можете в тестовом окружении выставить такое же входное значение, как переключатели sw[15:0] на плате.
![кобра](../../../.pic/Labs/board%20files/nexys_cobra.jpg)

View File

@@ -0,0 +1,380 @@
21
42
07
00
02
02
00
00
01
40
84
10
63
94
11
00
01
60
04
10
0c
00
06
00
0d
40
01
00
0e
02
00
00
2f
00
00
00
0c
40
b0
10
0d
40
b4
10
0e
40
b8
10
0f
40
bc
10
10
00
10
00
11
80
01
00
12
14
00
00
73
00
00
00
22
00
00
00
43
00
00
00
84
00
00
00
05
01
00
00
06
02
00
00
07
04
00
00
08
08
00
00
09
10
00
00
ea
1f
00
00
0b
00
04
20
00
00
04
30
00
00
04
30
00
00
04
30
00
00
04
30
00
00
04
30
00
00
04
30
00
00
04
30
00
00
04
30
00
00
04
30
c0
7e
a9
7e
0b
40
ad
13
20
45
2c
7c
40
65
2c
7c
60
85
2c
7c
80
a5
2c
7c
a0
c5
2c
7c
c0
e5
2c
7c
e0
05
2d
7c
00
26
2d
7c
14
60
86
13
00
02
50
7c
14
40
86
13
00
02
50
7c
14
20
86
13
00
02
50
7c
14
00
86
13
00
02
50
7c
14
e0
85
13
00
02
50
7c
14
c0
85
13
00
02
50
7c
14
a0
85
13
00
02
50
7c
14
80
85
13
00
02
50
7c
00
04
04
bc
01
60
06
13
c0
03
04
bc
01
40
06
13
80
03
04
bc
01
20
06
13
40
03
04
bc
01
00
06
13
00
03
04
bc
01
e0
05
13
c0
02
04
bc
01
c0
05
13
80
02
04
bc
01
a0
05
13
40
02
04
bc
01
80
05
13
00
02
04
bc
01
60
06
12
c0
01
04
bc
01
40
06
12
80
01
04
bc
01
20
06
12
40
01
04
bc
01
00
06
12
00
01
04
bc
01
e0
05
12
c0
00
04
bc
01
c0
05
12
80
00
04
bc
01
a0
05
12
40
00
04
bc
01
80
05
12
60
17
04
bc

View File

@@ -0,0 +1,211 @@
## This file is a general .xdc for the Nexys A7-100T
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project
# Clock signal
set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { CLK100 }]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {CLK100}];
#Switches
set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { SW[0] }]; #IO_L24N_T3_RS0_15 Sch=sw[0]
set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { SW[1] }]; #IO_L3N_T0_DQS_EMCCLK_14 Sch=sw[1]
set_property -dict { PACKAGE_PIN M13 IOSTANDARD LVCMOS33 } [get_ports { SW[2] }]; #IO_L6N_T0_D08_VREF_14 Sch=sw[2]
set_property -dict { PACKAGE_PIN R15 IOSTANDARD LVCMOS33 } [get_ports { SW[3] }]; #IO_L13N_T2_MRCC_14 Sch=sw[3]
set_property -dict { PACKAGE_PIN R17 IOSTANDARD LVCMOS33 } [get_ports { SW[4] }]; #IO_L12N_T1_MRCC_14 Sch=sw[4]
set_property -dict { PACKAGE_PIN T18 IOSTANDARD LVCMOS33 } [get_ports { SW[5] }]; #IO_L7N_T1_D10_14 Sch=sw[5]
set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports { SW[6] }]; #IO_L17N_T2_A13_D29_14 Sch=sw[6]
set_property -dict { PACKAGE_PIN R13 IOSTANDARD LVCMOS33 } [get_ports { SW[7] }]; #IO_L5N_T0_D07_14 Sch=sw[7]
set_property -dict { PACKAGE_PIN T8 IOSTANDARD LVCMOS18 } [get_ports { SW[8] }]; #IO_L24N_T3_34 Sch=sw[8]
set_property -dict { PACKAGE_PIN U8 IOSTANDARD LVCMOS18 } [get_ports { SW[9] }]; #IO_25_34 Sch=sw[9]
set_property -dict { PACKAGE_PIN R16 IOSTANDARD LVCMOS33 } [get_ports { SW[10] }]; #IO_L15P_T2_DQS_RDWR_B_14 Sch=sw[10]
set_property -dict { PACKAGE_PIN T13 IOSTANDARD LVCMOS33 } [get_ports { SW[11] }]; #IO_L23P_T3_A03_D19_14 Sch=sw[11]
set_property -dict { PACKAGE_PIN H6 IOSTANDARD LVCMOS33 } [get_ports { SW[12] }]; #IO_L24P_T3_35 Sch=sw[12]
set_property -dict { PACKAGE_PIN U12 IOSTANDARD LVCMOS33 } [get_ports { SW[13] }]; #IO_L20P_T3_A08_D24_14 Sch=sw[13]
set_property -dict { PACKAGE_PIN U11 IOSTANDARD LVCMOS33 } [get_ports { SW[14] }]; #IO_L19N_T3_A09_D25_VREF_14 Sch=sw[14]
set_property -dict { PACKAGE_PIN V10 IOSTANDARD LVCMOS33 } [get_ports { SW[15] }]; #IO_L21P_T3_DQS_14 Sch=sw[15]
### LEDs
#set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { LED[0] }]; #IO_L18P_T2_A24_15 Sch=led[0]
#set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { LED[1] }]; #IO_L24P_T3_RS1_15 Sch=led[1]
#set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { LED[2] }]; #IO_L17N_T2_A25_15 Sch=led[2]
#set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { LED[3] }]; #IO_L8P_T1_D11_14 Sch=led[3]
#set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { LED[4] }]; #IO_L7P_T1_D09_14 Sch=led[4]
#set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { LED[5] }]; #IO_L18N_T2_A11_D27_14 Sch=led[5]
#set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { LED[6] }]; #IO_L17P_T2_A14_D30_14 Sch=led[6]
#set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { LED[7] }]; #IO_L18P_T2_A12_D28_14 Sch=led[7]
#set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { LED[8] }]; #IO_L16N_T2_A15_D31_14 Sch=led[8]
#set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { LED[9] }]; #IO_L14N_T2_SRCC_14 Sch=led[9]
#set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { LED[10] }]; #IO_L22P_T3_A05_D21_14 Sch=led[10]
#set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { LED[11] }]; #IO_L15N_T2_DQS_DOUT_CSO_B_14 Sch=led[11]
#set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { LED[12] }]; #IO_L16P_T2_CSI_B_14 Sch=led[12]
#set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { LED[13] }]; #IO_L22N_T3_A04_D20_14 Sch=led[13]
#set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { LED[14] }]; #IO_L20N_T3_A07_D23_14 Sch=led[14]
#set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { LED[15] }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=led[15]
### RGB LEDs
#set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { LED16_B }]; #IO_L5P_T0_D06_14 Sch=led16_b
#set_property -dict { PACKAGE_PIN M16 IOSTANDARD LVCMOS33 } [get_ports { LED16_G }]; #IO_L10P_T1_D14_14 Sch=led16_g
#set_property -dict { PACKAGE_PIN N15 IOSTANDARD LVCMOS33 } [get_ports { LED16_R }]; #IO_L11P_T1_SRCC_14 Sch=led16_r
#set_property -dict { PACKAGE_PIN G14 IOSTANDARD LVCMOS33 } [get_ports { LED17_B }]; #IO_L15N_T2_DQS_ADV_B_15 Sch=led17_b
#set_property -dict { PACKAGE_PIN R11 IOSTANDARD LVCMOS33 } [get_ports { LED17_G }]; #IO_0_14 Sch=led17_g
#set_property -dict { PACKAGE_PIN N16 IOSTANDARD LVCMOS33 } [get_ports { LED17_R }]; #IO_L11N_T1_SRCC_14 Sch=led17_r
##7 segment display
set_property -dict { PACKAGE_PIN T10 IOSTANDARD LVCMOS33 } [get_ports { CA }]; #IO_L24N_T3_A00_D16_14 Sch=ca
set_property -dict { PACKAGE_PIN R10 IOSTANDARD LVCMOS33 } [get_ports { CB }]; #IO_25_14 Sch=cb
set_property -dict { PACKAGE_PIN K16 IOSTANDARD LVCMOS33 } [get_ports { CC }]; #IO_25_15 Sch=cc
set_property -dict { PACKAGE_PIN K13 IOSTANDARD LVCMOS33 } [get_ports { CD }]; #IO_L17P_T2_A26_15 Sch=cd
set_property -dict { PACKAGE_PIN P15 IOSTANDARD LVCMOS33 } [get_ports { CE }]; #IO_L13P_T2_MRCC_14 Sch=ce
set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { CF }]; #IO_L19P_T3_A10_D26_14 Sch=cf
set_property -dict { PACKAGE_PIN L18 IOSTANDARD LVCMOS33 } [get_ports { CG }]; #IO_L4P_T0_D04_14 Sch=cg
#set_property -dict { PACKAGE_PIN H15 IOSTANDARD LVCMOS33 } [get_ports { DP }]; #IO_L19N_T3_A21_VREF_15 Sch=dp
set_property -dict { PACKAGE_PIN J17 IOSTANDARD LVCMOS33 } [get_ports { AN[0] }]; #IO_L23P_T3_FOE_B_15 Sch=an[0]
set_property -dict { PACKAGE_PIN J18 IOSTANDARD LVCMOS33 } [get_ports { AN[1] }]; #IO_L23N_T3_FWE_B_15 Sch=an[1]
set_property -dict { PACKAGE_PIN T9 IOSTANDARD LVCMOS33 } [get_ports { AN[2] }]; #IO_L24P_T3_A01_D17_14 Sch=an[2]
set_property -dict { PACKAGE_PIN J14 IOSTANDARD LVCMOS33 } [get_ports { AN[3] }]; #IO_L19P_T3_A22_15 Sch=an[3]
set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { AN[4] }]; #IO_L8N_T1_D12_14 Sch=an[4]
set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { AN[5] }]; #IO_L14P_T2_SRCC_14 Sch=an[5]
set_property -dict { PACKAGE_PIN K2 IOSTANDARD LVCMOS33 } [get_ports { AN[6] }]; #IO_L23P_T3_35 Sch=an[6]
set_property -dict { PACKAGE_PIN U13 IOSTANDARD LVCMOS33 } [get_ports { AN[7] }]; #IO_L23N_T3_A02_D18_14 Sch=an[7]
##Buttons
set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { resetn }]; #IO_L3P_T0_DQS_AD1P_15 Sch=cpu_resetn
#set_property -dict { PACKAGE_PIN N17 IOSTANDARD LVCMOS33 } [get_ports { BTNC }]; #IO_L9P_T1_DQS_14 Sch=btnc
#set_property -dict { PACKAGE_PIN M18 IOSTANDARD LVCMOS33 } [get_ports { BTNU }]; #IO_L4N_T0_D05_14 Sch=btnu
#set_property -dict { PACKAGE_PIN P17 IOSTANDARD LVCMOS33 } [get_ports { BTNL }]; #IO_L12P_T1_MRCC_14 Sch=btnl
#set_property -dict { PACKAGE_PIN M17 IOSTANDARD LVCMOS33 } [get_ports { BTNR }]; #IO_L10N_T1_D15_14 Sch=btnr
set_property -dict { PACKAGE_PIN P18 IOSTANDARD LVCMOS33 } [get_ports { BTND }]; #IO_L9N_T1_DQS_D13_14 Sch=btnd
##Pmod Headers
##Pmod Header JA
#set_property -dict { PACKAGE_PIN C17 IOSTANDARD LVCMOS33 } [get_ports { JA[1] }]; #IO_L20N_T3_A19_15 Sch=ja[1]
#set_property -dict { PACKAGE_PIN D18 IOSTANDARD LVCMOS33 } [get_ports { JA[2] }]; #IO_L21N_T3_DQS_A18_15 Sch=ja[2]
#set_property -dict { PACKAGE_PIN E18 IOSTANDARD LVCMOS33 } [get_ports { JA[3] }]; #IO_L21P_T3_DQS_15 Sch=ja[3]
#set_property -dict { PACKAGE_PIN G17 IOSTANDARD LVCMOS33 } [get_ports { JA[4] }]; #IO_L18N_T2_A23_15 Sch=ja[4]
#set_property -dict { PACKAGE_PIN D17 IOSTANDARD LVCMOS33 } [get_ports { JA[7] }]; #IO_L16N_T2_A27_15 Sch=ja[7]
#set_property -dict { PACKAGE_PIN E17 IOSTANDARD LVCMOS33 } [get_ports { JA[8] }]; #IO_L16P_T2_A28_15 Sch=ja[8]
#set_property -dict { PACKAGE_PIN F18 IOSTANDARD LVCMOS33 } [get_ports { JA[9] }]; #IO_L22N_T3_A16_15 Sch=ja[9]
#set_property -dict { PACKAGE_PIN G18 IOSTANDARD LVCMOS33 } [get_ports { JA[10] }]; #IO_L22P_T3_A17_15 Sch=ja[10]
##Pmod Header JB
#set_property -dict { PACKAGE_PIN D14 IOSTANDARD LVCMOS33 } [get_ports { JB[1] }]; #IO_L1P_T0_AD0P_15 Sch=jb[1]
#set_property -dict { PACKAGE_PIN F16 IOSTANDARD LVCMOS33 } [get_ports { JB[2] }]; #IO_L14N_T2_SRCC_15 Sch=jb[2]
#set_property -dict { PACKAGE_PIN G16 IOSTANDARD LVCMOS33 } [get_ports { JB[3] }]; #IO_L13N_T2_MRCC_15 Sch=jb[3]
#set_property -dict { PACKAGE_PIN H14 IOSTANDARD LVCMOS33 } [get_ports { JB[4] }]; #IO_L15P_T2_DQS_15 Sch=jb[4]
#set_property -dict { PACKAGE_PIN E16 IOSTANDARD LVCMOS33 } [get_ports { JB[7] }]; #IO_L11N_T1_SRCC_15 Sch=jb[7]
#set_property -dict { PACKAGE_PIN F13 IOSTANDARD LVCMOS33 } [get_ports { JB[8] }]; #IO_L5P_T0_AD9P_15 Sch=jb[8]
#set_property -dict { PACKAGE_PIN G13 IOSTANDARD LVCMOS33 } [get_ports { JB[9] }]; #IO_0_15 Sch=jb[9]
#set_property -dict { PACKAGE_PIN H16 IOSTANDARD LVCMOS33 } [get_ports { JB[10] }]; #IO_L13P_T2_MRCC_15 Sch=jb[10]
##Pmod Header JC
#set_property -dict { PACKAGE_PIN K1 IOSTANDARD LVCMOS33 } [get_ports { JC[1] }]; #IO_L23N_T3_35 Sch=jc[1]
#set_property -dict { PACKAGE_PIN F6 IOSTANDARD LVCMOS33 } [get_ports { JC[2] }]; #IO_L19N_T3_VREF_35 Sch=jc[2]
#set_property -dict { PACKAGE_PIN J2 IOSTANDARD LVCMOS33 } [get_ports { JC[3] }]; #IO_L22N_T3_35 Sch=jc[3]
#set_property -dict { PACKAGE_PIN G6 IOSTANDARD LVCMOS33 } [get_ports { JC[4] }]; #IO_L19P_T3_35 Sch=jc[4]
#set_property -dict { PACKAGE_PIN E7 IOSTANDARD LVCMOS33 } [get_ports { JC[7] }]; #IO_L6P_T0_35 Sch=jc[7]
#set_property -dict { PACKAGE_PIN J3 IOSTANDARD LVCMOS33 } [get_ports { JC[8] }]; #IO_L22P_T3_35 Sch=jc[8]
#set_property -dict { PACKAGE_PIN J4 IOSTANDARD LVCMOS33 } [get_ports { JC[9] }]; #IO_L21P_T3_DQS_35 Sch=jc[9]
#set_property -dict { PACKAGE_PIN E6 IOSTANDARD LVCMOS33 } [get_ports { JC[10] }]; #IO_L5P_T0_AD13P_35 Sch=jc[10]
##Pmod Header JD
#set_property -dict { PACKAGE_PIN H4 IOSTANDARD LVCMOS33 } [get_ports { JD[1] }]; #IO_L21N_T3_DQS_35 Sch=jd[1]
#set_property -dict { PACKAGE_PIN H1 IOSTANDARD LVCMOS33 } [get_ports { JD[2] }]; #IO_L17P_T2_35 Sch=jd[2]
#set_property -dict { PACKAGE_PIN G1 IOSTANDARD LVCMOS33 } [get_ports { JD[3] }]; #IO_L17N_T2_35 Sch=jd[3]
#set_property -dict { PACKAGE_PIN G3 IOSTANDARD LVCMOS33 } [get_ports { JD[4] }]; #IO_L20N_T3_35 Sch=jd[4]
#set_property -dict { PACKAGE_PIN H2 IOSTANDARD LVCMOS33 } [get_ports { JD[7] }]; #IO_L15P_T2_DQS_35 Sch=jd[7]
#set_property -dict { PACKAGE_PIN G4 IOSTANDARD LVCMOS33 } [get_ports { JD[8] }]; #IO_L20P_T3_35 Sch=jd[8]
#set_property -dict { PACKAGE_PIN G2 IOSTANDARD LVCMOS33 } [get_ports { JD[9] }]; #IO_L15N_T2_DQS_35 Sch=jd[9]
#set_property -dict { PACKAGE_PIN F3 IOSTANDARD LVCMOS33 } [get_ports { JD[10] }]; #IO_L13N_T2_MRCC_35 Sch=jd[10]
##Pmod Header JXADC
#set_property -dict { PACKAGE_PIN A14 IOSTANDARD LVCMOS33 } [get_ports { XA_N[1] }]; #IO_L9N_T1_DQS_AD3N_15 Sch=xa_n[1]
#set_property -dict { PACKAGE_PIN A13 IOSTANDARD LVCMOS33 } [get_ports { XA_P[1] }]; #IO_L9P_T1_DQS_AD3P_15 Sch=xa_p[1]
#set_property -dict { PACKAGE_PIN A16 IOSTANDARD LVCMOS33 } [get_ports { XA_N[2] }]; #IO_L8N_T1_AD10N_15 Sch=xa_n[2]
#set_property -dict { PACKAGE_PIN A15 IOSTANDARD LVCMOS33 } [get_ports { XA_P[2] }]; #IO_L8P_T1_AD10P_15 Sch=xa_p[2]
#set_property -dict { PACKAGE_PIN B17 IOSTANDARD LVCMOS33 } [get_ports { XA_N[3] }]; #IO_L7N_T1_AD2N_15 Sch=xa_n[3]
#set_property -dict { PACKAGE_PIN B16 IOSTANDARD LVCMOS33 } [get_ports { XA_P[3] }]; #IO_L7P_T1_AD2P_15 Sch=xa_p[3]
#set_property -dict { PACKAGE_PIN A18 IOSTANDARD LVCMOS33 } [get_ports { XA_N[4] }]; #IO_L10N_T1_AD11N_15 Sch=xa_n[4]
#set_property -dict { PACKAGE_PIN B18 IOSTANDARD LVCMOS33 } [get_ports { XA_P[4] }]; #IO_L10P_T1_AD11P_15 Sch=xa_p[4]
##VGA Connector
#set_property -dict { PACKAGE_PIN A3 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[0] }]; #IO_L8N_T1_AD14N_35 Sch=vga_r[0]
#set_property -dict { PACKAGE_PIN B4 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[1] }]; #IO_L7N_T1_AD6N_35 Sch=vga_r[1]
#set_property -dict { PACKAGE_PIN C5 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[2] }]; #IO_L1N_T0_AD4N_35 Sch=vga_r[2]
#set_property -dict { PACKAGE_PIN A4 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[3] }]; #IO_L8P_T1_AD14P_35 Sch=vga_r[3]
#set_property -dict { PACKAGE_PIN C6 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[0] }]; #IO_L1P_T0_AD4P_35 Sch=vga_g[0]
#set_property -dict { PACKAGE_PIN A5 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[1] }]; #IO_L3N_T0_DQS_AD5N_35 Sch=vga_g[1]
#set_property -dict { PACKAGE_PIN B6 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[2] }]; #IO_L2N_T0_AD12N_35 Sch=vga_g[2]
#set_property -dict { PACKAGE_PIN A6 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[3] }]; #IO_L3P_T0_DQS_AD5P_35 Sch=vga_g[3]
#set_property -dict { PACKAGE_PIN B7 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[0] }]; #IO_L2P_T0_AD12P_35 Sch=vga_b[0]
#set_property -dict { PACKAGE_PIN C7 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[1] }]; #IO_L4N_T0_35 Sch=vga_b[1]
#set_property -dict { PACKAGE_PIN D7 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[2] }]; #IO_L6N_T0_VREF_35 Sch=vga_b[2]
#set_property -dict { PACKAGE_PIN D8 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[3] }]; #IO_L4P_T0_35 Sch=vga_b[3]
#set_property -dict { PACKAGE_PIN B11 IOSTANDARD LVCMOS33 } [get_ports { VGA_HS }]; #IO_L4P_T0_15 Sch=vga_hs
#set_property -dict { PACKAGE_PIN B12 IOSTANDARD LVCMOS33 } [get_ports { VGA_VS }]; #IO_L3N_T0_DQS_AD1N_15 Sch=vga_vs
##Micro SD Connector
#set_property -dict { PACKAGE_PIN E2 IOSTANDARD LVCMOS33 } [get_ports { SD_RESET }]; #IO_L14P_T2_SRCC_35 Sch=sd_reset
#set_property -dict { PACKAGE_PIN A1 IOSTANDARD LVCMOS33 } [get_ports { SD_CD }]; #IO_L9N_T1_DQS_AD7N_35 Sch=sd_cd
#set_property -dict { PACKAGE_PIN B1 IOSTANDARD LVCMOS33 } [get_ports { SD_SCK }]; #IO_L9P_T1_DQS_AD7P_35 Sch=sd_sck
#set_property -dict { PACKAGE_PIN C1 IOSTANDARD LVCMOS33 } [get_ports { SD_CMD }]; #IO_L16N_T2_35 Sch=sd_cmd
#set_property -dict { PACKAGE_PIN C2 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[0] }]; #IO_L16P_T2_35 Sch=sd_dat[0]
#set_property -dict { PACKAGE_PIN E1 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[1] }]; #IO_L18N_T2_35 Sch=sd_dat[1]
#set_property -dict { PACKAGE_PIN F1 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[2] }]; #IO_L18P_T2_35 Sch=sd_dat[2]
#set_property -dict { PACKAGE_PIN D2 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[3] }]; #IO_L14N_T2_SRCC_35 Sch=sd_dat[3]
##Accelerometer
#set_property -dict { PACKAGE_PIN E15 IOSTANDARD LVCMOS33 } [get_ports { ACL_MISO }]; #IO_L11P_T1_SRCC_15 Sch=acl_miso
#set_property -dict { PACKAGE_PIN F14 IOSTANDARD LVCMOS33 } [get_ports { ACL_MOSI }]; #IO_L5N_T0_AD9N_15 Sch=acl_mosi
#set_property -dict { PACKAGE_PIN F15 IOSTANDARD LVCMOS33 } [get_ports { ACL_SCLK }]; #IO_L14P_T2_SRCC_15 Sch=acl_sclk
#set_property -dict { PACKAGE_PIN D15 IOSTANDARD LVCMOS33 } [get_ports { ACL_CSN }]; #IO_L12P_T1_MRCC_15 Sch=acl_csn
#set_property -dict { PACKAGE_PIN B13 IOSTANDARD LVCMOS33 } [get_ports { ACL_INT[1] }]; #IO_L2P_T0_AD8P_15 Sch=acl_int[1]
#set_property -dict { PACKAGE_PIN C16 IOSTANDARD LVCMOS33 } [get_ports { ACL_INT[2] }]; #IO_L20P_T3_A20_15 Sch=acl_int[2]
##Temperature Sensor
#set_property -dict { PACKAGE_PIN C14 IOSTANDARD LVCMOS33 } [get_ports { TMP_SCL }]; #IO_L1N_T0_AD0N_15 Sch=tmp_scl
#set_property -dict { PACKAGE_PIN C15 IOSTANDARD LVCMOS33 } [get_ports { TMP_SDA }]; #IO_L12N_T1_MRCC_15 Sch=tmp_sda
#set_property -dict { PACKAGE_PIN D13 IOSTANDARD LVCMOS33 } [get_ports { TMP_INT }]; #IO_L6N_T0_VREF_15 Sch=tmp_int
#set_property -dict { PACKAGE_PIN B14 IOSTANDARD LVCMOS33 } [get_ports { TMP_CT }]; #IO_L2N_T0_AD8N_15 Sch=tmp_ct
##Omnidirectional Microphone
#set_property -dict { PACKAGE_PIN J5 IOSTANDARD LVCMOS33 } [get_ports { M_CLK }]; #IO_25_35 Sch=m_clk
#set_property -dict { PACKAGE_PIN H5 IOSTANDARD LVCMOS33 } [get_ports { M_DATA }]; #IO_L24N_T3_35 Sch=m_data
#set_property -dict { PACKAGE_PIN F5 IOSTANDARD LVCMOS33 } [get_ports { M_LRSEL }]; #IO_0_35 Sch=m_lrsel
##PWM Audio Amplifier
#set_property -dict { PACKAGE_PIN A11 IOSTANDARD LVCMOS33 } [get_ports { AUD_PWM }]; #IO_L4N_T0_15 Sch=aud_pwm
#set_property -dict { PACKAGE_PIN D12 IOSTANDARD LVCMOS33 } [get_ports { AUD_SD }]; #IO_L6P_T0_15 Sch=aud_sd
##USB-RS232 Interface
#set_property -dict { PACKAGE_PIN C4 IOSTANDARD LVCMOS33 } [get_ports { UART_TXD_IN }]; #IO_L7P_T1_AD6P_35 Sch=uart_txd_in
#set_property -dict { PACKAGE_PIN D4 IOSTANDARD LVCMOS33 } [get_ports { UART_RXD_OUT }]; #IO_L11N_T1_SRCC_35 Sch=uart_rxd_out
#set_property -dict { PACKAGE_PIN D3 IOSTANDARD LVCMOS33 } [get_ports { UART_CTS }]; #IO_L12N_T1_MRCC_35 Sch=uart_cts
#set_property -dict { PACKAGE_PIN E5 IOSTANDARD LVCMOS33 } [get_ports { UART_RTS }]; #IO_L5N_T0_AD13N_35 Sch=uart_rts
##USB HID (PS/2)
#set_property -dict { PACKAGE_PIN F4 IOSTANDARD LVCMOS33 } [get_ports { PS2_CLK }]; #IO_L13P_T2_MRCC_35 Sch=ps2_clk
#set_property -dict { PACKAGE_PIN B2 IOSTANDARD LVCMOS33 } [get_ports { PS2_DATA }]; #IO_L10N_T1_AD15N_35 Sch=ps2_data
##SMSC Ethernet PHY
#set_property -dict { PACKAGE_PIN C9 IOSTANDARD LVCMOS33 } [get_ports { ETH_MDC }]; #IO_L11P_T1_SRCC_16 Sch=eth_mdc
#set_property -dict { PACKAGE_PIN A9 IOSTANDARD LVCMOS33 } [get_ports { ETH_MDIO }]; #IO_L14N_T2_SRCC_16 Sch=eth_mdio
#set_property -dict { PACKAGE_PIN B3 IOSTANDARD LVCMOS33 } [get_ports { ETH_RSTN }]; #IO_L10P_T1_AD15P_35 Sch=eth_rstn
#set_property -dict { PACKAGE_PIN D9 IOSTANDARD LVCMOS33 } [get_ports { ETH_CRSDV }]; #IO_L6N_T0_VREF_16 Sch=eth_crsdv
#set_property -dict { PACKAGE_PIN C10 IOSTANDARD LVCMOS33 } [get_ports { ETH_RXERR }]; #IO_L13N_T2_MRCC_16 Sch=eth_rxerr
#set_property -dict { PACKAGE_PIN C11 IOSTANDARD LVCMOS33 } [get_ports { ETH_RXD[0] }]; #IO_L13P_T2_MRCC_16 Sch=eth_rxd[0]
#set_property -dict { PACKAGE_PIN D10 IOSTANDARD LVCMOS33 } [get_ports { ETH_RXD[1] }]; #IO_L19N_T3_VREF_16 Sch=eth_rxd[1]
#set_property -dict { PACKAGE_PIN B9 IOSTANDARD LVCMOS33 } [get_ports { ETH_TXEN }]; #IO_L11N_T1_SRCC_16 Sch=eth_txen
#set_property -dict { PACKAGE_PIN A10 IOSTANDARD LVCMOS33 } [get_ports { ETH_TXD[0] }]; #IO_L14P_T2_SRCC_16 Sch=eth_txd[0]
#set_property -dict { PACKAGE_PIN A8 IOSTANDARD LVCMOS33 } [get_ports { ETH_TXD[1] }]; #IO_L12N_T1_MRCC_16 Sch=eth_txd[1]
#set_property -dict { PACKAGE_PIN D5 IOSTANDARD LVCMOS33 } [get_ports { ETH_REFCLK }]; #IO_L11P_T1_SRCC_35 Sch=eth_refclk
#set_property -dict { PACKAGE_PIN B8 IOSTANDARD LVCMOS33 } [get_ports { ETH_INTN }]; #IO_L12P_T1_MRCC_16 Sch=eth_intn
##Quad SPI Flash
#set_property -dict { PACKAGE_PIN K17 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[0] }]; #IO_L1P_T0_D00_MOSI_14 Sch=qspi_dq[0]
#set_property -dict { PACKAGE_PIN K18 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[1] }]; #IO_L1N_T0_D01_DIN_14 Sch=qspi_dq[1]
#set_property -dict { PACKAGE_PIN L14 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[2] }]; #IO_L2P_T0_D02_14 Sch=qspi_dq[2]
#set_property -dict { PACKAGE_PIN M14 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[3] }]; #IO_L2N_T0_D03_14 Sch=qspi_dq[3]
#set_property -dict { PACKAGE_PIN L13 IOSTANDARD LVCMOS33 } [get_ports { QSPI_CSN }]; #IO_L6P_T0_FCS_B_14 Sch=qspi_csn

View File

@@ -0,0 +1,84 @@
`timescale 1ns / 1ps
module nexys_CYBERcobra_dz(
input CLK100,
input resetn,
input BTND,
input [15:0] SW,
output CA, CB, CC, CD, CE, CF, CG,
output [7:0] AN
);
CYBERcobra dut(
.clk_i(btn),
.rst_i(!resetn),
.sw_i(SW[15:0]),
.out_o(out)
);
localparam pwm = 1000;
reg [9:0] counter;
reg [3:0] semseg;
reg [7:0] ANreg;
reg CAr, CBr, CCr, CDr, CEr, CFr, CGr;
reg btn;
wire [31:0] out;
assign AN[7:0] = ANreg[7:0];
assign {CA, CB, CC, CD, CE, CF, CG} = {CAr, CBr, CCr, CDr, CEr, CFr, CGr};
always @(posedge CLK100) begin
if (!resetn) begin
counter <= 'b0;
ANreg[7:0] <= 8'b11111111;
{CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b1111111;
btn <= BTND;
end
else begin
btn <= BTND;
if (counter < pwm) counter = counter + 'b1;
else begin
counter = 'b0;
ANreg[1] <= ANreg[0];
ANreg[2] <= ANreg[1];
ANreg[3] <= ANreg[2];
ANreg[4] <= ANreg[3];
ANreg[5] <= ANreg[4];
ANreg[6] <= ANreg[5];
ANreg[7] <= ANreg[6];
ANreg[0] <= !(ANreg[6:0] == 7'b1111111);
end
case (1'b0)
ANreg[0]: semseg <= out[3 : 0];
ANreg[1]: semseg <= out[7 : 4];
ANreg[2]: semseg <= out[11: 8];
ANreg[3]: semseg <= out[15:12];
ANreg[4]: semseg <= out[19:16];
ANreg[5]: semseg <= out[23:20];
ANreg[6]: semseg <= out[27:24];
ANreg[7]: semseg <= out[31:28];
endcase
case (semseg)
4'h0: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0000001;
4'h1: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b1001111;
4'h2: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0010010;
4'h3: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0000110;
4'h4: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b1001100;
4'h5: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0100100;
4'h6: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0100000;
4'h7: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0001111;
4'h8: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0000000;
4'h9: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0000100;
4'hA: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0001000;
4'hB: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b1100000;
4'hC: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0110001;
4'hD: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b1000010;
4'hE: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0110000;
4'hF: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0111000;
default: {CAr,CBr,CCr,CDr, CEr, CFr, CGr} <= 7'b0111111;
endcase
end
end
endmodule

View File

@@ -0,0 +1,77 @@
`timescale 1ns / 1ps
module nexys_CYBERcobra(
input CLK100,
input resetn,
input BTND,
input [15:0] SW,
output CA, CB, CC, CD, CE, CF, CG,
output [7:0] AN
);
CYBERcobra dut(
.clk_i(CLK100),
.rst_i(!resetn),
.sw_i({7'b0,splash,SW[7:0]}),
.out_o(out)
);
localparam pwm = 1000;
reg [9:0] counter;
reg [3:0] semseg;
reg [7:0] ANreg;
reg CAr, CBr, CCr, CDr, CEr, CFr, CGr;
reg [3:0] btn;
reg [10:0] btn_reg;
wire splash;
wire [31:0] out;
assign AN[7:0] = ANreg[7:0];
assign {CA, CB, CC, CD, CE, CF, CG} = {CAr, CBr, CCr, CDr, CEr, CFr, CGr};
assign splash = ((btn == 4'b1111) ^ btn_reg[10]) && (btn == 4'b1111);
always @(posedge CLK100) begin
if (!resetn) begin
counter <= 'b0;
ANreg[7:0] <= 8'b11111111;
{CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b1111111;
btn <= 4'b0;
btn_reg <= 0;
end
else begin
btn <= (btn << 1'b1) + BTND;
btn_reg <= (btn_reg << 1'b1) + (btn == 4'b1111);
if (counter < pwm) counter = counter + 'b1;
else begin
counter = 'b0;
ANreg[1] <= ANreg[0];
ANreg[2] <= ANreg[1];
ANreg[3] <= ANreg[2];
ANreg[4] <= ANreg[3];
ANreg[5] <= ANreg[4];
ANreg[6] <= ANreg[5];
ANreg[7] <= ANreg[6];
ANreg[0] <= !(ANreg[6:0] == 7'b1111111);
end
case (1'b0)
ANreg[0]: semseg <= out[3 : 0];
ANreg[1]: semseg <= out[7 : 4];
ANreg[2]: semseg <= out[11: 8];
ANreg[3]: semseg <= out[15:12];
ANreg[4]: semseg <= out[19:16];
ANreg[5]: semseg <= out[23:20];
ANreg[6]: semseg <= out[27:24];
ANreg[7]: semseg <= out[31:28];
endcase
case (semseg)
4'h1: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b1110001; //L
4'h3: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0110110; //?
4'h8: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0000001; //O
4'hA: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b0001000; //A
4'hC: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b1001000; //H
default: {CAr, CBr, CCr, CDr, CEr, CFr, CGr} <= 7'b1111111; //
endcase
end
end
endmodule

View File

@@ -0,0 +1,24 @@
E1
FF
FF
0F
02
00
00
20
23
00
00
00
01
60
04
10
E0
5F
04
4F
00
00
04
80

View File

@@ -0,0 +1,52 @@
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: MIET
// Engineer: Nikita Bulavin
//
// Create Date:
// Design Name:
// Module Name: tb_cybercobra
// Project Name: RISCV_practicum
// Target Devices: Nexys A7-100T
// Tool Versions:
// Description: tb for CYBERcobra 3000 Pro 2.1
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module tb_CYBERcobra();
CYBERcobra dut(
.clk_i(clk),
.rst_i(rstn),
.sw_i (sw_i ),
.out_o(OUT)
);
wire [31:0] OUT;
reg clk;
reg rstn;
reg [15:0] sw_i;
initial clk <= 0;
always #5 clk = ~clk;
initial begin
$display( "\nStart test: \n\n==========================\nCLICK THE BUTTON 'Run All'\n==========================\n"); $stop();
rstn = 1'b1;
#10;
rstn = 1'b0;
sw_i = 16'b100001000; //значение, до которого считает счетчик
//#260;
//sw_i = 15'b0;
#10000;
$display("\n The test is over \n See the internal signals of the CYBERcobra on the waveform \n");
$finish;
end
endmodule

View File

@@ -0,0 +1,313 @@
# Написание программы под процессор CYBERcobra
Чтобы максимально "прочувствовать" принцип работы созданного вами процессора, вам необходимо написать один из [вариантов программ](#индивидуальные-задания). Вариант выдается преподавателем.
Порядок выполнения задания следующий:
1. В первую очередь необходимо ознакомиться с заданием и изучить пример, приведенный в конце задания. Если возникли вопросы по заданию или примеру, свяжитесь с преподавателем. Чем лучше вы понимаете что от вас ожидают, тем проще будет выполнить задание.
2. Составьте алгоритм работы программы (буквально возьмите листочек, и нарисуйте блок-схему). Прежде чем вы погрузитесь в увлекательное приключение с ноликами и единицами, вам нужно составить четкую карту вашего путешествия.
3. Проверьте вашу блок-схему на данных из примера. Если все сошлось, проверьте вашу блок-схему на других данных. Не забывайте про краевые случаи (отрицательные числа, деление на ноль, переполнения и прочее — для каждого задания они могут быть разными).
4. После того как вы убедились в работоспособности алгоритма на всех возможных данных, наступает время претворить его в виде двоичной программы.
5. Программа описывается в текстовом файле. Для удобства был написан специальный конвертер, который будет принимать на вход текстовый файл с комментариями и двоичным кодом, разделенным пробелами, а на выход выдавать текстовый файл, которым можно будет проинициализировать память инструкций. Подробнее о конвертере смотрите в разделе [cyberconverter](#cyberconverter). Пример текстового файла, который сможет принять конвертер:
```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
```
6. При реализации условных переходов следует иметь в виду пару правил:
1. Блок ветвления (аналог `if/else`) состоит из двух наборов инструкций:
1. инструкции блока `if` (если условие выполнилось)
2. инструкции блока `else` (если условие не выполнилось)
При этом сразу за инструкцией ветвления описываются инструкции блока `else` (т.к. в случае не выполнения условия перехода, `PC` перейдет к следующей инструкции).
Для того, чтобы после выполнения инструкций блока `else` не начались исполняться инструкции блока `if`, в конце блока этих инструкций необходимо добавить безусловный переход на инструкцию, следующую за инструкциями блока `if`.
2. Если вы реализуете не ветвление (аналог блока `if/else`), а только проверку условия для выполнения какого-то блока инструкций (аналог блока `if` без блока `else`), вы должны помнить, что блок `else` все равно есть, просто в этом блоке нет инструкций. Однако вы, как и в прошлом правиле, должны добавить безусловный переход на инструкцию, следующую за инструкциями блока `if`.
Этого можно избежать, если инвертировать ваше условие. В этом случае, если ваше инвертированное условие выполнится, вы сможете сразу пропустить нужное количество инструкций и начать исполнять инструкцию за пределами вашего блока `if`. Если инвертированное условие не выполнится (т.е. выполнится исходное условие), `PC` перейдет к следующей инструкции, где и будут находиться ваши инструкции блока `if`.
Звучит достаточно запутанно, поэтому давайте рассмотрим пару примеров. Сначала мы запишем нашу идею на языке Си, а затем перенесем её в двоичный код под архитектуру CYBERcobra:
```C
if(reg[1]==reg[5])
{
reg[2] = 10;
reg[3] = 15;
}
else
{
reg[2] = 7;
goto if_end; // Поскольку в памяти программы блок else будет идти
// сразу за инструкцией условного перехода, необходимо
// добавить в конце инструкцию безусловного перехода,
// чтобы не начать исполнять инструкции блока if.
}
if_end:
```
Мы хотим проверить на равенство значения в регистровом файле по адресам `1` и `5`. Если это так, записать значения `10` и `15` по адресам `2` и `3` соответственно. В противном случае, записать значение `7` по адресу `2`.
Это можно реализовать следующей двоичной программой:
```text
//J B WS ALUop RA1 RA2 const WA
0 1 00 11000 00010 00101 00000011 00000 // Если регистры 2 и 5 равны,
// перемести PC на 3 инструкции вперед
// (перешагни через две
// инструкции блока else)
//---------------------------------------
// блок else
//---------------------------------------
0 0 00 00000000000000000000111 00010 // reg[2] = 7
1 0 00 00000 00000 00000 00000011 00000 // goto if_end
//---------------------------------------
//---------------------------------------
// блок if
//---------------------------------------
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
```
Рассмотрим второй пример, где нет блока `else`:
```C
if(reg[1] == reg[5])
{
reg[2] = 10;
reg[3] = 15;
}
```
Как упоминалось ранее, можно реализовать этот условный переход по той же схеме (тогда пример программы на Си примет вид):
```C
if(reg[1] == reg[5])
{
reg[2] = 10;
reg[3] = 15;
}
else
{
goto if_end;
}
if_end:
```
А можно инвертировать условие:
```C
if(reg[1] != reg[5])
{
}
else
{
reg[2] = 10;
reg[3] = 15;
}
```
В этом случае, нет нужды делать безусловный переход на инструкцию, следующую за инструкциями блока `if`, т.к. там нет никаких инструкций.
Такое условие можно реализовать следующей двоичной программой:
```text
//J B WS ALUop RA1 RA2 const WA
0 1 00 11001 00010 00101 00000011 00000 // Если регистры 2 и 5 НЕ РАВНЫ,
// перемести PC на 3 инструкции вперед
// (перешагни через две
// инструкции блока else)
//---------------------------------------
// блок else
//---------------------------------------
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` размещено корректное значение.
12. В случае, если вы не успели довести свой процессор до работоспособного состояния, вы можете проверить программу на готовом процессоре из ветки [`Я-не-смог`](https://github.com/MPSU/APS/tree/%D0%AF-%D0%BD%D0%B5-%D1%81%D0%BC%D0%BE%D0%B3).
## cyberconverter
[cyberconverter](cyberconverter.cpp) — это программа, которая преобразует текстовый файл с инструкциями архитектуры CYBERcobra в текстовый файл, который сможет принять память инструкций.
cyberconverter может обрабатывать файлы, содержащие комментарии (начинающиеся с `//`), пробелы и пустые строки, а так же наборы символов `0` и `1`. Комментарии, пробелы и пустые строки удаляются, после чего оставшиеся строки из 32 нулей и единиц нарезаются на четверки по 8 бит, конвертируются в шестнадцатиричные значения и записываются в выходной файл.
cyberconverter принимает до двух аргументов. Порядок запуска следующий:
1. Вызов справки:
```bash
cyberconverter -h
cyberconverter --help
```
2. Преобразование программы, записанной в файле `test.txt`, с записью результата в файл `program.txt`:
```bash
cyberconverter test.txt program.txt
```
3. Если не указан второй аргумент, результат будет записан в файл:
`<имя_исходногоайла>_converted.<расширение исходного файла>`:
```bash
cyberconverter test.txt
```
Результат будет записан в файл `test_converted.txt`.
4. Если программа будет запущена без аргументов, то исходным файлом будет считаться файл `program.txt`.
В случае отсутствия исходного файла, наличия неподдерживаемых символов или неверной длины инструкции будет выведено сообщение об ошибке.
## Индивидуальные задания
1. Вычислить [циклический сдвиг](https://ru.wikipedia.org/wiki/Битовый_сдвиг#Циклический_сдвиг) вправо `a >> sw_i`.
Пример: `a = 0...01011`, `sw_i = 0...010`.
Результат вычислений: `out_o = 110...010`.
2. Вычислить `a - sw_i` без использования операции вычитания.
Пример: `sw_i = 0...011`, `a = 0...0100`.
Результат вычислений: `out_o = 0...001`.
3. Вычислить [циклический сдвиг](https://ru.wikipedia.org/wiki/Битовый_сдвиг#Циклический_сдвиг) влево `a << sw_i`.
Пример: `a = 10...01011`, `sw_i = 0...10`.
Результат вычислений: `out_o = 0...0101110`.
4. Поменять местами `[7:0]` и `[15:8]` биты числа `sw_i`. Вывести результат в `out_o`.
Пример: `sw_i = 0...010100000_1110010`.
Результат вычислений: `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`.
---
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`.
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`.
8. Посчитать длину окружности при заданном радиусе `sw_i`, считая, что `pi = 3`. Вывести результат в `out_o`.
Пример: `sw_i = 0...010`.
Результат вычислений: `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`.
10. Найти количество нулей в двоичном представлении числа `sw_i`. Вывести результат в `out_o`.
Пример: `sw_i = 1...10110_0010`.
Результат вычислений: `out_o = 0...0101`.
11. Найти наибольший двоичный разряд числа `sw_i`, значение которого равно `1`. Если такого нет, вывести `32`. Вывести результат в `out_o`.
Пример: `sw_i = 0...0110`.
Результат вычислений: `out_o = 0...010`.
12. Сформировать число, состоящее из чётных двоичных разрядов числа `sw_i`. Вывести результат в `out_o`.
Пример: `sw_i = 0...011_1011_1000`.
Результат вычислений `out_o = 0...01_0100`.
13. Найти количество единиц в двоичном представлении числа `sw_i` (обрати внимание, `sw_i` знаковое число). Вывести результат в `out_o`.
Пример: `sw_i = 0...0101_0110`.
Результат вычислений: `out_o = 0...0100`.
14. Найти количество двоичных разрядов, в которых различаются числа `sw_i` и `a`. Вывести результат в `out_o`.
Пример: `sw_i = 0...0110`, `a = 0...01110`.
Результат вычислений: `out_o = 0...01`.
15. Вывести в `out_o` подряд все единицы входного числа `sw_i`.
Пример: `sw_i = 0...01011011011`.
Результат вычислений:`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...0001_0000_0100_0011`.
Результат вычислений: `out_o = 0...01010`.
---
17. Найти остаток от деления `sw_i` на `a`. Вывести результат в `out_o`.
Пример: `sw_i = 0...0101`, `a = 0...010`.
Результат вычислений: `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`.
19. Определить, сколько раз встречается `11` в двоичном представлении `sw_i` без пересечений. Вывести результат в `out_o`.
Пример: `sw_i = 0...01110`.
Результат вычислений: `out_o = 0...01`.
20. Вывести в `out_o` результат целочисленного деления `a/sw_i`.
Пример: `sw_i = 0...010`, `a = 0...0111`.
Результат вычислений: `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`.
22. В числе `sw_i` заменить справа-налево каждое `00` на `11`. Вывести результат в `out_o`.
Пример: `sw_i = 1...101000`.
Результат вычислений: `out_o = 1...101011`.
---
23. Поменять местами чётные биты числа `sw_i` с нечётными битами этого числа (то есть соседние биты поменять местами). Вывести результат в `out_o`.
Пример: `sw_i = 0...01010_0111`.
Результат вычислений: `out_o = 0...0101_1011`.
24. Инвертировать первые `sw_i` бит числа `a`. Вывести результат в `out_o`.
Пример: `sw_i = 0...011`, `a = 0...01010_0011`.
Результат вычислений: `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`.
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`.
---
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`.
```
1011 (11 в двоичном виде)
x 1110 (14 в двоичном виде)
======
0000 (это 1011 x 0)
1011 (это 1011 x 1, сдвинутое на 1 влево)
1011 (это 1011 x 1, сдвинутое на 2 влево)
+ 1011 (это 1011 x 1, сдвинутое на 2 влево)
=========
10011010 (154 в двоичном виде)
```
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`.
<!-- 25. *Зажечь все светодиоды на 50% яркости ([подсказка](http://wiki.amperka.ru/конспект-arduino:шим)) -->
29. Удалить все вхождения `sw_i[2:0]` из `a` со сдвигом вправо (заполняя удаленные области).
Пример: `a = 0...010011010`, `sw_i[2:0] = 101`.
Результат вычислений: `out_o = 0...010010`

View File

@@ -0,0 +1,137 @@
#include <fstream>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
void print_help(const std::string program_name)
{
using namespace std;
cout << "Usage: " << program_name << " [input file] [output file]\n\n";
cout << "Convert CYBERcobra program file into $readmemh acceptable file.\n";
cout << "CYBERcobra program file may contain only comments (starting with \"//\"),\n";
cout << "whitespaces and binary digits '0' or '1'.\n";
cout << "This program will erase this parts from every line and then convert\n";
cout << "32-bits binary strings into 4 little endian 8-bit strings in hex-format.\n\n";
cout << "If output file omitted, the <input_file_base>_converted.<input_file_ext>\n";
cout << "will be produced.\n\n";
cout << "If input file omitted, program.txt will be used.\n\n";
cout << "Example:\n\n";
cout << program_name << " open \"program.txt\" and produce \"program_converted.txt\"\n";
cout << program_name << " test.txt open \"test.txt\" and produce \"test_converted.txt\"\n";
cout << program_name << " test.txt myname.dat open \"test.txt\" and produce \"myname.dat\"\n";
}
int main(int argc, char ** argv)
{
using namespace std;
/*
Parse argument list and print help message if needed
*/
string ifname;
string ofname;
string start;
string end;
string filename = argv[0];
size_t dot_pos;
filename = filename.substr(filename.find_last_of("/\\") + 1);
switch (argc)
{
case 1:
ifname = "program.txt";
ofname = "program_converted.txt";
break;
case 2:
if (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))
{
print_help(filename);
return 0;
}
ifname = argv[1];
dot_pos = ifname.find(".");
if(dot_pos != string::npos)
{
start = ifname.substr(0, dot_pos);
end = ifname.substr(dot_pos, ifname.size() - dot_pos);
ofname = start + "_converted" + end;
}
else
{
ofname = ifname + "_converted";
}
break;
case 3:
ifname = argv[1];
ofname = argv[2];
break;
default:
print_help(filename);
return 0;
}
/*
Program body
*/
// Open input and output files
ifstream ifs(ifname);
if(!ifs)
{
cerr << "Unable to open file: \"" << ifname << "\"" << endl;
return -1;
}
ofstream ofs(ofname);
if (!ofs.is_open())
{
cerr << "Unable to open file: \"" << ofname << "\"" << endl;
return -1;
}
string str;
size_t line_counter = 0;
while (getline(ifs, str))
{
line_counter++;
// trim line from comments and whitespaces, skip empty lines after trimming
auto comment_pos = str.find("//");
if(comment_pos != string::npos)
{
str.erase(comment_pos);
}
str.erase(remove_if(str.begin(), str.end(), ::isspace), str.end());
if(!str.size())
{
continue;
}
if(str.size()!=32)
{
cerr << "line " << line_counter << " length is not equal 32 after trimming comments and whitespaces" << endl;
return -2;
}
// split 32-bits binary line into 4 little-endian hex lines and write them into file
for (size_t i = 0; i < 4; i++)
{
// For every 8-bit part of 32-bit line get int representation.
// If illegal character found, throw error.
size_t valid_char_num;
string byte_substr = str.substr(8*(3-i), 8);
int cur_byte = std::stoi(byte_substr, &valid_char_num, 2);
if(valid_char_num != 8)
{
cerr << "Illegal character '" << byte_substr.at(valid_char_num) <<
"' found in line " << line_counter << ": \"" << str << "\"\n";
cerr << "Should be only '0' or '1'." << endl;
return -3;
}
char hex_byte_str[3];
// convert int representation into hex string
snprintf(hex_byte_str, 3, "%02x", cur_byte);
ofs << hex_byte_str << "\n";
}
}
ifs.close();
ofs.close();
return 0;
}

View File

@@ -0,0 +1,7 @@
//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