现在做 FPGA 练习题,基本流程是:写完 Verilog,在仿真工具里写简单 testbench,跑一遍波形,看输出大致对就算过了。但经常在上板后还是遇到异常,比如某些边界条件没考虑、复位没处理好、跨时钟域有隐患。想请教:在工程里,大家通常怎么设计 testbench 和仿真用例,才能比较有信心说「这个模块在可接受范围内是正确的」?有没有适合学生阶段的仿真 checklist 或示例?
FPGA 仿真只会跑波形,不知道怎么「证明自己没写错」?
提问
回答 6

其实你遇到的问题很典型,很多人刚学FPGA时都这样。我的建议是:不要只盯着波形看,而是把testbench当成一个自动化的检查工具。比如你写一个计数器模块,不要只给一个时钟让它跑,然后看波形里计数对不对。你可以写一个assert语句,在仿真里自动判断:每次时钟上升沿后,如果计数没按预期递增或复位后没归零,仿真就立刻报错并停下来。这样你就能快速定位问题,而不是靠肉眼一行一行看波形。另外,对边界条件比如满、空、溢出这些,可以专门写几个test case,用随机化的输入去覆盖。学生阶段可以先从简单的self-checking testbench开始,比如定义一个任务,输入一组激励和期望输出,然后在仿真结束时打印“测试通过”或“测试失败”。这样慢慢就能建立信心。

我觉得核心是你要把需求拆成可验证的点,而不是笼统地说“功能正确”。比如你做一个FIFO,可以列出:写满后不能再写、读空后不能再读、同时读写时数据不丢失、复位后状态清零。每个点对应一个test case,并且用断言自动检查。我常用的方法是:在testbench里加一个monitor模块,它只负责观察输出,并和预期值比对。比如你写一个加法器,monitor就检查每个周期输出是不是等于输入A+B,如果有错就打印错误信息。这样你跑一遍仿真,就能知道所有关键场景是否通过,而不是只看到几个波形。学生可以试着从状态机、计数器这些基础模块开始,每个模块写3-5个test case,覆盖正常、边界和异常情况,慢慢就会形成自己的checklist。

我理解你的困惑,因为只看波形确实容易漏掉问题。我建议你试试“激励+监控”的testbench结构。激励模块负责产生各种输入,比如随机延迟、异步复位、突发数据,监控模块则自动检查输出。比如你写一个跨时钟域同步器,激励模块可以故意让输入变化很快,监控模块检查输出是否稳定且无亚稳态传递。对于学生来说,可以先从简单模块练起:比如一个移位寄存器,写一个testbench让它跑1000个周期,每次检查输出是否等于输入延迟了N拍。如果所有周期都通过,那基本就稳了。另外,可以在仿真中故意加入非理想情况,比如在复位过程中给输入,或者时钟抖动,看模块能不能扛住。长期坚持这种习惯,你上板后出错的概率会大大降低。

说实话,我刚开始做FPGA练习时也跟你一样,跑个波形觉得对了就完事,结果上板各种翻车。后来我发现,问题在于你只看了理想情况。比如你测FIFO,你测过写满之后继续写吗?测过读空之后继续读吗?测过复位信号在中间突然拉高吗?这些边界条件才是上板炸雷的重灾区。建议你做一个checklist:每个模块先列出所有可能的输入组合和状态,然后每个组合写一个test case,用assertion自动检查输出,不要靠眼睛盯波形。另外,对跨时钟域信号,一定要在仿真里加随机延迟,不然根本发现不了亚稳态隐患。

我理解你的困惑,因为很多教程只教你怎么写testbench,没教你怎么测才算全面。我的做法是:先写一个需求文档,把模块的每个功能点拆成可验证的条目,比如计数器就测边界值、溢出、复位后初始值、使能信号中断等。然后每个条目对应一个test case,用系统任务如$monitor或$display自动打印关键信号,再用assertion做自动检查。这样仿真跑完,看log就知道哪里挂了,不用盯着波形。另外,强烈建议在testbench里加入随机化激励和延迟,比如用$random,能覆盖很多你没想到的时序组合。

我是学生党,也踩过这个坑。后来看了几个开源项目的testbench,学了一招:用self-checking testbench。就是你先准备好一组输入和对应的期望输出,比如存到文件里,仿真时让testbench自动读入输入激励,同时比对输出和期望值,不一致就报错。这样跑一次就能验证所有预设场景。另外,对于状态机,我习惯在testbench里故意给非法状态或者非连续输入,看状态机能不能正确恢复。还有,复位信号不要只在开始时拉一次,要在运行过程中随机拉几次,模拟上电不稳定或者按键抖动。你可以搜一下systemverilog assertion,学起来不难,但能极大提升信心。
发表回答
登录后可在本页底部提交回答
