查看原文
其他

加油站| 毛刺滤除的代码应该如何写(大疆FPGA逻辑岗B卷)

相量子 达尔闻说 2021-01-17

达尔闻加油站,充分利用你的碎片时间涨知识。

加油站系列是此前达尔闻求职系列的延续,秋招季以来该系列已经分享了众多笔试题解析,覆盖华为硬件逻辑岗、大华硬件岗、海康威视等。今后,达尔闻将继续给大家带来最新鲜的笔试题目解析,通过这种方式查漏补缺,检测水平,补充完善你的知识库。下拉文末可以回看全部解析文章

本期加油站解析题目来源大疆硬件逻辑岗,共1道编程题,涉及知识点包含:毛刺滤波消除的操作方法,文中给出了两种编程代码。

今天解析的题目是上期预留的大疆FPGA逻辑岗B卷的问答题,也是1道编程题,与求职第九弹中讲解的紫光展锐数字IC岗的编程题同属一种类型,在第九弹中总结了该类型题目的解法,通过这道题目再巩固下解题步骤。

3、设计一个电路,使用时序逻辑对一个单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(
02      input  wire   sys_clk    ,   //系统时钟50MHz
03      input  wire   sys_rst_n  ,   //全局复位
04      input  wire   data_in    ,   //bit输入信号
05
06      outputreg    data_out      //滤除高电平或者低电平宽度小于4个时钟周期后的单bit输出信号
07);
08
09reg     [1:0]  cnt_low;
10reg     [1:0]  cnt_high;
11    
12//cnt_low:用于计数低电平宽度小于4个时钟周期的抖动
13always@(posedgesys_clk ornegedge  sys_rst_n)
14      if(sys_rst_n ==1'b0)   
15         cnt_low <=2'b0; 
16      else   if(&cnt_low ==1'b1|| data_in ==1'b1)//如果少了后一个条件会导致cnt_low没有及时清零而导致错误
17         cnt_low <=2'b0; 
18      else   if(data_in ==1'b0)
19         cnt_low <= cnt_low +1'b1;
20
21//cnt_high:用于计数高电平宽度小于4个时钟周期的抖动
22always@(posedgesys_clk ornegedge  sys_rst_n)
23      if(sys_rst_n ==1'b0)   
24         cnt_high <=2'b0;
25      else   if(&cnt_high ==1'b1|| data_in ==1'b0)    //如果少了后一个条件会导致cnt_high没有及时清零而导致错误
26         cnt_high <=2'b0;
27      else   if(data_in ==1'b1)
28         cnt_high <= cnt_high +1'b1;          
29           
30//data_out:滤除高电平或者低电平宽度小于4个时钟周期后的单bit输出信号
31always@(posedgesys_clk ornegedge  sys_rst_n)
32      if(sys_rst_n ==1'b0)   
33         data_out <=1'b0;
34      else   if((&cnt_low ==1'b1&& data_in ==1'b0)||(&cnt_high ==1'b1&& data_in ==1'b1))    //与上当前输入信号的状态是最关键的条件
35         data_out <= data_in;
36
37endmodule

//------------------------------------

方法二RTL功能代码如下所示:

//-------------------------------------
01module  filter(
02      input  wire   sys_clk       ,   //系统时钟50MHz
03      input  wire   sys_rst_n  ,   //全局复位
04      input  wire   data_in       ,   //bit输入信号
05
06      outputreg    data_out      //滤除高电平或者低电平宽度小于4个时钟周期后的单bit输出信号
07);
08
09reg            data_in_reg;
10reg     [1:0]  cnt;
11
12wire           podge;
13wire           nedge;
14
15//data_in_reg:将单bit输入信号打一拍
16always@(posedge sys_clk ornegedge sys_rst_n)
17      if(sys_rst_n ==1'b0)
18         data_in_reg <=1'b0;
19      else
20         data_in_reg <= data_in;
21    
22//podge:对输入信号进行上升沿检测
23assign podge = data_in &&(~data_in_reg);
24
25//nedge:对输入信号进行下降沿检测
26assign nedge =~data_in && data_in_reg;
27    
28//cnt:用于计数宽度小于4个时钟周期的抖动
29always@(posedgesys_clk ornegedge  sys_rst_n)
30      if(sys_rst_n ==1'b0)   
31         cnt <=2'b0; 
32      else   if(podge ==1'b1|| nedge ==1'b1)
33         cnt <=2'b0; 
34      else  
35         cnt <= cnt +1'b1;  
36           
37//data_out:滤除高电平或者低电平宽度小于4个时钟周期后的单bit输出信号
38always@(posedgesys_clk ornegedge  sys_rst_n)
39      if(sys_rst_n ==1'b0)   
40         data_out <=1'b0;
41      else   if(&cnt ==1'b1)
42         data_out <= data_in_reg;
43
44endmodule

//----------------------------------------

4)Testbench代码的编写

这两种方法的法Testbench仿真代码可以使用同一个,如下所示:

//--------------------------------------
01`timescale1ns/1ns
02
03module  tb_filter();
04
05//声明Testbench中信号的端口类型
06reg     sys_clk;
07reg     sys_rst_n;
08reg     data_in;
09
10wire    data_out;
11
12//定义Testbench中的内部变量
13reg[7:0]  tb_cnt;
14
15//初始化系统时钟、全局复位
16initialbegin
17      sys_clk     =1'b0;  
18      sys_rst_n <=1'b0;
19      #20
20      sys_rst_n <=1'b1;  
21end
22
23//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
24always  #10 sys_clk =~sys_clk;
25
26//tb_cnt:通过该计数器的计数值来控制毛刺区间
27always@(posedgesys_clk ornegedge sys_rst_n)
28      if(sys_rst_n ==1'b0)
29         tb_cnt <=8'b0;
30      else   if(&tb_cnt ==1'b1)
31         tb_cnt <=8'b0;
32      else
33         tb_cnt <= tb_cnt +1'b1;
34
35//data_in:模拟单bit输入信号
36always@(posedgesys_clk ornegedge sys_rst_n)
37      if(sys_rst_n ==1'b0)
38         data_in <=1'b0;
39      else   if((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;   //计数器在该区间内产生非负随机数01,模拟单bit输入信号高电平或者低电平宽度小于4个时钟周期的毛刺
41      else   if((tb_cnt <8'd20)||(tb_cnt >8'd65&& tb_cnt <8'd80))
42         data_in <=1'b1//计数器在该区间内保持为1
43      else   if((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//计数器在该区间内保持为0
45    
46//----------------filter_inst------------
47 filter  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_in
51                        
52      .data_out  (data_out  )   //output   data_out
53);
54
55endmodule 

//-------------------------------------------

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)总结

通过上面的分析验证可以看到两种方法各有特点,方法一容易通过第一感觉想到,但是容易出错,其中还有坑的地方。方法二虽然不容易立刻想出,但更容易实现,且坑更少思路更清晰。希望大家在做相关题目时一定要用正确的分析方法和流程,画画波形,仔细分析,在平时的工程项目中也可以用该方法快速准确的设计出要想逻辑电路。


下期预告

下期加油站,给大家带来大疆硬件岗位的笔试题目解析。

END

目前,我们正在通过大疆硬件岗和FPGA逻辑岗的题目,为大家带来笔试题的解析,以及知识的补充。如果有想要解析的题目,可以发给达尔闻安排。同时,欢迎加入达尔闻求职技术交流群,进群方式:添加妮姐微信(459888529),并备注求职,即可邀请进群

达尔闻求职“加油站”系列:

磁珠的用法、PCB布线3W规则

单比特信号跨时钟域问题详解

如何搞定状态机问题

达尔闻 求职“笔试经”系列:

华为硬件逻辑岗(FPGA)

紫光展锐数字IC岗(编程题)


达尔闻 求职“面试经”系列
从无人机爱好者到获得DJI大疆Offer
offer拿到手软,最后选华为!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存