1、背景
前一阵子,一位工程师基于某国产 FPGA,在其 EDA 工具上调用了 DSP 模块 IP,实现了一个由多个 DSP 模块级联组成的带符号定点运算功能,并进行测试验证。
该工程师在写完代码后,在 Questasim 上通过了 RTL 仿真,查看了时序分析,并最后上板测试无误(注意,此时的所有操作都是正常情况下进行的),至此就将工程提交至其它同事。但在后续工作中,收到了来自同事的消息:烧录了该工程的开发板未通过在低温、低压下的测试。
随后,作者作为协助者,将这件事情完整地描述出来了。
2、具体细节
我们先来看一下具体细节!
2.1、顶层控制
1、为了确保代码可适配性,使用了 IP 核产生内部时钟和复位信号。内部时钟信号来源于 OSC 输出。
2、同步释放、异步复位。
3、使用 Clk、rst_N_d2 对有符号定点数级联运算模块进行控制。
2.2、运算实现
这部分的内容对应于上文截图中的 dsp_mult 模块,用于实现运算。
- 其中:
- D_L[17:0]=D_U[17:0],即 D_L 和 D_U 的数据输入是一样的,这个是在 IP 输入接口完成的。
- 在同一个 DSP 模块中,输入 C_L、A_L 可以连接到级联输出 C_L_CAS_OUT、A_L_CAS_OUT,而 C_L_CAS_OUT、A_L_CAS_OUT 可以连接到输入 C_U、A_U。同理,输入 C_U、A_U 也可以连接到级联输出 C_U_CAS_OUT、A_U_CAS_OUT。
- 在相邻的两个 DSP 模块中,把第一个 DSP 的输级联输出 C_U_CAS_OUT、A_U_CAS_OUT,直接连接到第二个 DSP 的输入 C_L、A_L 上,即可达到级联的目的。
- 为了使输入数据对其,输入 C_L、A_L、C_U、A_U 到级联输出 C_L_CAS_OUT、A_L_CAS_OUT、C_U_CAS_OUT、A_U_CAS_OUT 过程,以及级联输出 C_U_CAS_OUT、A_U_CAS_OUT 到 C_L、A_L 的过程,均没有开启寄存器。否则,这是一个 FIR 滤波器,必须要给 D、U 打节拍进行数据对齐才能确保各个 DSP 的输出与预期相符,而且随着级联数越多,所需寄存器就越多。
- 基于上述,完成了多个 DSP 级联的带符号的定点数计算:R[55:0] = 2 * ( C[17:0] + D[17:0] ) x A[17:0] + U[55:0]。且每个 DSP 的输入、输出都是一样的。
3、复现、解决与反思
3.1、复现与分析
1、遇到 Bug 第一步,按照反馈过来的结果,工程师使用对方寄过来的开发板,下载该工程,进行低温低压下的现象复现。果然,问题复现了。 2、随后将代码中的时钟源由内部 OSC 输出改为外部时钟引脚输入,低温、低压下,上板测试通过。由于内部 OSC 时钟频率远高于外部时钟引脚输入的时钟频率,因此,有些怀疑可能是由于时钟频率过高造成的。 3、基于 2 的怀疑,使用 PLL 将外部时钟输入分别分频至多个不同的时钟频率,进而驱动 DSP 的运算,对于代码中的 dsp_mult 模块,多次上板在低温低压下进行测试。最终发现,只有驱动 dsp_mult 模块的时钟频率不超过 2 倍时钟输入引脚的频率时,低温低压下的上板测试才会通过。 4、基于上述测试与分析,目前已基本确认问题是内部 OSC 输出时钟频率过高造成的。
3.2、修复与改进
基于<3.1、复现与分析>认为,当下如果继续使用内部 OSC 输出作为时钟输入信号来驱动 dsp_mult 模块,则需要降频。
3.2.1、关于降频方案的实施
在一位资深大佬建议下,直接使用 counter 计数,来实现降频。
工程师如有所思,写下了如下代码:
reg [1:0] clk_count;
reg Clk;
always @(posedge Clk_o or negedge rst_N)
if (!rst_N) begin
clk_count <= 2'b00;
clk <= 1'b0;
end else begin
clk_count <= clk_count + 1'b1;
if(clk_count == 2'b11) Clk <= ~Clk;
else clk <= Clk;
end
并让 clk 来驱动 dsp_mult 模块。资深大佬看了之后直摇头,并给出了如下的方案。
reg [1:0] clk_count;
always @(posedge Clk_o or negedge rst_N)
if (!rst_N) clk_count <= 2'b00;
else clk_count <= clk_count + 1'b1;
wire Clk;
assign Clk = clk_count[1];
改完代码后的效果为:
(只改动了这部分的代码,因此工程师只提供了这部分改动后的效果截图。)
本来吧,到这里,工程师认为故事也能结束的。但是吧,依旧出意外了。
3.2.2、新的问题
- 问题 1、尝试在 Questasim 上跑 RTL 仿真,但 DSP 输出结果为不定态。
工程师设置好 do 文件中的文件路径后,打开 Questasim,并执行 do questasim.do 命令。运行结果并无语法报错,也可查看波形。但波形里面显示,vmb 模块没读取到 dat 文件,导致没有数据输入 DSP,进而导致 DSP 输出结果为不定态。
- 问题 2、在 Questasim 上跑通 RTL 仿真后,常温、常压下,上板测试的时候,输出引脚上的信号显示 DSP 的输出结果与预期结果不一致,而其他信号符合预期。
此时,并没看到时序分析报告上有任何异常,且有尝试在工程中加 debug 信号来进行信号抓取。但是,由于未知因素,加入 debug 信号后,工程在编译的过程中会报错,卡在了布局布线那一步。
4、问题的解决、分析与反思
4.1、新问题的解决
4.1.1、解决新问题 1
- 1、尝试在 Questasim 上跑 RTL 仿真,但 DSP 输出结果为不定态。
由于通过波形判断出 vmb 模块没读取到 dat 文件,那么是不是说在设置 vmb 的 IP 核调用时,没有写好相对路径。但这一步使 EDA 软件工具自己识别的,按理说也不会出错的。vmb_v1.v、mb_v2.v、vmb_v3.v、vmb_v4.v、vmb_v5.v 中的相对路径自动设置如下。
那么,继续查看 questasim.do 文件,也没发现 questasim.do 文件中列出 vmb 文件的相对路径出问题。
而且,工程师事先还调整过 FPGA 工程与 Questasim 仿真目录的相对路径,放在了同一目录下,为的就是路径的对齐与一致。
但是,经过大佬提示,问题还真就出现在了这里。EDA 软件工具默认的相对路径起始是 FPGA 工程下的根目录,而 Questasim 仿真工程中的相对路径的起始是该文件的所在目录。
恰巧,vmb_v1.v、mb_v2.v、vmb_v3.v、vmb_v4.v、vmb_v5.v 在目录 dsp_18x18_add_18x18_carry_u_cascade/src。也就是说,Questasim 仿真工程 vmb_v1.v、mb_v2.v、vmb_v3.v、vmb_v4.v、vmb_v5.v 中的相对路径想要指向目标文件,就得在这些文件中的相对路径中再增加一个'../',多一级返回。即,如下图所示:
其实,根据工程师自己的回忆来看,最初事先进行的路径的对齐与一致,也是为了规避比这个,只不过不是因为 vmb 模块,而是为了其他模块。
最后的解决办法是,工程师拷贝了一份 vmb_v1.v、mb_v2.v、vmb_v3.v、vmb_v4.v、vmb_v5.v 到仿真工程 questasim_dsp_18x18_add_18x18_carry_u_cascade 目录下,并修改了 questasim.do 文件中的文件与路径。
4.1.2、解决新问题 2
针对问题 2,工程师有些不知所措了。有尝试过做以下更改:
assign Clk = Clk_o ; // Clk_o clk_count[1];
即,此时将内部 OSC 的输出 Clk_o 传递给 Clk,由 Clk_o 来驱动 dsp_mult 模块。此前的测试中,常温、常压下,上板验证可以的。很庆幸的是,这个更改现在也可使代码在板子上跑起来了。
那么,问题就很清晰了,那就是时钟驱动信号出了问题。但是,那部分代码,并不复杂,又会是哪里出了问题呢。经过资深大佬的指点,先看了一眼复位信号。
乍一看,没啥大问题,就是同步释放、异步释放嘛!rst_N_d2 信号,就是用于 dsp_mult 模块的复位输入。那么,再看一眼 dsp_mult 模块的时钟信号。
就会注意到了:dsp_mult 模块的时钟信号是 Clk,dsp_mult 模块的复位信号是由 Clk_o 驱动的。
那么,所谓的同步释放、异步释放就是个空摆设。rst_N_d2 信号会先于 Clk 信号进入到 dsp_mult 模块中,造成 DSP 初始计算结果出错,并与预期结果比对,进而引脚信号报错。
4.2、原有问题的分析
按照<4.1、新问题的解决>以及更前面的章节分析,可以解决内部 OSC 输出时钟信号频率过高导致 dsp_mult 模块功能出错的问题。那么,为什么时钟信号的频率过高就会导致 dsp_mult 模块功能出错呢?
如果看的足够仔细,就会发现<2.2、运算实现>中有提到:'为了使输入数据对其,输入 C_L、A_L、C_U、A_U 到级联输出 C_L_CAS_OUT、A_L_CAS_OUT、C_U_CAS_OUT、A_U_CAS_OUT 过程,以及级联输出 C_U_CAS_OUT、A_U_CAS_OUT 到 C_L、A_L 的过程,均没有开启寄存器。否则,这是一个 FIR 滤波器,必须要给 D、U 打节拍进行数据对齐才能确保各个 DSP 的输出与预期相符,而且随着级联数越多,所需寄存器就越多。'。
事实就是如此,C、A 信号输入到各级 C_L、A_L、C_U、A_U 上的过程就是一个较长的信号串联传递过程,且没有任何寄存器。并且,随着级联数目的增加,串联的长度也会按比例增加,那么在真实电路中,C、A 信信号到达最后一级 C_L、A_L、C_U、A_U 所需要的时间就越长。因此,不能承受过高频率时钟的驱动;否则,就会代码跑飞。
也看过时序分析,但很巧的是,时序报告并没有将这个路径给报告出来,因此在看完时序报告后就在常温、常压下进行了上板测试。直到低温、低压测试时,才被暴露出来。
4.3、反思与总结
通过与工程师的交流,并把这个过程给分享了出来,并不是真的只是为了公开处刑。只是站在工程师的角度来看:
- 1、新问题 1,实际上已经遇到过,却依旧顾此失彼,依旧翻车了。
- 2、新问题 2,在笔者写《复位信号的同步与释放(同步复位、异步复位、异步复位同步释放)》与《翻译:How do I reset my FPGA?》期间,就已经探讨过同步释放、异步复位,但也还是翻车了。
- 3、关于原有问题,很明显一开始就注意到了这个数据传输路径是没有寄存器的,因为是自己设置的参数配置,关闭了寄存器选项,但也承认确实没有意识到这一点会带来时序上的问题,从某种意义上来说这是可以避免的。
- 4、分析与总结,不是为了批判,而是为了更好的进步。
最后,感谢一下提供的亲身素材。

