逻辑功能是同时执行的。即把这3项写到一个Verilog模块文件中去,它们的次序不会影响逻辑实现的功能,是同时进行的即并发的。然而,在“always”模块内,逻辑是按照指定的顺序执行的。“always”块中的语句称为“顺序语句”,因为它们是顺序执行的。而“always”模块之间是同时执行的。看我们编的抢答器例子就会明白,一个“always”内的if…else if…必须顺序执行,否则其功能没有任何意义;三个“always”块之间没有先后顺序。为了能实现我们所要求的功能,“always”模块内部的语句将按照书写的顺序执行。 可以看出Verilog模块中所有过程块、连续赋值语句、实例引用表示的是一种通过变量名互相连接的关系,出现的先后次序不影响程序功能。
在Verilog HDL设计中容易犯的一个通病是由于不正确使用语言,生成了并不想要的锁存器。举一个简单的错误例子,原因是在“always”块中不正确使用if语句。
[例1 ] 有锁存器 always@(al or d) begin if(al) q<=d; end [例2]无锁存器 always@(al or d) begin if(al) q<=d; else q<=0; end
检查一下前边的“always”块,if语句保证了只有当al=1时,q才取d的值。这段程序没有写出al=0时的结果,那么当al=0时会怎么样呢?在“always”块内,如果在给定的条件下变量没有赋值,这个变量将保持原值,也就是说会生成一个锁存器!如果设计人员希望当al=0时q的值为0,else项就必不可少了,请注意看例2的“always”块,整个Verilog程序模块综合起来后,“always”块对应的部分不会生成锁存器。
[例3] 有锁存器
always @(sel[1:0] or a or b) case(sel[1:0]) 2’b00: q<=a; 2’b11: q<=b; endcase [例4]无锁存器
always @(sel[1:0] or a or b) case(sel[1:0]) 2’b00: q<=a; 2’b11: q<=b; default: q<=’b0; endcase
Verilog HDL程序另一种偶然生成锁存器是在使用case语句时缺少default项的情况下发生的。Case语句的功能是,在某个信号(本例中的sel)取不同的值时,给另一个信号(本例中的q)赋不同的值。注意看例3。如果sel=00时q取a值,而sel=11时q取b的值。这个例子中表述不清楚的是:如果sel取00和11以外的值时q将被赋予什么值?程序是用Verilog HDL写的,即默认为q保持原值,这就会自动生成锁存器。例4很明确,程序中的case语句有default项,指明了如果sel不取00或11时,编译器或仿真器应赋给q的值。程序所示情况下q赋为0,因此不需要锁存器。以上就是怎样避免偶然生成锁存器的错误。
16
如果用到if语句,最好写上else项。如果用case语句,最好写上default项。遵循上面两条原则,就可以避免发生这种错误,使设计者更加明确设计目标,同时也增强了Verilog程序的可读性。
3. 稳态电路的设计,在这个例子中也可以看到应用在模块中的实例元件的定义方法。
要求:在001181092-Xilinx实验仪上完成一个键对三个发光管的控制,使它们在闪、顺序亮、全亮三种状态间变化。
程序设计:文件名exe2.v。
module exe2(Clock,Clk1,Clr1,Clr2,Out1,Out2,Out3); input Clock; input Clk1; input Clr1; input Clr2; output Out1; output Out2; output Out3;
wire Clk2,QT1,NQT1,QT2,NQT2,QF1,NQF1,QF2,NQF2; wire Mout1,Mout2,Mout3,Mout4,Mout5; genclk gc(Clock,Clk2);
fd3 FD1(Clk1,Clr1,Clr1,QT1,NQT1,QT2,NQT2); fd4 FD2(Clk2,Clr2,QF1,NQF1,QF2,NQF2);
nand nand1(Mout1,NQT1,NQT2); nand nand2(Mout2,QT2,Clk2); nand nand3(Mout3,QT1,NQF2,NQF1); nand nand4(Mout4,QT1,NQF2,QF1);
nand nand5(Mout5,QT1,QF2,NQF1);
and and1(Out1,Mout1,Mout2,Mout3); and and2(Out2,Mout1,Mout2,Mout4); and and3(Out3,Mout1,Mout2,Mout5);
endmodule
module fd3(Clk,Clr,K,Q1,NQ1,Q2,NQ2);
input Clk,Clr,K; output Q1,NQ1,Q2,NQ2;
JK jk1(Clk,Q2,K,Clr,Q1,NQ1); JK jk2(Clk,NQ1,K,Clr,Q2,NQ2);
endmodule
//产生四分频的时钟
//调用实现三分频子程序 //调用实现四分频子程序
//五个与非门
//实现三分频子程序
//调用JK触发器子程序
17
module fd4(Clk,Clr,Q1,NQ1,Q2,NQ2); //实现四分频子程序 input Clk,Clr;
output Q1,NQ1,Q2,NQ2;
JK jk3(Clk,Clr,Clr,Clr,Q1,NQ1); JK jk4(NQ1,Clr,Clr,Clr,Q2,NQ2);
//调用JK触发器子程序
endmodule
module genclk(clock,clk); input [0:0] clock; output [0:0] clk;
reg [21:0] buffer; assign clk=buffer[21];
always@(posedge clock) begin
buffer=buffer+1; end
endmodule
module JK(Clk,J,K,Clr,Q,NQ); input Clk,J,K,Clr; output Q,NQ; reg Q;
assign NQ=!Q;
always@(negedge Clk or negedge Clr) begin if(Clr==0) Q=0; else
case ({J,K}) 0:Q=Q; 1:Q=0; 2:Q=1;
3:Q=NQ; endcase end
//产生大约5Hz的时钟信号 //实现JK触发器子程序 //异步清零
//实现JK触发器的基本方程
18
endmodule
程序对应电路原理图一起看,将比较容易掌握。
闪和顺序亮的间隔时间由时钟Clk2控制,由程序中的产生时钟信号的子程序可得:
221?1?0.18963s,所以clk2?5.27Hz。
11.05920此程序比前面复杂的是调用子程序,主程序分三个部分:时钟产生 、三分频器和四分频器的实现、与非门和与门的实例元件。时钟产生调用一个子程序;三分频器和四分频器分别调用子程序,然后它们各自的子程序又调用JK触发器的子程序。这里完全体现了自顶向下(Top-Down)设计的基本思想:从系统级开始,把系统划分为基本单元,然后再把每个基本单元划分为下一层次的基本单元,一直这样做下去,直到可以直接用EDA元件库中的基本元件来实现为止。其实现过程为:
编好主程序后,保存得到如图a的界面。在工程项目窗口中,可以看到几个带问号的子程序名。
图a
然后把单独编好的fd3和fd4以及genclk的源程序模块拷贝到图a的编辑窗口中,并在原主程序模块之外,保存后得到如图b的界面。在工程项目窗口中带问号的JK触发器程序名,同上,将JK触发器的程序模块添加进去,并保存得到如图c的界面,工程项目窗口中的问号全部去掉。
19
图b
图c
20