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,90 @@
# Тестовое окружение (Testbench)
Для проверки правильного функционирования цифровых устройств необходимо разработать тестовое окружение. Тестовое окружение (testbench) это блок, который окружает проверяемое устройство, формирует для него тестовые сигналы и автоматически проверяет, что сигналы на выходе проверяемого устройства соответствуют заложенным функциям. Тестовое окружение не является реальным аппаратным блоком (он только симулируется), поэтому в нем возможно использовать традиционные конструкции программирования. Например, то, что описывается в блоках `initial` в тестовом окружении, выполняется как программа в классическом программировании строчка за строчкой.
![../.pic/Basic%20Verilog%20structures/testbench/tb_1.png](../.pic/Basic%20Verilog%20structures/testbench/tb_1.png)
Для того, чтобы создать тестовое окружение (testbench) необходимо создать новый файл симуляции в проекте. Для этого нажмите на `Add source`, после чего нужно выбрать `Add or create simulation sources` (на картинке ниже) → `Create File…` и так далее.
![../.pic/Basic%20Verilog%20structures/testbench/tb_2.png](../.pic/Basic%20Verilog%20structures/testbench/tb_2.png)
Так как для проверки разных модулей придется создавать различные тестовые окружения, то Vivado придется сообщать в явном виде какой из файлов симуляции вы сейчас хотите запустить. Для этого надо щелкнуть на нужном файле правой кнопкой и выбрать пункт `Set as Top` (продемонстрировано на картинке далее).
![../.pic/Basic%20Verilog%20structures/testbench/tb_3.png](../.pic/Basic%20Verilog%20structures/testbench/tb_3.png)
Название файла, для которого будет запускаться симуляция, отображается жирным шрифтом в окне `Sources` в папке `Simulation Sources`. После того, как выбран нужный файл симуляции, его можно запустить через панель `PROJECT MANAGER`, нажав на `Run Simulation`, а затем, в самом простом случае, можно запустить поведенческое моделирование `Run Behavioral Simulation`, оно позволяет увидеть поведение устройства в ответ на воздействия testbenchа. Временные задержки на прохождение сигналов через цифровые блоки при этом не учитываются. Так же можно посмотреть на реакцию устройства после синтеза `Post-Synthesis` или после имплементации `Post-Implementation`. Функциональная симуляция не учитывает временные задержки, временная учитывает. Наиболее приближенная к реальности симуляция `Post-Implementation Timing Simulation`. Она учитывает временные задержки конкретных компонентов, в конкретной ПЛИС, с конкретными задержками распространения сигнала по каждому из проводов.
![../.pic/Basic%20Verilog%20structures/testbench/tb_4.png](../.pic/Basic%20Verilog%20structures/testbench/tb_4.png)
Пример тестового окружение для сумматора
Ниже приводится пример тестового окружения, в котором:
создаются провода и регистры для подключения к тестируемому модулю,
подключается проверяемый модуль,
описывается задача `task`, которую, подобно функции или подпрограмме, можно вызывать с различными параметрами,
в блоке `initial` последовательно два раза вызывается задача `add_op`, после чего симуляция останавливается `$stop`,
пример генерации тактового сигнала для подачи на вход проверяемого устройства.
``` verilog
`timescale 1ns / 1ps // Первое число указывает в каких величинах задержка
// например, использование #10 это 10 наносекунд
// если бы параметр был 10ns, то #10 означало бы 100ns
// Второе число указывает точность симуляции
// тут симуляция происходит для каждой пикосекунды
module my_testbench (); // объявляем модуль тестового окружения
// внешних сигналов нет, поэтому скобки пустые
reg [31:0] A, B; //❶ объявляем регистры для управления входами сумматора
wire [31:0] S; // объявляем провод для подключения к выходу суммы
reg Cin; // объявляем регистр для управления входом Cin
wire Cout; // объявляем провод для подключения к выходу Cout
adder dut ( //❷ подключаем тестируемый модуль
.a(A), // dut (device under test) классическое название тестируемого модуля,
.b(B), // при желании можно использовать любое другое имя
.cin(Cin),
.s(S),
.cout(Cout));
initial begin // блок последовательного исполнения, начинает работу с момента времени 0
add_op(6, 3); //❹ запустить задачу task add_op с параметрами 6 и 3
add_op(2, 7); // когда закончиться предыдущая задача запустить новую
$stop; // остановить симуляцию
end
task add_op; //❸ объявляем задачу add_op
input [31:0] a_op, b_op; // task получает на вход два параметра
begin
A = a_op; // подать на вход A сумматора новое значение a_op
B = b_op; // подать на вход B сумматора новое значение b_op
Cin = 0; // подать на вход Cin ноль
#100; // выждать 100 ns чтобы сигнальчики разбежались и сумматор успел посчитать
if (S == (a_op + b_op)) // если реальность (S) и ожидание (a_op+b_op) совпадают, то
$display("GOOD %d + %d = %d", A, B, S); // вывести в терминал сообщение good
else // в противном случае
$display("BAD %d + %d = %d", A, B, S); // вывести в терминал другое сообщение
end
endtask
endmodule
```
``` verilog
reg clk; //❺ это вообще не относится к описанному выше testbenchу
always #10 clk = ~clk; // каждые 10ns менять clk на противоположное значение
```
Блоков initial в тестовом окружении может быть сколько угодно. Все эти блоки запускаются на исполнение параллельно. Блоков task так же может быть сколько угодно много и каждый из них может выполнять разные проверки. Так же поддерживаются множество стандартных языковых конструкции, например цикл for. Параметры для $display передаются так же, как у printf в языке C (на википедии есть вся информация).
Данный пример проверяет две суммы (6+3) и (2+7). Тест необходимо дополнить большим количеством проверок: несколько с очень большими числами, несколько с отрицательными, несколько с отрицательными и положительными, несколько со входным переносом Cin = 1, несколько операций с числами вызывающим переполнение (чтобы проверить формирование Cout).
По аналогии с этим примером необходимо реализовать модули проверки для:
- АЛУ (по несколько проверок на каждую операцию)
- Регистрового файла (последовательно записать какие-нибудь данные в разные адреса, а потом считать, убедившись, что все правильно, при этом считывание по адресу 0 должно всегда показывать 0)
- Памяти инструкций (считать содержимое из нескольких ячеек, чтобы убедиться, что память проинициализирована)