实现 HDL 代码时应遵守哪些最佳实践?
与更常见的软件开发领域相比,有哪些共同点和不同点?
有点旧线程,但想投入我的 0.02 美元。这并不是 Verilog/VHDL 所特有的……更多关于硬件设计的一般信息……特别是针对定制 ASIC 的可综合设计。
这是我基于多年的行业(而不是学术)设计经验得出的观点。它们没有特定的顺序
我的总括性声明是为验证执行而设计。在硬件设计中,验证是最重要的。在实际硅中发现错误时要昂贵得多。你不能只是重新编译。因此,前硅得到了更多的关注。
了解控制路径和数据路径之间的区别。这使您能够创建更优雅和可维护的代码。还允许您节省门并最小化 X 传播。例如,数据路径永远不需要可复位触发器,控制路径应该总是需要它。
在验证之前证明功能。通过正式的方法或通过波形。这有很多优点,我将解释 2。首先,它可以节省您在洋葱剥皮问题上浪费的时间。与许多应用程序级设计(尤其是在学习时)和大多数课程作业不同,代码更改的周转时间非常长(从 10 分钟到几天不等,具体取决于复杂性)。每次更改代码时,您都需要进行详细说明、lint 检查、编译、波形调出,最后是实际仿真……这本身可能需要数小时。其次,您不太可能遇到难以解决的问题。请注意,这是关于硅前验证的。这些肯定会击中后硅成本你很多$$$。相信我,验证功能的前期成本极大地降低了风险,值得付出努力。这有时很难说服最近的大学毕业生。
有“鸡块”。鸡位是通过驱动程序设置的 MMIO 中的位,用于禁用芯片中的功能。它旨在恢复置信度不高的更改(置信度与验证工作成正比)。在前硅中达到所有可能的状态几乎是不可能的。除非在后硅片中得到证明,否则无法真正满足您对设计的信心。即使只有 1 个状态在 0.000005% 的时间内被击中暴露错误,它也会在后硅中击中,但不一定在前硅中。
不惜一切代价避免控制路径中的异常。您遇到的每一个新异常都会使您的验证工作加倍。这个很难解释。假设有一个 DMA 块可以将数据保存到另一个块将使用的内存中。可以说保存的数据结构取决于正在完成的某些功能。如果您决定设计使得不同函数之间保存的数据结构不同,您只需将验证工作乘以 DMA 函数的数量。如果遵循此规则,则保存的数据结构将是所有数据的超集,可用于对内容位置进行硬编码的每个函数。一旦 DMA 保存逻辑针对 1 个功能进行验证,它就针对所有功能进行验证。
最小化接口(读取最小化控制路径)。这与最小化异常有关。首先,每个新接口都需要验证。这包括测试台中的新检查器/跟踪器、断言、覆盖点和总线功能模型。其次,它可以成倍地增加您的验证工作!假设您有 1 个接口用于读取缓存中的数据。现在让我们说(出于某种奇怪的原因)您决定需要另一个接口来读取主内存。您只是将验证工作量翻了两番。您现在需要在任何给定时间验证这些组合n:
理解和交流假设。缺乏这一点是块到块通信问题的主要原因。您可以拥有一个完全验证的完美区块。但是,如果不了解所有假设,您的区块将在连接时失败。
最小化潜在状态。设计的状态(有意或无意)越少,验证所需的工作就越少。将类似函数分组为 1 个顶级函数(如序列器和仲裁器)是一种很好的做法。很难识别和定义这个高级函数以使其包含尽可能多的较小函数,但这样做可以极大地消除状态,进而消除潜在的错误。
始终提供离开街区的强信号。大多数情况下,失败是解决方案。您不知道端点块将如何处理它。您可能会遇到时间问题,这会对您的完美实施产生直接影响。
除非性能受到负面影响,否则避免使用粉状 FSM。与摩尔相比,Mealy FSM 更有可能产生时序问题
.. 最后是我最不喜欢的一个:“如果它没有坏,就不要修复它”由于涉及的风险和错误的高成本,很多时候黑客是解决问题的更实际的解决方案。其他人则通过提及现有组件的利用来避开这一点。
至于与更传统的软件设计进行比较:
离散事件驱动编程是一种完全不同的范式。人们看到 verilog 语法并认为“哦,它就像 C”......然而,事实并非如此。尽管语法相似,但人们必须有不同的想法。例如,传统的调试器在可综合的 RTL 上几乎没有意义(测试台设计不同)。纸上的波形是最好的工具。然而,话虽如此,FSM 设计有时可以模仿过程编程。具有软件背景的人往往对 FSM 很着迷(我知道我一开始就是这样做的)。
System Verilog 有很多很多(和很多)测试台特定的功能。它是完全面向对象的。就测试台设计而言,它与传统的软件设计非常相似。但是,它确实还有一个与之相关的维度,即时间。必须考虑竞争条件和协议延迟
至于验证,它也是不同的(和相同的)。有3种主要方法;
...为了完整起见,我还需要讨论最佳测试台设计实践...但那是另一天
对不起,长度..我在“区域”:)
关于这个主题的最好的书是Reuse Methodology Manual。它涵盖了 VHDL 和 Verilog。
特别是一些在软件中没有完全匹配的问题:
一些相同的包括
HDL 之类的 Verilog 和 VHDL 似乎确实鼓励意大利面条式代码。大多数模块由几个“总是”(Verilog)或“进程”(VHDL)块组成,它们可以按任何顺序排列。模块的整体算法或功能通常是完全模糊的。弄清楚代码是如何工作的(如果你没有编写它)是一个痛苦的过程。
几年前,我看到了这篇论文,它概述了一种更结构化的 VHDL 设计方法。基本思想是每个模块只有 2 个进程块。一个用于组合代码,另一个用于同步(寄存器)。它非常适合生成可读和可维护的代码。
在 HDL 中,代码的某些部分可以同时工作,例如两行代码“可以同时工作”,这是一个优势,明智地使用。这是习惯于逐行语言的程序员一开始可能很难掌握的东西:
应该特别注意引导过程——一旦你的芯片正常工作,你就大功告成了。
在硬件上调试通常比调试软件要困难得多,因此:
简单的代码是首选,有时还有其他方法可以加速你的代码,在它已经运行之后,例如使用更高速度的芯片等。
避免组件之间的“智能”协议。
HDL 中的工作代码比其他软件更宝贵,因为硬件很难调试,所以重用,并且还考虑使用模块的“库”,有些是免费的,有些是出售的。
设计不仅应考虑 HDL 代码中的错误,还应考虑您正在编程的芯片以及与芯片接口的其他硬件设备上的故障,因此应该真正考虑一种易于检查的设计。
一些调试技巧:
如果设计包含多个构建块,则可能希望创建从这些块之间的接口到芯片外部测试点的线路。
您需要在设计中保存足够多的行,以便将有趣的数据转移到外部设备进行检查。您也可以使用这些行和代码作为告诉您当前执行状态的一种方式 - 例如,如果您在某个时候收到数据,则将一些值写入行,在执行的后期您写入另一个值, ETC'
如果您的芯片是可重新配置的,这将变得更加方便,因为您可以定制特定的测试,并随时为每个测试重新编程输出(这看起来非常适合 LED :)。)
编辑:
通过智能协议,我的意思是如果你的两个物理单元连接,它们应该使用最简单的通信协议进行通信。也就是说,不要在它们之间使用任何复杂的自制协议。
原因是这样的——在 FPGA/ASIC 的“内部”查找错误非常容易,因为你有模拟器。因此,如果您确定数据如您所愿,并在您的程序发送时发出,您就达到了硬件乌托邦——能够在软件级别工作:)(使用模拟器)。但是,如果您的数据无法以您想要的方式到达您的手中,并且您必须找出原因……您将不得不连接到线路,这并不容易。
在线路上查找错误很难,因为您必须使用特殊设备连接到线路,记录线路在不同时间的状态,并且您必须确保您的线路按照协议运行。
如果您需要连接两个物理单元,请使“协议”尽可能简单,直到它不会被称为协议:) 例如,如果这些单元共享一个时钟,则在它们之间添加 x 条数据线,并让一个单元写入这些,另一个单元读取,例如,在每个时钟下降时传递一个在它们之间有 x 位的“字”。如果您有 FPGA,如果原始时钟速率对于并行数据来说太快 - 您可以根据您的实验控制其速度,例如使数据保持在至少“t”时钟周期等的线上。我假设并行数据传输更简单,因为您可以在较低的时钟频率下工作并获得相同的性能,而无需在一个单元上拆分您的单词并在另一个单元上重新组装。(希望每个单元接收的“时钟”之间没有延迟)。即使这可能太复杂了:)
关于 SPI、I2C 等,我没有实现它们中的任何一个,我可以说我已经连接了两个运行在同一个时钟上的 FPGA 的腿,(不记得中间电阻的确切形成),很多更高的速率,所以我真的想不出一个很好的理由来使用它们,作为在您自己的 FPGA 之间传递数据的主要方式,除非 FPGA 彼此相距很远,这是使用串行而不是比并行总线。
一些 FPGA 公司使用JTAG来测试/编程他们的产品,但不确定它是否被用作高速传输数据的方式,并且它是一种协议......(仍然可能有一些内置的芯片支持)。
如果您确实必须实现任何已知协议,请考虑为此使用预制的 HDL 代码 - 可以找到或购买。