学习笔记(二) 基于FPGA的交通灯

1,实现的主要要求:
(1)东西方向红灯亮30s,南北方向绿灯亮;
(2)东西方向黄灯亮3s,南北方向绿灯亮;
(3)南北方向红灯亮30s,东西方向绿灯亮;
(4)南北方向黄灯亮3s,东西方向绿灯亮;
(5)回到(1),同时用数码管显示每个灯亮的剩余时间;

2,实现方案
电路的整体RTL图如下,
在这里插入图片描述
由上图可知,电路主要分为两个部分,一个是交通灯控制模块,其中状态的流转通过状态机实现,状态转移图如下:
在这里插入图片描述
另一个是数码管显示模块,通过两个数码管来显示剩余时间;
3,顶层模块代码

module traffic_top(
	input  clk,
	input  rst_n,
	
	output [5:0]led,
	output [1:0]seg_sel,
	output [7:0]seg_led
);

wire en;
wire data;

traffic_led u1(
	.clk		 (clk),
	.rst_n	 (rst_n),
	.led		 (led),
	.time_cnt (data),
	.en		 (en)
);

smg_display u2(
	.clk		 (clk),
	.rst_n	 (rst_n),
	.data	    (data),
	.en		 (en),
	.seg_sel	 (seg_sel),
	.seg_led	 (seg_led)
);
endmodule

4,traffic_led模块代码

module traffic_led(
	input 		    clk,			//系统时钟
	input 			 rst_n,		//复位信号  
	
	output reg[5:0] led,			//从高到低分别表示x方向红黄绿,y方向红黄绿
	output reg[4:0] time_cnt,	//灯亮的时间的倒计时
	output reg      en			//数码管显示使能信号
);

//状态定义,独热码
parameter  x_pass  = 4'b0001,
			  x_flash = 4'b0010,
			  y_pass  = 4'b0100,
			  y_flash = 4'b1000;
			 
parameter  t1s = 27'd50_000_000;	//1S时间在系统时钟下需要计数次数

reg [26:0] cnt;					//1s计时计数器

//当前状态和次态
reg [3:0]  state,nextstate;

//红灯和黄灯亮的时间
parameter  time_x_pass  = 6'd30,
			  time_x_flash = 6'd3,
			  time_y_pass  = 6'd30,
			  time_y_flash = 6'd3;

//1s计数
always @(posedge clk,negedge rst_n)begin
	if(!rst_n)
		cnt <= 27'd0;
	else if(cnt < t1s)
		cnt <= cnt + 1'b1;
	else
		cnt <= 27'd0;
	end		 
	
//状态转换
always @(posedge clk,negedge rst_n)begin
	if(!rst_n)
		state <= x_pass;
	else 
		state <= nextstate;
	end

//组合逻辑,描述状态的转换
always @* begin
	if(!rst_n)
		nextstate <= x_pass;
	else begin
		case(state)
			x_pass:if(time_cnt == 1'b1)		//当time_cnt 计数到1时,进入下一状态
						nextstate <= x_flash;
					else
						nextstate <= x_pass;
			x_flash:if(time_cnt == 1'b1)
						nextstate <= y_pass;
					else
						nextstate <= x_flash;
			y_pass:if(time_cnt == 1'b1)
						nextstate <= y_flash;
					else
						nextstate <= y_pass;
			y_flash:if(time_cnt == 1'b1)
						nextstate <= x_pass;
					else
						nextstate <= y_flash;
			default:nextstate  <= x_pass;
		endcase
	end
end
		

//状态输出
always @(posedge clk,negedge rst_n)begin
	if(!rst_n)begin						//复位时,x方向红灯亮,y方向绿灯亮,且数码管不使能
		led 		<= 6'b100001;
		time_cnt <= time_x_pass;
		en 		<= 1'b0;
	end
	else begin
		en <= 1'b1;
	if(cnt == t1s)
		case(nextstate)
			x_pass:begin
					led <= 6'b100001;
					if(time_cnt == 1'b1)
						time_cnt <= time_x_pass;
					else
						time_cnt <= time_cnt - 1'b1;
					end
			x_flash:begin
					led <= 6'b010001;
					if(time_cnt == 1'b1)
						time_cnt <= time_x_flash;
					else
						time_cnt <= time_cnt - 1'b1;
					end
			y_pass:begin
					led <= 6'b001100;
					if(time_cnt == 1'b1)
						time_cnt <= time_y_pass;
					else
						time_cnt <= time_cnt - 1'b1;
					end
			y_flash:begin
					led <= 6'b001010;
					if(time_cnt == 1'b1)
						time_cnt <= time_y_flash;
					else
						time_cnt <= time_cnt - 1'b1;
					end
			default:led <= 6'b100001;
		endcase
	end
end
		
endmodule

仿真文件及仿真结果

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

reg 			clk;
reg 			rst_n;
// wires                                               
wire [5:0]  led;
wire [5:0]  time_cnt;
wire 			en;

// assign statements (if any)                          
traffic_led i1 (
// port map - connection between master ports and signals/registers   
	.clk		(clk),
	.led		(led),
	.rst_n	(rst_n),
	.time_cnt(time_cnt),
	.en		(en)
);
initial                                                
begin                                                  
	clk 		 = 1'b0;
	rst_n 	 = 1'b0;
	#20 rst_n = 1'b1;
                      
end                 

always #2 clk = ~clk;                                   
                                         
endmodule

在这里插入图片描述
5,数码管显示模块代码

module smg_display(
	input 		clk,
	input 		rst_n,
	input [4:0] data,				//要显示的数据
	input 		en,				//数码管使能信号
	
	output reg [1:0] seg_sel,	//位选
	output reg [7:0] seg_led	//段选
);

localparam CLK_DIV = 4'd10;	//时钟分频系数,5MHZ的时钟,用于驱动数码管
localparam MAX_NUM = 13'd5000;//计数1ms

reg [3:0] 	clk_cnt;				//时钟分频计数器
reg 		 	d_clk;				//数码管驱动时钟
reg [7:0] 	num;					//8位的bcd码寄存器
reg [12:0]  cnt0;					//数码管驱动时钟计数器
reg 			flag;					//标记计数达到1ms
reg 			cnt_sel;				//数码管位选计数器
reg [3:0] 	num_disp;			//数码管当前显示数据

wire [3:0] data0;					//个位
wire [3:0] data1; 				//十位

assign data0 = data%10;
assign data1 = data/10;

//对系统十分频,得到驱动时钟d_clk
always @(posedge clk,negedge rst_n)begin
	if(!rst_n)begin
		clk_cnt <= 4'd0;
		d_clk   <= 1'b0;
	end
	else if(clk_cnt == (CLK_DIV/2 - 1'b1))begin
			clk_cnt <= 4'd0;
			d_clk   <= ~d_clk;
		end
	else begin
		clk_cnt <= clk_cnt + 1'b1;
		d_clk   <= d_clk;
	end
end

//将十进制数转换为用8421bcd码表示
always @(posedge d_clk,negedge rst_n)begin
	if(!rst_n)
		num <= 8'd0;
	else begin
		num[7:4] <= data1;
		num[3:0] <= data0;
	end
end

//每当计时1ms输出一个时钟周期的脉冲
always @(posedge d_clk,negedge rst_n) begin
	if(!rst_n) begin
		cnt0 <= 13'd0;
		flag <= 1'b0;
	end
	else if(cnt0 < MAX_NUM - 1'b1) begin
		cnt0 <= cnt0 + 1'b1;
		flag <= 1'b0;
		end
	else begin
		cnt0 <= 13'd0;
		flag <= 1'b1;
	end
end

//seg_sel在0,1之间转换,用于选择当前要显示的数码管
always @(posedge d_clk,negedge rst_n)begin
	if(!rst_n)
		cnt_sel <= 1'b0;
	else if(flag)
		cnt_sel <= ~cnt_sel;
	else
		cnt_sel <= cnt_sel;
end

//控制数码管位选信号,使两个数码管轮流显示
always @(posedge d_clk,negedge rst_n)begin
	if(!rst_n) begin
		seg_sel  <= 2'b11;
		num_disp <= 4'd0;
	end
	else begin
		if(en)begin
			case(cnt_sel)
				1'b0:begin
					seg_sel  <= 2'b10;
					num_disp <= num[3:0];
				end
				1'b1:begin
					seg_sel  <= 2'b01;
					num_disp <= num[7:4];
				end
				default:begin
					seg_sel  <= 2'b11;
					num_disp <= 4'd0;
				end
			endcase
		end
		else
			begin
				seg_sel  <= 2'b11;
				num_disp <= 4'd0;
			end
		end
	end
	
//控制数码管段选信号,显示字符
always @(posedge d_clk,negedge rst_n)begin
	if(!rst_n)
		seg_led <= 8'hc0;
	else begin
		case(num_disp)	
			4'd0 : seg_led <= 8'b01000000; //显示数字 0
			4'd1 : seg_led <= 8'b01111001; //显示数字 1
			4'd2 : seg_led <= 8'b00100100; //显示数字 2
			4'd3 : seg_led <= 8'b00110000; //显示数字 3
			4'd4 : seg_led <= 8'b00011001; //显示数字 4
			4'd5 : seg_led <= 8'b00010010; //显示数字 5
			4'd6 : seg_led <= 8'b00000010; //显示数字 6
			4'd7 : seg_led <= 8'b01111000; //显示数字 7
			4'd8 : seg_led <= 8'b00000000; //显示数字 8
			4'd9 : seg_led <= 8'b00010000; //显示数字 9
			default:seg_led <= 8'b01000000;
		endcase
	end
end

endmodule

仿真文件及结果

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

// test vector input registers
reg 			clk;
reg [4:0] 	data;
reg 			en;
reg 			rst_n;
// wires                                               
wire [7:0]  seg_led;
wire [1:0]  seg_sel;
wire 			cnt_sel;

// assign statements (if any)                          
smg_display i1 (
// port map - connection between master ports and signals/registers   
	.clk		(clk),
	.data		(data),
	.en		(en),
	.rst_n	(rst_n),
	.seg_led (seg_led),
	.seg_sel (seg_sel)
);
initial                                                
begin                                                  
    clk 	     = 1'b0;
	 en 	     = 1'b0;
	 data 	  = 5'd0;
	 rst_n 	  = 1'b0;
	 #20 rst_n = 1'b1;
		  en    = 1'b1;
		  data  = 5'd9;
	
end                  
always #1 clk = ~clk;                                                                                                  
                                     
endmodule

在这里插入图片描述
6,总结与反思
再一次的受到了巨大打击,本以为这么简单的东西很快就能做好,但还是耗了不少时间,尤其是在最开始竟然没有丝毫的思路,最后还是借鉴了别人的思路并且只是实现了灯亮,没有能够实现灯的闪烁。在写的过程中很多细节的东西很容易的就忽略了,经常就是这样想得很简单,看别人的代码也觉得容易,一到自己做动手了,就会出现各种问题。最严重的感觉现在的思路和大脑的灵活度都不如以前了。以前刚学完verilog做数字中的时候,各个模块的共能以及模块之间的联系都是很清楚的,而现在对模块的理解以及记忆就远不如从前了。可能是因为太久的时间没有认真用心去学习了,导致大脑都有些退化了。
自己还是太菜了,差到我自己都不知道该怎么说,所以啊,后面还是要多写代码,多思考才行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值