日常记录(64)DPI

DPI部分和so文件和.a文件相互区分,
so是编译成动态库以后,使用sv_lib、sv_root命令,在运行时使用,获取到API;
.a文件是静态链接后,使用-P xxx.tab xxx.a文件,在编译时候使用,生成的simv,是PLI,有$符号;
DPI这部分,是直接将c文件当成普通的sv文件,放在之前,直接编译到simv。

最简单的例子

SV代码

由sv代码调用C代码,import表示从外部导入,即将外部的C代码导入到这里、
function int factorial(input int i)表示的是目前的代码调用的函数
factorial = function int my_factorial(input int i)表示目前的调用函数是my_factorial,而外部对应的C函数为factorial。这里的重命名方法可以防止一些C和SV的关键字冲突。

import "DPI-C" function int factorial(input int i);
import "DPI-C" factorial = function int my_factorial(input int i);

program test_dpi ();
    initial begin
        int ans = factorial(5);
        int ans2 = my_factorial(5);
        $display("ans is %0d", ans);
        $display("ans is %0d", ans2);
    end
endprogram : test_dpi

C代码

int factorial(int n){
        if(n==1)
            return 1;
        else
            return factorial(n-1) * n;
}

结果

ans is 120
ans is 120

参数方向与C库调用

SV代码

  • 默认参数方向是input类型的,在output类型的变量过程中,被调用的C语言注明了指针。若不注明为指针,则输出值的sum为0。另外不支持ref类型。
  • 在调用的sin函数过程中,是c库的内部函数,因此直接可以使用而不用在C代码中实现。
import "DPI-C" function int addmul(input int a, b, output int sum);
import "DPI-C" function real sin(input real r);
module taa12();
    initial begin
        int sum;
        int ans = addmul(2, 3, sum);
        $display("mul is %d, sum is %d", ans, sum);
    end

    initial begin
        $display("sin(0)=%f", sin(3.1415926/2));
    end
endmodule

C代码

int addmul(int a, int b, int* sum)
{
    *sum = a + b;
    return a*b;
}

输出结果

mul is           6, sum is           5
sin(0)=1.000000

数据类型传递

SV代码

  1. chandle数据类型是专用于DPI的,表示一个C、C++指针。
  2. bit[6:0]和svBitVecVal对应到SV和C。bit对应到svBit。
  3. 功能上实现了上升沿进行计数,下降沿进行置位,避免冲突。
点击查看代码
import "DPI-C" function chandle counter_new();
import "DPI-C" function void delete(input chandle inst);
import "DPI-C" function void counter7(input chandle inst, output bit[6:0] out, input bit[6:0] in, input bit reset, load);
program automatic test ();
    initial begin
        bit [6:0] o1, o2, i1, i2;
        bit reset, load, clk1;
        chandle inst1, inst2;

        inst1 = counter_new();
        inst2 = counter_new();

        fork
            forever begin
                #10 clk1= ~clk1;
            end
            forever begin
                @(posedge clk1) begin
                    counter7(inst1, o1, i1, reset, load);
                    counter7(inst2, o2, i2, reset, load);
                end
            end
        join_none

        reset = 0;
        load = 0;
        i1 = 120;
        i2 = 10;

        @(negedge clk1);
        load = 1;
        @(negedge clk1);
        load = 0;

        #300;
        delete(inst1);
        delete(inst2);
        $finish;
    end
endprogram : test

C代码

  1. 实现了一个计数器的功能实现,使用malloc的方式建立一个空间,创建了一个静态的计数器数据,并返回。delete用于释放空间。counter7用于实现7位的计数功能。
  2. svdpi.h的声明,才使得svBitVecVal等数据类型可以使用,而malloc.h本用于malloc函数,,veriuser.h本用于io_printf函数,可能被vcs自动编译了。
  3. 编译后的vc_hdrs.h文件中包括了C的头文件了。
点击查看代码
#include <svdpi.h>
/* #include <malloc.h> */
/* #include <veriuser.h> */
typedef struct{
    unsigned char cnt;
} c7;

void * counter_new(){
    c7* c = (c7*) malloc(sizeof(c7));
    c->cnt = 0;
    return c;
}

void delete(c7 *inst)
{
    free(inst);
}

void counter7(c7 *inst, svBitVecVal *count, const svBitVecVal *i, const svBit reset, const svBit load)
{
    if(reset){
        inst->cnt = 0;
    }else if (load) {
        inst->cnt = *i;
    }else {
        inst->cnt ++;
    }
    inst->cnt &=0x7f;
    *count = inst->cnt;
    io_printf("C: cnt, i, reset, load, %d, %d, %d, %d\n", *count, *i, reset, load);
}

输出结果

点击查看代码
C: cnt, i, reset, load, 1, 120, 0, 0
C: cnt, i, reset, load, 1, 10, 0, 0
C: cnt, i, reset, load, 120, 120, 0, 1
C: cnt, i, reset, load, 10, 10, 0, 1
C: cnt, i, reset, load, 121, 120, 0, 0
C: cnt, i, reset, load, 11, 10, 0, 0
C: cnt, i, reset, load, 122, 120, 0, 0
C: cnt, i, reset, load, 12, 10, 0, 0
C: cnt, i, reset, load, 123, 120, 0, 0
C: cnt, i, reset, load, 13, 10, 0, 0
C: cnt, i, reset, load, 124, 120, 0, 0
C: cnt, i, reset, load, 14, 10, 0, 0
C: cnt, i, reset, load, 125, 120, 0, 0
C: cnt, i, reset, load, 15, 10, 0, 0
C: cnt, i, reset, load, 126, 120, 0, 0
C: cnt, i, reset, load, 16, 10, 0, 0
C: cnt, i, reset, load, 127, 120, 0, 0
C: cnt, i, reset, load, 17, 10, 0, 0
C: cnt, i, reset, load, 0, 120, 0, 0
C: cnt, i, reset, load, 18, 10, 0, 0
C: cnt, i, reset, load, 1, 120, 0, 0
C: cnt, i, reset, load, 19, 10, 0, 0
C: cnt, i, reset, load, 2, 120, 0, 0
C: cnt, i, reset, load, 20, 10, 0, 0
C: cnt, i, reset, load, 3, 120, 0, 0
C: cnt, i, reset, load, 21, 10, 0, 0
C: cnt, i, reset, load, 4, 120, 0, 0
C: cnt, i, reset, load, 22, 10, 0, 0
C: cnt, i, reset, load, 5, 120, 0, 0
C: cnt, i, reset, load, 23, 10, 0, 0
C: cnt, i, reset, load, 6, 120, 0, 0
C: cnt, i, reset, load, 24, 10, 0, 0
C: cnt, i, reset, load, 7, 120, 0, 0
C: cnt, i, reset, load, 25, 10, 0, 0

四状态数值对应

  1. 单比特的logic 对应svLogic的两位(aval和bval),aval为低位,bval为高位。00表示0,01表示1,10表示z,11表示x。
  2. 向量的logic如[31:0]等对应svLogicVecVal结构体,其中包括了bval和aval,取出对应到各个bit,进行组合确定四状态的值。

C++调用

SV代码

  1. 和上面基本一致,但是改了函数名部分
  2. 还是调用的C的方法。但是对面的C的方法内部又调用了CPP
点击查看代码
import "DPI-C" counter7_new=function chandle counter_new();
import "DPI-C" function void counter7(
    input chandle inst,
    output bit [6:0] cnt,
    input bit [6:0] in,
    input bit reset, load
);

program automatic test ();
    initial begin
        bit [6:0] o1, i1;
        bit reset, load, clk1;
        chandle inst1, inst2;

        inst1 = counter_new();

        fork
            forever begin
                #10 clk1= ~clk1;
            end
            forever begin
                @(posedge clk1) begin
                    counter7(inst1, o1, i1, reset, load);
                end
            end
        join_none

        reset = 0;
        load = 0;
        i1 = 120;

        @(negedge clk1);
        load = 1;
        @(negedge clk1);
        load = 0;

        #300;
        $finish;
    end
endprogram : test

C++代码

  1. 实现了一个C++的类,其中包括了一个内部计数的cnt。
  2. 外部又使用了静态方法,实现了和C++类的连接。其中的this用法什么的都和sv相同。
点击查看代码
#include <svdpi.h>
#include <veriuser.h>

class Counter7
{
    public:
        Counter7();
        void counter7_signal(svBitVecVal *cnt, svBitVecVal *i, svBit reset, svBit load);

    private:
        unsigned char cnt;
};

Counter7::Counter7()
{
    cnt=0;
}

void Counter7::counter7_signal(svBitVecVal *cnt, svBitVecVal *i, svBit reset, svBit load)
{
    if (reset) {
        this->cnt=0;
    }else if (load) {
        this->cnt=*i;
    }else {
        this->cnt++;
    }
    this->cnt &= 0x7F;
    *cnt = this->cnt;
}

extern "C" 
{
    void * counter7_new()
    {
        return new Counter7;
    }

    void counter7(void * inst, svBitVecVal *cnt, svBitVecVal *i, svBit reset, svBit load)
    {
        Counter7 *c7 = (Counter7 *)inst;
        c7->counter7_signal(cnt, i, reset, load);
        io_printf("cnt %0d, i %0d, reset %0d, load %0d\n", *cnt, *i, reset, load);
    }
}

开放数组

SV代码

传入了一个定长数组data[20]

点击查看代码
mport "DPI-C" function void fib_oa(output bit[31:0] data[]);

program taa ();
    bit [31:0] data[20];
    initial begin
        fib_oa(data);
        foreach (data[i]) begin
            $display(i, data[i]);
        end
    end
endprogram : taa

C代码

  1. svOpenArrayHandle用于获取开放数组句柄
  2. svGetArrayPtr用于获取实际的数组元素指针
点击查看代码
#include <svdpi.h>
void fib_oa(const svOpenArrayHandle data_oa)
{
    int i, *data;
    data = (int *) svGetArrayPtr(data_oa);
    data[0] = 1;
    data[1] = 1;
    for (i = 2; i < 20; i++) {
        data[i] = data[i-1] + data[i-2];
    }
}

实际结果

点击查看代码
          0         1
          1         1
          2         2
          3         3
          4         5
          5         8
          6        13
          7        21
          8        34
          9        55
         10        89
         11       144
         12       233
         13       377
         14       610
         15       987
         16      1597
         17      2584
         18      4181
         19      6765

其它方法

开放数组除了上面使用的svOpenArrayHandle和svGetArrayPtr以外,还有很多方法。
svLeft、svRight、svLow、svHigh获取数组左边界、右边界、下界、上界。
svIncrement、svSize、svDimension、svSizeOfArray获取方向、大小、维数、字节量

svGetArrElemPtr获取元素指针。

压缩的开放数组

SV代码

这里的压缩数组中,倒序的9:1,在foreach中也是倒序的。

点击查看代码
import "DPI-C" function void view_pack(input bit[63:0] b64[]);

program test ();
    bit [1:0][0:3][6:-1] bpack [9:1];

    initial begin
        foreach (bpack[i]) begin
            bpack[i] = i;
        end

        bpack[2]=64'h12345678_90abcdef;
        foreach (bpack[i]) begin
            $info("%0d value %0d", i, bpack[i]);
        end
        $display("bpack[2][0] = %h", bpack[2][0]);
        $display("bpack[2][0][0] = %h", bpack[2][0][0]);

        view_pack(bpack);
    end
endprogram : test

C代码

其中的svGetArrElemPtr1用于获取压缩的开放数组h第1维的第i个元素。
可以替换为svGetArrElemPtr。
svGetArrElemPtr2、svGetArrElemPtr3分别为获取第二维、第三维的元素。

点击查看代码
#include <svdpi.h>
void view_pack(const svOpenArrayHandle h)
{
    int i;
    for (i = svLow(h, 1);  i< svHigh(h,1); i++) {
        io_printf("C:b64[%0d]=%llx\n", i, *(long long int *)svGetArrElemPtr1(h,i));
    }
}

运行结果

点击查看代码
Info: "taa12-6.sv", 13: test: at time 0
9 value 9
Info: "taa12-6.sv", 13: test: at time 0
8 value 8
Info: "taa12-6.sv", 13: test: at time 0
7 value 7
Info: "taa12-6.sv", 13: test: at time 0
6 value 6
Info: "taa12-6.sv", 13: test: at time 0
5 value 5
Info: "taa12-6.sv", 13: test: at time 0
4 value 4
Info: "taa12-6.sv", 13: test: at time 0
3 value 3
Info: "taa12-6.sv", 13: test: at time 0
2 value 1311768467294899695
Info: "taa12-6.sv", 13: test: at time 0
1 value 1
bpack[2][0] = 90abcdef
bpack[2][0][0] = 90
C:b64[1]=1
C:b64[2]=1234567890abcdef
C:b64[3]=3
C:b64[4]=4
C:b64[5]=5
C:b64[6]=6
C:b64[7]=7
C:b64[8]=8
$finish at simulation time                    0

传递结构

SV代码

定义了压缩的结构RGB_T,注意pack的返回方式是使用函数名的方式,该函数名指代了返回值的结构体。
将结构体传入C的invert函数方法中。实现传值。

点击查看代码
typedef struct packed { bit [ 7:0] r, g, b; } RGB_T;
import "DPI-C" function void invert(inout RGB_T pstruct);
program automatic test;

    class RGB;
        rand bit [ 7:0] r, g, b;
        function void display(string prefix="");
            $display("%sRGB=%x,%x,%x", prefix, r, g, b);
        endfunction : display

        // Pack the class properties into a struct
        function RGB_T pack();
            pack.r = r; pack.g = g; pack.b = b;
        endfunction : pack

        // Unpack a struct into the class properties
        function void unpack(RGB_T pstruct);
            r = pstruct.r; g = pstruct.g; b = pstruct.b;
        endfunction : unpack
    endclass : RGB
    initial begin
        RGB pixel;
        RGB_T pstruct;
        pixel = new;
        repeat (5) begin
            assert(pixel.randomize()); // Create random pixel
            pixel.display("\nSV: before "); // Print it
            pstruct = pixel.pack(); // Convert to a struct
            invert(pstruct); // Call C to invert bits
            pixel.unpack(pstruct); // Unpack struct to class
            pixel.display("SV: after "); // Print it
        end
    end
endprogram

C代码

定义了结构体和SV对应。然后传值。

点击查看代码
typedef struct {
    unsigned char b, g, r; // x86 big-endian
    /* unsigned char r, g, b; // SPARC format */
} *p_rgb;
void invert(p_rgb rgb) {
    rgb->r = ~rgb->r; // Invert the color values
    rgb->g = ~rgb->g;
    rgb->b = ~rgb->b;
    io_printf("C: Invert rgb=%02x,%02x,%02x\n",
            rgb->r, rgb->g, rgb->b);
}


结果

点击查看代码
SV: before RGB=b9,6c,a7
C: Invert rgb=46,93,58
SV: after RGB=46,93,58

SV: before RGB=5b,80,8a
C: Invert rgb=a4,7f,75
SV: after RGB=a4,7f,75

SV: before RGB=3a,56,33
C: Invert rgb=c5,a9,cc
SV: after RGB=c5,a9,cc

SV: before RGB=07,52,59
C: Invert rgb=f8,ad,a6
SV: after RGB=f8,ad,a6

SV: before RGB=e0,4c,64
C: Invert rgb=1f,b3,9b
SV: after RGB=1f,b3,9b
$finish at simulation time                    0

纯导入与关联导入

纯导入

使用pure关键字在import "DPI-C" 之后。不访问任何全局变量,静态变量,不对文件读写,不进行进程操作等。默认,输出仅依赖于输入。

关联导入

使用context关键字在import "DPI-C" 之后。需要记录上下文。

简单导出

使用export将函数导出给C调用,无需说明返回值、参数,只说明是function,以及function名。
SV代码

module block;
    import "DPI-C" context function void c_display();
    export "DPI-C" function sv_display; // No type or args
    initial c_display();
    function void sv_display();
        $display("SV: block");
    endfunction
endmodule : block

C代码

void c_display() {
    io_printf("C: c_display\n");
    sv_display();
}

结果:

C: c_display
SV: block

上下文与作用域

SV代码

两个module,让block先行,然后保存当前作用域,打印。
第二个module在打印的时候,由于作用域被第一个给改变了,
因此在执行C中调用的SV的时候,调用了第一个module的sv_display。

点击查看代码
module block;
    import "DPI-C" context function void c_display();
    import "DPI-C" context function void save_my_scope();
    export "DPI-C" function sv_display;

    function void sv_display();
        $display("SV: %m");
    endfunction : sv_display

    initial begin
        save_my_scope();
        c_display();
    end
endmodule : block 

module top;
    import "DPI-C" context function void c_display();
    export "DPI-C" function sv_display;

    function void sv_display();
        $display("TSV: %m");
    endfunction : sv_display

    block b1();
    initial #1 c_display();
endmodule : top

C代码

svScope 是全局的量,表示SV的作用域值。
svGetScope用于获取当前对应SV的作用域。
svSetScope用于设置当前对应SV的作用域。

点击查看代码
#include <svdpi.h>

extern void sv_display();
svScope my_scope;

void save_my_scope() {
    my_scope = svGetScope();
}
void c_display() {
    // Print the current scope
    io_printf("\nC: c_display called from scope %s\n",
            svGetNameFromScope(svGetScope()));

    // Set a new scope
    svSetScope(my_scope);
    io_printf("C: calling %s.sv_display\n",
            svGetNameFromScope(svGetScope()));
    sv_display();
}

运行结果

第一次调用过程中,

点击查看代码
C: c_display called from scope top.b1
C: calling top.b1.sv_display
SV: top.b1.sv_display

C: c_display called from scope top
C: calling top.b1.sv_display
SV: top.b1.sv_display

与其它语言交互

SV代码,注明:+script=tpl.pl,注意perl的位置。

import "DPI-C" function int call_perl(string s);
program automatic perl_test;
    int ret_val;
    string script;
    initial begin
        if (!$test$plusargs("script")) begin
            $display("No +script switch found");
            $finish;
        end
        $value$plusargs("script=%s", script);
        $display("SV: Running '%0s'", script);
        ret_val = call_perl(script);
        $display("SV: Perl script returned %0d", ret_val );
    end
endprogram : perl_test

C的system和Verilog的$system,调用外部的程序执行。

#include "vc_hdrs.h"
#include <stdlib.h>
#include <wait.h>
int call_perl(const char* command) {
    int result = system(command);
    return WEXITSTATUS(result);
}

#!/usr/bin/perl
print "Perl: Hello world!\n" ;
exit (3)
posted @ 2022-02-26 21:20  大浪淘沙、  阅读(1165)  评论(0)    收藏  举报