Module<模块名>(<端口列表>)
端口说明
参数定义
数据类型定义
连续赋值语句(assign)
过程块(initial和always)
-行为描述语句
底层模块实例
任务和函数
延时说明块
endmodule
module mux2_1(out,a,b,sel); //端口定义
output out; //输入输出列表
input a,b,sel;
not i5(sel_n,sel); //结构描述
and i6(sel_a,a,sel);
and i7(sel_b,sel_n,b);
or i8(out,sel_a,sel_b);
endmodule
下面逐行解释上例代码:
module mux2_1(out,a,b,sel); //端口定义
声明模块名及其端口列表。
output out;
指定端口out的方向为输出(output),output是用于声明端口方向的一个Verilog关键字。
input a,b,sel;
指定端口a,b,sel的方向为输入(input)。
not i5(sel_n,sel); //结构描述
生成一个Verilog内建基本门级元件not(非门)的实例(也叫做模块的调用,类似于C语言中的函数调用),该实例名为i5。第一个端口sel_n是输出端口,信号sel连接到该not元件的输入端口。
and i6(sel_a,a,sel);
and i7(sel_b,sel_n,b);
生成Verilog内建基本门级元件and(与门)的两个实例,实例名分别是i6和i7。
or i8(out,sel_a,sel_b);
生成Verilog内建基本门级元件or(或门)的实例i8。
endmodule
用关键字endmodule示意模块结束。
模块的描述方式又称为建模方式。大致可以按以下四类抽象级别来进行描述。
这是VerilogHDL最高抽象级别的描述方式。行为描述是通过行为语句来实现的,行为功能可使用下述过程语句结构描述。
只有寄存器类型数据能够在这两种语句中被赋值。寄存器型数据在被赋新值之前保持原有值不变。所有的 initial 语句和 always 语句在零时刻并发执行。
module cnt_4bit(q, clear, clock);
output [3:0] q;
input clear, clock;
reg [3:0] q;
always @(posedge clear or negedge clock)
begin
if (clear)
q = 4'd0;
else
q = (q + 1) % 16;
end
endmodule
模块 cnt_4bit 的输出端口 q 是一个 4bit 的位矢量,代表 4 根输出端口线 q[3],q[2],q[1],q[0]。由于该输出端口要在always语句中被赋值,所以它被定义为 reg 型(寄存器型)数据。
clear,输入端口,清零端。
clock,输入端口,时钟信号。
always语句中包含一个或事件控制,以及相关联的顺序过程(begin-end对)。此或事件控制的作用是当输入端口clear、clock上发生事件,即clear上升沿(posedge)到来或clock下降沿(negedge)到来时,就执行下面的顺序过程。
顺序语句执行完后被挂起,always语句再次等待clear、clock的值发生变化。
也称为RTL(寄存器传输级)描述方式。在这种描述方式下,设计者需要知道数据是如何在寄存器之间传输的以及将被如何处理。
在Verilog中数据流描述方式主要用来描述组合逻辑,具体由连续赋值语句“assign”来实现。下面仍以 4bit 二进制行波计数器为例,根据自顶向下(Top-Down)的设计方法,用数据流描述的方式来实现它。
第一步,设计顶层模块 cnt_4bit_1 ,代码包含了4个T触发器模块T_ff实例(模块调用)。
module cnt_4bit_1 (q, clear, clock);
output [3:0] q;
input clear,clock;
T_ff tff0(q[0], clear, clock);
T_ff tff1(q[1], clear, q[0]);
T_ff tff2(q[2], clear, q[1]);
T_ff tff3(q[3], clear, q[2]);
endmodule
第二步,设计T_ff触发器模块,该模块内又包含了其下一层的D触发器模块 edge_dff 实例。
module T_ff(q, clear, clock);
output q;
input clear, clock;
edge_dff ff1(q, , ~q, clear, clock);
endmodule
数据流操作符"~"代表对信号q取反,与Verilog内建基本门级元件not实现功能相同。
edge_dff ff1(q, , ~q, clear, clock);
其中的空格表示默认调用。
第三步,运用数据流描述语句设计最底层模块负边沿触发D触发器 edge_dff。
module edge_dff(q, qbar, d, clear, clock);
output q, qbar;
input d, clear, clock;
wire s, sbar, r, rbar, cbar;
//输入锁存器:锁存器是电平敏感的。而一个边沿敏感的触发器需要使用3个RS锁存器来实现
assign sbar = ~(rbar & s),
s = ~(sbar & cbar & ~clock),
r = ~(rbar & ~clock & s),
rbar = ~(r & cbar & d);
//输出锁存
assign cbar = ~clear,
qbar = ~(q & r & cbar);
endmodule
s、sbar、r、rbar、cbar被定义为 wire(连线)型数据,wire也是Verilog的关键字。可以理解为它们都是指导线。
在其后的代码中使用assign语句对模块的输入、输出端口和连线型数据之间的数据流传输关系进行了描述。
assign cbar = ~clear,
例如此行,可以理解为 cbar 导线上的电平为 clear 上的电平取反。体现到物理电路上,就如下图。
在这种描述方式下,模块是按照逻辑门和它们之间的互连来体现的。具体来说,门级描述方式就是指调用Verilog内建的基本门级元件来对硬件电路进行结构设计。
仍以行波计数器为例,其第三步的模块edge_dff的设计可以采用门级描述的方式来实现。
module edge_dff_1 (q, qbar, d, clear, clock);
output q, qbar;
input d, clear, clock;
wire cbar, clkbar, sbar, s, r, rbar;
not N1(cbar, clear),
N2(clkbar,clock);
nand NA1(sbar, rbar, s),
NA2(s, sbar, cbar, clkbar),
NA3(r, s, clbar, rbar),
NA4(rbar, r, cbar, d),
NA5(q, s, qbar),
NA6(qbar, q, cbar, r);
endmodule
内置门级元件有14种,分4类:
具体定义即调用会在单独的文章中写到。
也称为晶体管级描述方式,是Verilog最低抽象级别的描述方式。是调用Verilog内建的基本开关级元件来对硬件电路进行结构设计。
由于有了这一级别的描述方式,使得用户在MOS(Metal-Oxide Semiconductor,金属氧化物半导体)晶体管级别进行设计成为可能。
可以使用CMOS(互补金属氧化物半导体)设计nor门(或非门)。
module my_nor(out, a, b);
output out;
input a, b;
wire c;
supply1 pwr;
supply0 gnd;
pmos(c, pwr, b);
pmos(out, c, a);
nmos(out, gnd, a);
nmos(out, gnd, b);
endmodule
程序中的supply1、supply0是Verilog的关键字,分别定义电路的电源和地。pmos、nmos都是Verilog的基本开关级元件。
可以在同一个设计中混合使用这四种描述方式。从设计的成熟性上考虑,大多数模块都可以化为门级描述来实现。
通常来说,描述方式越抽象,设计的灵活性和技术独立性也越强,而越靠近开关级的描述方式,对技术的依赖性也越强,设计本身也就越不灵活,一个小改动就可能导致整个设计的大改变。
抽象程度:
行为级>数据流级>门级>开关级
灵活性:
行为级>数据流级>门级>开关级
一个设计一旦完成就应该对它进行测试。通过编写激励块,输入激励信号然后检测结果可以检测一个设计功能的正确性。
将激励块和设计块分开来是设计者应该养成的一个好习惯。
通常测试块也被称为测试凳(Test Bench),应用不同的测试凳可以对一个设计块进行全方位的测试。
激励信号的应用方式大致被分为两种:
一,在激励块内调用设计块,并且直接驱动设计块的信号。
二,在顶层的假模块内同时调用激励块和设计块,激励块和设计块仅通过接口相互作用。顶层模块的作用仅仅是为了调用设计块和激励块。
将行波计数器设计模块进行改写:
module cnt_4bit(q, clear, clock);
output [3:0] q;
input clear, clock;
reg [3:0] q;
always @(posedge clear or negedge clock)
begin
if (clear)
q = 4'd0;
else
q = (q + 1) % 16;
end
endmodule
编写激励块
module stimulus1;
reg clk;
reg reset;
wire [3:0] q;
cnt_4bit r1(q, reset, clk); //调用设计块cnt_4bit生成实例r1
//控制信号clk以驱动设计块,时钟周期设为10个时间单位
initial
clk = 1'b0; //设置clk到0
always
#5 clk = ~clk //clk每隔5个时间单位反转一次
//控制复位信号reset以驱动设计块,0~15为高,15~195为低,195~205为高,然后变低
initial
begin
reset = 1'b1;
#15 reset = 1'b0;
#180 reset = 1'b1;
#10 reset = 1'b0;
#20 $finish;
end
//监视输出
initial
$monitor($time,"output q = %d", q);
endmodule
接下来就可以运行仿真器,以检测设计块功能的正确性。
指从模块模板生成实际的电路结构对象,这样的电路结构对象被称为模块实例,模块调用也被称为实例化。
每一个实例都有它自己的名字、变量、参数和I/O接口。
被复制的电路块将一直存在,因为硬件电路结构不会随着时间而发生变化。
Verilog中,模块不能被嵌套定义,但可以包含其他模块的拷贝,即实例。
模块调用语句的基本格式:
<模块名><参数值列表><实例名> (<端口连接表>);
参考文献:
1.《精通Verilog HDL语言编程》,刘 波 编著,电子工业出版社,2007年5月第一次印刷。