设计一个电路,使用时序逻辑对一个单bit信号进行毛刺滤除操作。高电平或者低电平宽度小于4个时钟周期的为毛刺。用Verilog或者VHDL写出代码。(大疆FPGA逻辑岗B卷) 解析:这道题一眼看上去让人感觉很简单,使答题者降低了对该题目的警惕,因为在做题的过程中没法进行在线综合与仿真,所以直到答完交卷都无法发现自己的错误,反而沉浸在解出题目所获得的成就感中。如果能够仔细分析,按照标准规范的步骤来,即便不进行综合与仿真,也能够避免一些不必要的错误,接下来请看我们的分析过程。 1)题干中有效信息的获取与整理 通读题目后发现题干很简短,要求也很清晰,就是要实现一个“滤波”功能的逻辑设计。从题干中获取的主要信息有两个: ①要处理的是单bit信号; ②滤除高电平或者低电平宽度小于4个时钟周期的为毛刺。 2)详细的分析及波形图的绘制 如图1所示,用波形图的方式表达出所要做的工作。data_in就是外部输入的单bit信号,假设它已经同步于系统时钟sys_clk了,在图中模拟了输入信号data_in的波形,并标出了其中稳定态和抖动态(高电平或者低电平宽度小于4个时钟周期的为毛刺),滤除抖动态后的波形为data_out信号。 图1 方法一:电平计数法 解这道题目最容易让人想到的方法是用一个计数器来计数高电平或者低电平所用的时钟周期个数,如果小于4那就滤除掉。如图2所示,产生一个cnt计数器,计数值为0~3循环。计数器最关键的设计就是控制其何时开始计数、何时清零。这里,需要cnt计数器在data_in信号高电平和低电平期间都进行计数(蓝色竖虚线是计数的低电平,绿色竖虚线是计数的高电平),因为data_in信号是单bit的,也就是cnt计数器会在整个data_in的输入过程中都进行计数,这样cnt计数器的存在就没有任何意义了,所以cnt计数器并不会像所画的这种计数值的变化来进行计数。就想如果能够让高电平和低电平分开计数可能效果会好。 图2 如图3所示,设计两个计数器,其中cnt_low用于计数data_in中的低电平,cnt_high用于计数data_in中的高电平,且两个计数器不能同时计数,一个计数器在计数的时候另一个计数器要清零。这两个计数器的计数值只要有一个达到3就可以让输出信号data_out等于data_in的值。分析到这里,大多数同学和我们所想的是一样的,不画波形的同学往往分析到这里就认为已经结束了,开始进行代码的编写。但如果画出波形仔细观察会发现在红色圈1标注的位置并不需要拉高,因为红色圈2处data_in信号高电平的持续时间小于4个时钟周期,是毛刺信号,应该被滤除才对,如果能够分析到这里就说明你已经很棒了。然后我们就思考如何解决这个问题。 图3 如图4所示,一些同学会想之所以会产生上图中的现象主要是因为cnt_high计数器已经计数到3了,表示已经滤除掉毛刺了,但其实还没有滤除,因为产生了误判,开始怀疑是计数器的计数值不够的原因,所以又想办法让计数器计数到4,但是新的问题又出现了,我们发现红色圈1标注的位置需要拉高,而红色圈2处data_in信号高电平的持续时间等于4个时钟周期,已经不再是毛刺信号了,不应该被滤除掉,与题目要求不符。分析到这里一些同学就进入了所谓的“坑”中不能自拔,完全被迷惑住了,其实是思考的方向错了。 图4 其实在图3中已经很接近答案了,落实到代码中其实就是少了条件而已,如图5所示,将图3中红色的竖虚线继续向上延申,将输出信号data_out等于data_in的值时的条件除了两个计数器计数到3以外再加一个条件就可以了,即在cnt_low计数器计数到3时且data_in还要为低电平,cnt_high计数器计数到3时且data_in还要为高电平,这个条件很容易漏掉,如果稍不注意就会掉到坑里。下面看到的data_out波形就和图1中data_out的一样了,可以完美的将高电平或低电平宽度小于4个时钟周期的毛刺滤除掉。 图5 方法二:边沿计数法 其实在刚开始分析题目的时候,大多数同学的第一反应是通过检测data_in信号高低电平的个数来滤除毛刺,这就把大家引入到了一条完全不同的解题思路中,如果一开始就想到通过检测data_in信号“沿”的方法也许就不会遇到上面的坑了。如图6所示,蓝色竖虚线为下降沿,绿色竖虚线为上升沿。 图6 找到了“沿”后,就可以通过沿来判断毛刺了,为什么可以这样做?原因是有电平的变化一定会产生沿的变化,这种转化的思想一定不要忘记,我们可以通过计数器计数上升沿和下降沿的个数来判断毛刺。计数器检测到上升沿或下降沿标志信号就说明此时可能有抖动产生,计数器清零准备好计数,如果计数器计数到3时还没有检测到新的沿说明之前检测到的沿变化不是抖动,是正常的信号变化,我们就可以让输出信号data_out等于输入信号data_in的值了。这种方法也可以完美的将高电平或低电平宽度小于4个时钟周期的毛刺滤除掉,且思路和代码的编写都更加简单。 图7 3)RTL代码的编写 方法一RTL功能代码如下所示: //--------------------------------------01module filter(02inputwire sys_clk ,//系统时钟50MHz03inputwire sys_rst_n ,//全局复位04inputwire data_in ,//单bit输入信号0506outputreg data_out //滤除高电平或者低电平宽度小于4个时钟周期后的单bit输出信号07);0809reg[1:0] cnt_low;10reg[1:0] cnt_high;1112//cnt_low:用于计数低电平宽度小于4个时钟周期的抖动13always@(posedgesys_clkornegedge sys_rst_n)14if(sys_rst_n==1'b0)15 cnt_low<=2'b0;16elseif(&cnt_low==1'b1||data_in==1'b1)//如果少了后一个条件会导致cnt_low没有及时清零而导致错误17 cnt_low<=2'b0;18elseif(data_in==1'b0)19 cnt_low<=cnt_low+1'b1;2021//cnt_high:用于计数高电平宽度小于4个时钟周期的抖动22always@(posedgesys_clkornegedge sys_rst_n)23if(sys_rst_n==1'b0)24 cnt_high<=2'b0;25elseif(&cnt_high==1'b1||data_in==1'b0)//如果少了后一个条件会导致cnt_high没有及时清零而导致错误26 cnt_high<=2'b0;27elseif(data_in==1'b1)28 cnt_high<=cnt_high+1'b1;2930//data_out:滤除高电平或者低电平宽度小于4个时钟周期后的单bit输出信号31always@(posedgesys_clkornegedge sys_rst_n)32if(sys_rst_n==1'b0)33 data_out<=1'b0;34elseif((&cnt_low==1'b1&&data_in==1'b0)||(&cnt_high==1'b1&&data_in==1'b1))//与上当前输入信号的状态是最关键的条件35 data_out<=data_in;3637endmodule //------------------------------------ 方法二RTL功能代码如下所示: //-------------------------------------01module filter(02inputwire sys_clk ,//系统时钟50MHz03inputwire sys_rst_n ,//全局复位04inputwire data_in ,//单bit输入信号0506outputreg data_out //滤除高电平或者低电平宽度小于4个时钟周期后的单bit输出信号07);0809reg data_in_reg;10reg[1:0] cnt;1112wire podge;13wire nedge;1415//data_in_reg:将单bit输入信号打一拍16always@(posedgesys_clkornegedgesys_rst_n)17if(sys_rst_n==1'b0)18 data_in_reg<=1'b0;19else20 data_in_reg<=data_in;2122//podge:对输入信号进行上升沿检测23assignpodge=data_in&&(~data_in_reg);2425//nedge:对输入信号进行下降沿检测26assignnedge=~data_in&&data_in_reg;2728//cnt:用于计数宽度小于4个时钟周期的抖动29always@(posedgesys_clkornegedge sys_rst_n)30if(sys_rst_n==1'b0)31 cnt<=2'b0;32elseif(podge==1'b1||nedge==1'b1)33 cnt<=2'b0;34else35 cnt<=cnt+1'b1;3637//data_out:滤除高电平或者低电平宽度小于4个时钟周期后的单bit输出信号38always@(posedgesys_clkornegedge sys_rst_n)39if(sys_rst_n==1'b0)40 data_out<=1'b0;41elseif(&cnt==1'b1)42 data_out<=data_in_reg;4344endmodule //---------------------------------------- 4)Testbench代码的编写 这两种方法的法Testbench仿真代码可以使用同一个,如下所示: //--------------------------------------01`timescale1ns/1ns0203module tb_filter();0405//声明Testbench中信号的端口类型06reg sys_clk;07reg sys_rst_n;08reg data_in;0910wire data_out;1112//定义Testbench中的内部变量13reg[7:0] tb_cnt;1415//初始化系统时钟、全局复位16initialbegin17 sys_clk =1'b0;18 sys_rst_n<=1'b0;19#2020 sys_rst_n<=1'b1;21end2223//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz24always#10sys_clk=~sys_clk;2526//tb_cnt:通过该计数器的计数值来控制毛刺区间27always@(posedgesys_clkornegedgesys_rst_n)28if(sys_rst_n==1'b0)29 tb_cnt<=8'b0;30elseif(&tb_cnt==1'b1)31 tb_cnt<=8'b0;32else33 tb_cnt<=tb_cnt+1'b1;3435//data_in:模拟单bit输入信号36always@(posedgesys_clkornegedgesys_rst_n)37if(sys_rst_n==1'b0)38 data_in<=1'b0;39elseif((tb_cnt>=8'd20&&tb_cnt<=8'd28)||(tb_cnt>=8'd60&&tb_cnt<=8'd65)||(tb_cnt>=8'd80&&tb_cnt<=8'd85)||(tb_cnt>=8'd120&&tb_cnt<=8'd140))40 data_in<={$random}%2; //计数器在该区间内产生非负随机数0、1,模拟单bit输入信号高电平或者低电平宽度小于4个时钟周期的毛刺41elseif((tb_cnt<8'd20)||(tb_cnt>8'd65&&tb_cnt<8'd80))42 data_in<=1'b1; //计数器在该区间内保持为143elseif((tb_cnt>8'd28&&tb_cnt<8'd60)||(tb_cnt>8'd85||tb_cnt<8'd120)||(tb_cnt>8'd140))44 data_in<=1'b0; //计数器在该区间内保持为04546//----------------filter_inst------------47filter filter_inst(48.sys_clk (sys_clk ),//input sys_clk 49.sys_rst_n(sys_rst_n),//input sys_rst_n 50.data_in (data_in ),//input data_in5152.data_out (data_out )//output data_out53);5455endmodule //------------------------------------------- 5)ModelSim仿真验证 方法一的整体仿真波形如图8所示。 图8 如图9所示,我们将图8红色框中的信号放大后可以看到cnt_low和cnt_high两个计数器的计数值和输入数据data_in之间关系,也可以发现凡是高电平或者低电平宽度小于4个时钟周期的输入信号都已经被滤除掉了。 图9 方法二的整体仿真波形如图10所示。 图10 如图11所示,我们将红色框中的信号放大可以看到cnt计数器、podge、nedge沿标志信号和输入数据data_in之间关系,最后也能够实现滤除高电平或者低电平宽度小于4个时钟周期的输入信号。 图11 6)思考 方法一和方法二,虽然可以实现相同的毛刺的效果,但还是有细微差别的,不知道大家能不能发现,可以仔细观察并思考。 7)总结 通过上面的分析验证可以看到两种方法各有特点,方法一容易通过第一感觉想到,但是容易出错,其中还有坑的地方。方法二虽然不容易立刻想出,但更容易实现,且坑更少思路更清晰。希望大家在做相关题目时一定要用正确的分析方法和流程,画画波形,仔细分析,在平时的工程项目中也可以用该方法快速准确的设计出要想逻辑电路。
|