FPGA研发之道(14)写在coding之前的铁律
时间:12-04
来源:互联网
点击:
作者:阿昏豆
写在coding之前的那些铁律
(1)注释: 好的代码首先必须要有注释,注释至少包括文件注释,端口注释,功能语句注释。
文件注释:文件注释就是一个说明文:这通常在文件的头部注释,用于描述代码为那个工程中,由谁写的,日期是多少,功能描述,有哪些子功能,及版本修改的标示。这样不论是谁,一目了然。即使不写文档,也能知道大概。
接口描述:module的接口信号中,接口注释描述模块外部接口,例如AHB接口,和SRAM接口等等。这样读代码的人即可能够判断即模块将AHB接口信号线转换成SRAM接口信号。
功能语句注释: 内部关键逻辑,状态机某状态,读过程、写过程。
注释的重要性,毋庸置疑,好的注释,能够提高代码的可读性,可维护性等等。总之,养成注释的好习惯,代价不大,但是收益很大。
(2)语句:
开始写代码是,在FPGA设计中,特别是在可综合的模块实现中,verilog的语句是很固定的。在FPGA的设计中,不外乎时序逻辑和组合逻辑,除此之外,别无他法。对于开始功能编码来说,只需知道组合逻辑信号即可生效,时序逻辑在时钟的下一拍起效就够了。
下面是编码的实例。
组合逻辑:两种组合逻辑的描述,其功能是一致的。
assign A = B ? 1 : D ? 2 :3;
always@(*)
if(B)
A = 1
else if(D)
A = 2;
else
A = 3;
组合逻辑 如果是异步复位的话,描述如下
always@(posedge sys_clk or negedge rst_n)
if(!rst_n)
a <= 0;
else
a <= b;
也就是说,在verilog的可综合电路的编码中,只需要三种语句,分别是assign, always(*) 及时序的always(`CLOCK_EDGE clk ) 。 `CLOCK_EDGE 可以是上升沿或者下降沿。
为什么用always@(*) 而不是always(敏感信号列表)。“*”包含所有敏感信号列表,如果在coding过程中,漏掉了某个敏感信号,则会导致仿真不正确,例如本例中,敏感变量列表中,需要B or C 但是如果漏掉一个,仿真就会在B或C有变化时,输出没有变化。导致仿真和功能不一致,但是对于综合工具来说,功能还是能够正常工作的,不会因为敏感变量列表中的值未列全而不综合某条语句。某些情况下,敏感列表的值可能有十几个甚至更多,遗漏是可能发生的事情,但是为了避免这种问题,最好采用always(*)而不用敏感变量列表的方式,来避免仿真结果不一致的情况发生。
(3)赋值:老话重提,阻塞与非阻塞
很多同志喜欢钻研阻塞赋值和非阻塞赋值,这两种赋值,分别在always块里面用于的阻塞“=”给组合逻辑赋值,非阻塞”<=”给时序逻辑赋值。这应该是铁律,应该在编码过程中被严格的遵守下来。“为什么?,不这么用程序也能跑”。这句话部分是正确的,疑问永远是工程师最好的老师。
诚然,某些情况下,不严格的执行也跑,但是在某些情况下,实现二者就不一样。
对于下面两个例子来说明,为什么?
对于value1的描述方式:其综合后的如下所示
如果从实际的编译结果上看 b和b1 及c和c1其使用阻塞赋值和非阻塞赋值最终的结果是一致的,因此,也就是说,某些情况下,二者的编译结果一致。
而对于value2的描述方式:其综合后的电路图如下所示。
而对于第二中描述方式,阻塞赋值和非阻塞赋值的区别就显现出来了,从综合后的图中可以看到,c1信号是b1信号的寄存,而c信号和b信号为同一信号,都为a信号的寄存。
作为FPGA工程师,一项基本的能力,就是要知道代码综合后的电路和时序,不要让其表现和你预想的不一致,“不一致”就意味着失败。即是代码的失败,也是工程的失败
对于阻塞和非阻塞赋值区别和详细说明来说,其能够编写一本书(如有时间也可专题详述),但是对FPGA工程师,对于verilog的编码而言,则只需要按照时序逻辑用“<=”非阻塞,组合逻辑用阻塞“=”赋值即可。不要挑战那些规律,试图通过语言的特性来生成特殊电路的尝试是不可取的,开个玩笑的话,是没有前途的,要把设计的精力放在通过可用的电路来实现需求上,不要舍本逐末。在数字电路设计中,我们需要的是一个确定的世界,“所见及所得”,不要让你所想的和综合编译工具得认识不一致。这也就是不要乱用和混用这两个赋值的原因。
(4)一个变量一个“家”
不要在两个always语句中同一个变量赋值。(这是必须的)
也尽量不要在同一个always语句中,对两个变量赋值。(这是可选的)
如果是一组信号,其有共同的控制条件,则在同一always语句中赋值能够减少代码行数,提高可读性,除此之外,最好分开来写。如果几个不太相关的信号在同一里面赋值,其可读性极差,在组合逻辑中,还容易产生latch。
而前者赋值方式,综合工具肯定会报错,这到不用很担心,因为能够报的错误时是最容易被发现的。俗语说:“咬人的狗不叫”,而对于FPGA设计来说“致命的BUG,从来不报错”。
(5)锁存
FPGA中不要有锁存器的产生。最容易产生的是在always(*)语句中,最后一定是所有分支条件都要描述并赋值,(一定要有最后的else)。状态机中,同样如此,不但需要有default的状态,每个状态的都要有所有的分支都要赋值。
锁存器,是FPGA设计的大敌,因为会导致非你想要的错误功能的产生,并且导致时序分析错误,就会产生前述的问题“所见不是所得”,并且综合工具不会报错。
如果你设计的电路功能,仿真正确,而实际工作不正常,有一部分的原因是生成了锁存器,如果设计很大,不容易查的话,可以打开综合报告,搜索“LATCH”关键词,查看是否有锁存器的产生,一句话“锁存器,必杀之”。
时序逻辑会产生锁存器吗?当然不会,时序逻辑综合结果必然是触发器,因此不用检查时序逻辑的分支条件。
综上:这是写在coding之前的话,编码的主要功能应该是用可靠的电路来描述FPGA功能和需求,不要试图通过语言的特性来描述功能,设计的主要精力应放在用已知的电路(组合逻辑,时序逻辑)描述未知功能。
写在coding之前的那些铁律
(1)注释: 好的代码首先必须要有注释,注释至少包括文件注释,端口注释,功能语句注释。
文件注释:文件注释就是一个说明文:这通常在文件的头部注释,用于描述代码为那个工程中,由谁写的,日期是多少,功能描述,有哪些子功能,及版本修改的标示。这样不论是谁,一目了然。即使不写文档,也能知道大概。
接口描述:module的接口信号中,接口注释描述模块外部接口,例如AHB接口,和SRAM接口等等。这样读代码的人即可能够判断即模块将AHB接口信号线转换成SRAM接口信号。
功能语句注释: 内部关键逻辑,状态机某状态,读过程、写过程。
注释的重要性,毋庸置疑,好的注释,能够提高代码的可读性,可维护性等等。总之,养成注释的好习惯,代价不大,但是收益很大。
(2)语句:
开始写代码是,在FPGA设计中,特别是在可综合的模块实现中,verilog的语句是很固定的。在FPGA的设计中,不外乎时序逻辑和组合逻辑,除此之外,别无他法。对于开始功能编码来说,只需知道组合逻辑信号即可生效,时序逻辑在时钟的下一拍起效就够了。
下面是编码的实例。
组合逻辑:两种组合逻辑的描述,其功能是一致的。
assign A = B ? 1 : D ? 2 :3;
always@(*)
if(B)
A = 1
else if(D)
A = 2;
else
A = 3;
组合逻辑 如果是异步复位的话,描述如下
always@(posedge sys_clk or negedge rst_n)
if(!rst_n)
a <= 0;
else
a <= b;
也就是说,在verilog的可综合电路的编码中,只需要三种语句,分别是assign, always(*) 及时序的always(`CLOCK_EDGE clk ) 。 `CLOCK_EDGE 可以是上升沿或者下降沿。
为什么用always@(*) 而不是always(敏感信号列表)。“*”包含所有敏感信号列表,如果在coding过程中,漏掉了某个敏感信号,则会导致仿真不正确,例如本例中,敏感变量列表中,需要B or C 但是如果漏掉一个,仿真就会在B或C有变化时,输出没有变化。导致仿真和功能不一致,但是对于综合工具来说,功能还是能够正常工作的,不会因为敏感变量列表中的值未列全而不综合某条语句。某些情况下,敏感列表的值可能有十几个甚至更多,遗漏是可能发生的事情,但是为了避免这种问题,最好采用always(*)而不用敏感变量列表的方式,来避免仿真结果不一致的情况发生。
(3)赋值:老话重提,阻塞与非阻塞
很多同志喜欢钻研阻塞赋值和非阻塞赋值,这两种赋值,分别在always块里面用于的阻塞“=”给组合逻辑赋值,非阻塞”<=”给时序逻辑赋值。这应该是铁律,应该在编码过程中被严格的遵守下来。“为什么?,不这么用程序也能跑”。这句话部分是正确的,疑问永远是工程师最好的老师。
诚然,某些情况下,不严格的执行也跑,但是在某些情况下,实现二者就不一样。
对于下面两个例子来说明,为什么?
对于value1的描述方式:其综合后的如下所示
如果从实际的编译结果上看 b和b1 及c和c1其使用阻塞赋值和非阻塞赋值最终的结果是一致的,因此,也就是说,某些情况下,二者的编译结果一致。
而对于value2的描述方式:其综合后的电路图如下所示。
而对于第二中描述方式,阻塞赋值和非阻塞赋值的区别就显现出来了,从综合后的图中可以看到,c1信号是b1信号的寄存,而c信号和b信号为同一信号,都为a信号的寄存。
作为FPGA工程师,一项基本的能力,就是要知道代码综合后的电路和时序,不要让其表现和你预想的不一致,“不一致”就意味着失败。即是代码的失败,也是工程的失败
对于阻塞和非阻塞赋值区别和详细说明来说,其能够编写一本书(如有时间也可专题详述),但是对FPGA工程师,对于verilog的编码而言,则只需要按照时序逻辑用“<=”非阻塞,组合逻辑用阻塞“=”赋值即可。不要挑战那些规律,试图通过语言的特性来生成特殊电路的尝试是不可取的,开个玩笑的话,是没有前途的,要把设计的精力放在通过可用的电路来实现需求上,不要舍本逐末。在数字电路设计中,我们需要的是一个确定的世界,“所见及所得”,不要让你所想的和综合编译工具得认识不一致。这也就是不要乱用和混用这两个赋值的原因。
(4)一个变量一个“家”
不要在两个always语句中同一个变量赋值。(这是必须的)
也尽量不要在同一个always语句中,对两个变量赋值。(这是可选的)
如果是一组信号,其有共同的控制条件,则在同一always语句中赋值能够减少代码行数,提高可读性,除此之外,最好分开来写。如果几个不太相关的信号在同一里面赋值,其可读性极差,在组合逻辑中,还容易产生latch。
而前者赋值方式,综合工具肯定会报错,这到不用很担心,因为能够报的错误时是最容易被发现的。俗语说:“咬人的狗不叫”,而对于FPGA设计来说“致命的BUG,从来不报错”。
(5)锁存
FPGA中不要有锁存器的产生。最容易产生的是在always(*)语句中,最后一定是所有分支条件都要描述并赋值,(一定要有最后的else)。状态机中,同样如此,不但需要有default的状态,每个状态的都要有所有的分支都要赋值。
锁存器,是FPGA设计的大敌,因为会导致非你想要的错误功能的产生,并且导致时序分析错误,就会产生前述的问题“所见不是所得”,并且综合工具不会报错。
如果你设计的电路功能,仿真正确,而实际工作不正常,有一部分的原因是生成了锁存器,如果设计很大,不容易查的话,可以打开综合报告,搜索“LATCH”关键词,查看是否有锁存器的产生,一句话“锁存器,必杀之”。
时序逻辑会产生锁存器吗?当然不会,时序逻辑综合结果必然是触发器,因此不用检查时序逻辑的分支条件。
综上:这是写在coding之前的话,编码的主要功能应该是用可靠的电路来描述FPGA功能和需求,不要试图通过语言的特性来描述功能,设计的主要精力应放在用已知的电路(组合逻辑,时序逻辑)描述未知功能。
- 基于FPGA的片上系统的无线保密通信终端(02-16)
- 基于Virtex-5 FPGA设计Gbps无线通信基站(05-12)
- 基于FPGA的DVI/HDMI接口实现(05-13)
- 基于ARM的嵌入式系统中从串配置FPGA的实现(06-09)
- 采用EEPROM对大容量FPGA芯片数据实现串行加载(03-18)
- 赛灵思:可编程逻辑不仅已是大势所趋,而且势不可挡(07-24)