废话不多说,直接上菜,干饭
目录
Verilog HDL 快速入门FPGA超级干货第一季
1.模块
设计的数据流行为使用连续赋值语句进行描述 ; 时序行为使用过程结构描述。一个模块可以在另一个模块中使用。
说明部分用于定义不同的项,例如模块描述中使用的寄存器和参数。语句定义设计的功能和结构。说明部分和语句可以散布在模块中的任何地方;但是变量、寄存器、线网和参数等的说明部分必须在使用前出现。
建模一个半加器电路的模块的简单实例
module HalfAdder (A, B, Sum, Carry) ;
input A, B;
output Sum, Carry;
assign #2 Sum = A ^ B;
assign #5 Carry = A & B;
endmodule
模块的名字是HalfAdlder。 模块有4个端口: 两个输入端口A和B,两个输出端口S u m和C a rry。由于没有定义端口的位数, 所有端口大小都为1位;同时, 由于没有各端口的数据类型说明, 这四个端口都是线网数据类型。模块包含两条描述半加器数据流行为的连续赋值。从这种意义上讲,这些语句在模块中出现的顺序无关紧要,这些语句是并发的。每条语句的执行顺序依赖于发生在变量 A和B上的事件。
在模块中,可用下述方式描述一个设计:
- 数据流方式;
- 行为方式;
- 结构方式;
- 上述描述方式的混合。
2.时延
assign #2 Sum = A ^ B; //#2指2个时间单位。
使用编译指令将时间单位与物理时间相关联。这样的编译器指令需在模块描述前定义,
如下所示:
` timescale 1ns /100ps
此语句说明时延时间单位为 1 n s并且时间精度为100ps (时间精度是指所有的时延必须被限定在0 . 1 n s内)。 如果此编译器指令所在的模块包含上面的连续赋值语句 , #2 代表2 n s。如果没有这样的编译器指令 , Verilog HDL 模拟器会指定一个缺省时间单位。
3.数据流描述方式
用数据流描述方式对一个设计建模的最基本的机制就是使用连续赋值语句。在连续赋值
语句中,某个值被指派给线网变量。 连续赋值语句的语法为:
assign [delay] LHS_net = RHS_expression;
右边表达式使用的操作数无论何时发生变化 , 右边表达式都重新计算 , 并且在指定的时延后变化值被赋予左边表达式的线网变量。时延定义了右边表达式操作数变化与赋值给左边表达式之间的持续时间。 如果没有定义时延值 , 缺省时延为0。
下图显示了使用数据流描述方式对 2 - 4解码器电路的建模的实例模型。
代码如下(示例):
`timescale 1ns/1ns
module test(a,b,en,z);
input a,b,en;
output wire [3:0] z;
wire abar,bbar;
assign #1 abar = ~a; / / 语句 1。
assign #1 bbar = ~b; / / 语句 2。
assign #2 z[0] = ~(abar & bbar &en); / / 语句 3。
assign #2 z[1] = ~(abar & b &en); / / 语句 4。
assign #2 z[2] = ~(a & bbar & en); / / 语句 5。
assign #2 z[3] = ~(a & b &en); / / 语句 6。
endmodule
激励文件:
`timescale 1ns/1ns
module test_tb();
reg a,b,en;
wire [3:0] z;
initial
begin
en = 1'b0;
a = 1'b0;
b = 1'b0;
#5 en = 1'b1;
#10 a = 1'b1;
#5 b = 1'b1;
#5 a = 1'b0;
#10 b = 1'b0;
end
test test_inst
(
.en (en),
.a (a),
.b (b),
.z (z)
);
endmodule
以反引号开始的第一条语句是编译器指令, 编译器指令` t i m e s c a l e 将模块中所有时延
的单位设置为1 n s,时间精度为1 ns。例如,在连续赋值语句中时延值# 1和# 2分别对应时延1 ns和2 ns。
模块Decoder2x4有3个输入端口和1个4位输出端口。线网类型说明了两个连线型变量 Abar和B b a r (连线类型是线网类型的一种 )。此外,模块包含6个连续赋值语句。参见波形图。当 E N在第5 ns变化时,语句3、4、5和6执行。这是因为 E N是这些连续赋值语句中右边表达式的操作数。 Z[ 0 ]在第7 ns时被赋予新值0。当A在第15 ns变化时, 语 句1、5和6执行。执行语句5和6不影响Z[ 0 ]和Z[ 1 ]的取值。执行语句5导致Z[ 2 ]值在第17 ns变为0。执行语句1导致A b a r在第16 ns被重新赋值。由于A b a r的改变,反过来又导致Z[ 0 ]值在第18 n s变为1。
请注意连续赋值语句是如何对电路的数据流行为建模的;这种建模方式是隐式而非显式的建模方式。此外 ,连续赋值语句是并发执行的,也就是说各语句的执行顺序与其在描述中出现的顺序无关。
连续赋值语句实例
4.行为描述方式
设计的行为功能使用下述过程语句结构描述:
- initial语句:此语句只执行一次。
- always语句:此语句总是循环执行 , 或者说此语句重复执行。
只有寄存器类型数据(reg型)能够在这两种语句中被赋值。(换句话说,initial、always语句中必须是reg型数据被赋值),寄存器类型数据在被赋新值前保持原有值不变。所有的初始化语句和 a l w a y s语句在0时刻并发执行。
下例为a l w a y s语句对1位全加器电路建模的示例:
module FA_Seq(A, B, Cin, Sum, Cout) ;
input A, B, Cin ;
output Sum, Cout;
reg Sum, Cout;
reg T1, T2, T3;
always@ (A or B or Cin)
begin
Sum = (A ^ B) ^ Cin;
T1 = A & Cin;
T2 = B & Cin;
T3 = A & B;
Cout = (T1| T2) | T3;
end
endmodule
分析:
模块FA_Seq 有三个输入和两个输出。由于Sum、Cout、T1、T2和T3在always 语句中被赋值,它们被说明为 reg 类型(reg 是寄存器数据类型的一种)。always 语句中有一个与事件控制(紧跟在字符@ 后面的表达式)。相关联的顺序过程( begin - end对)。这意味着只要A、B或Cin 上发生事件,即A、B或Cin之一的值发生变化,顺序过程就执行。在顺序过程中的语句顺序执行,并且在顺序过程执行结束后被挂起。顺序过程执行完成后,always 语句再次等待A、B或Cin上发生的事件。
在顺序过程中出现的语句是过程赋值模块化的实例。模块化过程赋值在下一条语句执行前完成执行。过程赋值可以有一个可选的时延。
时延可以细分为两种类型:
1) 语句间时延: 这是时延语句执行的时延。
2) 语句内时延: 这是右边表达式数值计算与左边表达式赋值间的时延。
下面是语句间时延的示例:
Sum = (A ^ B) ^ Cin;
#4T1 = A & C i n;
在第二条语句中的时延规定赋值延迟 4个时间单位执行。就是说,在第一条语句执行后 等待 4个时间单位,然后执行第二条语句。下面是语句内时延的示例。
Sum = #3 (A^ B) ^ Cin;
这个赋值中的时延意味着首先计算右边表达式的值 , 等待3个时间单位,然后赋值给Sum。
如果在过程赋值中未定义时延,缺省值为 0时延,也就是说,赋值立即发生。
下面是initial语句的示例:
`timescale 1ns/1ns
module Test(Pop, Pid) ;
output Pop, Pid;
reg Pop, Pid;
initial
begin
Pop = 0; // 语句 1。
Pid = 0; // 语句 2。
Pop = #5 1; // 语句 3。
Pid = #3 1; // 语句 4。
Pop = #6 0; // 语句 5。
Pid = #2 0; // 语句 6。
end
endmodule
分析
这一模块产生如图所示的波形。i n i t i a l语句包含一个顺序过程。这一顺序过程在 0 ns时开始执行,并且在顺序过程中所有语句全部执行完毕后 , initial语句永远挂起。这一顺序过程包含带有定义语句内时延的分组过程赋值的实例。语句 1和2在0 ns时执行。第三条语句也在 0时刻执行,导致P o p 在第5 ns时被赋值。语句 4在第5 ns执行,并且P i d 在第8 ns被赋值。同样,P o p在14 ns被赋值0,P i d在第16 ns被赋值0。第6条语句执行后,i n i t i a l语句永远被挂起。
5.结构化描述形式
1) 内置门原语(在门级);
2) 开关级原语(在晶体管级);
3) 用户定义的原语(在门级);
4) 模块实例 (创建层次结构)。
通过使用线网(wire)来相互连接。
下面的结构描述形式使用内置门原语描述的全加器电路实例。
module FA_Str (A, B, Cin, Sum, Cout) ;
input A, B, Cin ;
output Sum, Cout;
wire S1, T1, T2, T3;
xor //内置门,变量类型由线网(wire)连接
X1 (S1, A, B) , // X1是门的门名称,S1是输出端,A,B是输入端。
X2 (Sum, S1, Cin) ;
and
A1 (T3, A, B) ,
A2 (T2, B, Cin) ,
A3 (T1, A, Cin) ,
or
O1 (Cout, T1, T2, T3) ; //Cout是输出端,T1, T2, T3是输入端;
endmodule
分析:
在这一实例中,模块包含门的实例语句,也就是说包含内置门 x o r、a n d和o r 的实例语句。门实例由线网类型变量 S 1、T 1、T 2和T 3互连。由于没有指定的顺序 , 门实例语句可以以任何顺序出现;图中显示了纯结构; x o r、a n d和o r是内置门原语; X 1、X 2、A 1等是实例名称。紧跟在每个门后的信号列表是它的互连;列表中的第一个是门输出,余下的是输入。例如, S 1与xor门实例X1的输出连接,而A和B与实例X 1的输入连接。
6.混合设计描述方式
在模块中,结构的和行为的结构可以自由混合。也就是说,模块描述中可以包含实例化的门、模块实例化语句or\and\xor、连续赋值语句assign以及 a l w a y s语句和i n i t i a l语句的混合。它们之间可以相互包含。来自 a l w a y s语句和i n i t i a l语句(切记只有寄存器类型数据可以在这两种语句中赋值)的值能够驱动门或开关,而来自于门或连续赋值语句(只能驱动线网)的值能够反过来用于触发a l w a y s语句和i n i t i a l语句。
下面是混合设计方式的1位全加器实例。
module FA_Mix (A, B, Cin, Sum, Cout) ;
input A,B, Cin;
output Sum, Cout;
reg Cout;
reg T1, T2, T3;
wire S1;
xor X1(S1, A, B ); // 门实例语句。
always
@ (A or B or Cin ) begin // always 语句。
T1 = A & Cin;
T2 = B & Cin;
T3 = A & B;
Cout = (T1| T2) | T3;
end
assign Sum = S1 ^ Cin; // 连续赋值语句。
endmodule
分析:
只要A或B上有事件发生,门实例语句即被执行。只要 A、B或Cin上有事件发生,就执行always 语句,并且只要S 1或C i n上有事件发生,就执行连续赋值语句。
7.设计模拟
Verilog HDL不仅提供描述设计的能力,而且提供对激励、控制、存储响应和设计验证的建模能力。激励和控制可用初始化语句产生。验证运行过程中的响应可以作为“变化时保存”或作为选通的数据存储。最后,设计验证可以通过在初始化语句中写入相应的语句自动与期望的响应值比较完成。
下面是测试模块Top的例子。
模块功能文件:
`timescale 1ns/1ns
module FA_Seq(A, B, Cin, Sum, Cout) ;
input A, B, Cin ;
output Sum, Cout;
reg Sum, Cout;
reg T1, T2, T3;
always@ (A or B or Cin)
begin
Sum = (A ^ B) ^ Cin;
T1 = A & Cin;
T2 = B & Cin;
T3 = A & B;
Cout = (T1| T2) | T3;
end
endmodule
激励仿真文件:
`timescale 1ns/1ns
module Top(); // 一个模块可以有一个空的端口列表。
reg PA, PB, PCi;
wire PCo, PSum;
// 正在测试的实例化模块:
FA_Seq F1(PA, PB, PCi, PSum, PCo); // 定位。使用位置关联方式
initial
begin
reg [3:0] Pal;
//需要4位, Pal才能取值8。
for (Pal = 0; Pal < 8; Pal = Pal + 1)
begin
{PA, PB, PCi} = Pal;
#5 $display (“PA, PB, PCi = %b%b%b”, PA, PB, PCi, “ : : : PCo, PSum=%b%b”, PCo, PSum) ;
end
end
endmodule
在测试模块描述中使用位置关联方式将模块实例语句中的信号与模块中的端口相连接。
也就是说,PA连接到模块FA_Seq的端口A,PB连接到模块FA_Seq的端口B,依此类推。注意初始化语句中使用了一个for循环语句,在PA、PB和PCi上产生波形。for 循环中的第一条赋值语句用于表示合并的目标。自右向左,右端各相应的位赋给左端的参数。初始化语句还包含有一个预先定义好的系统任务。系统任务 $display将输入以特定的格式打印输出。
系统任务display调用中的时延控制规定 $d i s p l a y任务在5个时间单位后执行。这 5个时间单位基本上代表了逻辑处理时间。即是输入向量的加载至观察到模块在测试条件下输出之间的延迟时间。
这一模型中还有另外一个细微差别。 P a l在初始化语句内被局部定义。为完成这一功能,
初始化语句中的顺序过程( b e g i n - e n d)必须标记。在这种情况下 , ONLY _ O N C E是顺序过程标记。如果在顺序过程内没有局部声明的变量,就不需要该标记。测试模块产生的波形如图显示。下面是测试模块产生的输出。
验证与非门交叉连接构成的 R S _ F F模块的测试模块
功能模块:
`timescale 1ns/1ns
module RS_FF(Q, Qbar, R, S) ;
output Q, Qbar; //wire型的线网
input R, S;
nand (Q, S, Qbar); //与非门,Q为输出,R、Qbar为输入;
nand (Qbar, R, Q);
endmodule
激励文件:
`timescale 1ns/1ns
module test();
reg TS, TR;
wire TQ, TQb; //wire型的线网
//测试模块的实例语句:
RS_FF NSTA(.Q(TQ),.S(TS),.R(TR),.Qbar(TQb)); //采用端口名相关联的连接方式。
// 加载激励:
initial
begin:ONLY_ONCE
TR = 0;
TS = 0;
#5 TS = 1;
#5 TS = 0;
TR = 1;
#5 TS = 1;
TR = 0;
#5 TS = 0;
#5 TR = 1;
end
//输出显示:
initial
$monitor ("At time %t ," ,$time,"TR = %b, TS=%b, TQ=%b, TQb= %b", TR, TS, TQ, TQb);
endmodule
模块Test是一个测试模块。测试模块中的 RS _ FF用实例语句说明其端口用端口名关联方式连接。在这一模块中有两条初始化语句。第一个初始化语句只简单地产生 TS和TR上的波形。
这一初始化语句包含带有语句间时延的程序块过程赋值语句。
第二条初始化语句调用系统任务 $m o n i t o r。这一系统任务调用的功能是只要参数表中指定的变量值发生变化就打印指定的字符串。产生的相应波形如图所示。请注意` t i m e s c a l e指令在时延上的影响。
验证与非门交叉连接构成的 R S _ F F模块的测试模块 (加时延后)
功能模块:
`timescale 1ns/1ns
module RS_FF(Q, Qbar, R, S) ;
output Q, Qbar; //wire型的线网
input R, S;
nand #1 (Q, S, Qbar); //与非门,Q为输出,R、Qbar为输入;
nand #1 (Qbar, R, Q);
endmodule
激励文件同上
RS_ FF模块描述了设计的结构。在门实例语句中使用门时延;例如,第一个实例语句中
的门时延为1个时间单位。该门时延意味着如果 R或Qbar假定在T时刻变化,Q将在T+ 1时刻获得计算结果值。
Verilog HDL 快速入门FPGA超级干货第二季
链接:
Verilog HDL 快速入门FPGA超级干货第二季