潘伟涛 · 2022年04月15日

[干货]FPGA设计中大位宽、高时钟频率时序问题调试经验总结

今天跟大家分享的内容很重要,也是我们调试FPGA经验的总结。随着FPGA对时序和性能的要求越来越高,高频率、大位宽的设计越来越多。在调试这些FPGA样机时,需要从写代码时就要小心谨慎,否则写出来的代码可能无法满足时序要求。另外,最近跟网友聊天时,有谈到公众号寿命的问题,我觉得网络交换FPGA公众号应该在这方面有优势,作为高校里从事FPGA开发的老师,又有兴趣做公众号的维护,不会遇到其他公众号做着做着就停更的问题,我们的公众号也许将陪伴你十年甚至几十年。同时,也请大家放心,我们今后每篇文章都将用心的去总结归纳,确保篇篇都是干货!新的一年开始了,希望在新的一年里,继续给大家分享平时遇到的FPGA调试中遇到的问题及经验,一起进步,一起努力,加油!

跨时钟域信号的约束写法

问题一:没有对设计进行全面的约束导致综合结果异常,比如没有设置异步时钟分组,综合器对异步时钟路径进行静态时序分析导致误报时序违例。

约束文件包括三类,建议用户应该将这三类约束文件分开写在三个xdc/sdc文件中。

第一类是物理约束,它主要对设计顶层的输入输出引脚的分配约束、电平标准的约束,如下图所示:在quartus环境下,对pcie_rstn和pcie_refclk的电平标准和管脚进行了约束。
c6a34e04926127c2879c68c459759b95.png

如下图所示:在vivado环境下,对rst_n和sys_clk_PCIe_p的电平标准和管教进行了约束。
4dd9297853ede50bc75d46e476830cf2.png

第二类是调试约束,用户在使用ila调试时,Vivado会自动生成相关ila的调试约束。

如下图所示,这是Vivado自动生成的相关ila的调试约束。

67452f0b5f9a7f91058ef589d848a88c.jpg

第三类是时序约束,这类约束的种类最多,它包括时钟周期约束、输入输出延迟约束、跨时钟域路径约束、多周期路径约束、伪路径约束等。

时钟周期约束:用户需要将设计中的所有时钟进行约束后,综合器才能进行合理的静态时序分析。一个设计中的时钟主要分为两类:主时钟和生成时钟。主时钟包括由全局时钟引脚接入的时钟、高速收发器的输出时钟。生成时钟包括由MMCM/PLL产生的时钟、用户逻辑分频产生的时钟,建议用户不要使用后者,因为它通常是由组合逻辑或触发器生成的时钟,这种时钟的歪斜、抖动、驱动能力都很差。对时钟进行约束时,主要针对时钟的频率、占空比、抖动、不确定性等参数进行约束。

全局时钟引脚接入的时钟约束举例:

如下图所示,在quatus环境下,对全局时钟引脚接入的时钟pcie_refclk进行了约束,因为占空比是50%,抖动和不确定性也采用默认值,所以图中只对频率进行了约束。

56502e97950af841c54c5d39153e625d.png

如下图所示,在vivado环境下,对全局时钟引脚接入的时钟sys_clk_PCIe_p进行了约束,因为占空比是50%,抖动和不确定性也采用默认值,所以图中只对频率进行了约束。

248e40569283a2b585df9a7fb46e2efa.png

高速收发器的输出时钟约束举例:由于高速收发器通常是例化IP核来使用的,所以这种约束通常是IP核自带的。

如下图所示,在vivado环境下,PCIE IP核中对高速收发器的输出时钟进行约束。
f7437669c91922bf9258d83afad30d6d.png

MMCM/PLL生成时钟约束举例:

如下图所示,在quatus环境下,在sdc中加入以下命令,quatus会对所有PLL产生的时钟进行了约束。因为用户只要对PLL的输入时钟(通常情况下是主时钟)进行了约束,在sdc中加入以下命令后,quatus能够根据输入时钟和输出时钟的关系自动推断出PLL的输出时钟的时钟周期、占空比、相位关系等。
480d19347d311fd2f4f0ff98ec56fcb8.png

如下图所示,在vivado环境下,用户对PCIE IP核中的MMCM的输出时钟进行重命名,用户只要确保对MMCM的输入时钟(通常情况下是主时钟)进行了约束,Vivado会自动能够根据输入时钟和输出时钟的关系自动推断出PLL的输出时钟的时钟周期、占空比、相位关系等。
818f90ee52d05c7fdc993b9887460127.png

跨时钟域约束:在介绍跨时钟域之前,先介绍两个概念:同步时钟和异步时钟。同步时钟:当两个时钟间的相位是固定的,则可以称这两个时钟为同步时钟。一般同源,如由同一个MMCM/PLL产生的两个时钟可以称为同步时钟。异步时钟:无法判定两个时钟间相位时,则可以称这两个时钟为异步时钟。两个来自不同晶振的时钟,一定是异步时钟。通常情况下设计中不同的主时钟肯定是异步时钟。由不同的MMCM/PLL产生的两个输出时钟即使频率相同,但是由于相位关系不确定,所以也属于异步时钟。

用户想要进行跨时钟域的约束,首先需要对设计中的所有时钟进行异步时钟分组。若用户没有设置异步时钟分组,综合器在综合时会认为所有的时钟都是相关的,从而对某些源时钟与目的时钟属于异步时钟关系的路径进行了静态时序分析,由于源时钟与目的时钟的相位关系不确定,所以该路径的建立时间或保持时间必定是存在违例的。若用户设置了异步时钟分组,Vivado在时序分析时,当源时钟和目的时钟属于同一个时钟组时,才会分析此时序路径;而源时钟和目的时钟属于不同时钟组时,则会略过此时序路径的分析。

那么如何进行时钟的异步分组呢?首先要按照上面提到同步时钟和异步时钟的概念对设计中的所有时钟进行分类,属于异步时钟关系的时钟必定要划分在不同的分组中,属于同步时钟关系的时钟可以分在同一个分组也可以分在不同的分组中,如何划分要看具体情况而定。

如下图所示,在quatus环境下,对时钟进行异步时钟分组的划分,图中主要有3类时钟,

由全局时钟引脚输入的pcie_refclk,PCIE IP核中的MMCM输出的时钟(图中用通配符*表示)

Flash_pll输出的两个不同频率的时钟outclk0和outclk1。首先要确定全局时钟引脚输入的时钟和PCIE IP核中的MMCM输出的时钟以及Flash\_pll输出的两个不同频率的时钟属于异步时钟关系,它们必须要划分在不同的分组中。但是,Flash_pll输出的两个不同频率的时钟outclk0和outclk1如何进行划分呢?首先,考虑一下设计中,outclk0和outclk1之间有没有进行数据交互,如果没有数据交互,那么就将这两个时钟划分在同一个时钟分组即可,图中就属于这种情况。如果有数据交互,可以分为以下三种方案进行操作:

  1. 将这两个时钟划分在不同的时钟分组,用户在逻辑中进行了跨时钟域处理。(推荐使用)
  2. 将这个时钟划分在相同的时钟分组,用户在逻辑中进行了跨时钟域处理,在xdc/sdc中添加set_flase_path(伪路径约束)禁止综合器对跨时钟域路径(通常是双触发器同步的跨时钟路径)进行静态时序分析。
  3. 将这个时钟划分在相同的时钟分组,用户逻辑中不需要进行跨时钟域处理,由后端保证时序。

9c79e1720756ee8e657cd7bc26a3186c.png

伪路径约束:伪路径约束主要用于以下情况:1.上文提到的对通过双触发器同步的跨时钟域路径设置伪路径。2.异步复位路径

在vivado环境下,通过以下指令将异步复位路径sys-rst_n 设置为伪路径

set_false_path-from [get_ports sys_rst_n]

在quatus环境下,通过以下指令将异步复位路径pcie_rstn 设置为伪路径

set_false_path-from [get_ports pcie_rstn] -to *

判断条件过长问题

问题二:一个always块的判断条件中的部分变量或赋值语句中的部分被赋值变量是直接由组合逻辑产生的。当组合逻辑不是特别长时或FPGA的资源利用率比较低时,这种时序问题很可能会被综合器优化处理掉。但是当组合逻辑链路过长时,尤其是大位宽的寄存器进行逻辑运算生成了较长的组合逻辑链路时,这种代码风格导致的时序问题就会比较明显,最终导致always块的输入信号的延时变大,建立时间违例。

有时序问题的代码如下图所示,req_start_addr_reg信号和PC_LAST_ADDR信号属于位宽较大的寄存器,图中有三个典型问题需要注意。

948c23402504b12781e7fde864c0493d.png

第一个需要注意的问题:PC_LAST_ADDR信号是一个组合逻辑链路的输出信号,该组合逻辑链路是由逻辑运算导致的。错误的地方在于PC_LAST_ADDR信号直接被当做always块的输入信号使用,导致always块的输入信号的延时变大,建立时间违例。正确的处理方式应该是将PC_LAST_ADDR信号打一拍后,再将打一拍后的输出信号作为always块的输入信号使用。

第二个需要注意的问题:在always块的第三个和第四个的判断条件中都是先进行了逻辑运算后再进行逻辑判断的。这个问题的本质和第一个问题相同,都是将组合逻辑的输出信号直接作为always块的输入信号使用,导致always块的输入信号的延时变大,建立时间违例。正确的处理方式应该是将组合逻辑单独拎出来,然后将组合逻辑的输出结果打一拍后,再将打一拍后的输出信号作为always块的输入信号使用。而且在always块的第三个和第四个的判断条件中使用了三段式状态机的n_state作为了判断条件,原因同上。正确的处理方式应该尽量使用c_state作为判断条件。

第三个需要注意的问题:在always块的第四个赋值语句中,将两个信号进行逻辑运算后进行赋值操作。这个问题的本质和第一个问题相同,都是将组合逻辑的输出信号直接作为always块的输入信号使用,导致always块的输入信号的延时变大,建立时间违例。正确的处理方式应该是将组合逻辑单独拎出来,然后将组合逻辑的输出结果打一拍后,再将打一拍后的输出信号作为always块的输入信号使用。

修改之后的代码如下图所示:

2725fcf16bba7b97f2a38db53eaaaa61.jpg

总结:在编写代码时,应该注意以下三点:

1.应该尽量保证每一个always块的判断条件简洁(判断条件中尽量只进行逻辑判断,尽量避免逻辑运算)
2.应该尽量保证每一个always块的判断条件中的每一个变量都是直接来源于某个always块输出信号,尽量避免将组合逻辑的输出直接作为某个always块的判断条件的一部分,这样就可以保证每一个always块的输入信号的延时比较固定,有利于时序收敛。
3.应该尽量保证每一个always块的赋值语句中的被赋值变量都是直接来源于某个always块输出信号,尽量避免将组合逻辑的输出直接作为某个always块的赋值语句中的一部分,这样就可以保证每一个always块的输入信号的延时比较固定,有利于时序收敛。

if else嵌套层数过多

问题三:always块中的if…else…嵌套层数过多导致综合问题

有时序问题的代码如下图所示,always块中嵌套了三层if…else…,正确的处理方式应该尽量减少always块中if…else…的嵌套层数。

667620e48e31a3395a3dc27facb21c31.png

修改之后的代码如下图所示,

a33932380c4a25767ce5befb6e6ec354.png

总结: 在编写代码时,应该尽量减少always块中的if…else…嵌套层数.

逻辑信号扇出过大

问题四:部分用户逻辑信号扇出过大,导致驱动能力不足。

有时序问题的代码如下图所示,图中三个always块产生了一组RAM的写信号,这组信号作为55个always块的输入信号,也就是说该信号驱动了55个always块,普通的信号的扇出能力一般在15-20左右。这组信号作为普通的用户逻辑,如果采用加BUFG的方式来增加驱动能力,这样不仅浪费BUFG的资源,而且BUFG会给信号加入极大的延时,这种延时对于有些高速设计来说也许是不可接受的,一般只有全局复位信号才会使用BUFG增加驱动能力。所以对于这种用户逻辑信号,推荐采用复制寄存器的方式解决驱动能力不够的问题。

6c1e092ad1dc389bc3814a3af3d3c671.png

修改之后的代码如下图所示:将原来的一组信号,复制了两份,总共三组信号,且三组信号的逻辑相同,每组信号驱动的always块的个数变成了原来的1/3。但是同时需要注意的问题是,这种复制的寄存器有极大的可能会被综合器当作等效寄存器优化掉,为了防止综合器多管闲事,在quatus下需要在声明被复制的寄存器前面加上(* noprune *)告知综合器此寄存器不需要优化,在vivado下需要在声明被复制的寄存器前面加上(* keep =“true”*)告知综合器此寄存器不需要优化。

7c6be0d8341c3b057f530dd9b043eba2.png

总结:在编写代码时,需要注意信号驱动能力与扇出的问题,对扇出大的用户逻辑信号进行寄存器复制解决驱动能力不足的问题。对于全局复位信号,使用加BUFG解决驱动能力不足的问题。

数据选择器过大

问题五:用户需要使用大型的数据选择器(8选1以上的选择器)时,如果直接使用组合逻辑的case语句实现大型数据选择器,可能会导致以下问题:综合器综合速度变慢,逻辑资源占用率变大,数据选择器的相关信号时序违例会很大,数据选择器的输出结果也会极不稳定。

有时序问题的部分代码如下图所示:用户需要一个128选1的数据选择器,图中直接使用了一个组合逻辑的case语句实现了128选1。正确的处理方式应该是将一个128选1的选择器,分为3级,第一级使用了16个8选1,第二级使用了2个8选1,第三级使用了1个2选1,总共消耗3个时钟,选出最终的输出结果,注意每一级的输出结果都需要打一拍后再输入到下一级进行选择。
8ba285a9174e2a708fba89da598eaf8f.jpg

修改之后的部分代码如下图所示,

560df5d89b966e2f01103f9065fd528f.png

882871acdb67764e359a4aecad9225b6.png

86e2f2e593d91e448a436f5fb2718923.png

总结:大型数据选择器不能直接使用组合逻辑的case语句实现,在对选择器的延时要求不是很高的情况下,最好将大型数据选择器进行分级选择的处理。

大位宽RAM数据总线约束

问题六:高速设计中,RAM的输出或寄存器的位宽太宽时(64bit以上),可能会出现在某个时钟的上升沿时,寄存器的某些bit由于布线导致路径时延不一致,不能与其他bit的数据在同一个时钟上升沿到达,导致用户采样到数据出现错误或者采样到亚稳态。所以,用户要使用某个数据位宽很宽的寄存器时,无论这个寄存器数据来源于FIFO还是来源于用户逻辑,建议先将该寄存器打拍,然后使用打拍后的数据,这样更有利于时序收敛。

    但如果从RAM里面输出的大位宽的数据总线经过打拍后仍然不稳定又该怎么办呢?笔者在实际调试过程中发现,采用对使用大位宽总线RAM的时钟信号进行约束的方法非常有效。具体实现跟FPGA外围管脚时钟信号约束的方法一样,比如下图中在vivado工具中可以对设计中内部某个用到大位宽的RAM的时钟进行创建即可。

07d8175b47d9e3c11682a71968d0f0f2.jpg

总结:大位宽的数据总线需要保持数据传输过程中时延的一致性,尽可能的多采用时序逻辑,同时对于大位宽RAM的时钟要进行约束。


结束语

顺便提一下,在刚刚过去的2019年公众号分析统计的数据中,本公众号经常使用的词汇前十名排行榜如下图,从图中使用频度较高的词可以看出,本公众号关注的内容非常切合公众号的名字,同时,从中也能看出,时钟对于FPGA调试的确起着举足轻重的作用。

全文完。

作者:郑圆圆
原文链接:网络交换FPGA

推荐阅读

更多IC设计技术干货请关注IC设计技术专栏。
推荐阅读
关注数
20187
内容数
1307
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息