4 M7 g0 q0 q0 w# T3 J: }1 V' a8 L
在正式开始之前,我们先看看一位FPGA工程师的工作日常:' g0 t& L) I. z2 ~
开始设计代码
6 ^: \3 J% a$ n5 p/ {- k* j5 n开始写第一个always代码
% l; v5 U8 u( o( A4 v发现要增加一个信号,因此写第二个always,设计这个新增的信号
- Y6 f3 U, {3 B' Q) F回到第一个always上,继续完善这个代码
8 w/ v5 g5 Y5 f1 o/ k开始写第三个always代码! s9 ~9 r' m* O: o/ i6 O1 r
感觉第一个always有情况没考虑到4 \8 x) q5 b+ v( w
一阵重新思考
5 n' I" |+ _* l' ^% c/ i回去修改第一个always的代码
* m$ }" H2 h% C' c) S$ Y+ A写完后,得了,不检查代码了,仿真再说吧。9 L, [2 O/ k* z5 X6 r/ \4 P( D
! T$ `" S/ O( V0 |2 E% E3 M5 O
仿真过程:
+ x v0 r4 s1 t, t/ Y: g+ O, b每个时钟上升沿一个一个检查( i8 ^% m) L% |; _6 j8 l7 g+ U; R7 Z
发现这时某信号没有变高3 B& P: h7 i: T, p3 j. d- o7 l# D
检查代码,把BUG补上
. e$ A) ^: k( V+ Y4 E继续检查波形,继续补BUG- ?+ s/ _- e; [6 T3 p
发现信号A和B时序对不齐
/ A, _. w. z9 M( g1 u4 e9 ~ w思考是打补丁呢还是打补丁呢
% i8 t6 @8 ?# ~2 I# m# B是改这个信号呢,还是改那个信号,还是加一个信号9 ~+ X# ]0 w, I I5 [! }
一番折腾后,终于对齐了1 Z; Z" O) ?. q
修改测试文件,再测试2 ` t% y; z$ T4 E- e8 ]
还是有BUG,继续打补丁
* q1 q( y9 h/ E/ h3 G6 K, O' T. a0 A' r H0 `% x
该上板调试了( I, U3 e. l+ \ V. e
系统跑一会没问题,长时间跑就出BUG+ p% M) H8 o6 t$ o' v8 w5 O
用调试工具各种分析各种定位, J& f% l" ?/ F& c* f5 c
一番折腾后,终于找到BUG
$ S G9 U/ E Z1 J# j' B0 N. ?一个corner没想到/粗心大意漏了个条件/. m. o& v1 n4 N9 Y+ n+ P# T
早知道,要没这BUG,我早就做完了
' J: J$ n9 F2 W0 @1 ?
B8 L5 u2 E, o; K又出现BUG了,又要来折腾啦。8 U" V u4 h' X, f
, l5 P: }6 |4 H" ?3 F5 o 这个场景是不是觉得很熟悉?还有下面这些情形也许都遇到过:一个项目看上去很简单,精心设置了架构,结果越做发现冲突越多,直到整个逻辑完全混乱。本来一天可以的完成的事不知道怎么搞的一个星期还没有完成;本来只需要做一行更改,结果却涉及到N个模块;出现了一个非常小的BUG打了一个补丁,然后补丁越来越多,到最后无法解决。诸如此类等等情况不一而足,究其原因,总离不开“混乱”两个字。这些混乱的根源是什么?又该如何解决呢? 一个好的FPGA项目的设计作品,不仅依赖于架构设计,优秀的代码也是必不可少的关键因素。而好的代码最基本的就是清晰整洁。整洁的代码运行稳定,也是后期维护和升级的基础。正如C++语言发明者Bjarne Stroustrup说的那样:“代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,避免其他人优化时不知所措从而出现混乱状态。整洁的代码只做好一件事。” 这段话说得实在太好了,整洁的代码只去做好一件事。事实上,有两点只要做到了,就可以大大提高自己代码的整洁度。第一、写简单的代码;第二、把复杂的代码简单化。下面我们通过一个小的实例来说明一下。我们先来看这样一组代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | always @(posEDGE clk or negedge rst_n)begin if(rst_n==1'b0)begin shi_ge <= 0 ; end else if(((set_flag == 1'b1 && set_sel == 4)&& (key_vld == 1 && key_num == 4'b0010)) || shi_ge_add)begin if(shi_shi ==2 && shi_ge == 3)begin shi_ge <= 0 ; end else if((shi_shi == 0 || shi_shi ==1) && shi_ge == 9)begin shi_ge <= 0 ; end else begin shi_ge <= shi_ge + 1 ; end end end |
这个程序时一个数字时钟功能的其中一份关于小时个位的代码。小时个位复位等于0(第3行代码);设置的语句(第5行代码),意思是当你选中小时的个位并且按键按下去,小时个位+1,或者说正常情况下一个小时+1。这里需要注意的是:首先小时的计数方式在0:00——9:00,10:00——19:00,20:00——23:00情况下+1;另外几个时间点清零。 我们来分析一下,在这份代码的设计中需要考虑到很多因素。第一、需要考虑按键;第二、按下去时与正常计数的关系;第三、需要数多少次清零,比如说9点、19点、23点清零;当很多因素混在一起去考虑,特别是格式没有被规范的时候,就容易出现混乱、遗漏点或是相互之间出现冲突,出错的可能性随之变大。 接下来我们来看另外一组代码的思路和操作。 首先,我们建立一个通用的计数器模板,命名为jsq。每次遇到计数器,只需要输入JSq,即可调入该模板。(注:关于模板的设置以后章节介绍) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt <= 0; end else if(add_cnt)begin if(end_cnt) cnt <= 0; else cnt <= cnt + 1; end end assign add_cnt = ; assign end_cnt = add_cnt && cnt== ; |
接下来设置什么时候个位+1,分为两种情况:1、按键按下去;2、自然计数+1;(第13行) 采用变量法设置X-1;即先不用去管数多少下,反正数完就清零;(第14行) 最后我们设置数多少下。20:00时数4下;其它时候数10下;(16~21行) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt <= 0; end else if(add_cnt)begin if(end_cnt) cnt <= 0; else cnt <= cnt + 1; end end assign add_cnt =((set_flag == 1'b1 && set_sel == 4)&& (key_vld == 1 && key_num == 4'b0010)) || shi_ge_add ; assign end_cnt = add_cnt && cnt== x-1 ; always @(*)begin if(shi_s == 2) x = 4 ; else x = 10 ; end | . G. ~/ x- [- v- r, _5 f% n- l* W3 t
现在我们来回顾一下这段代码,从中不难发现,设计的总体思路有着严密的逻辑和步骤,并采取了便捷工具(模板)来规范了代码编写,减少了设计量。最重要的是设计者的意图清晰了然,控制语句直截了当,代码之间相互依赖性非常低,作者之外的开发者阅读和增补非常轻松。 # i! ]1 s- J" L: y$ a
|