fpga-systems-magazine

UVM тест таблицы sin/cos

Главная » Статьи » Языки » SystemVerilog
Amurak
19.01.2021 10:53
10853
5
0.0

Оглавление

1 Аннотация 

2 Описание тестируемого компонента

3 Описание тестового окружения 

3.1 Верхний уровень

3.2 Тест 

3.3 Транзакции

3.4 Драйвер 

3.5 Монитор

3.6 Scoreboard

3.7 Агент

3.8 Генератор транзакций

3.9 Итог

4 Послесловие

Список условных обозначений, сокращений и терминов
DUT – Design Under Test
HDL – Hardware Description Language
TLM – Transaction-Level Modeling
UVM – Universal Verification Methodology
VHDL – Very High Speed Integrated Circuit Hardware Description Language

1 Аннотация

В данном руководстве описывается пример построения тестового окружения с использованием UVM для проверки компонента, описанного при помощи HDL.
В качестве тестируемого компонента (DUT) используется таблица синуса/косинуса, описанная на языке VHDL.

Схема подключения тестируемого компонента к тестовому окружению показана на рисунке 1.1.


Рисунок 1.1 – Схема тестового окружения


В простейшем случае тестовое окружение содержит в себе:

  • генератор входных воздействий для подачи сигнала на DUT;
  • некий эталон, функциональность которого должен реализовывать DUT;
  • блок сравнения результатов, полученных с эталона и с выхода DUT.

По результатам работы тестового окружения делается вывод о том, насколько тестируемый компонент соответствует эталонной модели.

2 Описание тестируемого компонента

Тестируемый компонент представляет собой табличную реализацию функций sin(x), cos(x)
в целочисленной арифметике. Графики данных функций представлены на рисунке 2.1


Рисунок 2.1 – Графики функций sin(x), cos(x)

Интерфейс DUT представлен в таблице 2.1.

Таблица 2.1 – Порты DUT

Name Dir Type Description
iCLK in std _ logic тактовый сигнал
iPHASE _ V in std _ logic входная значимость
iPHASE  in [12]unsigned фаза
oSINCOS _ V out std _ logic выходная значимость
oSIN out [16]signed  синус
oCOS out [16]signed  косинус

Вход фазы iPHASE, сопровождаемый значимостью iPHASE _ V, представляет собой 12-битное беззнаковое число в диапазоне [0...4095].

Выходы синуса oSIN и косинуса oCOS представляют собой 16-битные знаковые числа в диапазоне [−32768...32767] и сопровождаются значимостью oSINCOS _ V.

Временная диаграмма работы DUT представлена на рисунке 2.2.


Рисунок 2.2 – Временная диаграмма DUT

Приведенная диаграмма не учитывает задержку между приемом и выдачей данных, а также особенности тактирования

Функциональная схема тестируемого компонента показана на рисунке 2.3


Рисунок 2.3 – Функциональная схема DUT

Ядро компонента – память, в которой хранятся значения синуса/косинуса. Для заполнения памяти используется функция инициализации.

Помимо этого в компоненте используются триггеры для входных и выходных сигналов. Сигнал значимости транслируется со входа на выход с учетом используемых триггеров.

3 Описание тестового окружения

3.1 Верхний уровень

Верхним уровнем тестового окружения в данном примере является файл tb _ top.sv. Создаем файл с таким названием и следующим содержанием:

// tb_top.sv
‘timescale 100ps/100ps

module tb_top;
 bit clk = 0; // simple clock
 always #5 clk = ~clk; // 100 MHz
endmodule

Здесь нет ничего интересного, кроме объявленного тактового сигнала.
Сюда необходимо подключить тестируемый компонет, для этого создадим файл sincos _ if.sv, в котором опишем следующий интерфейс:

// sincos_if.sv
interface sincos_if (input bit iclk);
    bit iphase_v;
    bit[11:0] iphase;
    bit osincos_v;
    bit[15:0] osin;
    bit[15:0] ocos;
endinterface

Данный интерфейс схож с интерфейсом тестируемого компонента (см. 2). Дополним файл tb _ top.sv кодом подключения DUT:

sincos_if sincos_if_h(clk); // connect iclk to clk

sin_cos_table #(
)
dut(
      .iCLK (sincos_if_h.iclk)
    , .iPHASE_V (sincos_if_h.iphase_v)
    , .iPHASE (sincos_if_h.iphase)
    , .oSINCOS_V (sincos_if_h.osincos_v)
    , .oSIN (sincos_if_h.osin)
    , .oCOS (sincos_if_h.ocos)
 );
Особенности сопряжения интерфейсов при симуляции смешанных (VHDL/Verilog) исходников читайте в документации к используемому вами симулятору.

На текущем этапе наше тестовое окружение имеет вид, показанный на рисунке 3.1.


Рисунок 3.1 – Тестовое окружение

На одном клоке далеко не уедешь, поэтому самое время добавить немножко UVM.

3.2 Тест

Первое, что необходимо сделать для создания тестового окружения по UVM, это создать тест. Для этого используется класс uvm _ test.

Создадим файл sincos _ test _ default.svh со следующим содержанием:

// sincos_test_default.svh
class sincos_test_default extends uvm_test; // [UVM] class
    ‘uvm_component_utils(sincos_test_default) // [UVM] macro

    extern function new(string name, uvm_component parent);
endclass

function sincos_test_default::new(string name, uvm_component parent);
    super.new(name, parent);
endfunction

Здесь для нашего теста объявлен класс, наследующий класс uvm _ test. Чтобы тест можно было запустить, в нем, как минимум, должен быть объявлен конструктор (функция new()).

Поскольку это ООП, то во всех классах должна быть реализована функция-конструкторкласса.

 

Здесь и далее комментарии с метками [UVM] отмечают код, использующий ресурсы UVM библиотек. В данном руководстве я не буду объяснять, что этот код делает, его просто нужно вставлять. Для полного понимания смысла жизни RTFM по библиотекам UVM.

Для удобства подключения UVM компонентов, которые мы будем реализовывать, создадим пакет sincos _ package.sv и добавим в него наш тест:

// sincos_package.sv
package sincos_package;
    import uvm_pkg::*; // [UVM] package
    ‘include "uvm_macros.svh" // [UVM] package

    ‘include "sincos_test_default.svh"
endpackage

Теперь наш тест можно запустить из созданного ранее тестового окружения. Для этого дополним файл tb _ top.sv следующим кодом:

import uvm_pkg::*; // [UVM] package
‘include "uvm_macros.svh" // [UVM] macroses
import sincos_package::*; // connect our package

initial begin
    run_test("sincos_test_default"); // [UVM] run test routine
end

На текущем этапе наше тестовое окружение имеет вид, показанный на рисунке 3.2.


Рисунок 3.2 – Тестовое окружение с пустым тестом

Здесь мы видим наш тестируемый компонент, подключенный через интерфейс; тактовый сигнал, поступающий на этот интерфейс и пустой тест, который никак не  взаимодействует с DUT.

Чтобы связать тестируемый компонент с тестом, нужно передать в тест интерфейс, к которому подключен DUT. Для этого модернизируем файлы tb _ top.sv и  sincos _ test _ default.svh:

// tb_top.sv
initial begin
    uvm_config_db #(virtual sincos_if)::set( // [UVM] pass interface
        null, "*", "sincos_if_h", sincos_if_h); // to UVM database
    run_test("sincos_test_default"); // [UVM] run test routine
end
// sincos_test_default.svh
class sincos_test_default extends uvm_test; // [UVM] class
    ‘uvm_component_utils(sincos_test_default) // [UVM] macro

    extern function new(string name, uvm_component parent);
    extern function void build_phase(uvm_phase phase); // [UVM] build phase

    virtual sincos_if sincos_if_h;
endclass

function void sincos_test_default::build_phase(uvm_phase phase);
    // get bfm from database
    if (!uvm_config_db #(virtual sincos_if)::get( // [UVM] try to get interface
        this, "", "sincos_if_h", sincos_if_h) // from uvm database
    ) ‘uvm_fatal("BFM", "Failed to get bfm"); // otherwise throw error
endfunction

После этого тестовое окружение примет вид, показанный на рисунке 3.3.


Рисунок 3.3 – Тестовое окружение с пустым тестом и подключенным интерфейсом

Теперь мы можем запускать пустой UVM тест и у него есть связь с нашим тестируемым компонентом.

3.3 Транзакции

Для дальнейшего описания тестового окружения нам нужно определить в каком виде по нашему тесту будут передаваться данные, как из них будут формироваться входные воздействия на DUT, в каком виде будут считываться выходные сигналы, с чем будет работать эталонная модель и так далее.

Для упрощения этих задач в UVM существуют транзакции, для описания которых используется класс uvm _ sequence _ item.

Создадим файл sincos _ seqi.svh, в котором опишем транзакцию для нашего примера:

// sincos_seqi.svh
class sincos_seqi extends uvm_sequence_item; // [UVM] class
    ‘uvm_object_utils(sincos_seqi); // [UVM] macro

    extern function new(string name = "sincos_seqi");

    rand int phase_v[];
    rand int phase;
    int sin;
     int cos;

    constraint c_phase_v {
        foreach(phase_v[i])
            phase_v[i] inside {[0:1]};
            phase_v.size inside {[5:5]};
            phase_v.sum == 1;
    }

    constraint c_phase {
        phase inside{[0:4095]};
    }

    extern function bit do_compare(uvm_object rhs, uvm_comparer comparer);
    extern function string convert2string();
endclass

Здесь объявлен класс, наследующий класс uvm _ sequence _ item.

В транзакции используются следующие переменные:

  • phase _ v – массив, предназначенный для формирования сигнала значимости. Данная переменная объявлена как «rand» для возможности использования рандомизации при создании новых транзакций;
  • phase – предназначена для передачи значений фазы. Также объявлена как «rand»;
  • sin – предназначена для передачи значений синуса;
  • cos – предназначена для передачи значений косинуса.

Чтобы ограничить значения, которые могут принимать переменные при рандомизации, используются ограничения c _ phase _ v (ограничивает размер массива и гарантирует, что только один из его элементов будет равен единице) и c _ phase (ограничивает значения фаз).

Помимо этого в транзакции реализованы две функции: do _ compare() и convert2string().

Функция do _ compare() имеет следующую реализацию:

function bit sincos_seqi::do_compare(uvm_object rhs, uvm_comparer comparer);
    sincos_seqi RHS;
    bit same;

    same = super.do_compare(rhs, comparer); // [UVM] call papa

    $cast(RHS, rhs);
    same = (phase == RHS.phase && sin == RHS.sin && cos == RHS.cos) && same;
    return same;
endfunction

Функция предназначена для сравнения двух транзакций и возвращает 1, если они равны. В противном случае – 0.

Функция convert2string() имеет следующую реализацию:

function string sincos_seqi::convert2string();
    string s;
    s = $sformatf("phase = %6d; sin = %6d, cos = %6d", phase, sin, cos);
    return s;
endfunction

Функция предназначена для представления транзакции в виде строки для дальнейшего использования, например, в выводе логов.

Теперь нужно добавить описанную транзакцию в пакет sincos _ package.sv:

‘include "sincos_seqi.svh"
typedef uvm_sequencer #(sincos_seqi) sincos_seqr; // [UVM] sequencer

Здесь же добавляем uvm _ sequencer, который будет работать с нашими транзакциями.

3.4 Драйвер

Теперь, когда у нас есть описание транзакций для нашего теста, можно рассмотреть компонент, который будет преобразовывать их в сигналы описанного ранее интерфейса. Для реализации такого компонента используется класс uvm _ driver – драйвер.

Создадим файл sincos _ drvr.svh, в котором опишем драйвер для нашего примера:

// sincos_drvr.svh
class sincos_drvr extends uvm_driver #(sincos_seqi); // [UVM] class
    ‘uvm_component_utils(sincos_drvr) // [UVM] macro

    extern function new(string name, uvm_component parent);
    extern task run_phase(uvm_phase phase); // [UVM] run phase

    virtual sincos_if sincos_if_h; // our interface
    sincos_seqi sincos_seqi_h; // handler for transactions
endclass

task sincos_drvr::run_phase(uvm_phase phase);
    forever begin
        seq_item_port.get_next_item(sincos_seqi_h); // [UVM] request transaction

        foreach(sincos_seqi_h.phase_v[i]) begin
            @(posedge sincos_if_h.iclk)
            sincos_if_h.iphase_v <= sincos_seqi_h.phase_v[i];
            if (sincos_seqi_h.phase_v[i] == 1’b1)
                sincos_if_h.iphase <= sincos_seqi_h.phase[11:0];
        end

        seq_item_port.item_done(); // [UVM] finish transaction
    end
endtask

Здесь объявлен класс, наследующий класс uvm _ driver. Класс содержит интерфейс sincos _ if _ h и драйвер взаимодействует с ним в процессе выполнения run _ phase.

Так же в run _ phase реализуется механизм TLM: осуществляется запрос транзакции, ее обработка и завершение.

Получив очередную транзакцию, драйвер выполняет цикл для каждого бита из массива phase _ v. Переключение элементов массива происходит по тактовому сигналу интерфейса sincos _ if _ h, при этом каждый бит выставляется на вход iphase _ v. При обнаружении ненулевого бита, драйвер выставляет значение фазы phase транзакции на вход iphase интерфейса.

Добавляем драйвер в пакет sincos _ package.sv:

‘include "sincos_drvr.svh"

3.5 Монитор

Компонент монитор, по сути, выполняет функцию, противоположную драйверу: он анализирует сигналы интерфейса и на основе анализа формирует транзакции. Для реализации такого компонента используется класс uvm _ monitor

Создадим файл sincos _ mont.svh, в котором опишем монитор для нашего примера:

// sincos_mont.svh
class sincos_mont extends uvm_monitor; // [UVM] class
    ‘uvm_component_utils(sincos_mont); // [UVM] macro

    extern function new(string name, uvm_component parent);
    extern function void build_phase(uvm_phase phase); // [UVM] build phase
    extern task run_phase(uvm_phase phase); // [UVM] run phase

    virtual sincos_if sincos_if_h; // our interface

    sincos_aprt sincos_aprt_i; // analysis port, input
    sincos_seqi sincos_seqi_i; // transaction, input
    sincos_aprt sincos_aprt_o; // analysis port, output
    sincos_seqi sincos_seqi_o; // transaction, output
endclass

function void sincos_mont::build_phase(uvm_phase phase);
    // build analysis ports
    sincos_aprt_i = new("sincos_aprt_i", this);
    sincos_aprt_o = new("sincos_aprt_o", this);
endfunction

task sincos_mont::run_phase(uvm_phase phase);
    forever @(posedge sincos_if_h.iclk) begin
        if (sincos_if_h.iphase_v == 1) begin
            sincos_seqi_i = sincos_seqi::type_id::create("sincos_seqi_i");
            sincos_seqi_i.phase = sincos_if_h.iphase;
            sincos_aprt_i.write(sincos_seqi_i); // [UVM] write to aprt
        end

        if (sincos_if_h.osincos_v == 1) begin
            sincos_seqi_o = sincos_seqi::type_id::create("sincos_seqi_o");
            sincos_seqi_o.sin = $signed(sincos_if_h.osin);
            sincos_seqi_o.cos = $signed(sincos_if_h.ocos);
            sincos_aprt_o.write(sincos_seqi_o); // [UVM] write to aprt
        end
    end
endtask

 Здесь объявлен класс, наследующий класс uvm _ monitor. Класс содержит интерфейс sincos _ if _ h и монитор взаимодействует с ним в процессе выполнения run _ phase.
По тактовому сигналу монитор проверяет входную (iphase _ v) значимость интерфейса sincos _ if _ h. Если она не равна нулю, то формируется новая входная транзакция (sincos _ seqi _ i), которая записывается в порт анализа (sincos _ aprt _ i).

Аналогично, по выходной значимости (osincos _ v), формируется выходная транзакция (sincos _ seqi _ o), записываемая в (sincos _ aprt _ o).

Добавляем монитор в пакет sincos _ package.sv:

typedef uvm_analysis_port #(sincos_seqi) sincos_aprt;
‘include "sincos_mont.svh"

 Здесь же добавляем uvm _ analysis _ port, который будет работать с нашими транзакциями.

3.6 Scoreboard

Поскольку прямой перевод слова «scoreboard» (табло) здесь не очень подходит, далее будет использоваться английское написание.

Данный компонент выполняет две функции:

  • формирование эталонной транзакции на основе данных, полученных из анализа входных сигналов интерфейса sincos _ if _ h;
  • сравнивание эталонной транзакции с данными, полученными из анализа выходных сигналов интерфейса sincos _ if _ h.

Создадим файл sincos _ scrb.svh, в котором опишем scoreboard для нашего примера:

// sincos_scrb.svh
‘uvm_analysis_imp_decl(_i) // [UVM] macro
‘uvm_analysis_imp_decl(_o) // [UVM] macro

class sincos_scrb extends uvm_scoreboard; // [UVM] class
    ‘uvm_component_utils(sincos_scrb) // [UVM] macro

    extern function new(string name, uvm_component parent);
    extern function void build_phase(uvm_phase phase); // [UVM] build phase

    uvm_analysis_imp_i #(sincos_seqi, sincos_scrb) sincos_aprt_i;
    uvm_analysis_imp_o #(sincos_seqi, sincos_scrb) sincos_aprt_o;

    sincos_seqi sincos_seqi_queue_i[$];
    sincos_seqi sincos_seqi_queue_o[$];

    extern virtual function void write_i(sincos_seqi sincos_seqi_h);
    extern virtual function void write_o(sincos_seqi sincos_seqi_h);

    extern function void processing();

    extern virtual function int get_ideal_sin(int phase, int max = (2 ** 15 - 1));
    extern virtual function int get_ideal_cos(int phase, int max = (2 ** 15 - 1));
endclass

 Здесь объявлен класс, наследующий класс uvm _ scoreboard. Класс содержит порты анализа для входных (sincos _ aprt _ i) и выходных (sinco _ aprt _ o) транзакций. При записи данных в эти порты вызываются функции write _ i() и write _ o() соответственно:

function void sincos_scrb::write_i(sincos_seqi sincos_seqi_h);
    sincos_seqi_queue_i.push_back(sincos_seqi_h);
endfunction

function void sincos_scrb::write_o(sincos_seqi sincos_seqi_h);
    sincos_seqi_queue_o.push_back(sincos_seqi_h);
    processing();
endfunction

 При вызове функции write _ i входная транзакция записывается в очередь sincos _ seqi _ queue _ i. При вызове функции write _ o выходная транзакция записывается в очередь sincos _ seqi _ queue _ o, после чего вызывается функция processing():

function void sincos_scrb::processing();
    sincos_seqi sincos_seqi_i;
    sincos_seqi sincos_seqi_o;
    string data_str;

    sincos_seqi_i = sincos_seqi_queue_i.pop_front();
    sincos_seqi_i.sin = get_ideal_sin(sincos_seqi_i.phase);
    sincos_seqi_i.cos = get_ideal_cos(sincos_seqi_i.phase);

    sincos_seqi_o = sincos_seqi_queue_o.pop_front();
    sincos_seqi_o.phase = sincos_seqi_i.phase;

    data_str = {
        "\n", "actual: ", sincos_seqi_o.convert2string(),
        "\n", "predicted: ", sincos_seqi_i.convert2string()
    };

    if (!sincos_seqi_i.compare(sincos_seqi_o)) begin
        ‘uvm_error("FAIL", data_str)
        fail_cnt++;
    end else
        ‘uvm_info("PASS", data_str, UVM_HIGH)
endfunction

 Из очереди входных транзакций считывается sincos _ seqi _ i и для ее значения фазы phase рассчитываются эталонные значения sin/cos путем вызова функций get _ ideal _ sin()/get _ ideal _ cos().

Из очереди выходных транзакций считывается sincos _ seqi _ o и ее значение фазы phase копируется из входной транзакции (для простоты сравнения).
После этого формируется строка для вывода значений двух транзакций, выполняется их сравнение и результат выводится в лог.

Таким образом, в простейшем случае, можно оценить результаты работы теста путем чтения лога.

Добавляем scoreboard в пакет sincos _ package.sv:

‘include "sincos_scoreboard.svh"

 3.7 Агент

Компоненты класса uvm _ agent используются для группировки других компонентов, работающих с одним интерфейсом. Для нашего примера мы сгруппируем написанные ранее драйвер, монитор и scoreboard.

Создадим файл sincos _ agnt.svh:

// sincos_agnt.svh
class sincos_agnt extends uvm_agent; // [UVM] class
    ‘uvm_component_utils(sincos_agnt) // [UVM] macro

    extern function new(string name, uvm_component parent);
    extern function void build_phase(uvm_phase phase); // [UVM] build phase
    extern function void connect_phase(uvm_phase phase); // [UVM] connect phase

    virtual sincos_if sincos_if_h; // our interface

    sincos_seqr sincos_seqr_h;
    sincos_drvr sincos_drvr_h;
    sincos_mont sincos_mont_h;
    sincos_scrb sincos_scrb_h;
endclass

function void sincos_agnt::build_phase(uvm_phase phase);
    sincos_seqr_h = uvm_sequencer #(sincos_seqi)::type_id::create("sincos_seqr_h", this);
    sincos_drvr_h = sincos_drvr::type_id::create("sincos_drvr_h", this);
    sincos_mont_h = sincos_mont::type_id::create("sincos_mont_h", this);
    sincos_scrb_h = sincos_scrb::type_id::create("sincos_scrb_h", this);

    sincos_drvr_h.sincos_if_h = this.sincos_if_h;
    sincos_mont_h.sincos_if_h = this.sincos_if_h;
endfunction

function void sincos_agnt::connect_phase(uvm_phase phase);
    sincos_drvr_h.seq_item_port.connect(sincos_seqr_h.seq_item_export);
    sincos_mont_h.sincos_aprt_i.connect(sincos_scrb_h.sincos_aprt_i);
    sincos_mont_h.sincos_aprt_o.connect(sincos_scrb_h.sincos_aprt_o);
endfunction

 Здесь объявлен класс, наследующий класс uvm _ agent. Класс содержит интерфейс sincos _ if _ h, секвенсер, драйвер, монитор и scoreboard.

В функции connect _ phase интерфейс передается в драйвер и монитор, драйвер подключается к секвенсеру, порты анализа монитора подключаются к соответствующим портам анализа scoreboard. 

Полученный агент имеет структуру, представленную на рисунке 3.4.


Рисунок 3.4 – Структура класса-агента

Не забываем добавить агент в пакет sincos _ package.sv:

‘include "sincos_agnt.svh"

 Осталось встроить полученный компонент в тест, который мы создали в 3.2 и добавить к нему генератор транзакций (sequence).

3.8 Генератор транзакций

Генератор транзакций предназначен для формирования, очереди транзакций по некоторому алгоритму. Для реализации такого компонента используется класс uvm _ sequence.

Создадим файл sincos _ seqc _ default.svh, в котором опишем генератор для нашего теста sincos _ test _ default:

// sincos_seqc_default.svh
class sincos_seqc_default extends uvm_sequence #(sincos_seqi); // [UVM] class
    ‘uvm_object_utils(sincos_seqc_default); // [UVM] macro

    extern function new(string name = "sincos_seqc_default");
    extern task body();

    sincos_seqi sincos_seqi_h;
endclass

task sincos_seqc_default::body();
    repeat(100) begin
        sincos_seqi_h = sincos_seqi::type_id::create("sincos_seqi_h");
        start_item(sincos_seqi_h); // [UVM] start transaction
        assert(sincos_seqi_h.randomize());
        finish_item(sincos_seqi_h); // [UVM] finish transaction
    end
endtask

 Здесь объявлен класс, наследующий класс uvm _ sequence. Описанный генератор в цикле выполняет следующие действия:

  • создает новую транзакцию;
  • рандомизирует ее;
  • заканчивает транзакцию.

3.9 Итог

Встраиваем описанные нами компоненты в тест, для этого возвращаемся к файлу sincos _ test _ default.svh:

// sincos_test_default.svh
class sincos_test_default extends uvm_test; // [UVM] class
    ‘uvm_component_utils(sincos_test_default) // [UVM] macro

    extern function new(string name, uvm_component parent);
    extern function void build_phase(uvm_phase phase); // [UVM] build phase
    extern task run_phase(uvm_phase phase); // [UVM] run phase

    virtual sincos_if sincos_if_h; // virtual handler

    sincos_agnt sincos_agnt_h;
    sincos_seqc_default sincos_seqc_default_h;
endclass

function void sincos_test_default::build_phase(uvm_phase phase);
    // get bfm from database
    if (!uvm_config_db #(virtual sincos_if)::get( // [UVM] try get interface
        this, "", "sincos_if_h", sincos_if_h) // from uvm database
    ) ‘uvm_fatal("BFM", "Failed to get bfm"); // otherwise throw error

    sincos_agnt_h = sincos_agnt::type_id::create("sincos_agnt_h", this);
    sincos_agnt_h.sincos_if_h = this.sincos_if_h;

    sincos_seqc_default_h =
        sincos_seqc_default::type_id::create("sincos_seqc_default_h", this);
endfunction

task sincos_test_default::run_phase(uvm_phase phase);
    phase.raise_objection(this); // [UVM] start sequence
        sincos_seqc_default_h.start(sincos_agnt_h.sincos_seqr_h);
    phase.drop_objection(this); // [UVM] finish sequence
endtask

 Здесь мы добавили агент и генератор транзакций. Интерфейс, полученный ранее из верхнего уровня, передается в агент, в результате чего все его компоненты, использующие этот интерфейс, оказываются подключенными к DUT.

Остается только запустить генератор, использовав для него секвенсер, расположенный внутри агента.

В результате наше тестовое окружение имеет вид, показанный на рисунке 3.5.


Рисунок 3.5 – Итоговое тестовое окружение

Теперь после запуска теста генератор начнет выдавать транзакции, передавать их в агент, после чего они будут переданы драйверу (по запросу от него). Драйвер сформирует временную диаграмму на вход DUT. DUT выдаст сигналы на выход. Мониторы считают вход и выход тестируемого компонента, сформируют транзакции и передадут их в scoreboard. Scoreboard, в свою очередь, определит, насколько работа DUT соответствует эталону.

4 Послесловие

Исходные коды для рассмотренного примера выложены в репозиторий.

Для дальнейшего изучения UVM и подходов к написанию крутых тестовых окружений рекомендую почитать следующие источники:

  • Accellera UVM Class Reference Manual 1.2 – официальное описание библиотеки UVM
  • Accellera UVM Users Guide 1.2 – обширное описание техник применения компонентов UVM
  • Salemi R., The UVM Primer - An Introduction to the Universal Verification Methodology(2013) Собственно, данный пример был создан на основе примеров из этой книги.Spear C., SystemVerilog for Verification A Guide to Learning the Testbench Language Features(2012)
  • Verification Academy – сайт содержит больше количество информации по верификации, для полного доступа нужна регистрация на корпоративную почту
  • Гитхаб – можно найти примеры

Если вы дочитали до этого момента, то респект.

Скачать статью в формате PDF (залогиньтесь)

Скачать статью в формате PDF

Поддержать автора Поддержать проект fpga-systems

 

10853
5
0.0

Всего комментариев : 5
avatar
1 aasharapov • 14:46, 27.01.2021
Входная и выходная значимость режет глаз. Знак? 
А, нет, не знак: признак начала нового значения - рис. 2.2.
Но на том же рисунке что за (0) в конце как этого признака для фазы, так и для самой фазы?
avatar
2 yuri3464 • 20:27, 04.05.2021
Господа, с моей точки зрения (я занимался верификацией много лет и мои верификационные IP использовались  в MIPS, Apple (клиент компании Denali, сейчас Cadence) и других компаниях), так вот, с моей точки зрения, это не самый удачный пример. Я могу разобрать его по строчкам, но главная проблема:

Когда совершенно новый человек на это смотрит, у него возникает вопрос "а зачем городить весь этот огород для верификации модуля, у которого один вход, один выход и все данные передаются последовательным burst-ом? Зачем для этого все эти транзакции, порты, scoreboard итд? Почему бы не написать один task на верилоге длиной 30 строк, который будет в цикле класть на вход данное и проверять выход?"

Так вот, чтобы такого вопроса не возникало, в примере должны быть:

1. Или конвейерные транзакции, например как на шине AXI.

2. Или транзакции с ответами, которые выходят в другом порядке, чем запросы.

3. Или транзакции с interleave.

4. Или несколько источников транзакций.

5. В рандомизации должны быть интересные условия в constraints, а не просто de-facto эквивалент нескольких вызовов функции $urandom.

6. В примере нужно чтобы где-нибудь были covergroups, потому что рандомизация без functional coverage сразу вызывает вопрос "а как мы проверим, что наши случайные тестs покрывают все случаи, требуемые по функциональной спецификации на DUT? Давайте лучше делать directed тесты и constrained random только как дополнение".

Иллюстрация: транзакции на шине AXI - адрес новой транзакции может оказаться на шине раньше чем данные  предыдущей транзакции - вот здесь конструкция драйвер / монитор / scoreboard упрощает верификацию, а не усложняет ее, как с кодом в вашем примере:



Еще более интересные случаи, когда и адрес, и данные второй транзакции оказываются целиком между адресом и данными первой транзакции, или когда транфсеры данных второй транзакции переплетаются (interleave) с данными первой транзакции. Вот тут действительно нужен scoreboard (хотя эти случаи не годятся для вводного примера).

*** Поскольку прямой перевод слова «scoreboard» (табло) здесь не очень подходит, далее будет использоваться английское написание. ***

Кстати, а вы знаете, почему scoreboard так называется ("табло")? Потому что при верификации блоков посложнее в нем действительно хранится таблица транзакций in-flight ("в полете") которые могут заканчиваться не в том порядке, в котором выходили запросы. В рассматриваемом случае таблица состоит из двух очередей, поэтому для нее название "табло" действительно не очень подходит, но для примера с конвейерными транзакциями (не говоря уже об out-of-order и interleave) это совершенно подходящее название - для них действительно нужно строить табло с transaction ID.

Да, знаю, что создать простой и убедительный пример трудно. Но читателя нужно убедить, что все эти методики реально облегчают жизнь, а ваш пример в этом не убеждает. К сожалению 90%+ примеров в интернете и книжках тоже страдают этой проблемой.

В частности упомянутая книжка Ray Salemi - это просто слабая книга. Демонстрировать UVM на примере TinyALU - это примерно как агитировать за использование C++ с помощью следующего кода для сложения 2+2:

Number a, b, c;

a = new Number (2);
b = new Number (2);
c = new Number ();

c.setValue (a.getValue () + b.getValue ());
cout << setw (4) << c.getValue ();

Читатель на это посмотрит и скажет: "ой, спасибо, я лучше вернусь к своему питончику, где я могу написать print 2+2".
avatar
5 Amurak • 11:54, 08.05.2021
Благодарю за развернутый комментарий)

Цель конкретно этой статьи (точнее статья является копией PDF, выложенной на гитхабе вместе с исходниками)  именно в том, чтобы построчно разобрать на самом простейшем примере самые базовые конструкции UVM. Сам пример, по сути, является калькой примера из Ray Salemi - не знаю, насколько книга слабая, но когда мы с товарищем вкатывались в UVM, мы начинали именно с нее. На мой взгляд в ней вполне себе объясняется, зачем городить все эти транзакции, порты и прочие табло и как раз разобрано, как от одного таска на верилоге перейти ко всему этому огороду.
Поскольку я занимаюсь цифровой обработкой сигналов, то я просто переписал пример с ALU на более близкий мне пример с таблицей sin/cos.

Убеждать читателя в том, что ему стоит использовать UVM я точно не собирался, так как это совсем другой вопрос.

С уважением, Виталий.
avatar
3 yuri3464 • 20:42, 04.05.2021
Поправка к предыдущему комменту: "адрес новой транзакции может оказаться на шине раньше чем данные  предыдущей транзакции" - я имел в виду "адрес новой транзакции может оказаться на шине раньше чем данные  для записи (write data) предыдущей транзакции. То есть транзакция состоит из адреса и данных и они оказываются на шине в течение нескольких циклов, но не все подряд и в промежутках на шине может появиться адрес или данные другой транзакции"
avatar
4 Amurak • 11:38, 08.05.2021
Опечатка закралась в рисунок) Там должно быть просто iPHASE_V и iPHASE.
avatar

FPGA-Systems – это живое, постоянно обновляемое и растущее сообщество.
Хочешь быть в курсе всех новостей и актуальных событий в области?
Подпишись на рассылку

ePN