01signal.com

逻辑之外:FPGA 设计师的人性特质

本页是关于如何成为专业 FPGA 设计师的系列文章中的第五篇,也是最后一篇。和前几篇不同,这一篇不谈技术。它把焦点放在我们作为人本身,以及哪些特质会让 FPGA 设计师的日子过得更轻松、更好。

这其实很重要

在一个以技术为主题的网站上谈 FPGA 设计师的性格和态度,看起来有点怪。但在写给未来 FPGA 设计师的指南里,我觉得也适合讨论一下那些决定了你是“一切尽在掌控”还是“完全一团乱麻”的人性品质与行为。

几乎每个职业都有适合自己的性格:如果你不擅长跟人打交道,就很难做汽车销售;如果你不喜欢小孩,也当不好老师。同样的,有一些性格特质会助你成为优秀的 FPGA 工程师,也有一些则会拖你后腿。

我不是在说教,也不是想吓退谁。我更不是在讲“你必须拼命努力”。恰恰相反:我的目的是把事情做对,然后用更小的力气达到目标。

在这一页里,我会尽量描述:除了拥有这样那样的专业知识之外,还需要什么才能成为一名成功的 FPGA 设计师,并与这个职业和平共处。这一页讲的是工程师的性格。有自律(self-discipline)和自我控制力、属于精确型的人,这些会更好,这大概不会让人意外。对细节稍微有点偏执,也没有坏处。

但别把这和“人生就是受苦”或者“把自己累死”混为一谈。情况恰恰相反:如果你有能力以某种方式工作,你会更快、更轻松地把事情做完。它会是有趣的、有回报的。一次就做对是理想状态,而且它是可以做到的。

反过来说,如果你觉得自己和我下面描述的“理想 FPGA 设计师”一点都不像,甚至连边都沾不上,嗯,我该怎么说呢?要不,把整个想法再重新考虑一下?

“它能跑”还不够

在软件世界里,尤其是网页和应用,通常的做法是随便写点东西,跑一下,哪儿不对就改,然后再跑。有了 AI,连“随便写”这一步都省了,机器自己写代码、自己测试,最后把结果提交到 Git 仓库里。

背后的思路是:只要代码通过了测试,活就算干完了。如果还有 bug,QA 会发现,或者用户的抱怨很快就会来。到那时,按标准流程发一个修复请求,然后把 bug 修掉就是了。很多软件团队就是按这种方式组织起来的。

这种做法适不适合软件开发,是另一个话题。我想明确说的是:对专业的 FPGA 设计来说,这种思路是大错特错的。“它能跑”对 FPGA 项目来说远远不够。也许对爱好者够用,但对要投入实际量产的项目绝对不行。人很容易掉进这个陷阱,尤其是在漫长的调试之后终于看到东西能用了,于是高高兴兴收工。

这听起来可能有点清教徒式的说教,但道理很简单:要让 FPGA 设计可靠,唯一的办法就是一次性地向自己证明,你的设计保证能工作。任务不是在设计通过所有测试时就结束的,而是只有当你能说服自己它不可能失败时才结束。

我拿数学定理来打个比方:你不会因为用几个数字试了试、每次都得到正确结果,就说它是对的。只有当你有了一份数学证明,你才会认为这个等式是正确的。

实践中,这意味着 Verilog 文件里的每一行都要像数学推导里的一个等式那样对待,时序约束(timing constraints)和其他相关源文件也一样。这听起来可能有点极端,但这样的小心从长远看是值得的。

FPGA 与其他电子元件之间的连接也是如此:你不能因为它能工作就假定电气接口没问题。你要向自己证明数据手册里的要求都得到了满足:既有 FPGA 数据手册里的要求,也有其他元件数据手册里的要求。电平对吗?FPGA 设计能确保两侧的所有时序要求都满足吗?

这是不是说我的设计从来就没有 bug?当然不是。我是人,我会犯错。就像一份数学证明,哪怕我尽力了,也可能做不到 100% 正确。我可能忘了检查边角情况,可能把加号写成减号,总而言之可能搞砸。

但这种方法严谨的好处在于:我最后留下的那几个 bug 通常一眼就能看出来,也比较好修。而一旦修好,设计就是真的能用。没有 bug,没有谜团,没有巫术。一块稳稳工作的电子设备。

这是通往“快速达到目标”的一条慢路。

真有人这么严谨地工作吗?

简短回答:绝对没有。我尤其在做自由职业者的时候对此深有体会。每接到一个新客户,我的第一项任务都是把那个 FPGA 项目稳定下来。这有点像急诊室里医生把病人稳定住。而那些负责项目的工程师,在经历了长时间的紧张和挫败之后,往往也需要某种意义上的“稳定”。

“先随便写、再调试”这种态度,不幸在 FPGA 行业里也很常见。这不奇怪,因为 Verilog 课程里介绍仿真器的方式,常常和软件世界里介绍调试器的方式如出一辙。背后的暗示往往是:靠反复试错达到想要的结果。仿真到在电脑上能跑,然后综合、修修补补,直到在硬件上也能跑。

更糟的是,还有一些管理者在鼓励这种工作方式。他们急着看到结果,一旦看到某个看起来没问题的东西,就指望你赶紧去做下一个任务。进度很紧,“bug 以后再修”。

这种做法带来的结果是:很难、有时甚至不可能做出真正可靠的 FPGA 设计。于是就得靠大量的仿真回归测试和大量的硬件测试来弥补。有些公司,只要板子上有 FPGA,就会在每一块板子离开产线之前做温度测试。出厂前的硬件测试变成了一个独立部门,有自己的硬件和软件,目的只有一个:确认 FPGA 确实做了它该做的事。

而在这一切背后,是一群压力巨大的 FPGA 工程师,他们进度落后,疯狂追赶那些神秘出现又神秘消失的 bug。

情况也不总是这么糟。当 FPGA 工作在相对较低的频率下、需求简单、偶发故障又没那么要命时,试错法也能干得相当不错。

此外,现实中大多数做 FPGA 的人,都处在刻度尺的中间位置:没有极端到把 Verilog 当成数学等式,但有着比常人更高的耐心、自律和对精确的尊重。他们就是靠这些在这个领域里把事情做成。

知道自己到底在做什么

如果我已经让你相信,试错不是做 FPGA 的正确方式,那么显然需要一条更严谨、更精确的路。但这本身还不够。如果你并不确切理解自己在做什么,严谨的态度也帮不上忙。

比如,在 Verilog 代码里看到异步复位(asynchronous reset)是相当常见的(我在前几页也提到过)。这是复位逻辑的一种正当方法,但前提是用法正确。绝大多数情况下,这个复位用得不对,因此并不能保证逻辑按预期表现。可现实中,一切运行良好。通常是。关于这个主题有单独一篇文章。

异步复位被误用的原因,大概是人们从别处把 Verilog 代码复制粘贴到自己的代码里。这种编码模式太熟悉、太显而易见了,以至于人们不会停下来想一想它到底意味着什么。而在这种情况下,还要想一想它不意味着什么、也不保证什么。

“知道自己到底在做什么”,是能够向自己证明设计保证能工作的前提。这意味着要准确理解每一条 Verilog 表达式的含义,以及综合器(synthesizer)可能如何解释它。同样的原则也适用于时序约束(timing constraints)以及其他与 FPGA 设计相关、供开发工具使用的信息。

但正如我已经承认的,大多数 FPGA 设计师并没有严谨到能证明设计一定能工作。不过,知道自己敲下的每一个字的确切含义,怎么说也是朝着正确方向迈出的一步。

抽象地思考

如果你上过计算机科学的课程,你大概见过为了描述软件而发明出来的各种抽象“生物”。比如总是以特定方式组织的数据结构、类和对象、数据流,以及在循环每次迭代后都保持不变的不变量,等等。在计算机科学里,人们一直在发明想象中的生物,好让我们人类能越过内部的细枝末节,转而与一个更简单的表示打交道。这减轻了我们大脑的负担,使得理解复杂的软件设计成为可能。这就是我们所说的抽象。

与抽象相对的,是具体化的思维方式。或者我该叫它“讲故事”?用这种态度,一段计算机程序被当成一个故事来讲:先做这个,再做那个,如果这个成立就做这个,否则做那个。用一根手指顺着代码把事件顺序走一遍,你就全明白了。

对于开发简单脚本(script)、网站、手机应用和其他简单软件来说,讲故事的方式很好用。如果用户按了这个按钮,就跳到这个界面或网页,然后从那里往下走。软件沿着一条简单的执行线索推进,也可以用一条思维线索来理解。

我想用一个例子来说明这两种思维方式的差别。看看下面这个用 C 写的函数,它计算 n!,也就是 n 的阶乘:

unsigned int factorial(unsigned int n) {
  if (n == 0)
    return 1;

  return n * factorial(n - 1);
}

这是递归的经典例子。你怎么理解上面这段代码?

讲故事的方式就是“试一遍”。听起来大概是这样的:“假设这个函数被以 n=3 调用。它会以 n=2 调用自己,然后 n=1,然后 n=0。现在展开来看:所以对 n=0 那次调用返回 1,然后 1*1,然后返回 2*1,最后是 3*2,也就是 6,这正是正确答案。很好,它能工作。”这个过程也许是拿着调试器单步执行完成的。

另一种方式类似于数学归纳法证明。我们假定这个函数确实返回 n 的阶乘,并确认如果它对 n-1 成立,那么对 n 也成立。最后确认它在 n=0 时给出正确值,而且递归总会终止在 n=0。执行的先后顺序无关紧要,因为这个函数被当作一个抽象的、只负责完成自身使命的存在。

你可能会问:何必把事情搞复杂?讲故事的方式已经解释得很清楚了。对此我的回答是:没错,但那只是因为例子足够简单才成立。

现在终于说到重点了:如果你理解软件时只会讲故事,那它对你成为 FPGA 设计师会是个障碍。第一个显而易见的原因是:在 FPGA 里,一切都是同时发生的。FPGA 里很少有东西能用“先这个、再那个”来描述。

第二个、也是更重要的原因是:FPGA 设计往往很复杂。你常常需要抽象,才能在脑子里把握住到底发生了什么。比如,一个很好的习惯是把 Verilog 模块定义成:它的功能可以用几句话讲清楚,而不需要太多细节。它各端口的接口也应该容易描述。当一个复杂的逻辑块能被归结为几个简单的想法时,人犯错的概率就小得多。这个原则对软件同样适用,只是没那么关键。

抽象思维的第三个理由,与“向自己证明正确性”的能力有关。讲故事只能覆盖你有能力想到的那些场景。真正的证明覆盖一切。

面对复杂的任务时,可能有必要在写第一行 Verilog 之前先发明一些新的理论“生物”。比如,如果你需要一个既输入又输出数据包的模块,可以定义 N 为当前存放在它存储缓冲区里的数据包个数。有了这个,你就能证明缓冲区永远不会满。这听起来不是什么大事,但朝着数学思维迈出这一步可能帮助很大。模块里的所有逻辑很可能都和“把 N 维持在正确的范围内”有关。

诀窍在于找到那些真正有助于把逻辑做对的抽象“生物”。这可能意味着好几天不写一行 Verilog,然后突然很快就写出了一个简短的 Verilog 模块。这样构思出来的代码往往短小、优雅、容易理解,而且第一次就能成功,并且之后永远都能成功。

不过,如果你的老板每天都问你在干什么,这招可能不太好使:好几天什么都拿不出来,等你终于弄出点东西,得到的回应可能是“你就搞出这么个不起眼的模块?”

所以再说一遍,纯粹以数学化的方式做 FPGA 设计不一定适合所有人。但我还是建议你改掉“讲故事”的习惯,如果你有的话。

别信任工具

或者更准确地说:开发工具不会阻止你犯下可怕的错误。它们甚至可能连警告都不给。

世界上没有任何软件编译器会生成与源代码要求的行为不一致的可执行代码。如果真有,那就报 bug。而综合器(synthesizer)则完全可能生成不满足 Verilog 代码所要求行为的逻辑。运气好的话,会有对此的警告。运气再好一点,你还能在综合器吐出的一堆无害信息和警告中注意到这一条。

FPGA 开发套件的其他部分也会玩阴的。最明显的一点是:即使时序约束(timing constraints)没有满足,它们中的大多数也照样会生成可以加载进 FPGA 的比特流(bitstream)。这意味着 FPGA 可能按综合器设想的方式工作,也可能不。或者也许工作一会儿,然后就不工作了。你会收到一条警告,甚至是关键警告(critical warning)。可软件编译器会完成一次编译,然后交给你一个可能跑不起来的可执行文件吗?

还有无穷无尽的其他方式能搞砸一个 FPGA 设计,而工具不会拦住你。这算是一种文化现象。就好像有人故意想让 FPGA 变得难对付。

我和好几家不同的 FPGA 厂商、以及相当多的 FPGA 开发工具打过交道。很有意思的是,它们全都有把事情搞难的同样倾向,而安全网则是只能奢望的东西。这不是某一家廉价又邪恶的 FPGA 厂商的问题,而是所有厂商都一样。

更别提,FPGA 开发工具有 bug 也不罕见。运气好的话,它们在尝试构建项目时就崩溃了,至少这不是一个会让你的设计出故障的 bug。不过,导致比特流(bitstream)本身有缺陷的 bug 也是会发生的,尽管没那么频繁。新版开发套件刚发布时更糟。在 FPGA 世界里,最新的未必是最好的,这话说得还算客气。

尽管如此,如果你的设计不工作,别去怀疑工具,尤其是当你刚入这一行时。而且如果你认定是工具害了你,那就找出确凿证据。找出 FPGA 里那个本该是某个样子、结果却是另一个样子的逻辑单元。说服自己,确实是工具的错才弄成这样。光是“感觉工具疯了”是不够的。对于看起来像那种情况的事情,几乎总有一个更简单的解释。

尤其是,如果你改了 Verilog 代码里某个不相关的地方,然后 bug 消失了,这并不意味着工具有 bug。这种表现更像是时序约束定义不清以及其他类似错误导致的。

应对开发工具这类问题的关键,哪怕我要重复自己也要说,就是知道自己到底在做什么。具体到这个话题,就是去读工具发出的警告和信息,理解它们是什么意思,或者至少大致知道它们和什么有关。要弄清哪些警告是无害的、总是出现、可以而且应该被忽略,这靠的是经验。至于工具为什么一个版本接一个版本地不停吐出大量没用的警告——我是不是提过“文化”这个词?

所以,隔一段时间就把所有警告扫一遍、看有没有什么扎眼的东西,是个好习惯。即使——尤其是——在一切运行得完美无缺的时候也要这么做。不仅因为“它能跑”还不够,也因为这是学习哪些警告无害的一种方式。

事实上,有些警告正好说明你做对了。比如,某条警告说某个寄存器因为取值恒定、或与另一个寄存器等价而被从设计里移除了。这就是个机会,让你停下来一秒,想想这在上下文中是否说得通。很多时候,这类警告会帮你发现关于自己代码的有趣事实。

从需求到设计

写软件的时候,需求和你需要写的东西之间通常有一条相当直的线。对简单软件(比如网站和手机应用)尤其如此。

而 FPGA 设计就没那么容易了。对 FPGA 的需求听起来往往是“这些是元件,我们希望它这样表现”。

比如,配置可能是一块带摄像传感器、一个 FPGA 以及连到另一台设备的线缆的板子。任务是:从摄像传感器取图像,对像素做基本的信号处理,然后把输出按你雇主规定的视频数据传输格式发给那台设备。

关于 FIFO、状态机(state machine)、流水线(pipeline)和 RTL 设计,你什么都懂。可你怎么做出别人要你做的东西?没人会告诉你“去写一个状态机”。人们期望的是你能做出一个能用的东西。

运气好的话,需求和以前的许多项目类似。照搬框图,模仿逻辑,也许复制几个部分。但很多时候,你的项目需求里有某些东西,使得这类模仿并不合理。

这时候,你同时也成了 FPGA 架构师。你来决定任务如何划分成功能单元、每个单元做什么、它们之间如何交互。这件事在项目最开始做得越好,项目后续的编写和维护就越容易。太多时候,在没有更好方案的情况下,一个已有项目的框图被硬套到一个新项目上。这条捷径是有代价的。

那么,怎么才能成为好的架构师?很大程度上靠经验,也靠观察其他项目里采用的方案。我前面提到了抽象:以抽象的视角理解一个设计——不管是自己的还是别人的——有助于你更好地构建下一个项目。如果你把 Verilog 模块看作执行某项你能用短名字叫出来的任务的功能块,你就拥有了一套可用于自己项目的调色板。如果你能看出几根线如何连接两个模块,并且能给这些模块通过这些线进行交互的方式起个名字,那你就更有能力决定新项目各部分之间如何交互。你越善于识别已有设计中的理论“生物”,就越善于搭建新的设计。

如果 FPGA 架构师这部分听起来很难,让我分享一个小秘密:它确实很难。我做过几次,它要花掉大量时间,也留下大量遗憾。在这个初始阶段的每一个小错误,都会带来重大的后果。

但记住,你很可能不需要从零开始设计一个项目,作为刚入 FPGA 这行的新人更是如此。这项任务通常会交给团队里资历最深的 FPGA 工程师。所以在戴上系统架构师这顶帽子之前,你大概还有些时间。即使你是团队里唯一被招进来的 FPGA 工程师,多半也是维护现有代码,或者在一个已有项目的基础上开发新项目。

但为这项任务做准备,永远不会太早。

小结

从很多方面看,这一页的主题一直是各种形式的自律。它是这样一种能力:克服那些常被认为是人之常情的做法——不精确地、不先想清楚就动手(尤其是写 Verilog 代码和时序约束(timing constraints)),然后再去修错(仿真和在硬件上调试)。为“东西能跑了”而高兴,并急着去做下一个任务(“它能跑”还不够)。用简单、自然(讲故事)的方式向自己解释事情,而不是去寻找它们背后的抽象理念。跳过那些细小而枯燥的细节,比如工具发出的警告。

很遗憾,我们的人性是天性就与 FPGA 设计师这个身份为敌的。但请注意,我完全没说要拼命工作。如果活能干完,追求少干点活不叫懒。目标其实是用更少的力气,拿到更好的结果。只要你方法对,这是可以做到的。

这也不是要你变成机器人。它讲的是在工作中有自控力和精确性,但绝不是像机器一样干活。我们需要我们人的大脑。机器人可以工作很多小时,而我们的大脑会累。自律有时意味着在一个漫长而乏味的日子之后离开桌子、休息一会儿。哪怕那个 bug 还在那里。

总结整个系列——现在应该很明显了,FPGA 设计并不是最轻松的职业,我列出了相当多的原因。如果你不喜欢一直在学电子方面的新东西,也许这行不适合你。如果你觉得自己没有那份把事情做得彻底而精确的自律,那是另一个值得重新考虑的理由。

如果我到现在都还没把你吓跑,那么欢迎你加入这个圈子,祝你有最好的运气和本事。而比什么都重要的是,希望你能像我喜欢这个选择一样,喜欢你的选择。

本页由机器从英文翻译而来。如有疑问,请参阅原文。
Copyright © 2021-2026. All rights reserved. (852e87d4)