Verilog和SystemVerilog中的变量和线网
在SystemVerilog扩展Verilog之后,很多人在使用SystemVerilog时经常使用到logic,并且认为logic和以前Verilog中的reg一样,是一种变量,这种说法不全面,不能认为是正确的。下面我们通过示例说明logic到底是不是一种变量名?并且以此示例了SystemVerilog和Verilog中变量和线网的发展变化。
在使用Verilog和SystemVerilog进行设计和验证环境编写的时候,经常会遇到两种数据对象:变量和线网。两者的主要区别在于使用过程中对其采用的赋值方式和其是否具有保持数值的功能,因此两者模拟的硬件结构也是完全不同的,变量类似于具有一定保存数值的寄存器,而线网则类似于电路连线,其本身并不能保存数值。线网常用于连续赋值语句,变量常用于过程性赋值语句中(两种赋值语句方式可参考《Verilog系列:【32】Verilog中的几种赋值语句》)。在Verilog和SystemVerilog中变量和线网采用的数值系统目前主要有两种,如下表所示:
数据类型(data type) | 数据集合 | 使用语句 |
2-state(2值类型) | 0 - 逻辑0或者假 1 - 逻辑1或者真 | SystemVerilog |
4-state(4值类型) | 0 - 逻辑0或者假 1 - 逻辑1或者真 Z - 高阻态 X - 不定态 | SystemVerilog、Verilog |
数据对象 | 类型名 | 数值系统 |
变量 | reg | 4-state(0,1,X,Z) |
integer | ||
time | ||
real | ||
realtime | ||
线网 | wire | |
wand | ||
wor | ||
tri | ||
tri0 | ||
tri1 | ||
triand | ||
trior | ||
trireg | ||
supply0 | ||
supply1 |
其实在Verilog中还有一种特殊的数据对象parameter,在Verilog中既不属于变量也不属于线网,是一种常量,parameter在程序运行的过程中是不能像变量和线网那样被改变(但是在编译阶段可以通过参数传递进行修改),并且在设计中parameter、变量和线网不能同名。关于parameter可参考《Verilog系列:【18】参数三姐妹-parameter-local-specparam》
`timescale 1 ns / 1 ps module top_tb; reg sig1,sig2; wire sig3; wire sig4; initial begin #10 sig1 = 1'b0;sig2 = 1'b1; #10 sig1 = 1'b1;sig2 = 1'b0; #10 sig1 = 1'bx;sig2 = 1'bz; #10 sig1 = 1'bz;sig2 = 1'bx; #2 $stop; end assign sig3 = sig2; endmodule // top_tb【仿真结果】
示例中,sig1和sig2声明为reg,在没有对其进行操作时,其默认值为不定态“X”,sig3和sig4声明为wire类型,在没有对其进行操作时,其默认值为高阻态“Z”,但是示例中sig3通过连续赋值语句被信号sig2一直驱动,所以sig3值在实际仿真时与sig2保持一致。这里需要注意的线网类型中的一个特例trireg,trireg的默认值与其他线网不同,其默认值是“X”不是“Z”。
SystemVerilog对Verilog进行了扩展,在数据对象(data object)和数据类型(data type)进行了更为细致的划分。其中数据对象专指变量和线网(此处不讨论parameter),数据类型指定了变量和线网所对应的数值系统,说白了就是变量或者线网可以取值的不同集合。目前SystemVerilog中主要有两种基础的数据类型:4-state(4值数据类型,此与Verilog中的4-state一致)和2-state(2值数据类型,0和1)。也就是说,在SystemVerilog中对于一个信号的描述已经不像Verilog那样,一个信号的声明将由数据对象(data object)和数据类型(data type)共同确定。2-state相较于4-state使用更少的存储空间,在一些设计和验证平台中经常需要用到2-state的情况,增加了设计和构建验证平台的灵活性和丰富性。
在SystemVerilog中变量的声明是通过var开始的,只是在具体使用时经常将这个关键字省略。而reg、logic等则不是表示指示变量的,而是表示数据类型(data type),在SystemVerilog中常用的数据类型如下表所示:
变量(var) | 默认值 |
4-state integral(integer、reg、logic、time) | X |
2-state integral(byte、shortint、int、longint、bit) | 0 |
real、shortreal、realtime | 0.0 |
enumeration | Base type default initial value |
string | “”(empty string) |
class | null |
chandle | null |
SystemVerilog中线网并没有像变量那样通过一个通用的关键字var进行标识,而是保持了与Verilog基本一致,只是增加了一种新的线网类型uwire,其使用可参考《Verilog系列:【21】线网类型知多少》,并且所有的线网对象的数据类型都是4-state。
【示例】
`timescale 1 ns / 1 ps module top_tb; var bit sig1; bit sig2; var logic sig3; wire logic sig4; // wire bit sig5; // illegal wire sig6; initial begin #1 sig1 = 1'b0;sig3 = 1'b1; #3 sig1 = 1'b1;sig3 = 1'b0; #4 sig1 = 1'bx;sig3 = 1'bx; #2 sig1 = 1'b1;sig3 = 1'b1; #7 sig1 = 1'bz;sig3 = 1'bz; #3 sig1 = 1'b1;sig3 = 1'b1; #5 $stop; end assign sig2 = sig1; assign sig4 = sig3; assign sig6 = sig3; endmodule // top_t【仿真结果】
示例中sig1和sig2声明时均指定了数据类型为2-state,在后续的程序中虽然给其赋予了不定态和高阻态,但是因为其数据类型为2-state,所以其取值只能为0或者1,可以通过仿真观测到给sig1和sig2被赋予不定态或者高阻态时,实际上其值为2-state数据类型的默认值0,即给2-state数据类型的信号赋予不定态或者高阻态,该信号值将为0。sig3和sig4声明为4-state,其取值范围为(0,1,X,Z)的值可以是4-state中的任何一个。线网sig6并没有显式的指明其数据类型,但是此时其默认的数据类型实际是logic,即sig6的值可以是4-state中的任何一个。即如果没有明确指定线网的数据类型,那么该线网的默认数据类型为logic,即示例中的“wire sig6”与“wire logic sig6”是等价的(在没有多结构性驱动的情况下,后续示例会介绍到)。同时线网的数据类型不能为2-state,所以当在线网声明时指定其数据类型为2-state是不允许的。
通过上述示例可以看到,线网在声明的时候必须显式的指明线网类型,否则该信号会被默认为变量。变量类型的关键字var可以省略,但是省略var时数据类型不能省略。当省略数据类型时,变量类型关键字var就不能省略,此时默认数据类型为logic。所以我们可以知道在实际的使用过程中,我们经常使用变量时是省略了关键字var的,使用的是数据类型。
在设计和构建验证环境时,经常需要在信号声明时指定初始值,但是一般建议不要在信号声明时指定初始值(综合后的结果可能与你仿真结果大相径庭)。变量声明时指定初始值后,当有其他驱动该变量时,变量的初始值将会被改变。而线网在声明时指定初始值相当于对该线网进行连续赋值,即该初始值将始终保持,并且不会被其他驱动所覆盖。
【示例】
`timescale 1 ns / 1 ps module top_tb; wire sig1 = 1'b1; logic sig2 = 1'b0; logic sig3; initial begin #10 sig3 = 1'b1;sig2 = 1'b1; #10 sig3 = 1'b0;sig2 = 1'b0; #10 sig3 = 1'b1;sig2 = 1'b1; #10 sig3 = 1'b0;sig2 = 1'b0; #5 $stop; end assign sig1 = sig3; endmodule // top_tb【仿真结果】
`timescale 1 ns / 1 ps module top_tb; wire sig1; logic sig2; logic sig3; initial begin #10 sig3 = 1'b1; #10 sig3 = 1'b0; #10 sig3 = 1'b1; #10 sig3 = 1'b0; // #10 sig2 = 1'bz; // illegal #5 $stop; end assign sig1 = sig3; assign sig2 = sig3; endmodule // top_tb【仿真结果】
示例中,sig2和sig3均为变量,但是sig2应用到了连续赋值语句中,如果在同一个模块内部,再将sig2应用到过程性赋值语句中那么编译将不会通过。也就是说同一个变量不能既出现在连续赋值语句中又出现在过程性赋值语句中。
【示例】
`timescale 1 ns / 1 ps module top_tb; var logic sig1; var logic sig2; initial begin #10 sig1 = 1'b1; #13 sig1 = 1'b0; #12 sig1 = 1'b1; #14 sig1 = 1'b0; #3 sig2 = 1'b0; #4 $stop; end assign sig2 = sig1; endmodule // top_t【仿真结果】
示例中,sig2既用于连续赋值语句又再initial过程块中使用,编译时会报出编译错误。
此外,在Verilog和SystemVerilog中,我们知道连接inout类型的端口只能使用线网型信号,如果一个信号没有显式的指明线网,那么该信号是属于变量,不能直接连接到inout端口,这也就是为什么很多资料中都提到,当连接端口为inout时,logic不能替换wire。
【示例】
`timescale 1 ns / 1 ps module top_tb; logic sig1,sig2,sig3; wire sig4; assign sig4 = sig1; assign sig4 = sig2; assign sig3 = sig2; assign sig3 = sig1; initial begin sig1 = 1'bz;sig2 = 1'bz; #3 sig1 = 1'b0;sig2 = 1'b0; #2 sig1 = 1'b1;sig2 = 1'b0; #3 sig1 = 1'bz;sig2 = 1'b0; #4 sig1 = 1'b0;sig2 = 1'b1; #2 $stop; end endmodule // top_tb【仿真结果】
当一个变量被一个结构性驱动(连续赋值)驱动时,该变量不能再被其他过程性语句或者结构性驱动再驱动,但是线网可以同时被多个结构性驱动所驱动。
通过上述示例,可以对变量和线网总结如下(本文不涉及parameter):
1)在Verilog中,变量和线网只有四值:0,1,X,Z;在SystemVerilog中变量除了可以有4-state外还可以有2-state,具体取决于变量声明时指定的数据类型,但是SystemVerilog中的线网和Verilog一样,只有4-state;
2)在Verilog中,没有像SystemVerilog那样对变量、线网、数据类型进行详细的区分,仅有变量和线网的区分,并且两者都是4-state,而SystemVerilog将信号按照对象和数据类型进行了较为详细的划分,信号的声明包括数据对象和数据类型两部分,组成如下图所示:
其中变量关键字var声明时可以省略,但是当变量关键字省略时,数据类型不能省略;关键字不省略时,数据类型可以省略,此时默认的数据类型为4-state。平时我们经常使用的都是省略了变量关键字var,直接使用数据类型的方式。线网只能使用4-state数据类型。
3)在Verilog中变量不能用于连续赋值语句的LHS,但在SystemVerilog中变量不仅可以用于过程性赋值语句中,还可以用于连续赋值语句中,但是不能同时既用于连续赋值语句又用于过程性赋值语句中。
4)线网声明时指定的初值在运行的过程中一直有效,但变量声明时指定的初值当有其它驱动时该初值将会被修改覆盖。
5)线网的默认值为“Z”(除trireg之外,trireg默认值为“X”),变量的默认值取决于其数据类型。
6)在SystemVerilog中,任何使用wire的地方都可以用logic替换,但要求此时的logic不能有多结构性的驱动。