目录
5.1干干净净
5.2代码划分
1.我们要学会善于分类、归纳和总结,按照高内聚、低耦合的原则,把代码划分为模块、函数和任务,形成合理的层次结构。
高内聚:让每一个模块、函数或任务只做一个功能,隐藏内部实现细节,提供一个干净的接口。
低耦合:模块之间尽量用少量的连线连接,避免大量的信号线连来连去,这样可以减少错误的发生。
2.模块划分时,要将关键路径逻辑和非关键路径逻辑放在不同模块,这样在综合时就可以对含有关键路径的模块做速度优化,而对含有非关键路径的模块做面积优化,而如果把它们都放到同一模块里,就不能对它们使用不同的综合策略。
关键路径通常是指同步逻辑电路中,组合逻辑时延最大的路径(这里我认为还需要加上布线的延迟),也就是说关键路径是对设计性能起决定性影响的时序路径。
优化关键路径可以有效提高性能。
3.模块划分时,要将相关的组合逻辑放在同一个模块,这样在综合时可以对其进行优化,因为综合工具通常不越过模块的边界来优化逻辑。
4.我们要注意提取公共的代码或常用的代码,形成模块、函数和任务,便于项目组内使用和以后的移植。
5.在模块内部,我们也要合理地切分逻辑,让相关地代码紧挨着,组合在一起形成一个个不同的逻辑块,按照合理的顺序安排这些逻辑块。进一步我们可以用固定长度的横线分割这些逻辑块,并针对每个逻辑块加注释。这样我们就可以按照每个逻辑块阅读和理解,就能够很好地阅读和理解整个模块。另外当函数或任务地代码行非常多时,就要考虑把它分解了,把每一块相关地部分用子函数或子任务来实现。
6.模块内不要有重复地代码,因为重复地代码会造成代码混乱、维护困难和修改遗漏等问题。设计时,可以使用子模块、函数、任务、循环语句和寄存器来消除重复的代码。
5.3代码要求
5.3.1Verilog部分
代码要求A:
1.设计时要把应用文档和设计文档写好。在设计文档中要把设计思路、数据通路、实现细节等描述清楚,在经过评审通过后才能开始编写代码。
2.设计时要尽量考虑用可靠的IP,因为可以保证设计的质量。
3.要把每个模块放到一个单独的文件里,模块名和文件名保持一致,<文件名>=<模块名>.<扩展名>,这样名字规范,便于查找。
4.顶层模块应该只包含模块之间的互连。
5.按照合理的层次结构组织各个模块,并按照合理的目录结构存放各个文件。
代码要求B:
1.要避免书写可能会导致竞争冲突的语句,因为这些语句会给仿真调试带来很大的麻烦。
数字电路中,信号传输与状态变换时都会有一定的延时。
- 在组合逻辑电路中,不同路径的输入信号变化传输到同一点门级电路时,在时间上有先有后,这种先后所形成的时间差称为竞争(Competition)。
- 由于竞争的存在,输出信号需要经过一段时间才能达到期望状态,过渡时间内可能产生瞬间的错误输出,例如尖峰脉冲。这种现象被称为冒险(Hazard)
通过verilog书写规范来避免竞争的方法:
1.时序电路设计时用非阻塞语句<=,组合逻辑设计时用阻塞语句=;
2.不要在多个always块中为同一个变量赋值;
3.避免latch产生;
4.多使用时钟同步电路,利用触发器进行打拍延迟使同步电路信号的变化都发生在时钟边沿;
5.采用格雷码计数器,减少多位数同时变化的可能性,可以减少信号同时变化的可能性。
2.要避免实例化具体的门级电路。
门级电路的可读性差,难以理解和维护,而且如果使用特定工艺的门级电路,设计将变得不可移植。
3.要避免使用内部三态电路,建议用多路选择器代替内部三态电路。
要避免内部三态总线出现悬空状态,在不驱动总线的时候,可以使用上/下拉电阻把它们拉到默认状态,也可以直接把它们驱动为默认状态。任何器件的输入都不能悬空,因为如果输入悬空,会导致输入处于中间电平状态,导致器件有很大的电流消耗。
4.要避免使用嵌入式的综合指令。
5.要避免使用锁存器Latch,也要避免出现因为敏感列表不全而生成的Latch。
如果在always中case或if语句的分支逻辑不全,就会生成锁存器,所以要注意检查它们的分支逻辑,将条件赋值语句写全,例如在if语句最后加一个else,在case语句最后加一个defaults。另外,如果在组合always块的开始为所有组合逻辑的输出赋默认值,同样可以避免生成Latch。
在常规的设计中,锁存器一般只用于顶层模块的clock_gate,这样既可以在此模块不工作时把clock停掉以节省功耗,又可以保证clock上不会出现glitch。例如,
always@(free_clk or en)begin
if(~free_clk) l_clk_en = en;
end
assign clk = (l_clk_en & free_clk);
代码要求C:
1.要保证时钟和复位信号不会出现任何的glitch,因为glitch会导致电路工作出现错误。
2.要尽量保持时钟和复位信号简单,不要使用复杂的组合逻辑,便于在测试模式下做bypass,便于后端生成时钟树和复位树。另外,模块中所有的寄存器要尽量做到同时复位。
3.要小心使用门控时钟。
因为门控时钟用不好会引起毛刺,会给时序带来问题(例如时序可能会变紧张),会给扫描链的插入带来问题(例如某些寄存器可能不会插入到扫描链上),而且门控时钟不便于模块移植。
我们可以借助于综合工具自动插入门控时钟,也可以在时钟生成模块(CGM)中手工控制,或者在顶层模块中手工控制。
若手工实现门控时钟,注意门控信号要从latch或者register输出,以避免出现glitch。
4.要避免使用模块内部产生的时钟。因为在测试模式下要把内部时钟bypass到来自于模块外部的可控时钟上,这样才能把使用内部时钟的寄存器插入到扫描链上。在设计中最好使用同步设计。
5.要避免使用模块内部产生的复位。因为在ATPG模式下要把内部复位bypass到来自于模块外部的可控复位信号。
6.如果确实要使用门控时钟、内部时钟或内部复位,就把产生这些信号的代码放到一个独立模块里,并在顶层模块实例化这个独立模块,这样所有的子模块就是用单一的时钟和复位信号。在测试模式下,要把它们bypass到来自于模块外部的可控时钟和复位。
7.在一个模块内尽量只用一个时钟。在多时钟的设计中,最好把用于时钟域隔离的逻辑放到一个单独的模块里,这样做既便于综合处更优的结果,也便于预处理网表仿真时的SDF文件。
8.只用时钟的一个沿(上升沿或下降沿)采样信号,不要既用上升沿,又用下降沿。
但是如果设计需要既用上升沿,又用下降沿,那么最好分为两个模块设计。建议在顶层模块中对时钟clock生成一个取反的时钟clock_n,如果其他模块要用下降沿,那么就可以用posedge clock_n,这样的好处是在整个设计中采用同一种时钟沿触发。
9.要对跨时钟域的信号做同步化处理。
1.要同步的信号必须是从寄存器出来的信号,不能是从组合逻辑出来的信号;
2.要同步的信号至少要锁存两个clock,然后才能使用;
3.对多位计数器信号同步要使用Gray码。
10.要避免出现多周期路径和假路径。
多周期路径就是从一个寄存器的输出到另一个寄存器的输入的路径不能在一个周期内完成,需要多个周期才能完成。在STA时要用set_multicycle_path把它们设置为例外。
假路径就是设计者认为可以不用考虑Timing的逻辑路径,在STA时要用set_false_path把它们设置为例外。
11.要写可测性的设计DFT。
在写功能逻辑的时候,就要考虑ATPG和BIST等测试模式,要为它们添加各种测试逻辑和bypass逻辑。
代码要求D:
1.对于组合逻辑,要用always@(*),不要用always@(a or b or c)。
因为如果敏感列表不全,可能会引起RTL仿真和网表仿真不一致,但是用always@(*)就可以完全避免敏感列表不全的情况发生,而且更加方便,不用费力去填敏感列表。
2.要注意阻塞逻辑和非阻塞逻辑的使用场合。
1.不要在一个always块内混杂阻塞赋值和非阻塞赋值;
2.对于组合逻辑always@(*),就用阻塞赋值=;
3.对于锁存逻辑always@(*),就用非阻塞逻辑<=;
4.对于时序逻辑always@(posedge clk),就用非阻塞逻辑<=;
3.要编写合理的状态机电路。
状态机电路要分为组合逻辑和时序逻辑两个不同的进程。组合逻辑包括状态译码和输出,时序逻辑包括状态寄存器的切换。所有的状态都要处理,不要出现无法处理的和使状态机时空的状态。
4.case语句通常综合成无优先级的多路复用器,而if-else或条件赋值语句(?:)则综合成有优先级的多路复用器。case语句的时序要比if语句的时序好一些,而优先级编码器一般只在信号到达有先后的时候才使用。
代码要求E:
1.对于模块的输入信号,尽量在寄存器锁存之后再用,但是如果输入信号是其他模块的寄存器输出,那么就不要寄存器锁存;对于模块的输出信号,尽量用寄存器锁存知乎再输出。
这样做可以使输入时序、输出强度和输出延迟都得到预测,从而在做模块综合和STA的时候,处理起来更简单,可以获得更好的Timing。
2.不要驱动input信号,因为这是错误的,但是仿真和综合不报错误。
要保证每个output信号都被驱动,对于暂时不用的output信号,就用0或1驱动。
3.模块实例化要采用按端口名字连接的方式,而不要采用按端口位置连接的方式。
4.在写设计代码时,reg变量只能在一个always语句中赋值,但是如果是写验证代码,就没有要求。
5.在写设计代码时,函数和任务中不要使用全局变量,否则会引起RTL仿真和网表仿真不一致。但是如果是写验证代码,就没有要求。
6.在写验证代码时,不要引用模块的内部信号。
7.在写设计代码时,不要使用那些只能用于仿真的语句,如force/release、fork/join、initial、repeat、forever、===、!==等,要谨慎使用那些受限制的语句,如disable语句。
代码要求F:
1.在`include的文件名中不要添加目录名。
2.为了保持代码的可读性和可维护性,常用`define做常数声明,最好把`define定义的参数放在一个独立的文件中,然后在模块的头部用`include包含这个参数文件,或者不在模块的头部包含这个文件,在文件列表中把这个参数文件名放到所用模块文件的前面。
3.为了使模块可配置和可移植,可以使用parameter。
代码要求G:
5.3.2 SystemVerilog部分
1.使用logic代替reg和wire。使用logic时,它会根据使用的位置来确定用途(寄存器、锁存器还是线网)。
另外对于要求是单驱动的线网,如果代码错误地对此线网做了多驱动,用wire不会报错,但用logic会报错。
2.在只用二值逻辑的地方,可以使用bit、byte、shortint、int和longint,这样可以节省仿真的内存。
3.使用新的数据类型,如struct、enum、array、queue和typedef等类型。
4.使用always_comb、always_ff和always_latch来代替Verilog中的always,可以避免Verilog中always的误用。
5.使用unique case和unique if代替综合指令full_case和parallel_case。
6.使用interface,简化接口。
7.使用break和continue,简化for和while循环内的跳转。
8.使用return语句,简化task和function的返回。
9.使用类,实现设计抽象和随机验证,检查验证覆盖率。
10.使用断言,尽早检测出发生的错误。