经过数字逻辑电路课程的学习,大家已对多路选择器(数据选择器)有了一定的认识。本节将通过建模2选1的数据选择器,简单介绍Verilog的各级建模语言。
一、行为级建模
使用always块来对数据选择器进行描述,其后面的小括号为该always块的敏感列表(sensitive list),只要sl或a或b其中有一个变化时,就执行其后的语句。需要注意的是,always块内的输出out,必须定义为reg型变量,因为该值在同一块内可能多次变化。
module muxtwo (out,a,b,sl);
input a,b,sl;
output out;
reg out;
always@ (sl or a or b)
if(!sl) out=a;
else out=b;
endmodule
RTL级语言为一种行为描述语言,只关心电路功能。
二、RTL建模
数据流建模,其标志多为并行的多个assign连续赋值语句,将对输入输出结点以及内部的各个结点的信号进行赋值定义操作。代码如下:
module muxtwo(out,a,b,sl);
input a,b,sl;
output out;
wire nsl,sela,selb;//定义内部结点
assign nsl= ~sl;
assign sela= a & nsl;
assign selb= b & sl;
assign out= sela | selb;
endmodule
三、门级建模
该电路的门级图如上,两者相不同的是,数据流建模代码只需要电路图中的结点名称,门级建模代码不仅需要各结点名称,而且需要各个具体的门器件名称,以便例化出该名称的对应门,并且描述该所需门的输入输出。代码如下:
module muxtwo(out,a,b,sl);
input a,b,sl;
output out;
wire nsl,sela,selb;
not u1(nsl,sl);
//not表示的是Verilog语言中自带的非门,该语句表示例化一个叫u1的非门,其输入是sl,输出是nsl。
and #1 u2(sela,a,nsl);
//#1表示的是该与门的输入到输出延迟1个时间单位。
//and表示的是例化调用Verilog语言中自带的与门,其输入是nsl和a,输出是sela。
and #1 u3(selb,b,sl);
or #2 u4(out,sela,selb);
//#2表示的是该或门的输入到输出延迟2个时间单位。
//or表示的是例化调用Verilog语言中自带的或门,其输入是sela和selb,输出是out。
endmodule
四、Verilog语法的主要特点之一——模块例化
模块例化是指,对低层次模块的调用。说白了就是在一个module......endmodule中需要使用其他的module......endmodule的功能,则通过调用模块名(调用方式见后)将该模块的功能移植到当前的主模块中。这个过程在主模块中只体现被调用(被例化)模块的输入和输出。使用原理就像C语言中的函数一样,调用时只关心函数的输入输出。同时,它其实是与C++中的“类与对象”同出一辙。
1. 门的例化:在刚刚的门级建模语句中,已经出现了对与门,或门,非门的例化,在此先介绍各个门在Verilog库中的名称:
与门 | and |
或门 | or |
非门 | not |
与非门 | nand |
或非门 | nor |
异或门 | xor |
同或门 | xnor |
缓冲器 | buf |
具体的,当拥有这些门后,该如何在语句中运用呢?下面是门的声明使用格式:
<门类型> [延时] <门的例化名> (<输出1,输出2......>,<输入1,输入2......>);
如:xor g1 (out[j],i0[j],i1[j]); //延时可以省略。
2. 模块的例化:模块的例化方式主要有两种:
①顺序例化: |
格式:<模块名> <例化名>(端口信号与被例化模块的定义内的端口顺序相接);
注:该种方式例化,若有不需连接的端口,需要跳过并空出该端口在模块定义中的位置,如:(<例化块的端口信号名1>,,<例化块的端口信号名3>),跳过了例化块的端口信号名2。 |
②名称例化: |
格式:<模块名> <例化名>(.<被例化模块的端口名1>(<例化块的端口信号名1>), .<被例化模块的端口名2>(<例化块的端口信号名2>),······); |
例如,全加器 full_adder (a,b,c_out,sum); 的例化应用:
//顺序例化
full_adder m0 (a[0],b[0],c_out[0],sum[0]);
full_adder m1 (a[1],b[1],c_out[1],sum[1]);
full_adder m2 (a[2],b[2],c_out[2],sum[2]);
full_adder m3 (a[3],b[3],c_out[3],sum[3]);
//名称例化
full_adder m0 (.a(a[0]),.b(b[0]),.c_out(c_out[0]),.sum(sum[0]));
full_adder m1 (.a(a[1]),.b(b[1]),.c_out(c_out[1]),.sum(sum[1]));
full_adder m2 (.a(a[2]),.b(b[2]),.c_out(c_out[2]),.sum(sum[2]));
full_adder m3 (.a(a[3]),.b(b[3]),.c_out(c_out[3]),.sum(sum[3]));
//两种例化方式结果等价
//若令a=[0110],即a[0]=0,a[1]=1,......,b=[1100],c_out=0,
//则最后的结果可推得为,sum[3]=0,sum[2]=0,sum[1]=1,sum[0]=0,c_out=1
五、testbench入门
testbench是一个模块的测试模块(激励块),就是给予所设计的模块一个或多个激励,测试所得到的结果是否满足所设定的功能。用来验证设计的模块的正确性。testbench为顶层模块,不会被其他模块例化,所以不需要有端口。
对于前面的二选一数据选择器,写一个testbench:
module test;
reg a,b,sl;
wire out;
muxtwo mux(out,a,b,sl); //例化数据选择器模块
initial
begin
a=0; b=1; sl=0;
#5 b=0;
#5 b=1; sl=1;
#5 a=1;
#5 $finish;//系统函数,表示结束当前仿真
end
initial
$monitor($time,"out=%b,a=%b,b=%b,sl=%b",out,a,b,sl);
//每次out,a,b,sl的值有变化时,打印out,a,b,sl的值。(系统函数monitor之后介绍)
endmodule
其输出结果应该为:
time | out | a | b | sl |
0 | 0 | 0 | 1 | 0 |
5 | 0 | 0 | 0 | 0 |
10 | 1 | 0 | 1 | 1 |
15 | 1 | 1 | 1 | 1 |
20 | finish | finish | finish | finish |