如何理解FPGA程序的利用警告信息排除代码中的小问题

调试FPGA程序,尤其是大型程序,一直是一项耗时耗力的工作。首先,由于HDL语言沿空间平行展开的特点不同于一般基于时间线性叙述的计算机语言,各元素之间的逻辑关系更紧密,不易理解和思考,也没有许多调试方法。同时,FPGA程序的综合和路由过程相对较慢。通常一个中型程序需要等待几十分钟才能得到输出文件。大型设计一夜之间运行的情况很常见,任何笔误都会带来高昂的沉没成本。每次说起这个,都会忍不住想起多年前的一次出差做实验。遇到一个研究所的大哥,他开玩笑说他喜欢领导分配FPGA任务,因为只要按下合成键,就可以安心休息半个下午。那个时候,世界刚打开的时候,“钓鱼”这个词还没有发明出来,没想到再强大最花哨的CPU也拯救不了这种等待。

离家近一点。在逻辑思路基本正确、程序编写普遍规范的前提下,大部分问题只是编写代码时的一些疏忽。通过实现功能仿真,其实大部分逻辑问题都可以找到。但是在实际工作中,往往没有时间和耐心去做仿真,而是在编码后直接进行硬件调试。这时,通过仔细阅读综合器给出的警告信息,可以在最耗时的布局布线之前发现并纠正各种小问题,从而有效提高开发效率。在调试阶段,如果遇到难以理解的现象,回过头来分析警告信息也是一种有效的方法。在这篇文章中,我们以vivado’

(以上图片来自网络:Mandapati的我的数字设计日记)

为了描述方便,我们先构建一个示例项目,包括模块top和adder。为了避免在很多经典教材中被称为“示例代码不专注于软件工程”,如“a=(b++)+(++c)”,本程序尽可能贴近实际项目编写(除了没有评论)。首先,它有一个特定的功能,将两个并发输入的数据流相加然后累加。其次,数据端口定义采用现在常用的AXI-Stream风格。

模块top是最顶层的模块,其源码Top.v如下所示。该模块具有时钟信号clk和异步复位信号rst。输入数据端口din_tdata[31:0],支持流控握手信号din_tvalid和din_tready。在模块内部,输入数据首先被拆分成两个16位的数据,分别代表两个要相加的数据流,注入到加法器模块adder中。加法器的输出数据为 adder_out[15:0]。从第 31 行开始的 always 语句完成了加法结果的累加。累加结果从acc_tdata端口输出,匹配数据有效标志为acc_tvalid。

看一下加法器的源码Adder.v,如下图。两个数据通道 din1 和 din2 共享同一组流控制握手信号 din_tvalid 和 din_tready。加法结果从端口 doout 输出。在模块内部,第 14 行的 always 语句完成了所有逻辑。核心语句在第21行:当输入数据有效且后端设备就绪时,进行加法操作。作者在这里写的时候也很感慨。超过 30 行代码只是为了服务第 23 行的“+”号。

至此,示例程序构建完成。程序比较小,按下Run Synthesis按钮,等待半分钟左右即可看到综合结果。我们来看看修改代码时常见的警告信息。

1. 常量驱动

警告就是警告,因为合成器无法判断它是否真的有问题。许多警告可以忽略。比如上面的例程看起来很完美d触发器逻辑功能,但是在合成完成后,还是会得到如下警告信息:

在这里,合成器提醒我们顶部模块的端口 din_tready 被驱动为常数 1,这可能是一个潜在的问题。在top的逻辑中,din_tready的作用是提醒模块外的前端数据源“是否准备好接收数据”。驱动为 1 表示“随时准备接收数据”。随机分析代码,我们可以看到din_tready是由adder实例adder1驱动的,在adder内部(adder.v第31行),信号来自adder输出的tready。回顾top.v的第17行,原来加法器的tready设置为常数1,这就是根本原因。具体到这个例子,逻辑本身问题不大,因为顶层模块的输入口只有数据有效信号acc_tvalid,

所以,在这个例子中,这个由常量驱动的警告可以“基本上”被忽略。不过问题不禁让人琢磨,比如这个例程没有考虑到在复位信号rst的有效期间应该拉低din_tready来禁止数据输入,不是很全面。进一步,我们也可以想想,这个接口定义在实际系统中是否存在隐患?也就是后级模块真的可以无条件接收数据吗?这些都是预警信息带来的好处。

2. 垃圾

下面开始折腾代码。首先,移除 top.v 的第 27 行的端口连接,只留下空括号。这样,adder1 实例的dout_tvalid 输出就悬空了。

综合后得到如下图的警告。合成器通知adder1中dout_tvalid对应的寄存器资源被移除。

很明显,这是因为顶部断开了信号连接,所以在加法器内部给dout_tvalid信号赋值d触发器逻辑功能,但是在整个逻辑的其他地方都没有用到,也没有输出,所以合成器会发出警告发出警告后。它被删除了。从这个例子可以看出,如果一个信号被自动移除,首先考虑的应该是它是否没有被其他地方使用。但是,正如您在下一个示例中立即看到的那样,这并不是信号被优化掉的唯一原因。

3. 被动信号

先恢复源码,然后尝试注释掉Top.v的第17行:

综合后得到如下警告信息:

第一条消息直截了当:没有驱动 adder_tready 信号。这显然是前面提到的修改带来的,源程序中缺少对adder_tready的赋值。接下来的第二条和更多消息令人困惑:adder1/dout[15:0] 已从逻辑中删除。这些信号明明是被后续的累加操作用到的,为什么要优化呢?通过分析adder中的逻辑关系可以知道,这还是因为adder_tready没有被驱动,所以综合器认为后面所有依赖于adder_tready的信号都没有存在的意义,所以把所有的脑子都去掉了。这提醒我们,如果发现一大块逻辑消失了,我们不仅要回头看是否缺少最终输出,但也期待看看是否存在不确定或未驱动的输入。当然,对于各种异常情况,不同的合成器,同一个合成器的不同参数,表现会有很大的不同。比如作者也看到了一些合成器会直接给未驱动的信号赋值为0。这种善意的掩饰,有时让人难以发现问题所在。

4. 多个驱动器

在top.v的第16行,把原来的adder_d2改成adder_d1,这是典型的笔误。本来是分别给信号addr_d1和addr_d2赋值,结果不小心把赋值给信号addr_d1改了两次。

对于上述情况,合成器清楚地表明一个信号是多驱动的,如下图所示。

但是,它指出的对象不是addr_d2,而是我的数据源din_tdata。这是因为,在合成器看来,din_tdata[15:0] 和 din_tdata[31:16] 都连接到 addr_d2[15:0],实际上是 din_tdata[15:0] 之间的点对点和 din_tdata[31:16] 短路,所以他们自己面临多驱动问题,此时 addr_d2 只是一个“别名”。就好像出门忘记戴帽子,合成器告诉你:小心冷风吹到头皮了。这种机器式的叙事风格有时会引起一些小麻烦,但习惯了就好。

5. 重置缺失

top.v 第 31 行的 always 语句使用异步重置。复位信号rst和时钟clk作为语句的触发条件。在语句内部,通过判断rst是否为真来选择reset操作。这是一个典型的用verilog编写的异步复位语句。在这里,尝试注释掉第 36 行,如下所示。

合成器将给出以下警告。

字面含义可以理解为:语句中有reset段,但是没有对acc_tvalid信号进行reset操作,导致逻辑缺失,或者综合器无法判断是否应该设置或者reset,所以担心综合结果和仿真结果不匹配。.

这种类型的警告可以帮助我们识别初始值不确定的寄存器,因为我们忘记写复位,这往往是许多重大错误的根源。如果有一些寄存器不需要reset操作,你应该写一个always部分只触发clk来避免上面的警告。

那么,此时综合结果是否产生了预期的逻辑呢?打开综合输出的逻辑图(下图),可以看到acc_tvalid是由一个没有reset和set的D触发器驱动的,符合修改语句的初衷。但是,我们仍然应该尽量避免这种非标准的符号。尤其对于新手来说,理解语言与真实逻辑的映射关系很重要,记住verilog有几个常用的语句例程。新颖的写作方式可能会导致完全不可预测的综合结果。

6. 位宽不匹配

修改adder模块的端口声明,如下图,将din1和din2的位宽分别由16bit改为17和15。

如下图,综合器会明确指出在top.v中实现加法器模块时遇到端口宽度不匹配。

需要指出的是,至少对于vivado + verilog来说,位宽不匹配警告只对模块端口连接有用。如果分配了两个不同位宽的信号,合成器将直接截断高位或用零填充高位而不给出任何警告,除非截断操作触发无用信号警告。因此,无论是wire类型还是reg类型,赋值时的位宽对齐问题都需要程序员自己注意。例如,在下面的段中,16bit src 被分配给 16bit dst1 和 15bit dst2。显然,分配给dst2时最高位会丢失,但此时合成器不会给出警告。这是verilog语言本身的一个特性。不,此外,因为 dst1 使用了 src 的所有位,在合成器的眼里,src中没有无用的位,也没有触发无用的信号警告。最终的结果是你可能错写了dst2少了1位,但是这个错误要到后面通过各种故障调试才发现。不是合成器懒惰,而是verilog语言本身就是这样设计的。相比之下,VHDL 更加严格。不同位宽的信号相互分配,没有警告,而是直接报错。但是verilog语言本身就是这样设计的。相比之下,VHDL 更加严格。不同位宽的信号相互分配,没有警告,而是直接报错。但是verilog语言本身就是这样设计的。相比之下,VHDL 更加严格。不同位宽的信号相互分配,没有警告,而是直接报错。

7. 不应该存在的闩锁

将top.v中对adder_d1和adder_d2的直接赋值语句改为always段,如下图:

上述修改会产生如下所示的警告信息: adder_d1 和 adder_d2 变量引入了锁存器。

分析上面的语句,我们可以看到din_tvalid确实相当于锁存使能信号。为 1 时,din_tdata 可以穿透到 adder_d1 和 adder_d2。如果你打开原理图观察综合结果,你会发现这里用到了一个叫做LDCE的锁存元件。

我们知道,FPGA公认的基本逻辑资源是查找表和D触发器。是否有锁存器取决于具体的FPGA型号和合成器的算法。因此,用 HDL 语言编写闩锁式语句并不是一个好方法。,这就是存在此警告的原因。

到目前为止,我们已经简要介绍了合成阶段的一些常见警告问题。当然,在后续的实现操作中,还会有更多的不理解的提示和警告,更多的是与具体设备的内部结构和元件有关。在这些阶段,需要更多地关注 XDC 文件、物理和时序约束,而不是 HDL 语言本身。

射频芯片系列设计现场报名

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论