设计师使用HDL已超过十年,用它们取代基于原理图的设计方法并传达设计理念。 Verilog和VHDL是电子设计中使用最广泛的两种HDL。 Verilog拥有约35,000名活跃的设计师,他们使用Cadence的Verilog软件套件完成了超过50,000个设计。
即使Verilog成功,许多经验丰富的Verilog用户仍然认为其编程语言界面( PLI)作为“软件任务”。一步一步的方法可以帮助您在编写PLI函数时“打破僵局”。通过学习PLI设计的基本知识而不会被太多细节困扰,您将获得可以立即使用的PLI基础知识。
为什么要使用PLI?
PLI为Verilog提供了应用程序接口(API)。本质上,PLI是一种从Verilog代码调用C函数的机制。人们通常会调用在Verilog中调用PLI例程的构造,如果它是模拟器的一部分,则调用“系统任务”或“系统函数”,如果用户编写它,则调用“用户定义的任务”或“用户定义的函数”。由于PLI的基本机制在两种情况下都保持不变,因此本文使用术语“系统调用”来表示这两种结构。大多数Verilog模拟器包含的常见系统调用示例是$ display,$ monitor和$ finish。
您使用PLI主要用于执行使用Verilog语法无法执行的任务。例如,IEEE Standard1364-1995 Verilog有一个用于执行文件写入的预定义构造($ fwrite,这是使用PLI编写的另一个内置系统调用),但它没有用于直接从a读取寄存器值的构造。文件(参考文献2)。更常见的任务是PLI是实现预期结果的唯一方法,包括编写功能模型,计算延迟和获取设计信息。 (例如,没有Verilog构造在设计层次结构中给出当前模块的父实例名称。)
为了说明创建PLI例程的基本步骤,请考虑清单1中的问题。问题比使用PLI解决的现实问题简单得多,但它显示了许多用于构建PLI例程的基本步骤。当您在列表中运行Verilog时,它应该在时间100打印寄存器的值为10,在时间300打印3.您可以考虑创建一个PLI例程作为两个步骤:首先,编写PLI例程在C;然后,编译并将此例程链接到模拟器的二进制代码。
编写PLI例程
PLI例程与模拟器接口的方式因模拟器而异,尽管主要功能保持不变。本文讨论两种最流行的商业模拟器的接口机制,Cadence的Verilog-XL(参考文献3)和Synopsys的VCS(参考文献4)。虽然其他商业模拟器支持PLI,但它们的接口机制与这两者没有显着差异。多年来,Verilog PLI已发展为PLI 1.0和Verilog程序接口(VPI)(参见侧栏“Verilog PLI的简史”)。本文仅涉及PLI 1.0。尽管接口部件和版本存在差异,但您可以将PLI例程的创建分解为四个主要步骤。
步骤1:包含头文件
按照惯例,C程序在文件veriuser.c中实现PLI例程。虽然您可以更改此名称,但在Verilog-XL环境中生成编译脚本时,vconfig工具会采用此默认名称。现在,假设您将PLI例程保留在文件veriuser.c中。
在Verilog-XL环境中,文件veriuser.c必须以以下行开头:
在VCS环境中,文件必须以:
开头
这些头文件包含程序将使用的Verilog PLI的最基本数据结构。
步骤2:声明函数原型和变量
PLI例程由几个函数组成。正如您对普通C程序所做的那样,您应该在函数定义之前放置函数的原型声明。对于这种情况,函数显示为:
在上面的函数中,int原型声明意味着这些函数在它们结束时返回一个整数执行。如果没有错误,则正常返回值为0.但是,如果函数位于单独的文件中,则应将它们声明为外部函数:
与任何其他C程序一样,典型的PLI例程可能需要一些其他管家变量。
步骤3:设置基本数据结构
您必须定义一个数字PLI程序中的数据结构。 Verilog模拟器通过这些变量与C代码通信。 Open Verilog International(OVI)(www.ovi.org/pubs.html)是一个标准化Verilog的组织,它只推荐一个强制数据结构veriusertfs。但是,这些数据结构的确切数量和语法因模拟器而异。例如,Verilog-XL需要四个这样的数据结构及其功能,以便任何PLI例程工作; VCS不需要它们,而是使用单独的输入文件。
Verilog-XL的主要接口数据结构是一个结构数组或一个名为veriusertfs的表。 Verilog-XL使用此表来确定与此PLI例程对应的系统调用关联的属性。模拟器无法识别除veriusertfs之外的任何名称。 veriusertfs的每个元素都有一个独特的函数,你需要所有这些函数来实现正确编写PLI例程的总体目标。 veriusertfs中的行数与用户定义的系统调用数加上最后一个条目的行数相同,这是强制性的0.在这种情况下,veriusertfs数组应如下所示:
第一个条目usertask表示系统调用没有返回任何内容。它等同于Pascal中的过程或函数返回C中的void。
在前面的数据结构中,my_ checktf和my_calltf是用于实现系统调用$ print_reg的两个函数的名称。这些名称是任意的,您可以用其他名称替换它们。函数my_checktf通常称为checktf例程。它检查传递的参数的有效性。同样,my_calltf(通常称为calltf例程)执行系统调用的主要任务。这些名称在veriusertfs中的位置非常重要。例如,如果要将my_checkt用作任何其他名称作为检查函数,则它必须是第三个元素。函数veriusertfs为您在此例程中不会使用的一些其他用户定义函数提供选项。零替换任何您不使用的函数。因此,条目中的第二,第四和第六个元素是零。表1总结了veriusertfs行中每个条目的目标。如果有其他系统调用,则需要在veriusertfs中为每个系统调用单独的条目。
Verilog-XL还需要以下变量或函数:
第一个变量veriuser_version_str是一个字符串,表示应用程序的用户定义 - 版本信息。 bool(布尔)变量是一个整数子类型,允许值为0和1.在大多数情况下,您可以使用Cadence为这些变量或函数提供的默认值。
在VCS中,而不是表,您在单独的文件中使用等效信息,通常称为pli.tab。该名称也是用户定义的。在使用$ print_reg的当前示例中,此文件的内容为:
步骤4:组成函数
原型声明到位后,您就可以编写两个函数my_checktf()和my_calltf(),它们构成了PLI应用程序的主体。
如前所述,checktf例程检查传递参数的有效性。最好检查参数总数是否与预期相同,以及是否需要每个参数。例如,在这种情况下,您希望程序只传递一个类型为register的参数。您可以在函数my_checktf()中执行此任务(清单2)。
以tf_开头的函数是库函数,通常称为实用程序例程。库函数在前一个函数中使用,它们的用法是tf_nump(),它确定传递的参数数量,以及tf_typep(),它根据Verilog代码中系统调用中的位置确定参数类型。在这种情况下,系统调用是$ print_reg。
因此,tf_typep(1)给出第一个参数的类型,tf_typep(2)给出第二个参数的类型,依此类推。如果参数不存在,则tf_typep()返回错误。 (在这种情况下,tf_typep(2)不存在。)在当前示例中,您希望程序将寄存器值作为参数传递。因此,类型应该是tf_readwrite。如果导线是预期参数,则类型应为tf_readonly。为了便于错误条件检查,最好首先检查参数的数量,然后检查它们的类型。 tf_error()函数输出错误消息并通知模拟器增加其错误计数。这些库函数和常量是您在步骤1中包含在文件顶部的头文件的一部分。
calltf函数是PLI例程的核心。它通常包含PLI例程的主体。在这种情况下,它应该读取寄存器的值,然后打印该值。以下代码显示了如何完成此任务:
在上面的代码中,io_printf()与C中的printf()执行相同的工作,在标准输出中打印值。此外,该函数在Verilog日志文件中打印相同的信息。函数tf_getp()获取寄存器的整数值。函数tf_gettime()返回当前模拟时间,不需要输入参数。 Calltf是PLI例程中最复杂的函数。它经常运行几百行。
现在你已经创建了两个主要功能,你需要把它们放在一个地方。清单3显示了如何在Verilog-XL环境中实现$ print_reg。
接下来的任务是让Verilog模拟器了解新系统调用的存在。要完成此任务,您必须编译PLI例程,然后将其链接到模拟器的二进制文件。虽然您可以通过运行C编译器并合并目标文件来手动将PLI代码与Verilog二进制文件集成,但使用脚本更方便。在Verilog-XL中,一个名为vconfig的程序会生成此脚本。此脚本的默认名称是cr_vlog。在生成此脚本时,程序vconfig会询问您喜欢的已编译Verilog的名称。它还询问是否包含来自标准供应商的模型库,它们是PLI代码。对于大多数这些问题,如果您在不输入任何内容的情况下按回车键输入的默认答案就足够了,除非您有自定义环境。最后,vconfig会询问您的veriuser.c文件的路径。一旦生成脚本cr_vlog,您只需要运行脚本来生成可以执行新系统调用的自定义Verilog模拟器。
使用此PLI例程编译的Verilog示例运行生成输出Verilog-XL模拟器(清单4)。
修改寄存器的值
使用PLI是读取设计信息并在设计数据库中修改此信息。以下示例显示了如何进行此修改。创建系统调用$ invert,以打印寄存器的值(如上例所示),按位反转内容,并打印此更新值。存在许多方法来反转二进制数的值。通过使用简单的算法进行反演,如下面的列表所示,可以帮助您更深入地了解PLI机制和库函数:
将寄存器的内容读取为string;
将字符串中的所有字符串转换为twos,将所有零转换为1,将所有二进制转换为零,将所有Z转换为X,并使X保持不变;和
将修改后的字符串放回寄存器。
第二步将所有1转换为零和0。请注意,在这种情况下,checktf函数与前一个函数没有区别,因为在这两种情况下输入参数的数量和类型都是相同的。
创建PLI例程
列表图5显示了VCS环境中$ invert例程的实现。该程序使用以下库函数:
tf_strgetp()以字符串形式返回寄存器的内容。只需liketf_getp(),它将寄存器的内容读作十进制。在清单5中,tf_strgetp(1,b)以二进制格式读取第一个参数的内容。 (h或o代替b,分别以十六进制或八进制格式读取)。例程然后将内容复制到字符串val_string。
tf_strdelputp()将值从字符串值规范写入参数。该函数有许多参数,包括参数索引号(系统调用中参数的相对位置,最左边的参数从1开始);参数包含其值的字符串的大小(在这种情况下,它应与寄存器的大小相同);编码基数;实际的字符串;和另外两个与延迟相关的参数,你可以通过为它们传递零来忽略它们。虽然在当前示例中没有显示,但是tf_strdelputp()的更简单的十进制对应是tf_putp()。
重要的是要记住程序只能更改或覆盖来自PLI例程的某些对象的值。任何尝试更改无法放在Verilog中的过程赋值左侧的变量(例如连线)都会导致错误。通常,您可以修改tf_readwrite类型的变量的内容。 checktf函数my_checktf()检查此功能。
清单5使用的另一个用户定义函数是move(),它有三个参数。在第三个参数中,第二个参数替换第一个参数的每个匹配项。在当前情况下,对move()的一系列调用将零更改为1和1更改为零。但是,一旦程序将零转换为1,您必须将字符串中的转换元素与原始元素区分开来。为了做出这种区分,程序首先将所有初始的更改为两个。二是二进制逻辑的无效值,但您可以在此示例的中间步骤中使用它。最后,程序将所有两个转换为零。程序通过使用tf_strdelputp()函数将反转的值放回寄存器来完成其任务。
清单6显示了一个包含调用$ invert的Verilog程序示例。在VCS环境中,文件列出与PLI例程关联的功能。虽然这个文件可以有任何名称,但通常称之为pli.tab。该文件等同于Verilog-XL中的veriusertfs []结构。当前示例中此文件的内容为:
通过将此程序保存在test.v文件中,可以生成可执行二进制文件pli_invert命令:
执行pli_invert,你得到:
你现在了解PLI例程的基本结构以及将其链接到构建Verilog自定义版本的机制。您还可以通过PLI例程读取,转换和修改Verilog中设计数据库的基本组件的值,并读取例程的模拟时间。这些任务是PLI例程执行的基本任务,也是最常用的任务。考虑到PLI提供的所有信息,这些信息只是冰山一角。您还可以使用PLI访问寄存器内容以外的设计信息。 (有关Verilog和C建模的更多信息,请参阅参考文献5和6。)
全部0条评论
快来发表一下你的评论吧 !