Сменив недавно работу, перейдя с языка VHDL на язык SystemVerilog и оказавшись в команде, где есть отдельная группа верификаторов, я осознал, что сильно отстал в верификации. На VHDL ранее мной писались лишь простые тесты разработчика, которые показывали, что блок выполняет требуемую функцию и ничего более. Каждый тест писался с нуля и не было повторного использования кода. Решив исправить эту проблему, я погрузился в чтение SystemVerilog for Verification A Guide to Learning the Testbench Language Features за авторством Chris Spear, Greg Tumbush. Прочитав книгу, понял, что нужно написать какой-то тестовый проект, дабы закрепить полученную информацию. Вспомнил, что видел на хабре цикл статей по верификации от @pcbteach, написанный на verilog, и решил написать тест для сумматора с интерфейсами AXI-Stream на SystemVerilog.
Содержание
1 Общая структура тестового окружения
2 Тестовое окружение для проверки сумматора
3 Реализация интерфейсов для тестирования сумматора
4 Реализация классов для тестирования сумматора
5 Подключение тестового окружения и тестируемого устройства
6 Вывод
1. Общая структура тестового окружения
Данная схема приведена в книге как опора для написания тестового окружения.
Generator формирует транзакцию и передает ее в Agent. На уровне Generator транзакция представляет собой экземпляр класса, без какой-либо привязки, как эти данные должны передаваться в физическом мире.
Agent получает транзакцию и передает ее в Driver (для передачи в тестируемый блок) и Scoreboard (для анализа результата выполнения).
Driver переводит транзакцию в её физическое воплощение (управляет сигналами шины) и передает в тестируемое устройство (Device Under Test).
Scoreboard на основе исходной транзакции предсказывает, какая транзакция должна получится на выходе тестируемого устройства.
Monitor принимает сигналы шины с выхода тестируемого устройства (Device Under Test) и преобразует их в экземпляр класса.
Checker сравнивает транзации от Monitor и Scoreboard.
Environment объединяет в себя все блоки, используемые для тестирования.
Таким образом, при выходе за пределы блока Environment экземпляр класса транзакции переводится в физическое представление сигналов и поступает в тестируемое устройство, а с выхода тестируемого устройства физическое представление сигналов переводится в экземпляр класса транзакции. Каждый блок выполняет только одно действие, следовательно такие блоки будут более универсальными.
2. Тестовое окружение для проверки сумматора
У рассматриваемого сумматора два входа с интерфейсами AXI-Stream - для загрузки операнда 1 и операнда 2. И один выход с интерфейсом AXI-Stream - результат работы сумматора. Соответственно необходимо два экземпляра блоков Generator, два экземпляра блоков Agent, два экземпляра блоков Driver, один экземпляр блока Monitor, один экземпляр блока Cheker, один экземпляр блока Scoreboard.
SystemVerilog позволяет реализовать взаимодействие между блоками внутри Environment несколькими способами:
массивы с синхронизацией с помощью событий (events);
массивы с синхронизацией с помощью семафоров (semaphores);
почтовые ящики (mailbox);
очереди (queues)
и другие варианты.
Также, SystemVerilog предоставляет возможность реализовать взаимодействие между Environment и DUT с помощью:
виртуальных интерфейсов (virtual interface);
абстрактных классов (abstract classes).
Взаимодействие между блоками внутри Environment будет осуществляться с помощью почтовых ящиков, взаимодействие между Environment и DUT будет осуществляться с помощью виртуальных интерфейсов которые показались мне более простыми в использовании.
3. Реализация интерфейсов для тестирования сумматора
interface AXIS_Bus #(parameter DW=8) (input clk);
logic [DW-1:0] tdata;
logic tvalid;
logic tready;
modport slave (
input tdata,
input tvalid,
output tready
);
modport master (
output tdata,
output tvalid,
input tready
);
endinterface
Для взаимодействия с тестируемым устройством используется интерфейс AXIS-Bus, в котором описаны сигналы tdata, tvalid, tready, используемые блоком сумматора.
4. Реализация классов для тестирования сумматора
4.1 Реализация классов для тестирования сумматора
`ifndef OPERAND_ITEM_CLS_PKG__SV
`define OPERAND_ITEM_CLS_PKG__SV
package operand_item_cls_pkg;
class operand_item_cls #(parameter int DW = 4);
rand bit [DW-1:0] m_data;
function void print(string tag = "");
$display("[%s] Item value = 0x%0h", tag, m_data);
endfunction : print
endclass : operand_item_cls
endpackage : operand_item_cls_pkg
`endif //OPERAND_ITEM_CLS_PKG__SV
Класс operand_item_cls используется для задания операнда, поступающего на вход сумматора. Класс имеет параметр DW, определяющий ширину (в битах) поля m_data. Также в классе есть метод для печати текущего значения поля tdata. У поля m_data установлен модификатор rand, говорящий о том, что при каждом вызове функции randomize для объекта класса, значение поля m_data будет принимать случайное значение.
4.2 result_item_cls
`ifndef RESULT_ITEM_CLS_PKG__SV
`define RESULT_ITEM_CLS_PKG__SV
package result_item_cls_pkg;
class result_item_cls #(parameter int DW = 5);
bit [DW-1:0] m_data;
function void print(string tag = "");
$display("[%s] Item value = 0x%0h", tag, m_data);
endfunction : print
endclass : result_item_cls
endpackage : result_item_cls_pkg
`endif //RESULT_ITEM_CLS_PKG__SV
Класс result_item_cls используется для получения результата с выхода сумматора. Класс имеет параметр DW, определяющий ширину (в битах) поля m_data. Также в классе есть метод для печати текущего значения поля m_data.
4.3 generator_cls
`ifndef GENERATOR_CLS_PKG__SV
`define GENERATOR_CLS_PKG__SV
package generator_cls_pkg;
`include "rand_check.svh"
import operand_item_cls_pkg::*;
class generator_cls #(parameter int DW = 4);
operand_item_cls #(.DW(DW)) item;
mailbox #(operand_item_cls #(.DW(DW))) mbx;
function new (input mailbox #(operand_item_cls #(.DW(DW))) mbx);
this.mbx = mbx;
endfunction : new
task run (input int count);
repeat (count) begin
item = new();
`SV_RAND_CHECK(item.randomize());
mbx.put(item);
end
endtask : run
endclass : generator_cls
endpackage : generator_cls_pkg
`endif //GENERATOR_CLS_PKG__SV
Класс generator_cls используется для формирования транзакции и передачи ее в блок agent_cls. Транзакция представляет собой экземпляр класса operand_item_cls. При создании экземпляра класса в конструктор (функция new) передается указатель на почтовый ящик, используемый для передачи транзакции в блок agent_cls.
Создание транзакций осуществляется в методе run, который принимает на вход количество транзакций, которые необходимо отправить. В цикле создается объект транзакции (почтовый ящик не хранит объект, только ссылку на него), заполняется случайным значением и помещается в почтовый ящик.
`SV_RAND_CHECK - макрос, проверяющий, что заполнение случайными значениями экземпляров класса выполнено успешно.
4.4 agent_cls
`ifndef AGENT_CLS_PKG__SV
`define AGENT_CLS_PKG__SV
package agent_cls_pkg;
import operand_item_cls_pkg::*;
class agent_cls #(parameter int DW = 4);
mailbox #(operand_item_cls #(.DW(DW))) gen2agt, agt2drv, agt2scb;
operand_item_cls #(.DW(DW)) item;
function new(input mailbox #(operand_item_cls #(.DW(DW))) gen2agt, agt2drv, agt2scb);
this.gen2agt = gen2agt;
this.agt2drv = agt2drv;
this.agt2scb = agt2scb;
endfunction : new
task run();
forever begin
gen2agt.get(item);
agt2scb.put(item);
agt2drv.put(item);
end
endtask : run
endclass : agent_cls
endpackage : agent_cls_pkg
`endif //AGENT_CLS_PKG__SV
Класс agent_cls используется для приема транзакции от generator_cls и передачи ее в driver_cls и scoreboard_cls (фактически транзакция дублируется). Транзакция представляет собой экземпляр класса operand_item_cls. При создании экземпляра класса в конструктор (функция new) передаются указатели на почтовые ящики, используемые для приема транзакции и передачи ее в следующие блоки.
Передача транзакции осуществляется в методе run. В бесконечном цикле выполняется получение транзакции из почтового ящика от generator_cls и помещение транзакции в почтовые ящики driver_cls и scoreboard_cls.
4.5 driver_cls
`ifndef DRIVER_CLS_PKG__SV
`define DRIVER_CLS_PKG__SV
package driver_cls_pkg;
import operand_item_cls_pkg::*;
class driver_cls #(parameter int DATA_WIDTH = 4, parameter int AXIS_WIDTH = 8);
virtual AXIS_Bus #(.DW(AXIS_WIDTH)) vif;
mailbox #(operand_item_cls #(.DW(DATA_WIDTH))) mbx;
operand_item_cls #(.DW(DATA_WIDTH)) item;
function new (input mailbox #(operand_item_cls #(.DW(DATA_WIDTH))) mbx, input virtual AXIS_Bus #(.DW(AXIS_WIDTH)) vif);
this.mbx = mbx;
this.vif = vif;
endfunction : new
task run (input int count, input int min_delay, input int max_delay, input bit en_display = 0);
if (en_display)
$display("[driver_cls] Starting...");
vif.tvalid <= 1'b0;
repeat (count) begin
if (en_display)
$display("[driver_cls][ Waiting for item...");
mbx.get(item);
if (en_display)
item.print("driver_cls");
repeat ($urandom_range(min_delay, max_delay)) begin
@(posedge vif.clk);
end
vif.tdata <= item.m_data;
vif.tvalid <= 1'b1;
@(posedge vif.clk);
while (!vif.tready) begin
@(posedge vif.clk);
end
vif.tvalid <= 1'b0;
if (en_display)
$display("[driver_cls] Item sended...");
end
endtask : run
endclass : driver_cls
endpackage : driver_cls_pkg
`endif //DRIVER_CLS_PKG__SV
Класс driver_cls используется для передачи транзакции в тестируемое устройство. Транзакция представляет собой экземпляр класса operand_item_cls. Помимо параметра DATA_WIDTH для задания ширины поля исходного операнда, класс имеет параметр AXIS_WIDTH для задания ширины интерфейса AXI-Stream, используемого при взаимодействии с тестируемым устройством. При создании экземпляра класса в конструкторe
(функция new) передаются указатели на почтовый ящик, содержащий транзакцию, и виртуальный интерфейс, подключенный к тестируемому устройству.
Чтобы сформировать переключение отдельных сигналов в интерфейсе из экземпляра класса используется метод run, который принимает на вход количество транзакций. В методе циклично выполняется формирование сигналов. Из почтового ящика извлекается транзакция, ожидается случайное время перед началом транзакции, на шину tdata интерфейса выставляется содержимое поля m_data класса operand_item_cls, на tvalid выставляется значение 1. Ожидается появление сигнала tready, равного 1, после этого tvalid переходит в состояние 0, транзакция считается переданной
4.6 monitor_cls
`ifndef MONITOR_CLS_PKG__SV
`define MONITOR_CLS_PKG__SV
package monitor_cls_pkg ;
import result_item_cls_pkg::*;
class monitor_cls #(parameter int DATA_WIDTH = 5, parameter int AXIS_WIDTH = 8);
virtual AXIS_Bus #(.DW(AXIS_WIDTH)) vif;
mailbox #(result_item_cls #(.DW(DATA_WIDTH))) mbx;
function new (input mailbox #(result_item_cls #(.DW(DATA_WIDTH))) mbx, input virtual AXIS_Bus #(.DW(AXIS_WIDTH)) vif);
this.vif = vif;
this.mbx = mbx;
endfunction : new
task run (input int count, input int min_delay, input int max_delay, input bit en_display = 0);
if (en_display)
$display("[monitor_cls] Starting...");
vif.tready <= 1'b0;
repeat (count) begin
result_item_cls #(.DW(DATA_WIDTH)) item = new;
repeat ($urandom_range(min_delay, max_delay)) begin
@(posedge vif.clk);
end
vif.tready <= 1'b1;
@(posedge vif.clk);
while (!vif.tvalid) begin
@(posedge vif.clk);
end
item.m_data = vif.tdata;
vif.tready <= 1'b0;
if (en_display)
item.print("monitor_cls");
mbx.put(item);
end
endtask : run
endclass : monitor_cls
endpackage : monitor_cls_pkg
`endif //MONITOR_CLS_PKG__SV
Класс monitor_cls используется для анализа переключений сигналов tdata, tvalid, tready и формирования объекта класса result_item_cls, в котором хранится результат работы сумматора. Помимо параметра DATA_WIDTH для задания ширины поля результата, класс имеет параметр AXIS_WIDTH для задания ширины интерфейса AXI-Stream, используемого при взаимодействии с тестируемым устройством. При создании экземпляра класса в конструктор (функция new) передаются указатели на почтовый ящик, куда необходимо положить транзакцию, и на виртуальный интерфейс, подключенный к тестируемому устройству.
Чтобы сформировать экземпляр класса из отдельных сигналов интерфейса используется метод run, который принимает на вход количество транзакций. В методе циклично выполняется формирование экземпляра класса. Ожидается случайное время перед началом транзакции. На сигнал tready интерфейса выставляется значение 1, ожидается появление сигнала tvalid, равного 1. После этого значение сигнала tdata интерфейса заносится в поле m_data экземпляра класса и сигнал tready принимает значение 0, транзакция считается обработанной.
4.7 scoreboard_cls
`ifndef SCOREBOARD_CLS_PKG__SV
`define SCOREBOARD_CLS_PKG__SV
package scoreboard_cls_pkg;
import operand_item_cls_pkg::*;
import result_item_cls_pkg::*;
class scoreboard_cls #(parameter int DW = 4);
mailbox #(operand_item_cls #(.DW(DW))) mbx_op1, mbx_op2;
mailbox #(result_item_cls #(.DW(DW+1))) mbx_res;
function new ( input mailbox #(operand_item_cls #(.DW(DW))) mbx_op1, mbx_op2, input mailbox #(result_item_cls #(.DW(DW+1))) mbx_res);
this.mbx_op1 = mbx_op1;
this.mbx_op2 = mbx_op2;
this.mbx_res = mbx_res;
endfunction : new
task run (int count, input bit en_display = 0);
operand_item_cls #(.DW(DW)) operand1, operand2;
result_item_cls #(.DW(DW+1)) result;
if (en_display)
$display("[scoreboard_cls] Starting... ");
repeat (count) begin
fork
mbx_op1.get(operand1);
mbx_op2.get(operand2);
join
result = new ();
result.m_data = operand1.m_data + operand2.m_data;
mbx_res.put(result);
end
endtask : run
endclass : scoreboard_cls
endpackage : scoreboard_cls_pkg
`endif //SCOREBOARD_CLS_PKG__SV
Класс scoreboard_cls используется для вычисления ожимаемого результата от сумматора на основе исходных операндов. Исходные операнды представлены экземплярами класса operand_item_cls, ожидаемыый результат представлен экземпляром класса result_item_cls. При создании экземпляра класса в конструктор (функция new) передаются указатели на почтовые ящики, в которых хранятся операнды и в который необходимо поместить ожидаемый результат работы сумматора.
Анализ операндов и вычисление ожидаемого результата выполняется в методе run, который принимает на вход количество транзакций. В цикле выполняется извлечение операндов из почтовых ящиков, формируется объект для сохранения результата, в почтовый ящик заносится ожидаемое значение результата.
4.8 checker_cls
`ifndef CHECKER_CLS_PKG__SV
`define CHECKER_CLS_PKG__SV
package checker_cls_pkg;
import result_item_cls_pkg::*;
class checker_cls #(parameter int DW = 5);
mailbox #(result_item_cls #(.DW(DW))) mbx_res_sc, mbx_res_mon;
int count_good;
int count_bad;
function new ( input mailbox #(result_item_cls #(.DW(DW))) mbx_res_sc, mbx_res_mon);
this.mbx_res_sc = mbx_res_sc;
this.mbx_res_mon = mbx_res_mon;
count_good = 0;
count_bad = 0;
endfunction : new
task run (int count, input bit en_display = 0);
result_item_cls #(.DW(DW)) result_sc, result_mon;
if (en_display)
$display("[checker_cls] Starting... ");
repeat (count) begin
fork
mbx_res_sc.get(result_sc);
mbx_res_mon.get(result_mon);
join
if (result_sc.m_data == result_mon.m_data) begin
count_good++;
end
else begin
count_bad++;
end
end
if (count_bad != 0) begin
$display("[checker_cls] Fail!");
$finish;
end
else if (count_good == count) begin
$display("[checker_cls] Pass!");
$finish;
end
endtask : run
endclass : checker_cls
endpackage : checker_cls_pkg
`endif //CHECKER_CLS_PKG__SV
Класс checker_cls используется для проверки соответствия ожидаемого результата работы сумматора и реального результата работы сумматора. Результаты представлены экземплярами класса result_item_cls. При создании экземпляра класса в конструктор (функция new) передаются указатели на почтовые ящики, в которых хранятся результы работы сумматора (ожидаемый и реальный).
Анализ результатов выполняется в методе run, который принимает на вход количество транзакций. В цикле выполняется извлечение результатов из почтовых ящиков и их сравнение. Если ожидаемый результат равен реальному результату, увеличивается счетчик корректных транзакций, иначе, увеличивается счетчик некорректных транзакций. В случае, если была получена некорректная транзакция, тест останавливается
4.9 config_cls
`ifndef CONFIG_CLS_PKG_SV
`define CONFIG_CLS_PKG_SV
package config_cls_pkg;
class config_cls;
rand bit [31:0] run_for_n_trans;
rand bit [ 7:0] min_delay;
rand bit [ 7:0] max_delay;
constraint reasonable {
run_for_n_trans inside {[1:1000]};
min_delay < max_delay;
}
endclass : config_cls
endpackage : config_cls_pkg
`endif //CONFIG_CLS_PKG_SV
Класс config_cls используется для получения случайных параметров тестового окружения. В классе заданы следующие поля:
run_for_n_trans (количество повторений выполнения теста);
min_delay (минимальная задержка на интерфейсе AXI-Stream);
max_delay (максимальная задержка на интерфейсе AXI-Stream).
У полей заданы идентификаторы rand, означающие, что при каждом вызове функции randomize() к экземпляру класса, поля будут заполнены случайным значением. Для поля run_for_n_trans указано ограничение, что после вызова функции randomize() значение поля run_for_n_trans должно быть в диапазоне от 1 до 1000. Для полей min_delay и max_delay указано ограничение, что min_delay должно быть меньше max_delay.
4.10 environment_cls
`ifndef ENVIRONMENT_CLS_PKG__SV
`define ENVIRONMENT_CLS_PKG__SV
package environment_cls_pkg;
import operand_item_cls_pkg::*;
import result_item_cls_pkg::*;
import generator_cls_pkg::*;
import agent_cls_pkg::*;
import driver_cls_pkg::*;
import monitor_cls_pkg::*;
import scoreboard_cls_pkg::*;
import checker_cls_pkg::*;
import config_cls_pkg::*;
`include "rand_check.svh"
parameter OPERAND_QTY = 2;
class environment_cls #(parameter int DATA_WIDTH = 4, parameter int AXIS_IN_WIDTH = 8, parameter int AXIS_OUT_WIDTH = 8);
generator_cls #(.DW(DATA_WIDTH)) gen[OPERAND_QTY-1:0];
agent_cls #(.DW(DATA_WIDTH)) agt[OPERAND_QTY-1:0];
driver_cls #(.DATA_WIDTH(DATA_WIDTH), .AXIS_WIDTH(AXIS_IN_WIDTH)) drv[OPERAND_QTY-1:0];
monitor_cls #(.DATA_WIDTH(DATA_WIDTH+1), .AXIS_WIDTH(AXIS_OUT_WIDTH)) mon;
scoreboard_cls #(.DW(DATA_WIDTH)) scb;
checker_cls #(.DW(DATA_WIDTH+1)) chk;
config_cls cfg;
mailbox #(operand_item_cls #(.DW(DATA_WIDTH))) mbx_gen2agt [OPERAND_QTY-1:0];
mailbox #(operand_item_cls #(.DW(DATA_WIDTH))) mbx_agt2scb [OPERAND_QTY-1:0];
mailbox #(operand_item_cls #(.DW(DATA_WIDTH))) mbx_agt2drv [OPERAND_QTY-1:0];
mailbox #(result_item_cls #(.DW(DATA_WIDTH+1))) mbx_scb2chk;
mailbox #(result_item_cls #(.DW(DATA_WIDTH+1))) mbx_mon2chk;
virtual AXIS_Bus #(.DW(AXIS_IN_WIDTH)) operand1;
virtual AXIS_Bus #(.DW(AXIS_IN_WIDTH)) operand2;
virtual AXIS_Bus #(.DW(AXIS_OUT_WIDTH)) result;
function new( input virtual AXIS_Bus #(.DW(AXIS_IN_WIDTH)) operand1, operand2, input virtual AXIS_Bus #(.DW(AXIS_OUT_WIDTH)) result);
cfg = new();
this.operand1 = operand1;
this.operand2 = operand2;
this.result = result;
endfunction
function void gen_cfg();
`SV_RAND_CHECK(cfg.randomize());
endfunction : gen_cfg
function void build();
for (int i = 0; i < OPERAND_QTY; i++) begin
mbx_gen2agt[i] = new(1);
mbx_agt2scb[i] = new(1);
mbx_agt2drv[i] = new(1);
end
mbx_scb2chk = new(1);
mbx_mon2chk = new(1);
for (int i = 0; i < OPERAND_QTY; i++) begin
gen[i] = new(.mbx(mbx_gen2agt[i]));
agt[i] = new(.gen2agt(mbx_gen2agt[i]), .agt2drv(mbx_agt2drv[i]), .agt2scb(mbx_agt2scb[i]));
end
drv[0] = new(.mbx(mbx_agt2drv[0]), .vif(operand1));
drv[1] = new(.mbx(mbx_agt2drv[1]), .vif(operand2));
mon = new(.mbx(mbx_mon2chk), .vif(result));
scb = new(.mbx_op1(mbx_agt2scb[0]), .mbx_op2(mbx_agt2scb[1]), .mbx_res(mbx_scb2chk));
chk = new(.mbx_res_sc(mbx_scb2chk), .mbx_res_mon(mbx_mon2chk));
endfunction : build
task run();
fork
gen[0].run(cfg.run_for_n_trans);
gen[1].run(cfg.run_for_n_trans);
agt[0].run();
agt[1].run();
drv[0].run(cfg.run_for_n_trans, cfg.min_delay, cfg.max_delay);
drv[1].run(cfg.run_for_n_trans, cfg.min_delay, cfg.max_delay);
mon.run(cfg.run_for_n_trans, cfg.min_delay, cfg.max_delay);
scb.run(cfg.run_for_n_trans);
chk.run(cfg.run_for_n_trans);
join
endtask : run
endclass : environment_cls
endpackage : environment_cls_pkg
`endif //ENVIRONMENT_CLS_PKG__SV
Класс environment_cls служит для подключения между собой всех блоков, участвующих в тестировании. Класс имеет параметры DATA_WIDTH (ширина операндов и результата), AXIS_IN_WIDTH (ширины интерфейса AXI-Stream для операндов), AXIS_OUT_WIDTH (ширина интерфейса AXI-Stream для результата). При создании экземпляра класса в конструктор (функция new) передаются указатели на виртуальные интерфейсы, подключаемые к тестируемому устройству, также создается объект класса config_cls.
Функция gen_cfg используется для задания случайных параметров тестового окружения.
Функция build создает почтовые ящики для обмена транзакциями между блоками, а так же экземпляры блоков generator_cls, agent_cls, driver_cls, monitor_cls, scoreboard_cls.
Функция run запускает создание транзакций, их передачу, прием и проверку.
5. Подключение тестового окружения и тестируемого устройства
Классы для тестирования сумматора реализованы. Теперь необходимо подключить классы для тестирования сумматора и тестируемое устройство между собой.
5.1 test_cls
`ifndef TEST_CLS_PKG__SV
`define TEST_CLS_PKG__SV
package test_cls_pkg;
import environment_cls_pkg::*;
class test_cls #(parameter int DATA_WIDTH = 4, parameter int AXIS_IN_WIDTH = 8, parameter int AXIS_OUT_WIDTH = 8);
environment_cls #(.DATA_WIDTH(DATA_WIDTH), .AXIS_IN_WIDTH(AXIS_IN_WIDTH), .AXIS_OUT_WIDTH(AXIS_OUT_WIDTH)) env;
function new( input virtual AXIS_Bus #(.DW(AXIS_IN_WIDTH)) operand1, operand2, input virtual AXIS_Bus #(.DW(AXIS_OUT_WIDTH)) result);
env = new(operand1, operand2, result);
endfunction
task run ();
env.gen_cfg();
env.build();
env.run();
endtask
endclass : test_cls
endpackage : test_cls_pkg
`endif //TEST_CLS_PKG__SV
Для подключения тестового окружения используется класс test_cls. Класс имеет параметры DATA_WIDTH (ширина операндов и результата), AXIS_IN_WIDTH (ширины интерфейса AXI-Stream для операндов), AXIS_OUT_WIDTH (ширина интерфейса AXI-Stream для результата). При создании экземпляра класса в конструктор (функция new) передаются указатели на виртуальные интерфейсы, подключаемые к тестируемому устройству.
В методе run выполняется последовательно задание случайных параметров для тестирования, создание всех необходимых блоков, сборку их между собой и запуск тестирования.
5.2 tb
`timescale 1ns/1ps
module tb;
import test_cls_pkg::*;
localparam CLK_PERIOD = 10ns;
localparam CNT_RESET_CYCLE = 10;
logic clk;
logic rstn;
localparam integer ADDER_WIDTH = 8;
localparam integer IN_AXIS_WIDTH = $ceil($itor(ADDER_WIDTH) / 8) * 8;
localparam integer OUT_AXIS_WIDTH = $ceil($itor(ADDER_WIDTH+1) / 8) * 8;
AXIS_Bus #(IN_AXIS_WIDTH) operand1_if (clk);
AXIS_Bus #(IN_AXIS_WIDTH) operand2_if (clk);
AXIS_Bus #(OUT_AXIS_WIDTH) result_if (clk);
initial begin : clk_gen
clk <= 1'b0;
forever begin
#(CLK_PERIOD/2);
clk <= ~clk;
end
end : clk_gen
initial begin : rst_gen
rstn <= 1'b0;
repeat (CNT_RESET_CYCLE)
@(posedge clk);
rstn <= 1'b1;
end : rst_gen
test_cls #(.DATA_WIDTH(ADDER_WIDTH), .AXIS_IN_WIDTH(IN_AXIS_WIDTH), .AXIS_OUT_WIDTH(OUT_AXIS_WIDTH)) test;
initial begin : stim_gen
if ($test$plusargs("SEED")) begin
int seed;
$value$plusargs("SEED=%d", seed);
$display("Simalation run with random seed = %0d", seed);
$urandom(seed);
end
else
$display("Simulation run with default random seed");
test = new(operand1_if, operand2_if, result_if);
@(posedge rstn);
test.run();
end : stim_gen
adder_axis_pipe_mp #(
.ADDER_WIDTH (ADDER_WIDTH)
)
u_adder_axis_pipe_mp(
.ACLK_I (clk),
.ARST_N (rstn),
.AXIS_OPERAND1_IF (operand1_if),
.AXIS_OPERAND2_IF (operand2_if),
.AXIS_RESULT_IF (result_if)
);
initial begin : watchdog
@(posedge rstn);
@(posedge clk);
$display("Transaction quantity = %4d", test.env.cfg.run_for_n_trans);
repeat(test.env.cfg.run_for_n_trans * test.env.cfg.max_delay) @(posedge clk);
$display("ERROR! Watchdog error!");
$finish;
end : watchdog
endmodule
В модуле tb происходит подключение тестового окружение и тестируемого устройства. В блоке clk_gen выполняется генерация тактового сигнала для работы тестируемого устройства и тестового окружения. В блоке rst_gen формируется сигнал сброса тестируемого устройства и тестового окружения, блок stim_gen подключает порты блока adder_axis_pipe_mp к тестируемому окружению и запускает тест. В качестве начального значения для random используется переменная SEED, которая передается через параметры командой строки (PLUSARG).
6. Вывод
# Simulation run with default random seed
# Transaction quantity = 568
# [checker_cls] Pass!
...
# Simalation run with random seed = 68
# Transaction quantity = 563
# [checker_cls] Pass!
Было разработано тестовое окружение для сумматора с интерфейсами AXI-Stream на языке SystemVerilog с использованием классов. В результате получился набор классов, каждый из которых функционально закончен, что позволяет их переиспользовать использовать повторно
в других тестовых окружениях. Генерация случайных значений стала проще при использовании метода класса randomize(). С физическим интерфейсом взаимодействую только классы driver_cls и monitor_cls. Не рассмотрена технология тестирования UVM, возможно будет позже.
wesker_96
Мой комментарий будет кратким — большое спасибо.