对一次 国产FPGA代码上板低温低压测试 的反思与总结
说明1:如果文章有误,欢迎大家指出、讨论,笔者也会积极改正,希望大家一起进步!
说明2:最近发生了很多事情,身心俱疲,距离上次更新已经过了八个月了。也该回归初心、努力更新了,加油!
文章目录
1、背景
前一阵子,朋友Alex,基于某国产FPGA,在其EDA工具上调用了DSP模块IP,实现了一个由多个DSP模块级联组成的R[55:0] = ( C_L[17:0] + D_L[17:0] ) x A_L[17:0] + ( C_U[17:0] + D_U[17:0] ) x A_U[17:0] + U[55:0] 的带符号定点运算功能,并进行测试验证。
Alex在写完代码后,在Questasim上通过了RTL仿真后,看了时序分析,并最后上板测试无误(注意,此时的所有操作都是正常情况下进行的),至此就将工程提交至其它同事。但在后续工作中,收到了来自同事的消息:烧录了该工程的开发板未通过在低温、低压下的测试。
然后,Alex,他懵了!
再然后就是,笔者小庄作为Alex的好朋友,就把这件事情给完整地描述出来了(公开处刑)。
2、具体细节
我们先来看一下具体细节!
2.1、顶层控制
3、使用Clk、rst_N_d2对 有符号定点数级联运算模块 进行控制

2、同步释放、异步复位

1、为了确保代码可适配性,使用了IP核产生内部时钟和复位信号。内部时钟信号来源于OSC输出。

2.2、运算实现
这部分的内容对应于上文截图中的 dsp_mult模块 ,用于实现运算。

- 其中:
- 1、D_L[17:0]=D_U[17:0],即D_L和D_U的数据输入是一样的,这个是在IP输入接口完成的。
- 2、在同一个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。
- 3、在相邻的两个DSP模块中,把第一个DSP的输级联输出C_U_CAS_OUT、A_U_CAS_OUT,直接连接到第二个DSP的输入C_L、A_L上,即可达到级联的目的。
- 4、为了使输入数据对其,输入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的输出与预期相符,而且随着级联数越多,所需寄存器就越多。
- 5、基于上述,Alex便完成了多个DSP级联的带符号的定点数计算:R[55:0] = 2 * ( C[17:0] + D[17:0] ) x A[17:0] + U[55:0] 。且每个DSP的输入、输出都是一样的。
3、复现、解决与反思
3.1、复现与分析
- 1、遇到Bug第一步,按照反馈过来的结果,Alex和某大佬,使用对方寄过来的开发板,下载该工程,进行低温低压下的现象复现。果然,问题复现了。
- 2、随后将代码中的时钟源由内部OSC输出改为外部时钟引脚输入,低温、低压下,上板测试通过。由于内部OSC时钟频率远高于外部时钟引脚输入的时钟频率,因此,有些怀疑可能是由于时钟频率过高造成的。
- 3、基于2的怀疑,使用PLL将外部时钟输入分别分频至多个不同的时钟频率,进而驱动DSP的运算,对于代码中的dsp_mult模块,多次上板在低温低压下进行测试。最终发现,只有驱动dsp_mult模块的时钟频率不超过2倍时钟输入引脚的频率时,低温低压下的上板测试才会通过。
- 4、基于上述测试与分析,目前已基本确认问题是内部OSC输出时钟频率过高造成的。
3.2、修复与改进
基于<3.1、复现与分析>认为,当下如果继续使用内部OSC输出作为时钟输入信号来驱动dsp_mult模块,则需要降频。
用B站某位UP主的话来说:这时候,不出意外的话就要发生意外了。
3.2.1、关于降频方案的实施
在一位资深大佬建议下,直接使用counter计数,来实现降频。
Alex如有所思,写下了如下代码:
reg [1:0] clk_count; reg Clk; always @(posedge Clk_o or negedge rst_N) if (!rst_N) begin clk_count <= 2'b00; clk <= 1b0; end else begin clk_count <= clk_count + 1'b1; if(clk_count == 2'b11) Clk<= ~Clk; elseClkk <= 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]; 改完代码后的效果为:

(只改动了这部分的代码,因此Alex只提供了这部分改动后的效果截图。)
本来吧,到这里,Alex认为故事也能结束的。但是吧,依旧出意外了。
3.2.2、新的问题
- 问题1、尝试在Questasim上跑RTL仿真,但DSP输出结果为不定态。
Alex设置好do文件中的文件路径后,打开Questasim,并执行 do questasim.do 命令。运行结果并无语法报错,也可查看波形。但波形里面显示,vmb模块没读取到dat文件,导致没有数据输入DSP,进而导致DSP输出结果为不定态。
- 问题2、在Questasim上跑通RTL仿真后,常温、常压下,上板测试的时候,输出引脚上的信号显示DSP的输出结果与预算结果不一致,而其他信号符合预期。
此时,并没看到时序分析报告上有任何异常,且有尝试在工程中加Debugware来进行信号抓取。但是,由于未知因素,加入Debugware后,工程在编译的过程中会报错,卡在了布局布线那一步。
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文件的相对路径出问题。

而且,Alex事先还调整过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中的相对路径想要指向目标文件,就得在这些文件中的相对路径中再增加一个“…/”,多一级返回。即,如下图所示:

其实,根据Alex自己的回忆来看,最初事先进行的路径的对齐与一致,也是为了规避比这个,只不过不是因为vmb模块,而是为了其他模块。
最后的解决办法是,Alex拷贝了一份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,Alex有些不知所措了。有尝试过做以下更改:
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所需要的时间就越长。因此,不能承受过高频率时钟的驱动;否则,就会代码跑飞。
Alex也看过时序分析,但很巧的是,时序报告并没有将这个路径给报告出来,因此在看完时序报告后就在常温、常压下进行了上板测试。直到低温、低压测试时,才被暴露出来。
4.3、反思与总结
其实,通过与Alex的交流,并把这个过程给分享了出来,并不是真的只是为了公开处刑。只是站在Alex的角度来看:
- 1、新问题1,实际上Alex已经遇到过,却依旧顾此失彼,依旧翻车了。
- 2、新问题2,在笔者写《复位信号的同步与释放(同步复位、异步复位、异步复位同步释放)》与《翻译:How do I reset my FPGA?》期间,Alex就已经和笔者探讨过同步释放、异步复位,但也还是翻车了。
- 3、关于原有问题,很明显Alex一开始就注意到了这个数据传输路径是没有寄存器的,因为是他自己设置的参数配置,关闭了寄存器选项,但Alex自己也承认确实没有意识到这一点会带来时序上的问题,从某种意义上来说这是可以避免的。
- 4、分析与总结,不是为了批判,而是为了更好的进步。
最后,感谢一下Alex提供的亲身素材。