5.1 Verilog 程序描述方式

Verilog HDL模型可以是实际电路中不同级别的抽象,即对同一物理电路可以使用Verilog在不同层次上对其进行描述。

模块抽象级别分为:行为模块、结构模块。
行为模块:只是从行为和功能的角度来描述某个电路模块,如:1)、2)、 3)。
结构模块:只是从电路结构的角度来描述某个电路模块,如:4) 。

Verliog HDL支持数字电路系统的5种不同描述方法:
1). 系统级 (system)
2). 算法级 (algorithmic)
3). RTL级 (Register-Transfer-Level)
4). 门级 (gate-level)
5). 开关级 (switch-level)

5.2 门级结构描述

一个逻辑电路是由许多逻辑门和开关组成,它表示的是电路结构,它是电路布线的依据。

门级电路的描述是由基本逻辑门(and、or、not、xor等)互连而成的具有一定功能的电路模块。

一些常用门级结构以及关键字如下(Verliog HDL共提供26个门类型):

门类型及其关键字:
and ———— 与门
nand ———— 与非门
nor ———— 或非门
or ———— 或门
xor ———— 异或门
xnor ———— 异或非门
buf ———— 缓冲器
not ———— 非门

门与开关的说明语法可以用标准的声明语句格式和一个实例引用加以说明。其中门的类型是门声明语句所必需的,驱动能力延时可选项,门实例1是在本模块中引用的第一个该类型的门。

门申明语句:
<门的类型>[<驱动能力><延时>]<门实例1>[,<门实例2>,...,<门实例n>];

eg:
nand #10 nd1(a,data,clock,clear),
         nd2(b,data,clock,clear);

该例在模块中使用了2个名为nd1和nd2的与非门(nand),输入为data,clock,clear,输出分别为a和b,
输出与输入的延时为10个延时单位。  

Verilog模块引用:Verilog的模块可以在另一个模块中引用,便于程序的编写。在代码编写时,对端口的顺序没有要求,只要对各端口的连接准确即可。

模块引用语法:
被引模块名 被引模块实例名(.被引端口1(端口1)...  .被引端口n(端口n))

其中引用端口和端口的关系可以想象为电路中线路的连接,即被引端口1与端口1相连。其中被引模块实例名自己命名,一般为原模块名后+_inst。

eg:在test2.v中引用test1.v (文件中仅展示端口,只是为了更好的理解引用模块以及端口的连接)

文件test1.v
module test1(
input clk1,
input rst_n1,
input a,

output b
);
....
endmodule


文件test2.v
module test2(
input clk2,
input rst_n2,
input c,

output d
);
//对文件test1.v引用
test1 test1_inst(
.clk1         (clk2),
.rst_n1       (rst_n2),
.a            (c),

.b            (d)

);
....
endmodule

为了更加直观的展示上例中的各端口连接情况,下图给出了上例代码的RTL视图:
在这里插入图片描述
注:该例中的接口只是为了方便理解而随意设定,具体连接还要根据自己的代码调整。

5.3 行为级描述建模

首先对综合器进行一个简单的介绍,所谓综合器(synthesis tool)就是能够把行为级的Verliog模块自动转换为门及结构的工具

因此在对复杂电路设计时,相比于门级结构的描述,行为的描述会比较直观,因此使用行为级的描述来开始设计过程,然后通过仿真测试验证其功能,然后通过综合器对其进行转换,再次经过Verliog语言的仿真测试验证,便完成了前端的逻辑设计。

所谓行为描述就是使用比较抽象的高级程序语句(if...else等)、过程块语句结构(always和initial)、块语句(begin...end 等)对电路进行描述。

5.3.1 仿真测试的Verliog行为描述建模

为了对已设计的模块进行检验需要产生一系列信号输入到所设计的模块观察输出是否符合设计要求。因此需要编写测试模块也就是测试文件(testbench),一般对所测试模块的测试文件命名为:测试模块名_tb.v。

对上面的例中test1.v文件编写一个测试文件
产生一个每隔5个时间单位就反转一次的时钟信号,即产生一个时钟周期为10个时间单位的时钟信号。
`timescale 1ns/1ps   //仿真时间单位设置
module test1_tb;    //仿真测试模块名

test1 test1_inst(   //对被测试文件进行例化
             .clk1,
             .rst_n1,
             .a,

             .b
);
	reg			clk1; // 在下面代码中赋值的信号类型设置为reg型
	reg			rst_n;
	reg         a;
	wire        b;
	event end_first_pass;
	`define stim   #100 a=1'b  // 宏定义 stim可使程序简洁
initial begin      //初始状态设置                                         
  rst_n=0;         //复位信号设置为低电平
  #10 rst_n=1; 
end 
initial begin
  clk1=0;           //时钟信号设置为低电平
end
always begin        //每隔5个时间单位,时钟翻转一次                                       
#5 clk1=~clk1;                                           
end
initial 
begin 
repeat(2)   //重复2次下面的a的变化
   begin
   a=1'b0;
   `stim 0; //宏定义stim引用,等同于#100 a=1'b0;注意引用时要用`符号
   `stim 1;
 # 200 -> end_first_pass;
   end
   $ finish;  //仿真结束
end
endmodule

在上述程序中,event用来定义一个事件,以便以后的操作中触发这一事件。它的触发方式是:

# time (触发的时间) -> (事件名)

5.3.2 可综合性问题

行为级建模是最符合人类逻辑思维方式的描述角度,它不关心电路的具体结构,只关注算法。因此在综合器中不一定能够综合成电路的形式。

所谓逻辑综合就是将较高级抽象层次的描述自动转换成较低层次的描述即通过综合器把Verilog程序转换成标准的门级结构网标(并非真实具体的门级电路).
真实具体的电路还需要利用ASIC和FPGA制造厂商的布局布线工具,根据综合后生成的标准门级结构网标来生成。

目前门级和RTL级抽象描述的Verilog模块可以用综合器自动转换成标准的逻辑网表(即EDIF文件或电子设计接口文件)

5.4 用户定义的原语

用户定义的原语(User Defined Primitives,UDP):可以定义自己设计的基本逻辑元件的功能。

定义UDP的语法:
primitice 原件名(输出端口名,输入端口名1,输入端口名2....)
   output 输出端口名;
   input  输入端口名1,输入端口名2....;
   reg    输出端口名;
   initial begin  // 为时序电路内部寄存器赋初值,只允许0,1或X,默认值为X
   输出端口寄存器或时序逻辑内部寄存器赋初值(01或X);
   end
   table 
   //输入1     输入2      输入3     ....   :输出
    逻辑值     逻辑值     逻辑值     ....   :逻辑值;
    逻辑值     逻辑值     逻辑值     ....   :逻辑值;
    逻辑值     逻辑值     逻辑值     ....   :逻辑值;
    ....            ....                  :  ... ;
  endtable
endprimitive

UDP是用查表的方法来确定其输出的,

且只能有一个输出端并位于端口说明列表的第一项

最多允许又10个输入端;

所有端口变量必须是1位的(标量)

真值表中只允许出现0,1,X三种逻辑值,高阻态Z不允许出现;

只有输出端口才可以被定义为寄存器类型变量;

更多推荐