【FPGA实战篇一】按键消抖及流水点灯

一、FPGA简识

1、认识FPGA

FPGA是一种数字集成电路芯片,英文全称为Field Programmable Gate Array,中文名称为“现场可编程逻辑门阵列”。FPGA是数字电路的物理实现方式之一[1]。与数字电路的另一种重要实现方式ASIC(Application Specific Integrated Circuit,专用集成电路)芯片相比,FPGA的一项重要特点是其可编程特性,即用户可通过程序指定FPGA实现某一特定数字电路。
FPGA开发编程语言
设计FPGA的常用编程语言有Verilog HDL和VHDL,其中Verilog HDL为国内常用。此外,System Verilog,Xilinx HLS,Chisel等也可以作为FPGA的编程语言,但它们并未被广泛使用。与C/C++,Jave,Python等计算机编程语言不同的是,FPGA编程语言属于硬件编程语言,因此在使用FPGA编程语言时不能照搬软件设计思路。
FPGA开发设计流程
FPGA的典型设计流程,开发人员首先要根据设计说明(spec)制定电路方案,如果设计规模啊较大,开发人员还需要对整体电路方案进行划分。之后,根据电路方案,开发人员使用Verilog HDL或其他语言对电路进行描述和仿真。
在这里插入图片描述

2、按键消抖原理

按键消抖通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。
在这里插入图片描述

二、创建工程

1、quartus创建

创建一个new project
①打开quartus主界面,点击new project wizard
在这里插入图片描述
②点击【File】→【New project Wizard】
在这里插入图片描述
进入工程创建界面,按照图示操作
在这里插入图片描述
点击next,直至跳至如下界面
在这里插入图片描述
按照图中提示进行相关选择
芯片:EP4CE6F17C8
在这里插入图片描述
点击next,直至出现如下界面,点击finish
在这里插入图片描述
创建工程完成,首界面如图
在这里插入图片描述
添加编写程序的文件(Verilog HDL

【File】→【New】→【Verilog HDL】
在这里插入图片描述
至此,用quartus的方式创建工程就完成了,下面我们介绍另外一种工程创建方式。

2、文件夹文本方式创建

新建一个文件夹,填写项目名称(鼠标右击可以选择新建文件夹以及文本文档)
在这里插入图片描述
在此文件夹下创建三个文件夹
在这里插入图片描述
在rtl文件下新建文本文档(你所需要编写的项目模块以及其名称)
在这里插入图片描述
注意:要修改文本扩展名,按图示勾选
在这里插入图片描述
打开quartus,点击New project Wizard,在路径选择上选择你创建的项目下prj文件
在这里插入图片描述
此后步骤同上,创建后界面如图
在这里插入图片描述
导入我们刚刚创建的文本文档
在这里插入图片描述
进入下示界面,选择创建的文本文档(选择当前需要编程的文档)
在这里插入图片描述
点击ok,可以看到如示界面
在这里插入图片描述
至此,有关quartus的工程创建部分就结束了。(我这里的文件都是为了演示步骤用,名字随便取的,下面的讲解是严格按照工程实践进行的)

三、Verilog 代码编写

顶层模块key_led

module key_led(
	input							clk		,		//系统时钟
	input 						rst_n		,		//复位 低电平有效
	
	input		wire				key_in	,		//顶层模块定义wire型
	output 	wire	[3:0]		led
	);

	wire press;

	//模块例化
	key_debounce u_key_debounce(
		.clk				(clk		),
		.rst_n			(rst_n	),
		.key				(key_in	),
		.press			(press	)			//按键按下标志
	);
	
	key_driver u_key_driver(
		.clk				(clk		),
		.rst_n			(rst_n	),
		.en				(press	),
		.led_o			(led		)
	);
endmodule

驱动模块key_driver

module key_driver(
	
	input		wire				clk		,
	input		wire				rst_n		,
	input		wire				en			,
	
	output	reg	[3:0]		led_o		
	
	);
	
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			led_o <= 0;
		end
		else if(en) begin
			led_o <= {
    
    ~led_o[0], led_o[3:1]};
		end	
	end
	
endmodule

key_debounce

module key_debounce(
		input 	wire		clk		,		//50MHz	20ns
		input 	wire		rst_n		,
		input 	wire		key		,
		output 	reg 		press				//按键按下标志
	);
	
	//参数
	parameter	DELAY_TIME = 1000_000;	//延时20ms
	
	//信号定义
	reg				key_r0		;		//同步 当前时钟周期输入状态
	reg				key_r1		;		//打拍 前一个时钟周期输入的状态
	wire				key_nedge	;		//下降沿
	
	reg	[19:0]	delay_cnt	;		//计数20ms,需要20ms/20ns = 1000_000
	reg				delay_flag	;
	
	//同步
	always @(posedge clk or negedge rst_n) begin
		if(rst_n == 1'b0) begin
			key_r0 <= 1'b1;
			key_r1 <= 1'b1;
		end
		else begin
			key_r0 <= key;
			key_r1 <= key_r0;
		end
	end
	
	assign key_nedge = ~key_r0 & key_r1;	//检测下降沿
	//assign key_pedge = key_r0 & ~key_r1;	//检测上升沿
	
	//delay_cnt
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			delay_cnt <= 0;
		end
		else if(delay_flag) begin
			if(delay_cnt == DELAY_TIME - 1) begin
				delay_cnt <= 0;
			end
			else begin
				delay_cnt <= delay_cnt + 1;
			end
		end
	end
	
	//delay_flag
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			delay_flag <= 1'b0;
		end
		else if(key_nedge)begin
			delay_flag <= 1'b1;
		end
		else if(delay_cnt == DELAY_TIME - 1) begin
			delay_flag <= 1'b0;
		end
	end
	
	//press
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			press <= 1'b0;
		end
		else if(delay_cnt == DELAY_TIME - 1)begin
			press <= ~key_r0;
		end
		else begin
			press <= 1'b0;
		end
	end

endmodule
	

key_led_tb

//	        单位/精度
`timescale 1ns/1ns

module key_led_tb();

	reg				tb_clk	;
	reg				tb_rst_n	;
	
	reg				tb_key	;
	
	wire	[3:0]		tb_led	;
	
	reg	[5:0]		i			;
	
	key_led u_key_led(
		.clk			(tb_clk),		//系统时钟
		.rst_n		(tb_rst_n),		//复位 低电平有效
	
		.key_in		(tb_key),		//顶层模块定义wire型
		.led			(tb_led)
	);
	
	defparam		u_key_led.u_key_debounce.DELAY_TIME = 20;
	parameter	CLOCK_PERIOD = 20;
	
	initial		tb_clk = 1'b0;
	
	always #(CLOCK_PERIOD/2)  tb_clk =	~tb_clk;
	
	initial i = 0;
	
	initial	begin
		tb_rst_n = 1'b0;
		tb_key = 1'b1;
		#(CLOCK_PERIOD * 20);
		tb_rst_n = 1'b1;
		#(CLOCK_PERIOD * 20);
		for(i = 0; i < 100; i = i + 1) begin
			tb_key = {
    
    $random};
			#(CLOCK_PERIOD * 30);
		end
		tb_key = 1'b1;
		#(CLOCK_PERIOD * 30);
		$stop;		//停止仿真
	end

endmodule

需要添加的文本如下
在这里插入图片描述
编译代码
在这里插入图片描述
编译结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210712224936351.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1FXRVJUWXp4dw==,size_16,color_FFFFFF,t_70
如果有报错,可以双击报错地方,回到错误地方进行修改。

四、模拟仿真

设置modelsim(安装可以参考我以前的博客)
【Tools】→【 op…】
在这里插入图片描述
按照图中提示操作
在这里插入图片描述
查看电路图
在这里插入图片描述
在这里插入图片描述
查看任意一个展开图
在这里插入图片描述
设置testbeach
在这里插入图片描述
在这里插入图片描述
开始仿真
在这里插入图片描述
仿真界面
在这里插入图片描述
这样就可以开始仿真了。
点击sim,添加仿真波形
在这里插入图片描述
鼠标右击添加波形
在这里插入图片描述
选择全部
在这里插入图片描述
点击run,查看波形
在这里插入图片描述

五、开发板验证

编辑引脚约束文件
在这里插入图片描述

set_location_assignment		PIN_E1	-to	clk
set_location_assignment		PIN_E15	-to	rst_n

set_location_assignment		PIN_E16	-to	key_in
set_location_assignment		PIN_G15	-to	led[0]
set_location_assignment		PIN_F16	-to	led[1]
set_location_assignment		PIN_F15	-to	led[2]
set_location_assignment		PIN_D16	-to	led[3]

添加脚本
在这里插入图片描述
双击文件
在这里插入图片描述
点击快捷键查看引脚约束状况
在这里插入图片描述
或者按照图示选择
在这里插入图片描述

查看引脚约束
在这里插入图片描述
连接开发板并打开电源,点击编译
在这里插入图片描述
如果编译出现错误,按照下面提示进行设置再编译
【assessments】→【device】
在这里插入图片描述
在这里插入图片描述
再进行如下设置(点击OK)
在这里插入图片描述
编译成功,出现如下界面(这里没有连接到开发板)
在这里插入图片描述
如果这里没有驱动,则需要我们下载
在这里插入图片描述
连接开发板,烧录
在这里插入图片描述
结果演示(通过k2按键,实现灯的亮灭)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

六、参考资料

简单的Quartus 18.0 入门级别使用(二)——创立file+仿真模拟.

猜你喜欢

转载自blog.csdn.net/QWERTYzxw/article/details/118684663