Интерфейс Verilog VPI

Verilog VPI

Verilog Procedural Interface (VPI) – это такой механизм вызвать функцию, написанную на языке C, из кода Verilog во время симуляции проекта. Известно довольно много встроенных в Verilog функций называемых system tasks, например, $display, $monitor, $finish. Про некоторые из них я уже писал. Интерфейс VPI призван облегчить отладку, дать разработчикам возможность добавлять новую функциональность для отладки проектов Verilog HDL.

На первый взгляд может показаться, что этот интерфейс дает возможность писать код для ПЛИС на языке C, но это не так. VPI не позволяет делать синтезируемый для ПЛИС код. Еще раз повторяюсь – это только расширение возможностей отладчика Verilog.

Тем не менее, знать о возможностях VPI довольно полезно.
Я хочу проиллюстрировать использование VPI на простом примере при использовании свободного отладчика Icarus Verilog.

Предположим, мне нужен модуль на C взаимодействующий с отладчиком Verilog. Я хочу из Verilog передавать числовые значения в модуль на C и еще хочу читать какие-то числовые значения из модуля на C.

verilog pli

Для этих целей определим новые функции system tasks такие как: $setval() и $getval(). Эти функции можно будет вызывать из тестбенча на языке Verilog во время симуляции проекта.

Мой тестбенч пусть будет вот такой простой (файл testbench.v):


module testbench();

reg [7:0]v0;
reg [7:0]v1;

initial
begin

$display("read from C module:");
    $getval("myvar0",v0);
    $getval("myvar1",v1);
    $display("v0=%d",v0);
    $display("v1=%d",v1);

$display("pass values to C module..");
    $setval("myvar0",33);
    $setval("myvar1",1);

$display("read from C module:");
    $getval("myvar0",v0);
    $getval("myvar1",v1);
    $display("v0=%d",v0);
    $display("v1=%d",v1);

    $finish;
end

endmodule


 Эта программа Verilog Testbench не делает ничего существенного. Она только:

  • читает две «переменных» myvar0 и myvar1 из внешнего модуля написанного на C и печатает их значения в консоли;
  • передает в этот Си-шный модуль новые значения для этих «переменных»;
  • опять читает значения «переменных» и опять печатает их.

Си-шный модуль, как Вы понимаете, пишется на языке C  :-)
В принципе ничего особенного тут нет, за исключением того, что для взаимодействия с симулятором Verilog нужно пользоваться его библиотекой и его интерфейсными функциями определенными в заголовочном файле vpi_user.h

Каждую новую функцию system task нужно зарегистрировать с помощью vpi_register_systf(). Доступ к переменным симулятора не очень тривиален, но основные функции это конечно vpi_get_value() и vpi_put_value().

Просто приведу код моего модуля VPI на Си вот здесь (файл mypli.c):


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <vpi_user.h>

int my_var0 = 0;
int my_var1 = 0;

int get_my_value(char* pparam_name)
{
    if(strcmp(pparam_name,"myvar0")==0)
    {
        return my_var0;
    }
    else
    if(strcmp(pparam_name,"myvar1")==0)
    {
        return my_var1;
    }
    vpi_printf("MYPLI, cannot get unknown var: %s\n",pparam_name);
    return 0;
}

void set_my_value(char* pparam_name, int value)
{
    if(strcmp(pparam_name,"myvar0")==0)
    {
        my_var0 = value;
        return;
    }
    else
    if(strcmp(pparam_name,"myvar1")==0)
    {
        my_var1 = value;
        return;
    }
    vpi_printf("MYPLI, cannot set unknown var: %s\n",pparam_name);
}

//---------------------------------------------------
static int setval_calltf(char* user_data)
{
    vpiHandle systfref, args_iter, argh0,argh1;
    struct t_vpi_value argval0,argval1;
    PLI_BYTE8 *arg_value0;
    float arg_value1;

    systfref = vpi_handle(vpiSysTfCall, NULL);
    args_iter = vpi_iterate(vpiArgument, systfref);

    argh0 = vpi_scan(args_iter);
    argh1 = vpi_scan(args_iter);

    argval0.format = vpiStringVal;
    vpi_get_value(argh0, &argval0);
    arg_value0 = argval0.value.str;

    argval1.format = vpiRealVal;
    vpi_get_value(argh1, &argval1);
    arg_value1 = argval1.value.real;

    set_my_value(arg_value0,arg_value1);

    vpi_free_object(args_iter);
    return 0;
}

static int getval_calltf(char* user_data)
{
    vpiHandle systfref, args_iter, argh0,argh1;
    struct t_vpi_value argval0,argval1;
    PLI_BYTE8 *arg_value0;

    systfref = vpi_handle(vpiSysTfCall, NULL);
    args_iter = vpi_iterate(vpiArgument, systfref);

    argh0 = vpi_scan(args_iter);
    argh1 = vpi_scan(args_iter);

    argval0.format = vpiStringVal;
    vpi_get_value(argh0, &argval0);
    arg_value0 = argval0.value.str;

    argval1.format = vpiIntVal;
    vpi_get_value(argh1, &argval1);

    argval1.value.integer = get_my_value(arg_value0);
    vpi_put_value(argh1,&argval1,NULL,vpiNoDelay);
    vpi_free_object(args_iter);
    return 0;
}

void register_interface()
{
    s_vpi_systf_data tf_data;

    tf_data.type      = vpiSysTask;
    tf_data.sysfunctype = vpiIntFunc;
    tf_data.compiletf = 0;
    tf_data.sizetf    = 0;
    tf_data.user_data = 0;

    tf_data.tfname    = "$setval";
    tf_data.calltf    = setval_calltf;
    vpi_register_systf(&tf_data);

    tf_data.tfname    = "$getval";
    tf_data.calltf    = getval_calltf;
    vpi_register_systf(&tf_data);
}

void (*vlog_startup_routines[])() = {
    register_interface,
    0
    };


 В этой программе функции «рабочие лошадки» - это set_my_value() и get_my_value(). Они устанавливают и читают переменную для симулятора.

Теперь расскажу о том, как этот модуль скомпилировать.
Я пользуюсь очень простым симулятором Icarus Verilog. Если у Вас рабочая операционная система – это Linux (Ubuntu), то установить Icarus Verilog очень просто:

% sudo apt-get install iverilog

Все готово.

В принципе, скомпилировать модуль можно с помощью обычного компилятора в Linux – gcc. Как-то вот так:

% gcc -c -fpic mypli.c
% gcc -shared -o mypli.vpi mypli.o -lvpi

Но, к сожалению, это может не получиться просто потому, что компилятору нужно дополнительно показать пути к iverilog библиотекам и включаемых файлам. Гораздо проще выдать вот такую команду:

% iverilog-vpi mypli.c

После успешной компиляции, если в коде C нет ошибок, то получится модуль VPI с новыми функциями для симулятора Verilog.

Компилирую testbench с помощью икаруса:

% iverilog-vpi –o qqq testbench.v

Запускаю симулятор икаруса:

% vvp -M. -mmypli qqq
read from C module:
v0 = 0
v1 = 0
pass values to C module..
read from C module:
v0 = 33
v1 = 1

Здесь видно, что с
Ключ –M указывает симулятору дополнительный путь к модулям VPI. Ключ –m говорит какой модуль VPI дополнительно подключить к симуляции.

Вот снимок консоли, где видны все команды:

Verilog VPI Console

Таким образом, мы смогли передать число из программы тестбенч Verilog в модуль на языке C и обратно.

Должен сказать, что программный интерфейс VPI – это часть стандарта языка Verilog HDL IEEE 1364. Это значит, что все симуляторы от вендоров должны поддерживать его.

Язык SystemVerilog, как продолжение языка Verilog так же имеет свой программный интерфейс к внешним модулям. Он называется DPI (Direct Programming Interface). В некоторой степени это развитие и совершенствование интерфейса VPI.

 

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