quartus时序逻辑的开始

时序逻辑的开始

时序逻辑的开始

  • 时序逻辑的开始
    • 时序逻辑和组合逻辑(D触发器仿真)
    • 阻塞赋值与非阻塞赋值
    • 计数器

时序逻辑和组合逻辑(D触发器仿真)

1、定义
组合逻辑有一个最大的缺陷就是存在竞争冒险(很危险,使电路处于一个不稳定的状态,使用时序逻辑可以极大避免这一问题,提高系统稳定性)
时序逻辑最基本的单元——寄存器,存储功能,一般由D触发器构成,由时钟脉冲控制,每个D触发器能够存储一位二进制码。
寄存器还具有复位清零功能,分为同步复位和异步复位。
2、时序逻辑的两个特点:
①时序电路只有在时钟沿到来时才检测信号是否有效,上升沿之间的毛刺现象都会被自然过滤掉,大大减少了毛刺现象产生的干扰,提高电路的可靠性。
②时序电路还能产生延时一拍的效果,组合逻辑中是直接将输入信号的值赋给输出信号,两者信号的值一致,但是如果在时序逻辑中,输出信号会延迟输入一拍,如果时钟和数据对齐,默认当前时钟沿采集到的数据是在该时钟上升沿前一时刻的值。今后我们使用的大部分都是时序逻辑,要记住延迟一拍的效果。
3、绘制波形图时注意:
①时序电路延迟一拍; ②同步复位和异步复位区别。

时序逻辑中学会绘制波形图进行分析很重要!

这里使用D触发器来控制,按键点亮LED灯。区别于之前的组合逻辑实现。
quartus时序逻辑的开始_第1张图片
1、D触发器同步复位verilog代码(详细注释)

module flip_flop
(	input wire  sys_clk,    //时序逻辑,一定有时钟信号,由板载晶振传入,50MHZ
	input wire  sys_rst_n,   //低电平有效,由板卡的复位按键输入
	input wire  key_in,
	
	output   reg  led_out    //always用reg 
);

always@(posedge sys_clk)    //时序逻辑中,always语句端口列表一般是时钟上升沿或者下降沿,一般选上升沿
	if(sys_rst_n == 1'b0)
		led_out <= 1'b0;    //当上升沿,复位信号有效//当复位信号有效时,给输出信号赋初值,初值一般都赋值为零
							//时序逻辑中赋值一定要使用非阻塞赋值,<=,用小于等于号
	else
		led_out <= key_in ;
	
endmodule

异步复位时,不用考虑时钟信号的上升沿,只需修改代码always中的敏感列表:
quartus时序逻辑的开始_第2张图片
当检测到时钟信号上升沿或者是复位信号下降沿,立刻执行,不需要等待时钟信号上升沿。
编译后两者RTL视图:
同步复位:
quartus时序逻辑的开始_第3张图片
异步复位:
quartus时序逻辑的开始_第4张图片
同步复位比异步复位多了一个选择器,会消耗更多的逻辑资源,Altera推荐使用异步复位

2、仿真文件的编写
时模输入输出实例化,initial初始always赋值加系统(时间格式和监测)
首先是同步复位(关于时钟的设置有详细说明)



`timescale 1 ns/ 1 ns   
module flip_flop_vlg_tst();

//reg eachvec;
reg key_in;
reg sys_clk;
reg sys_rst_n;
                                             
wire led_out;

                        
flip_flop i1 (
  
	.key_in(key_in),
	.led_out(led_out),
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n)
);
initial                                                
	begin                                                  
    sys_clk = 1'b1;  //时钟信号要用阻塞赋值 
	sys_rst_n <= 1'b0;
	key_in <= 1'b0;
	#20     //延迟20个时钟单位,即延迟20ns
	sys_rst_n <= 1'b1;   //这里进行了拉高操作,因为复位信号低电平有效,复为完成以后,再拉高,系统才能正常工作
	#210
	sys_rst_n <= 1'b0;    
	#40
	sys_rst_n <= 1'b1;   //为更直观看出同步复位和异步复位的区别
	
	
	$display("Running testbench");                       
	end                   //生成复位信号之后,模拟系统时钟                                 
always  #10 sys_clk = ~sys_clk;    //用always语句产生时钟,让时钟每隔10ns反转一次,即一个时钟周期是20ns,可以换算成50Mhz                                               
									//模拟时钟信号生成完成
always  #20 key_in <= {$random}% 2;		//开始生成信号key_in,使用随机数模拟按键输入
//求模取余法,产生非负随机数0和1,每隔20ns产生一次随机数
//选择20ns是因为在时序逻辑中保证按键信号变化时间小于或等于时钟周期,这样不会产生毛刺,利于波形观察

initial   //initial系统函数格式+监测,便于观察
	begin 
		$timeformat(-9,0,"ns",6);  
		$monitor("@time %t:key_in=%b sys_clk=%b sys_rst_n=%b led_out=%b",$time,key_in,sys_clk,sys_rst_n,led_out);  
	end                             
                                                       
//@eachvec;                                              
                                            
                                                 
endmodule


①过程中有一个报错,系统函数前要用$符号表示!!
$random 系统函数

?②在该过程编码中,时钟信号为什么要用阻塞赋值?非阻塞赋值可以吗?
阻塞赋值和非阻塞赋值在组合逻辑和时序逻辑中的应用和区别

试了下,仿真文件中时钟信号用非阻塞赋值不会报错。
参考: 阻塞与非阻塞赋值,不只是比原始信号差一个时钟周期的问题!

3、RTL Simulation
同步复位结果:
quartus时序逻辑的开始_第5张图片
异步复位:(同样延迟一个拍,当复位信号有效时,输出信号会立即发生变化;当复位信号无效时,输出信号并不会立刻受到影响,而是要等到时钟信号的上升沿输出信号才会发生变化,即异步复位,同步释放
这里的波形图有点问题,复位信号持续=1
quartus时序逻辑的开始_第6张图片
插入一个技巧,同步复位和异步复位仿真文件可以不变,改变了.v文件之后直接在modelsim软件中进行再编译即可得到波形。(虽然这里得到的波形有些问题,可能是未设置仿真结束时间,但是这一操作可以参考)
在library选项卡中:
quartus时序逻辑的开始_第7张图片

阻塞赋值与非阻塞赋值

一、定义与区别
阻塞赋值
1、阻塞赋值的赋值号用 “=” 表示,对应的电路结构往往与触发沿没有关系,只与输入电平的变化有关系
2、它的操作可以认为是只有一个步骤的操作,即计算赋值号右边的语句并更新赋值号左边的语句,此时不允许有来自任何其他verilog语句的干扰,直到现行的赋值完成,才允许下一条的赋值语句的执行
3、串行块(begin-end)中,各条阻塞赋值语句将以它们在顺序块中的排列次序依次执行,顺序执行
非阻塞赋值
1、阻塞赋值的赋值号用 “<=” 表示,对应的电路结构往往与触发沿有关系,只有在触发沿的时刻才能进行非阻塞赋值
2、它的操作可以看作两个步骤:①在赋值开始时,计算赋值号右边的语句;②在赋值结束后,更新赋值号左边的语句(等到end结束之后,同时将左边的语句进行更新)。
3、在计算非阻塞语句赋值号右边的语句和更新赋值号左边的语句期间,允许其他的verilog语句同时进行操作
4、非阻塞操作只能用于对寄存器类型变量reg进行赋值,因此只能用于 “initial” 和 “always” 块中,不允许用于连续赋值 “assign” 。

二、阻塞赋值与非阻塞赋值验证仿真代码:
阻塞赋值verilog代码:

module blocking
(	input wire  sys_clk,    //时序逻辑,一定有时钟信号,由板载晶振传入,50MHZ
	input wire  sys_rst_n,   //低电平有效,由板卡的复位按键输入
	input wire  [1:0]  in,   //输入一个两位宽的输入信号
	
	output   reg  [1:0]  out    //always用reg 


);
reg in_reg ;             //定义中间变量方便仿真波形的观察
always@(posedge sys_clk or negedge sys_rst_n)    //异步复位
	if(sys_rst_n == 1'b0)
		begin
			in_reg = 2'b0;
			out = 2'b0;    //阻塞赋值
		end					
	else
		begin
			in_reg = in;
			out = in_reg;    //阻塞赋值
		end			
endmodule

RTL视图:(用了一个寄存器)
quartus时序逻辑的开始_第8张图片
仿真文件代码:(仿真文件中,initial初始化赋值,时钟信号用的是阻塞赋值,其余用的是非阻塞赋值,两次验证仿真文件一致)


`timescale 1 ps/ 1 ps
module blocking_vlg_tst();

reg eachvec;

reg [1:0] in;
reg sys_clk;
reg sys_rst_n;
                                            
wire [1:0]  out;

                       
blocking i1 (

	.in(in),
	.out(out),
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n)
);
initial                                                
	begin                                                  
	sys_clk = 1'b1;  //时钟信号 阻塞赋值
	sys_rst_n <= 1'b0;
	in <= 2'b0;
	#20
	sys_rst_n <= 1'b1; //延迟拉高复位信号操作,使系统正常工作
	
	$display("Running testbench");                       
	end                             //信号初始化                       

always  #10 sys_clk = ~sys_clk;    //生成模拟时钟,时钟频率50Mhz                                               
									//模拟时钟信号生成完成
always  #20 in <= {$random}% 4;	//求模取余法模拟输入信号 in为两位宽,00 01 10 11,对4进行求余,刚好可以生成非负数0 1 2 3 
	                                                 
endmodule


RTL Simulation仿真波形(添加-分组-隐藏路径)
quartus时序逻辑的开始_第9张图片
我们发现输入信号in,中间变量in_reg和输出信号out它们的关系有个延迟一拍的效果,中间变量in_reg一定要等到复位信号被释放后,且第一个时钟沿到来之后,才会将in值赋值给它,所以它比输入信号延迟一拍,但是in_reg和out没有延迟一拍的关系,他俩在同一时刻变化,因为是阻塞赋值,赋值号右边的表达式值有变化,赋值号左边的表达式立刻发生变化,我们看到的最终结果就是inreg和out的波形变化是一样的。

对代码进行修改,使用非阻塞赋值
quartus时序逻辑的开始_第10张图片
RTL视图:(用了两个寄存器)
quartus时序逻辑的开始_第11张图片
代码修改后直接在modelsim软件——Library选项卡进行再编译,得到仿真波形结果:
quartus时序逻辑的开始_第12张图片
我们可以看到in_reg变量延迟了in一个时钟周期,输出信号out延迟了变量in_reg一个时钟周期,延迟了in两个时钟周期。这是因为in_reg一定要等到复位被释放胡,且第一个时钟沿到来时,才会把in值赋值给它,这里与阻塞赋值相同;out延迟in_reg一个时钟周期是因为使用非阻塞赋值,赋值号右边的表达式有变化了,赋值号左边表达式的值不会立即变化,需要等到下一个时钟沿到来时一起变化。相对于输入信号,输出信号相当于打了两拍。
当我们想对一个信号打两拍时,使用阻塞赋值达不到效果。

编写组合逻辑电路时,使用阻塞赋值;
编写时序逻辑电路时,使用非阻塞赋值。

总结编码的一些规范:
①编写时序逻辑时,采用非阻塞赋值;
②使用always语句块编写组合逻辑时,使用阻塞赋值,敏感列表一定要使用电平触发的方式;
③在一个always语句块中,不要既使用阻塞赋值,又使用非阻塞赋值,这样会造成不可预测的后果;
④锁存器不推荐使用,非要使用时,一定要采用非阻塞赋值,非阻塞赋值实现时序逻辑,继而实现锁存器最安全;
⑤推荐一个always语句块只对一个变量赋值,方便代码后期的维护和修改。

计数器

1、概念
①利用寄存器实现一个具有计数器功能的电路,在fpga设计中, 一切与时间有关的设计都需要用到计数器。
②计数器在数字系统中主要是对脉冲的个数进行计数,以实现测量、计数和控制的功能,同时兼有分频功能。
③计数器在数字系统中应用广泛,如电子计算机的控制器中对指令地址进行计数,以便顺序取出下一条指令,在运算器中作乘法、除法运算时记下加法、减法次数,又如在数字仪器中对脉冲的计数等等。同时计数器也是在FPGA中最常用的一种时序逻辑电路,根据计数器的计数值,我们可以精确计算出fpga内部各信号之间的时间关系,每个信号什么时候拉高,什么时候拉低,高低电平保持多久,都可以利用计数器进行精准控制,而让计数器计数的,是让外部晶振产生的时钟,所以可以实现精确控制计数的时间。
④从0开始计数,可以实现不断循环计数,计数到需要的值或者溢出清零。
⑤计数器时间间隔1s,这种计数是模块内部产生的,用板载led灯进行验证,前0.5s处于点亮状态,后0.5s处于熄灭状态。
2、功能
quartus时序逻辑的开始_第13张图片
考虑两点:
1)计数器什么时候开始计数?
复位信号撤销,时钟沿到来,就可以立即计数。
2)计数器什么时候清零?
计数器记满了需要清零;
计数到我们需要的计数值清零。
本文中计数时间间隔1s,考虑1s的时间需要计数器计数多少个个数
1s
f = 50Mhz = 510^4KHz = 510 ^7Hz 单位时间内信号进行周期性变化的次数
每次变化时间 t = 1/f =210^-8s = 20ns
1s之内有多少个ns M = 5
10^7
即在50MHz频率下,计数器要完成M次计数才能实现1s的计数,另外,计数器是从0开始计数的,应为 M-1次。

这里做一个改进,计数器不用计数到M-1次,而是计数到(M/2)-1次。
在这里插入图片描述
这一改进的好处在于:
计数到M-1次:十进制状态下M-1=49,999,999
切换成二进制 10 1111 1010 1111 0000 0111 1111
计数位cnt需要26位比特位 [25:0]设置为26位宽;
计数到(M/2) -1次:十进制状态下=24,999,999
切换成二进制 1 0111 1101 0111 1000 0011 1111
计数位cnt需要25位比特位 [24:0]设置为25位宽;
节省寄存器资源,养成精简设计的习惯。故采用第二种方法。

2.verilog代码编写

参数定义
在这里插入图片描述
parameter 和 localparam区别:
都可以在模块内部进行参数定义,localparam只能使用在模块内部;
parameter除了在模块内部进行参数定义,还能写在端口列表前作为在实例化中参数传递的接口。
在仿真文件实例化中可以对该参数进行数值修改,最终运行是实例化中的参数,而不是模块文件中定义的参数值。
在仿真时,仿真文件中实例化时对参数进行修改,还可以缩短仿真时间。
注意,带有参数接口的实例化,实例化名称应写在端口列表的前面。

注意点:
1.参数定义
2.计数器变量cnt的赋值与处理
3.输出信号led_out的赋值与处理

代码如下:

module counter
#(
	parameter CNT_MAX = 25'd24_999_999   //计数器最大计数值,使用参数传递的方法定义参数,方便参数调用和参数修改

)

(	input wire  sys_clk,    //时序逻辑,一定有时钟信号,由板载晶振传入,50MHZ
	input wire  sys_rst_n,   //低电平有效,由板卡的复位按键输入
	
	output   reg  led_out   
);
        
//localparam CNT_MAX = 25'd24_999_999; 

reg [24:0] cnt ;             //声明寄存器变量,25位宽

always@(posedge sys_clk or negedge sys_rst_n)    //计数器变量的赋值
	if(sys_rst_n == 1'b0)    //当复位信号有效时
		cnt <= 25'd0;         //计数器变量赋值为0	
	else  if (cnt == CNT_MAX)   //当计数到最大值,让它归零=0
		cnt <= 25'd0;
	else             //复位条件无效,而且没有计数到最大值,等待时钟沿,自+1
		cnt <= cnt + 25'd1;

  always@(posedge sys_clk or negedge sys_rst_n)    //输出信号赋值
	if(sys_rst_n == 1'b0)  
		led_out <= 1'b0;                                                    
	else  if (cnt == CNT_MAX)   //当计数到最大值,输出信号取反
		led_out <= ~led_out;
	else             //复位条件无效,而且没有计数到最大值,让输出信号保持原来的电平
		led_out <= led_out;
	
endmodule

查看RTL视图,RTL视图与代码与波形都是一一对应的,生成的RTL视图太大, 这里不作展示。

3、仿真文件的编写
注意有参数定义的实例化编写,需要自己修改,模板里面没有,注意实例化名称位置。
quartus时序逻辑的开始_第14张图片
具体代码如下:


`timescale 1 ns/ 1 ns
module counter_vlg_tst();

reg sys_clk;
reg sys_rst_n;
                                           
wire led_out;
                      
counter 
#(
	.CNT_MAX (25'd24)
) 
i1       //含参数定义的实例化需要自己编写
        //①实例化名称要写在端口列表前;②这里将CNT_MAX赋值为24,运行的时候值就是24,而不是编译文件中的24,999,999

 (
	.led_out(led_out),
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n)
);
initial               //初始化赋值,延迟20ns将复位信号拉高                                   
	begin                                                  
    sys_clk = 1'b1;
	sys_rst_n <= 1'b0;
	#20
	sys_rst_n <= 1'b1; 
	$display("Running testbench");                       
	end                                                    

always  #10 sys_clk = ~sys_clk;    //生成模拟时钟,时钟频率50Mhz                                               
									//模拟时钟信号生成完成

initial   //initial系统函数格式+监测,便于观察
	begin 
		$timeformat(-9,0,"ns",6);  
		$monitor("@time %t:led_out=%b",$time,led_out);  
	end   
                                                                      
endmodule


4、仿真结果

在查看波形时,sim选项卡——添加实例化模型波形——全选,分组——设置仿真时间,restart,run(图示)——全局视图
在这里插入图片描述
ohno 仿真波形错误了,核查问题去了……
果然是仿真时间错乱导致的,在代码中我设置的是50MHZ,那么就是20ns为一个时钟周期
首先把modelsim显示的仿真时间单位由ps设置为ns
参考: MODELSIM时间标度修改

仿真时间有两个地方设置:
一个是settings里面的仿真结束时间,一般设置为1us,如果不设置的话,需要在modelsim中手动stop;
二是上面modelsim中设置仿真时间,10us,方便对波形进行观察和分析。

仿真时间在代码、quartus和modelsim软件中均要一致,设置好后得到波形如下:
quartus时序逻辑的开始_第15张图片
波形正确。
另有一种使用脉冲标志信号flag的方法,引入脉冲标志信号,每一次当高脉冲来时,才执行对输出out取反的指令。这种信号在后面会经常使用,这种信号会使代码中if括号内的条件更加清晰简洁,而且在多处需要使用脉冲标志信号的地方要比全部写出更加节省逻辑资源。
另一个有用的信号——使能信号

5、加入脉冲信号cnt_flag
如何精确的产生我们需要的flag脉冲标志信号?
加入脉冲信号后,对verilog文件修改:
quartus时序逻辑的开始_第16张图片
具体代码:

module counter
#(
	parameter CNT_MAX = 25'd24_999_999   //计数器最大计数值,使用参数传递的方法定义参数,方便参数调用和参数修改

)

(	input wire  sys_clk,    //时序逻辑,一定有时钟信号,由板载晶振传入,50MHZ
	input wire  sys_rst_n,   //低电平有效,由板卡的复位按键输入
	
	output   reg  led_out   
);
        
//localparam CNT_MAX = 25'd24_999_999; 

reg [24:0] cnt ;             //声明寄存器变量,25位宽
reg cnt_flag;  //先声明一个脉冲标志信号

always@(posedge sys_clk or negedge sys_rst_n) 
	if(sys_rst_n == 1'b0)
	cnt_flag <= 1'b0;
	else if (cnt == (CNT_MAX - 25'd1))    //计数器计数到最大值—1时,拉出一个高电平,根据绘制的波形图进行编码
	cnt_flag <= 1'b1;
	else
	cnt_flag <= 1'b0;
	
always@(posedge sys_clk or negedge sys_rst_n)    //计数器变量的赋值
	if(sys_rst_n == 1'b0)    //当复位信号有效时
		cnt <= 25'd0;         //计数器变量赋值为0	
	else  if (cnt == CNT_MAX)   //当计数到最大值,让它归零=0
		cnt <= 25'd0;
	else             //复位条件无效,而且没有计数到最大值,等待时钟沿,自+1
		cnt <= cnt + 25'd1;

  always@(posedge sys_clk or negedge sys_rst_n)    //输出信号赋值
	if(sys_rst_n == 1'b0)  
		led_out <= 1'b0;                                                    
	else  if (cnt_flag == 1'b1)   //当脉冲信号为高电平时,输出信号取反
		led_out <= ~led_out;
	else             //复位条件无效,而且没有计数到最大值,让输出信号保持原来的电平
		led_out <= led_out;
	
endmodule

对其进行编译,仿真文件一致,直接在mldelsim重新加载编译。
quartus时序逻辑的开始_第17张图片
寄存器有延迟一拍的效果,所以在max-1到达最大值时输出信号刚好与输入信号周期一致。
flag信号作为条件,对输出信号进行取反,输入信号同样是寄存器,有延迟一拍的效果,输出信号的电平保持刚好与计数器一个完整的周期是一致的。

全编译——管脚绑定——下载到开发板,固化程序

你可能感兴趣的:(FPGA,编程语言,verilog,fpga)