草庐IT

verilog设计抢答器【附源码】

青柠Miya 2024-07-16 原文

抢答器设计

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﹕adolph1354238998@gmail.com
			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﹕adolph1354238998@gmail.com
			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 

3.4、板级验证

3.4.1、顶层文件

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

RTL视图

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

4、总结

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

有关verilog设计抢答器【附源码】的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  3. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  4. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  5. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  6. ruby-on-rails - 设计注册确认 - 2

    我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:

  7. ruby-on-rails - 设计通过 reset_password_token 获取用户 - 2

    我正在尝试创建密码规则来设计可恢复的密码更改。我通过passwords_controller.rb做了一个父类(superclass),但我需要在应用规则之前检查用户角色,但我所拥有的只是reset_password_token。 最佳答案 假设您的模型是用户:User.with_reset_password_token(your_token_here)Source 关于ruby-on-rails-设计通过reset_password_token获取用户,我们在StackOverflow

  8. ruby-on-rails - Rails 5,公寓和设计 : sign in with subdomains are not working - 2

    我已经使用Apartment设置了一个Rails5应用程序(1.2.0)和Devise(4.2.0)。由于某些DDNS问题,应用只能在app.myapp.com下访问(请注意子域app)。myapp.com重定向到app.myapp.com。我的用例是每个注册该应用的用户(租户)都应该通过他们的子域(例如tenant.myapp.com)访问他们的特定数据。用户不应限定在其子域内。基本上应该可以从任何子域登录。重定向到租户的正确子域由ApplicationController处理。根据Devise标准,登录页面位于app.myapp.com/users/sign_in。这就是问题开始的

  9. ruby-on-rails - 设计中的 ArgumentError::RegistrationsController#new 错误的参数数量(2 代表 0..1) - 2

    我在关注RyanbatesRailsCast的devise和omniauth(第235集-devise-and-omniauth-revised)。当我尝试使用Twitter登录时,标题中不断出现错误。defself.new_with_session(params,session)ifsession["devise.user_attributes"]new(session["devise.user_attributes"],without_protection:true)do|user|user.attributes=paramsuser.valid?end完整跟踪:C:/Ruby20

  10. elasticsearch源码关于TransportSearchAction【阶段三】 - 2

    1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>

随机推荐