规佳VerilogHDL代码规范

一、特征规范

对于reg信号,尽可能的放在自己单独的always块中。除非其中变量为一组信号,变化完全一致,否则不可放在同一个always模块之中。

例如图 1代码,不规范代码一眼看不清每个信号逻辑作用,几个信号混在一起,犹如乱麻,逻辑上交织不开,增加调试困难。而分离的代码能够明显将信号逻辑作用显示。阅读方便,若逻辑出错,修改起来,很是方便。

1代码示例图

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

2多个变量一个逻辑块示例图

分离原因:

分离编写,每个信号,逻辑清晰。何时赋值、何时变化,一目了然,书写方便,若仿真中,发现错误,定位修改,亦为方便。

当编写某信号时,不必考虑其它信号赋值,只需专一考虑,当前信号逻辑关系,在何种条件下,进行何种变化,或者需要如何变化,其条件是什么。

代码字数少,错误概率下降;代码行数低,滚动翻页查找信号容易。写作速度提升,成功率提升。

这一点非常关键,请按这样书写。

在always块中,尽可能将条件和赋值放在一行,多个条件赋值行,尽量进行列对齐,这就形成左条件右赋值结构,如此表达,文字就如实际电路图一样。图 1所示。若一行太长,可将赋值换行等,如图 2。

图 3左条件右赋值示例图

若相关EDA工具不允许同行,则信号赋值部分换行后也应尽量往右侧缩进。

如果一个逻辑块中只有一个信号,begin end可以省去了。

原因:减少字数,让逻辑部分凸显;减少行数,减少上下翻动页面的操作,会节省很多时间;行数多了,不容易定位问题。如图 3所示,不规范代码占用11行,而规范代码仅占4行,代码行数大大降低;另外,规范代码,一眼可分辨axis_tvalid信号何时变为1,何时变为0,而不规范代码不易分辨axis_tvalid信号行为;

4有无begin…end对比示例图

若相关EDA工具不允许非必要begin end缺失,需要添加,但也尽量少用。

不得使用一段式状态机,而使用规佳多段式状态机。其特性:

  1. 继承于3段式状态机;
  2. 结合规范1就演变为多段式状态机;
  3. 使用One-Hot编码,方便阅读。ST[IDLE],就是一个寄存器,不是ST==IDLE的比较组合逻辑,FPGA中大量寄存器资源,ASIC可能存在扣寄存器问题。若寄存器资源紧缺,可通过综合工具设定状态机类型。

模板如下:

图 5规佳状态机示例图

第一段:各个状态定义段。用于定义各个状态。

第二段:状态信号定义段。声明定义状态变量。

第三段:调试信号赋值段。仿真调试时,可将该信号变为ASIC显示,不需要文字对照,即可获知当前何种状态。

第四段:状态信号赋值段。当前状态行为定义,一般不需要改变。

第五段:下一状态转移段。描述状态机转移行为。这一段将状态转移展现十分清晰,故一般写状态时,并不需要再画流程图之类,代码描述就是流程图。

第六段:各个信号赋值段。根据当前状态以及条件,对各个信号进行描述。

不必背诵段数,使用时,复制粘贴,修改状态名,编写状态转移部分,尽量一个模块一个状态机,各模块ST名称不需要改,所有状态机模块,都是使用一个ST。

并不需要,区分摩尔,还是米勒,各个信号,根据状态,以及条件,分别编写,信号赋值,逻辑正确即可。如遇后面,时序不足,在最长路径,则可以将,有些信号,逻辑输出,改为寄存器输出。

状态机犹如树干,各信号犹如华果,写状态机如画树,画好树干添华果。

当一次调用多个相同模块时,应当使用generate例化多个模块。如此可一段代码例化多个实例,大大减少行数,减少错误。

如下图所示,一行代码可生成多个DAC模块的例化,并且可通过传递参数进行更改。一个dac81416有16个DAC,如此例化,可以控制16*CHIP_NUM个DAC。

图 6使用for generate例化多个模块示意图

不推荐使用标准fifo,因为标准fifo,当empty信号拉低说明有数据时,有效数据不能在rddata信号给出,需要rd读有效后,下一拍有效数据才能在rddata信号给出,来回占用3个周期,使用极为不便。

首选推荐使用axis接口fifo,时序控制容易,甚至多个fifo可以收尾连接。

若不能使用axis接口fifo,使用类似first word passthough模式(Xilinx),show ahead模式(Intel Altera)。

二、命名规范

在有些协议规范中,定义了信号、状态或状态跳转条件名称,请尽量按照协议中名称命名。方便与协议对照,出现问题修改。例如SATA协议、FC协议。

图 7信号名与协议名一致示例图

多个单词组成的一个信号,需要使用大小写或下划线形式区分,例如axisTvalid、axis_tvalid、AxisTvalid、Axis_Tvaild。

信号名中不需要加入信号的输入、输出、wire、reg等属性。例如一个名叫cnt的信号,不需要添加cnt_o,cnt_i,cnt_w,cnt_r指明cnt是输入信号?输出信号?wire信号?reg信号?

图 8信号名规范示例图

若信号名都是a、b、c、d、temp之类,代码很是难度,维护更加困难,故而信号起名字要起的有意义,尽量符合信号含义本身,如:

Cnt:计数器;

pCnt:packet cnt,数据包计数;

tCnt:time cnt,时间计数;

bCnt:bit cnt,bit计数;

sel、arb:select,arbiter仲裁选择;

二、模块规范

遵循Verilog-2001版规范,一次性在model中声明信号及输入输出类型,不需要全部将信号列入model中,再次声明输入输出类型。

尽量对齐,方便例化使用。将每行的逗号分隔符放在每行的前面,也是不错的选择。

图 9模块定义规范示例图

尽量将不同种类的信号,进行分组,用空行分隔,必要时添加注释。

图 10数据分组示例图

一个文件中仅包含一个模块。

文件说明可以添加在文件头部或者尾部。

图 11模块说明示例图

在模块例化时,最好保证信号的信号名与外部信号名一致,方便查找对信号查找。

图 12模块例化信号名示例图

三、表达规范

例如if( rst ==1 ),可以写成if( rst );例如

if( a ==1 && b ==0 && c ==1 )

if( a &  ~b  & c  )

应写成后者,为减少字数,突出电路,突出逻辑。

切记VerilogHDL描述的是心中的电路。

always@(a or b or c)容易遗漏信号,并带来书写麻烦,建议使用always@*或always@(*)代替。

always@*中的block用“=”赋值,always@(posedge clk)中用“<=”赋值。

在always@*中,变量若没有提前设定初值,if中没有else,或case中没有default,综合时,就会产生Latch,影响时序分析,影响电路功能。需要避免。

若需要Latch,则需单独说明。

四、复位规范

为保证recoverytime和removaltime两个时间约束,需要使用异步复位、同步撤销的复位电路。复位电路代码如下图所示。

图 13复位模块示例图

按照Xilinx官方建议,为避免异步复位信号,扇出过大,占用时钟树资源,提倡同步高电平复位,甚至不需要复位。不用复位较难,故模块内部使用同步复位。

在有些设计中,无法保证全部都是高电平复位,故在复位模块设计时,同时给出该时钟的同步高低电平复位:clk_100m;rst_100m;rst_n_100m;

图 14顶层模块复位示例图

如上图所示,可以在aresets中更改复位撤销次序,由此可见,areset将控制所有复位,撤销时,rst_100m先撤销,rst_125m后撤销,最后是rst_200m撤销。

五、多时钟规范

当信号跨时钟域时,不可避免的会出现亚稳态。亚稳态主要是寄存器特性导致。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非门原理图

双所存,可以保证单位宽信号无误跨时钟域传输。

多位宽数据跨时钟域传递时,应使用异步FIFO,尽量不要跨时钟域使用,因为添加约束时,需要将这些路径faultpass掉。当信号多时,将是一个复杂的工作。

所以多位宽信号使用异步FIFO进行跨时钟域。

另外,若跨时钟域寄存器太多,可以在架构上考虑,将前面的总线信号,进行跨时钟域传输。

有些时候,某些模块时钟需求较低,而内部工作时钟较高,若使用较低时钟,会占用一部分时钟资源,还牵扯数据跨时钟域问题,这时候可以考虑由内部高频工作时钟,产生低频时钟使能,而低频模块工作时,寄存器工作在高频使能,但是由时钟使能控制更新。如下图串口发送逻辑代码。

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

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部