verilog设计抢答器【附源码】

抢答器设计

  • 1、实验平台
  • 2、实验目的
    • 2.1、实验内容
  • 3、实验流程
    • 3.1、实验原理
    • 3.2、系统架构
    • 3.3、子功能模块设计
      • 3.3.1、中央控制模块
          • 模块框图
          • 信号定义
          • 设计文件
      • 3.3.2、数码管驱动模块
          • 设计文件
      • 3.3.3 LED驱动模块
      • 3.3.4、按键消抖模块
    • 3.4仿真验证
    • 3.4、板级验证
      • 3.4.1、顶层文件
  • 4、总结

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、系统架构

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

verilog设计抢答器【附源码】_第1张图片

3.3、子功能模块设计

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

3.3.1、中央控制模块

模块框图

verilog设计抢答器【附源码】_第2张图片

信号定义
信号名 端口类型 数据位宽 信号说明
Clk i 1 输入时钟信号,50MHz
Rst_n i 1 输入复位信号,低电平有效
key_A i 1 选手A按键
key_B i 1 选手B按键
key_C i 1 选手C按键
key_0 i 1 主持人按键
Data_time O 16 10s倒计时数据
Led_en O 1 倒计时结束,使能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;
	always@(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

	always@(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
	
	always@(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

	always@(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;
	
	
	
	always@(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;
//描述位选信号切换
	//描述刷新计数器
	always@(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
	
	always@(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
	
//段选信号描述
	always@(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
	
	always@(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
	always@(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 输出
	always@(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;
	always@(*)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 

verilog设计抢答器【附源码】_第3张图片

3.4、板级验证

3.4.1、顶层文件

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

RTL视图
verilog设计抢答器【附源码】_第4张图片

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

4、总结

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

你可能感兴趣的:(FPGA学习,fpga开发,Verilog,抢答器)