学习《搭建你的数字积木数字系统与VerilogHDL设计入门教程》-汤勇明等之部分基础内容
这一部分内容还是比较基础的,但是也是很重要的内容,这是深入学习FPGA的开始吧,我时钟相信理解到什么程度就会设计出什么程度的作品,所以基础很重要。
学习内容:
- 触发器和寄存器;
- 移位寄存器;
- 计数器;
- 时序逻辑设计实例;
- 总结
与组合逻辑电路不同不同,时序逻辑的输出不仅与当前的输入有关,还与过去的输入(上一个或还之前的输入)有关,但是时间一去不复返,在FPGA设计中也是这样,数据在时钟同步下不断进来,当前时刻的数据当前把握即当前时钟处理,但是若需要之前的时钟数据需要又怎么办呢?这就需要一个存储器件能够记住过去时钟下的输入值,或者是其他的输出值。而这里的器件就是下面要说的触发器和锁存器,这是一种时序电路元件,而这种包括锁存器和触发器的电路,我们就称为时序电路。正是因为触发器和锁存器的特性,时序电路的输出不仅与当前输入有关,还与过去输出有关。
触发器和锁存器
触发器和锁存器是基本时序电路元件,是时序电路设计的基础,很多大规模的功能模块都是以寄存器级为基础设计的,掌握基础时序逻辑单元的verilog HDL描述,对深入了解和掌握时序数字电路设计有很大的帮助(不吹不黑,很能帮助对电路的理解)。
D触发器(D Flip-Flop )
DFF是基本逻辑电路中最基本的元器件。上升沿触发是最简单的。
分类:
以上的D触发器只是最基本的D触发器,实用的D触发器还可以包含异步复位信号rst,额外的控制信号en组成以下几种触发器:
- 有两个稳定状态(简称稳态),正好用来表示逻辑 0 和 1。
- 在输入信号作用下,触发器的两个稳定状态可相互转换(称为状态的翻转)。输入信号消失后,新状态可长期保持下来,因此具有记忆功能,可存储二进制信息。
- 一个触发器可存储 1 位二进制数码。
- 触发器有记忆功能,由它构成的电路在某时刻的输出不仅取决于该时刻的输入,还与电路原来状态有关。而门电路无记忆功能,由它构成的电路在某时刻的输出完全取决于该时刻的输入,与电路原来状态无关。
-
触发器和门电路是构成数字电路的基本单元。
D触发器真值表 CLK q* 0 q(保持) 1 q(保持) 上升沿 D - 1. 上升沿触发的触发器
- 2. 带异步复位、上升沿触发的触发器
- 3. 带异步复位和置位、上升沿触发的触发器
- 4.带异步复位和时钟使然、上升沿触发的触发器
- 5.带同步复位、上升沿触发的触发器
Verilog实现
时序电路通常都采用过程语句来进行描述posedge即positive edge用来指定时钟的变化方向为0变为1。表明状态变化总是在clk信号的上升沿触发,反应出触发器边沿触发的特性。需要注意的是输入信号data并没有包含在敏感列表中,这就验证了输入信号data采样是实践是时钟上升沿,而自身值的变化并不会立即导致输出变化。
//1、上升沿触发器
module dff(data,clk,q);
input data,clk;
output q;
reg q;
always@(posedge clk)
begin
q<=data;
end
endmodule
异步复位触发器复位脚高电平触发可以发生在时钟的任意时刻,即与时钟clk不同步(异步)不受时钟控制使用异步复位信号设计违背了同步设计方法,因此。
//2、带异步复位、上升沿触发的触发器
module dff_asynrst(data,rst,clk,q);
input data,rst,clk;
output q;
reg q;
always@(posedge clk or posedge rst)
begin
if(rst==1’b1)
q<=1’b0;
else q<=data;
end
endmodule
//3、带异步复位和置位、上升沿触发的触发器
module dff_asynrst(data,rst,set,clk,q);
input data,rst,set,clk;
output q;
reg q;
always@(posedge clk or posedge rst or posedge set)
begin
if(rst==1’b1)
q<=1’b0;
else if(set==1’b1)
q<=1’b1;
else
q<=data;
end
endmodule
//4、带异步复位,同步钟使能、上升沿触发的触发器
/*使能信号en只有在时钟上升沿来临时才会生效,所以它是同步信号*/
//一段式
module dff_asynrst(data,rst,en,clk,q);
input data,rst,en,clk;
output q;
reg q;
always@(posedge clk or posedge rst)
begin
if(rst==1’b1)
q<=1’b0;
else if(en==1’b1)
q<=data;
end
endmodule
//5、带异步复位,同步钟使能、上升沿触发的触发器
/*使能信号en只有在时钟上升沿来临时才会生效,所以它是同步信号*/
//两段式
module dff_reset_en_2seg(
input data,
input rst,
input en,
input clk,
output reg q);
reg r_reg,r_next;
always@(posedge or negedge reset)
begin
if(rst)
r_reg<=1'b0;
else
r_reg<=r_next;
end
//next_state logic 组合逻辑,综合为数据选择器
always@* //(*)也可以
begin
if(en)
r_next = data;
else
r_next = r_reg;
end
//output logic 组合逻辑
always@(*)
begin
q = r_reg
end
endmodule
上述两段式代码的RTL图为:一个带异步复位的D触发器,其输入d来自一个数据选择器的输出r_next,而这个数据选择器的sel端就为en,两个输入分别为数据输入端口d和该D触发器的输出端反馈r_reg。
//6、带同步复位、上升沿触发的触发器
module dff_synrst(data,rst,clk,q);
input data,rst,clk;
output q;
reg q;
always@(posedge clk)
begin
if(rst==1’b1)
q<=1’b0;
else q<=data;
end
endmodule
//7、带同步复位、同步使能,上升沿触发的触发器
module dff_synrst(data,rst,en,clk,q);
input data,rst,en,clk;
output q;
reg q;
always@(posedge clk)
begin
if(rst==1’b1)
q<=1’b0;
else if(en)
q<=data;
end
endmodule
基本锁存器
下面是一个电平触发型锁存器,与边沿触发器不同的是,它的工作方式为:当时钟CLK为高电平时,其输出q才会随着输入d的变化而更新;当clk为低电平时,锁存器将保持原来高电平时锁存的值。
verilog实现:
//基本锁存器的一种描述
//与D触发器不同,当敏感信号clk电平从低变为高时候,过程语句被启动,顺序执行if语句,clk为1时将d的数据
//更新至q。当clk从0到1或者一致保持低电平都不会再启动过程语句,锁存器的输出q一直保持原来的状态,这就
//意味着在设计模块中引入了存储元件。而此处省略的else分支描述了这个触发器的预期行为。
module latch_1(
input clk;
input a,
output reg q
);
always(clk , d)
begin
if(clk)
q <= a;
end
//含异步清0控制的锁存器
module(
input clk,rst_n,
input d,
output reg q
);
assign q = (!rst_n)?0:(clk?d:q);
endmodule
/*
//另外一种描述
module(
input clk,rrt_n,
input d,
output reg q
);
always@(clk or d or reset)
begin
if(!rst_n)
q <= 0
else if(clk)
q <= d;
end
endmodule
*/
上面两个都是对锁存器的描述,第一个使用了具有并行语句特色的连续赋值语句,其中用到了条件操作,而第二个描述用的是过程语句,把数据信号d,复位信号reset和时钟信号clk都在敏感列表中,从而实现特例reset的异步特性和clk电平触发特性。
寄存器
- 边沿触发的存储单元,在上升或下降沿数据变化,一个周期里只能变化一次。
- 用来暂时存放参与运算的数据和运算结果。在实际的数字系统中,通常把能够用来存储一组二进制代码的同步时序逻辑电路称为寄存器。
位寄存器
D触发器可以用于比特信号的存储。每当时钟上升沿,D触发器就会将输入数据锁存到q,等待外部采样。在实际数字系统中,一般的D触发器的时钟输入端都时钟有时钟信号输入这就意味着每个时钟上升沿,当前d的值都将被锁存在q中,而时钟变化频率通常是上百k。基于D触发器这样的特性,为了设计一个位寄存器,使得它在我们需要的时候从从输入线in_data加载一个值,我们可以在D触发器上增加一根线load,当我们想要从in_data中加载一个值的时候就将load置位,那么在下一个时钟上升沿,in_data的值将被存储在q中。
module reg_1
#(parameter DataWidth = 8)
(
input clk,
input rst_n,
input load,
input [DataWidth-1:0] Idata,
output[DataWidth-1:0] reg Dout
);
always(posedge clk or negedge rst_n)
begin
if(!rst_n)
Dout <= 0;
else if(load)
Dout <= Idata;
end
寄存器组
寄存器组是由一组拥有同一个输入端口和一个或多个输出端口的寄存器组成的。写地址信号w_addr指定了数据存储位置,读地址信号r_addr指定数据检索位置(因为现在有多个寄存器,因此数据的存放有选择就需要指定)。寄存器组通常用于快速、临时存储。下面给出一个参数化的寄存器组的verilog实现代码:
//参数化寄存器组的描述
module
#(parameter W = 2, //地址线数
B = 8 //位宽
)
( input clk,
input wr_en,
input [w-1:0] w_addr,r_addr
input [B-1] w_data,
output [B-1:0] r_data
);
reg [B-1:0] array_reg [2**W-1:0]; //定义内部寄存器组,即2^W个位宽为B的寄存器组
always@(posedge clk)
begin
if(wr_en)
array_reg[w_addr] <= w_data;
end
assign r_data = array_reg[r_addr];
endmodule
参数W指定了地址线的位数,表明在这个寄存器组中有2^W个字,参数B指定了一个字的位数。
移位寄存器
移位寄存器是一种在时钟脉冲的作用下,将寄存器中的数据按位移动的逻辑
电路主要功能:串并转换
- –串行输入串行输出
- –串行输入并行输出
- –并行输入串行输出
一个N位的移位寄存器就包含N个触发器。在每一个时钟脉冲作用下,数据会从一个触发器移动到另一个触发器。下面介绍不同移位寄存器的几种不同描述和实现方法。
串入串出移位寄存器
8位移位寄存器由8个D触发器串联构成,在时钟信号的作用下,前级的数据向后移动
简单实现:
module shift_1(din,clk,dout);
input din,clk;
output dout;
reg dout;
reg tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7;
always@(posedge clk)
begin
tmp1<=din;
tmp2<=tmp1;
tmp3<=tmp2;
tmp4<=tmp3;
tmp5<=tmp4;
tmp6<=tmp5;
tmp7<=tmp6;
dout<=tmp7;
end
endmodule
串入并出移位寄存器
4位串行输入并行输出移位寄存器的逻辑电路如下图所示。该寄存器由4个同步D触发器组成,这种D触发器的R端是是非同步清零端。(异步清零)

实现:
module shift_2(din,clk,clr,q);
input din,clk,clr;
output [3:0] q;
reg [3:0] q;
always@(posedge clk or negedge clr) begin
if(clr==1’b0)
q<=4’b0000;
else begin
q[0]<=din;
q=q<<1;
end
end
endmodule
并入串出移位寄存器
- 并入串出移位寄存器可以将一组二进制数并行送入一组寄存器,然后把这些数据串行从寄存器内输出。
- 一个同步并入串出移位寄存器的基本管脚:
- –并行输出输入端:data
- –时钟脉冲输入端:clk
- –加载数据端:load
- –串行数据输出端:dout
module shift3(clk,din, load,q);
input clk,load;
input [3:0] din;
output q;
reg q;
reg [3:0] tmp;
always@(posedge clk )
begin
if(load==1’b1)
tmp<=din;
else begin
tmp<=tmp<<1;
tmp[0]<=1’b0;
q<=tmp[3];
end
end
endmodule
module shift_reg8(clk,din, load,q);
input clk,load;
input [7:0] din;
output q;
reg q;
reg [7:0] reg8; //定义内部8位寄存器
always@(posedge clk )
begin
if(load)
reg8<=din; //load高电平实现数据加载,为低时开始移位
else begin
reg8[6:0] <= {reg8[7:1]} //实现reg8整个向右移动一位即向低位移动一位
end
end
assign q <=reg8[0]; //reg8最低位输出
endmodule