当前位置: 首页 > news >正文

Verilog和FPGA的自学笔记8——按键消抖与模块化设计

好几天不写文章了哈,真是不好意西~~

倒不是我偷懒,而是正在研读夏宇闻老师的《Verilog数字系统设计教程》。
收获是真大,我验证了一些自己之前的猜想,也纠正了自己的理解错误(tips:不少错误仍隐藏于之前的几篇笔记里……)

当然这也不能怪我,只能怪豆包Verilog水平太低哈哈(推卸责任ing……)

这几天在家自己实现了一个按键触发的宏观上的D触发器控制的led灯。简单点说。就是按一下,灯亮了,再看一下,灭了,再按一下,又亮了,再按一下,又(读者:无不无聊)

不是这有啥难的?这不现场敲都行?

always @(posedge key)led<= ~led;

哎,敲完就觉得不对了:咱做嵌入式开发时候,不得加个delay(5)来给key做消抖处理?

嗯,对,那正好上一次介绍了Verilog如何做延时(不知道的复习一下流水灯),那正好写一个就行了。

转念一想又不对了,Verilog咋知道按键按下再开始计时呢?这是一个过程,这不典型的软件编程思维思考硬件编程嘛?

哦,对,记得上次流水灯时写了个标志flag,那咱只要把key的边沿检测提取一下,此时立个flag(顺道立个FPGA的flag哈哈),然后让cnt开始计时就差不多啦~

0.模块化设计

Verilog最大的一个特点就是结构化,这样可以将庞大的工程分发给不同人完成,同时也使整个工程结构清晰。(个人感觉和C++里的函数作用挺像的。。。)
《Verilog数字系统设计教程》中讲到,一般RTL代码都是从上往下设计的,也就是先做顶级模块,之后再做下面的小模块。

这样做是有原因的。

讲点题外话,记得小学学画画,当时老师最多的教诲就是:XXX,别老盯着细节画!

这句话是有道理的,因为细节画好了,如果框架不合适,哎……重新来过吧您呐~~

而且模块化设计还有个好处。就是你可以一个模块一个模块的测试,做好一个确保没问题了,再做下一个,一步步来,最后项目成功的几率也更高。

所以先设计顶层模块(Top-module)!
不,应该先画画~
在这里插入图片描述
再一次露一手我优秀 稀烂 的画工(^_^)

可以看到,我们有一个顶层模块,内部包含了两个子模块,一个Debounce专门负责按键消抖,一个LED负责控制LED。通过这样的模块化设计,我们很容易在下次使用时直接搬运debounce,从而避免重复造轮子。

Top module如下:
(读者:哎啥是顶层模块?)
(作者:大概就是统领全局的模块,我这样理解应该没问题……)

module top_module(input sys_clk,input sys_rst,input key, output led
);wire key_posedge;led u_led(.key_posedge(key_posedge),.sys_clk    (sys_clk),.sys_rst    (sys_rst),.led        (led)
);debounce u_debounce(.sys_clk    (sys_clk),.sys_rst    (sys_rst),.key        (key),.key_d(key_posedge)
);endmodule

我们在设计顶层模块时,可以把整个大模块想象为一个黑箱,只考虑需要给它什么,以及我们需要得到什么,这样思路就会清晰许多~

至于下面的例化模块,我并不是一开始就写好的,而是现在其他两个.v文件里写好模块接口定义部分以后再回来写的。关于书写顺序,大家怎么舒服怎么来~

就是这么简单!

1.边沿检测!

关于边沿检测的实现,我们可以画个波形图来理解,假设我们的时钟clk和按键key的理想信号:
在这里插入图片描述

先把key寄存一下,并增加其取反信号:
在这里插入图片描述

我们取key和~key1的位与结果:
在这里插入图片描述
这样就提取了完美的key上升沿信号key_posedge!

always@(posedge sys_clk or negedge sys_rst) beginif(!sys_rst)key1 <= 1'b0;elsekey1 <= key;
endassign	key_posedge = key & ~key1;		//取上升沿

我们一般通过以上的always语块来寄存目标信号,并通过下面的assign语句提取上升沿。

有些时候,我们也需要提取下降沿甚至是双沿(上升沿+下降沿),此时我们只要抄上always语句,再选择以下对应的assign就阔以了~

assign	key_posedge = ~key & key1;		//取下降沿
assign	key_posedge =  key ^ key1;		//取双沿

至于原理嘛……懒得码字了,就靠大家自己啦哈哈!

2.Debounce模块设计

(就不写中文~)

根据上次流水灯的经验,咱最好把不同的功能实现放到不同的always块里。一个块对应一个功能,这样好~

所以类似的,一个采样块,一个计数块,一个……把按键信号送出去的块(哎呀第三个块我起不出俩字的名%#@&)

刚不久我们讨论了如何提取信号边沿(延时一拍),但现实中的key可不见得一定和clk同步,甚至有可能当你按下按键时,也是clk恰好到来之时。

(在一个系统中,这种不合群的信号也称为异步信号)

那这样就不太好办,时间太短可能导致信号提取不出来。所以我们在采样块中选择延时两拍的方式,确保信号采样正确:

always @(posedge sys_clk or negedge sys_rst)beginif(!sys_rst) beginkey1 <= 1'b1;key2 <= 1'b1;endelse beginkey1 <= key;key2 <= key1;end
end

key1延时一拍,key2延时两拍。这样确保了key1和key2中间严格间隔一个clk。

所以,在FPGA这种并行性很强的器件里,为了尽量让所有的信号都听从指挥,我们通常做成同步时序而不用异步时序,这样可以保证系统工作的稳定性。

那啥就是同步时序呢?

就是不管什么情况,你always块全由clk触发就行了!

就比如我这个按键消抖可以这样写:

always @(posedge sys_clk or negedge sys_rst)beginif(!sys_rst)cnt <= 18'b0;else if(key1 != key2)cnt <= CNT_MAX;else if(cnt > 18'b0)cnt <= cnt - 18'b1;else begincnt <= 18'b0;flag <= 1'b1;end
endalways @(posedge flag or negedge sys_rst)beginif(!sys_rst)led<= 1'b0;elseled <= ~led;
end

也可以这样写:

always @(posedge sys_clk or negedge sys_rst)beginif(!sys_rst)cnt <= 18'b0;else if(key1 != key2)cnt <= CNT_MAX;else if(cnt > 18'b0)cnt <= cnt - 18'b1;elsecnt <= 18'b0;
endalways @(posedge sys_clk or negedge sys_rst)beginif(!sys_rst)key_d <= 1'b1;else if(cnt == 18'b0)key_d <= key2;elsekey_d <= key_d;
end

可以清晰的看到,第一段代码的第二个always始终由flag的上升沿触发。

虽然不是不行,但你会发现所有always块都由clk触发的这段代码,它的整个同步感很强,那系统工作时出错概率自然就会低很多~

第一种的逻辑简单,更符合我们的认知。但为了效果更好,我们以后就强迫自己直接把(posedge sys_clk or negedge sys_rst)这个条件贴上,写完之后考虑如何用别的组合电路实现功能。

行啦,大家把消抖部分的模块自己写写改改,这次代码最难的部分也就差不多啦~~

关于仿真的initial语句今天多说两句。

关于消抖模块的仿真文件,如果按照我们以前的写法是这样的:

`timescale 1ns/1nsmodule tb_debounce();reg sys_clk;
reg sys_rst;
reg key;
wire key_d;initial beginsys_clk <= 1'b0;sys_rst <= 1'b0;key <= 1'b1;#20sys_rst <= 1'b1;#80key <= 1'b0;#5key <= 1'b1;#5key <= 1'b0;#7key <= 1'b1;#9key <= 1'b0;#300key <= 1'b1;#9key <= 1'b0;#7key <= 1'b1;#5key <= 1'b0;#5key <= 1'b1;endalways #1 sys_clk <= ~sys_clk;debounce #(18'd25) u_debounce (.sys_clk    (sys_clk),.sys_rst    (sys_rst),.key        (key),.key_d(key_d)
);endmodule

这两天读《Verilog数字系统设计教程》,学了个fork-join语块,感觉挺适合某些人的思维(比如我),于是在这顺道分享分享~

fork-join语块与begin-end语块最大的区别是,前者是并行块,内部语句同时执行,后者是顺序块,内部语句依次执行。

书中有句话写的特别好,就是:fork像是一个分支的起点,打开各条路,而join则将所有分支收束起来。
在这里插入图片描述

而begin-end更像一串糖葫芦:

在这里插入图片描述

就比如上面写的让key翻来覆去的initial语句,也可以用fork-join语块改写成下面这样:

`timescale 1ns/1nsmodule tb_debounce();reg sys_clk;
reg sys_rst;
reg key;
wire key_d;initial fork#0   sys_clk <= 1'b0;#0   sys_rst <= 1'b0;#0   key <= 1'b1;#20  sys_rst <= 1'b1;#100 key <= 1'b0;#105 key <= 1'b1;#110 key <= 1'b0;#117 key <= 1'b1;#126 key <= 1'b0;#400 key <= 1'b1;#409 key <= 1'b0;#416 key <= 1'b1;#421 key <= 1'b0;#427 key <= 1'b1;#700 key <= 1'b0;#705 key <= 1'b1;#710 key <= 1'b0;#717 key <= 1'b1;#726 key <= 1'b0;#1000 key <= 1'b1;#1009 key <= 1'b0;#1016 key <= 1'b1;#1021 key <= 1'b0;#1027 key <= 1'b1;joinalways #1 sys_clk <= ~sys_clk;top_module u_top_module(.sys_clk(sys_clk),.sys_rst(sys_rst),.key(key),.led(led)
);endmodule

这样貌似再时间尺度上清晰了许多。
不过这个看个人习惯哈~ 没必要为了追求新颖就写这个……

哦对了,这里还需要介绍以下参数(parameter)的一个用法。
当模块被实例化时,可以通过 #(参数值) 这种方式更改模块内部的初始值。
比如上面测试用的代码最后,在原模块名称和例化模块名称中间写了个#(18’d25),这意思就是要让里面的CNT_MAX初始化时等于18’d25。

这样可就方便许多啦。之前因为延时时间过长不便于仿真,我们不得不把CNT的初值删一些0去,但下载验证时有时就忘了改回去……捂脸。这下我们只在仿真时通过这种方式进行初值重装,同时不影响vivado对rtl代码综合的那部分,理想!

注:关于多个参数传递等需要时我们再介绍~

下面是我的仿真波形,大家可以参考下~
在这里插入图片描述
可以看到debounce模块是成功的。

3.LED控制

这里和边沿检测一毛一样,自己怎么喜欢怎么来~

很多按键的操作,我看市面上很多都是上升沿触发,就是它得等你松开按键之后才有反应(while(!key)),但我就不喜欢,我喜欢让它一按下去就有反应!

所以在我的程序中,提取了下降沿:

module led(input key_posedge,input sys_clk,input sys_rst,output reg led
);reg key;
wire led_filp;always @(posedge sys_clk or negedge sys_rst)beginif(!sys_rst)key <= 1'b0;elsekey <= ~key_posedge;
endalways @(posedge sys_clk or negedge sys_rst)beginif(!sys_rst)led <= 1'b0;else if(led_filp)led <= ~led;else led <= led;
endassign	led_filp = ~key & ~key_posedge;endmodule

尽管消抖模块那边我们已经打了两拍,但这里为了提取边沿,我们仍需再打一拍,这样才能弄个脉冲出来~

4.总体仿真

在这里插入图片描述
字有点小哈,大体凑合看看形状就得了……

今天这篇真是累啊,足足6000多字,也是我这段时间几乎全部收获了……

这个工程算是第一次采用多文件模块化设计,第一次引入顶层模块,对Verilog的层次感又有了新的体会……

不知道后面会先写状态机还是IP核,可能又要闭关一段时间了,大家期待吧~~

如果有不明白或错误之处,也希望大家在评论区给出,帮助大家的同时也能再次提升自己对于FPGA和Verilog的理解,感谢大家!!

系列链接:
上一篇:Verilog和FPGA的自学笔记7——流水灯与时序约束(XDC文件的编写)
下一篇:码字ing……

http://www.dtcms.com/a/553387.html

相关文章:

  • 深入解析 display: flow-root:现代CSS布局的隐藏利器
  • 汕头网站制作方法购物网站价格
  • 电商网站建设精准扶贫的目的建筑施工特种证书查询入口官网
  • spring-ai advisors 使用与源码分析
  • 关键词解释:点积(Dot Product)在深度学习中的意义
  • 本地部署DeepSeek-OCR:打造高效的PDF文字识别服务
  • 机器视觉系统中工业相机的常用术语解读
  • 【论文精读】GenRec:基于扩散模型统一视频生成与识别任务
  • seo提高网站排名wordpress内容页不显示
  • Velero(原名Heptio Ark) 是一个专为 Kubernetes 设计的开源备份恢复工具
  • 企业网站模板中文 产品列表深圳福田区住房和建设局网站
  • 制作网站的价格一般由什么组成
  • Spring MVC 架构总览与请求处理流程
  • 网站推广的优势有做二手厨房设备的网站吗
  • 请问聊城做网站wordpress模板个人博客
  • 蒲福风力等级表
  • 小小电脑安装logisim-evolution
  • C# 六自由度机械臂正反解计算
  • 【开题答辩全过程】以 基于Java的旅游网站的设计与开发为例,包含答辩的问题和答案
  • 【深入学习Vue丨第一篇】Props 完全指南
  • U-net 系列算法总结
  • 什么网站可以做模型挣钱网站建设公司有多少家
  • 网站建设的杂志建筑专业网站建设
  • vue3+ts面试题(一)JSX,SFC
  • 网站开发 数字证书网站制作设计方案
  • PCB设计----阻抗不连续的解决方法
  • 网站制作北京网站建设公司哪家好安平县哪家做网站
  • 14. setState是异步更新
  • 22. React中CSS使用方案
  • 深度对比 ArrayList 与 LinkedList:从底层数据结构到增删查改的性能差异实测