Verilog-HDL与CPLD/FPGA设计应用讲座
第 5 讲 典型基本逻辑路的Verilog-HDL描述
5.1 数据选择器
5.2 编码器
5.3 同步RS触发器
5.4 带有复位端的同步D触发器
本讲列举几个典型又简单的基本逻辑电路的Verilog-HDL描述。需要说明,仅有本讲的知识还不能具备用Verilog-HDL描述逻辑电路和系统的基本功,但却可以通过几个例子了解Verilog-HDL描述逻辑电路的过程和方法。
5.1 数据选择器
数据选择器又称为多路开关,简称MUX(Multiplexer)。它的逻辑功能是在地址选择信号SEL的控制下,从多路输入(A、B…)数据中选择某一路数据作为输出,一个2-1数据选择器的逻辑电路符号如图1所示。
逻辑电路如图2所示,有关A、B及SEL的解释读者还可参考有关书籍(1)。
图2 2-1数据选择器的逻辑电路
例1 2-1数据选择器的Verilog-HDL描述
/* 2-1 SELECTOR */
module SEL ( A, B, SEL, F ); //模块名及端口参数,范围至endmodule。
input A, B, SEL; //输入端口定义
output F ; //输出端口定义
assign F = ~SEL & A | SEL & B; //assign语句,实现功能:F=(/SEL·A)+(SEL·B)。
endmodule //模块结束
例2 使用case语句的2-1数据选择器
/* 2-1 SELECTOR */
module SEL ( A, B, SEL, F ); //模块名及参数定义,范围至endmodule。
input A, B, SEL; //输入端口定义
output F; //输出端口定义
assign F = SEL2_1_FUNC ( A, B, SEL ); //用assign语句实现function函数调用
function SEL2_1_FUNC; //function函数及函数名,至endfunction为止。
input A, B, SEL; //输入端口定义
case ( SEL ) //case语句,至于endcase为止。
0: SEL2_1_FUNC = A; //功能:SEL=0时,返回A。
1: SEL2_1_FUNC = B; //功能:SEL=1时,返回B。
endcase //case语句结束
endfunction //function函数结束
endmodule //模块结束
【解说】if_else语句的使用方法
if_else 语句用来先判定所给的条件是否满足,然后根据判定的结果(真或假)来执行所给出的两种操作之一。
例3 使用if_else语句的2-1数据选择器
/* 2-1 SELECTOR */
module SEL ( A, B, SEL, F ); //模块名及参数定义,范围至endmodule。
input A, B, SEL; //输入端口定义
output F; //输出端口定义
assign F = SEL2_1_FUNC ( A, B, SEL ); //用assign语句实现function函数调用
function SEL2_1_FUNC; //function函数及函数名,至endfunction为止。
input A, B, SEL; //输入端口定义
if ( SEL == 0 ) // if语句,与else配合使用。
SEL2_1_FUNC = A; //如果SEL= 0,则返回A。
else //if相呼应
SEL2_1_FUNC = B; //否则返回B
endfunction //function函数结束
endmodule //模块结束
【解说】function函数
function函数的目的是返回一个用于表达式的值 ,函数的形式为:
function <返回值位宽或类型说明> 函数名;
端口定义;
局部变量定义;
其他语句;
endfunction
在例2中,用到了function函数。
下面是2-1数据选择器的测试模块。
例4 2-1数据选择器的测试模块
/* 2-1 SELECTOR_TEST */
`timescale 1ns/1ns //将仿真的单位设定为 1ns
module SEL_TEST; //测试模块名及参数定义,范围至endmodule。
reg [1:0] IN; //寄存器定义,输入端口,数据宽度为2bit。
reg SEL_IN; //寄存器定义,输入端口。
wire F; //线网定义,输出端口。
SEL SEL ( IN[0], IN[1], SEL_IN, F ); //底层模块名、实例名及参数。
always #150 SEL_IN = ~SEL_IN; //每隔150ns,SEL_IN反相一次(注1)。
always #50 IN = IN + 1; //每隔50ns,IN = IN + 1(注2)。
initial begin //从initial始,输入信号变化。begin与end相呼应。
SEL_IN = 0; IN = 0; //一开始,SEL_IN = 0,IN = 0。
//之后,按(注1)、(注2)规律变化。
#450 $finish; //仿真到450ns时结束
end //与begin呼应
endmodule //模块结束
Verilog HDL 提供了多种编写测试模块的方法。上例中的 always 语句意指反复执行。例如"always #50 IN = IN + 1;"是说每隔一个仿真单位(在此为50ns)IN便加1;而"always #150 SEL_IN = ~SEL_IN;"是说每隔一个仿真单位(在此为150ns)SEL_IN便反转一次。在initial程序段中,将 "SEL_IN" 和 "IN" 初始化为0,并用"#450 $finish"标示仿真在450ns时结束。
图3给出了2-1数据选择器的仿真结果。通过该图我们可以验证用Verilog-HDL描述的数据选择器是否正确。例如:随机抽取其中的某个时刻(光标指向处)。此时,SEL_IN=0,IN[0]=0,IN[1]=1,F=0。符合数据选择器的功能。
图3 2-1数据选择器的仿真结果
5.2 编码器
在数字系统中,常需要将特定意义的信息编成若干二进制代码,这个过程称为编码,实现编码的数字电路称为编码器。
图4示出了一个2位二进制编码器的功能框图。它有4个输入端,即IN0、IN1、IN2和IN3,2个输出端Y0和Y1。在此,设图中的4个开关在同一时刻只有一个闭合,并规定开关处于打开状态时,对应的输入为逻辑"0",闭合时为逻辑"1"。
图4 二进制编码器的功能框图
上述二进制编码器的真值表如表1所示。
Y0= IN1+ IN3
Y1= IN2+ IN3
由逻辑关系式便可以很容易地画出逻辑电路,如图5所示。由逻辑电路图可知,当Y0 和Y1均为逻辑"0"时,与输入IN0没有任何关系。因此,当4个开关都打开时,输出Y0 和Y1也为逻辑"0"。这在实际应用问题中是需要考虑的,在此不作进一步的讨论。
图5 二进制编码器的逻辑电路
例5 2位二进制编码器的Verilog-HDL描述
/* Data Difinision */
`define SW_IN0 4'b0001 //编译时,将SW_IN0转换为4bit的二进制数0001
`define SW_IN1 4'b0010 //编译时,将SW_IN0转换为4bit的二进制数0010
`define SW_IN2 4'b0100 //编译时,将SW_IN0转换为4bit的二进制数0100
`define SW_IN3 4'b1000 //编译时,将SW_IN0转换为4bit的二进制数1000
/* ENCORDER */
module ENC ( IN, Y ); //模块名及参数定义,范围至endmodule。
input [3:0] IN; //输入端口定义
output [1:0] Y; //输出端口定义
assign Y = FUNC_ENC ( IN ); //用assign语句实现function函数调用
function [1:0] FUNC_ENC; //function函数及函数名,至endfunction为止。
input [3:0] IN; //输入端口定义
case ( IN ) //case语句,至于endcase为止。
`SW_IN0: FUNC_ENC = 0; //当IN= SW_IN0时,返回0。
`SW_IN1: FUNC_ENC = 1; //当IN= SW_IN1时,返回1。
`SW_IN2: FUNC_ENC = 2; //当IN= SW_IN2时,返回2。
`SW_IN3: FUNC_ENC = 3; //当IN= SW_IN3时,返回3。
endcase //case语句结束
endfunction //function函数结束
endmodule //模块结束
例6 2位二进制编码器的测试模块
`timescale 1ns/1ns
/* ENCORDER_TEST */
module ENC_TEST; //测试模块名及参数定义,范围至endmodule。
reg [3:0] IN; //寄存器定义,输入端口,数据宽度为4bit。
wire [1:0] Y; //线网定义,输出端口,数据宽度为2bit。
integer i,j; //定义i、j为抽象型整形数。
ENC ENC ( IN, Y ); //底层模块名、实例名及参数。
initial begin //从initial始,输入信号变化。begin与end相呼应。
j = {2'b10, 2'b0, 1'b1}; //位拼接运算,意为10与00与1拼接。
for ( i = 0; i <= 3; i = i + 1 ) //循环,在begin与end之间。
begin // begin与end相呼应
j=j>>1; //右移1位
IN = j[3:0]; //代入
#200; //经过200ns
end //与begin呼应
$finish; //停止运行
end //与begin呼应
endmodule //模块结束
2位二进制编码器的的仿真结果如图6所示,IN表示编码器的输入,Y表示输出。通过观察,可以得到仿真结果和前述设计的2位二进制编码器完全一致。即:当IN为"1000、0100、0010、0001"时,Y对应为"11、10、01、00"。
5.3 同步RS触发器
同步RS触发器有一个时钟端CLK。只有在时钟端的信号电平发生上跳(正触发)或下跳(负触发)时,触发器的输出才发生变化。
图7为正触发型同步RS触发器的逻辑符号。R和S为输入端,CLK为时钟端,上升沿触发有效,Q和 是互为反相的输出端。
图8为正触发型同步RS触发器的时序图。从图中可以看出,只有当时钟脉冲CLK的上升沿到来时电路的输出状态才有可能发生变化,而变化与否又取决于R端和S端。R端和S端均为低电平时,输出保持电路原有状态(在图8中,原有状态为"不定");R端和S端分别为高电平和低电平时,输出Q为低电平;R端和S 端分别为低电平和高电平时,输出Q为高电平;R端和S端均为高电平时,输出为"不定"。在实际应用中应避免这种R端和S端均为高电平的情况出现。
例7 同步RS触发器的Verilog-HDL描述
/* SY_RS_FF */
module SY_RS_FF ( R, S, CLK, Q, QB ); //模块名及参数定义,范围至endmodule。
input R, S, CLK; //输入端口定义
output Q, QB; //输出端口定义
reg Q; //寄存器定义
assign QB = ~Q; //assign语句,QB=/Q。
always @( posedge CLK ) //在CLK的上跳沿,执行以下语句。
case ({ R ,S }) //case语句,至于endcase为止。
1:Q <= 1; //当R,S的组合为01,则令Q=1。
2:Q <= 0; //当R,S的组合为01,则令Q=1。
3:Q <= 1'bx; //当R,S的组合为11,则令Q为1bit的数,数值为不定(x)。
endcase //case语句结束
endmodule //模块结束
例8 同步RS触发器的测试模块
/* SY_RS_FF_TEST */
`timescale 1ns/1ns //将仿真的单位设定为 1ns
module SY_RS_FF_TEST; //测试模块名及参数定义,范围至endmodule。
reg R, S, CLK; //寄存器定义,输入端口。
wire Q, QB; //线网定义,输出端口。
Parameter STEP = 40; //定义STEP为40,在以后,凡STEP都视为40。
SY_RS_FF SY_RS_FF ( R, S, CLK, Q, QB ); //底层模块名、实例名及参数。
always #( STEP/2 ) CLK = ~CLK; //每隔STEP/2,CLK就反转一次。
initial begin //从initial始,输入信号变化。begin与end相呼应。
CLK = 1; R = 0; S = 0; //参数初始化
#( 2*STEP-10 ) R = 1; //在经过( 2*STEP-10 )后,R = 1。
#( STEP/2 ) R = 0; //在经过( STEP/2 )后,R = 0。
#( STEP/2 ) S = 1; //在经过( STEP/2 )后,S = 1。
#STEP R = 1; //在经过STEP后,R = 1。
#( STEP/2 ) S = 0; //在经过( STEP/2 )后,S = 0。
#STEP R = 0; //在经过STEP后,R = 0。
#STEP $finish; //在经过STEP后,停止运行。
end //与begin呼应
endmodule //模块结束
图9为同步RS触发器的仿真结果。由图可以看出,当R端和S端均为相同电平时,输出波形为"不变"或"不定"。
5.4 带有复位端的同步D触发器[To top]
在同步D触发器的实际应用中,有时需要有一个非同步的复位端。这种D触发器的逻辑符号如图10所示。其时序图如图11所示。
图10 带有复位端的同步D触发器的逻辑符号
图11 带有复位端的同步D触发器的时序图
从图11可以看出,在初始状态下,电路的逻辑处于"不定"状态,复位脉冲的到来将电路初始化为Q=0的状态。随后,在D端的控制下,电路的状态作相应的翻转。图中,t1-t6时刻说明了电路的逻辑状态。
带有复位端的同步D触发器的Verilog-HDL描述
例9 带有R端D触发器的Verilog-HDL描述
/* R_SY_D_FF */
module R_SY_D_FF ( RB, D, CLK, Q, QB ); //模块名及参数定义,范围至endmodule。
input RB, D, CLK; //输入端口定义
output Q, QB ; //输出端口定义
reg Q; //寄存器定义
assign QB = ~Q; //assign语句,QB=/Q。
always @( posedge CLK or negedge RB ) //如果CLK端有上跳或RB端有下跳脉冲,
//则执行下面(Q <= ( !RB )? 0: D;)的语句。
Q <= ( !RB )? 0: D; //如果RB是低电平,则Q=0,否则Q=D。
endmodule //模块结束
例10 D触发器的测试模块
`timescale 1ns/1ns //将仿真的单位设定为 1ns
module R_SY_D_FF_TEST; //模块名及参数定义,范围至endmodule。
reg RB, D, CLK; //寄存器定义,输入端口。
wire Q, QB; //线网定义,输出端口。
parameter STEP = 50; //定义STEP为50,在以后,凡STEP都视为50。
R_SY_D_FF R_SY_D_FF ( RB, D, CLK, Q, QB ); //底层模块名、实例名及参数。 always #( STEP/2 ) CLK = ~CLK; //每隔STEP/2,CLK就反转一次。
initial
begin //从initial始,输入信号变化。begin与end相呼应。
RB = 1; D = 0; CLK = 0; //参数初始化
#( STEP/4 ) RB = 0; //在经过( STEP/4 )后,RB = 0。
#( STEP*3/4 ) D = 1; //在经过( STEP*3/4 )后,D = 1。
#( STEP/4 ) RB = 1; //在经过( STEP/4 )后,RB = 1。
#( 2*STEP ) D = 0; //在经过( 2*STEP )后,D = 0。
#STEP D = 1; //在经过STEP后,D = 1。
#( STEP/2 ) RB = 0; //在经过( STEP/2 )后,RB = 0。
#( STEP/2 ) RB = 1; //在经过( STEP/2 )后,RB = 1。
#( STEP/4 ) $finish; //在经过STEP后,停止运行。
end //与begin呼应
endmodule //模块结束
图12示出了带有复位端的同步D触发器的仿真结果。RB是复位信号,当RB="0"时,Q="0"。从图中可以看出:在复位信号RB的作用下,Q="0"。其后,RB="1"。当CLK的上跳沿到来时,Q就变化为和D相同的状态;上跳沿以外的其它时刻,Q保持状态不变。
图12 带有复位端的同步D触发器的仿真结果
参考书
(1) John M. Yarbrough著,李书浩等译:数字逻辑应用与设计, 机械工业出版社,北京,pp141-145, 2000.4.
(2) 夏宇闻:复杂数字电路与系统的Verilog HDL设计技术,北京航空航天大学出版社,1998.
(3) 常晓明:Verilog-HDL实践与应用系统设计,北京航空航天大学出版社, 2003.1.
文章评论(0条评论)
登录后参与讨论