Тестирование целочисленного сумматора с интерфейсами AXI-Stream на SystemVerilog

от автора

Сменив недавно работу, перейдя с языка 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 будет осуществляться с помощью виртуальных интерфейсов которые показались мне более простыми в использовании.

Рисунок 1 - Структурная схема тестового окружения с учетом особенностей сумматора.

Рисунок 1 — Структурная схема тестового окружения с учетом особенностей сумматора.

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, возможно будет позже.

Исходные коды: https://github.com/Finnetrib/AXI-Stream-Adder/tree/axis-adder-v2


ссылка на оригинал статьи https://habr.com/ru/articles/835030/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *