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,379 @@
# Лабораторная работа 3 "Регистровый файл и внешняя память"
Процессор — это программно-управляемое устройство выполняющее обработку информации и управление этим процессом. Очевидно, программа, которая управляет процессором, должна где-то храниться. Данные, с которыми процессор работает, тоже должны быть в доступном месте. Нужна память!
## Цель
Описать на языке SystemVerilog элементы памяти для будущего процессора:
- память команд (Instruction Memory);
- память данных (Data Memory);
- регистровый файл (Register File).
## Допуск к лабораторной работе
Для успешного выполнения лабораторной работы, вам необходимо освоить:
- приведенные способы описания [мультиплексоров](../../Basic%20Verilog%20structures/Multiplexors.md)
- способы описания [регистров](../../Basic%20Verilog%20structures/Registers.md)
- [конкатенацию](../../Basic%20Verilog%20structures/Concatenation.md).
## Ход работы
1. Изучить способы организации памяти (раздел [#теория про память](#теория-про-память)).
2. Изучить конструкции SystemVerilog для реализации запоминающих элементов (раздел [#инструменты](#инструменты-для-реализации-памяти)).
3. В проекте с прошлой лабораторной реализовать модули: Instruction Memory, Data Memory и Register File ([#задание](#задание-по-реализации-памяти)).
4. Проверить с помощью тестового окружения корректность их работы.
5. Проверить работу регистрового файла в ПЛИС.
## Теория про память
Память — это устройство для упорядоченного хранения и выдачи информации. Различные запоминающие устройства отличаются способом и организацией хранения данных. Базовыми характеристиками памяти являются:
- V — объем (количество бит данных, которые единовременно может хранить память);
- a — разрядность адреса (ширина шины адреса, определяет адресное пространство — количество адресов отдельных ячеек памяти);
- d — разрядность хранимых данных (разрядность ячейки памяти, как правило совпадает с разрядностью входных/выходных данных).
В общем случае `V = 2^a * d`.
Для объема памяти в 1 KiB ([кибибайт](https://ru.wikipedia.org/wiki/%D0%9A%D0%B8%D0%B1%D0%B8%D0%B1%D0%B0%D0%B9%D1%82), 1024 байта или 8192 бита) разрядность адреса может быть, например, 10 бит (что покрывает 2^10 = 1024 адреса), тогда разрядность хранимых данных должна быть 8 бит. 1024 * 8 = 8192, то есть 1 кибибайт. Если разрядность адреса, например, 8 бит (что покрывает 2^8 = 256 адресов), то разрядность данных `d = V / 2^a` это 8192 / 256 = 32 бита.
Однако, может быть такое, что не все ячейки памяти реально реализованы на кристалле микросхемы, то есть некоторые адреса существуют, но по ним не имеет смысла обращаться, а объем памяти, соответственно, не равен `V ≠ 2^a * d` — он меньше. Подобные случаи будут рассмотрены отдельно.
Память можно разделить на категории: ПЗУ (постоянное запоминающее устройство) и ОЗУ (оперативное запоминающее устройство). Из ПЗУ можно только считывать информацию, которая попадает в ПЗУ до начала использования памяти и не может изменяться в процессе работы. Из ОЗУ можно считывать и записывать информацию. В самом простом случае ПЗУ имеет один вход адреса `addr` и один выход считываемых данных `read_data`. На вход `addr` подается адрес требуемой ячейки памяти, на выходе `read_data` появляются данные, которые хранятся по этому адресу.
Для ОЗУ требуется больше сигналов. Кроме входного `addr` и выходного `read_data` добавляются: входные данные для записи `write_data`, сигнал синхронизации `clk`, который определяет момент записи данных и сигнал разрешения на запись `write_enable`, который контролирует нужно ли записывать данные или только считывать. Для того, чтобы записать информацию в такую память необходимо:
- выставить адрес `addr` в который планируется запись данных,
- выставить сами данные для записи на вход `write_data`,
- установить сигнал `write_enable` в состояние разрешения записи (как правило это 1) и
- дождаться нужного фронта `clk` — в этот момент данные будут записаны по указанному адресу. При этом, на выходе `read_data` будут старые данные, хранящиеся по адресу `addr`. На одном такте происходит одновременное считывание информации и запись новой.
Так же возможна реализация, в которой вход `read_data` и выход `write_data` объединены в единый вход/выход `data`. В этом случае операции чтения и записи разделены во времени и используют для этого один единый порт ввода-вывода (`inout`, двунаправленный порт) `data`.
![../../.pic/Labs/lab_03_memory/fig_01.drawio.png](../../.pic/Labs/lab_03_memory/fig_01.drawio.png)
*Рисунок 1. Примеры блоков ПЗУ и ОЗУ*
Кроме того, различают память с **синхронным** и **асинхронным** чтением. В первом случае, перед выходным сигналом шины данных ставится дополнительный регистр, в который по тактовому синхроимпульсу записываются запрашиваемые данные. Такой способ может очень сильно сократить **критический путь** цифровой схемы, но требует дополнительный такт на доступ в память. В свою очередь, асинхронное чтение позволяет получить данные не дожидаясь очередного синхроимпульса, но такой способ увеличивает критический путь.
Еще одной характеристикой памяти является количество доступных портов. Количество портов определяет к скольким ячейкам памяти можно обратиться одновременно. Проще говоря, сколько входов адреса существует. Все примеры памяти рассмотренные выше являются **однопортовыми**, то есть у них один порт. Например, если у памяти 2 входа адреса `addr1` и `addr2` — это **двухпортовая память**. При этом не важно, можно ли по этим адресам только читать/писать или выполнять обе операции.
Регистровый файл, который будет реализован в рамках данной работы, является **трехпортовым**, и имеет 2 порта на чтение и 1 порт на запись.
С точки зрения аппаратной реализации память в ПЛИС может быть **блочной**, **распределенной** или **регистровой**. **Блочная память** — это аппаратный блок памяти, который можно сконфигурировать под свои нужды. **Распределенная** и **регистровая** память (в отличие от блочной) реализуется на **конфигурируемых логических блоках** (см. [как работает ПЛИС](../../Introduction/How%20FPGA%20works.md)). Такая память привязана к расположению конфигурируемых логических блоков ПЛИС и как бы равномерно распределена по всему кристаллу. Вместо реализации логики конфигурируемые логические блоки используются для нужд памяти. Чтобы понять почему это возможно, рассмотрим структуру логического блока:
![../../.pic/Labs/lab_03_memory/fig_02.png](../../.pic/Labs/lab_03_memory/fig_02.png)
*Рисунок 2. Структурная схема логического блока в ПЛИС*
В логическом блоке есть **таблицы подстановки**(Look Up Table, LUT), которые представляют собой не что иное как память, которая переконфигурируется под нужды хранения, а не реализацию логики. Таким образом, трехвходовой LUT может выступать в роли восьмиразрядной памяти.
Однако LUT будет сложно приспособить под многопортовую память: посмотрим на схему еще раз: три входа LUT формируют адрес одной из восьми ячеек. Это означает, что среди этих восьми ячеек нельзя обратиться к двум из них одновременно.
Для реализации многопортовой памяти небольшого размера лучше воспользоваться расположенным в логическом блоке D-триггере (**DFF** на *рис. 2*). Не смотря на то, что D-триггер позволяет воспроизвести только 1 разряд элемента памяти, он не ограничивает реализацию по портам.
Таким образом, плюс распределенной памяти относительно регистровой заключается в лучшей утилизации ресурсов: на восемь разрядов памяти у нас уйдет один трехвходовой LUT, в то время как для восьми разрядов регистровой памяти у нас уйдет 8 D-триггеров.
Минусом является ограниченность в реализации многопортовой памяти.
Сравним блочную память с распределенной/регистровой: поскольку большой объем памяти съест много логических блоков при реализации распределенной/регистровой памяти, такую память лучше делать в виде блочной.
В то же время, к плюсам распределенной/регистровой памяти можно отнести возможность синтезировать память с асинхронным портом на чтение, чем мы и воспользуемся при реализации однотактного процессора (если бы порт чтения памяти был синхронным, нам потребовалось ждать один такт, чтобы получить инструкцию из памяти инструкций или данные из регистрового файла, что затруднило бы реализацию однотактного процессора, где каждая инструкция должна выполняться ровно за один такт).
Обычно синтезатор сам понимает, какой вид памяти подходит под описанную схему на языке SystemVerilog.
В случае, если под описанную схему подходит несколько видов памяти, есть возможность выбрать конкретную вручную, причем способы могут различаться от производителя к производителю, поэтому за подробностями лучше обращаться к документации. Например у Xilinx за это отвечает [следующий раздел](https://docs.xilinx.com/r/en-US/ug901-vivado-synthesis/RAM-HDL-Coding-Techniques) документации по синтезу.
## Инструменты для реализации памяти
### Описание памяти на языке SystemVerilog
Память на языке SystemVerilog объявляется [подобно регистрам](../../Basic%20Verilog%20structures/Registers.md), используя ключевое слово `logic`. Но, кроме разрядности (разрядности ячеек памяти, в данном случае) после имени регистра (памяти, в данном случае) указывается количество создаваемых ячеек либо в виде натурального числа, либо в виде диапазона адресов этих ячеек.:
```SystemVerilog
logic [19:0] memory1 [16]; // memory1 и memory2 являются полностью
logic [19:0] memory2 [0:15]; // идентичными памятями.
logic [19:0] memory3 [15:0]; // memory3 будет такой же памятью, что и
// предыдущие, но на временной диаграмме
// Vivado при ее отображении сперва будут
// идти ячейки, начинающиеся со старших
// адресов (что в рамках данного курса
// лабораторных работ будет скорее минусом).
logic [19:0] memory3 [1:16]; // А вот memory3 хоть и совпадает по
// размеру с предыдущими реализациями,
// но отличается по адресному пространству
// обращение по нулевому адресу выдаст
// недетерминированный результат. Это не
// значит что память будет плохой или
// дефектной, просто надо учитывать эту её
// особенность.
```
В приведенном листинге `logic [19:0] memory1 [16];` создается память с шестнадцатью (от 0-го до 15-го адреса) 20-битными ячейками памяти. В таком случае говорят, что ширина памяти 20 бит, а глубина 16. Для адресации такой памяти потребуется адрес с разрядностью ceil(log2(16)) = 4 бита (`ceil` — операция округления вверх). Это однопортовая память.
Для обращения к конкретной ячейке памяти используются квадратные скобки с указанием нужного адреса `memory[addr]`. Грубо говоря, то, что указывается в квадратных скобках будет подключено ко входу адреса памяти `memory`.
Реализация асинхронного подключения к выходу памяти осуществляется оператором `assign`. А, если требуется создать память с синхронным чтением, то присваивание выходу требуется описать внутри блока`always_ff`.
Так как запись в память является синхронным событием, то описывается она в конструкции `always_ff`. При этом, как и при описании регистра, можно реализовать управляющий сигнал разрешения на запись через блок вида `if(write_enable)`.
```SystemVerilog
module mem16_20 ( // создать блок с именем mem16_20
input logic clk, // вход синхронизации
input logic [3:0] addr, // адресный вход
input logic [19:0] write_data, // вход данных для записи
input logic write_enable, // сигнал разрешения на запись
output logic [19:0] async_read_data// асинхронный выход считанных данных
output logic [19:0] sync_read_data // синхронный выход считанных данных
);
logic [19:0] memory [0:15]; // создать память с 16-ю
// 20-битными ячейками
// асинхронное чтение
assign read_data = memory[addr]; // подключить к выходу async_read_data
// ячейку памяти по адресу addr
// (асинхронное чтение)
// синхронное чтение
always_ff(@posedge clk) begin // поставить перед выходом sync_read_data
sync_read_data <= memory[addr]; // регистр, в который каждый такт будут
end // записываться считываемые данные
// запись
always_ff @ (posedge clk) begin // каждый раз по фронту clk
if(write_enable) begin // если сигнал write_enable == 1, то
memory[addr] <= write_data; // в ячейку по адресу addr будут записаны
// данные сигнала write_data
end
end
endmodule
```
В случае реализации ПЗУ нет необходимости в описании входов для записи. В таком случае описание памяти занимает всего пару строк. Чтобы инициализировать такую память (то есть поместить в нее начальные значения, чтобы было что из нее читать), требуемое содержимое нужно добавить к прошивке, вместе с которой данные попадут в ПЛИС. Для этого в проект добавляется текстовый файл с содержимым памяти (более подробно об этом в [`Как добавить файл с содержимым памяти в проект`](../../Vivado%20Basics/How%20to%20add%20a%20mem-file.md)). Для того, чтобы отметить данный файл в качестве инициализирующего память, необходимо использовать одну из двух системных функций: `$readmemh` и `$readmemb`. `$readmemh` используется для файлов, описывающих содержимое памяти 16-ричными цифрами, в то время как `$readmemb` используется для файлов, описывающих содержимое памяти двоичными цифрами. Любую из этих системных функций необходимо поместить внутрь блока `initial`. У этих системных функций есть два обязательных аргумента:
- имя инициализирующего файла
- имя инициализируемой памяти
и два опциональных:
- стартовый адрес, начиная с которого память будет проинициализирована данным файлом (по-умолчанию равен нулю)
- конечный адрес, на котором инициализация закончится (даже если в файле были ещё какие-то данные).
Пример полного вызова одной из этих системных функций выглядит так:
`$readmemb("<data file name>",<memory name>,<start address>,<end address>);`
Однако на деле обычно используются только обязательные аргументы:
`$readmemb("<data file name>",<memory name>);`
Пример описанной выше памяти:
```SystemVerilog
module rom16_8 (
input logic [3:0] addr1, // первый 4-битный адресный вход
input logic [3:0] addr2, // второй 4-битный адресный вход
output logic [7:0] read_data1, // первый 8-битный выход считанных данных
output logic [7:0] read_data2 // второй 8-битный выход считанных данных
);
logic [7:0] ROM [0:15]; // создать память с 16-ю 8-битными ячейками
initial begin
$readmemh("mem.txt", ROM); // поместить в память RAM содержимое
end // файла mem.txt
assign read_data1 = R0M[addr1]; // реализация первого порта на чтение
assign read_data2 = R0M[addr2] // реализация второго порта на чтение
endmodule
```
Содержимое файла `mem.txt`, к примеру может быть таким (каждая строка соответствует значению отдельной ячейки памяти, начиная со стартового адреса):
```hex
FA
E6
0D
15
A7
```
## Задание по реализации памяти
Необходимо описать на языке SystemVerilog следующие три вида памяти:
### 1. Память инструкций
У данной памяти будет два порта:
- 32-битный вход адреса
- 32-битный выход данных (асинхронное чтение)
```SystemVerilog
mоdulе instr_mеm(
inрut logic [31:0] addr_i,
оutрut logic [31:0] rеаd_dаtа_o
);
```
Из [теории про память](#теория-про-память) вы могли догадаться, что такой модуль описывает память ёмкостью `4 GiB`. Однако в реальности, наша память будет куда меньше (в ПЛИС попросту не хватит ресурсов на реализацию памяти подобного объёма).
На практике, внутри данного модуля вы должны будете реализовать память с 1024-мя 8-битными ячейками.
Как так вышло, что разрядность ячеек отличается от разрядности выходного сигнала?
Дело в том, что по спецификации процессор RISC-V должен работать с памятью с побайтовой адресацией. Байтовая адресация означает, что процессор способен обращаться к отдельным байтам в памяти (за каждым байтом памяти закреплен свой индивидуальный адрес).
Именно поэтому, ячейки памяти должны быть восьмибитными.
Однако в то же время, инструкции процессора будут 32-битными (состоящими из 4 подряд идущих байт), поэтому выходной сигнал данной памяти будет 32-разрядным.
Как сделать так, чтобы подавать на выход 4 подряд идущих байта? С помощью [конкатенации](../../Basic%20Verilog%20structures/Concatenation.md).
На вход реализуемого вами модуля приходит адрес младшего байта инструкции, которую вы должны вернуть (этот адрес всегда будет кратен четырем, поскольку в наших лабах инструкции всегда будут состоять из 4 байт).
На выход необходимо подать 32-разрядную склейку:
![../../.pic/Labs/lab_03_memory/fig_03.drawio.png](../../.pic/Labs/lab_03_memory/fig_03.drawio.png)
*Рисунок 3. Пример склейки для чтения*
Обращение в эту память по адресам, превышающим `1020` должны выдавать значение `32'd0`. Почему именно `1020`? Если обратиться по адресу `1021` (чего произойти не может, т.к. адрес всегда будет кратен четырем, но предположим что такое обращение все-таки произошло), то вы должны будете вернуть данные из ячеек памяти по адресам: `1021`, `1022`, `1023` и `1024`. При этом ячейки по адресу 1024 уже не будет (ведь 1024 ячейки расположатся по адресам `[0:1023]`).
Как реализовать подобный функционал? Разумеется, с помощью [мультиплексора](../../Basic%20Verilog%20structures/Multiplexors.md).
В данном случае, вы можете либо воспользоваться конструкцией `if-else` в блоке `always_comb`, либо **тернарным условным оператором** во время реализации **непрерывного присваивания**. В любом из случаев, вам нужно будет реализовать условие того, что пришедшее значение на шине адреса попадает в диапазон допустимых адресов. Если это так, вернуть склейку данных из памяти, начинающихся с этого адреса, в противном случае — вернуть 0.
### 2. Память данных
У данной памяти будет 6 портов:
- вход тактового синхроимпульса
- вход запроса на работу с памятью
- вход сигнала разрешения записи
- 32-битный вход адреса
- 32-битный вход данных записи
- 32-битный выход данных синхронного чтения
```SystemVerilog
mоdulе data_mеm(
inрut logic clk_i,
input logic mem_req_i,
inрut logic write_enable_i,
inрut logic [31:0] addr_i,
inрut logic [31:0] write_data_i,
оutрut logic [31:0] rеаd_dаtа_o
);
```
Идея этой памяти повторяет идею памяти инструкций: несмотря на разрядность адреса и данных, внутри будет память, состоящая из 4096-и 8-битных ячеек.
Однако в отличие от памяти инструкций, в память данных добавлено два управляющих сигнала (`mem_req_i`и `write_enable_i`). Сигнал `mem_req_i` является сигналом запроса на работу с памятью. Без этого сигнала память не должна выполнять операции чтения/записи (вне зависимости от сигнала `write_enable`, определяющего происходит сейчас запись или чтение). Как сделать так, чтобы не происходило чтение без запроса? Например возвращать на шину чтения специальное "магическое число".
- В случае `mem_req_i == 0` или `write_enable_i == 1` (т.е. когда не выполняется операция), на выходе `read_data_o` должно оказаться значение `32'hfa11_1eaf` (поскольку `1` схожа с латинским символом `l`, это выражение можно прочесть как `fall_leaf`).
- В случае, если `mem_req_i == 1` и значение `addr_i` **попадает** в диапазон [0:4095], на выходе `read_data_o` должна оказаться склейка из 4 значений ячеек памяти, начиная ячейки, размещенной по пришедшему адресу (обратите внимание, что в отличие от памяти инструкций, мы **не уменьшаем** диапазон допустимых значений до `[0:4092]`, поскольку архитектура RISC-V допускает обращение в одну отдельную ячейку памяти).
- В случае, если `mem_req_i == 1` и значение `addr_i` **не попадает** в диапазон [0:4095], на выходе `read_data_o` должно оказаться магическое число `32'hdead_beef`.
Зачем нужны эти магические числа `32'hfa11_1eaf` и `32'hdead_beef`? У этих чисел легко узнаваемая сигнатура, позволяющая обратить на них внимание. В случае, если при чтении из памяти в регистровый файл попадут эти значения, увидев их вы сможете почувствовать что "что-то не то", и проверить: а было ли в памяти по указанному адресу действительно такое значение (в отличие от значения `32'h0000_0000`, которое не вызовет у вас никаких вопросов). Вероятность того, что такие числа возникнут в естественном ходе работы программы достаточно мала, а значит скорее всего если вы встретите эти числа — это сигнал того, что что-то в вашем процессоре работает неправильно (например, произошло обращение за пределы памяти, или неправильно формируется сигнал `mem_req_i`).
Если `mem_req_i == 1` и `write_enable_i == 1` происходит запрос на запись в память. В этом случае, необходимо по положительному фронту `clk_i` записать в четыре подряд идущие ячейки, начиная с той, что расположена по адресу `addr_i` соответствующие байты с шины `write_data_i`. Во всех других случаях (любой из сигналов `mem_req_i`, `write_enable_i` равен нулю), запись в память не производится.
![../../.pic/Labs/lab_03_memory/fig_04.drawio.png](../../.pic/Labs/lab_03_memory/fig_04.drawio.png)
*Рисунок 4. Пример склейки для записи*
### 3. Регистровый файл
У данного модуля будет 8 портов:
- вход тактового синхроимпульса
- вход сигнала разрешения записи
- 5-битный вход первого адреса чтения
- 5-битный вход второго адреса чтения
- 5-битный вход адреса записи
- 32-битный вход данных записи
- 32-битный выход данных асинхронного чтения по первому адресу
- 32-битный выход данных асинхронного чтения по второму адресу
```SystemVerilog
mоdulе rf_r𝚒sсv(
inрut logic сlk_i,
inрut logic write_enable_i,
inрut logic [ 4:0] write_addr_i,
inрut logic [ 4:0] read_addr1_i,
inрut logic [ 4:0] read_addr2_i,
inрut logic [31:0] write_data_i,
оutрut logic [31:0] read_data1_o,
оutрut logic [31:0] read_data2_o
);
```
На языке SystemVerilog необходимо реализовать модуль регистрового файла (`rf_r𝚒sсv`) для процессора с архитектурой RISC-V, представляющего собой трехпортовую ОЗУ с двумя портами на чтение и одним портом на запись и состоящей из 32-х 32-битных регистров с именем `rf_mem`.
При этом по адресу `0` должно всегда считываться значение `0` вне зависимости от того, какое значение в этой ячейке памяти, и есть ли она вообще. Такая особенность обусловлена тем, что при выполнении операций очень часто используется ноль (сравнение с нулем, инициализация переменных нулевым значением, копирование значения одного регистра в другой посредством сложения с нулем и записи результата и т.п.).
Как и в случае реализации [памяти инструкций](#1-память-инструкций), вы можете решить эту задачу с помощью мультиплексора, управляющим сигналом которого является сигнал сравнения адреса на чтение с нулем.
Либо же можно проинициализировать нулевую ячейку памяти нулем с запретом записи в неё каких-либо значений. В этом случае в ячейке всегда будет ноль, а значит и считываться с нулевого адреса будет только он.
### 4. Проверка в ПЛИС
Последним этапом будет проверка работоспособности вашего регистрового файла в ПЛИС (файлы для прошивки как обычно расположены в папке [board files](board%20files)).
Разработанные блоки будут использованы при реализации процессора `CYBERcobra 3000 Pro 2.0` и последующих лабораторных для реализации системы с процессором с архитектурой RISC-V.
## Порядок выполнения работы
1. Внимательно ознакомьтесь с заданием. В случае возникновения вопросов, проконсультируйтесь с преподавателем.
2. Реализуйте память инструкций. Для этого:
1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `instr_mem.sv`.
2. Опишите в нем модуль памяти инструкций с таким же именем и портами, как указано в задании.
1. Сперва необходимо создать память (массив регистров). Как это сделать, сказано в разделе [описание памяти на языке SystemVerilog](#описание-памяти-на-языке-systemverilog). Разрядность ячеек памяти должна быть 8 бит, количество ячеек — 1024.
2. Добавить в проект [`файл с содержимым памяти инструкций`](program.txt)([`как добавить файл, инициализирующий память`](../../Vivado%20Basics/How%20to%20add%20a%20mem-file.md)). Данный файл будет использоваться при вызове системной функции `$readmemh` в описании памяти инструкций.
3. К созданной памяти необходимо подключить выход модуля `read_data_o`. При подключении должен быть использован вход модуля `addr_i` и оператор [конкатенации](../../Basic%20Verilog%20structures/Concatenation.md).
4. При подключении выхода `read_data_o` помните, что чтение по адресам, превышающим `1020` должно возвращать `0`.
3. После описания памяти инструкций, её необходимо проверить с помощью тестового окружения.
1. Тестовое окружение находится [`здесь`](tb_instr_mem.sv).
2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md).
3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_instr_mem`).
4. **Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!**
3. Реализуйте память данных. Для этого:
1. В `Design Sources` проекта создайте `SystemVerilog`-файл `data_mem.sv`.
2. Опишите в нем модуль памяти данных с таким же именем и портами, как указано в задании.
1. Описание модуля будет схожим с описанием модуля памяти инструкций, однако порт чтения в этот раз будет **синхронным** (запись в него будет происходить в блоке `always_ff`). Кроме того необходимо будет описать логику записи данных в память.
2. Запись в ячейки памяти описывается подобно записи данных в [регистры](../../Basic%20Verilog%20structures/Registers.md), только при этом, происходит доступ к конкретной ячейке памяти с помощью входа `addr_i` (как осуществляется доступ к ячейкам памяти сказано в разделе [описание памяти на языке SystemVerilog](#описание-памяти-на-языке-systemverilog)).
3. Необходимо помнить, что запись будет вестись в 4 ячейки памяти одновременно: ту, на которую указывает адрес и следующие за ней три ячейки.
4. Обратите внимание что работа с памятью должна осуществляться только когда сигнал `mem_req_i == 1`, в противном случае запись не должна производиться, а на шину `read_data_o` должно возвращаться магическое число `32'hfall_leaf`.
5. Как и в памяти инструкций, при чтении по адресам вне допустимого диапазона (только в этот раз старше адреса `1023`), на шине `read_data_o` должно выставляться значение `32'hdead_beaf`.
3. После описания памяти данных, её необходимо проверить с помощью тестового окружения.
1. Тестовое окружение находится [`здесь`](tb_data_mem.sv).
2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md).
3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_data_mem`).
4. **Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!**
4. Реализуйте регистровый файл. Для этого:
1. В `Design Sources` проекта создайте `SystemVerilog`-файл `rf_riscv.sv`.
2. Опишите в нем модуль регистрового файла с таким же именем и портами, как указано в задании.
1. В отличии от памяти инструкций и данных, ячейки памяти регистрового файла должны быть 32-битными (а на 8-битными). Это означает, что реализация портов чтения и записи будет проще.
2. Не забывайте, что у вас 2 порта на чтение и 1 порт на запись, при этом каждый порт не зависит от остальных (в модуле 3 независимых входа адреса).
3. Чтение из нулевого регистра (чтение по адресу 0) всегда должно возвращать нулевое значение. Этого можно добиться двумя путями:
1. Путем добавления мультиплексора перед выходным сигналом чтения (мультиплексор будет определять, пойдут ли на выход данные из ячейки регистрового файла, либо в случае если адрес равен нулю, на выход пойдет константа ноль).
2. Путем инициализации нулевого регистра нулевым значением и запретом записи в этот регистр (при записи и проверки write_enable добавить дополнительную проверку на адрес).
4. Каким образом будет реализована эта особенность регистрового файла не важно, выберите сами.
3. После описания регистрового файла, его необходимо проверить с помощью [`тестового окружения`](../../Basic%20Verilog%20structures/Testbench.md).
1. Тестовое окружение находится [`здесь`](tb_rf_riscv.sv).
2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md).
3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_rf_riscv`).
4. **Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!**
5. Добавьте в проект модуль верхнего уровня ([nexys_rf_riscv.sv](board%20files/nexys_rf_riscv.v)), соединяющий регистровый файл с периферией в ПЛИС. Описание модуля находится [здесь](board%20files)
6. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](board%20files/nexys_a7_100t.xdc)), если тот еще не был подключен, либо замените его содержимое данными из файла к этой лабораторной работе.
7. Проверьте работу регистрового файла в ПЛИС.

View File

@@ -0,0 +1,39 @@
# Проверка работы регистрового файла на ПЛИС
Если вы не понимаете, что лежит в этой папке, или если надо вспомнить, как прошить ПЛИС, можно воспользоваться [`этой инструкцией`](../../../Vivado%20Basics/Program%20nexys%20a7.md)
Для работы с регистровым файлом, необходимо выставлять сигналы адресов и данных.
У платы недостаточно переключателей, для такого количества входов регистрового файла, поэтому подача сигналов на вход происходит в несколько этапов.
Сперва можно ввести один из адресов регистрового файла (`A1`/`A2`/`A3`). Ввод осуществляется через `SW[4:0]`. Для того, чтобы выставить введенный адрес на какой-то из входов адреса, необходимо нажать одну из следующих кнопок:
1. `BTNL` чтобы выставить значение с `SW[4:0]` на `A1`
2. `BTNC` чтобы выставить значение с `SW[4:0]` на `A2`
3. `BTNR` чтобы выставить значение с `SW[4:0]` на `A3`
(расположение кнопок смотри на изображениях ниже).
После выставления адресов, переключатели можно использовать в качестве ввода первых шестнадцати бит данных. Для записи введенных данных по адресу `A3` используется кнопка `BTND`, для чтения данных по адресам `A1`, `A2` (с выводом результатов чтения на семисегментные индикаторы) используется кнопка `BTNU`.
Управление регистровым файлом.
![../../../.pic/Labs/board%20files/nexys_rf1.jpg](../../../.pic/Labs/board%20files/nexys_rf1.jpg)
Ниже показан пример последовательности действий для работы с платой.
Установка значения `5'b10101` на входы `A1`,`A2`,`A3`.
![../../../.pic/Labs/board%20files/nexys_rf2.jpg](../../../.pic/Labs/board%20files/nexys_rf2.jpg)
Запись информации по адресу А3.
![../../../.pic/Labs/board%20files/nexys_rf3.jpg](../../../.pic/Labs/board%20files/nexys_rf3.jpg)
Чтение по адресам А1 и А2.
![../../../.pic/Labs/board%20files/nexys_rf4.jpg](../../../.pic/Labs/board%20files/nexys_rf4.jpg)
Для того чтобы считать информацию по любому адресу, достаточно установить желаемый адрес на один из портов чтения и нажать на кнопку чтения.
![../../../.pic/Labs/board%20files/nexys_rf5.jpg](../../../.pic/Labs/board%20files/nexys_rf5.jpg)
Попробуйте записать информацию в нулевой регистр, затем по другим адресам, после чего считайте записанную информацию и убедитесь, что она соответствует той, которую вы записывали (с учетом особенностей регистрового файла RISC-V).

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,141 @@
`timescale 1ns / 1ps
module nexys_rf_riscv(
input CLK100,
input resetn,
input BTND, BTNU, BTNL, BTNR, BTNC,
input [15:0] SW,
output [15:0] LED,
output CA, CB, CC, CD, CE, CF, CG, DP,
output [7:0] AN,
output LED16_B, LED16_G, LED16_R, LED17_B, LED17_G, LED17_R
);
wire [31:0] WD3;
wire WE;
wire [31:0] RD1;
wire [31:0] RD2;
localparam pwm = 1000;
reg [9:0] counter;
reg [3:0] semseg;
reg [7:0] ANreg;
reg CAr, CBr, CCr, CDr, CEr, CFr, CGr, DPr;
reg [15:0] LEDr;
reg [4:0] a1;
reg [4:0] a2;
reg [4:0] a3;
reg [31:0] rd1;
reg [31:0] rd2;
rf_riscv DUT
(
.clk (CLK100),
.A1 (a1),
.A2 (a2),
.A3 (a3),
.WD3 (WD3),
.WE (WE),
.RD1 (RD1),
.RD2 (RD2)
);
assign LED = {1'b0, a1, a2, a3};
assign AN[7:0] = ANreg[7:0];
assign {CA, CB, CC, CD, CE, CF, CG, DP} = {CAr, CBr, CCr, CDr, CEr, CFr, CGr, DPr};
assign LED16_G = BTNC | BTNR;
assign LED17_G = BTNL | BTNR;
assign {LED16_R, LED17_R} = {2{BTND}};
assign {LED16_B, LED17_B} = {2{BTNU}};
assign WD3 = 32'b0 | SW[15:0];
assign WE = BTND;
always @(posedge CLK100) begin
if (!resetn) begin
counter <= 'b0;
ANreg[7:0] <= 8'b11111111;
{CAr, CBr, CCr, CDr, CEr, CFr, CGr, DPr} <= 8'b11111111;
{a1, a2, a3} <= 'b0;
{rd1, rd2} <= 'b0;
end
else begin
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
a1 <= BTNL? SW[4:0]: a1;
a2 <= BTNC? SW[4:0]: a2;
a3 <= BTNR? SW[4:0]: a3;
rd1 <= BTNU? RD1: rd1;
rd2 <= BTNU? RD2: rd2;
case (1'b0)
ANreg[0]: begin
semseg <= (rd2) % 5'h10;
//DPr <= 1'b1;
end
ANreg[1]: begin
semseg <= (rd2 / 'h10) % 5'h10;
//DPr <= 1'b1;
end
ANreg[2]: begin
semseg <= (rd2 / 'h100) % 5'h10;
//DPr <= 1'b1;
end
ANreg[3]: begin
semseg <= (rd2 / 'h1000) % 5'h10;
//DPr <= 1'b1;
end
ANreg[4]: begin
semseg <= (rd1) % 5'h10;
//DPr <= 1'b1;
end
ANreg[5]: begin
semseg <= (rd1 / 'h10) % 5'h10;
//DPr <= 1'b1;
end
ANreg[6]: begin
semseg <= (rd1 / 'h100) % 5'h10;
//DPr <= 1'b1;
end
ANreg[7]: begin
semseg <= (rd1 / 'h1000) % 5'h10;
//DPr <= 1'b1;
end
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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,130 @@
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: MIET
// Engineer: Nikita Bulavin
//
// Create Date:
// Design Name:
// Module Name: tb_data_mem
// Project Name: RISCV_practicum
// Target Devices: Nexys A7-100T
// Tool Versions:
// Description: tb for data memory
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module tb_data_mem();
parameter ADDR_SIZE = 4096;
parameter TIME_OPERATION = 50;
logic CLK;
logic REQ;
logic WE;
logic [31:0] A;
logic [31:0] WD;
logic [31:0] RD;
data_mem DUT (
.clk_i (CLK),
.mem_req_i (REQ),
.write_enable_i (WE ),
.addr_i (A ),
.write_data_i (WD),
.read_data_o (RD)
);
logic [31:0] RDa;
integer i, err_count = 0;
assign A = i;
parameter CLK_FREQ_MHz = 100;
parameter CLK_SEMI_PERIOD= 1e3/CLK_FREQ_MHz/2;
initial CLK <= 0;
always #CLK_SEMI_PERIOD CLK = ~CLK;
initial begin
$timeformat(-9, 2, " ns", 3);
$display( "\nStart test: \n\n==========================\nCLICK THE BUTTON 'Run All'\n==========================\n"); $stop();
REQ = 1;
WE = 0;
i = 1; #10;
if (RD !== 32'hx) begin
$display("The data memory should not be initialized by the $readmemh function");
err_count = err_count + 1;
end
for (i = 0; i < ADDR_SIZE; i = i + 4) begin
@(posedge CLK);
WE = 1;
WD = $urandom;
end
for (i = 0; i < (ADDR_SIZE+1); i = i + 1) begin
if (i != (ADDR_SIZE+1)) begin
REQ = |($urandom %10);
WE = 0;
#TIME_OPERATION;
RDa = RD;
WD = $urandom;
#TIME_OPERATION;
WE = $urandom % 2;
#TIME_OPERATION;
if ((WE && REQ || !REQ) && RD !== 32'd4195425967) begin
$display("When writing (write_enable_i = %h) read_data_o should be equal to fa11_1eaf, your data: %h_%h, time: %t", WE, RD[31:16],RD[15:0], $time);
err_count = err_count + 1;
end
if ((!WE && REQ) && RD !== RDa) begin
$display("When reading (write_enable_i = %h), the data %h is overwritten with data %h at address %h, time: %t", WE, RDa, RD, A, $time);
err_count = err_count + 1;
end
end
else begin
REQ = 1;
#TIME_OPERATION;
if (RD !== 32'd3735928559) begin
$display("When reading (write_enable_i = %h) at an address greater than 4095, it should return dead_beef yor data: %h_%h, time: %t", WE, RD[31:16],RD[15:0], $time);
err_count = err_count + 1;
end
end
#TIME_OPERATION;
end
#TIME_OPERATION;
REQ = 1;
WE = 1;
#TIME_OPERATION;
for (i = 0; i < 8; i = i + 4) begin
WD = i? 32'hfecd_ba98: 32'h7654_3210;
#TIME_OPERATION;
end
WE = 0;
i = 2;
#TIME_OPERATION;
if (RD !== 32'hba98_7654) begin
$display("data is being written to the cell incorrectly. RAM [0:7] must be 0x0123456789abcdef, time: %t", $time);
err_count = err_count + 1;
end
@(posedge CLK)
i = 0;
@(negedge CLK);
if (RD !== 32'hba98_7654) begin
$display("reading from data memory must be synchronous, time: %t", $time);
err_count = err_count + 1;
end
@(posedge CLK); #5;
if (RD !== 32'h7654_3210) begin
$display("synchronous data memory read error, time: %t", $time);
err_count = err_count + 1;
end
$display("Number of errors: %d", err_count);
if( !err_count ) $display("\ndata_mem SUCCESS!!!\n");
$finish();
end
endmodule

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff