简易流水灯

前言

        之前在安装部分和创建工程部分,讲到很多规范操作,就比如创建工程时做到工作库,这都是一些工程规范,要学会FPGA,必须要遵守这些规范。本节,我们实现一个流水灯设计程序的编写,借该项目说明Verilog的工程规范。

正文

一、简易流水灯

        1.项目需求

        假设现有FPGA开发板有4个引脚连接4个LED灯,1个连接按键(低电平有效),1个连接系统时钟(50MHz)需要完成任务:复位按键按下,4个LED灯熄灭,复位按键弹起后点亮第一个灯,然后熄灭第一个灯同时点亮第二个灯,依次往后,第四个灯熄灭后点亮第一个灯,循环往复,实现流水。

        2.技术介绍

        假设LED灯高电平点亮,利用有限状态机理论,设计验证将开发板上集成的led外设依次点亮,从左到右依次点亮(12341234)。将每点亮一个Led灯作为一个状态,那么依次点亮一次可以划分为4个状态,每一个状态执行的动作(输出)点亮对应的led灯,每一个状态持续的时间为:设置为1秒钟。状态之间跳转的条件为:延迟计数器计时到1秒钟。

        什么是状态机?状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作,完成特定操作的控制中心。

        状态机知识:状态机的基本要素有 3 个,其实我们在第一节的举例中都有涉及,只是没有点明,它们是:状态、输出和输入。根据状态机的输出是否与输入条件相关,可将状态机分为两大类:摩尔(Moore)型状态机和米利(Mealy)型状态机。根据状态机的三个基本要素,又可以划分为一段式,二段式,三段式。  

        摩尔状态机:摩尔状态机的输出仅仅依赖于当前状态,而与输入条件无关。         

        米勒型状态机:米勒型状态机的输出不仅依赖于当前状态,而且取决于该状态的输入条件。

        一段式描述方法将状态转移判断的组合逻辑和状态寄存器转移的时序逻辑混写在同一个always 模块中,不符合将时序和组合逻辑分开描述的Coding Style(代码风格),而且在描述当前状态时要考虑下个状态的输出,整个代码不清晰,不利于维护修改,并且不利于附加约束,不利于综合器和布局布线器对设计的优化。

            两段式 FSM 描述方法虽然有很多好处,但是它有一个明显的弱点就是其输出一般使用组合逻辑描述,而组合逻辑易产生毛刺等不稳定因素,并且在FPGA/CPLD 等逻辑器件中过多的组合逻辑会影响实现的速率(这点与ASIC 设计不同)。所以在上节我们特别提到了在两段式FSM 描述方法中,如果时序允许插入一个额外的时钟节拍,则尽量在在后级电路对FSM 的组合逻辑输出用寄存器寄存一个节拍,则可以有效地消除毛刺。但是很多情况下,设计并不允许额外的节拍插入(Latency),此时,解决之道就是采用3 段式FSM 描述方法。三段式描述方法与两段式描述方法相比,关键在于使用同步时序逻辑寄存FSM 的输出。

        对比一下上节两段式 FSM 的描述,我们可以清晰发现三段式与两段式FSM 描述的最大区别在于两段式采用了组合逻辑输出,而三段式巧妙地根据下一状态的判断,用同步时序逻辑寄存FSM 的输出。消除了组合逻辑输出的不稳定与毛刺的隐患,而且更利于时序路径分组,一般来说在FPGA/CPLD 等可编程逻辑器件上的综合与布局布线效果更佳。

        总结三类状态机特点:

        一段式:整个状态机写到一个 always 模块里面。在该模块中既描述状态转移,又描述状态的输入和输出 。

        二段式:用两个 always 模块来描述状态机。其中一个 always 模块采用同步时序描述状态转移。另一个 always模块采用组合逻辑判断状态转移条件,描述状态转移规律及其输出 ,注意组合逻辑输出要用阻塞赋值。

        三段式:在两个 always 模块描述方法基础上,使用三个 always 模块。 一个 always 模块采用同步时序描述状态转移;一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,注意组合逻辑输出要用阻塞赋值;另一个 always 模块描述状态输出(可以用组合电路输出,也可以时序电路输出)。

状态

功能

说明

s0

点亮第一个led灯,led=0111,延迟计数器计数:cnt=cnt+ 1,状态转移条件为:cnt = max - 1

第一个状态,max表示计时钟最大值:1s/20ns=50_000_000;

s1

点亮第2个led灯,led=1011,延迟计数器计数:cnt=cnt+ 1,状态转移条件为:cnt = max - 1

第2状态,max表示计时钟最大值:1s/20ns=50_000_000;

s2

点亮第3个led灯,led=1101,延迟计数器计数:cnt=cnt+ 1,状态转移条件为:cnt = max - 1

第3状态,max表示计时钟最大值:1s/20ns=50_000_000;

s3

点亮第4个led灯,led=1110,延迟计数器计数:cnt=cnt+ 1,状态转移条件为:cnt = max - 1

第4状态,max表示计时钟最大值:1s/20ns=50_000_000;

        状态转移图:

        3.顶层架构

        ctrl_max为产生控制延时信号的模块,通过flag切换不同的延时时间,产生的MAX信号为延时计数器最大值,最大值给到led_state模块,led_state里面有延时计数器,以该计数器为状态机跳转条件,控制led变化。

        4.端口描述

clk时钟
rst_n复位(低电平有效)
led[0]led灯低位
led[1]led灯次低位
led[2]led灯次高位
led[3]led灯高位

二、代码验证

        ctrl_max模块,产生不同的延时计数值:

module ctrl_max(

	input clk,
	input rst_n,
	input flag,//每次状态机切换产生一次该信号
	
	output reg [25:0] MAX //延时计数器最大值,一秒:50_000_000
);

reg [3:0] cnt;//存储最大值的寄存器,4bit数据,最大为4‘d15

//always @(posedge clk,negedge rst_n)固定产生5_000的计数信号
//begin
//	if(rst_n == 0)
//		MAX <= 26'd5;
//	else
//		if(flag == 1)
//			if(MAX == 50_000_000)
//				MAX <= 26'd5;
//			else
//				MAX <= MAX + 26'd5_000;每次增加5_000的计数信号
//		else
//			MAX <= MAX;
//end 

always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		cnt <= 4'd0;
	else
		if(flag == 1)//状态机需要切换
			cnt <= cnt + 4'd1;//寄存器切换
		else
			cnt <= cnt;
end 

always @(*)begin
	if(rst_n == 0)
		MAX <= 26'd50_000_000;
	else
		case(cnt)//多路选择逻辑,对应MAX会输出不同的值,现象就是每个灯亮的时间不同
			4'd0	:	MAX <= 26'd50;
			4'd1	:	MAX <= 26'd50_000;
			4'd2	:	MAX <= 26'd10_000;
			4'd3	:	MAX <= 26'd500_000;
			4'd4	:	MAX <= 26'd100_000;
			4'd5	:	MAX <= 26'd50_000;
			4'd6	:	MAX <= 26'd60_000;
			4'd7	:	MAX <= 26'd70_000;
			4'd8	:	MAX <= 26'd80_000;
			4'd9	:	MAX <= 26'd90_000;
			4'd10	:	MAX <= 26'd50000_000;
			4'd11	:	MAX <= 26'd50_000;
			4'd12	:	MAX <= 26'd40_000;
			4'd13	:	MAX <= 26'd30_000;
			4'd14	:	MAX <= 26'd20_000;
			4'd15	:	MAX <= 26'd10_000;
			default	:	MAX <= 26'd50_000_000;
		endcase
end 

endmodule 

        led_state三段式状态机控制模块:

//三段式
module led_state_v2(

	input clk,
	input rst_n,
	input [25:0] MAX,//接受延时计数值
	
	output flag,//表示led依次循环点亮三次标志信号,高电平有效
	output reg [3:0] led //4个LED灯
);


//定义状态变量 parameter是常量

reg [1:0] n_state;
reg [1:0] c_state;
//定义状态参数
parameter s0 = 2'd0;//第1个Led灯
parameter s1 = 2'd1;//第2个Led灯
parameter s2 = 2'd2;//第3个Led灯
parameter s3 = 2'd3;//第4个Led灯

reg [25:0] cnt;//1秒钟延迟计数器
//parameter MAX = 50_000_000;

reg [3:0] cycle_cnt;

//FSM1
always@(posedge clk,negedge rst_n)///判断状态转移条件
begin
	if(rst_n == 0)
		c_state <= s0;
	else
		c_state <= n_state;
end 

//FSM2
always @(*) begin//描述状态转移
	if(rst_n == 0)
		n_state <= s0;
	else
		case(c_state)
			s0	:	begin
						if(cnt == MAX - 1)
							n_state <= s1;
						else
							n_state <= s0;
					end 
			s1	:	begin
						if(cnt == MAX - 1)
							n_state <= s2;
						else
							n_state <= s1;
					end 		
			s2	:	begin
						if(cnt == MAX - 1)
							n_state <= s3;
						else
							n_state <= s2;
					end 
			s3	:	begin
						if(cnt == MAX - 1)
							n_state <= s0;
						else
							n_state <= s3;
					end 		
			default	:	n_state <= s0;
		endcase
end 

//FSM3
always@(posedge clk,negedge rst_n)//状态输出
begin
	if(rst_n == 0)
		begin
			cnt <= 26'd0;
			led <= 4'hf;
		end 
	else
		case(n_state)
			s0	:	begin
						led <= 4'b0111;
						if(cnt < MAX - 1)
							cnt <= cnt + 26'd1;
						else 
							cnt <= 26'd0;
					end 
			s1	:	begin
						led <= 4'b1011;
						if(cnt < MAX - 1)
							cnt <= cnt + 26'd1;
						else 
							cnt <= 26'd0;
					end
			s2	:	begin
						led <= 4'b1101;
						if(cnt < MAX - 1)
							cnt <= cnt + 26'd1;
						else 
							cnt <= 26'd0;
					end
			s3	:	begin
						led <= 4'b1110;
						if(cnt < MAX - 1)
							cnt <= cnt + 26'd1;
						else 
							cnt <= 26'd0;
					end 			
			default	:	begin cnt <= 26'd0; led <= 4'hf; end 
		endcase
end

always@(posedge clk,negedge rst_n)//产生延时计数器切换信号
begin
	if(rst_n == 0)
		cycle_cnt <= 4'd0;
	else
		if(cnt == MAX - 1)
			if(cycle_cnt < 11)//每一种延时计数值下执行12次,0到11,
				cycle_cnt <= cycle_cnt + 4'd1;
			else
				cycle_cnt <= 4'd0;
		else
			cycle_cnt <= cycle_cnt;	
end 

assign flag = ((cnt == MAX - 1)&&(cycle_cnt == 11))?1'b1:1'b0;
//每一种延时计数值下执行12次,当执行到第12次时,
//并且第12次的计数器记到最大值时产生延时计数器切换信号
//切换下一个最大延时计数值。
		
endmodule 

        顶层代码        

module led_top(

	input clk,
	input rst_n,
	
	output [3:0] led
);

wire [25:0] MAX;
wire flag;
ctrl_max ctrl_max_inst(

				.clk(clk),
				.rst_n(rst_n),
				.flag(flag),
				
				.MAX(MAX) //一秒:50_000_000
);

led_state_v2 led_state_v2_inst(

			.clk(clk),
			.rst_n(rst_n),
			.MAX(MAX),
			
			.flag(flag),//表示led依次循环点亮三次标志信号,高电平有效
			.led(led)
);

endmodule 

        仿真代码

`timescale 1ns/1ps
module led_state_tb;

	reg clk;
	reg rst_n;
	
	wire [3:0] led;
//#(.MAX(5))表示传递参数,其目的为了仿真验证方便
led_top /*#(.MAX(5))*/led_state_inst(

			.clk(clk),
			.rst_n(rst_n),
			
			.led(led)
);

initial clk = 1;
always #10 clk = ~clk;

initial begin
	rst_n = 0;
	#200
	rst_n = 1;
	#1000
	$stop;
end 

endmodule 

三、仿真验证

        运行仿真:

        可以看到,在不同的时间段led输出的频率不同,放大观察:

        此处led变化12次,对应我们代码逻辑里面的每12个状态后产生延时计数器切换信号,切换一种点亮频率。调用中间信号细致分析波形图:

        可以看到状态转移满足我们设计要求。三段不同输出频率led的延时计数器最大值分别为50_000,10_000,500_000,时钟20ns变化一次。对应每个状态变化时间为1_000_000ns,200_000ns,10_000_000ns。

参考资料

状态机

一二三段式状态机

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张明阳.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值