Verilog学习笔记

参考夏宇闻的verilog书籍,以及verilog菜鸟笔记
基础:数电

一、绪论

1.1举个例子对verilog语言进行总体性概括

例1:描述一个adder的三位加法器
一个简单的verliog程序
看该简单的代码编写,咱们可以得到如下结论:

  1. verilog HDL 程序是由模块构成的。每个模块的内容都是嵌套于module和endmodule两个语句之间。每个模块实现特定的功能。模块之间可以进行像C语言一样的嵌套方式。这样整合成模块就有一个好处:大型数字电路可以分割成很多小的模型来实现特定的功能,最后通过顶层的模块调用小模块来实现整体的功能。
  2. 每个模块都要进行端口定义,并且输入输出口,然后对模块的功能进行行为的逻辑描述。
  3. Verilog HDL程序的书写自由,一行可以书写几个语句,一个语句也可以分写很多行,除了endmodule语句以外,每一个语句以及数据的定义都是要最后写上分号的。
  4. 关于注释:两种方法咯,和C相同。/……/or//……

1.2 解释一下模块的结构是什么?

在这里插入图片描述看上面这个图示,左边为程序模块,右边为电路图。左边的代码部分是对右边电路图的描述。观察左边其可以概括为四部分:端口定义、I/O说明、内部信号说明、功能定义。这部分全部夹在module和endmodule之间,也就是说以后的所有verilog代码都是如此结构构成的。
对上述代码简单做一个分析:block就是端口定义名字了。input与output就是I/O说明,后面两个语句就是逻辑功能的定义,非常简单。
模块的端口如何定义呢,如下图。
请添加图片描述I/O说明的格式如何定定义呢,两种方式。其一,直接定义法:
请添加图片描述

其二,也可以定义在模块端口中:
在这里插入图片描述
内部信号说明怎么定义呢,我们常用的往往是wire与reg两种变量。其定义的规则一般如下,对这方面具体的阐述放在后面介绍。
在这里插入图片描述

介绍完以上这些,最难的还是功能如何定义的问题。有三种方法,
其一,用”assign”语句,常用于组合逻辑电路。
其二,用实例元件,类似于C语言的调用库函数,这些元件都是标准的电路。
其三,用”always”语句,常用于组合逻辑与时序电路。
我这里只是做一个简单的阐述,具体怎么用,后面再说。

注意的一个点就是功能部分执行的先后顺序。上面讲的三个语句从宏观来看都是一起执行的。对,就是类似于并发。但是呢,always语句不一样,微观来看,他的内部是顺序执行的,比如它内部可能你会写if…else语句,这明显只能顺序执行。

Verilog 设计方法

Verilog 的设计多采用自上而下的设计方法(top-down)。即先定义顶层模块功能,进而分析要构成顶层模块的必要子模块;然后进一步对各个模块进行分解、设计,直到到达无法进一步分解的底层功能块。这样,可以把一个较大的系统,细化成多个小系统,从时间、工作量上分配给更多的人员去设计,从而提高了设计速度,缩短了开发周期。
在这里插入图片描述在这里插入图片描述Verilog 的设计流程,一般包括以下几个步骤:

需求分析

工作人员需要对用户提出的功能要求进行分析理解,做出电路系统的整体规划,形成详细的技术指标,确定初步方案。例如,要设计一个电子屏,需要考虑供电方式、工作频率、产品体积、成本、功耗等,电路实现采用 ASIC 还是选用 FPGA/CPLD 器件等。

功能划分

正确地分析了用户的电路需求后,就可以进行逻辑功能的总体设计,设计整个电路的功能、接口和总体结构,考虑功能模块的划分和设计思路,各子模块的接口和时序(包括接口时序和内部信号的时序)等,向项目组成员合理分配子模块设计任务。

文本描述(verilog代码编写)

可以用任意的文本编辑器,也可以用专用的 HDL 编辑环境,对所需求的数字电路进行设计建模,保存为 .v 文件。

功能仿真(前仿真)

对建模文件进行编译,对模型电路进行功能上的仿真验证,查找设计的错误并修正。

此时的仿真验证并没有考虑到信号的延迟等一些 timing 因素,只是验证逻辑上的正确性。

逻辑综合

综合(synthesize),就是在标准单元库和特定的设计约束的基础上,将设计的高层次描述(Verilog 建模)转换为门级网表的过程。逻辑综合的目的是产生物理电路门级结构,并在逻辑、时序上进行一定程度的优化,寻求逻辑、面积、功耗的平衡,增强电路的可测试性。

但不是所有的 Verilog 语句都是可以综合成逻辑单元的,例如时延语句。

布局布线

根据逻辑综合出的网表与约束文件,利用厂家提供的各种基本标准单元库,对门级电路进行布局布线。至此,已经将 Verilog 设计的数字电路,设计成由标准单元库组成的数字电路。

时序仿真(后仿真)

布局布线后,电路模型中已经包含了时延信息。利用在布局布线中获得的精确参数,用仿真软件验证电路的时序。单元器件的不同、布局布线方案都会给电路的时序造成影响,严重时会出现错误。出错后可能就需要重新修改 RTL(寄存器传输级描述,即 Verilog 初版描述),重复后面的步骤。这样的过程可能反复多次,直至错误完全排除。

FPGA/CPLD 下载或 ASIC 制造工艺生产

完成上面所有步骤后,就可以通过开发工具将设计的数字电路目标文件下载到 FPGA/CPLD 芯片中,然后在电路板上进行调试、验证。

如果要在 ASIC 上实现,则需要制造芯片。一般芯片制造时,也需要先在 FPGA 板卡上进行逻辑功能的验证。

二、 基本语法框架

基本参考夏宇闻的《verilog VHDL》。现在列出具体框架如下:

请添加图片描述

2.1 编译预处理

在Verilog HDL语言中,为了和一般的语句相区别,这些预处理命令以符号“ `”开头(注意这个符号是不同于单引号“ '”的)。这些预处理命令的有效作用范围为定义命令之后到本文件结束或到其它命令定义替代该命令之处。
Verilog HDL提供了以下预编译命令:

`accelerate,`autoexpand_vectornets,`celldefine,`default_nettype,`define,`else,
`endcelldefine,`endif,`endprotect,`endprotected,`expand_vectornets,`ifdef,`include,
`noaccelerate,`noexpand_vectornets , `noremove_gatenames , `noremove_netnames ,
`nounconnected_drive , `protect , `protecte , `remove_gatenames , `remove_netnames ,
`reset,`timescale,`unconnected_drive 

在这一小节里只对常用的define、include、`timescale进行介绍:

宏定义 `define:用一个指定的标识符(即名字)来代表一个字符串

`define 标识符(宏名) 字符串(宏内容)
如:`define signal string 
它的作用是指定用标识符signal来代替string这个字符串,在编译预处理时,把程序中在该命令以后
所有的signal都替换成string。这种方法使用户能以一个简单的名字代替一个长的字符串,也可以用
一个有含义的名字来代替没有含义的数字和符号,因此把这个标识符(名字)称为“宏名”,在编译预
处理时将宏名替换成字符串的过程称为“宏展开”。`define是宏定义命令。 
其他细节看书本57页。

“文件包含”处理`include:一个源文件可以将另外一个源文件的全部内容包含进来,即将另外的文件包含到本文件之中。
在这里插入图片描述

[1](1)文件aaa.v 
module aaa(a,b,out); 
	input a, b; 
	output out; 
	wire out; 
	assign out = a^b; 
endmodule 
(2)文件 bbb.v 
`include "aaa.v" 
module bbb(c,d,e,out); 
	input c,d,e; 
	output out; 
	wire out_a; 
	wire out; 
 	aaa aaa(.a(c),.b(d),.out(out_a)); 
	assign out=e&out_a; 
endmodule 
//相当于如下代码
module aaa(a,b,out); 
input a, b; 
output out; 
wire out; 
assign out = a ^ b; 
endmodule 
module bbb( c, d, e, out); 
input c, d, e; 
output out; 
wire out_a; 
wire out; 
 aaa aaa(.a(c),.b(d),.out(out_a)); 
assign out= e & out_a; 
endmodule
//更多关于include的细节关注课本,这里提一下include的功能

时间尺度 `timescale:跟在该命令后的模块的时间单位和时间精度。

`timescale 命令的格式如下: 
`timescale<时间单位>/<时间精度> 
在这条命令中,时间单位参量是用来定义模块中仿真时间和延迟时间的基准单位的。时间精度参量是
用来声明该模块的仿真时间的精确程度的,该参量被用来对延迟时间值进行取整操作(仿真前),因此
该参量又可以被称为取整精度。如果在同一个程序设计里,存在多个`timescale命令,则用最小的时
间精度值来决定仿真的时间单位。另外时间精度至少要和时间单位一样精确,时间精度值不能大于时
间单位值。
举例:
`timescale 1ns/1ps
在这个命令之后,模块中所有的时间值都表示是1ns的整数倍。这是因为在`timescale命令中,定义
了时间单位是1ns。模块中的延迟时间可表达为带三位小数的实型数,因为 `timescale命令定义时间
精度为1ps. 
`timescale 10us/100ns
在这个例子中,`timescale命令定义后,模块中时间值均为10us的整数倍。因为`timesacle 命令定
义的时间单位是10us。延迟时间的最小分辨度为十分之一微秒(100ns),即延迟时间可表达为带一位
小数的实型数。 
//更多关注书本

条件编译命令ifdef、else、endif :时希望对其中的一部分内容只有在满足条件才进行编译,也就是对一部分内容指定编译的条件。

1) `ifdef 宏名 (标识符) 
	程序段1 
	`else 
	程序段2 
	`endif 
它的作用是当宏名已经被定义过(用`define命令定义),则对程序段1进行编译,程序段2将被忽略;
否则编译程序段2,程序段1被忽略。其中`else部分可以没有,
2) `ifdef 宏名 (标识符)
	程序段1 
	`endif 
//更多关注书本

2.2 常量、变量、赋值

a.基本语法

Verilog 是区分大小写的。
格式自由,可以在一行内编写,也可跨多行编写。
每个语句必须以分号为结束符。空白符(换行、制表、空格)都没有实际的意义,在编译阶段可忽略。例如下面两中编程方式都是等效的。
不换行(不推荐)

wire [1:0]  results ;assign results = (a == 1'b0) ? 2'b01 : (b==1'b0) ? 2'b10 : 2'b11 

换行(推荐)

wire [1:0]  results ;
assign      results = (a == 1'b0) ? 2'b01 :
            (b==1'b0) ? 2'b10 :
                2'b11 ;
注释

Verilog 中有 2 种注释方式:

用 // 进行单行注释:

reg [3:0] counter ;  // A definition of counter register

用 /* 与 */ 进行跨行注释:

wire [11:0]  addr ;
/* 
Next are notes with multiple lines.
Codes here cannot be compiled.
*/
assign   addr = 12'b0 ;
标识符与关键字

标识符(identifier)可以是任意一组字母、数字、$ 符号和 _(下划线)符号的合,但标识符的第一个字符必须是字母或者下划线,不能以数字或者美元符开始。
另外,标识符是区分大小写的。
关键字是 Verilog 中预留的用于定义语言结构的特殊标识符。
Verilog 中关键字全部为小写。

reg [3:0] counter ; //reg 为关键字, counter 为标识符
input clk; //input 为关键字,clk 为标识符
input CLK; //CLK 与 clk是 2 个不同的标识符

b.verilog 数值表示

数值种类

Verilog HDL 有下列四种基本的值来表示硬件电路中的电平逻辑:

0:逻辑 0 或 "假"
1:逻辑 1 或 "真"
x 或 X:未知
z 或 Z:高阻

x 意味着信号数值的不确定,即在实际电路里,信号可能为 1,也可能为 0。

z 意味着信号处于高阻状态,常见于信号(input, reg)没有驱动时的逻辑结果。例如一个 pad 的 input 呈现高阻状态时,其逻辑值和上下拉的状态有关系。上拉则逻辑值为 1,下拉则为 0 。

整数数值表示方法

数字声明时,合法的基数格式有 4 中,包括:十进制('d 或 'D),十六进制('h 或 'H),二进制('b 或 'B),八进制('o 或 'O)。数值可指明位宽,也可不指明位宽。
指明位宽:

4'b1011         // 4bit 数值
32'h3022_c0de   // 32bit 的数值

其中,下划线 _ 是为了增强代码的可读性。
不指明位宽:
一般直接写数字时,默认为十进制表示,例如下面的 3 种写法是等效的:

counter = 'd100 ;  //一般会根据编译器自动分频位宽,常见的为32bit
counter = 100 ;
counter = 32'h64 ;

负数表示

通常在表示位宽的数字前面加一个减号来表示负数。例如:

-6'd15  
-15

-15 在 5 位二进制中的形式为 5’b10001, 在 6 位二进制中的形式为 6’b11_0001。(不知道怎么推出来的)

需要注意的是,减号放在基数和数字之间是非法的,例如下面的表示方法是错误的:

4'd-2 //非法说明
实数表示方法

实数表示方法主要有两种方式:

十进制:

30.123
6.0
3.0
0.001

科学计数法:

1.2e4         //大小为12000
1_0001e4      //大小为100010000
1E-3          //大小为0.001
字符串表示方法

字符串是由双引号包起来的字符队列。字符串不能多行书写,即字符串中不能包含回车符。Verilog 将字符串当做一系列的单字节 ASCII 字符队列。例如,为存储字符串 “www.runoob.com”, 需要 14*8bit 的存储单元。例如:

reg [0: 14*8-1]       str ;
initial begin
    str = "www.runoob.com";
end  

c.verilog数据类型

Verilog 最常用的 2 种数据类型就是线网(wire)与寄存器(reg),其余类型可以理解为这两种数据类型的扩展或辅助。

线网(wire)

wire 类型表示硬件单元之间的物理连线,由其连接的器件输出端连续驱动。如果没有驱动元件连接到 wire 型变量,缺省值一般为 “Z”。举例如下:

wire   interrupt ;
wire   flag1, flag2 ;
wire   gnd = 1'b0 ;

线网型还有其他数据类型,包括 wand,wor,wri,triand,trior,trireg 等。这些数据类型用的频率不是很高,这里不做介绍。

寄存器(reg)

寄存器(reg)用来表示存储单元,它会保持数据原有的值,直到被改写。声明举例如下:

reg    clk_temp;
reg    flag1, flag2 ;

例如在 always 块中,寄存器可能被综合成边沿触发器,在组合逻辑中可能被综合成 wire 型变量。寄存器不需要驱动源,也不一定需要时钟信号。在仿真时,寄存器的值可在任意时刻通过赋值操作进行改写。例如:

reg rstn ;
initial begin
    rstn = 1'b0 ;
    #100 ;
    rstn = 1'b1 ;
end
向量

当位宽大于 1 时,wire 或 reg 即可声明为向量的形式。例如:

reg [3:0]      counter ;    //声明4bit位宽的寄存器counter
wire [32-1:0]  gpio_data;   //声明32bit位宽的线型变量gpio_data
wire [8:2]     addr ;       //声明7bit位宽的线型变量addr,位宽范围为8:2
reg [0:31]     data ;       //声明32bit位宽的寄存器变量data, 最高有效位为0

对于上面的向量,我们可以指定某一位或若干相邻位,作为其他逻辑使用。例如:

wire [9:0]     data_low = data[0:9] ;
addr_temp[3:2] = addr[8:7] + 1'b1 ;

Verilog 支持可变的向量域选择,例如:

reg [31:0]     data1 ;
reg [3:0]      byte1 [7:0];
integer j ;
always@* begin
    for (j=0; j<=3;j=j+1) begin
        byte1[j] = data1[(j+1)*4-1 : j*4];
        //把data1[7:0]…data1[31:24]依次赋值给byte1[0][7:0]…byte[3][7:0]
    end
end

Verillog 还支持指定 bit 位后固定位宽的向量域选择访问。

[bit+: width] : 从起始 bit 位开始递增,位宽为 width。
[bit-: width] : 从起始 bit 位开始递减,位宽为 width。 
//下面 2 种赋值是等效的
A = data1[31-: 8] ;
A = data1[31:24] ;

//下面 2 种赋值是等效的
B = data1[0+ : 8] ;
B = data1[0:7] ;

对信号重新进行组合成新的向量时,需要借助大括号。例如:

wire [31:0]    temp1, temp2 ;
assign temp1 = {byte1[0][7:0], data1[31:8]};  //数据拼接
assign temp2 = {32{1'b0}};  //赋值32位的数值0 

整数,实数,时间等数据类型实际也属于寄存器类型。

整数(integer)

整数类型用关键字 integer 来声明。声明时不用指明位宽,位宽和编译器有关,一般为32 bit。reg 型变量为无符号数,而 integer 型变量为有符号数。例如:

reg [31:0]      data1 ;
reg [3:0]       byte1 [7:0]; //数组变量,后续介绍
integer j ;  //整型变量,用来辅助生成数字电路
always@* begin
    for (j=0; j<=3;j=j+1) begin
        byte1[j] = data1[(j+1)*4-1 : j*4];
        //把data1[7:0]…data1[31:24]依次赋值给byte1[0][7:0]…byte[3][7:0]
        end
end

此例中,integer 信号 j 作为辅助信号,将 data1 的数据依次赋值给数组 byte1。综合后实际电路里并没有 j 这个信号,j 只是辅助生成相应的硬件电路。

实数(real)

实数用关键字 real 来声明,可用十进制或科学计数法来表示。实数声明不能带有范围,默认值为 0。如果将一个实数赋值给一个整数,则只有实数的整数部分会赋值给整数。例如:

real        data1 ;
integer     temp ;
initial begin
    data1 = 2e3 ;
    data1 = 3.75 ;
end
 
initial begin
    temp = data1 ; //temp 值的大小为3
end
时间(time)

Verilog 使用特殊的时间寄存器 time 型变量,对仿真时间进行保存。其宽度一般为 64 bit,通过调用系统函数 $time 获取当前仿真时间。例如:

time       current_time ;
initial begin
       #100 ;
       current_time = $time ; //current_time 的大小为 100
end
数组

在 Verilog 中允许声明 reg, wire, integer, time, real 及其向量类型的数组。

数组维数没有限制。线网数组也可以用于连接实例模块的端口。数组中的每个元素都可以作为一个标量或者向量,以同样的方式来使用,形如:<数组名>[<下标>]。对于多维数组来讲,用户需要说明其每一维的索引。例如:

integer          flag [7:0] ; //8个整数组成的数组
reg  [3:0]       counter [3:0] ; //由4个4bit计数器组成的数组
wire [7:0]       addr_bus [3:0] ; //由4个8bit wire型变量组成的数组
wire             data_bit[7:0][5:0] ; //声明1bit wire型变量的二维数组
reg [31:0]       data_4d[11:0][3:0][3:0][255:0] ; //声明4维的32bit数据变量数组

下面显示了对数组元素的赋值操作:

flag [1]   = 32'd0 ; //将flag数组中第二个元素赋值为32bit的0值
counter[3] = 4'hF ;  //将数组counter中第4个元素的值赋值为4bit 十六进制数F,等效于counter[3][3:0] = 4'hF,即可省略宽度;
assign addr_bus[0]        = 8'b0 ; //将数组addr_bus中第一个元素的值赋值为0
assign data_bit[0][1]     = 1'b1;  //将数组data_bit的第1行第2列的元素赋值为1,这里不能省略第二个访问标号,即 assign data_bit[0] = 1'b1; 是非法的。
data_4d[0][0][0][0][15:0] = 15'd3 ;  //将数组data_4d中标号为[0][0][0][0]的寄存器单元的15~0bit赋值为3

虽然数组与向量的访问方式在一定程度上类似,但不要将向量和数组混淆。向量是一个单独的元件,位宽为 n;数组由多个元件组成,其中每个元件的位宽为 n 或 1。它们在结构的定义上就有所区别。

存储器

存储器变量就是一种寄存器数组,可用来描述 RAM 或 ROM 的行为。例如:

reg               membit[0:255] ;  //256bit的1bit存储器
reg  [7:0]        mem[0:1023] ;    //1Kbyte存储器,位宽8bit
mem[511] = 8'b0 ;                  //令第512个8bit的存储单元值为0
参数

参数用来表示常量,用关键字 parameter 声明,只能赋值一次。例如:

parameter      data_width = 10'd32 ;
parameter      i=1, j=2, k=3 ;
parameter      mem_size = data_width * 10 ;

但是,通过实例化的方式,可以更改参数在模块中的值。此部分以后会介绍。

局部参数用 localparam 来声明,其作用和用法与 parameter 相同,区别在于它的值不能被改变。所以当参数只在本模块中调用时,可用 localparam 来说明。

字符串

字符串保存在 reg 类型的变量中,每个字符占用一个字节(8bit)。因此寄存器变量的宽度应该足够大,以保证不会溢出。

字符串不能多行书写,即字符串中不能包含回车符。如果寄存器变量的宽度大于字符串的大小,则使用 0 来填充左边的空余位;如果寄存器变量的宽度小于字符串大小,则会截去字符串左边多余的数据。例如,为存储字符串 “run.runoob.com”, 需要 14*8bit 的存储单元:

reg [0: 14*8-1]       str ;
initial begin
    str = "run.runoob.com";
end  

有一些特殊字符在显示字符串中有特殊意义,例如换行符,制表符等。如果需要在字符串中显示这些特殊的字符,则需要在前面加前缀转义字符 \ 。例如下表所示:

在这里插入图片描述

d.表达式

在这里插入图片描述

2.3 描述一个过程的准备

assign
全加器(例子)
时延
惯性时延
initial语句
always 语句
阻塞赋值
非阻塞赋值
使用非阻塞赋值避免竞争冒险(暂时不看-数电没学完)

2.4 去描述一个过程吧

时延控制
边沿触发事件控制
电平敏感事件控制

三、不同抽象级别的verilog模型

Verilog模型可以是实际电路不同级别的抽象。这些抽象的级别和它们对应的模型
类型共有以下五种:

  1. 系统级(system)
  2. 算法级(algorithmic)
  3. RTL级(RegisterTransferLevel):
  4. 门级(gate-level):
  5. 开关级(switch-level)

对于数字系统的逻辑设计工程师而言,熟练地掌握门级、RTL级、算法级、系统级是非
常重要的。暂时可以不用了解开关级别描述。

2.1门级结构描述

2.1.1.与非门、或门和反向器等及其说明语法

Verilog HDL中有关门类型的关键字共有26个之多,在本教材中我们只介绍最基本的八个。

and 与门 
nand 与非门 
nor 或非门 
or 或门 
xor 异或门 
xnor 异或非门 
buf 缓冲器 
not 非门

门声明语句的格式,如下:

<门的类型>[<驱动能力><延时>]<门实例1>[<门实例2>,…<门实例n>];
/*门的类型是门声明语句所必需的,它可以是Verilog HDL语法规定的26种门类型中的任意一种。驱动能
力和延时是可选项,可根据不同的情况选不同的值或不选。门实例1是在本模块中引用的第一个这种类
型的门,而门实例n是引用的第n个这种类型的门。有关驱动能力的选项我们在以后的章节里再详细加以
介绍.最后我们用一个具体的例子来说明门类型的引用: */
nand #10 nd1(a,data,clock,clear);
//引用了一个与非门,名字叫nd1,输入为data,clock,clear,输出为a,输出与输入的延时为10个时间单位。
基本的程序结构:
module<模块名><端口列表><定义>
<模块条目>
endmodule
2.1.2 (补充)数电上的行为描述和结构描述介绍


数电书本上内容:
两种描述方式,其一是行为描述方式,其二是结构描述方式
行为描述方式:通过行为语句来描述电路要实现的功能,表示输出与输入之间转换的行为,不涉及具体的结构,是一种行为建模的描述方式。对下面二选一数据选择器进行行为描述。
在这里插入图片描述

module mux_2_to_1(a,b,out,outbar,sel);
	input a,b,sel;
	output out,outbar;
	assign out =sel?a:b;
	assign outbar =-out;
endmodule 

结构描述方式:通过对组合电路的各个子模块间相互连接关系的描述来说明电路的组成。各个模块还可以对其他模块进行调用,也就是模块的实例化。其中调用模块成为层次结构中的上级模块,被调用模块成为下级模块。任何硬件电路都是一个一级一级不同层次的若干单元组成。
在这里插入图片描述

module muxgate(a,b,out,outbar,sel);
	input a,b,sel;
	output out,outbar;
	wire out1,out2,selb;//定义内部三个连接点
		and a1(out1,a,sel);
		not i1(selb,sel);
		and a2(out,out1,out2);
		or o1(out,out1,out2);
		assign outbar =-out;
	endmodule 


在这里插入图片描述

2.1.3 用门级结构描述D触发器
module flop(data,clock,clear,q,qb);
	input data,clock,clear;
	output q.qb;
	nand #10 nd1(a,data,clock,clear),
				  nd2(b,ndata,clock),
				  nd4(d,c,b,clear),
				  nd5(e,c,nclock),
				  nd6(f,d,nclock),
				  nd8(qb,q,f,clear);
	nand #9 nd3(c,a,d),
				 nd7(q,e,qb);
	not #10 ivl(ndata,data),
				 iv2(nclock,clock);
endmodule
//综上,门级描述有点像结构描述。
2.1.4 由已经设计成的模块来构成更高一层的模块

引用的方法:只需在前面写上已编的模块名,紧跟着写上引用的实例名,按顺序写上
实例的端口名即可,见下面的两个例子:

1) flop f1op_d( d1, clk, clrb, q, qn); 
2) flop flop_d (.clock(clk),.q(q),.clear(clrb),.qb(qn),.data(d1));

这两个例子都表示实例f1op_d引用已编模块flop。从上面的两个例子可以看出引用时f1op_d的端口信号与flop的端口对应有两种不同的表示方法。模块的端口名可以按序排列也可以不必按序排列,如果模块的端口名按序排列,只需按序列出实例的端口名。(见例1)。如果模块的端口名不按序排列,则实例的端口信号和被引用模块的端口信号必需一一列出(见例2)。

举个例子:用上面构成的触发器。来描述一个四位的寄存器。
在这里插入图片描述

module hardreg(d,clk,clrb,q);
input		clk,clrb;
input[3:0]  d;
output[3:0] q;

flop	 f1(d[0],clk,clrb,q[0],),
		 f2(d[1],clk,clrb,q[1],),
		 f3(d[2],clk,clrb,q[2],),
		 f4(d[3],clk,clrb,q[3],);
endmodule
/*在上面这个结构描述的模块中,hardreg定义了模块名;f1,f2,f3,f4分别为图5中的各个基本部件,而
其后面括号中的参数分别为图5中各基本部件的输入输出信号。请注意当f1到f4实例引用已编模块flop
时,由于不需要flop端口中的qb口,故在引用时把它省去,但逗号仍需要留着。 */
2.1.5 用户定义的原语(UDP)

用户定义的原语是从英语User Defined Primitives直接翻译过来的,在Verilog HDL 中我们常用它的缩写UDP来表示。
利用UDP用户可以定义自己设计的基本逻辑元件的功能,也就是说,可以利用UDP来定义有自己特色的用于仿真的基本逻辑元件模块并建立相应的原语库。这样,我们就可以与调用Verilog HDL基本逻辑元件同样的方法来调用原语库中相应的元件模块来进行仿真。由于UDP是用查表的方法来确定其输出的,用仿真器进行仿真时,对它的处理速度较对一般用户编写的模块快得多
与一般的用户模块比较,UDP更为基本,它只能描述简单的能用真值表表示的组合或时序逻辑

定义UDP的语法:

UDP模块的结构与一般模块类似,只是不用module而改用primitive关键词开始,不用endmodule而改用endprimitive关键词结束。
//例子:
primitive 元件名(输出端口名,输入端口名1,输入端口名2,…) 
 	output 输出端口名; 
	input 输入端口名1, 输入端口名2,…; 
	 reg 输出端口名;  
		 initial 
			 begin 
 				输出端口寄存器或时序逻辑内部寄存器赋初值(01,或 X); 
		 end 
 	table 
	 //输入1 输入2 输入3: 输出 
	 逻辑值 逻辑值 逻辑值 … : 逻辑值 ; 
	 逻辑值 逻辑值 逻辑值 … : 逻辑值 ; 
	 逻辑值 逻辑值 逻辑值 … : 逻辑值 ; 
	 … … … … :; #怎么说这个table
	endtable 
endprimitive 
注意点: 
1) UDP只能有一个输出端,而且必定是端口说明列表的第一项。 
2) UDP可以有多个输入端,最多允许有10个输入端。 
3) UDP所有端口变量必须是标量,也就是必须是1位的。 /#标量是什么意思?
4) 在UDP的真值表项中,只允许出现01、X三种逻辑值,高阻值状态Z是不允许出现的。 
5) 只有输出端才可以被定义为寄存器类型变量。 
6) initial语句用于为时序电路内部寄存器赋初值,只允许赋01、X三种逻辑值,缺省值为X。 
上面多多少少会有一些不理解,但是不着急,后面会慢慢深入的学习。

2.2 Verilog HDL的行为描述建模

2.2.1 仅用于产生仿真测试信号的verilog HDL行为级描述建模

为了对已设计的模块进行检验往往需要产生一系列信号作为输出,输入到已设计的模块,并检查已设计模块的输出,看它们是否符合设计要求。这就要求我们编写测试模块,也称作测试文件,常用带.tf扩展名的文件来描述测试模块。

ps:数电的时序逻辑电路不要落下

  下面的Verilog HDL行为描述模型用于产生时钟信号,以验证电路功能。其输出的仿真信号共有2个,分别是时钟clk、复位信号reset。初始状态时,clk置为低电平,reset为高电平。reset信号输出一个复位信号之后,维持在高电平。这一功能可利用下面的语句来实现: 
		
module gen_clk ( clk, reset); 
	output clk; 
	output reset; 
	reg clk, reset; 
	initial 
		begin 
			reset = 1; //initial state 
			clk=0; 
			#3 reset = 0;  //仿真时刻延迟第3秒的时候reset被置为0
			#5 reset = 1; //仿真时刻延迟第5秒的时候reset被置为1
		end 
 always #5 clk = ~clk; //以后每隔5个时间单位,时钟就翻转一次,这一功能可利用下面的语句来实现:从而该模块所产生的时钟的周期为10个时间单位。
 
endmodule

用这种方法所建立的模型主要用于产生仿真时测试下一级电路所需的信号,如下一级电路有输出反馈到上一级电路,并对上一级电路有影响时,也可以在这个模型中再加入输入信号,用于接收下一级电路的反馈信号。可以利用这个反馈信号再在这个模块中编制相应的输出信号,这样就比用简单的波形描述信号能更好地仿真实际电路。
举例:对上面的思维寄存器进行行为级别的测试仿真
在这里插入图片描述

在这里插入图片描述

用门级别描述D触发器
module flop(data,clock,clear,q,qb);
	input data,clock,clear;
	output q.qb;
	nand #10 nd1(a,data,clock,clear),
				  nd2(b,ndata,clock),
				  nd4(d,c,b,clear),
				  nd5(e,c,nclock),
				  nd6(f,d,nclock),
				  nd8(qb,q,f,clear);
	nand #9 nd3(c,a,d),
				 nd7(q,e,qb);
	not #10 ivl(ndata,data),
				 iv2(nclock,clock);
endmodule
//引用上面已设计的模块flop,用它构成一个四位寄存器
module hardreg(d,clk,clrb,q);
input		clk,clrb;
input[3:0]  d;
output[3:0] q;

flop	 f1(d[0],clk,clrb,q[0],),
		 f2(d[1],clk,clrb,q[1],),
		 f3(d[2],clk,clrb,q[2],),
		 f4(d[3],clk,clrb,q[3],);
endmodule
/*我们再举一个简单的例子,即编制上面完成的设计(即hardreg模块)的测试文件。这个测试文件
不仅要包括时钟信号(clock)、数据(data[3:0])、清零信号(clearb)的变化,还需引用四位寄存器.(hardreg)模块,以观测各种组合信号输入到该四位寄存器(hardreg)模块后,它的输出(q[3:0])的变
化。这个测试文件完整的源程序如下: */
module hardreg_top; 
	reg clock, clearb; 
	reg [3:0] data;
	wire [3:0] qout; 
	`define stim #100 data=4'b //宏定义 stim,可使源程序简洁 
	event end_first_pass; //定义事件end_first_pass 
	hardreg reg_4bit (.d(data), .clk(clock), .clrb(clearb), .q(qout)); 
/********************************************************************************** 
把本模块中产生的测试信号data、clock、clearb输入实例reg_4bit以观察输出信号qout.实例
reg_4bit引用了hardreg 
**********************************************************************************/ 
initial 
begin 
clock = 0; 
clearb = 1; 
end 
 
always #50 clock = ~clock; 
always @(end_first_pass) 
clearb = ~clearb; 
always @(posedge clock) 
$display("at time %0d clearb= %b data= %d qout= %d", $time, clearb, data, qout); 
/***************************************************** 
 类似于C语言的 printf 语句,可打印不同时刻的信号值 
******************************************************/ 
initial 
begin 
repeat(2) //重复两次产生下面的data变化
begin 
data=4'b0000; 
`stim0001; 
/*************************************************************** 
 宏定义stim引用,等同于 #100 data=4'b0001;。注意引用时要用 `符号。 
****************************************************************/ 
`stim0010; 
`stim0011; 
`stim0100; 
`stim0101; 
. 
. 
. 
`stim1110; 
`stim1111; 
end 
#200> end_first_pass;
/*********************************************** 
延迟200个单位时间,触发事件end_first_pass 
************************************************/ 
$finish; //结束仿真 
end 
endmodule 
  • 9
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值