verilog开发经验小谈

最近的工程均与FPGA有关,从前仿到综合实现,以及在线的debug。期间遇到了许多问题,在老师的指导和自己的摸索下,将问题一一解决,从中受益颇多。特此开文以记录二三。


*本文叙述顺序遵从工程的顺序,从基础的代码书写以及前仿,到综合实现,最后在线debug。
工程基于的软件平台主要为vivado,主板为ZC706,工程目标为基于ZC706配置AD9364。*

1. 基础部分

本部分不细分代码书写以及前仿工作,主要说明一些代码应该保持的风格,方便后续综合实现。

虽然Verilog HDL以其近似C语言的语法、风格占据HDL的半壁江山,但其诸多语法指令中,真正能够被综合实现的仅有少数。
但这并不意味着不能被综合的语句就失去了用武之地,当我们在初步coding时,前仿(logic simulation)是必不可少的,需要用大量的testbench对多个模块乃至顶层进行逻辑、功能的仿真测试。而在这个阶段,那些不能被综合的语句将显得尤为方便,比如一些模块或需要initial块来完成一些模块内的参数初始化;或者$time指令来进行一些延时;或者用一些简单的for语句来模拟小的循环任务;再或者利用file操作语句来将仿真中间/结果变量打印在log中方便观察等等。因此,更好的使用这些不被综合的语句将使得前仿事半功倍。
但回过头来,这些语句终究是不能陪我们走到最后的。在前仿逐渐深入与功能的慢慢完善,我们需要将代码中存在的这些语句剔除或用其他可综合语句替代。
除此之外,另有一些需要注意的不可综合代码风格。
- (1) Verilog中最常用的always语句块。为了逻辑的清晰性以及代码的可读性,我们时常会将同一个信号在不同的always块中赋值,甚至在不同源时钟信号触发的always块中互相访问。虽然这样的做法在前仿中可以通过,但为了综合和实现,最好不要让它留到最后。由于vivado并不会很智能的去分析代码,因此其往往以块为单元去优化和构造硬件结构,如此将同一个信号在不同的always块中赋值,会使得硬件构造上起冲突,从而不能正常实现所期的逻辑功能,甚至会出现其中一个always块被完全优化掉的情况。另一方面,如果在不同源时钟信号触发的always块中互相访问同一个信号,如一个always块中对某信号赋值,另一个always块中对该信号进行访问。这种情况下,虽然可以正常综合与实现,但会出现timing上的警告,因为由某源时钟信号触发而产生的信号传递到另一个源时钟的块中时,会产生较大的时延,从而引发timing failed。该时钟问题不仅仅会在always块中出现,在不同源时钟触发的IP块中亦不可忽视。
- (2) 信号线类型信号与时钟类型信号。信号线类型信号即默认的普通wire信号,而时钟类型信号为clock类型信号,该信号为特殊的wire信号。尽管平时我们都直接给clk规定wire类型,但一般由原语IBUFGDS/IBUFDS或IP核clock wizard等产生的时钟信号都会被自动转换为clock类型。若随意将该类型时钟信号赋值给普通wire型信号,如assign clk_1 = locked? clk: 0;语句,并不会如预期得到一个clock类型的clk_1信号,而是得到一个普通wire型clk_1信号,尽管clk_1的逻辑变化同clk,但由clk_1去触发或驱动别的模块是绝对不行的,这样会引入巨大的时延。
- (3) 多层嵌套的if…else语句。在Verilog中,if…else语句是相当常见的,对于逻辑的表示也必不可少,对其层层嵌套也可以使某些复杂逻辑表达的更为方便。但该语句在硬件实现是靠多层LUT和reg来实现的,并且每个LUT单元的深度很小,如果if…else判断条件较为复杂,则需要数层LUT单元来实现。而我们知道Verilog中if…else语句的判断执行是串行的,也就是从第一个条件开始逐一判断,如此一来,若elseif的深度较大,会产生较大的延时。而如果嵌套了多层的if…else语句,实现该串逻辑的LUT层次将会大大增加,延时也会增长的可怕。这样会带来如timing异常、冒险等多种问题,致使最后调试麻烦*10000。所以对于一些逻辑较为复杂的情况,最好多花时间将逻辑理清,构造一些互异的变量,从而用case语句块来替代多层的if…else。case语句块在Verilog中具有相当优势,其条件的判断直接基于LUT,相当于并行判断,所带来的延时甚微。


2. 综合实现部分

本部分也将综合和实现合在一起来写。然其实并不尽写综合实现中需要注意的问题,亦会寻根溯源的回到最基本的问题上进行叙述。

在综合实现环节中,除了上一节中所述的需要注意的一些语法及代码风格问题,更多的会遇到timing、I/O的约束问题。为了减少乃至避免这类问题,我们得从一些很基本的问题入手。
- (1) 时钟源问题。当FPGA要与其他硬件模块通信时,务必做到控制数据传输的时钟源同步。最好的办法便是构造同源时钟。若硬件模块自带时钟源,则可以方便的将其发出的时钟源进行PLL锁定或进一步分频来得到想要的不同频率时钟,从而使用这些同源时钟来控制FPGA与其的数据通信。否则会出现timing异常,或者后期debug时采样不正常等现象。
- (2) 接口电平问题。其实接口电平问题可以提前来说,但大家往往都是开发到中期才会注意到这些问题带来的危害。接口电平种类根据不同的FPGA和硬件模块分为很多种,我这里遇到的是LVCMOS25、LVDS_25以及LVDS等类型。在一些精密设备,或者航天设备中,大多采用LVDS类型来消除电源引入的噪声影响。因此在选用这种类型的接口后,需要在约束文件中进行相应的修改。此外需要注意,综合后最好检查I/O planning窗口,确定在LVDS信号所在的BANK中是否都是同一种电平类型(LVDS),如果同一个BANK中具有多种电平类型,尤其是电平大小不同的类型,会造成一些意想不到的错误。最后,对于LVDS的信号,在代码中还需使用原语IBUFDS/OBUFDS等,来进行接口电平类型的转换,而不是直接用逻辑语句手动转换。


3. debug部分

这里的debug指利用vivado自带的debug_core进行上板在线调试。

debug_core的使用也具有相当一些值得注意的地方。
- (1) 构建debug_core。由两种方式,一种是在综合完毕后,直接使用set up debug选项来添加需要观察的信号线,但这种方法不能直观的看所加的信号线是不是所需要的信号线,即不能在schemetic中清晰的看出,因此由此构建出的debug_core有时不能正常工作。另一种方式是切换到debug窗口,在dgb_hub右键creat一个新的debug_core,然后直接在schemetic中找到所需要观察的信号线,右键assign到debug_core的probe中。这里最好加入FDCE和FDRE类型的信号线,否则debug_core也可能无法正常工作。此外,给debug_core指定的时钟信号也非常重要,该时钟信号必须为free running的信号,即开机就在run,而不是等到一定条件后才开始运作的时钟信号。
- (2) 部分信号线添加的技巧。有时候我们需要观察一个输入的信号,如该信号的类型为IBUF,此时不能直接把它添加到debug_core中。我们可以先用高倍钟对该信号进行采样,如此采样所得的信号一般即为上述所提到的两种信号类型,这样就可以放心的添加入debug_core之中。
- (3) debug_core的使用。在使用过程中,我们要善于用trigger窗口,对某些信号线赋予trigger的条件,这样就可以在窗口中显示出当该信号线满足条件时的所有信号的波形图,对分析和调试十分有帮助。此外,利用好vio接口,可以让debug事半功倍。

你可能感兴趣的:(ABOUT,MCU)