2019年8月8日星期四

谈谈我见过的最匪夷所思的bug

那是大二夏季学期《计算机设计与实践》课里,要求是用VHDL语言在Digilent Nexys 3开发板上实现一个简单的CPU。

为了方便调(zhuang)试(bi),我使用了PmodCLP模块扩展显示,显示的内容是当前正在执行的指令助记符和所有寄存器的值。按照我的理解,把这个显示模块写好以后,我就可以把它当成一个元件来使用,只要在主控制模块里面写好元件例化和CPU输出到显示模块输入的管脚映射就可以了。然而,在我对CPU做了一点修改之后,惊奇地发现显示模块完全不工作了,没有任何输出。我想不出自己做的修改和显示模块有任何关系,就在我尝试了无数种不同的等价的修改方法并且全部失败以后,突发奇想的我把主控制模块里面CPU的元件例化语句和显示模块的元件例化语句调换了位置,先例化显示模块再例化CPU。因为课堂上学到过元件例化语句都是并行语句不分前后,我不认为这种方法会有什么卵用。然而当下载成功的那一刻,我发现整个世界都变美好了——显示回来了,运行没有任何错误。

几年过去了,我一直都不知道为什么两句元件例化的顺序会对综合的结果造成影响,而且似乎不只是元件例化,有同学还通过调换两个相邻的信号赋值语句(也是并行语句)解决了他两天没调出来的bug。这种经历让我对硬件编程充满了厌恶,虽然这些神奇的问题很可能是由于我们的设计太乱,但这种无法解释的真实结果与代码预期结果的不一致,极大地破坏了我心目中编程的数学美感。

最近在一次和学弟的聊天中,我终于知道了VHDL Synthesis出现这种诡异表现的原因。学弟曾经在Intel实习,负责优化FPGA的Synthesis流程的一部分。据他介绍,Synthesis(综合)是一个NP问题,只能通过近似算法求解,这个近似算法是一个随机算法,需要有一个random seed(随机数种子),而那个seed,就是源代码文件的hash值。因此,每次修改代码内容,搜索算法得到的解都会变化。即使是语义上完全等价的修改(如调换元件例化顺序)也同样会造成影响。

果然,每个bug都是有原因的:)