说明:优先级是M的规则为强制项,优先级为R的规则为建议项。
通用约束
- 应有全局观念。
优先级:M
说明:你所编写的代码在成为最终硅片上的一部分之前,需要经过许多设计者利用各种各样的工具进行各种各样的处理。有时,在编码时你多做一点小事,可以为后续的流程节省几天,甚至几周的时间。反过来,在编码时,您的一点点偷懒,则可能会使后端工程师头皮发麻,项目进度延迟几周。所以,编码时应有全局的观念,在想考虑自身需要的同时,也要考虑别人或别的流程的需要。
2. 适当分解,使模块功能尽量单一;简化和标准化模块间接口,追求代码的简洁、清晰(高内聚,低耦合)。
3.适当抽象,把模块中存在共性的功能和代码尽量提取出来,设计成公共模块或信号。一些通用端口,可以使用interface实现,便于后期集成或修改。
4.简化条件表达式,如果太复杂的条件可以先分段产生中间信号。
5.提高整体编码方法学,在迭代中完成设计。在编码中,随时补充assert来证明自己的设计意图,随时补充cover来挑战验证的质量。
好代码没有一个绝对的标准。本节也只是些概括性的描述。这些描述是我们写一个好代码的最终目标。牢记住这些,这是我们写出好代码的方式。
命名规则
(1)一个文件只包含一个模块,且文件名和模块名一致。
优先级:M
说明:除了宏定义文件不需要定义模块单元外,其他文件均有且仅有定义一个模块单元。这样做的目的是保持文件结构清晰。
文件命名最好与设计模块的名称保持一致且为小写。文件名均需要使用缩写,通常使用IP代号缩写+“_”+功能性描述缩写方式。
说明:例如usb_clk_gen.v、ata_decoder.v等。
不当的做法: /process.v module TEST(a,b,c); … endmodule |
好的做法: usb_clk_gen.v/ module usb_clk_gen (a,b,c) … endmodule |
(2)参数(parameter)和宏(define)等常量大写。
优先级:M
说明:参数(parameter)多定义状态机状态名等,需要大写。宏(define)多定义全局变量,也需要大写。
`define DLY 1
parameter BUS_WIDTH = 16;
(3)所有命名中只允许出现字母、数字、下划线,且命名必须以字母开头,不能以下划线结尾。
优先级:M
说明:Verilog规定只能使用大写字母A~Z,小写字母a~z,数字0~9,下划线;只能以字母或下划线开头。但某些EDA工具不允许以下划线开头的命名,使用下划线开头的命名可能会导致语法错误。不要使用连续的下划线命名,不要使用大小写命名混用,更不能只通过大小写来区分,这不是好的编码习惯,代码可读性会很差,维护困难。建议除了参数(parameter)和宏(define)等常量大写,其他所有命名都小写。
不当做法:
GetSignal, wait____long__________time
好的做法:
wr_addr,rd_addr,bank0,TIMER_PARA
(4)禁用verilog/VHDL关键字来命名。
优先级:M
说明:由于IP应用的环境复杂,命名不当很容易出现问题。初学者容易犯一个错误:不能用_reg作为最后的后缀名,因为综合工具会给寄存器自动加上_reg,如果命名就用_reg作为后缀名,则扰乱了网表的可读性。
(5)使用有具体含义的信号名称,但名称不要超过20个字符,可以使用有意义的缩写,如acknowledge可缩写为ack;命名中不同单词使用下划线分割。
优先级:M
说明:尽量使用有具体含义的名称,通常受到信号长度的限制,一般会用到缩略语。信号名称长度太长的话,ASIC厂家定义的最大长度可能无法满足要求,导致后端的网表信号会被截断,而找不到原来定义的长名称信号。
不好的做法:A1,XYZ等
好的做法:ram_addr等
尽量使用有意义的缩写方法,常用缩写方法:取英文子母前面3个或4个字母比如addr,ack ;或者去掉元音保留辅音,比如cfg,pkt, clk, rst等。常用的命名缩写见下表:
全称 | 缩写 | 中文含义 | 全称 | 缩写 | 中文含义 |
acknowledge | ack | 应答 | length | len | (帧、包)长 |
address | addr | 地址 | pointer | ptr | 指针 |
arbiter | arb | 仲裁 | read | rd | 读 |
check | chk | 校验 | ready | rdy | 应答信号 |
clock | clk | 时钟 | receive | rx | 接收 |
config | cfg | 配置 | request | req | 请求 |
control | ctrl | 控制 | reset | rst | 复位 |
count | cnt | 计数 | segment | seg | 段 |
data in | din | 数据输入 | source | src | 源端口 |
data out | dout | 数据输出 | temporary | tmp | 临时 |
decode | de | 译码 | transmit | tx | 发送 |
decrease | dec | 减一 | valid | vld(v) | 有效 |
input | in | 输入 | write | wr | 写操作 |
(6)时钟信号使用clk关键字命名;复位信号使用rst关键字命名; latch信号名称加_lat后缀命名;三态信号使用_z后缀命名;异步信号使用_a命名;状态机当前状态以状态机名称+cur_st,下一个状态以状态机名称+next_st表述。
优先级:M
说明:好的做法如下,hclk,clk_77m,rst_n,hrst_n,arst,myfsm_next_st等等.
(9)定义多bit总线使用降序;但定义数组时,使用升序的写法。
优先级:M
好的做法:
32bit的sram_data总线定义为sram_data[31:0];
1个具有4个8bit元素的数组,reg [7:0] fir_d[0:3];
(10)建议延迟寄存器加1d、2d后缀。
优先级:R
好的做法:
打一拍后的信号统一加后缀”_1d”表示;
打两拍后的信号统一加后缀”_2d”表示;
打三拍后的信号统一加后缀”_3d”表示;
(11)例化模块时,模块端口信号的命名和连接信号的命名一致。
优先级:R
说明:例化模块时,模块端口信号的命名和链接信号的命名一致。便于IP的阅读理解:
不当的做法: block u_block( .rst_n (reset_n ), .clk (clock ), .addr (address ), .wr_data (write_data), …… ); |
好的做法: block u_block( .rst_n (rst_n ), .clk (clk ), .addr (addr ), .wr_data_data(wr_data), …… ); |
(12)例化名需要与原模块名称一致,如果有多个,加后缀_num.
优先级:M
说明:如果例化一次,则使用“u_”+模块名称;如例化多次,则使用u+ num+模块名称。
好的做法:
例化一次模块:
decoder u_decoder(…);
例化多次:
mux4 u0_mux4(…);
mux4 u1_mux4(…);
文件头规则
(1)每个文件必须包含文件头。
(2)文件头必须包含于文件头的边界符。
(3)文件头内容建议包含作者和联系方式。
(4)文件头内容必须包含发布版本编号和版本描述。
(5)文件头内容必须包含发布时间。
(6)文件头内容必须包含模块的功能描述。
(7)文件头内容必须包含修订记录。
/*************************************************************************************************************\
Copyright(C) : XXX Company
Author : zhangsan
Email : xxxx.@163.com
Project Name : CBB Product Ver : V1.0
Module Name : async_fifo.v
Module Called : dcsdp_ram.v
Called By : xxx module
Target Device : Xilinx K7 series
Tools Version : Vivado 2018
Description : for asynchronous FIFO control
Modification History:
Date By Review Rev Change Description
2022/03/12 zhangsan lisi v1.0 init
2022/03/16 wangwu zhangsan v1.0 add amost full and amost empty control
\*************************************************************************************************************/
在gvim编辑器中,推荐使用verilog-mode插件,直接生成header,自动例化等。
注释规则
(1)端口定义时加注释
优先级:M
说明:端口定义处一定要加注释。
好的做法:
module fec_encode( …,
dout,
…)
output dout; //encode data output
…
(2)内部信号定义加注释
优先级:R
说明:内部定义和使用的信号如reg和wire类型要加注释。
好的做法:
reg [n:0] in_reg; //register definition
wire [m:0] in_wire; //wire definition
(3)独立功能的实现加注释
优先级:R
说明:完成独立功能的always和assign等语句使用注释说明功能。
好的做法:
…
//my function comment here…
always @(posedge clk)
…
//my function comment here
assign signal_a= register_b
(4)注释使用单行。
优先级:R
说明:使用单行注释//便于添加和删除注释,不要使用/**/。
(5)例化cell加注释
优先级:M
说明:例化的cell如RAM、IO等必须有注释才容易被理解。
(6)删除不必要的冗余代码。
优先级:R
说明:包括以前的代码、不必要的逻辑,保持代码的清晰整洁。最典型的一个例子,冗余代码可能被综合器报告警,淹没掉一些我们需要关注的综合告警。
(7)综合指令加注释。
优先级:R
说明:综合指令,可以加入特别的注释。
(8)编译指令加注释。
优先级:R
例如想要设置一些仿真时需要加入的代码,保证满足仿真和实现的需要,加入注释注明。还有一些ASIC和FPGA代码有差异的地方,可以用FPGA的宏来区分。
好的做法:
`ifdef FOR_SIMULATION //show some test message for debug
…
`else //NORMAL CONFIG,i.e,for syntesis
…
`endif //end FOR_SIMULATION
编码风格
(1)使用缩进的方式编写代码,使用4个空格代替Tab键
优先级:M
说明:代码最高层次顶格写,子层次缩进4个空格。代码中不允许出现Tab键。原因是不同系统下不同的编辑器对Tab键显示不一致。为了保证代码的清晰,统一使用4个空格代替Tab键。
(2)禁止多个HDL语句写在同一行。
优先级:M
说明:禁止多个HDL语句写在同一行。不要试图测试编译器的能力,这会消耗不必要的工作量。
好的做法:
out1 = a1 & b1;//and operation
out2 = a2 | b2;//or operation
不好的做法:
out1 = a1 & b1; out2 = a2 | b2;
(3)一个端口定义占用一行。
优先级:M
说明:一个端口定义占用一行,不要多个端口一起定义。例如:
好的做法:
input clk ; //port clk description
input rst_n ; //port rst_n description
不好的做法:
input clk, rst_n;
(4)保持每行在72个字符以内。
优先级:R
说明:为了保证代码便于浏览和显示,每行不超过72个字符。原因是终端一行显示80个字符,如果显示行号,已经占用了8个字符的位置,所以只剩下72个字符来显示代码。对于超出长度的代码分多行完成,使用回车分割,第一个非空隔字符从原始行左侧第一个非空字符起向右缩进4格开始,表示该行的延续。
(5)端口的描述,先input,然后output、inout,时钟和复位最先定义。
优先级:R
说明:端口描述的此项;input、output、inout.时钟和复位等影响全局的信号最好放在最前面。一个模块如果信号很多,最好按照与其他模块的连接关系进行分组,便于读者理解和分析。
(6)使用显示端口映射,禁止使用位置端口映射。
优先级:M
说明:模块的例化verilog标准支持2种映射。一种是端口映射,一种是位置映射。例如:
好的做法:
adder u_addr
(
.a(a),
.b(b),
.sum (sum)
);
不当的做法:
adder u_addr(a,b,sum);
(7)避免端口连接时使用表达式(会产生glue logic)
优先级:M
说明:端口连接时使用表达式是不允许的,这样会产生glue logic,尤其是在顶层时,综合工具找不到模块的边界,不利于综合。例如
好的做法和不当的做法:
foo u_foo
(
.bad((m&n)^((p|q)>4’hc), //bad
.good(good) //good
)
(8)在一个地方定义所有的内部net
优先级:R
说明:需要在一个地方定义所有的内部net,建议定义位置在端口定义后面。
(9)使用参数定义而不是宏定义可配置的参数。
优先级:R
说明:一般来讲,能够使用parameter定义的,尽量不要使用define定义。因为define定义的是全局变量,容易与其他模块出现定义冲突,产生redefinition(重复定义)的告警,尽量慎用define语句。parameter是局部变量,则不会有类似问题。
(10)参数如果有关系(如倍数关系),需要通过定义指明。
优先级:R
说明:好的做法
parameter HALFWORD = 16;
parameter WORD = (2* HALFWORD);
(11)状态机的状态编码使用参数定义。
优先级M
说明:使用参数来定义,可以使状态机的可读性增强。
(12)复杂的表达式要用括号(不要测试编译工具的能力)
优先级:M
说明:复杂的表达式需要多使用括号来清晰的表述设计者的思路。虽然不使用括号也没有特别的问题,但这样并没有带来什么好处,反而使代码变得晦涩难懂,也考验了编译工具的能力。例如:
不当的做法:
if(&a == 1’b1 && !flag == 1’b1 || b == 1’b1)
好的做法:
if((&a == 1’b1 )&&( !flag == 1’b1) || (b == 1’b1))
(13)线型信号必须显式定义。
优先级;M
说明:线型信号必须显式定义。位宽为1bit的线型信号不定义是被大多数编译器设别的,但是多bit的线型信号必须要显式定义才行。所以还是那句老话,不要试图挑战编译器的能力。另外从语言的发展来看,类型的检查只能越来约严格。
不当的做法:
block u_block(
.signal_a(int_signal_a) ,// int_signal_a未被定义
.signal_b(int_signal_b) ,// int_signal_b未被定义
…
);
好的做法:
wire int_signal_a; //internal signal for block signal_a
wire int_signal_b; //internal signal for block signal_b
block u_block(
.signal_a(int_signal_a) ,// int_signal_a被定义
.signal_b(int_signal_b) ,// int_signal_b被定义
…
);
(14)操作符的左右位宽必须匹配。
优先级:M
说明:这个错误经常会犯,赋值时一定要注意类型是否一致,例如:
不当的做法:
wire [31:0] signal_a; //33bit
wire [7:0] signal_b; //8 bit
…
signal_x = signal_a & signal_b
…
好的做法:
wire [31:0] signal_a; //33bit
wire [7:0] signal_b; //8 bit
…
assign signal_x[7:0] = signal_a[7:0] & signal_b[7:0]
…
(15)条件判断语句的表达式必须是1bit
优先级R
说明:条件判断语句的表达式必须是1bit,否则会有多余的bit未被引用。例如:
不当的做法:
reg [7:0] data; //8 bit register
if(data) //data 为多bit值被用作逻辑判断
data_en = 1;
好的做法:
reg [7:0] data; //8 bit register
if(data>0) //此处为逻辑表达式
data_en = 1;
(16)状态机的编码风格使用组合逻辑和时序逻辑分开的方式。
优先级:R
说明:状态机一般采用三段式进行描述:一个组合逻辑完成下一个状态的计算,一个时序逻辑下完成下一个状态的跳转,一个时序逻辑完成输出计算。
(17)条件不全的case语句必须有default赋值,状态机必须有一个缺省状态。
优先级:M
说明:使用组合逻辑描述时,条件不全的case语句必须有default赋值,否则会产生不必要的latch。使用时序逻辑描述的状态机也可能会被综合工具优化,如果以前的状态在实际电路中不存在了,一旦状态寄存器进入该状态,电路会出现意想不到的致命错误。在状态机中设定一个default状态就确保了状态寄存器处于非法值时,仍可以进入正常工作。
(18)assign表述只用来修改名。
优先级:R
说明:建议在设计实现中,除了使用assign修改信号名称外,其他均使用always结构来表述,不提倡使用assign语句实现组合逻辑。
(19)一个always块只使用一种赋值语句,其中时序逻辑使用非阻塞赋值语句,组合逻辑使用阻塞赋值语句。
优先级:M
说明:阻塞赋值操作符用等号(=)表示。在赋值时,先计算等号右边的值,这时赋值语句不允许任何别的语句的执行。直到右边赋值给左边的时刻,才允许其他赋值语句执行。如果一个阻寒赋值的右边变量刚好是另一个阻塞赋值的左边变量,且都由同一个时钟沿触发,这时阻塞赋值会出现问题。非阻塞赋值操作符用小于等于号 (<=)表示,非阻寒赋值语句允许其他的语句同时进行操作。
(20)除时钟产生模块以外,内部其他模块不允许产生内部时钟
优先级:M
说明:除时钟产生模块以外,内部其他模块只能直接使用外部输入时钟、PLL输出时钟或者时钟产生模块产生的门控时钟和其他时钟。
(21)数值计算时,注意有符号数和无符号数处理的不同
优先级:M
说明:
- 扩位:有符号数扩展符号位,无符号数高位扩展0。
- 截位:直接截位无区别,四舍五入截位需要注意进位后的加法操作。
- 加减乘运算:使用“*”“+”“-”时,有符号数需要用signed强制转换为有符号数据类型,无符号数直接运算即可。
- 比较大小:有符号数比较,操作数需要用signed强制转换为有符号数据类型。
(22)使用signed/unsigned进行有符号数/无符号数的转换和运算
优先级:R
说明:两个有符号数相乘,使用signed转成有符号数后,用”*”来写乘法运算,可以充分利用综合工具的优化功能,对DataPath进行自动优化,达到更优的面积和时序性能。简单地说,手动补符号位等操作,会让综合工具识别不出DataPath。后续章节会详细讲解。
reg [15:0] mult_in_a;
reg [15:0] mult_in_b;
reg [31:0] signed mult_res;
assign mult_res = $signed(mult_in_a) * $signed(mult_in_b);
(23)拆分运算逻辑到最小单元,明确每一步操作的位宽,使用中间信号与赋值来保证算术表达式位宽的正确性.
优先级:R
说明:
// bad
input signed [3:0] a;
input signed [7:0] b;
output [11:0] c;
assign c = $unsigned(a* b);// 首先综合第一层操作a*b为4*8=8乘法,再做$unsigned去符号处理。乘法结果为8位并非想要的12位。这里a*b即自检测的表达式。
// good
input signed [3:0] a;
input signed [7:0] b;
output [11:0] c;
wire signed [11:0] c_sgn;// 使用中间变量明确乘法结果为12位。
assign c_sgn =a*b;// 综合为4*8=12乘法。
assign c= $unsigned(c_sgn);// 去符号处理,得到想要的结果
(24)条件表达式中严禁使用+、-、<<、>>等运算符。
优先级:R
说明:这是由于会导致潜在的位宽不匹配问题。比如,“if ((a+b)>c)”这样的写法,存在自检测表达式“a+b”,其位宽没有显式声明,比较时存在隐患,要坚决杜绝。而诸如“if (a>=(b-10’d1))”这样的写法 (a、b为10bit),是允许的。
(25)RAM读出数据如果需要连续多拍使用,必须先进行锁存
优先级:R
说明:虽然到目前为止,所有的Vendor提供的RAM模型,只要地址不变,并且不进行写操作,数据输出端口的读出数据可以保持不变。但为了使用更安全,RAM读出数据如果需要连续多拍使用,建议先进行锁存,例如通过读使能进行锁存。
(26)时钟相关
优先级:R
说明:
- 采用模块级时钟门控。根据功能划分将空闲模块的时钟关断或者降低时钟工作频率。时钟复位管理模块为各个模块产生可单独关断和打开的时钟;软件在操作过程中,根据业务场景关闭处于空闲状态的模块时钟。(静态节能)
- 对复杂模块内部的二级模块,建议支持时钟自动门控时钟。自动门控使能信号由逻辑自行产生,时钟自动门控支持Bypass功能。(动态节能)
- 减少时钟门控的级联情况。时钟门控不得超过两级;多个门控的条件,可以先进行逻辑运算,通过一级时钟门控控制,而不是多级门控级联。多级CG串联,会导致CG使能端的寄存器时序难收敛;级数越多,时序越难收敛,会被后端骂的。
- RTL编码要便于综合工具自动产生电路级时钟门控。自动插入门控的条件: 寄存器的值保持不变(always语句块中,最后一个else是q<= q)。
- 时钟和复位要做到本地化。相关联的时钟,复位不允许通过不同的路径进入本模块;相互交互逻辑的时钟应尽量晚分叉,减少OCV对时序收敛的影响。如下图,时钟在A点处分叉,计算setup时,需要计算蓝色段时延减去红色段的时延。工具计算时,两者均按照相同的工艺来计算时延。实际芯片上由于制造工艺的差别,额外引入一些工具无法计算的差异。
时钟路径的公共段不存在差异,因此逻辑的时钟越晚分叉,工艺偏差引入的无法计算的量就越小,有利于提高后端质量。
- 时钟产生电路建议单独划分成一个模块;复位信号产生电路建议单独划分成一个模块;如果设计中存在异步逻辑,则应单独划分成模块。方便后端快速理解芯片的时钟、复位电路,并且方便写约束。
(27)Memory使用规范
优先级:R
说明:
- 在设计上尽量减小RAM的大小,减少无效读写次数,避免无效访问。可以对RAM的时钟、片选、地址进行门控处理,减少时钟和地址总线不必要的翻转。例如某些RAM在某种工作模式下没有用到则可以关闭时钟;片选信号在没有读写操作时必须为无效状态;地址在没有读写操作时必须为固定值,例如为上一次操作的历史值。
- RAM选型时,尽量采用单口RAM。使用双口RAM,尤其是双口均可读写的RAM要慎重;以TSMC28HPM工艺为例,双口RAM面积是同等比特数单口RAM的2~3倍。
- 尽量减少小容量的RAM。在选型时,容量较小的RAM直接使用寄存器搭建(一般容量小于1024bit的RAM,推荐使用寄存器搭建,40nm工艺1bit寄存器大概面积为5.3um^2,22nm工艺1bit寄存器大概面积为2.9um^2)。
- 容量稍大,但深度和宽度比例失调的RAM,也推荐直接使用寄存器搭建,例如512bitx2的RAM,建议用寄存器搭建。RAM位宽不宜太大,选择位宽太大的RAM要慎重。具体根据Vendor的工艺要求以及PR的要求而定,例如不超过64bit。
- 对地址总线进行编码。编码的目的是为了降低地址总线的翻转率,例如,对于深度为2^n,并且地址规律递增或者递减可以进行格雷码编码,减少地址bit位翻转,降低功耗。
TIPS:将小的RAM合并为大的RAM,可以减小面积;将大的RAM拆分为小的RAM,可以降低功耗和优化时序,这两者在设计时需要进行折中权衡,例如:1024*32的两块RAM和2048*32的一块RAM。面积上,1块2048的比2个1024的小;功耗上,采用2块1024的RAM,每次读写只操作了2块中的一块,读写的动态功耗小。采用1块2048的RAM,读写的总次数不变,但是由于每次操作的都是一块大RAM,读写的动态功耗大。
(28)使用generate/for结构实现一个模块的多次例化、或者一段逻辑的多次例化。
优先级:R
说明:
generate/for结构的应用范围比单独for结构的应用范围更广,推荐使用generate/for结构。使用generate/for和for循环结构时必须定义进程名(如果不命名,每次block综合的名字可能不一样,里面信号的hierarchy就会变化),使用进程名区分后,一个模块中的多个循环体可以使用同一个循环变量。否则,每个循环体需要定义单独的循环变量。
使用单独for结构时,如果下标是一个大于1的范围,必须使用增量下标形式描述。假定循环变量为i,下标寻址范围为sel[2*i+1:2*i]。在单独for结构中,这种描述会被认为是两个变量,出现错误,因此必须使用增量下标形式sel[2*i+:2]。在generate/for结构中,可以使用sel[2*i+1:2*i]的描述方法。
(29)大模块的接口设计,要考虑可以支持在后端收敛时序困难时,信号可以被pipeline处理。
优先级:R
芯片规模较大时,subchip之间的连线延时可能会达到几ns。对于几百兆的时钟频率,即使是单纯的布线延时都不够。接口设计为可以插入Pipeline,当时序收敛有困难时,在subchip间插入打拍寄存器,可以大大简化后端时序收敛的难度。
(30)只使用可综合的语句。
优先级:M
说明:只有可综合的、可实现的设计才有被我们这里讨论的价值。常见的不能综合的表达:
实时函数、事件(尤其关于时钟)、系统函数(包含os函数、PLI函数、EDA工具接口函数)、用户自定义单元(UDP)、宏单元、disable语句在always block中、运算符===和!==等等。系统任务(如$display、$monitor)禁止使用
STA规则
(1)避免出现组合逻辑反馈环
优先级:M
说明:逻辑反馈环如下图中间的组合逻辑
在实践中,避免使用组合逻辑环路主要是因为它的特性所导致的危害:
- 组合逻辑环路违反了同步设计原则,容易振荡,从而导致整个设计不稳定和不可靠。
- 组合逻辑环路的行为功能取决于该环路上的延迟(逻辑延迟和布线延迟),一旦延迟发生变化,整个设计的行为功能将变得无法预测。
- 组合逻辑环路的振荡将导致EDA软件做无穷无尽的计算。为了完成这种计算,EDA软件将会切割环路。不同的EDA软件的切割方式不尽相同,这可能会与设计者的设计初衷相违背,从而导致逻辑功能错误。
- 组合逻辑环路无法进行静态时序分析(STA),可能会出现时序违例,或者导致STA过程时间过长。逻辑反馈环导致STA分析工具计算时序路径的延迟出现循环。如果由于出于设计需求有逻辑反馈环,可以使用set_disable_timing来避免逻辑反馈环时序检查时出现的循环,另外STA工具也会自动停止该循环。
(2)尽量使用同步设计。
优先级:M
说明:在无法避免异步设计的时候才使用异步设计,而且异步设计要求尽量与同步设计分开。很多STA分析工具无法处理异步电路。异步电路设计在功能和时序两个方面均需要进行认真验证。
(3)简化寄存器时钟域
优先级:M
说明:简化寄存器的时钟域,可以减小数据在寄存时出现亚稳态的概率。避免内部产生的信号或者门控时钟连接到寄存器的时钟端上。如果内部产生的时钟和门控时钟是出于低功耗等设计意图而无法避免,则尽量将该部分电路在电路顶层使用一个专门的时钟模块实现。
(4)慎重使用多周期(multi-cycle) 路径和伪路径 (false path),尽量用pipleline代替multi-cycle。
优先级:M
说明:多时钟路径和伪时钟路径属于两种异常时序路径,主要是为了提高后端的处理效率,以及测试质量。因为,异常路径越多,后端的处理效率一般就会越低。多时钟路径是指从一级寄存器的输出到下一级寄存器的输入中间的这部分逻辑,需要两个时钟周期或多个时钟周期才能完成的逻辑,而不是一个时钟周期完成,例如多位乘法器、多位除法器等等处理逻辑。
伪路径指不需要时序检查工具检查的路径,数据在不同时钟域的转换电路通常需要设定为伪路径,另外还有逻辑上不需要检查时序的路径也要设定为伪路径。
(5)避免时钟作为数据使用。
优先级:M
说明:直接或间接的将时钟作为数据输入会导致工具出现Timing Violation和竞争冒险。一般的解决办法是插入buffer来解决时序问题。
(6)避免使用latch锁存器。
优先级:M
说明:一般下面几种情况会产生latch:
1)使用if选择语句的组合逻辑没有else部分表达;
2)使用case选择语句的组合逻辑没有default表达。
(7)如果有异步处理模块,需要用一个单独的模块去实现。
优先级:M
说明:如果有异步处理模块,最好用一个单独的模块去实现。这样做的好处是简化STA分析工作,避免在复杂的、大规模电路中完成时序分析。
仿真规则
(1)在时序always模块中使用非阻塞赋值语句。
优先级:M
always @(posedge clk) begin b <= a; c <= b; end |
always @(posedge clk) begin b = a; c = b; end |
说明:如果在时序always模块中使用阻塞赋值语句,会导致时序紊乱。例如下图,一个信号a寄存一个时钟输出b,再寄存一个时钟输出c,如果阻塞赋值,会导致a寄存一个时钟输出b,c。
(2)在组合逻辑always模块中使用阻塞赋值语句。
优先级:M
说明:在组合逻辑always模块中使用阻塞赋值会认为导致仿真器无缘无故的空运行。综合之后该代码不会有问题。
(3)避免在组合always模块中敏感信号列表中缺失信号。
优先级:M
说明:这会导致综合前和综合后的仿真结果不一致。
不好的做法:
always@(a)
z = a or b;
(4)避免敏感信号列表出现冗余信号。
优先级:M
说明:这回导致仿真器作多余的判断操作而延长仿真时间。
不好的做法:
always @(a,b,c)
z = a or b;
(5)存储器内容需要初始化,避免仿真结果因仿真器而不同。
优先级:M
说明:所有存储单元,包括latch和寄存器,他们很可能处于控制电路的控制路径上,存储器内容的初始化就必须要被复位或置位。每个没有被初始化的latch和寄存器的初始值会因仿真器的不同而不同,仿真结果依赖仿真器不是我们愿意看到的。
(6)不要用X给信号赋值。
优先级;M
说明:这个要求主要是仿真x态的扩散。因为如果被赋值信号是一个控制信号,他参与的逻辑运算均为x态,这样会导致很多信号出现x态。
(7)不要使用delay延迟。
优先级:M
说明:由于综合器是忽略这些延迟的,所以如果加入该延迟,会导致前仿真和后仿真的结果不一致。
DFT相关规则
(1)尽量避免在电路内部使用三态总线。
优先级:M
说明:在测试过程中,为了满足可控性原则,电路所有节点的状态必须确定。总线冲突和总线漂移会导致电路的不确定状态。因此在电路设计中要避免在电路中出现总线飘移和总线冲突现象。对于三态总线,如图,如果同时有两个三态驱动有效,会导致总线冲突;如果某一时刻没有任何三态驱动有效,会导致总线飘移。
(2)避免使用双向net
优先级:M
说明:避免使用双向net,主要原因是ATPG工具无法正确判断双向信号的方向而导致无法正确产生测试向量(Test Pattern)。
(3)避免使用latch
优先级:M
说明:latch会给DFT产生很多不利的影响。例如:DFT中的扫描测试设计存在全扫描模式和部分扫描模式。全扫描模式要求在插DFT链过程中将触发器用带扫描功能的触发器替代,而latch是不能够做扫描的,而是被当作黑盒处理。对于latch的处理,可以采用bypass的方法或者采用透明法处理,latch的存在显然会增加DFT的工作量,降低测试覆盖率。
(4)避免使用双沿时钟。
优先级:M
说明:使用双沿时钟使得将这些DFT寄存器串成DFT链变得复杂。针对双沿时钟主要两种解决方法:在上升沿和下降沿DFT寄存器之间插入保护电路 或 将上升沿、下降沿的DFT寄存器分配到不同的测试链。
(5)避免使用门控时钟。
优先级:R
说明:DFT的一个基本原则是时钟信号可控。基于ATPG的扫描测试要求被测试电路的所有触发器都使用特殊设计的具有扫描功能的触发器代替,使其在测试时链接成一个或多个移位寄存器链,这样,电路分成了可以分别进行测试的的纯组合电路和移位寄存器,电路中所有的状态可以直接从原始输入和输出端得到控制和观察,很明显,所有触发器的时钟都是要求外部可控的。门控时钟往往会切断扫描移位操作,这对DFT明显不利。如果确实为了节能,需要门控时钟,那插入门控时,也需要插入DFT时钟。
(6)避免使用内部产生的时钟。
优先级:M
说明:避免内部产生时钟。内部产生的时钟使得扫描链上的数据变得不同步,破坏了扫描操作,这样会降低失效覆盖率。
(7)避免使用模块的时钟、置位/复位信号作为数据。
优先级:M
说明:避免模块的时钟、置位、复位信号被作为数据。当时钟/复位信号作为数据信号,导致一些 Defect 无法测试,降低测试覆盖率。
(8)避免出现组合逻辑环
优先级:M
说明:组合逻辑反馈环是扫描测试技术无法解决的,主要问题有:
- 使得组合逻辑电路不停地产生连续动作,这样很难使用组合ATPG工具;
- 可能会导致竞争冒险,产生无法预计的电路动作
- 环路通常是与延迟相关的,任何ATPG都无法对其进行测试。
(9)避免常量输入或者浮空的输出。
优先级:R
说明:由于我们无法控制常量输入和观察浮空的输出,这会降低电路的控制性和可观察性,从而降低了失效覆盖率。
(10)避免直接的时钟和复位/置位信号输出。
优先级:R
(11)避免使用时钟当中置位/复位信号
优先级:R