代码移交完成后,为了尽快掌握第三方开发的FPGA代码,我面临的首要任务是搭建一个完备的仿真环境。
riple 原工程在Quartus II下采用波形文件搭建了仿真环境。用过Quartus II集成仿真工具的朋友应该了解,与Modelsim相比这个仿真工具存在以下几点不足:
1. 仿真的对象是综合后的网表,而不是HDL语言。如果把仿真对象看作是一个“黑盒子”,网表级仿真和语言级仿真是等价的;但是我面临的问题是尽快学习和掌握FPGA代码,而且后续的工作涉及到调试和维护,我需要的是对代码内部每一个网线型和寄存器型对象的观察和控制能力。这一点,只有支持语言级仿真的工具才能提供。(原有的仿真环境是通过把内部节点引出到顶层输出引脚来增强观察能力的。)
riple 2. 测试向量的输入只能是波形,而不是HDL语言。在测试向量很简单的情况下,手工输入波形比编写和调试行为级的HDL语言代码要快捷。但是在测试向量变得复杂后,手工输入和维护波形文件是繁琐易错的,而且波形文件很难进行版本差异比较,失去了版本控制工具提供的便利。进一步说,基于波形输入的仿真工具只能由设计者目视检查仿真结果的对错,不能实现仿真结果的自动化检查,也就不能支持基于仿真的回归测试。
riple 基于上述原因,在对原有的仿真环境进行了初步的学习后,我转向了在Modelsim下搭建仿真环境。
riple 在Modelsim下搭建仿真环境时遇到了下面几个问题,相应的解决方案也一起列出:
1. 原有的设计顶层采用了图形文件输入,Modelsim不能直接支持,需要在Quartus II下手工把图形设计文件转化为HDL文件。
riple 为了保证每次修改图形文件后在仿真中能够不遗漏相应的更新,需要自动保持图形文件和HDL文件的一致性。我编写了一个Tcl脚本bdf2vlog.tcl
来完成这个任务,这个脚本可以包含在Modelsim的仿真脚本中,每次运行Modelsim编译前自动执行。
riple 2. 原有的设计没有全局复位信号,仿真初始时刻所有的信号都是未知态。缺少初始复位的设计在Modelsim下进行HDL语言级别的仿真是不能运行的,但是在Quartus II和Modelsim下进行综合后网表级别的仿真是可以运行的,而且这样的设计在硬件中是可以正确运行的。在Quartus II手册中明确指出,FPGA器件中所有寄存器在配置完成后是确定处于逻辑“0”状态的(代码中明确指定复位后需要初始化为逻辑“1”的寄存器可以通过NOT Gate Push-back功能初始化为逻辑“1”状态)。这样看来,由于缺少复位设计而导致的HDL语言级别仿真失败,并不是因为原设计本身存在问题(尽管这样的风格并不好),而是因为我们在惯用的HDL语言中对时序逻辑的描述没能包含真实器件的初始值。综合后网表中对D触发器的描述包含了这一正确的初始值,所以能够在Quartus II和Modelsim下正常仿真。
riple 这一问题,是不可能通过修改HDL设计输入文件来解决的——无论是手工添加复位信号,还是修改对时序逻辑的初始化描述,涉及的工作量都太大了。好在Modelsim提供了一条控制仿真模型内部信号状态的命令:force。force命令的选项很多,可以支持多种用法,比如快速创建某种临时或永久的仿真模型内部状态或输入状态。在这里,我采用了“-cancel 0”选项来实现对被测对象内部时序逻辑的初始化,再配合上find signals /*这一通配查找命令,就可以快速全面地完成对时序逻辑的初始赋值操作,把未知态消灭在了仿真的初始0时刻。通过在各个模块加载完毕之后仿真运行之前执行脚本reg_init.tcl
,这一问题得到了解决。
riple 3.随后这个问题与上述问题类似,都是初始化问题——存储器初始化问题。这一问题同样只出现在HDL语言级别的仿真下,在网表级的仿真下和真实器件中都是不出现的。正常情况下,通过MegaWizard Plug-in Manager生成的存储器例化语句中,对存储器在仿真中的初始值是有设置的:POWER_UP_UNINITIALIZED = "FALSE"。有了这一参数,在仿真中,存储器取值可以自动初始化为逻辑“0”;对于Cyclone III器件,即使没有指定,这一参数的缺省值也是“FALSE”,从而保证了仿真和真实器件行为的一致性。原设计在输入文件中直接用HDL语言例化了存储器模块,但是由于该设计历经了多个FPGA器件系列,出于兼容性考虑,所有的存储器模块在例化时都没有指定器件系列(INTENDED_DEVICE_FAMILY)。由于没有指定器件系列,POWER_UP_UNINITIALIZED参数的指定值和缺省值都是无效的,这就导致了在HDL语言级别的仿真中,所有存储器的初始值为“X”,一些由存储器输出直接驱动的逻辑就不能正确运行了。
riple 这一问题,是可以通过修改HDL文件来解决的——存储器的例化数量并不多,位置也好找。我采用了跟第二个问题同样的解决方法:通过Modelsim提供的命令从被测对象外部来给存储器赋值。这里我采用mem list查找所有的存储器,采用mem load加载特定的存储器。加载存储器内容为“0”的命令不能在仿真0时刻立即运行,因为在仿真开始运行后存储器的仿真模型会把初始值设置为“X”。只有在存储器仿真模型正常初始化完成后,比如仿真运行1ns后,才能执行脚本mem_init.tcl
。
riple 用一个周末的时间解决了上述几个“非正常”问题后,搭建仿真环境的工作就剩下编写外部接口模型了。在接下来的几天内,我又编写了RGMII和MDIO接口的行为模型,最后完成了CPU接口的BFM行为模型。其中,CPU接口的行为模型采用了Modelsim控制台交互式激励输入方式,但是这个我自创的方法还不成熟,只有在配备了双核处理器的PC机上才能顺畅运行。等到这一方法成熟后,我会在博客上介绍这一技术。
riple June 26 2009 - June 30 2009
用户1073592 2009-11-26 16:51
用户1546547 2009-11-25 20:17