Verilog reg 和 Verilog wire 之间的区别经常让许多刚开始学习这种语言的程序员感到困惑(我也曾困惑过!)。作为初学者,我被告知要遵循以下准则,这些准则似乎普遍有效:
- 在始终块内部分配的信号左侧 (LHS) 使用 Verilog 规范
- 使用 Verilog wire 处理分配在 always 块外的信号左端 (LHS)
后来,当我采用 SystemVerilog 编写 RTL 设计时,我被告知现在一切都可以是 “类型逻辑”。这在一般情况下也行得通,但时不时会遇到关于变量、网和赋值的令人费解的错误信息。
于是,我决定找出这些数据类型的确切工作原理,来撰写这篇文章。我翻阅了语言参考手册,搜索了现已失效的 Verilog-2005 标准文件,并上了一堂历史课。请继续阅读我发现的 Verilog reg、Verilog wire 和 SystemVerilog 逻辑之间的区别。
Verilog data types, Verilog reg, Verilog wire
Verilog 数据类型分为两大类:网和变量。它们的区别在于它们如何表示不同的硬件结构。
网数据类型表示结构实体之间的物理连接(想象一根普通的导线),如门之间或模块之间。它不存储任何值。它的值来自其驱动程序的驱动内容。Verilog 线可能是最常见的网络数据类型,尽管还有许多其他网络数据类型,如 tri、wand、supply0。
变量数据类型通常代表一块存储空间。在下一次赋值之前,它可以保持一个赋给它的值。Verilog reg 可能是最常见的变量数据类型。Verilog reg 通常用于模拟硬件寄存器(尽管它也可以表示组合逻辑,如 always@(*) 块内部)。其他变量数据类型包括整数、时间、real、realtime。
几乎所有 Verilog 数据类型都是 4 态的,这意味着它们可以取 4 个值:
- 0 表示逻辑零,或假条件
- 1 代表逻辑 1,或真条件
- X 代表未知逻辑值
- Z 代表高阻抗状态
Verilog 经验法则 1:要表示存储空间时使用 Verilog reg,要表示物理连接时使用 Verilog wire。
Assigning values to Verilog reg, Verilog wire
Verilog 网络数据类型只能通过连续赋值来赋值。这意味着要使用连续赋值语句(赋值语句)等结构,或从输出端口驱动。连续赋值驱动网络的方式与门驱动网络的方式类似。右侧的表达式可以看作是连续驱动网络的组合电路。
Verilog 变量数据类型只能通过过程赋值来赋值。这意味着要在始终块、初始块、任务和函数中进行赋值。赋值发生在某种触发器上(如时钟的摆位),之后变量将保留其值,直到下一次赋值(在下一个触发器上)。这使得变量成为模拟触发器等存储元件的理想选择。
Verilog 第 2 条规则:用赋值语句或端口输出驱动 Verilog 线,并从 always 块中驱动 Verilog reg。如果要在 always@(*) 块内用组合逻辑驱动物理连接,则必须将物理连接声明为 Verilog reg。
SystemVerilog logic, data types, and data objects
SystemVerilog 引入了一种新的 2 状态数据类型(只允许逻辑 0 和逻辑 1,不允许 X 或 Z),用于测试台建模。为了区别旧的 Verilog 4 状态行为,增加了一种新的 SystemVerilog 逻辑数据类型来描述通用的 4 状态数据类型。
过去在 Verilog 中的数据类型,如 wire、reg、wand,现在在 SystemVerilog 中称为数据对象。wire、reg、wand(以及几乎所有以前的 Verilog 数据类型)都是 4 态数据对象。Bit、byte、shortint、int、longint 是新的 SystemVerilog 2 态数据对象。
仍然有两大类数据对象:nets和variables。我们熟悉的所有 Verilog 数据类型(现在的数据对象)都是 4 态数据对象,因此现在也应正确地包含 SystemVerilog 逻辑关键字。
wire my_wire; // implicitly means “wire logic my_wire”
wire logic my_wire; // you can also declare it this way
wire [7:0] my_wire_bus; // implicitly means “wire logic[15:0] my_wire_bus”
wire logic [7:0] my_wire_logic_bus; // you can also declare it this way
reg [15:0] my_reg_bus; // implicitly means “reg logic[15:0] my_reg_bus”
//reg logic [15:0] my_reg_bus; // but if you declare it fully, VCS 2014.10 doesn’t like it
如果未指定数据类型(2 态或 4 态),则隐式声明为逻辑变量。下面是一些变量声明示例。尽管有些工具似乎并不完全支持。
// From the SV-2012 LRM Section 6.8
var byte my_byte; // byte is 2-state, so this is a variable
// var v; // implicitly means “var logic v;”, but VCS 2014.10 doesn’t like this
var logic v; // this is okay
// var [15:0] vw; // implicitly means “var logic [15:0] vw;”, but VCS 2014.10 doesn’t like this
var logic [15:0] vw; // this is okay
var enum bit {clear, error} status; // variable of enumerated type
var reg r; // variable reg
不用太在意 var 关键字。添加这个关键字是为了保证语言的精确性(随着语言的发展,语言大师们会努力保持向后兼容性),在 RTL 设计中很可能看不到这个关键字。
I’m confused… Just tell me how I should use SystemVerilog logic!
说了这么多技术规范的废话,如果您正在使用 SystemVerilog 进行 RTL 设计,我有一个好消息要告诉您。在 RTL 设计的日常使用中,你几乎可以忘掉所有这些!
SystemVerilog 逻辑关键字 standalone 将声明一个变量,但规则已经改写,在 RTL 设计中几乎可以随处使用变量。因此,在其他文章的示例代码中,我使用 SystemVerilog 逻辑来声明变量和端口。
module my_systemverilog_module
(
input logic clk,
input logic rst_n,
input logic data_in_valid,
input logic [7:0] data_in_bus,
output logic data_out_valid, // driven by always_ff, it is a variable
output logic [7:0] data_out_bus, // driven by always_comb, it is a variable
output logic data_out_err // also a variable, driven by continuous assignment (allowed in SV)
);
assign data_out_err = 1'b1; // continuous assignment to a variable (allowed in SV)
// always_comb data_out_err = 1'b0; // multiple drivers to variable not allowed, get compile time error
always_comb data_out_bus = <data_out_bus logic expression>;
always_ff @(posedge clk, negedge rst_n)
if (!rst_n)
data_out_valid <= 1'b0;
else
data_out_valid <= <data_out_valid logic expression>;
endmodule
当您以这种方式独立使用 SystemVerilog
逻辑时,还有另一个好处,即改进了对意外多重驱动的检查。SystemVerilog
变量的多重赋值或连续赋值与过程(总是块)赋值的混合是一个错误,这意味着很可能会出现编译错误。在网络中允许混合赋值和多重赋值。因此,如果您真的需要一个多重驱动的网,您需要将其声明为线。
在 Verilog
中,从模块外部向模块输出端口(声明为 Verilog wire
或 Verilog reg
)赋值,或在模块内部向声明为输入端口的网络赋值都是合法的。这两种情况都是经常发生的非预期布线错误,会造成争用。对于 SystemVerilog
,声明为 SystemVerilog
逻辑变量的输出端口禁止多重驱动,而对声明为 SystemVerilog
逻辑变量的输入端口进行赋值也是非法的。因此,如果您犯了这种布线错误,很可能再次出现编译错误。
SystemVerilog 经验法则 1:如果使用 SystemVerilog 进行 RTL 设计,则应使用 SystemVerilog 逻辑进行声明:
- 所有点对点网络。如果特别需要多驱动网络,则使用传统网络类型之一,如 wire
- 所有变量(由始终块驱动的逻辑)
- 所有输入端口
- 所有输出端口
如果遵循这一规则,您就可以基本忘记 Verilog reg 和 Verilog wire 之间的区别!(好吧,大多数时候)
Conclusion
当我最初想知道为什么可以始终使用 SystemVerilog
逻辑关键字编写 RTL 时,我从未料到这会成为一项重大的工作,需要阅读和解释两种不同的规范,理解复杂的语言规则,并弄清其中的细微差别。至少我可以说,这些建议很容易记住。
我希望这篇文章能为您很好地总结 Verilog reg
、Verilog wire
、SystemVerilog
逻辑、它们的历史,以及一套有用的 RTL 编码建议。