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