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

Исходные коды

Комментарии (1)


  1. wesker_96
    09.08.2024 12:51
    +2

    Мой комментарий будет кратким — большое спасибо.