1.Verilog HDL模块的基本概念

  • 一个复杂电路的完整Verilog HDL模型是由若个Verilog HDL 模块构成的,每一个模块又可以由若干个子模块构成。利用Verilog HDL语言结构所提供的这种功能就可以构造一个模块间的清晰层次结构来描述极其复杂的大型设计。

  • 下面看一个简单的程序

    1
    2
    3
    4
    5
    6
    7
    8
    module  and2(out,a,b,c); //and2是模块名称
    input a,b; //端口定义
    output c;
    wire a,b; //数据类型说明
    reg c;
    always@(a or b) //逻辑功能描述
    out = a & b;
    endmodule

  • 这就是一个简单的与门模块,由此可以看出 Verilog HDL巳程序是由模块构成的。每个模块的内容都是嵌在module和endmodule两个语句之间,每个模块实现特定的功能,模块是可以进行层次嵌套的。

  • 每个模块首先要进行端口定义.并说明输入(input)和输出(output),然后对模块的功能进行逻辑描述。

  • Verilog HDL程序的书写格式自由,一行可以写几个语句,一个语句也可以分多行写。

  • 除了endmodule语句外,每个语句的最后必须有分号。

  • 一个模块是由两部分组成的,一部分描述接口;另一部分描述逻辑功能,即定义输入是如何影响输出的。

2.模块(block)的组成

  • Verilog HDL结构完全嵌在module和endmodule声明语句之间,每个Verilog程序包括4个主要部分:端口定义,I/O说明,信号类型声明和功能描述。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    module<模块名>(<端口列表>);
    端口说明(input,output,inout)
    参数定义(可选)
    数据类型定义
    连续赋值语句(assign)
    过程块(initialalways)
    行为描述语句
    低层模块实例
    任务和函数
    延时说明块
    endmodule

    上述的是总体的描述,后面会一一讲解每个部分的功能。

  • 模块声明

    模块声明包括模块名和端口列表。其格式如下:

    module 模块名(端口1,端口2,端口3,…);

    ​ 模块结束的标志为关键字: endmodule

  • 端口定义

    ​ input(输入端口),output(输出端口)和inout(双向端口)。

    ​ 格式如下:

    input 端口名1,端口名2,………,端口名N; //输入端口

    output 端口名1,端口名2,………,端口名N; //输出端口

    inout 端口名1,端口名2,………,端口名N; //输入输出端口

    ​ 也可以写在端口声明语句里,其格式如下(为了代码的可读性,一般不这么写):

    module module_name(input port1,input port2,…output port1,output port2… );

  • 信号类型说明

    信号可以分为端口信号和内部信号;

    • 所有信号都必须进行数据类型的定义,如寄存器类型(reg等),连线类型(wire等);
    • 如果信号没有定义数据类型,则综合器将其默认为wire型;
    • 端口的位宽最好定义在端口定义中,不要放在数据类型定义中;
    • 不能将input和inout类型声明为reg型;
  • 模块的端口表示的是模块的输人和输出口名,也就是说,它与别的模块联系端口的标识。

    在模块被引用时,在引用的模块中,有些信号要输入到被引用的模块中,有的信号需要从被引用的模块中取出来口。在引用模块时其端口可以用两种方法连接:

    ​ (1)在引用时,严格按照模块定义的端口顺序来连接,不用标明原模块定义时规定的端口名,例如:

    模块名(连接端口1信号名,连接端口2 信号名,连接端口3信号名,……);

    ​ (2)在引用时用“.”符号,标明原模块是定义时规定的端口名,例如:

    模块名( . 端口1名(连接信号1名),. 端口2名(连接信号2名),...);

    ​ 优点:在于可以用端口名与被引用模块的端口相对应,而不必严格按端口顺序对应,提高了程序的可读性和可移植性。

3.常量

  • 关键字

    ​ 关键字(又称保留字), 小写的英文字符串。如: module、endmodule、input、output、wire、reg、and、assign、always等。下面是所有的关键字:

    1
    2
    3
    always   and   assign   begin   buf   buf   if0   bufif1   case  casex  casez cmos    deassign   default   defparam  disable  edge  else  end  endcase endmodule    endfunction    endprimitive    endspecify  endtable  endtask event    for   force   forever   fork    function   highz0    highz1   if    ifnone    initial   inout  input   integer   join   large  macrmodule  medium   module 
    nand negedge nmos nor not notif0 notif1 or output parameter pmos posedge primitive pull0 pull1 pullup pulldown rcmos real realtime reg release repeat rnmos rpmos rtran rtranif0 rtranif1 scalared small specify specparam strong0 strong1 supply0 supply1
    table task time trantranif0 tranif1 tri tri0 tri1 triand trior trireg vectored wait wand weak0 weak1 while wire wor xnor xor
  • 标识符

    ​ 标识符(identifier)是程序代码中给对象(如模块、端口、变量等)取名所用的字符串。

    ​ 标识符的组成:由字母、数字字符、下划线(_)和美元符号($)组成,区分大小写,其第一个字符必须是英文字母或下划线,不能是数字或$。以$开始的字符串是为系统函数保留的,如“$display”。

    注意:1.关键字不能作为标识符使用。

    ​ 2.用有意义的有效的名字如Sum 、CPU_addr等。
    ​ 3.用下划线区分词。
    ​ 4.采用一些前缀或后缀,如
    ​ 时钟采用Clk 前缀:Clk_50,Clk_CPU;
    ​ 低电平采用_n 后缀:Enable_n;
    ​ 5.统一定的缩写如全局复位信号Rst。
    ​ 6.同一信号在不同层次保持一致性,如同一时钟信号必须在各模块保持一致。
    ​ 7.参数(parameter)采用大写,如SIZE 。

  • 用下列四种基本的值表示电路的逻辑状态:

    0:逻辑0或“假”;

    1:逻辑1或“真”;

    x:未知状态,通常在信号未被赋值前;

    z:高阻;

    在输入或表达式中,“z”的值通常被解释成“x”;

    z和x是不分大小写的,如01xz 与01XZ相同;

 Verilog HDL中,有3种类型的常量:整数型常量(整数)、实数型常量(实数)和参数型常量。
  • 整数

    整数的一般表达式为:

    <+/-><size> ’ <base format><number>

    其中 size : 大小,表示二进制位数(bit)。缺省为32位。(可有可无);

    ​ base format:数基,可为2(b)、8(o)、10(d)、16(h)进制。缺省为10进制;

    ​ number:是所选数基内任意有效数字,包括X、Z。

    ​ 默认数基为10进制

    ​ 当数值number大于指定的大小时,截去高位。2’b1101表示的是2’b01

    ​ 一个数字可以被定义为负数,只需在位宽表达式前加一个负号,注意必须在数字定义表达式的最前面。

    ​ 下划线符号_可以自由的在整数或实数中使用;就数值本身而言,它们没有任何意义。它们能够用来提高可读性;唯一的限制是下划线符号不能用来作为常数的首字符。例:a=8’b0001_0000;

1
2
3
4
5
6
7
-14             //十进制数-14
16’d255 //位宽为16的十进制数255
8’h9a //位宽为8的十六进制数9a
’o21 //位宽为32的八进制数21
’hAF //位宽为32的十六进制数AF
-4’d10 //位宽为4的十进制数-10
(3+2)’b11001 //非法表示,位宽不能为表达式
  • 实数

    (1)十进制格式,由数字和小数点组成(必须有小数点),例如:

    ​ 0.1, 3.1415, 2.0 √ 3. x

    (2) 指数格式:由数字和字符e(E)组成,e(E)的前面必须要有数字而且后面必须为整数,例如:

    1
    2
    3
    13_5.1e2       //其值为13510.0
    8.5E2 //850.0 (e与E相同)
    4E-4 //0.0004    
  • parameter(参数型)

    ​ 在Verilog HDL中为了提高程序的可读性和可维护性,用parameter来定义一个标识符代表一个常量,称为符号常量。

    其说明格式如下:

    parameter 参数名1 = 表达式,参数名2 = 表达式, …,参数名n = 表达式;

    parameter是参数型数据的确认符。确认符后跟着一个用逗号分隔开的赋值语句表。常用参数来声明运行时的常数。可用字符串表示的任何地方,都可以用定义的参数来代替。
    参数是本地的,其定义只在本模块内有效。

    举例说明:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    module md1(out,in1,in2);
    …..
    parameter cycle=20,
    p1=8, x_word=16’bx,
    file = “/user1/jmdong/design/mem_file.dat”;
    wire [p1:0] w1; //用参数来说明wire 的位宽
    ….
    initial begin $open(file); ……. //用参数来说明文件路径
    #20000 display(“%s”,file); $stop
    end
    endmodule
    • 参数型常数经常用于定义延迟时间和变量宽度。在模块或实例引用时,可通过参数传递改变在被引用模块或实例中已定义的参数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    module Decode(A,F);
    parameter Width = 1,Polarity = 1;
    ···
    endmodule
    module Top;
    wire[3:0] A4;
    wire[4:0] A5;
    wire[15:0] F16;
    wire[31:0] F32;
    Decode #(4,0) D1(A4,F16); //可通过参数的传递来改变定义时已规定的参数值
    Decode #(5) D2(A5,F32);
    defparam D2.Width = 5; //在一个模块中改变另一个模块的参数时
    endmodule
    • 在一个模块中改变另一个模块的参数时,需要使用defparam命令。在做布线后仿真时,就是利用这种方法把布线延迟通过布线工具生成的延迟参数文件反标注(Back-annotate)到门级Verilog网表上。

4.变量的数据类型

  • 线状网型变量(net)wire(需要被持续的驱动,驱动它的可以是门和模块)

当net驱动器的值发生变化时,Verilog自动的将新值传送到net上。在例子中,线网out由or门驱动。当or门的输入信号变化时将传输到线网out上。

  • wire型信号定义格式如下:

    wire [n-1:0] 变量名1,变量名2,…变量名n;

    wire [n:1] 变量名1,变量名2,…变量名n;

    1
    2
    3
    wire  a;              //定义了一个1位的wire型数据
    wire [7:0] b; //定义了一个8位的wire型向量
    wire [4:1] c, d; //定义了二个4位的wire型向量
  • 寄存器型变量 (reg)

    1
    2
    3
    4
    5
    6
    reg  [msb:lsb] 变量名1, 变量名2,…变量名n;
    例如:
    reg clock;
    reg [3:0] regb;
    reg [4:1] regc, regd;

    可以理解为实际电路中的寄存器,具有记忆性,是数据储存单元的抽象,在输入信号消失后它可以保持原有的数值不变。常代表触发器

    与线网型变量的根本区别在于:register型变量需要被明确地赋值,并且在被重新赋值前一直保持原值。

    只能在initial或always赋值,默认值是x。

    注意在always和initial块内被赋值的每一个信号都必须定义成reg型。

  • Verilog程序模块中,被声明为input或者inout型的端口,只能被定义为线网型变量,被声明为output型的端口可以被定义为线网型或者寄存器型变量,输入输出信号类型缺省时自动定义为wire型。

  • wire型信号可以用作任何方程式的输入,也可以用作“assign”语句或实例元件的输出,不可以在initial和always模块中被赋值。

  • 字符串的表示:

    是由一对双引号括起来的字符序列。必须在一行内写完。如”hello world!”是一个合法字符串。

    每个字符串(包括空格)被看作是8位的ASCII值序列。存储字符串“hello world!”,就需要定义一个8*12位的变量:

    1
    2
    3
    4
    5
    reg[8*12:1] stringvar;
    initial
    begin
    stringvar = “ hello world”;
    end
  • memory型

    对存储器(如RAM、ROM)进行建模。 通过扩展reg型数据的地址范围来生成的。

    格式:

    reg [msb:lsb] 存储器名1[upper1:lower1],存储器名2 [upper2:lower2],…;

    ​ 例如:reg [7:0] mem[1023:0];

    1
    2
    3
    4
    5
    6
    7
    8
    //在Verilog中可以说明一个寄存器数组。
    integer NUMS [7: 0]; // 包含8个整数数组变量;
    time t_vals [3: 0]; // 4个时间数组变量;
    reg [15: 0] MEM [0:1023]; // 1K x 16存储器MEM;
    //描述存储器时可以使用参数或任何合法表达式
    parameter wordsize = 16;
    parameter memsize = 1024;
    reg [wordsize-1: 0] MEM3 [memsize-1: 0];
  • 存储器寻址(Memory addressing)

    存储器元素可以通过存储器索引(index)寻址,也就是给出元素在存储器的位置来寻址。

         mem_name  [addr_expr]
    

    Verilog不支持多维数组。也就是说只能对存储器字进行寻址,而不能对存储器中一个字的位寻址。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    reg [8: 1] mema [0: 255]; // declare memory called mema
    reg [8: 1] mem_word; // temp register called mem_ word
    . . .
    initial
    begin
    $displayb( mema[5]); //显示存储器中第6个字节的内容
    mem_word = mema[5]; //将这个字节赋值给men_word
    $displayb( mem_word[8]); //显示第6个字节的最高有效位
    end
    endmodule

    尽管memory型数据和reg型数据的定义格式很相似,但要注意其不同之处。

    如一个由n个1位寄存器构成的存储器组是不同于 一个n位的寄存器的。

    1
    2
    3
    4
    5
    reg[n-1:0] rega;  //一个n位的寄存器
    reg mema[n-1:0];//一个由n个1位寄存器构成的寄存器数组
    rega = 0; //合法赋值语句
    mema = 0; //非法赋值语句
    mema[3] = 0; //合法赋值语句

5.端口数据类型

  • 一个端口看成是由相互连接的两个部分组成,一部分位于模块的内部,另一部分位于模块的外部。当在一个模块中调用(引用)另一个模块时,端口之间的连接必须遵守一些规则。

  • 输入端口:从模块内部来讲,输入端口必须为线网数据类型,从模块外部来看,输入端口可以连接到线网或者reg数据类型的变量。

    输出端口:从模块内部来讲,输出端口可以是线网或者reg数据类型,从模块外部来看,输出必须连接到线网类型的变量,而不能连接到reg类型的变量。

    输入/输出端口

    ​ 从模块内部来讲,输入/输出端口必须为线网数据类型;从

    ​ 模块外部来看,输入/输出端口也必须连接到线网类型的变

    ​ 量。

    位宽匹配

    ​ 在对模块进行调用的时候,verilog允许端口的内、外两个

    ​ 部分具有不同的位宽。一般情况下,verilog仿真器会对此

    ​ 警告。

  • 未连接端口

    ​ Verilog允许模块实例的端口保持未连接的状态。

    例如,如果模块的某些输出端口只用于调试,那么这些端

    口可以不与外部信号连接。

    端口与外部信号的连接

    ​ 在对模块调用的时候,可以使用两种方法将模块定义的端

    ​ 口与外部环境中的信号连接起来:按顺序连接以及按名字

    ​ 连接。但两种方法不能混合在一起使用。

    顺序端口连接:

    ​ 需要连接到模块实例的信号必须与模块声明时目标端口在

    ​ 端口列表中的位置保持一致。

  • 端口类型定义举例

  • 输入端口in1,in2可以由net/register(A,B)驱动,但输入端口in1,in2只能是net类型;

    输出端口out可以是net/register类型,输出端口只能驱动net(Y)。

    若输出端口out在过程块中赋值则为register类型;若在过程块外赋值(包括实例化语句),则为net类型。外部信号A,B类型判断方法与输出端口相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module DUT (O, in1, in2);
output O;
input in1, in2;
wire O, in1, in2; //只能为线型
and u1(O, in1, in2) ;
endmodule

module top;
wire y;
reg a, b; //可以用reg给a、b驱动
DUT u1 (y, a, b) ;
initial begin
a = 0; b = 0;
#5 a = 1; //过程块中只能给reg类型赋值
end
endmodule
  • 总结

    a.输入端口
    从模块内部来讲,输入端口必须为线网(net)数据类型;
    从模块外部来看,输入端口可以连接到线网(net)或reg数据类型的变量。

    b.输出端口
    从模块内部来讲,输出端口可以为线网(net)或reg数据类型;
    从模块外部来看,输出端口必须连接到线网(net)数据类型的变量。

    C.输入/输出端口
    从模块内部来讲,输入/输出端口必须为线网(net)数据类型;
    从模块外部来看,输入/输出端口必须连接线网(net)数据类型的变量。

6.运算符与表达式

1.算数运算符

在进行算术运算时,如果操作数的某一位为x或z,则整个表达式运算结果为不确定。 例1 + z = unknown。

两个整数进行除法运算时,结果为整数,小数部分被截去。如,6/4=1。

在进行加法运算时,如果结果和操作数的位宽相同,则进位被截去。

(+加 -减 *乘 /除 %求模)

  • 在进行算术运算时,Verilog根据表达式中变量的长度对表达式的值自动地进行调整。Verilog自动截断或扩展赋值语句中右边的值以适应左边变量的长度。

    将负数赋值给reg或其它无符号变量时,verilog自动完成二进制补码计算。

    1
    2
    3
    4
    5
    6
    reg[3:0] a,b;
    reg[15:0] c;
    a = -1; //a为无符号数,其值为1111
    b = 8;c=8;//b=c=1000
    b= b+a; //结果10111截断,b=0111
    c =c+a; //c=10111
  • 而进行取模运算时,结果值的符号位采用模运算式里第一个操作数的符号位。见下例:

    | 模运算表达式 | 结果 | 说明 |
    | ———————— | ———— | ———————————————————— |
    | 10%3 | 1 | 余数为1 |
    | 11%3 | 2 | 余数为2 |
    | 12%3 | 0 | 余数为0,即无余数 |
    | -10%3 | -1 | 结果取第一个操作数的符号位。所以余数为-1 |
    | 11%-3 | 2 | 结果取第一个操作数的符号位,所以余数为2 |

2.位运算符(除了~,其余都是双目运算符)

将两个操作数按对应位分别进行逻辑运算。

如果两个操作数的位宽不一样,则仿真软件会自动将短操作数向左扩展到两操作数位宽一致。

如果操作数的某一位为x时不一定产生x结果。

3.缩位运算符(单目运算符)

缩位运算符(Reduction Operators):又称缩减运算符,仅对一个操作数进行运算,按照从右到左的顺序依次对所有位进行运算,并产生一位的逻辑值

  • 缩减运算是对单个操作数进行或、与、非递推运算.最后的运算结果是1位的二进制数。

    1
    2
    3
    4
    5
    reg[3:0] B;
    reg C;
    assign C = &B;
    等价于:C = ((B[0]&B[1])&b[2])&B[3]

4.关系运算符

在进行关系运算时,如果声明的关系是假,则返回值是0;如果声明的关系是真,则返回值是1;如果操作数的某一位为x或z,则结果为不确定值。

5.等式运算符

逻辑相等:==

逻辑不等:!=

全等:===

z, x等位严格相等

非全等:!==

例:对于A=2’b1x 和 B=2’b1x,则

​ A == B结果为x, A===B结果为1

  • 相等运算符(==)和全等运算符(===)的区别:

    对于相等运算符,当参与比较的两个操作数逐位相等,其结果才为1,如果某些位是不定态或高阻值,其相等比较得到的结果就会是不定值。

    对于全等比较(===)是对这些不定态或高阻值的位也进行比较,两个操作数必须完全一致,其结果才为1,否则结果是0。

  • A== 1’bx ———— 当A为1’bx时,其结果仍为x;

  • A=== 1’bx ———— 当A为1’bx时,其结果仍为1;

6.逻辑运算符

逻辑运算符中,“&&”和“||”是双目运算符,它要求有两个操作数。

​ “!”是单目运算符,只要求一个操作数。

7.移位运算符

Verilog HDL的移位运算符只有左移和右移两个。其用法为:A>>n
A<<n; 表示把操作数A右移或左移n位,同时用0填补移出的位。

1
2
3
4
5
6
7
例如:
reg[3:0] start;
start = 1;
start = (start<<2);
//start = 0100;


8.位拼接运算符

在Verilog语言中有一个特殊的运算符:位拼接运算符{ }。

位拼接运算符{}可以把两个或多个信号的某些位拼接起来,表示一个整体信号进行运算操作。其使用方法如下:

{信号1的某几位,信号2的某几位,..,..,信号n的某几位}

对于一些信号的重复连接,可以使用简化的表示方式{n{A}}。这里A是被连接的对象,n是重复的次数。

1
2
3
4
5
6
例如:ain = 3’b010; bin = 4’b1100; 
{ain,bin} = 7’b0101100;
  {3 {2’b10} = 6’b101010;
位拼接还可以用嵌套的方式来表达:
{ a, 3'b101, {3{a,b}}}

注意:在位拼接表达式中不允许存在没有指明位数的信号。这是因为在计算机拼接信号的位宽的大小时必须知道其中每个信号的位宽。

9.条件运算符

三目运算符,对3个操作数进行运算,方式如下:

信号 = 条件?表达式1:表达式2

说明:当条件成立时,信号取表达式1的值,反之取表达式2的值。

例如:

assign out= (sel == 0) ? a : b;

例如:条件运算符描述的三态缓冲器

1
2
3
4
5
6
7
module likebufif ( in, en, out);
input in;
input en;
output out;
assign out = (en == 1) ? in : 'bz;
endmodule

10.优先级别