ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

易灵思Efinity与Modelsim FPGA联合仿真实战指南

易灵思Efinity与Modelsim FPGA联合仿真实战指南 1. 为什么要在易灵思Efinity平台上折腾Modelsim联仿接触过易灵思EfinixTrion或Titanium系列FPGA的朋友都清楚这家厂商的Efinity开发工具链走的是轻量级路线综合、布局布线、比特流生成一条龙都能在自家IDE里完成。但问题来了——Efinity自带的仿真能力相对基础波形查看、断点调试、覆盖率分析这些活儿跟Modelsim这种老牌仿真器比起来差距不是一星半点。尤其是做UART收发、SPI通信、图像处理流水线这类时序敏感的逻辑验证时没有一套趁手的仿真环境基本等于闭着眼睛调代码。我手头这个项目用的是易灵思Ti60F225开发板核心芯片是Trion T60封装F225资源规模中等偏上适合跑一些带DSP运算和高速接口的工程。项目里涉及UART_RX接收模块、定点数运算单元还有一路LVDS接收逻辑。这些模块单独跑综合没问题但时序对不对、状态机跳转是否正常、跨时钟域有没有亚稳态风险光靠看代码是看不出来的。必须上仿真而且要用Modelsim做联合仿真才能把波形抓得清清楚楚。所谓“联仿”说白了就是让Efinity负责综合前的编译和原语映射把易灵思特有的IP核、原语比如PLL、LVDS收发器、Block RAM转换成仿真模型再交给Modelsim去跑时序仿真或功能仿真。这一步的关键在于Efinity生成的仿真库和Modelsim的编译流程必须对齐否则要么找不到模块要么波形全是红线不定态调起来让人抓狂。这篇文章适合谁看如果你正在用易灵思的FPGA做项目手头有Modelsim不管是SE版还是DE版想搭建一套稳定的联仿环境那下面的内容应该能帮你省下不少试错时间。我会从工程结构、原语编译、Modelsim配置、波形调试四个维度把整个流程拆开揉碎讲一遍顺便把踩过的坑和私藏技巧一并倒出来。2. 联仿环境的整体设计与工具链选型2.1 Efinity与Modelsim的分工逻辑先理清楚一个概念Efinity和Modelsim在联仿流程里各自扮演什么角色。Efinity是“前端”负责把Verilog/VHDL代码综合成网表同时把易灵思特有的原语比如EFX_PLL、EFX_LVDS_RX、EFX_BRAM替换成仿真模型。这些仿真模型通常以Verilog源文件的形式存放在Efinity安装目录下的simlib文件夹里。Modelsim是“后端”负责编译这些仿真模型和你的设计文件然后跑仿真、出波形。为什么不让Efinity自己仿真因为Efinity内置的仿真器对复杂激励的支持有限波形窗口的交互体验也一般。Modelsim的优势在于支持Verilog和VHDL混合仿真、波形可以保存为WLF格式反复查看、支持断点和单步调试、覆盖率统计功能完善。对于需要反复迭代的模块级验证Modelsim的效率高得多。选型上我用的是Modelsim SE-64 2020.4版本。这个版本对SystemVerilog的支持比较完整而且64位架构跑大容量仿真时不容易爆内存。如果你用的是Modelsim DE 2022.2流程基本一致只是界面和部分命令有细微差别。Linux环境下Modelsim的安装和激活稍微麻烦一点但跑仿真的稳定性比Windows好尤其是长时间跑回归测试时不容易卡死。2.2 工程目录结构的规划联仿工程最忌讳的就是文件乱放。我的习惯是建一个顶层目录下面分四个子目录rtl/存放所有设计源代码包括顶层模块、UART_RX、定点数运算单元、LVDS接收逻辑等。sim/存放测试平台Testbench文件比如tb_uart_rx.v、tb_top.v。efinity_prj/Efinity工程目录包含.xml工程文件和综合脚本。modelsim_prj/Modelsim工程目录存放编译脚本compile.do和仿真脚本run.do。这样分的好处是Efinity和Modelsim各自管好自己的文件互不干扰。Efinity综合时只读rtl/和efinity_prj/Modelsim编译时只读rtl/、sim/和Efinity生成的仿真库。后期如果RTL代码有改动只需要重新跑一遍Modelsim的编译脚本不用动Efinity工程。注意Efinity生成的仿真库文件路径通常带有版本号和器件型号比如simlib/trion/t60/f225/。不同器件型号的仿真库不能混用否则会出现原语行为不一致的问题。2.3 仿真库的生成与编译策略Efinity生成仿真库的方式有两种一种是在IDE里通过菜单导出另一种是用命令行脚本自动生成。我推荐用命令行方式因为可以集成到Makefile或批处理脚本里方便自动化。具体操作是在Efinity工程目录下打开终端执行efx_run命令加上-simlib参数指定输出目录。比如efx_run -prj uart_rx_prj.xml -simlib ./simlib_output -device T60F225执行完成后simlib_output目录下会生成一堆.v文件包括efx_pll.v、efx_lvds_rx.v、efx_bram.v等。这些文件就是Modelsim需要编译的仿真模型。编译策略上我习惯把仿真库分成三组基础原语库、IP核库、用户设计库。基础原语库包括PLL、LVDS、BRAM这些IP核库包括UART、SPI等软核用户设计库就是自己的RTL代码。分组编译的好处是如果只改了用户代码只需要重新编译第三组前两组不用动节省时间。3. 核心细节解析与实操要点3.1 Efinity原语仿真模型的编译顺序Modelsim编译仿真库时顺序很重要。如果先编译用户设计再编译原语库Modelsim会报“模块未定义”的错误。正确的顺序是先编译易灵思的基础原语库efx_pll.v、efx_lvds_rx.v等。再编译IP核生成的仿真文件如果有。最后编译用户RTL和Testbench。在Modelsim的compile.do脚本里可以这样写# 创建work库 vlib work vmap work work # 编译基础原语库 vlog -work work ../simlib_output/efx_pll.v vlog -work work ../simlib_output/efx_lvds_rx.v vlog -work work ../simlib_output/efx_bram.v # 编译用户设计 vlog -work work ../rtl/uart_rx.v vlog -work work ../rtl/uart_tx.v vlog -work work ../rtl/top.v # 编译Testbench vlog -work work ../sim/tb_top.v这里有个细节vlog命令默认编译Verilog文件如果仿真库里有SystemVerilog文件需要加-sv参数。另外如果原语库文件之间有依赖关系比如efx_pll.v里例化了efx_pll_primitive那efx_pll_primitive.v必须在前面的行编译。实操心得Efinity不同版本生成的仿真库文件名可能略有差异。比如2023.1版本里LVDS接收原语叫efx_lvds_rx.v而2022.2版本里叫efx_lvds_rx_prim.v。编译前最好先ls一下目录确认文件名。3.2 Testbench的编写要点与时钟复位处理Testbench是仿真的灵魂。对于UART_RX接收模块Testbench需要做几件事产生时钟、产生复位、发送串行数据、检查接收结果。时钟产生很简单用always块加#延时即可。比如产生一个50MHz时钟initial begin clk 0; forever #10 clk ~clk; // 周期20ns频率50MHz end复位信号要注意易灵思FPGA的全局复位通常低电平有效但内部逻辑可能用高电平复位。Testbench里最好产生一个低电平复位脉冲持续至少100ns确保所有寄存器都回到初始状态。initial begin rst_n 0; #100; rst_n 1; endUART_RX的激励数据要符合协议起始位低电平、8位数据位LSB先发、停止位高电平。假设波特率是115200时钟频率50MHz那么每个比特的持续时间是50_000_000 / 115200 ≈ 434个时钟周期。在Testbench里可以用一个任务task来发送一个字节task send_byte(input [7:0] data); integer i; begin rx 0; // 起始位 #(434 * 20); // 434个时钟周期每个周期20ns for (i 0; i 8; i i 1) begin rx data[i]; #(434 * 20); end rx 1; // 停止位 #(434 * 20); end endtask这里434 * 20是比特周期对应的纳秒数。实际仿真时波特率可能有微小误差但UART接收模块通常有中点采样机制能容忍几个时钟周期的偏差。注意如果Testbench里用了#延时仿真时间会跑得比较慢。对于长时间仿真可以考虑用时钟周期计数代替绝对延时提高仿真效率。3.3 定点数运算模块的仿真验证项目里有一路定点数运算单元做的是Q格式的乘加运算。定点数仿真的难点在于小数点位置是隐含的波形上看到的是整数需要手动换算。比如Q15格式1位符号位15位小数位数值1.0对应整数32768数值0.5对应16384。在Testbench里验证定点数乘法时可以先用实数计算期望值再转换成定点数比较。比如// 期望结果0.5 * 0.25 0.125 // Q15格式0.5 - 16384, 0.25 - 8192 // 乘积16384 * 8192 134217728 // 右移15位134217728 15 4096 // 4096对应Q15的0.125仿真时把a和b分别设为16384和8192跑一个时钟周期后检查result是否等于4096。如果不等于说明乘法器的截位或舍入逻辑有问题。实操心得定点数仿真最容易出错的地方是溢出和舍入。建议在Testbench里加一个参考模型用实数运算把定点数结果和实数结果对比误差超过1个LSB就报错。这样能快速定位是算法问题还是硬件实现问题。4. 实操过程与核心环节实现4.1 Efinity工程配置与综合选项设置在Efinity里新建工程时器件型号要选对。Ti60F225对应的器件是T60F225封装是F225速度等级根据实际芯片选比如C3或C4。工程建好后把RTL文件添加到Design视图里设置顶层模块为top。综合选项里有两个关键设置一是Simulation模式要选Functional或Timing。功能仿真不跑时序速度快适合验证逻辑功能时序仿真会跑布局布线后的延时信息速度慢但能发现建立/保持时间违例。我一般先用功能仿真验证逻辑再用时序仿真检查关键路径。二是Generate Simulation Library选项要勾选上。这样Efinity在综合完成后会自动生成仿真库文件。如果没勾选需要手动执行efx_run -simlib命令。综合完成后Efinity会输出一个.bit文件和一个simlib目录。simlib目录里的文件就是Modelsim需要编译的仿真模型。4.2 Modelsim工程建立与编译脚本编写Modelsim工程可以手动建也可以用脚本自动建。我习惯用脚本因为可重复性好。在modelsim_prj/目录下新建一个compile.do文件内容如下# 清理旧库 if {[file exists work]} { vdel -all } # 创建新库 vlib work vmap work work # 编译仿真库 vlog -work work ../simlib_output/efx_pll.v vlog -work work ../simlib_output/efx_lvds_rx.v vlog -work work ../simlib_output/efx_bram.v # 编译RTL vlog -work work ../rtl/uart_rx.v vlog -work work ../rtl/uart_tx.v vlog -work work ../rtl/fixed_point_mul.v vlog -work work ../rtl/top.v # 编译Testbench vlog -work work ../sim/tb_top.v # 加载仿真 vsim -t 1ns -novopt work.tb_top # 添加波形 add wave -r /* # 运行仿真 run 1ms这里-novopt参数是关闭优化方便调试时查看内部信号。-t 1ns设置时间精度为1ns。add wave -r /*递归添加所有信号到波形窗口。run 1ms运行1毫秒仿真时间。在Modelsim的命令行里执行do compile.do就能自动完成编译和仿真。如果编译报错根据错误信息定位问题。常见的错误包括模块未定义编译顺序不对、端口不匹配RTL和Testbench端口名不一致、语法错误Verilog版本不兼容。注意Modelsim SE-64 2020.4默认使用Verilog-2001标准。如果RTL里用了SystemVerilog语法比如logic类型、always_ff块需要在vlog命令里加-sv参数否则会报语法错误。4.3 波形调试与红线问题排查波形全是红线不定态是联仿里最常见的问题。红线意味着信号的值是X即未知状态。产生红线的原因通常有三个复位信号没接对如果复位信号一直是低电平寄存器不会初始化输出就是X。检查Testbench里复位信号的时序确保仿真开始后复位信号有从低到高的跳变。仿真库没编译如果原语比如PLL的仿真模型没编译Modelsim会认为该模块不存在输出就是X。检查compile.do里是否包含了所有仿真库文件。跨时钟域信号没同步如果信号从慢时钟域传到快时钟域没有做同步处理采样时可能采到X。这种情况需要加两级寄存器同步。排查红线问题时我习惯从顶层往下查。先看时钟和复位信号是否正常再看模块的输入输出。如果某个模块的输出是红线但输入正常说明该模块内部有问题。可以打开该模块的源代码在关键信号上加add wave逐步缩小范围。实操心得Modelsim里可以用examine命令查看信号的当前值。比如examine /tb_top/uart_rx/state可以查看状态机的当前状态。如果状态是X说明状态机没复位。4.4 UART_RX接收仿真的完整流程以UART_RX接收模块为例完整仿真流程如下在Testbench里产生50MHz时钟和低电平复位脉冲。复位释放后调用send_byte任务发送一个字节8h55。UART_RX模块接收数据在rx_done信号上产生一个脉冲。Testbench在rx_done脉冲时检查rx_data是否等于8h55。如果相等打印“PASS”否则打印“FAIL”。仿真波形上可以看到rx信号从高电平跳变到低电平起始位然后依次是8位数据位最后回到高电平停止位。rx_done信号在停止位中点时产生一个时钟周期的高脉冲。rx_data在rx_done脉冲时稳定为8h55。如果rx_data不是8h55检查波特率是否匹配。比如发送端波特率是115200接收端波特率是9600那接收到的数据肯定是错的。另外检查采样点是否在比特中点。如果采样点太靠前或太靠后可能采到跳变沿导致数据错误。注意UART协议里数据位是LSB先发。如果发送8h55二进制01010101波形上先看到的是最低位1最后看到的是最高位0。如果顺序反了接收到的数据会变成8hAA。5. 常见问题与排查技巧实录5.1 Modelsim编译报错“Module not found”这个错误通常是因为仿真库没编译或者编译顺序不对。解决方法检查compile.do里是否包含了所有仿真库文件。确认仿真库文件的编译顺序先基础原语再IP核最后用户设计。如果仿真库文件有依赖关系确保被依赖的文件先编译。5.2 波形全是红线怎么破红线问题排查表现象可能原因解决方法时钟信号是红线时钟产生逻辑没执行检查initial块里是否有forever循环复位后寄存器仍是红线复位信号没接对检查复位信号的极性和时序原语输出是红线仿真库没编译重新编译仿真库跨时钟域信号是红线缺少同步器加两级寄存器同步状态机状态是红线状态机没复位检查复位逻辑5.3 仿真速度太慢的优化技巧Modelsim跑长时间仿真时速度可能很慢。优化方法关闭不必要的波形记录。只添加关键信号不要add wave -r /*。使用-novopt参数会降低仿真速度如果不需要调试内部信号可以去掉这个参数。减少#延时改用时钟周期计数。如果仿真时间超过1ms考虑分段跑每次跑100us保存波形后继续。5.4 定点数运算结果不对的排查思路定点数运算结果不对通常是因为小数点位置没对齐。排查步骤确认输入数据的Q格式。比如a是Q15b是Q13乘积的Q格式是Q28。确认输出数据的Q格式。如果输出要求Q15需要右移13位。检查移位操作是否用了算术右移而不是逻辑右移。算术右移会保留符号位逻辑右移会补零。检查溢出保护。如果乘积超出输出位宽需要饱和处理或截断。实操心得定点数仿真时可以在Testbench里加一个$display语句打印输入和输出的定点数值和对应的实数值。这样能直观地看到误差有多大。5.5 LVDS接收仿真的特殊注意事项LVDS接收原语的仿真跟普通逻辑不太一样。Efinity生成的LVDS仿真模型里差分信号是用单端信号模拟的。比如rx_p和rx_n两个信号仿真时需要给它们相反的激励。如果只给rx_p激励rx_n一直是X输出就是X。另外LVDS接收原语通常需要一个参考时钟。这个时钟的频率和相位要跟发送端匹配否则采样会出错。仿真时可以用PLL产生参考时钟或者直接在Testbench里用always块产生。注意LVDS仿真模型里可能有延时参数比如#1或#0.5。这些延时是模拟真实器件的传输延时仿真时不要忽略。6. 联仿流程的自动化与工程管理6.1 用Makefile管理编译和仿真流程手动执行do脚本虽然方便但每次都要打开Modelsim界面。如果要做回归测试最好用Makefile自动化。在modelsim_prj/目录下新建Makefilecompile: vlib work vmap work work vlog -work work ../simlib_output/efx_pll.v vlog -work work ../simlib_output/efx_lvds_rx.v vlog -work work ../rtl/uart_rx.v vlog -work work ../rtl/top.v vlog -work work ../sim/tb_top.v sim: vsim -c -t 1ns -novopt work.tb_top -do run -all; quit clean: rm -rf work transcript vsim.wlf执行make compile编译make sim跑仿真make clean清理临时文件。-c参数让Modelsim在命令行模式下运行不打开图形界面。run -all运行直到Testbench调用$finish。6.2 波形保存与离线分析Modelsim的波形可以保存为WLF格式方便离线分析。在run.do里加一行write wave offline.wlf这样仿真结束后波形会保存到offline.wlf文件。下次打开Modelsim用vsim -view offline.wlf命令加载波形不用重新跑仿真。实操心得如果仿真时间很长可以分段保存波形。比如每跑100us保存一次文件名加上时间戳。这样即使仿真中途崩溃也能保留之前的波形数据。6.3 版本兼容性与跨平台注意事项Efinity和Modelsim的版本兼容性是个坑。Efinity 2023.1生成的仿真库在Modelsim SE-64 2020.4里编译可能报错因为仿真库文件里用了新的SystemVerilog语法。解决方法要么升级Modelsim到2022.2以上要么在vlog命令里加-sv参数并确保Modelsim支持该语法。Linux环境下Modelsim的安装和激活稍微麻烦一点。安装包通常是.run文件执行chmod x后运行。激活需要生成license文件放到$LM_LICENSE_FILE环境变量指定的路径下。如果激活失败检查hostid是否匹配以及license文件里的日期是否过期。注意Windows和Linux下的Modelsim工程文件不通用。如果要在两个平台之间切换最好用脚本管理编译流程不要依赖工程文件。7. 从联仿到板级验证的衔接联仿通过后下一步就是上板验证。Efinity生成比特流后用下载器烧录到Ti60F225开发板。板级验证时UART_RX接收的数据可以通过串口打印到PC上或者用逻辑分析仪抓波形。板级验证和仿真的差异在于真实器件的时序有抖动电源有噪声信号完整性可能有问题。如果仿真通过但板级失败检查以下几点时钟频率是否匹配。仿真时用的50MHz板级可能是48MHz或100MHz。复位信号是否稳定。板级复位信号可能有毛刺需要加滤波。IO电平标准是否匹配。UART的TX/RX通常是3.3V LVCMOS如果对方是5V TTL需要电平转换。波特率误差是否在容忍范围内。晶振的精度通常是±50ppm115200波特率下误差约0.6%UART接收模块通常能容忍±2%的误差。实操心得板级调试时如果UART接收不到数据先用示波器看TX线上的波形。如果波形正常但接收不到检查接收模块的采样时钟是否跟发送端匹配。如果波形不正常检查IO约束和电平标准。8. 个人经验总结与后续扩展方向这套联仿流程跑通之后最大的感受是Efinity和Modelsim的配合虽然需要一些手动配置但一旦脚本写好后续的仿真效率非常高。尤其是做UART、SPI、I2C这类协议验证时Modelsim的波形调试能力比Efinity自带仿真器强太多。后续如果要扩展可以考虑几个方向一是把覆盖率统计加进来用Modelsim的vcover命令生成覆盖率报告看看Testbench有没有漏掉的分支二是把联仿流程集成到CI/CD里每次代码提交后自动跑仿真确保没有回归问题三是把定点数运算的参考模型用Python或MATLAB写好自动生成测试向量提高验证效率。最后分享一个小技巧Modelsim的波形窗口里可以用virtual function把定点数信号转换成实数值显示。比如定义一个fix2float函数把Q15格式的整数除以32768波形上就能直接看到实数值不用手动换算。这个功能在调试定点数算法时特别有用。
返回列表