跳转至

Verilog 常见问题分析

原始内容请参考提高班主页。在这里,我们将对这篇文档进行补充。

勿以善小而不为,勿以恶小而为之。——《三国志·蜀志传》

在使用 Vivado 进行工程设计时,不同阶段都可能出现各种各样的错误和警告。这些错误并不都来源于 Verilog 程序的语法错误,而是我们的设计本身蕴含的错误。本小节将会介绍一些常见的报错信息,希望能够帮助大家更好地排查自己的问题所在。


4.1 Warning:位宽不匹配

例子
MAX2.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
module MAX2 (
    input      [7:0]            num1, num2,
    output reg [7:0]            max
);
always @(*) begin
    if (num1 > num2)
        max = num1;
    else
        max = num2;
end
endmodule
Bug1_top.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
module Bug1_top(
    input  [7:0]              num1,
    input  [7:0]              num2,
    output                    out
);
MAX2 max2 (
    .num1(num1),
    .num2(num2),
    .max(out)
);
endmodule

打开 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 的信号(蓝色与红色交错的波形)。

Image title

这个问题的解决办法是检查问题中描述的接口是否被正确连接了。如果 Vivado 已经定位到了特定模块的具体某行,直接解决即可,否则需要逐一查看该模块与外部模块连接时对应接口的情况。

然而,对于模块内部的位宽不匹配问题,Vivado 就会采用自动补全或裁剪的策略,而不会报出任何提示信息。例如下面这段 Verilog 代码:

Example.v
1
2
3
4
5
6
7
8
9
module Bug(
    input [7:0]             num1,
    input [4:0]             num2,
    output [7:0]            out1,
    output [2:0]            out2
);
assign out1 = num1 | num2;
assign out2 = num1 & num2;
endmodule

Vivado 给出的 RTL 电路如下:

Image title

且不会报出任何 Warning 信息。因此,请大家在编写 Verilog 程序时,格外注意自己的位宽对应问题。模块之间与模块内部的位宽不匹配可能就会成为你最后发现的 Bug。

位宽不匹配的类似错误

在 RTL 分析阶段,我们还会遇到信号未连接、信号未使用等问题,这些问题的解决思路与位宽不匹配类似,都是回到代码中检查对应的接口即可。但切记:这些问题不应该被忽视,一定要在 RTL 分析阶段完全解决。

4.2 Warning:空载

例子
Bug2.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
module Bug2 (
    input  [3:0]                num1,
    input  [3:0]                num2,
    input  [3:0]                num3,
    output [3:0]                out1,
    output [3:0]                out2,
    output [3:0]                out3
);
assign out1 = num1 | num2;
assign out2 = num1 & num2;
assign out3 = num2 & num3;
endmodule
Bug2_top.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
module Bug2_top (
    input  [3:0]                num1,
    input  [3:0]                num2,
    output [3:0]                out1,
    output [3:0]                out2
);
Bug2 bug2 (
    .num1(num1),
    .num2(num2),
    .out1(out1),
    .out2(out2)
);
endmodule

打开 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
module Bug2_top (
    input  [3:0]                num1,
    input  [3:0]                num2,
    output [3:0]                out1,
    output [3:0]                out2
);
Bug2 bug2 (
    .num1(num1),
    .num2(num2),
    .num3(),
    .out1(out1),
    .out2(out2),
    .out3()
);
endmodule

这次 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
module Bug1_top(
    input  [7:0]              num1,
    input  [7:0]              num2
);
wire out;
MAX2 max2 (
    .num1(num1),
    .num2(num2),
    .max(our)
);
endmodule

此时 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;
由于 Vivado 本身的环路算法不是很好,组合环报错只能粗浅地具体到某个模块(甚至模块都定位不到,只报错『发现了组合环』),而由于模块之间的互联,这个环路可能会跨越多个模块,因此这个报错信息并不一定能够准确地定位到环路的位置。

想要解决组合环,首先应该对简单的电路部分进行检查,排除问题后再检查复杂的电路部分。一般情况下,组合环都是由于接线错误、输入接入输出等问题导致的,因此需要我们在编写代码时就注意这些问题。

聪明反被聪明误

考虑这样的一个代码:

assign a = a & 1;

这是一个必出组合环的情况,可是聪明的 Vivado 却会在实现电路时想尽一切办法避免组合环,在实现电路时并没有报出错误。这就很容易导致“仿真通过,上板不过”的问题。因此,组合环是一个极难发现又难以找到的问题,我们应当在初期设计时梳理清楚,尽量避免出现这种情况。

在这里,我们给大家介绍一个 2023 春季学期《组成原理实验》中的惊天组合环 Bug,以展现其极为困难与隐蔽的特性。

在某一次实验中,部分模块的依赖关系可以抽象为下图所示的电路结构:

Image title

这是四个组合逻辑模块,不包含任何寄存器与时钟信号。其中 Module4 的 singal 信号只与输入 input 有关。因此,从电路图上看,这是没有任何组合环路的。

Image title

正确的设计

然而,某位同学在 Module4 中是这样编写代码的:

always @(*) begin
    if (module3_out) begin
        // 生成 signal 的逻辑
        // 其他代码
    end
    else begin
        // 生成 signal 的逻辑
        // 其他代码
    end    
end

这直接导致 signal 信号与 Module3 的输出也产生了关联,进而产生了下图所示的组合环。

Image title

不幸地是,这次实验包含了近二十个不同功能的模块与数百个不同的信号,而组合环的报错并不会指出具体的错误位置。因此,最终多名助教合力找了一晚上才定位到这个问题。愿世间再无组合环


休息一会儿!

本部分内容到此结束,你理解了多少呢?


最后更新: October 16, 2023

评论

Authors: wintermelon008