碎碎思 · 2021年10月21日

【例说】Verilog HDL 编译器指令,你见过几个?

Verilog HDL 编译器指令

复杂一点的系统在进行设计或者验证时,都会用到一些编译器指令,那么什么是编译器指令?
image.png

Verilog HDL编译器指令由重音符(')开始。在Verilog 语言编译时,特定的编译器指令在整个编译过程中有效(编译过程可跨越多个文件),直到遇到其它的不同编译程序指令。不完整的标准编译器指令如下:
image.png
下面分解一下,每个指令单独说明一下:

’define和’undef

1.’define指令

’define指令用于文本替换,它很像C语言中#define指令。它生成一个文本宏。该指令既可以在模块内部定义,也可以在模块之外定义。一旦编译了’define指令,它在整个编译过程中都有效。

如果已经定义了一个文本宏,那么在它的宏名之前加上重音符号(’)就可以在源程序中'引用该文本宏。

在编译器编译时,将会自动用相应的文本块代替字符串'macro\_name。将Verilog HDL中的所有编译指令都看作预定义的宏名,将一个编译指令重新定义为一个宏名是非法的。

一个文本宏定义可以带有一个参数。这样,就允许为每一个单独的应用定制文本宏。

文本宏定义的语法格式如下:

'define <text_macro_name> <macro_text>

其中: (1)<text_macro_name>为文本的宏名字,其语法格式为

text_macro_identifier[<list_of_formal_arguments>]

①text_macro_identifier为宏标识符,要求简单标识符。

②<list_of_formal_arguments>为形参列表。一旦定义一个宏名,就可以在源程序的任何地方使用它,而没有范围限制。

(2)<macro_text>为宏文本,可以是与宏名同行的任意指定文本。

①如果指定的文本超过一行,那么新的一行需要用反斜杠()作为起始。这样,反斜杠后面的文本也将作为宏文本的一部分,参与宏替换。反斜杠本身并不参与宏替换,编译时将忽略它。

②如果宏文本包含了一个单行注释语句(以“//”开始的注释语句),则该语句不属于替换文本,编译时不参与替换。

③宏文本可以空白。

[例] ’define指令Verilog HDL化述的例子1。

'define wordsize 8

reg[1:'wordsize] data;
//define a nand with variable delay

'define var_nand(dly) nand #dly
'var_nand(2) gl21 (q21, nl0, nil);
'var_nand(5) gl22(q22, nl0, nil);

[例] ’define指令Verilog HDL非法描述的例子2

'define first_half "start of string
$ display( ’first_half end of string");

[例] 'define 指令 Verilog HDL 非法描述的例子 3。

'define max(a,b)((a)>(b)?(a):(b))
n = 'max(p + q, r + s) ;

将要扩展为

n = ((p + q ) >(r + s))?(p + q ) :(r + s);

2.'undef指令

'undef指令用于取消前面定义的宏。如果先前并没有使用指令’define进行宏定义,那么使用’undef指令将会导致一个警告。

’undef指令的语法格式如下:

'undef text_macro_identifier

一个取消的宏没有值, 就如同没有被定义一样。

'define SIZE 8
'define xor_b(x,y)(x &!y)|(!x & y)
//These text macros can be used as follow:
reg ['SIZE - 1 : 0] data out;
c = xor_b(a, b);
'undef SIZE

’celldefine和’endcelldefine

这两个指令用于将模块标记为单元模块,它们表示包含模块定义。某些PLI使用单元模块用于这些应用,如计算延迟。

该命令可以出现在源代码描述中的任何地方。但是,推荐将其放在模块定义的外部。

[例] ’celldefine指令Verilog HDL描述的例子。

'celldefine
module my_and(y, a, b);
output y;
input a, b;

assign y = a & b;

endmodule
'endcelldefine

’default_nettype

该指令用于为隐含网络指定网络类型,也就是为那些没有被说明的连线定义网络类型。它只可以出现在模块声明的外部,允许多个’default\_netype指令。

如果没有出现’default_netype指令,或者如果指定了’resetall指令,则隐含的网络类型是wire。当default_netype设置为none时,需要明确地声明所有网络;如果没有明确地声明网络,则产生错误。

’default_netype指令格式为:

'default_nettype default_nettype_value

其中default_nettype_value的值可以是wire、tri、tri0、tri1、wand、triand、wor、trior、trireg、uwire和none。

'ifdef、 'else、 ’elsif、 ’endif 和’ifndef

'ifdef编译器命令

条件编译:

显而易见,即只有在条件满足的时候才对这部分代码进行编译,也就是对一部分内容指定了编译的条件:

当满足条件时对一组语句进行编译,
当条件不满足时则对另外一组语句进行编译。

用途:

1、选择一个模板的不同代表部分。
2、选择不同的时序或结构信息。
3、对不同的EDA工具,选择不同的激励。(如:Verilog代码中的一部分可能因编译环境不同而不同,为避免在不同环境需要替换不同版本的Verilog 设计,条件编译就是一个很好的解决方案)

用法

'ifdef 宏名(标识符)
    程序段1...
'else
    程序段2...
'endif

当宏名被定义过了,就编译程序段1;反之,当宏名未被定义过,就编译程序段2;

其中,else部分可以省略。即:当宏名被定义过了,就编译程序段1;反之,不编译程序段1;

[例] ’ifdef 指令 Verilog HDL 描述的例子。

module and_op (a, b, c);
output a;
input b, c;
'ifdef behavioral
 wire a = b & c;
'else
 and a1 (a,b,c);
'endif

endmodule

‘ifndef编译器命令

额外的,还有‘ifndef语句,与’ifdef功能相反:

即当宏名没被定义过,就编译程序段1;反之,当宏名未被定义过了,就编译程序段2;

'ifndef 宏名(标识符)
    程序段1...
'else
    程序段2...
'endif

[例] ’ifndef 指令 Verilog HDL 描述的例子。

module test;
'ifdef first_block
 'ifndef second_nest
  initial $ display("first_block_is_defined");
 'else
  initial $ display("first_block and second_nest defined");
 'endif
'elsif second_block 
 initial $ display( "second_block defined, first_block is not”);
'else
 'ifndef last_result
  initial $ display("first_block, second_block,"
  ”last_result not defined.");
'elsif real_last
 initial $ display("first_block, second_block not defined,"
  " last_result and real last defined.");
'else
 initial $ display("Only last result defined!");
 'endif
'endif

endmodule

这里还有一个‘elsif指令,简单说明一下。

'ifndef test_macro_identifier
ifndef_group_of_1ines
{ 'elsif text_macro_identifier elsif_group_of_lines }
[ 'else else_group_of_lines ]
'endif

①当遇到’ifndef时,测试’ifdef文本宏标识符,查看在Verilog HDL源文件描述中是否使用'define作为一个文本宏名字;②如果’ifndef没有定义文本宏标识符,则对’ifndef所包含的行作为描述的一部分进行编译,如果还有’else或者’dsif编译器指令,则忽略这些编译器指令和相关的行组;③如果定义’ifiidef文本宏标识符,则忽略’ifndef所包含的行;④如果有’elsif编译器指令,测试'elsif文本宏标识符,查看在Verilog HDL源文件描述中,是否使用'define作为一个文本宏名字;⑤如果’elsdef定义文本宏标识符,则对’elsdef所包含的行作为描述的一部分进行编译,如果还有’else或者’elsif编译器指令,则忽略这些编译器指令和相关的行组;⑥如果没有定义第一个'elsif文本宏标识符,则忽略第一个’elsif所包含的行;⑦如果有多个’elsif编译器命令,将按照它们在Verilog HDL源文件中的描述顺序和评估第一个’elsif编译器指令的方法,对这些指令进行评估;⑧如果有一个’else编译器命令,则将’else所包含的行作为描述的一部分进行编译。

’include

在编译期间,’include编译器指令用于嵌入另一个文件的内容。既可以用相对路径名定义文件,也可以用全路径名定义文件。其语法格式为:

'include "filename"

使用’inchide编译器指令的优势主要体现在以下几方面:

(1)提供了一个配置管理不可分割的一部分;
(2)改善了VerilogHDL源文件描述的组织结构;
(3)便于维护Verilog HDL源文件描述。

[例 ]’include指令Verilog HDL描述的例子。

'include "parts/count. v"
'include "fileB"
'include "fileB" //包含 fileB

'resetall

该编译器遇到’resetall指令时,会将所有的编译指令重新设置为默认值。推荐在源文件的开始放置’resetall.将'resetall命令放置在模块内或者UDP声明中是非法的。其语法格式为

'resetall

’line

对于Verilog工具来说,跟踪Verilog HDL源文件的名字和文件的行的行号是非常重要的,这些信息可以用于调试错误消息或者源代码,Verilog PL1访问可以它。

然而,在很多情况下,Verilog源文件由其他工具进行了预处理。由于预处理工具可能在Verilog HDL源文件中添加了额外的行,或者将多个源代码行合并为一个行,或者并置多个源文件,等等,可能会丢失原始的源文件和行信息。

'line编译器命令可以用于指定的原始源代码的行号和文件名。如果其他过程修改了源文件,这允许定位原始的文件。当指定了新行的行号和文件名时,编译器就可以正确地定位原始的源文件位置。然而,这要求相应的工具不产生’line命令。

其语法格式为

'line number "filename" level

其中,number是一个正整数,用于指定跟随文本行的新行行号,filename是一个字符串常数,将其看作文件的新名字,文件名可以是全路径名字或者相对路径名字;level为该参数的值,可以是0、1或者2:①当为1的时候,输入一个include行后的下面一行是第一行;②当为2的时候,退出一个inlcude行后的下面一行是第一行;③当为0的时候,指示任何其他行。

[例] 'line 指令 Verilog HDL 描述的例子。

'line3 "orig.v" 2

//该行是 orig.v 存在 include 文件后的第 3 行。

’timescale

在Verilog HDL模型中,所有的时延都用单位时间表述。可使用'timescale编译器指令将时间单位与实际时间相关联,该指令用于定义时延的单位和时延精度。

作用:

timescale用于定义延时的单位和延时的精度,如timescale  1ns/100ps那么时间单位就是1ns,精度就是100ps。

时间单位,表示了仿真时测量的单位,比如延时1,1ns;精度则表示仿真器只识别的范围,比如精度是100ps,那么如果你1.3ns,编译器是识别,但是如果写1.32,那么由于精度达不到那么细,所以0.02被四舍五入掉。

\`timescale影响着全部模块,直到遇到另外的timescale。

’timescale编译器指令格式为:

’timescale time_unit/time_precision

其中,time_unit指定用于时间和延迟测量的单位,可选的值为1、10或100;time_precision用于仿真前,确定四舍五入延迟值。时间分辨率子,可选的单位为s \ms\us\ns\ps或fs。

[例] 'timescale 指令 Verilog HDL 描述的例子。

'timescale 10 ns / 1 ns
module test;
reg set;
parameter d = 1.55;
initial begin
# d set = 0;
# d set = 1;
end
endmodule

根据时间精度, 参数 d 的值从 1.55 四舍五入到 1.6。模块的时间单位是 10ns 精度是1 ns。因此, 参数 d 的延迟从 1.6 标定到 16。

’unconnected_drive和'nounconnected_drive

当一个模块所有未连接的端口出现在'unconnected_drive和’nounconnecteddrive指令之间时,将这些未连接的端口上拉或者下拉,而不是按通常的默认值处理。

指令’unconnected_drive使用pull1/pull0参数中的一个:当指定pull时,所有未连接的端口自动上拉;当指定pill0时,所有未连接的端口自动下拉。

建议成对使用’unconnected_drive和'nounconnected_drive指令,但不是强制要求。这些指令在模块外部成对指定。

'resetall指令包括'nounconnected_drive指令的效果。

[例]nounconnected_drive/ 'unconnected drive 指令 Verilog HDL 描述的例子。

'unconnected_drive pull1
module my_and(y, a, b);
output y;
input a, b;
assign y = a & b;
endmodule

module test;
reg b;
wire y;
my_and ul(y, ,b);
endmodule

'nounconnected_drive

'pragma

’pragma指令是一个结构化的说明,它用于改变对Verilog HDL源文件的理解。由这个指令所引入的说明称为编译指示。编译指示不同于Verilog HDL标准所指定的结果,它为指定实现的结果。其语法格式为

’pragma pragma_name [ pragma_expression { , pragma_expression } ]

其中 ,pragma_name 为编译指示的名字, 可以是 $ 开头的系统标识符或者一般标识符; pragma_expression 为编译指示表达式。

注:reset和resetall编译指示将恢复默认值和pragma_keywords所影响的状态。

'begin_keywords和’end_keyword

'begin_keywords和'end_keyword指令用于指定在一个源代码块中,基于不同版本的IEEE_Stdl364标准,确定用于关键字的保留字。该对指令只指定那些作为保留关键字的标识符。只能在设计元素(模块、原语和配置)外指定该关键字,并且需要成对使用。其语法格式为:

'begin_keywords "version_specifier"
    ...
'end_keyword

其中,version_specifier为可选的参数,包括1364-1995、1364-2001、1364-2001-noconfig和1364-2005。

[例] ’begin_keywords 和 'end_keyword 指令 Verilog HDL 描述的例子。

'begin_keywords "1364- 2001" //使 用 IEEE Std 1364- 2001 Verilog 关 键 字
'module  m2(...); 
reg[63:0] logic;           //logic 不 是 1364 - 2001 的 关 键 字

...

endmodule

'end_keywords

(补充一)Verilog编译器指示语句

设计者在写设计代码时,有时可能针对仿真写一些语句,这些语句可能是不为DC所接受,也不希望DC接受;设计者如果不对这些语句进行特殊说明,DC读入设计代码时就会产生语法错误。另一种情况是,设计者在写设计代码,有些设计代码是为专有的对象写的(如公司内部),这些专有的设计代码可能不希望被综合。Synopsys提供了引导语句,设计者可以使用这些引导语句控制DC综合的对象

可以利用HDL描述中的一些特定的注释语句来控制综合工具的工作,从而弥补仿真环境和综合环境之间的差异,这些注释语句称为编译器指示语句。

translate_off/ translate_on

这组语句用来指示DC停止翻译 “//synopsys.。.translate_off”之后的Verilog描述,直至出现 “//synopsys translate_on”。当Verilog代码钟含有供仿真用的不可综合语句时,这项功能能使代码方便地在仿真工具与综合工具之间移植。

例1(translate_off/ translate_on指示语句的使用):

//synopsys translate_off

//synopsys translate_on

parallel_case/ full_case

DC可能使用带优先级的结构来综合Verilog的case语句,为避免这种情况,可以使用“//synopsys.。.parallel_case”指示DC将case语句综合为并行的多路选择器结构。

(parallel_case指示语句的使用):

always @ (state)

case (state) //synopsys parallel_case

2’b00:new_state = 2’b01;

2’b01:new_state = 2’b10;

2’b10:new_state = 2’b00;

default:new_state = 2’b00;

endcase

另外,Verilog允许case语句不覆盖所有可能情况,当这样的代码由DC综合时将产生锁存器。为避免这种情况,可以使用“//synopsys full_case”指示DC所有可能已完全覆盖。

例2 (full_case指示语句的使用):

always @ (sel or a1 or a2)

case (sel) //synopsys full_case

2’b00:z = a1;

2’b01:z = a2;

2’b10:z = a1 & a2;

endcase

(补充二)Verilog PL1是什么?

上面有提到过PLI接口,这里简单介绍下,因为用的比较少,所以就一笔带过。

编程语言接口(Program Language Interface,PLI)提供了通过C语言函数对Verilog数据结构进行存储和读取操作的方法。

PLI接口主要提供以下三种功能。

(1)PLI接口允许用户编写自定义的系统任务和系统函数。用户写出相应的PLI程序并连接到仿真器后,就可以在自己写的VerilogHDL程序中使用这些系统任务和系统函数。一旦在仿真过程中调用这些任务或者函数,仿真器就会找到对应的用户所编写的PLI程序并执行,从而实现仿真器的定制。

(2)这个接口还允许用户在自己的PLI程序中与仿真器中例化的VerilogHDL硬件进行交互,如读一个线网络的值、向一排寄存器写值以及设置一个单元的延迟,等等。

对于PLI程序而言,仿真器中的Verilog实例完全透明,用户可以对这些硬件做任何操作(当然,不能修改硬件结构)。有了这个功能,用户就可以在自定义的任务/函数中对硬件执行某些用VerilogHDL语言难以完成的操作。

(3)某些特定的操作需要对仿真过程中一些信号的变化做出响应,虽然可以用always过程语句来监控少量信号的变化,但如果需要监测大量信号,这种机制并不现实。

PLI接口提供了一种函数回调机制解决这个问题。用户可以将某个线网络/寄存器等信号挂上一个PLI程序中的C函数。每当该信号变化时,调用这个C函数,从而很方便地监测信号。

除了上面所说的这些机制外,PLI还能让用户控制仿真的过程,例如暂停、退出以及向日志文件里写信息等,还可以获取仿真过程的数据,如当前仿真时间等。在实际的PLI程序中,同样不可缺少这些功能。

参考资料
1、http://www.elecfans.com/d/651...
2、EDA原理及Verilog实现
3、https://www.cnblogs.com/IClea...

原文:FPGA 的逻辑
作者:碎碎思

相关文章推荐

推荐阅读
关注数
10597
内容数
559
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息