Verilog 常见问题分析
原始内容请参考提高班主页。在这里,我们将对这篇文档进行补充。
勿以善小而不为,勿以恶小而为之。——《三国志·蜀志传》
在使用 Vivado 进行工程设计时,不同阶段都可能出现各种各样的错误和警告。这些错误并不都来源于 Verilog 程序的语法错误,而是我们的设计本身蕴含的错误。本小节将会介绍一些常见的报错信息,希望能够帮助大家更好地排查自己的问题所在。
4.1 Warning:位宽不匹配
例子
MAX2.v | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
Bug1_top.v | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
打开 Elaborated Design 后出现如下的提示信息:
[Synth 8-689] width (1) of port connection 'max' does not match port width (8) of module 'MAX2' [Bug1_top.v:9]
在我们的例子中,例化 MAX2 时,本应是一个 8bits 位宽的接口 max
却接上了一个位宽为 1bit 的信号 out
。
位宽不匹配是一个非常致命的错误(尽管它只是个 Warning),因为它会导致仿真中出现大量为 Z 或 X 的信号(蓝色与红色交错的波形)。
这个问题的解决办法是检查问题中描述的接口是否被正确连接了。如果 Vivado 已经定位到了特定模块的具体某行,直接解决即可,否则需要逐一查看该模块与外部模块连接时对应接口的情况。
然而,对于模块内部的位宽不匹配问题,Vivado 就会采用自动补全或裁剪的策略,而不会报出任何提示信息。例如下面这段 Verilog 代码:
Example.v | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
Vivado 给出的 RTL 电路如下:
且不会报出任何 Warning 信息。因此,请大家在编写 Verilog 程序时,格外注意自己的位宽对应问题。模块之间与模块内部的位宽不匹配可能就会成为你最后发现的 Bug。
位宽不匹配的类似错误
在 RTL 分析阶段,我们还会遇到信号未连接、信号未使用等问题,这些问题的解决思路与位宽不匹配类似,都是回到代码中检查对应的接口即可。但切记:这些问题不应该被忽视,一定要在 RTL 分析阶段完全解决。
4.2 Warning:空载
例子
Bug2.v | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Bug2_top.v | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
打开 Elaborated Design 后出现如下的提示信息:
[Synth 8-7071] port 'num3' of module 'Bug2' is unconnected for instance 'bug2' [Bug2_top.v:7]
[Synth 8-7071] port 'out3' of module 'Bug2' is unconnected for instance 'bug2' [Bug2_top.v:7]
[Synth 8-7023] instance 'bug2' of module 'Bug2' has 6 connections declared, but only 4 given [Bug2_top.v:7]
不难看出,Bug2 模块的输入端口 num3
和输出端口 out3
在顶层模块 Bug2_top 中都被忽略了,因此报出了空载警告和端口数目不匹配的警告。
空载是指在例化时忘记写某个模块的某个接口,这往往是模块接口太多而导致的粗心错误。报错信息中已经写的非常清楚:bug2 模块的 num3 端口和 out3 端口都没有接上。
那么,如果我们写出了端口,但不连接会怎么样呢?例如下面这段代码:
Bug2_top.v | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
这次 Vivado 就没有报错了。但按照我们先前的介绍:模块的输出端口可以悬空,输入端口不能悬空。因此,这样的设计依然存在一定的风险。
提醒
对于不需要的模块输入端口,请为其连接一个不影响结果的常值。
4.3 Warning:值不可达
[Synth 8-151] case item 32'hffff is unreachable
这类问题一般出现在 case 语句中,表示某个 case 分支永远不会被执行,这一般意味着 case 括号中的信号位宽不足以到达这个数。
这个问题的解决方法是根据定位的行数检查 case 括号中的变量是否位宽足够大 。如果不足则需要扩展位宽。
你有没有写错变量名?
有些时候,我们更容易把变量名拼写错误,而 Vivado 会默认把没有声明的变量当作 1 位宽的 wire 型信号,这时候也会出现值不可达的 Warning。Vivado 的这一操作带来的更为常见的后果是:手滑写错了变量名中的一个字母,新的信号被 Vivado 视作 wire 类型信号,不会报出 Error,可以正常仿真与综合,但结果就是不对。
例如,我们回到 4.1 小节中的例子:
Bug1_top.v | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
此时 Vivado 会报出警告:
[Synth 8-689] width (1) of port connection 'max' does not match port width (8) of module 'MAX2' [Bug1_top.v:9]
4.4 Warning:使用在声明之前
[Synth 8-333] identifier 'dout' is used before its declaration
这是一个在软件编程语言中必错的问题,但在 Verilog 中却是可以通过的。
实际上,Verilog 中的信号声明是可以放在任意位置的,但是如果你在声明之前使用了这个信号,那么 Vivado 就会给出警告。或许这个警告不会对正确性有任何影响,但它可能会加大资源使用量和综合电路的时间。
4.5 Warning:推断出锁存器
[Synth 8-373] inferring latch for variable 'psum_reg'
锁存器的产生一般代表着组合电路的 case 或 if-else 没有列举所有的情况,这时电路需要在某些情况下保持上一次的状态,这就需要锁存器来实现。可是,锁存器的定义就已经违背了组合电路的原则 ,这个电路也不再是组合逻辑的了。
在 Lab2 中,我们已经为大家详细介绍了锁存器的产生原因与避免策略,此处就不再展开。
注意:避免锁存器
许多同学可能认为,一些情况下的锁存器不一定代表错误,因为那些没有被列举的情况永远不会发生。这种想法是错误的!在逻辑上确实是这样,但不要忘记上板时信号可能会产生毛刺。一旦那些毛刺被捕捉到,那么这些没有被列举的情况就会发生,这时候锁存器就会被触发,导致电路状态直接锁死。但是,仿真时的理想环境保证了毛刺不会产生,一些锁存器也就不会影响电路的正确性。
请记住:锁存器是仿真通过上板不过的重要原因之一。如果你通过了行为仿真而上板结果不正确,那么一定要打开电路图,看看是否有锁存器产生。
4.6 Critical Warning:多驱动
[Synth 8-6859] multi-driven net on pin d_OBUF[1] with 1st driver pin 'd_OBUF[1]_inst_i_1/O'
多驱动是指一个输出信号同时被多个输入信号驱动,这是一个非常严重的问题,因为这会导致电路的行为不确定。在编写代码时,以下几种情况会被 Vivado 认为存在多驱动情况:
- 一个信号被多个 always 块赋值;
- 一个信号被多个 assign 语句赋值;
- 一个信号与多个模块的输出端口相连;
- 上述情况的混合。
在这条报错信息里,d 这根 wire 型变量被作为了多个模块的输出,因此是错误的。
Tips:定位多驱动的信号
多驱动问题一般是在实现电路时被报出。由于 Vivado 在实现时会将信号重命名、元件重排(元件可能被移到另一个模块中),因此报错信息中说的信号名可能与代码中的信号名不一致,但大多数情况下都是给代码中的信号名加了一些后缀。你可以基于此尝试寻找代码中对应的变量。
4.7 Critical Warning:组合环
[Synth 8-6859] Found Timing Loop
组合环是指一个信号经过组合电路后又回到了自己,例如;
assign a = b;
assign b = a;
想要解决组合环,首先应该对简单的电路部分进行检查,排除问题后再检查复杂的电路部分。一般情况下,组合环都是由于接线错误、输入接入输出等问题导致的,因此需要我们在编写代码时就注意这些问题。
聪明反被聪明误
考虑这样的一个代码:
assign a = a & 1;
这是一个必出组合环的情况,可是聪明的 Vivado 却会在实现电路时想尽一切办法避免组合环,在实现电路时并没有报出错误。这就很容易导致“仿真通过,上板不过”的问题。因此,组合环是一个极难发现又难以找到的问题,我们应当在初期设计时梳理清楚,尽量避免出现这种情况。
在这里,我们给大家介绍一个 2023 春季学期《组成原理实验》中的惊天组合环 Bug,以展现其极为困难与隐蔽的特性。
在某一次实验中,部分模块的依赖关系可以抽象为下图所示的电路结构:
这是四个组合逻辑模块,不包含任何寄存器与时钟信号。其中 Module4 的 singal
信号只与输入 input
有关。因此,从电路图上看,这是没有任何组合环路的。
然而,某位同学在 Module4 中是这样编写代码的:
always @(*) begin
if (module3_out) begin
// 生成 signal 的逻辑
// 其他代码
end
else begin
// 生成 signal 的逻辑
// 其他代码
end
end
这直接导致 signal
信号与 Module3 的输出也产生了关联,进而产生了下图所示的组合环。
不幸地是,这次实验包含了近二十个不同功能的模块与数百个不同的信号,而组合环的报错并不会指出具体的错误位置。因此,最终多名助教合力找了一晚上才定位到这个问题。愿世间再无组合环。
休息一会儿!
本部分内容到此结束,你理解了多少呢?