verilog codingstyle的建议
Verilog coding style建议
1. 设计必须采用同步设计;
同步设计就是保证电路中所有的寄存器都在同一个clock的控制下变化。因为目前的EDA工具并不能很好的支持异步电路的分析,用同步设计加上良好的编码规范得到的电路仿真结果就等同于实际电路的运行结果,若是异步电路,仿真结果与实际电路的结果可能不相同。异步电路的核心逻辑是用组合电路来实现的,电路的输出,主要信号等变化并不依赖于任何一个时钟,所有验证、调试都是相当繁琐而有难度的,所有建议采用同步设计。
2. 宏定义
利于仿真的角度出发,应该设置一个user delay(UD),即宏定义`define UD #1,对没有时序逻辑寄存器都延时一个单位, 默认是1ps,这样就可以模拟FPAG内部的延时;仿真结果更接近实际电路,值得一提的是,它是不被综合的,就是对电路的运行没有一点影响,仅仅只是为了仿真的方便才定义的。
3. 时序与组合电路分开描述
这是IC设计最基本的一个coding style,因为这样有利于debug。一般来说,时序电路是非常难以debug的,所有设计的话,时序电路尽量简单,而要完成的功能通过组合电路去完成,时序电路只设置为一个简单的D触发器。这样的话,进行debug的时候,看源码就可以只看组合模块,忽略时序电路(D flip_flop),既方便又节省时间。组合描述的敏感链表用always @ (*)来描述。其中*代表组合逻辑模块中影响输出的所有信号,这是verilog2001的新语法规定,大家不熟悉的话可以查一下。
4. 信号的赋值
建议一个信号只在一个always 块里面进行赋值,而一个always 块也只对一个信号进行赋值;时序设计采用非阻塞赋值,组合电路采用阻塞赋值,这样做又以下几个好处:
首先,可以很好的避免多驱动源的发生,减少这种错误发生的概率;
其次,debug的时候若是发现该信号有错误,就可以进行很简单的定位,因为这个信号只是这一个always里面赋值,非常有利于debug;
最后,时序采用非阻塞赋值,组合设计采样阻塞赋值,因为这样做最符合信号的实际情况;
5. 避免非目的的latch的产生
Latch对电路的危害:首先它是电平触发的,不能被同步,容易产生冒险-竞争现象,对电路造成一定的危害;其次,FPGA设计资源中,大部分器件没有latch这个资源,而需要一个门电路和一个触发器(FF)来构成,这样就造成资源浪费。最后,若在高速电路中,latch会给系统带来不可预料的危害,甚至会让整个系统崩溃。产生latch的主要原因在于,在进行组合逻辑设计时,采用了不完整的条件判断语句,或者是设计中有异步逻辑设计等原因。
防止latch产生的方法:
1> 使用完全的条件判断语句,if…else…,否则会产生透明锁存器;
2> 检查设计中是否含有combinational feedback loops,发现及时修改;
3> 为每个input 设计一个output,在case语句中设置default语句;
4> State machine中,尽量用完全条件语句(full case),如果不是,应加上default语句;
6.状态机的描述
状态机尽量采样两段式或者三段式来描述,极力的避免一段式描述。状态机必须保证它的安全性,就是不会进入死循环,特别是不会进入非预知状态,而且就算由于某种干扰进入非设计状态,也能很快的恢复到正常的循环状态中来。
一段式是指状态的同步跳转与信号的赋值、输入的条件都写在一个always模块里面,它的缺点就是状态转移判别的组合逻辑与状态寄存器转移的时序逻辑在一个always模块里面完成,而且描述当前状态事还要考虑下一个状态的输出,不符合时序与组合电路分开设计的coding style,而且代码不清晰,更难以维护,不利于综合和优化。
二段式的FSM描述:一个always模块采用同步时序方式描述状态转移,另一个always模块采用组合逻辑方式判断状态转移的条件,或者说来描述状态转移的规律;三段式一般类似于二段式,只是输出信号的判断是通过下一个状态来判别的,而不是当前状态来判别。
7. 信号的命名
1>状态机的命名
尽量避免0,1,2,S0,S1,S2……等命名,因为这样看起来不知道在干什么,只要看了具体的代码才明白这个状态在干什么,建议采用英文缩写等来表示,用parameter来定义,如FSM_IDLE,SEND_DATA,SEND_ADDR等,看了就会一目了然,空闲态,发送数据,发送地址等。
2>普通信号的命名
超过4个字母的尽量用_来分隔,看起来清晰,最好可以表达这个信号是干什么的,如TIME_CNT(一看就知道是计数的),
3>后缀的命名
根据每个公司不同,规定也不同,但是最好是统一一下,这样的话大家相互看代码的时候就不至于“犯晕”;举几个简单的例子。
_N,代表寄存器的下一个值,也就是上述组合与时序分开设计中组合逻辑的值;如TIME_CNT_N;
_B,代表低电平有效,如RST_B,复位信号低电平有效,ADC_CS_B,ADC芯片的片选信号CS是低电平有效的;
_EN ,使能信号,LCD_1602_EN,表示1602的使能信号;_SHIFT_REG,移位寄存器等等,大家可以自己琢磨一下,也可以自己定义一些。
4>复杂的立即数定义
较为复杂的立即数应该采用宏定义的方式来定义,如果在一个程序中多处用到该参数,在进行debug的时候发现参数有误而要进行调整的时候就不需要在源码中一个一个找,而只需修改定义的参数及可完成,当然简单的立即数赋值可以不用该种方式。
8.module名
设计的module名尽量与文件名一致,利于查找,测试激励文件在module名后面加_tb,如下所示:
Module USB_CTL; module USB_CTL_tb;
一看就会明白第二个module是用来测试第一个module的testbench文件;
一个module不要太长,不宜与维护,完成功能过长的话尽量采用例化的方式,利于代码移植;因为verilog 中的关键字都是小写的,所有建立大家用大写来定义信号,便于区分,(我这有Gvim编辑器,非常好用,大家如需要发给大家)刚接触这种风格可能感觉有点怪,但是如果时间久了,大家就会感觉到这种风格对于你的debug是非常方便的,有事倍功半的效果。一个工程中调试的时候建议建立四个文件夹,分别为quartus,modelsim,src,testbench,quartus主要用来下载,modelsim用于仿真,src用于存放源码,testbench用于存放激励文件,这样方便整理。
9.例化(instance)
被例化的名字命名为I_XXXX,如例化1602的控制层
LCD_1602 I_LCD_1620
(
.XXXX (XXXX)
…
);
其中I代表instance,就是例化的意思,I_LCD1602就意思例化LCD_1602这个模块,因为quartus编译的时候是看不得LCD_1602的,只能看到I_LCD_1602这个模块,这样就很容易知道例化的对象是谁,其实命名也没有为什么要这样命名之说,只是为了便于理解,方便的角度出发,而且写do文件的时候基本是可以不用看源码的信号,熟练的时候大家就可以体会到。