【Verilog】建议从Verilog中删除那些难看的寄存器数据

2019-04-18  本文已影响0人  江海寄余生_

Clifford E. Cummings

摘要

Verilog 语言中最令人困惑的概念之一是什么时候变量是reg,什么时候变成wire?虽然声明reg 和wire 的规则非常简单,但大多数新的和自学成才的Verilog用户不明白何时以及为什么需要这种声明。

目的

目的:

原因:

介绍

Verilog 中寄存器和线网变量的概念在很大程度上被误解。
VHDL的过程大致等同于Verilog 中的always 块,并且VHDL并行信号赋值大致等同于Verilog连续赋值。但VHDL不需要对过程和当前信号分配不同数据类型声明。在VHDL中,信号通常用于代替Verilog 寄存器和线网数据类型。
为什么Verilog用户对这两种不同的数据类型有负担?

寄存器和线网声明的简单规则

在Verilog中,寄存器数据类型包括:reg, integer, time, real 和realtime。线网数据类型包括: wire, tri, vor, trior, wand, triand, tri0, tril1, supply0, supply1 和trireg。让我们看看两个简单的Verilog实例来帮助理解寄存器和线网数据类型的声明。

module and2a(y, a, b);
    output y;
    input  a, b;
    
    assign y = a & b;
 endmodule
 
 Example 1
module and2b (y, a, b);
   output y;
   input  a, b;
   wire   y;
   
   assign y = a & b;
endmodule

Example 2

在实例1中, 使用连续赋值语句对2输入和门进行建模。y输出不必声明,因为他是一位线网型。实例2是完全相同的2输入和门,可选‘wire y’声明。
在实例3中, 我们决定用always 块替换连续赋值,但编译此代码时,Verilog编译器会报告‘非法左侧赋值’形式的语法错误,因为我们忘记更改‘wire’声明为‘reg’,如果把‘wire’改为‘reg’,该模型正确编译和模拟。

module and2c (y, a, b);
    output y;
    input  a, b;
    wire   y;
    
    always @(a or b) y = a & b;
endmodule 

Example 3

现在如果实例3中的2输入和门的always块被更改回连续赋值,如实例4所示,Verilog编译器将再次报告语法错误,但是这次该消息的格式变为‘非法转让给线网’,因为我们忘记了改变‘reg’为‘wire’,很烦人。

module and2d (y, a, b);
    output y;
    input  a, b;
    reg    y;
    
    assign y = a & b;
endmodule

Example 4

简单规则:在Verilog中,程序分配左侧的任何内容都必须声明为寄存器数据类型。Verilog中的其他一切都是线网数据类型,没有例外。

为什么区分线网型和寄存器型

为什么在Verilog中区分线网型和寄存器型数据类型?这个问题的答案似乎是,数据类型检查是一种简单的方法,可以识别连续和过程赋值中的同一个变量的错误分配。
连续赋值驱动线网型数据,如例5所示,多驱动能驱动相同的线网数据。

module drivers1 (y, a1, en1, a2, en2);
    output y;
    input  a1, en1, a2, en2;
    
    assign y = en1 ? a1 : 1'bz;
    assign y = en2 ? a2 : 1'bz;
endmodule

Example 5

过程赋值,例如always块赋值会导致对单个行为变量的更改。实例6的多个always块赋值只是赋值给相同的行为变量,而且不设置多个驱动过程,在这个例子中,最后的赋值胜出。

module drivers2 (y, a1, en1, a2, en2);
    output y;
    input  a1, en1, a2, en2;
    reg y;
    
    always @(a1 or en1)
    if (en1) y = a1;
    else y = 1'bz;
    
    always @(a2 or en2)
    if (en2) y = a2;
    else y = 1'bz;
endmodule

Example 6

如果尝试为统一变量设置驱动和行为级赋值,则驱动过程需要线网声明,而always块赋值需要reg声明,这两个语句都是相同的变量,这是语法错误。这种语法错误是维持Verilog设计人员试图对相同变量进行两种基本不同类型的赋值的一种方法。

module drivers3 (y, a1, en1, a2, en2);
    output y;
    input a1, en1, a2, en2;
    wire?/reg? y;
    
    always @(a1 or en1)
    if (en1) y = a1;
    else y = 1'bz;
    
    assign y = en2 ? a2 : 1'bz;
endmodule

Example 7

例7中的代码不能合法地将y变量声明为线网类型或寄存器类型。图1从概念上表明,例7的代码证尝试更改行为变量并通过连续赋值来驱动相同的变量。

如果一位设计师真的想对同一个变量做一个线网驱动变量的过程赋值,那么可以将always块中的LHS声明为通常被称为‘影子’寄存器的那个,这是一个临时寄存器,然后通过连续赋值被驱动到线网型变量中,如例8和图2所示。但是如果你打算这样做,那么你完全可以跳过always块的赋值,只需使用第二个连续赋值语句来进行分配。

module drivers4 (y, a1, en1, a2, en2);
    output y;
    input a1, en1, a2, en2;
    wire y;
    reg y_tmp;
    
    always @(a1 or en1)
    if (en1) y_tmp = a1;
    else y_tmp = 1'bz;
    
    assign y = y_tmp;
    assign y = en2 ? a2 : 1'bz;
endmodule

Example 8

条件编译

如果设计人员想要包含条件编译,选择always块或连续赋值,如示例9所示,该怎么办?条件编译的1位连续赋值不需要数据类型声明或可以包含可选的线网声明。
另一个有条件编译的分支,即1位始终块分配,需要注册数据类型声明。
一些公司的编码准则要求在所有I / O声明之后立即将所有数据类型声明放置在模块的顶部。 条件编译的always-block代码将违反本指南,除非单独的条件编译的声明部分添加到模块代码顶部附近的分组声明中(未显示)。

module inva (y, a);
    output y;
    input a;
    ` ifdef ASSIGN
    assign #(1:2:3,4:5:6) y = ~a;
    `else
    // mid-code reg declaration
    reg y;
    always @(a) #(1:2:3) y = ~a;
    `endif
endmodule

Example 9

Verilog-2000端口增强

在Verilog-1995中,所有寄存器类型的输出端口必须声明三次:

  1. 在模块头部

  2. 与端口声明,和

  3. 作为单独的寄存器数据类型。

module and2ora (y, a, b, c);
    output y;
    input a, b, c;
    reg y;
    reg tmp;
    always @(a or b)
        tmp = a & b;
    always @(tmp or a)
    y = tmp | c;
    endmodule

Example 10

Verilog-1995的另一个要求是,连续任务的LHS的任何网络变量都必须声明为不连接到端口,包括1bit线网。Verilog-1995 的这个不一致的要求在Verilog-2000中得到了修正。在广泛实施Verilog-2000之前,如果例10的always块分配被替换为如例11所示的等效连续赋值,则需要tmp的线网声明。

module and2orb (y, a, b, c);
    output y;
    input a, b, c;
    reg y;
    wire tmp;
    
    assign tmp = a & b;
    always @(tmp or a)
         y = tmp | c;
endmodule

Example 11

从Verilog-2000开始,可以使用端口声明简化增强功能。
为了本文的目的,使用以下端口声明样式定义:

也可以做一个风格#1和风格2#的混合体,但本文没有一个例子显示这种组合。
Verilog-2000中的第一个端口声明增强功能包括组合端口和类型声明的功能。 示例12显示了所有用数据类型声明的端口(以上称为样式#1)。 y输出不需要单独的寄存器声明。

module and2orc (y, a, b, c);
    output reg y;
    input wire a, b, c;
    reg tmp;
    always @(a or b)
        tmp = a & b;
    always @(tmp or a)
        y = tmp | c;
endmodule

Example 12

示例13还显示了合法的Verilog-2000声明,其中只有reg类型端口声明包含数据类型,而所有net类型端口声明省略数据类型(以前称为样式#2)。

module and2ord (y, a, b, c);
    output reg y;
    input a, b, c;
    reg tmp;
    always @(a or b)
        tmp = a & b;
    always @(tmp or a)
        y = tmp | c;
endmodule

Example 13

Verilog-2000的另一个端口增强功能是在模块头本身允许使用端口方向和数据类型,从而可以将所有端口声明为一次。 预期的模块头端口声明的方法是使用左括号开头编码模块头,然后是在单独的后续行中声明的每个端口,并在独立行上以结束括号和分号结尾, 如图所示在例14中。

module and2ore (
                        output reg y;
                        input a, b, c;
                        );
    reg tmp;
    always @(a or b)
        tmp = a & b;
    always @(tmp or a)
        y = tmp | c;
endmodule

Example 14

使模块头部中的所有端口声明都能保证所有端口都将被声明在模块的顶部,Verilog标准组织(VSG)预期在Verilog编译期间将允许增强优化和加速。 在Verilog-1995中,端口声明可以出现在模块中的任何地方,这意味着在读取endmodule语句之前,编译器无法识别并报告缺失的端口。

在示例14和示例15中所示的单声明,增强端口编码样式中,如果将tmp变量分配给always,则仍然需要将tmp变量声明为reg
块(示例14),或者如果从连续赋值(示例15)中指定,tmp变量可以省略或声明为连线。 在这两个示例中,y输出还需要reg声明。

module and2orf (
                        output reg y;
                        input a, b, c;
                        );
    wire tmp;
    assign tmp = a & b;
    always @(tmp or a)
        y = tmp | c;
endmodule

Example 15

所有Verilog-2000端口声明增强仍然存在的问题是, 将输出端口或内部变量分配从连续赋值更改为always 块仍需要改变增强型端口声明以反映变量的数据类型被修改,与Verilog-1995数据类型声明相同。
如果单独的寄存器和线网数据类型要求被消除,则示例16和示例17中所示的相同的增强型端口声明将是缩写和合法的。 请注意,在这两个示例中,声明都是相同的,不需要线网声明或寄存器声明。

module and2org (
                        output y;
                        input a, b, c;
                        );
    always @(a or b)
        tmp = a & b;
    always @(tmp or a)
        y = tmp | c;
endmodule

Example 16
module and2orh (
                        output y;
                        input a, b, c;
                        );
    assign tmp = a & b;
    assign y = tmp | c;
endmodule

Example 17
module and2ori (
                        output y;
                        input a, b, c;
                        );
    assign tmp = a & b;
    always @(tmp or c)
        y = tmp | c;
endmodule

Example 18

当然,在Verilog-2005中也可以简化和/或模型,方法是将前面例子中看到的单独赋值合并成例19中所示的单个连续赋值,或者合并成单个always块, 如例20所示。例20还显示了组合的灵敏度列表运算符“@* ”,用于将所有的RHS变量,if-表达式变量(不在本例中)和case-expression变量(本例中不是这个例子)收集到灵敏度列表中。“@* ”操作符是Verilog-2000的新增功能。

module and2orj (
                        output y;
                        input a, b, c;
                        );
    assign y = (a & b) | c;
endmodule

Example 19
module and2ork (
                        output y;
                        input a, b, c;
                        );
    always @*
        y = (a & b) | c;
endmodule

Example 20

在例19和例20中,代码都已经简化了等效的Verilog-1995模块,如例21所示。

module and2orl (y, a, b, c);
    output y;
    input a, b, c;
    reg y;
    always @(a or b or c)
        y = (a & b) | c;
endmodule

Example 21

声明线网型的优点和缺点

一些Verilog设计人员认为,在每个模块中都存在线网的情况下,声明所有线网(包括1位线网)是一种很好的做法。 做出所有声明的明显原因是(1)记录所有连线的存在,以及(2)Verilog对所有声明变量进行全面大小检查的错误概念。
声明所有连线也是一些习惯,这些习惯是由一些以前使用VHDL设计的工程师开发的,VHDL是一种需要所有信号声明的语言。 在VHDL中,编译器检查声明的信号,信号大小和实际VHDL模型中使用的信号大小。
在Verilog中,不存在同样严格的大小检查。 尽管会发生一些大小检查,除非代码体内的变量(不仅仅是声明中)包含位范围,否则大部分大小检查都可能 很容易被忽略。
许多变量声明为1位线网,然后用作总线,而不参考Verilog代码中的总线范围,将被转换为1位线网,其中所有前导位位置的分配都用0填充。

module invbad1 (y, a);
    output [7:0] y;
    input [7:0] a;
    wire tmp;
    assign tmp = ~a;
    assign y = tmp;
endmodule

Example 22

例22中的模型是一个人为的但简单的8位反相器的例子,其中内部tmp变量被错误地声明为1位线网。编译时没有语法错误或警告,并且使用例23中的testbench进行模拟时,1位tmp填充了前导零,导致模块输出的高7位始终为零。

module tb;
    reg [7:0] a;
    wire [7:0] y;
    inv_module u1 (.y(y), .a(a));
    
    initial begin
        $monitor ("y=%h a=%h", y, a);
        a = 8'h00;
        #10 a = 8'h55;
        #10 a = 8'hCC;
        #10 $finish;
    end
endmodule

Example 23

使用示例24中所示的1位reg变量的相同模型具有与示例22的1位Wire代码完全相同的问题。在任何情况下,wire或reg声明的存在都有助于定位编码错误; 事实上,可以认为声明的存在可能掩盖了变量被错误地声明的事实。

module invbad2 (y, a);
    output [7:0] y;
    input [7:0] a;
    reg tmp;
    always @(a) tmp = ~a;
    assign y = tmp;
endmodule

Example 24

作为一个侧面说明,即使VHDL执行了前面提到的所 有大小检查,作者完成的一个非常大的VHDL设计,作 者注意到他花了几乎同样多的时间调试顶部所需信号 声明的页面他花费在调试实际设计问题上的设计水平。
作者认为,将声明限制为总线声明有助于简明地显示哪些标识符应该具有多位宽度,同时消除往往会占用空间并掩盖内部总线存在的不必要和冗长的1位声明。 作者认为,短绒工具最适合检查尺寸并报告潜在问题。 作者承认,其他熟练的设计师持相反的观点, 即所有变量都应该声明。
在示例25和示例26中,已经做出了适当的内部总线声明,并且两种模型都可以正确模拟。

module invgood1 (y, a);
    output [7:0] y;
    input [7:0] a;
    wire [7:0] tmp;
    assign tmp = ~a;
    assign y = tmp;
endmodule

Example 25
module invgood2 (y, a);
    output [7:0] y;
    input [7:0] a;
    reg [7:0] tmp;
    always @(a) tmp = ~a;
    assign y = tmp;
endmodule

Example 26

线网型和寄存器型差异

Verilog网络和寄存器数据类型之间有一些显着差异。 图3显示了一个列出网络和寄存器数据类型之间重要区别的表格。

线网型 寄存器型
Verilog优势
初始值 高阻 x
多赋值 所有驱动的值 上次赋的值
用于声明的范围 wire, tri, wor, trior, wand, triand, tri0, tri1, supply0, supply1, trireg reg

处理现有的寄存器类型

为了实施注册禁令的增强,必须制定一项计划,使这种增强与现有的寄存器类型向后兼容声明。 所有Verilog-1995和Verilog-2000寄存器类型声明的现有模型都需要正确读取和模拟。
为了说明Verilog编译器如何将不同的寄存器数据类型视为向后兼容,图4显示了一个可能的实现表。

寄存器类型 线网型实现
reg wire
reg [msb:lsb] wire [msb:lsb]
integer wire signed[31:0]
time wire[63:0
real 只在过程块中赋值
realtime 只在过程块中赋值

上述integer,time,realtime和实时关键字仍然可以用来暗示某些范围内的某些数据类型。 整数声明还可以推断添加到Verilog-2000的签名线网数据类型。 实际和实时数据类型在渲染时可能没有意义,因此对这些变量的分配可能会继续限制为程序块,Verilog-AMS可能例外。
在内部,Verilog编译器可以继续像现在一样处理所有数据类型。 区别在于Verilog应该能够从代码的上下文中推断适当的内部数据类型。 如果未指定,则在always模块内部分赋值的1位线网变量在模拟开始时仍可能未知,并且在程序块内部赋值的线网变量仍然可以在没有Verilog强度的情况下实施。
上述实施建议可能存在一些小问题,但作者认为这些是IEEE Verilog标准组可以制定的小细节。
现有的寄存器数据类型声明可能大部分被忽略,除非它们涉及变量是否是带符号变量(整数),隐含的总线宽度(整数 - 32位宽/时间 - 64位宽)或显式总线宽度(reg [msb:lsb])。

一种新的语法检查

与其强迫用户区分过程块中使用的数据类型和过程块外使用的数据类型之间的区别,为什么不在过程块内部和外部赋值同一变量时定义语法错误。
提出的reg-removal增强存在的一个潜在问题是, 消除所需的net和register数据类型将允许设计人员进行驱动程序类型赋值和行为类型赋值给同一个变量。 这不应该被允许。
寄存器移除提案的实施应该伴随着一种新的语法检查,一种可以确定哪些变量是从程序块中分配的,哪些不是。从程序性指配和非程序性指派中指定同一个变量应是非法的。这实际上是Verilog编译器在当前的网络和注册声明要求下执行的。
其他赋值限制可能包括:

结论

总之,目前的净数据和寄存器数据类型要求令人困惑和烦人。 执行这些声明规则的唯一明显理由是不让程序员对由非程序性分配源驱动的变量进行程序分配。
为了消除上述问题并简化Verilog建模,作者提出了以下建议:

上一篇下一篇

猜你喜欢

热点阅读