当前位置:网站首页>verilog设计抢答器【附源码】

verilog设计抢答器【附源码】

2022-07-07 09:02:00 青柠Miya

1、实验平台

软件:PC、Quartus Prime 18.1、Modelsim 10.5b
硬件:Altera FPGA开发板(EP4CE6E22F17C8)

2、实验目的

  • 1、掌握数码管动态刷新原理
  • 2、逻辑练习

2.1、实验内容

基于开发板上的8位8段数码管和4个机械按键,制作一个抢答器,相关要求如下:
1、	设置四个按键,其中三位选手A、B、C,主持人0;
2、	主持人具有清除所有状态权限
3、	每次抢答开始后,选手需要在10S做出选择,否测视为放弃,一旦某位选手按下按键后,另外两名选手按键将失效
4、	如果没有选手抢答,数码管显示10s倒计时,计时结束时,LED不停闪烁;同时,所有选手按键失效,直到主持人重新开始
5、	最左边显示显示倒计时,低三位从左至右分别表示选手ABC,当某位选手抢答后,将显示对应字符(A or B or C),同时倒计时暂停,直至主持人按下后,所有状态恢复,开启新一轮抢答。

3、实验流程

3.1、实验原理

根据开发板的原理图,可得到以下资料

数码管:本质上为一组发光二极管按照一定顺序排列而成,其显示原理与LED无异。
在这里插入图片描述

在这里插入图片描述

根据硬件原理图所示,发光二极管,所有的阳极都接通3.3V的正电压,也即—高电平,所以如果我们想要
发光二极管导通的话,需要在阴极接通低电平,就可以让LED亮起来。

3.2、系统架构

根据系统要求,可以得到以下框架分布

在这里插入图片描述

3.3、子功能模块设计

根据系统构建,可得到以下模块

3.3.1、中央控制模块

模块框图

在这里插入图片描述

信号定义
信号名端口类型数据位宽信号说明
Clki1输入时钟信号,50MHz
Rst_ni1输入复位信号,低电平有效
key_Ai1选手A按键
key_Bi1选手B按键
key_Ci1选手C按键
key_0i1主持人按键
Data_timeO1610s倒计时数据
Led_enO1倒计时结束,使能LED闪烁
设计文件
/*================================================*\ Filename ﹕ctrl_mode.v Author ﹕Adolph Description ﹕中控模块,指令解析,数据显示控制 Called by ﹕responder.v Revision History ﹕ 2022-6-20 15:20:43 Revision 1.0 Email﹕[email protected] Company﹕ \*================================================*/
module ctrl_mode(
	input				clk		,
	input				rst_n	,
	input		[3:0] 	key_ctrl, //按键消抖信号
	
	output		[3:0]	data	,
	output	reg [3:0] 	key_sta	, //按键状态信号
	output	reg  		led_en	
);
//parameter declarations
	parameter TIME_S = 26'd50_000_000;
	
//internal reg / wire signals
	reg 	[25:0]	cnt_1s	 ;
	wire			add_1s	 ;
	wire			end_1s	 ;
	reg				latch_log;
	
	reg		[3:0]	cnt_delay;//显示9-0
	wire			add_delay;
	wire			end_delay;
	
	assign data   = cnt_delay;
// assign led_en = end_delay;
	[email protected](posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			led_en <= 1'b0;
		end
		else if(key_ctrl[0])begin
			led_en <= 1'b0;
		end
		else if(end_delay)begin
			led_en <= 1'b1;
		end
		else begin
			led_en <= led_en;
		end
	end

	[email protected](posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			key_sta <= 4'b0000;
		end
		else if(key_ctrl[0])begin //主持人按键按下之后
			key_sta <= 4'b0001;
		end
		else if(latch_log)begin 
			key_sta <= key_sta;
		end
		else begin
			case(key_ctrl)
				4'b0010:key_sta[1] <= 1'b1;
				4'b0100:key_sta[2] <= 1'b1;
				4'b1000:key_sta[3] <= 1'b1;
				default: ;
			endcase
		end
	end
	
	[email protected](posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			latch_log <= 1'b1; //初始上电后,锁定生效
		end
		else if(key_ctrl[0])begin //主持人按下,锁定失效,选手按下有效
			latch_log <= 1'b0;
		end
		else if(key_ctrl[1] || key_ctrl[2] || key_ctrl[3] || end_delay)begin //任一选手按下后,锁定生效;倒计时结束,同样不允许选手按键
			latch_log <= 1'b1; 
		end
		else begin
			latch_log <= latch_log;
		end
	end

	[email protected](posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			cnt_1s <= 'd0;
		end
		else if(add_1s)begin
			if(end_1s)begin
				cnt_1s <= 'd0;
			end
			else begin
				cnt_1s <= cnt_1s + 26'd1;
			end
		end
		else begin
			cnt_1s <= cnt_1s;
		end
	end 
	
	assign add_1s = key_sta == 4'b0001;
	assign end_1s = add_1s && cnt_1s >= TIME_S - 'd1;
	
	
	
	[email protected](posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			cnt_delay <= 'd10;
		end
		else if(key_ctrl[0])begin
			cnt_delay <= 'd10;
		end
		else if(add_delay)begin
			if(end_delay)begin
				cnt_delay <= cnt_delay;
			end
			else begin
				cnt_delay <= cnt_delay - 4'd1;
			end
		end
		else begin
			cnt_delay <= cnt_delay;
		end
	end 
	
	assign add_delay = end_1s;
	assign end_delay = add_delay && cnt_delay == 'd1 - 'd1;	

endmodule 

本模块较为简单,此处不做仿真验证,如有兴趣可自行验证

3.3.2、数码管驱动模块

数码管区驱动在之前的基础上有所改动,大家自行理解

设计文件
/*================================================*\ Filename ﹕seg_driver.v Author ﹕Adolph Description ﹕对输入的数据译码,并驱动数码管显示对应数据 Called by ﹕responder.v Revision History ﹕ 2022-5-30 14:27:22 Revision 1.0 Email﹕[email protected] Company﹕ \*================================================*/
module seg_driver(
	input				clk		,
	input				rst_n	,
	input		[03:0]	key_ctrl,
	
	input	  	[31:0]	dis_data,//倒计时数据
	output reg	[07:0]	dig_sel	,
	output reg	[07:0]	dig_seg	 
);
//wire [31:0]dis_data;


// assign dig_seg = 8'd0;
// assign dig_sel = 1'b0;
	localparam
		NUM_0  	= 8'hC0,
		NUM_1  	= 8'hF9,
		NUM_2  	= 8'hA4,
		NUM_3  	= 8'hB0,
		NUM_4  	= 8'h99,
		NUM_5  	= 8'h92,
		NUM_6  	= 8'h82,
		NUM_7  	= 8'hF8,
		NUM_8  	= 8'h80,
		NUM_9  	= 8'h90,
		NUM_A  	= 8'h88,
		NUM_B  	= 8'h83,
		NUM_C  	= 8'hC6,
		NUM_D  	= 8'hA1,
		NUM_E  	= 8'h86,
		NUM_F  	= 8'h8E,
		LIT_ALL	= 8'h00,
		BLC_ALL	= 8'hFF;
	parameter CNT_REF = 25'd1000;
	
	reg	[9:0]	cnt_20us; //20us计数器
	reg	[4:0] 	data_tmp; //用于取出不同位选的显示数据
	
// assign dis_data = 32'hABCD_4413;
//描述位选信号切换
	//描述刷新计数器
	[email protected](posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			cnt_20us <= 10'd0;
		end
		else if(cnt_20us >= CNT_REF - 10'd1)begin
			cnt_20us <= 10'd0;
		end
		else begin
			cnt_20us <= cnt_20us + 10'd1;
		end
	end
	
	[email protected](posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			dig_sel <= 8'hfe;//8'b1111_1110
		end
		else if(cnt_20us >= CNT_REF - 10'd1)begin
			dig_sel <= {
    dig_sel[6:0],dig_sel[7]};
		end
		else begin
			dig_sel <= dig_sel;
		end
	end
	
//段选信号描述
	[email protected](posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			data_tmp <= 5'd0;
		end
		else if(key_ctrl[0])begin
			data_tmp <= 5'h10;
		end
		else begin
			case(dig_sel)
				8'b1111_1110: begin
								if(key_ctrl[3])
									data_tmp <= 5'hc;
								else	
									data_tmp <= 5'h10;
							end
				8'b1111_1101: begin
								if(key_ctrl[2])
									data_tmp <= 5'hb;
								else	
									data_tmp <= 5'h10;
							end
				8'b1111_1011: begin
								if(key_ctrl[1])
									data_tmp <= 5'ha;
								else	
									data_tmp <= 5'h10;
							end
				8'b1111_0111:data_tmp <= 5'h10;
				8'b1110_1111:data_tmp <= 5'h10;
				8'b1101_1111:data_tmp <= 5'h10;
				8'b1011_1111:data_tmp <= 5'h10;
				8'b0111_1111:data_tmp <= dis_data[3-:4];
				default: data_tmp <= 5'hF;
			endcase
		end
	end
	
	[email protected](posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			dig_seg <= LIT_ALL;
		end
		else begin
			case(data_tmp)
				5'h0 : dig_seg <= NUM_0;
				5'h1 : dig_seg <= NUM_1;
				5'h2 : dig_seg <= NUM_2;
				5'h3 : dig_seg <= NUM_3;
				5'h4 : dig_seg <= NUM_4;
				5'h5 : dig_seg <= NUM_5;
				5'h6 : dig_seg <= NUM_6;
				5'h7 : dig_seg <= NUM_7;
				5'h8 : dig_seg <= NUM_8;
				5'h9 : dig_seg <= NUM_9;
				5'hA : dig_seg <= NUM_A;
				5'hB : dig_seg <= NUM_B;
				5'hC : dig_seg <= NUM_C;
				5'hD : dig_seg <= NUM_D;
				5'hE : dig_seg <= NUM_E;
				5'hF : dig_seg <= NUM_F;
				5'h10: dig_seg <= BLC_ALL;
				default:dig_seg <= LIT_ALL;
			endcase 
		end
	end

endmodule 

3.3.3 LED驱动模块

设计文件

module led_water(
	input 				clk		,//50MHz
	input				rst_n	,//low valid
	input				blink_en,//闪烁使能信号

	output	reg	[7:0]	led_o
);
//参数定义
	parameter CNT_MAX = 25'd500_0000;

//信号定义
	reg		[24:0] 	cnt;//500ms计数器,计数最大值 2500_0000,
	
	//计时 0-500ms
	[email protected](posedge clk or negedge rst_n)begin
		if(!rst_n)
			cnt <= 25'd0;
		else if(blink_en)begin
			if(cnt >= CNT_MAX - 25'd1)
				cnt <= 25'd0;
			else
				cnt <= cnt + 1'b1;
		end
		else 
			cnt <= 25'd0;
	end
	
	//led 输出
	[email protected](posedge clk or negedge rst_n)begin
		if(!rst_n)
			led_o <= 8'b0000_0000; //all light
		else if(blink_en)begin
			if(cnt >= CNT_MAX - 25'd1)
				led_o <= ~led_o;
			else
				led_o <= led_o; //s
		end
		else begin
			led_o <= 8'b0000_0000;
		end
	end
endmodule 

3.3.4、按键消抖模块

该模块在之前有设计过,这里不做过赘述,可参考抖 verilog实现按键消

3.4仿真验证

`timescale 1ns/1ns 		//仿真系统时间尺度定义

`define clk_period 20  	//时钟周期参数定义 

module tb_responder(); 
//激励信号定义 
	reg				Clk		; 
	reg				Rst_n	; 
	reg		[3:00]	key_in	; //
	
//响应信号定义 
	wire 	[7:0]	dig_sel	;
	wire 	[7:0]	dig_seg	;
	wire 	[7:0]	led_o   ; 
	
	defparam responder.DELAY_TIME = 200;
	defparam responder.ctrl_mode.TIME_S = 200;
	defparam responder.seg_driver.CNT_REF = 100;
	
	reg		[55:00]	ASCILL; //
	
	localparam
		NUM_0  	= 8'hC0,
		NUM_1  	= 8'hF9,
		NUM_2  	= 8'hA4,
		NUM_3  	= 8'hB0,
		NUM_4  	= 8'h99,
		NUM_5  	= 8'h92,
		NUM_6  	= 8'h82,
		NUM_7  	= 8'hF8,
		NUM_8  	= 8'h80,
		NUM_9  	= 8'h90,
		NUM_A  	= 8'h88,
		NUM_B  	= 8'h83,
		NUM_C  	= 8'hC6,
		NUM_D  	= 8'hA1,
		NUM_E  	= 8'h86,
		NUM_F  	= 8'h8E,
		LIT_ALL	= 8'h00,
		BLC_ALL	= 8'hFF;
	[email protected](*)begin
		case(responder.seg_driver.dig_seg)
			NUM_0  	: ASCILL = "NUM_0 ";
			NUM_1  	: ASCILL = "NUM_1 ";
			NUM_2  	: ASCILL = "NUM_2 ";
			NUM_3  	: ASCILL = "NUM_3 ";
			NUM_4  	: ASCILL = "NUM_4 ";
			NUM_5  	: ASCILL = "NUM_5 ";
			NUM_6  	: ASCILL = "NUM_6 ";
			NUM_7  	: ASCILL = "NUM_7 ";
			NUM_8  	: ASCILL = "NUM_8 ";
			NUM_9  	: ASCILL = "NUM_9 ";
			NUM_A  	: ASCILL = "NUM_A ";
			NUM_B  	: ASCILL = "NUM_B ";
			NUM_C  	: ASCILL = "NUM_C ";
			NUM_D  	: ASCILL = "NUM_D ";
			NUM_E  	: ASCILL = "NUM_E ";
			NUM_F  	: ASCILL = "NUM_F ";
			LIT_ALL	: ASCILL = "LIT_ALL";
			BLC_ALL	: ASCILL = "BLC_ALL";
			default : ASCILL = "LIT_ALL";
		endcase
	end
		
//实例化
	responder	responder(
		/*input */.clk		(Clk	),
		/*input */.rst_n	(Rst_n	),
		/*input [3:0] */.key_in	(key_in	), //按键消抖信号
		
		/*output [7:0] */.dig_sel	(dig_sel),
		/*output [7:0] */.dig_seg	(dig_seg), 
		/*output [7:0] */.led_o    (led_o  )
	);

//产生时钟 
	initial Clk = 1'b0;		       		 
	always #(`clk_period / 2) Clk = ~Clk;  		 

//产生激励 
	initial  begin	 
		Rst_n = 1'b0;	
		key_in = 4'b1111;
		#(`clk_period * 20 + 3);	 
		Rst_n = 1'b1;	 
		#(`clk_period * 20); 
		
		press_key0;
		#(`clk_period * 2000);
		press_key3;
		$stop(2); 
	end	 

	reg	[15:0]	my_rand;
	task	press_key0 ;
		begin
			//前抖动
			repeat(10)begin
				my_rand = {
    $random} % 50	;
				#my_rand key_in[0] = ~key_in[0];
			end
			
			key_in[0] = 1'b0;
			#(400 * `clk_period);	//21ms > 20ms
			
			//后抖动
			repeat(11)begin
				my_rand = {
    $random} % 50	;
				#my_rand key_in[0] = ~key_in[0];
			end
			
			key_in[0] = 1'b1;
			#(1000 * `clk_period);	//21ms > 20ms
			
		end
	endtask
	
	task	press_key1 ;
		begin
			//前抖动
			repeat(19)begin
				my_rand = {
    $random} % 50	;
				#my_rand key_in[1] = ~key_in[1];
			end
			
			key_in[1] = 1'b0;
			#(400 * `clk_period);	//21ms > 20ms
			
			//后抖动
			repeat(10)begin
				my_rand = {
    $random} % 50	;
				#my_rand key_in[1] = ~key_in[1];
			end
			
			key_in[1] = 1'b1;
			#(1000 * `clk_period);	//21ms > 20ms
			
		end
	endtask
	
	task	press_key2 ;
		begin
			//前抖动
			repeat(13)begin
				my_rand = {
    $random} % 50	;
				#my_rand key_in[2] = ~key_in[2];
			end
			
			key_in[2] = 1'b0;
			#(400 * `clk_period);	//21ms > 20ms
			
			//后抖动
			repeat(10)begin
				my_rand = {
    $random} % 50	;
				#my_rand key_in[2] = ~key_in[2];
			end
			
			key_in[2] = 1'b1;
			#(1000 * `clk_period);	//21ms > 20ms
			
		end
	endtask
	
	task	press_key3 ;
		begin
			//前抖动
			repeat(15)begin
				my_rand = {
    $random} % 50	;
				#my_rand key_in[3] = ~key_in[3];
			end
			
			key_in[3] = 1'b0;
			#(400 * `clk_period);	//21ms > 20ms
			
			//后抖动
			repeat(11)begin
				my_rand = {
    $random} % 50	;
				#my_rand key_in[3] = ~key_in[3];
			end
			
			key_in[3] = 1'b1;
			#(1000 * `clk_period);	//21ms > 20ms
			
		end
	endtask
endmodule 

在这里插入图片描述

3.4、板级验证

3.4.1、顶层文件

顶层文件在此不作讲解,根据下列RTL视图,相信读者可以很轻易的完成相应代码设计

RTL视图
在这里插入图片描述

具体实现效果如预期所述,请大家自行探索

4、总结

本设计实现了基本的功能
在设计过程中,由于位宽不匹配(quartus不会报错,只会报警告),导致程序运行效果不正确,多次检错才确定具体原因
故,告诫大家,在进行信号描述的时候,一定要将位宽进行匹配
原网站

版权声明
本文为[青柠Miya]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_43546203/article/details/125460431