# Тестовое окружение (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) - Памяти инструкций (считать содержимое из нескольких ячеек, чтобы убедиться, что память проинициализирована)