查看原文
其他

求职“笔试经”第九弹:紫光展锐数字IC编程题

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

不想错过我的推送,记得右上角-查看公众号-设为星标,摘下星星送给我!

虽然2019年秋招已经接近尾声,但达尔闻求职笔试经系列仍会继续下去,希望可以帮助到更多求职人。达尔闻欢迎大家投稿各类笔试题给我们解析。目前笔试经固定在每周二更新,如果大家有其他需求,可以进入达尔闻求职群,添加妮姐微信:459888529,注明:求职

今天让我们来看一下上次文末(点击查看上一篇解析)留下的紫光展锐的编程题,并在解析的最后附加上思考内容。

本次,文末留下5道华为硬件逻辑实习岗的题目(有多选哦),大家可以做一下,欢迎留言说出你的答案,抽取一位全对的小伙伴送定制PCB尺子一把。(提醒:后面3道学过数电的都应该会哦!)

题目:请用Verilog RTL描述如下图设计:以clk为基准,设计一个秒计数器,在指定的计数值产生中断,实时输出当前的秒数计数值。(紫光展锐数字IC岗)
<1>clk是时钟输入,频率为32.768KHz。
<2>rst_n是异步复位输入,低电平有效,复位整个系统,为高则整个系统开始工作,其上升沿已经同步于clk。
<3>start是启动信号,一个clk时钟周期的正脉冲,同步于clk。alarm[7:0]是配置信息,单位为秒,同步于clk。
<4>工作模式:收到start后,秒计数器sec_cnt从零开始以秒为单位来计数,计数到alarm[7:0]指定的数值时,产生一个int pluse(时钟周期的正脉冲),秒数计数器回零并停止。
解析:这种编程题如果单纯让大家不限时做的话,我想很多有FPGA功底的人都是可以完成的,但这是一道限时的笔试题,所以在难度上还是会增加不少,最怕的就是读完题目后瞬间懵掉不知如何下手,从而浪费了宝贵的考试时间。该题如果分析方法得当,然后顺着思路捋下去还是相当容易做出的。

下面就详细的分析以及代码的实现和仿真验证的过程。

第一步:题干中有效信息的获取与整理

首先不要畏惧这么长的题干,一定要仔细通读题目,从题目中提取最有效的信息,然后再整合到一起,那么所有的难点就都迎刃而解了。
该题目主要有一个主题干和4条信息以及一个框图,从框图上我们可以很容易的看出左边是输入信号,右边是输出信号,还有位宽的标注。
(1)主题干信息:根据主题干,我们了解到,需要设计一个秒计数器,且要实时输出当前的秒计数值。根据这两个信息,我们完全应该联想到需要两个计数器来实现:一个是用于计数1s时间的计数器,另一个是用于计数有多少个1s的计数器。
(2)第1条信息:我们知道用于计数的时钟是多少频率的,就可以知道计数1s的时间需要计数多少个数,题目中给出的是32.768KHz的晶振,如果大家对这个频率值敏感的话就能发现这是一个很常用的频率,在日常生活中不可或缺,32.768KHz比较容易分频以便于产生1s的时钟频率,因为32768刚好等于2的15次方。我们每天用的手表、手机、电脑上显示作用的钟就是由它演变过来的。在32.768KHz的系统时钟下计数到1s所需要计数的个数为{1s/[1/(32.768*10^3)Hz]s}=32768个=2^15个,那么计数1s的时间就需要计数(2^15-1)个数,大家可以回顾下我们在第三弹第4题中的解析,这里我们就可以巧妙的使用“&”带来的便利了。
(3)第2条信息:第二条信息是很简单的,也是我们在开发Altera器件中常用的复位方式,只要你做过相关的FPGA开发都可以写出来,其“异步复位,低电平有效”的核心代码如下:

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

always@(posedge clk ornegedge rst_n)

if(rst_n ==1'b0)

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

(4)第4条信息:告诉我们有一个start启动脉冲信号,但是启动什么还不清楚,但还知道alarm[7:0]输入信号是配置信息,单位为秒,就是告诉我们需要计数多少1s。

(5)第5条信息:最后一条信息的内容最多也最重要,告诉了我们工作模式,即收到start启动脉冲后,秒计数器sec_cnt从零开始以秒为单位来计数,计数到alarm[7:0]信号指定输入的数值时,产生一个int pluse(时钟周期的正脉冲),秒数计数器回零并停止。可能前面的信息还比较清晰,加上最后一条这么长的信息后就瞬间有点懵了,因为之间信号的关系有点错综复杂,为了保持这份“清醒”,我们需要适当的画波形图来对已知的信息进行整合。

第二步:详细的分析及波形图的绘制

我们使用Visio工具画波形图(绿色代表输入,黄色代表中间变量,红色代表输出)的方法来清晰的表达信号之间的相互关系,而真正笔试的时候我们完全可以在草稿纸上进行简易的手绘。首先根据框图画出四个输入信号,输入信号我们根据题目要求自己设定,时钟和复位我们很容易表达,start启动脉冲信号我们产生两次(之所以产生两次是因为第一次和第二次之间的连接部分的信号很容易出现问题,所以需要特别注意这些特殊位置各个信号的波形),对应的alarm输入信号我们一个设置为3,一个设置为4(设置为1、2数值太小表达效果不好,设置太大又会使整个波形图变得复杂),且当start启动脉冲信号有效时也要保证alarm输入信号是有效的,后面仿真我们也按照这个约定来产生输入激励对系统进行验证。所有的输入信号模拟的波形如下图所示:
根据从题目中获得的信息,我们需要先设计一个1s的计数器,且计数器当start启动脉冲信号有效时开始计数,计数器从0开始计数到32767的时间为1s,这个计数器一定是一个中间的寄存器变量,我们取名为cnt_1s。我们知道start启动脉冲信号仅仅是一个只存在一个时钟周期的脉冲信号,如果让它来作为cnt_1s秒计数器自加1的条件肯定是不可行的,因为那样cnt_1s秒计数器只会加一次,所以我们需要一个让cnt_1s秒计数器在start启动脉冲信号有效后一直计数的使能信号,我们将这个使能信号命名为cnt_en信号,这是整个题目继续做下去的一个关键点。使能信号的灵魂在于我们如何能够精准的控制它何时拉高、何时拉低。如下图所示,我们已经知道cnt_en使能信号拉高的条件是start启动脉冲信号有效,而拉低的具体条件现在还不能确定。cnt_en使能信号的起始和结束位置如下图所示:
有了cnt_en使能信号我们就可以让cnt_1s秒计数器在start启动脉冲信号来到后开始计数,并且当start起始信号消失后cnt_1s秒计数器仍可继续进行计数,当cnt_en使能为低时或cnt_1s秒计数器计数到32767时cnt_1s秒计数器清零(计数满会自动溢出清零,代码中可不必关心)。计数器的灵魂就是在于我们如何能够精准的控制它何时计数、何时清零。
控制好cnt_1s秒计数器后接下来就该加上需要实时输出的sec_cnt秒个数计数器了,当start启动脉冲信号有效时sec_cnt计数器开始计数1s的个数,当cnt_1s秒计数器计数到32767时表示1s的计数已经完成同时sec_cnt计数器自加1,sec_cnt计数器计数到和alarm输入的值相等时清零,但是千万不要漏掉另外一个清零条件——cnt_1s秒计数器也要计数到32767,否则就会出现如下第一张图所示的错误,即在sec_int计数器计数最后一个秒的时间时根本不到1s的时间就清零了。同时我们也可以确定cnt_en使能信号拉低的条件了,即在cnt_1s秒计数器计数到32767且sec_cnt计数器计数到和alarm输入的值相等时拉低。第二张图是分析得出的正确结果。

还有最后一个int 输出脉冲信号没有产生,题目中给出的信息是sec_cnt计数器计数到和alarm输入的数值相等时产生脉冲,和上面分析sen_cnt信号时的情况一样我们还需要加上一个条件——cnt_1s秒计数器同时也需要计数到32767才能产生int脉冲,如下图所示,这样整个设计就是完全符合题目要求的了。

第三步:RTL代码的编写

根据上面详细的分析过程,我们就可以写代码了,写代码的时候都不需要看文字分析,直接看最后手绘的波形图代码就可以脱口而出。cnt_en、cnt_1s、sec_cnt、int各需要一个always块来描述,那么就是4个always块,我们根据波形图中红色标记的竖虚线以及圈出的需要特别注意的关键位置就可以确定每个信号拉高、拉低、计数、清零的条件,思路清晰,代码简洁,一气呵成!
//---------------------------------
01module  sec_cnt_top
02#(
03parameter CNT_1S_WIDTH =4'd15
04)
05(
06input   wire           clk    ,  
07input   wire           rst_n  ,  
08input   wire           start  ,
09input   wire   [7:0] alarm  ,
10
11output  reg             int    ,
12output  reg   [31:0sec_cnt   
13);
14
15reg                      cnt_en;
16reg     [CNT_1S_WIDTH:0]  cnt_1s;       //这里为了节省仿真时间我们设置成参数方便在仿真中直接修改而不影响综合时的RTL代码的结果,笔试的时候可以直接写15,同时省去2、3、4行的代码
17
18//cnt_en:计数器工作使能信号
19always@(posedge clk ornegedge rst_n)
20if(rst_n ==1'b0)   
21     cnt_en <=1'b0;
22else   if(start ==1'b1)
23     cnt_en <=1'b1;
24else   if(&cnt_1s ==1'b1&& sec_cnt == alarm)
25     cnt_en <=1'b0;
26               
27//cnt_1s:计数1s的计数器
28always@(posedge clk ornegedge rst_n)
29if(rst_n ==1'b0)
30     cnt_1s <=16'b0;
31else   if(cnt_en ==1'b1)
32     cnt_1s <= cnt_1s +1'b1;       
33    
34//sec_cnt:计数有多少个1s的计数器                   
35always@(posedge clk ornegedge rst_n)
36if(rst_n ==1'b0)
37     sec_cnt <=32'b0;
38else   if(&cnt_1s ==1'b1&& sec_cnt == alarm)
39     sec_cnt <=32'b0;
40else   if(&cnt_1s ==1'b1)
41     sec_cnt <= sec_cnt +1'b1;
42
43//int:计数到alarm指定的数值时,产生一个的脉冲标志信号
44always@(posedge clk ornegedge rst_n)
45if(rst_n ==1'b0)
46     int <=1'b0;
47else   if(&cnt_1s ==1'b1&& sec_cnt == alarm)  
48     int <=1'b1;
49else  
50     int <=1'b0;
51                      
52endmodule
//---------------------------------

第四步:Testbench代码的编写

从做笔试题的角度来说到此已经结束了,但是从做工程的角度来说我们一定要做完整,所以我们还需要对编写的代码进行仿真验证,根据前面的分析我们编写对应的测试仿真文件Testbench产生测试激励。
//---------------------------------
01module  tb_sec_cnt_top();
02
03reg            clk;
04reg            rst_n;
05reg            start;
06reg     [7:0]  alarm;
07
08wire           int;
09wire    [31:0] sec_cnt;  
10
11//时钟、复位和信号的初始化
12initialbegin
13 clk    =1'b1;
14 rst_n <=1'b0;
15 start <=1'b0;
16 alarm <=8'd0;
17#20
18 rst_n  <=1'b1;
19end
20
21//输入信号的产生
22initialbegin
23#40
24//第一段
25 start <=1'b1;   
26 alarm <=8'd3;   
27#20
28 start <=1'b0;
29#1000
30//第二段
31 start <=1'b1;   
32 alarm <=8'd4;   
33#20
34 start <=1'b0;
35end
36
37//产生50MHz时钟,因为32.768KHz频率的时钟我们仿真不好产生,所以我们仿真的时候随便产生一个频率的时钟即可,因为计数的个数是确定的,所以不影响最终的仿真效果
38always#10 clk =~clk;      
39
40//实例化RTL模块
41 sec_cnt_top   
42#(
43.CNT_1S_WIDTH(4'd2)
44)
45 sec_cnt_top_inst(
46.clk   (clk   ),  //input           clk
47.rst_n(rst_n),  //input          rst_n
48.start(start),  //input          start
49.alarm(alarm),  //input    [7:0]  alarm
50                     
51.int   (int   ),  //output          int
52.sec_cnt(sec_cnt)//output   [31:0]sec_cnt
53);
54                  
55endmodule
//---------------------------------

第五步:ModelSim仿真验证

整体两段的波形如下所示:

图片可点击放大
第一段放大的波形如下所示:

图片可点击放大

我们把ModelSim中信号的顺序排列成和我们绘制的波形图中信号的顺序一致,对比后发现其结果是完全一样的(为了仿真方便我们将cnt_1s秒计数器的计数值在Testbench中通过参数的形式改为计数到7,不影响仿真效果)。

第六步:思考

如果我们把题目中clk时钟输入的频率改为50MHz,代码中除了cnt_1s秒计数器的计数值需要变化以外,还有其它地方需改变的吗?

第七步:总结

通过做这道题目,我们可以总结很多,也能够学到很多,但从做题来说只要静下心来慢慢的按照正确的方法分析,是绝对可以做出的。前面看上去很长的分析,实际上在大脑中的思考过程是很快的,而实战时波形图也不需要画这么好看。画波形图时一定要把握好“模拟”的效果,既不能太“粗”,也不能太“细”,太“粗”容易导致不能够表达出信号之间的关键位置的逻辑关系,而太“细”则会增加画波形图的复杂度而导致浪费时间。
整个分析的过程看上去轻轻松松,一气呵成,大家不免会感到太“假”,其实在真实的工程设计中,最初绘制的波形图并不能保证100%正确,万一最初绘制波形图时因为没有考虑全面而出现的一些小问题最终都可以通过ModelSim的仿真发现的,然后我们再回过头来修改最初绘制的波形图以及代码,这样子反复迭代,就能够高效的实现工程需求。这样的好处是能够让我们的思路极其清晰,并对设计中可能存在的错误点有一个预判,而如果一上来就进行代码的编写,在稍微复杂一点的设计中会出现很多问题,然后再结合ModelSim仿真修改代码最后搞的自己都看不懂改的是什么了。所以推荐大家要用好的方法学习下去。当然这只是一个小模块的设计,如果是一个完整项目的话,我们还要对系统进行整体框图架构和模块之间信号关系的设计,以后有机会再和大家交流更多。
下一期我们将对下面的题目进行详细的解析,大家可以提前做一下,敬请期待……
请留言说出下面5道问题的答案,抽取一位全对的小伙伴送定制PCB尺子哦!
15、下列哪些是FPGA开发工具(   )。(华为硬件逻辑实习岗)
A ISE             B  Vivado       
C CCS            D Quartus          
16、使用DMA的好处不包括(   )。(华为硬件逻辑实习岗)
A  减少数据的传输延时     
B  一定条件下可以降低系统的功耗  
C  软件复杂度肯定会降低  
D  降低CPU占用
17、二进制乘法遵循下面哪些规则(   )。(华为硬件逻辑实习岗)
A  0x1=0       B  1x0=0        
C  1x1=1       D  0x0=0
18、逻辑代数式:A*A=(   )。(华为硬件逻辑实习岗)
A  2A             B  A^2           
C  2A^2         D  A
19、逻辑表达式Y=AB,表示(   )。(华为硬件逻辑实习岗)
A  或门          B  异或门       
C  与非门       D  与门
上面的题,是不是比之前解析的要简单些呢,记得留言告诉我们答案。
END


目前,我们安排的是每周二更新求职笔试经系列,计划涵盖的公司包含:华为,京东,大疆,商汤,中兴,CVTE,AMD,海康威视,黑金,汇顶等。(会陆续补充)

关于更新时间:每周二,如果同学们诉求高,很希望看到更多的分享,我们可以加番。
达尔闻 求职“笔试经”系列:

第一弹:华为硬件逻辑岗

第二弹:海康威视硬件岗

第三弹:华为硬件逻辑岗

第四弹:华为硬件逻辑岗&通用硬件岗

第五弹:华为硬件逻辑岗&硬件通用岗

第六弹:华为硬件逻辑岗

第七弹:华为硬件逻辑岗

第八弹:大华嵌入式岗

Modified on

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

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