不经意间看到几年前自己写的 FGPA 设计,代码风格勉强说的过去,但是逻辑设计方面的安全隐患比比皆是。许多初学者编写 Verilog 代码,基本都是按照 C 语言的思维和风格去设计,造成了很多不规范的共性问题。
本节主要总结一些不规范且危险的 Verilog 设计。主要针对可综合的数字设计,testbench 是仿真程序,一般情况下要求不是很严格。
代码规范要讲述的内容与编码风格是不一样的。编码风格只是建议,设计者可以不按照本教程编码风格的建议,随心所欲的畅写代码。只要逻辑正确,电路安全,哪怕写成柳絮满天飞的风格,编译器能正常编译正常仿真即可。设计者可以高傲的说,写自己的代码,让别人猜去吧!
代码规范是在一定程度上必须要遵从的规则,否则可能会对数字电路逻辑的正确性造成一定影响。除非针对某种特殊的设计,或个人轻车熟路、把握十足,可以稍微的越界 Verilog 代码规范,否则在设计中还是建议多注意这些规范。尤其初学者特别容易触犯此类问题。
关于赋初值
变量声明时不要对变量进行赋初值操作。如果变量声明时设置初始值,仿真时变量会有期望的初值,但综合后电路的初始值是不确定的。如果信号初值会影响逻辑功能,则仿真过程可能会因验证不充分而错过查找出逻辑错误的机会。例如下面描述是不建议的:
reg [31:0] wdata = 32'b0 ;
赋初值操作应该在复位状态下完成,也建议寄存器变量都使用复位端,以保证系统上电或紊乱时,可以通过复位操作让系统恢复初始状态。
建议设计时,时钟采用正边沿逻辑,复位采用负边沿逻辑。复位设计详见《5.1 复位简介》。
复位时语句块中所有的信号都应该赋予初值,不要漏掉相关信号。
if (!rstn) begin
cnt <= 'b0 ; //漏掉 cout 赋初值,很危险
end
else if (cnt == 10) begin
cnt <= 4'b0 ;
cout <= 1'b1 ;
end
else begin
cnt <= cnt + 1'b1 ;
cout <= 1'b0 ;
end
end
关于 always 语句
不到万不得已不要在 2 个 always 块中分别使用同一时钟的上升沿和下降沿逻辑,否则会引入相对复杂的时钟质量和时序约束的问题。
always @(posedge clk) begin
a <= b ;
end
always @(negedge clk) begin
c <= d ;
end
禁止在一个 always 块中同时将时钟的双边沿作为触发条件,编译、仿真可能会按照设计人员的思想进行,但此类电路往往不可综合,或综合后电路功能不会符合预期。
always @(posedge clk or negedge clk) begin
a <= b ;
end
禁止在 2 个 always 块中为同一个变量赋值,这是很多初学者容易犯的错误。
always @(posedge clk) begin
a <= b ;
end
always @(negedge clk) begin
a <= d ;
end
一个 always 块中不要存在多个并行或不相关的条件语句,使用多个 always 分别描述。
当一个 always 语句中存在多个并行或不相关的条件语句时,仿真的执行结果或综合的实际电路中,不相关的条件语句都是并行执行的。但是仿真过程可能是顺序执行的,如果有延迟信息可能会导致不可以预知的错误结果。且该写法可读性较差,功能结构划分不明显。
always @(posedge clk) begin
if (a == b)
data_t1 <= data1 ;
if (a == b && c == d)
data_t2 <= data2 ;
else
data_t2 <= 'b0 ;
end
//推荐分开写
always @(posedge clk) begin
if (a == b)
data_t1 <= data1 ;
end
always @(posedge clk) begin
if (a == b && c == d)
data_t2 <= data2 ;
else
data_t2 <= 'b0
end
关于时钟与异步
设计中尽量使用同步设计。
必须要使用异步逻辑时,一定要对不同时钟域之间的信号进行同步处理,不能直接使用相关信号,否则会产生亚稳态电路。同步处理具体实现请参考《4.1 同步与异步》及其后面相关章节。
尽量不要直接将时钟信号与普通变量信号做逻辑操作,或对时钟信号进行电平信号的检测判断。例如下列描述都是不建议的。
assign dout = (clk == 1'b1) ? din : 0 ;
always @(posedge clk) begin
if (clk = 1'b1)
data_t1 <= data1 ;
end
不同条件下对时钟进行选择时,不能直接使用选择逻辑,否则会出现毛刺现象。详见《5.4 时钟切换》。
关于综合
一般情况下信号变量不要直接使用乘法 *、除法 /、求余数 % 等操作。这些操作符被综合后,结构和时序往往不易控制。应该使用相关优化后的 ip 模块或工艺库中的集成模块。但是 parameter 类型的常量就可以使用此类操作符,因为在编译之初编译器就会计算出常量运算的结果,不会消耗多余的硬件资源。
组合逻辑的条件语句中条件补充完整,组合逻辑的 always 语句中敏感信号要罗列完全,以避免不期望的 Latch 产生。详见《Verilog 教程》章节《6.5 Verilog 避免 Latch》。
逻辑设计时要考虑代码能不能综合成实际电路,会综合成什么样的电路。详见《9.2 可综合性设计》。
关于例化
例化时,连接输入端的信号可以是 reg 型或 wire 型变量,连接输出端的信号一定是 wire 型变量。但是端口信号声明时,输入信号必须是 wire 型变量,输出信号可以是 reg 型或 wire 型变量。
多个模块例化时,模块名字在前,例化名字在后,且例化名字不能相同。
点我分享笔记
笔记需要是本篇文章的内容扩展!文章投稿,可点击这里
注册邀请码获取方式
分享笔记前必须登录!
注册邀请码获取方式
-->