Co-Simulation of C with Verilog in VCS

Co-Simulation of C with Verilog in VCS

Introduction

Verilog PLI(编程语言接口)是一种从Verilog代码调用C或C ++函数的机制。

&#8195 在Verilog代码中调用的函数称为系统调用。 内置系统调用的示例是$ display,$ stop,$ random。PLI允许用户创建自定义系统调用,Verilog语法不允许我们做。 其中一些是:
- Power analysis
- Code coverage tools
- Modifying the Verilog simulation data structure - more accurate delays.
- Custom output displays.
- Co-simulation
- Design debug utilities
- Simulation analysis.
- C-model interface to accelerate simulation.
- Testbench modeling

为了实现PLI的这些应用,C代码应该可以访问Verilog simulator的内部数据结构。 为了方便这个Verilog PLI提供了所谓的acc 程序或简单的访问程序 。

有第二组程序,称为tf程序,或简单的任务和函数程序。tf和acc是PLI 1.0程序,非常广泛而且非常老。 Verilog 2001最新版本介绍的下一组例程称为vpi程序。 这些是小而清澈的PLI例程,因此是新版PLI 2.0。

您可以获得Verilog 2001 LRM或PLI 1.0 IEEE文档,了解提供的每个功能的详细信息。 Verilog IEEE LRM的写法是这样一种方式,即具有硬件背景的人可以理解。 如果您无法掌握上述IEEE文档,那么您可以购买图书部分列出的PLI书籍。

How PLI works

  1. 编写C/C++函数源代码
  2. 编译源代码生成共享文件(在Windows中既是*.DLL, UINX系统中*.so),比如在VCS仿真器中允许静态连接。
  3. verilog程序中调用C函数(通常是testbench中)
  4. 根据仿真器,在verilog代码编译过程中将C/C++函数细节传递给仿真器
  5. 一旦链接就像任何其他Verilog仿真一样运行仿真器
    @ Simulator|center

在仿真器执行Verilog代码期间,只要模拟器遇到用户定义的系统任务(以$开头),执行控制就被传递给PLI例程(C / C ++函数)

编写PLI应用程序

我们看到的例子太基础了,对于任何实际的目的都没有好处。 我们来考虑我们的臭名昭着的计数器示例,并在C中编写DUT参考模型和Checker,并将其链接到Verilog Testbench。 首先列出使用PLI编写C模型的要求。
- 意味着每当输入信号有变化时,调用C模型
- 意味着将Verilog代码中的变化信号(或任何其他信号)值转换为C代码
- 意味着从C代码驱动Verilog代码内的任何信号值

PLI应用程序规范

我们使用PLI定义我们臭名昭着的计数器测试平台的要求。 我们将把我们的PLI函数称为$counter_monitor。
- 用C实现计数器逻辑
- 用C语言实现checker逻辑
- 无论何时检查程序出现故障,终止仿真
@|center

调用C语言函数

在C写一个计数器是如此的酷,但是什么时候增加计数器值? 那么我们需要监视时钟信号的变化。每当时钟变化时,需要执行计数器功能。 这可以通过使用下面的例程来实现.
- 使用acc_vcl_add程序,其语法可以在Verilog PLI LRM中找到
acc_vcl_add程序基本上允许我们监视信号列表,并且每当监视信号变化时,它调用用户定义的功能(称为消费者C程序)。 VCL程序有四个参数:
- Handle to the monitored object
- Consumer C routine to call when the object value changes
- String to be passed to the consumer C routine
- Predefined VCL flags: vcl_verilog_logic for logic monitoring vcl_verilog_strength for strength monitoring
acc_vcl_add(net, display_net, netname, vcl_verilog_logic);

C源码基本部分

Counter_monitor是我们的C函数,它将从Verilog Testbench中调用。 对于任何其他C代码,我们需要包含特定于我们开发的应用程序的头文件。 在我们的例子中,我们需要包含acc程序包含文件。

访问程序acc_initialize初始化访问程序环境,并且必须在C语言应用程序调用任何其他访问例程之前调用。 在退出调用访问例程的C语言应用程序之前,还需要在程序结束时调用acc_close来退出访问例程环境。

// counter.c
#include "acc_user.h"

 handle clk ;
 handle reset ;
 handle enable ;
 handle dut_count ;
 void counter ();

 void counter_monitor() {
   acc_initialize();
   clk = acc_handle_tfarg(1);
   reset = acc_handle_tfarg(2);
   enable = acc_handle_tfarg(3);
   dut_count = acc_handle_tfarg(4);
   acc_vcl_add(clk,counter,null,vcl_verilog_logic);
   acc_close();
 }

 void counter () {
   io_printf("Clock changed state\n");
 }

可以用handle访问verilog对象。handle是预定义的数据类型,它是指向设计层次结构中特定对象的指针。每个handle传达访问c程序有关可访问对象的唯一实例的信息; 信息是关于对象类型,以及如何和在何处查找关于对象的数据。但是我们如何传递特定的对象信息来处理? 那么我们可以通过多种方法来做到这一点,但是现在我们将把Verilog作为参数传递给$ counter_monitor:这些参数可以通过acc_handle_tfarg()例程在C程序中访问。 参数是代码中的数字。

所以clk = acc_handle_tfarg(1)基本上使clk的第一个参数的句柄通过; 同样,我们分配所有的手柄。 现在我们可以使用cc_vcl_add(clk,counter,null,vcl_verilog_logic)程序将clk添加到需要监视的信号列表中。 这里clk是handle(句柄), counter是当clk更改时执行的用户函数。

verilog源代码

以下是计数器示例的简单测试平台的代码。 我们使用下面的代码中所示的语法来调用C函数。 如果传递的对象是一个即时的,那么它应该在双引号内传递。 由于我们所有的对象都是网络或电线,所以没有必要在双引号内传递它们。

//pli_counter_tb.v
 module counter_tb();
  reg enable;
  reg reset;
  reg clk_reg;
  wire clk;
  wire [3:0] count;

 initial begin
   enable = 0;
   clk_reg = 0;
   reset = 0;
   $display("%g , Asserting reset", $time);
    #10  reset = 1;
    #10  reset = 0;
   $display ("%g, Asserting Enable", $time);
    #10  enable = 1;
    #55  enable = 0;
   $display ("%g, Deasserting Enable", $time);
    #1  $display ("%g, Terminating Simulator", $time);
    #1  $finish;
 end

 always begin
    #5  clk_reg =  ! clk_reg;
 end

 assign clk = clk_reg;

 initial begin
   $counter_monitor (counter_tb.clk, counter_tb.reset, 
     counter_tb.enable, counter_tb.count);
 end

 counter U(
 .clk (clk),
 .reset (reset),
 .enable (enable),
 .count (count)
 );

 endmodule
// counter.v
module counter(
input  wire                      clk,
input  wire                      reset,
input  wire                      enable,
output reg [3:0]                 count 
);
always@(posedge clk or negedge reset) begin
    if(~reset)
         count <= 4'b0000;
    else if(enable)
         count <= count + 1'b1;
end
endmodule

VCS Simulation
根据使用的仿真工具,编译和运行阶段各不相同。 在VCS运行上面的代码时输入:

vcs -full64 -R -P pli_counter.tab pli_counter_tb.v counter.v pli_full_example.c -CFLAGS "-g -I$VCS_HOME/`vcs -platform`/lib" +acc+3

您将获得以下输出:

0 , Asserting reset
 Clock changed state
 Clock changed state
 Clock changed state
 20, Asserting Enable
 Clock changed state
 Clock changed state
 Clock changed state
 Clock changed state
 Clock changed state
 Clock changed state
 Clock changed state
 Clock changed state
 Clock changed state
 Clock changed state
 Clock changed state
 Clock changed state
 Clock changed state
 85, Deasserting Enable
 Clock changed state
 86, Terminating Simulator

完整C代码

所以现在我们看到当时钟发生变化时我们的函数被调用,我们可以编写计数器代码。 但是等等,有一个问题:每次计数器函数退出时,局部变量都会丢失它的值。 有几种方法可以保存变量状态。
- 将计数器变量声明为全局变量
- 使用tf_setworkarea()和tf_getworkarea()程序来存储和恢复局部变量的值。

由于我们只有一个变量,我们可以使用第一个解决方案。 即声明计数为全局变量。

要为计数器写入一个等效的模型,DUT需要时钟,复位,使能信号输入,代码检查器需要DUT计数的输出。需要使用PLI程序— acc_fetch_value(handle,”format”)—读取Verilog代码中的值。

但是返回的值是一个字符串,因此如果使用此程序读取多位向量信号,则需要将其转换为整数。 pli_conv是执行此转换的函数。 当DUT和TB计数值不匹配或换句话说,当模拟不匹配发生时,程序tf_dofinish()用于终止仿真。

#include "acc_user.h"

typedef char * string;
handle clk ;
handle reset ;
handle enable ;
handle dut_count ;
int count ;
int sim_time;
string high = "1";
void counter ();
int pli_conv (string in_string, int no_bits);

void counter_monitor() {
  acc_initialize();
  clk       = acc_handle_tfarg(1);
  reset     = acc_handle_tfarg(2);
  enable    = acc_handle_tfarg(3);
  dut_count = acc_handle_tfarg(4);
  acc_vcl_add(clk,counter,null,vcl_verilog_logic);
  acc_close();
}

void counter () {
  p_acc_value value;
  sim_time = tf_gettime();
  string i_reset = acc_fetch_value(reset,"%b",value);
  string i_enable = acc_fetch_value(enable,"%b",value);
  string i_count = acc_fetch_value(dut_count,"%b",value);
  string i_clk = acc_fetch_value(clk, "%b",value);
  int size_in_bits= acc_fetch_size (dut_count);
  int tb_count = 0;
  // Counter function goes here
  if (*i_reset == *high) {
    count = 0;
    io_printf("%d, dut_info : Counter is reset\n", sim_time);
  }
  else if ((*i_enable == *high) && (*i_clk == *high)) {
    if ( count == 15 ) {
      count = 0;
    } else {
      count = count + 1;
    }
  }
  // Counter Checker function goes checker logic goes here
  if ((*i_clk  != *high) && (*i_reset  != *high)) {
    tb_count = pli_conv(i_count,size_in_bits);
    if (tb_count  != count) {
        io_printf("%d, dut_error : Expect value %d, Got value %d\n", 
            sim_time, count, tb_count);
    tf_dofinish();
    } else {
        io_printf("%d, dut_info  : Expect value %d, Got value %d\n", 
            sim_time, count, tb_count);
    }
  }
}

// Multi-bit vector to integer conversion.
int pli_conv (string in_string, int no_bits) {
  int conv = 0;
  int i = 0;
  int j = 0;
  int bin = 0;
  for ( i = no_bits-1; i >= 0; i = i - 1) {
    if (*(in_string + i) == 49) {
      bin = 1;
    } else if (*(in_string + i) == 120) {
      io_printf ("%d, Warning : X detected\n", sim_time);
      bin = 0;
    } else if (*(in_string + i) == 122) {
      io_printf ("%d, Warning : Z detected\n", sim_time);
      bin = 0;
    } else {
      bin = 0;
    }
    conv = conv + (1 << j)*bin;
    j ++;
  } 
  return conv;
}

可以使用下面段落中所述的仿真器来编译和模拟上述代码。
- VCS
- Modelsim

VCS

使用VCS模拟器,您需要创建一个tab文件。 对于我们的示例,tab文件如下所示:

$counter_monitor call=counter_monitor acc=rw:*

这里$counter_monitor是用于Verilog代码的用户定义函数的名称,call = counter_monitor是在Verilog中调用$counter_monitor时调用的C函数。 acc = rw:告诉我们正在使用具有读和写访问模拟器内部数据的访问例程。 :表示这应适用于设计中的所有模块。

VCS PLI TAB FILE

PLI tab文件(也称为pli.tab文件)用于:
- 将用户定义的系统任务和系统功能与PLI应用程序中的功能相关联。 这使VCS MX能够在编译或执行系统任务或功能时调用这些功能。
- 限制由调试功能调用的PLI 1.0或PLI 2.0功能的范围和操作。

Syntax
$name PLI_specifications [access_capabilities]
其中,
$name: 指定用户自定义的函数或任务
PLI_specifications: 指定一个或多个规范,如C函数的名称(强制性),返回值的大小(仅限用户定义的系统函数),等等。
access_capabilities: 指定PLI应用程序中定义的功能的访问功能。 使用它来控制PLI 1.0或PLI 2.0功能访问设计层次结构的能力。

PLI_specifications

call=function
&#8195 指定在PLI应用程序中定义的函数的名称。这是强制的。
check=function
&#8195指定check函数的名称
misc=function 指
&#8195定misc函数的名称
data=integer
&#8195指定作为调用,检查和misc函数的第一个参数传递的值。 默认值为0
….(其他,见VCSMX UG)

access_capabilities

你可以在PLI tab文件中指定访问功能,原因如下:
- PLI函数与用户定义的系统任务或系统函数相关联。 为此,请在用户定义的系统任务或系统函数及其PLI规范的名称之后,在PLI tab文件中的一行上指定访问功能
- 对于VCS MX可以使用的调试特性。 为此,只需在PLI tab文件的一行中指定访问功能,而不需要相关的用户定义系统任务或系统函数

指定访问功能的格式如下:
acc=|+=|-=|:=capabilities:module_names[+]|%CELL|%TASK|*
其中,
- acc——一行开始位置指定访问功能的关键字
- =|+=|-=|:——添加、删除、更改指定功能的操作
- capabilities:
&#8195 逗号分隔的访问功能列表。 您可以在PLI规范中为功能指定的功能如下:
- r — read
- rw — read_write
- wn — write value to nets
- cbk — to be called when named objects change value
- …..(见 VCSMX UG)
- module_names —— 模块标识符(或名称)的逗号分隔列表
- + — 指定添加,删除或更改访问功能,不仅是指定模块的实例,还包括实例在指定模块的实例下分层实例
- %CELL%
&#8195 启用,禁用或更改PLI功能在“celldefine compiler指令”下编译的模块定义的所有实例以及Verilog库目录和库文件中的所有模块定义(如指定)中使用访问功能的能力 使用-y和-v分析选项)
- %TASK%
&#8195 启用,禁用或更改(取决于运营商)PLI功能在包含与PLI功能相关联的用户定义的系统任务或系统功能的模块定义的所有实例中使用访问功能的能力。
- *
&#8195 启用,禁用或更改(取决于操作员)PLI功能在整个设计中使用访问功能的能力。 使用通配符可能会严重阻碍VCS MX的性能。

VCS中用于编译代码的命令行选项有:

vcs -R -P pli_counter.tab pli_counter_tb.v counter.v pli_full_example.c -CFLAGS "-g -I$VCS_HOME/`vcs -platform`/lib" +acc+3

finial ouput:

 0 , Asserting reset
 10, dut_info : Counter is reset
 15, dut_info : Counter is reset
 20, Asserting Enable
 20, dut_info  : Expect value 0, Got value 0
 30, dut_info  : Expect value 0, Got value 0
 40, dut_info  : Expect value 1, Got value 1
 50, dut_info  : Expect value 2, Got value 2
 60, dut_info  : Expect value 3, Got value 3
 70, dut_info  : Expect value 4, Got value 4
 80, dut_info  : Expect value 5, Got value 5
 85, Deasserting Enable
 86, Terminating Simulator
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值