一、特征规范
规则1:变量表达必须分离;
对于reg信号,尽可能的放在自己单独的always块中。除非其中变量为一组信号,变化完全一致,否则不可放在同一个always模块之中。
例如图 1代码,不规范代码一眼看不清每个信号逻辑作用,几个信号混在一起,犹如乱麻,逻辑上交织不开,增加调试困难。而分离的代码能够明显将信号逻辑作用显示。阅读方便,若逻辑出错,修改起来,很是方便。

图 1代码示例图
另外例如图 2代码,所有变量行为一致,可以放入一个always块中。

图 2多个变量一个逻辑块示例图
分离原因:
分离编写,每个信号,逻辑清晰。何时赋值、何时变化,一目了然,书写方便,若仿真中,发现错误,定位修改,亦为方便。
当编写某信号时,不必考虑其它信号赋值,只需专一考虑,当前信号逻辑关系,在何种条件下,进行何种变化,或者需要如何变化,其条件是什么。
代码字数少,错误概率下降;代码行数低,滚动翻页查找信号容易。写作速度提升,成功率提升。
这一点非常关键,请按这样书写。
规则2:左条件右赋值
在always块中,尽可能将条件和赋值放在一行,多个条件赋值行,尽量进行列对齐,这就形成左条件右赋值结构,如此表达,文字就如实际电路图一样。如图 1所示。若一行太长,可将赋值换行等,如图 2。

图 3左条件右赋值示例图
若相关EDA工具不允许同行,则信号赋值部分换行后也应尽量往右侧缩进。
规则3:少用begin……end块
如果一个逻辑块中只有一个信号,begin end可以省去了。
原因:减少字数,让逻辑部分凸显;减少行数,减少上下翻动页面的操作,会节省很多时间;行数多了,不容易定位问题。如图 3所示,不规范代码占用11行,而规范代码仅占4行,代码行数大大降低;另外,规范代码,一眼可分辨axis_tvalid信号何时变为1,何时变为0,而不规范代码不易分辨axis_tvalid信号行为;

图 4有无begin…end对比示例图
若相关EDA工具不允许非必要begin end缺失,需要添加,但也尽量少用。
规则4:使用规佳多段状态机;
不得使用一段式状态机,而使用规佳多段式状态机。其特性:
- 继承于3段式状态机;
- 结合规范1就演变为多段式状态机;
- 使用One-Hot编码,方便阅读。ST[IDLE],就是一个寄存器,不是ST==IDLE的比较组合逻辑,FPGA中大量寄存器资源,ASIC可能存在扣寄存器问题。若寄存器资源紧缺,可通过综合工具设定状态机类型。
模板如下:

图 5规佳状态机示例图
第一段:各个状态定义段。用于定义各个状态。
第二段:状态信号定义段。声明定义状态变量。
第三段:调试信号赋值段。仿真调试时,可将该信号变为ASIC显示,不需要文字对照,即可获知当前何种状态。
第四段:状态信号赋值段。当前状态行为定义,一般不需要改变。
第五段:下一状态转移段。描述状态机转移行为。这一段将状态转移展现十分清晰,故一般写状态时,并不需要再画流程图之类,代码描述就是流程图。
第六段:各个信号赋值段。根据当前状态以及条件,对各个信号进行描述。
不必背诵段数,使用时,复制粘贴,修改状态名,编写状态转移部分,尽量一个模块一个状态机,各模块ST名称不需要改,所有状态机模块,都是使用一个ST。
并不需要,区分摩尔,还是米勒,各个信号,根据状态,以及条件,分别编写,信号赋值,逻辑正确即可。如遇后面,时序不足,在最长路径,则可以将,有些信号,逻辑输出,改为寄存器输出。
状态机犹如树干,各信号犹如华果,写状态机如画树,画好树干添华果。
规则5:使用for generate例化多个模块
当一次调用多个相同模块时,应当使用generate例化多个模块。如此可一段代码例化多个实例,大大减少行数,减少错误。
如下图所示,一行代码可生成多个DAC模块的例化,并且可通过传递参数进行更改。一个dac81416有16个DAC,如此例化,可以控制16*CHIP_NUM个DAC。

图 6使用for generate例化多个模块示意图
规则6:使用Axis接口FIFO,或者使用空信号与数据同时有效的FIFO。
不推荐使用标准fifo,因为标准fifo,当empty信号拉低说明有数据时,有效数据不能在rddata信号给出,需要rd读有效后,下一拍有效数据才能在rddata信号给出,来回占用3个周期,使用极为不便。
首选推荐使用axis接口fifo,时序控制容易,甚至多个fifo可以收尾连接。
若不能使用axis接口fifo,使用类似first word passthough模式(Xilinx),show ahead模式(Intel Altera)。
二、命名规范
规则7:信号名与协议规范中名称一致
在有些协议规范中,定义了信号、状态或状态跳转条件名称,请尽量按照协议中名称命名。方便与协议对照,出现问题修改。例如SATA协议、FC协议。

图 7信号名与协议名一致示例图
规则8:多单词信号,使用大小写、或下划线区分。
多个单词组成的一个信号,需要使用大小写或下划线形式区分,例如axisTvalid、axis_tvalid、AxisTvalid、Axis_Tvaild。
规则9:信号名不得加入输入、输出等属性
信号名中不需要加入信号的输入、输出、wire、reg等属性。例如一个名叫cnt的信号,不需要添加cnt_o,cnt_i,cnt_w,cnt_r指明cnt是输入信号?输出信号?wire信号?reg信号?

图 8信号名规范示例图
规则10:信号命名要有意义,符合信号实际意义,尽量使用常用字命名
若信号名都是a、b、c、d、temp之类,代码很是难度,维护更加困难,故而信号起名字要起的有意义,尽量符合信号含义本身,如:
Cnt:计数器;
pCnt:packet cnt,数据包计数;
tCnt:time cnt,时间计数;
bCnt:bit cnt,bit计数;
sel、arb:select,arbiter仲裁选择;
二、模块规范
规则11:使用Verilog-2001规范将定义模块
遵循Verilog-2001版规范,一次性在model中声明信号及输入输出类型,不需要全部将信号列入model中,再次声明输入输出类型。
尽量对齐,方便例化使用。将每行的逗号分隔符放在每行的前面,也是不错的选择。


图 9模块定义规范示例图
规则12:信号分组,空行隔开
尽量将不同种类的信号,进行分组,用空行分隔,必要时添加注释。

图 10数据分组示例图
规则13:一个模块一个文件
一个文件中仅包含一个模块。
规则14:文件头部或尾部添加文件说明
文件说明可以添加在文件头部或者尾部。

图 11模块说明示例图
规则15:模块例化时,模块信号名尽量与外部信号名保持一致,文字分开,不可紧凑。
在模块例化时,最好保证信号的信号名与外部信号名一致,方便查找对信号查找。

图 12模块例化信号名示例图
三、表达规范
规则16:尽量使用位运算
例如if( rst ==1 ),可以写成if( rst );例如:
if( a ==1 && b ==0 && c ==1 )
if( a & ~b & c )
应写成后者,为减少字数,突出电路,突出逻辑。
切记VerilogHDL描述的是心中的电路。
规则17:使用always@*表达敏感信号列表
always@(a or b or c)容易遗漏信号,并带来书写麻烦,建议使用always@*或always@(*)代替。
规则18:组合逻辑用阻塞赋值,寄存器用非阻塞赋值
always@*中的block用“=”赋值,always@(posedge clk)中用“<=”赋值。
规则19:组合逻辑块中避免Latch产生
在always@*中,变量若没有提前设定初值,if中没有else,或case中没有default,综合时,就会产生Latch,影响时序分析,影响电路功能。需要避免。
若需要Latch,则需单独说明。
四、复位规范
规则20:复位模块使用异步复位同步撤销。
为保证recoverytime和removaltime两个时间约束,需要使用异步复位、同步撤销的复位电路。复位电路代码如下图所示。

图 13复位模块示例图
规则21:模块内部使用同步复位,顶层复位模块提供高、低同步复位信号。
按照Xilinx官方建议,为避免异步复位信号,扇出过大,占用时钟树资源,提倡同步高电平复位,甚至不需要复位。不用复位较难,故模块内部使用同步复位。
在有些设计中,无法保证全部都是高电平复位,故在复位模块设计时,同时给出该时钟的同步高低电平复位:clk_100m;rst_100m;rst_n_100m;

图 14顶层模块复位示例图
规则22:注意各时钟复位撤销次序
如上图所示,可以在aresets中更改复位撤销次序,由此可见,areset将控制所有复位,撤销时,rst_100m先撤销,rst_125m后撤销,最后是rst_200m撤销。
五、多时钟规范
规则23:单位宽信号跨时钟域需要使用双寄存器同步,或者使用异步FIFO同步
当信号跨时钟域时,不可避免的会出现亚稳态。亚稳态主要是寄存器特性导致。MOS版的寄存器如下图所示,主要由反相器和传输门组成。信号能够锁存,主要是非门作用,而非门结构及特性如下图所示。
假定非门特性为y=f(x);f(0.4)=0.6; f(0.6)=0.3;。
若clock上升沿到来时,采集到了中间电平,如0.4v,那么f(0.4)=0.6 ; f(f(0.4))=0.4;f(0.4)=0.6 ; f(f(0.4))=0.4;若非门特性非常理论对称,则输出也是中间电平;实际上并不对称有些差异,可想而知,电平会经过两个非门无限次迭代,最终收敛稳定。非门上下mos管越不对称,收敛越快。所以ASIC会将2个做的不对称。
若只有1级寄存器,输出的Q,还处于迭代中,无法满足后面的逻辑时序;经过2级迭代,会令收敛时间大幅降低,可以满足后续逻辑时序。故跨时钟必须要两级寄存器同步。

图 15寄存器原理图

图 16非门原理图
双所存,可以保证单位宽信号无误跨时钟域传输。
规则24:使用异步FIFO进行多位宽信号跨时钟域
多位宽数据跨时钟域传递时,应使用异步FIFO,尽量不要跨时钟域使用,因为添加约束时,需要将这些路径faultpass掉。当信号多时,将是一个复杂的工作。
所以多位宽信号使用异步FIFO进行跨时钟域。
另外,若跨时钟域寄存器太多,可以在架构上考虑,将前面的总线信号,进行跨时钟域传输。
规则25:使用时钟使能模拟低频时钟
有些时候,某些模块时钟需求较低,而内部工作时钟较高,若使用较低时钟,会占用一部分时钟资源,还牵扯数据跨时钟域问题,这时候可以考虑由内部高频工作时钟,产生低频时钟使能,而低频模块工作时,寄存器工作在高频使能,但是由时钟使能控制更新。如下图串口发送逻辑代码。

图 17使用时钟使能模拟低频时钟