ARTICLE DETAIL

资讯详情

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

安路FPGA与ModelSim联仿环境搭建实战:库编译与调试指南

安路FPGA与ModelSim联仿环境搭建实战:库编译与调试指南 1. 为什么要自己动手搭“安路Modelsim”联仿环境做安路FPGA开发的朋友应该都有体会TD软件自带了一套仿真工具链新建工程之后只要点一下它就能自动帮你把库配好、仿真跑起来看起来挺省事。但等你真正开始写稍微复杂一点的逻辑或者想把UART、SPI、DDR控制器这些模块完整跑一遍仿真的时候很快就会发现自带工具在某些场景下并不顺手——波形查看体验一般批处理支持有限跟团队里其他人用的仿真环境不统一甚至不同版本的TD自带仿真器行为还有差异。我个人的做法一直很朴素用ModelSim做主要仿真平台用TD做综合和布局布线。安路官方对ModelSim的支持其实比较完整只是需要你自己动手把安路器件的仿真库编译进ModelSim。这一步听起来简单但实际做的时候坑很多尤其是库的路径、编译参数、器件系列对应关系搞错之后后面所有仿真都会跟着报错。这篇文章我就把从库编译到波形调试的完整流程拆开讲一遍把我在实际项目中踩过的坑和排查思路都放进来希望对正在折腾这个环境的同行有点用。适用的人我先说清楚用过Modelsim、但对安路库不熟的老手可以跳过前面直接看库编译章节新手建议从头看我会把涉及到的文件目录、命令、工程组织方式都写明白。核心目标只有一个——让你拿到一块安路FPGA板子之后能用ModelSim把仿真环境踏踏实实跑起来而不是在库编译和波形报错上耗掉半天。2. 库编译这一步卡住太多人完整操作和产出物验证2.1 先搞清Modelsim和TD之间的版本匹配关系很多人在这一步就直接卡住了装好ModelSim装好TD然后照着网上搜到的教程敲命令结果一会儿报找不到模块一会儿报版本不兼容。问题本质上就一个——安路的仿真库有对应的器件系列和工具版本要求你不能随便拿一个版本的库文件就往ModelSim里塞。我目前常用的组合是ModelSim SE-64 2020.4和TD 5.0.x。这里强调一下ModelSim SE和ModelSim DE的库编译接口其实一样但SE在批处理和覆盖率支持上更稳定所以我一直推荐用SE版本。TD方面安装目录下会有一个仿真库文件夹不同版本路径略有差异大致是TD安装目录\lib\arch\... // 器件架构相关文件 TD安装目录\lib\sim_prim\... // 仿真原语库 TD安装目录\lib\src\... // 部分IP核的仿真源码建议你先在文件管理器里把整个TD安装目录的lib子目录浏览一遍心里有个数。网上搜“modelsim se-64 2020.4”下载安装完之后不要急着建工程先把库准备好。这里有一个非常重要的经验不要试图手动把所有.v文件都拖进ModelSim里编译而是按照器件系列来区分。安路的器件主要有EG4系列、ELF系列、FH系列等不同的系列对应不同的原语文件。你当前工程用什么型号的FPGA就编译对应的库全部编译不仅慢而且可能出现同名模块覆盖的问题。2.2 库编译的完整命令流程我习惯用一个独立的目录专门存放编译好的仿真库不要跟具体工程混在一起。比如我机器上的路径是这样的D:\fpga_lib\anlogic\modelsim_se\第一次编译时先创建库目录并映射work库vlib work vmap work work然后进入TD的仿真库源码目录执行编译。以EG4系列为例典型的做法是先编译工艺原语库再编译器件原语库vlog -work work D:/TD/lib/sim_prim/eg4/*.v vlog -work work D:/TD/lib/sim_prim/common/*.v注意实际文件路径会因为TD版本不同而有差异你应该先看一下sim_prim目录下都放了哪些子目录。有些版本把不同系列分得很清楚有些则是统一放在一起这时候就需要你根据文件内容判断。编译完成后还需要把库映射关系写进modelsim.ini这样后面每次启动ModelSim都能直接找到安路的库。建议用vmap命令而不是手动编辑INI文件vmap anlogic_prim D:/fpga_lib/anlogic/modelsim_se/work这里我给anlogic_prim起了一个自定义的库别名这个名字后面在vsim命令里会用得到。如果你直接编辑modelsim.ini往往只改当前工作目录下的副本全局路径容易搞混所以我强烈建议用vmap维护。2.3 编译完怎么验证库是好的这一步很多人会跳过但跳过之后出了问题反而更浪费时间。验证方法其实很简单写一个最原始的逻辑例化一个安路的PLL原语或者一个简单的BUFR然后用ModelSim跑一遍仿真。我一般会建一个非常小的测试工程里面只有一个top模块和一个testbench例如例化一个PLLmodule pll_test ( input wire clk_in, output wire clk_out ); EG_PLL #( .FREQ(50.0), .DIV0(1), .DIV1(1) ) u_pll ( .CLKI(clk_in), .CLKOP(clk_out) ); endmodule然后编译、仿真。如果这个最基础的原语仿真没有问题说明库本身通得过。如果这里就报错那问题一定出在库编译环节先回去查路径和版本不要在后面的工程上浪费时间。2.4 不同TD版本间切换时的注意事项我身边有人会用多个TD版本比如一个项目用TD 4.6另一个项目用TD 5.0这时候要注意不同TD版本生成的仿真库不能直接混用。就算只是小版本升级原语的仿真模型也可能有改动建议每个TD版本单独建一个目录不要图省事覆盖着用。D:\fpga_lib\anlogic\td4.6\ D:\fpga_lib\anlogic\td5.0\每次切换工程时检查modelsim.ini里anlogic_prim指向的路径是不是对应当前工程使用的TD版本。这个问题很隐蔽因为ModelSim在打开工程时可能自动加载了旧路径下的库仿真报错时你根本想不到是版本问题。3. 从 testbench 到仿真运行一次能跑通的联仿工程要怎么建3.1 工程目录结构决定了你后面调试的效率库编好之后终于可以建真正的仿真工程了。很多新手习惯把源码、testbench、仿真脚本、约束文件全部堆在一个目录里一开始看着挺方便等工程变大之后根本没法管理。我的推荐结构是project/ ├── rtl/ // 存放设计源码 ├── tb/ // 存放testbench ├── sim/ // 存放ModelSim工程和脚本 ├── script/ // 存放编译脚本 ├── constraint/ // 存放约束文件不上仿真 ├── output/ // 存放仿真产物 └── doc/ // 文档在sim目录下创建ModelSim工程工程文件、波形文件、日志文件都留在这个目录里这样即使要清空重来也不会误删源码。3.2 写好一个真正可用的testbench关于testbench我见过太多刚入门的同事写出来的仿真激励连基本的复位时序都没有。这里分享一个经过多次验证的模板结构特别适合安路FPGA这种带PLL、需要稳定时钟的场景timescale 1ns/1ps module tb_uart_rx; reg sys_clk; reg sys_rst_n; reg uart_rxd; wire [7:0] rx_data; wire rx_done; // 时钟生成50MHz周期20ns initial begin sys_clk 1b0; forever #10 sys_clk ~sys_clk; end // 复位时序前100ns保持复位然后释放 initial begin sys_rst_n 1b0; #100; sys_rst_n 1b1; end // UART输入初始拉高 initial begin uart_rxd 1b1; // 后续在这里发送模拟的UART帧 #200; send_byte(8h5A); #1000; send_byte(8hA5); #2000; $finish; end // 模拟UART发送任务9600bps1起始位8数据1停止位 task send_byte(input [7:0] data); integer i; begin uart_rxd 1b0; // 起始位 #104167; for (i 0; i 8; i i 1) begin uart_rxd data[i]; // LSB优先 #104167; end uart_rxd 1b1; // 停止位 #104167; end endtask uart_rx u_uart_rx ( .clk (sys_clk), .rst_n (sys_rst_n), .rxd (uart_rxd), .rx_data (rx_data), .rx_done (rx_done) ); endmodule这个模板里有两个容易被忽略的点第一复位时序必须在时钟开始之后。有些人写initial begin sys_rst_n 0; #100; sys_rst_n 1; end但如果时钟生成块的初始赋值和复位块并发执行可能出现第一个时钟沿到来时复位还没稳定的情况。虽然大多数时候仿镇结果碰巧没问题但在时序严格的设计里容易引起初始化异常。第二UART的模拟发送任务用#104167是因为9600bps的位周期约为104.167微秒。如果你用timescale 1ns/1ps这个数字对应的是纳秒也就是0.104167ms。很多人在仿真UART时发不出正确数据往往就是这里算错了比例。3.3 用DO脚本取代手动操作进入ModelSim之后我很少手动点菜单编译全部通过DO脚本执行这样不仅可复现还能避免点错按钮。下面这个脚本是我常用的放在script目录下# run_sim.do vlib work vmap work work vmap anlogic_prim D:/fpga_lib/anlogic/modelsim_se/work vlog -work work ../rtl/uart_rx.v vlog -work work ../tb/tb_uart_rx.v vsim -L work -L anlogic_prim work.tb_uart_rx -voptargsacc log -r /* add wave -hex /tb_uart_rx/sys_clk add wave -hex /tb_uart_rx/sys_rst_n add wave -hex /tb_uart_rx/uart_rxd add wave -hex /tb_uart_rx/rx_data add wave -hex /tb_uart_rx/rx_done run 100us执行方式是在ModelSim控制台输入do ../script/run_sim.do有些文章喜欢在vsim命令后面不加-voptargsacc这里我必须强调如果你要看内部信号的波形这个参数是必须的。ModelSim默认会做优化某些中间变量可能被优化掉导致你在波形窗口里看不到信号。acc的意思是开启全量可观测性代价是仿真速度稍微慢一点但对于调试阶段的工程来说这点性能损失完全值得。3.4 库映射和编译顺序最容易出错的三个细节细节一先编译依赖的库再编译设计文件。如果你在testbench里例化了PLL等原语而这些原语模型又依赖anlogic_prim库里的模块那么先编译testbench后编译库虽然ModelSim不会立刻报错但在vsim阶段会告诉你找不到模块。细节二vmap命令只在当前工作目录下生效不会全局生效。如果你换了工作目录重新建工程之前用vmap映射的库别名就没了必须重新执行。这就是为什么我把vmap命令直接写进DO脚本而不是手动执行一次就完事。细节三Windows系统下路径分隔符统一用斜杠。ModelSim虽然能识别反斜杠但在DO脚本里反斜杠会被当作转义字符路径直接变成乱的报错会非常莫名其妙。这是Windows下做ModelSim脚本最容易踩的坑没有之一。4. 波形调试的硬功夫红线、不定态和协议对齐4.1 波形窗口显示红线到底是什么意思这次咱们把它彻底说清楚。很多人看到仿真波形是红线就慌了以为是代码错了。其实在ModelSim里信号显示红色通常表示高阻态或未初始化。细分下来有几种情况一是信号根本没有被驱动。比如一个wire类型的信号没有任何赋值语句在驱动它或者驱动它的模块没有例化成功。这种情况下波形就是红线本质上是Z状态。二是信号被驱动了但在仿真开始时没有初始化。比如一个reg类型的变量没有赋初值仿真时间0时刻它处于X状态波形显示为红色或金色。这种情况比Z要多见因为很多人在写testbench时忘了给初始信号赋初值。三是驱动冲突。比如两个always块同时给同一个reg赋值或者testbench里驱动了输入信号而设计内部又有一个assign语句试图反向驱动同一个网络这会导致波形出现不确定状态。从我自己的调试经验看仿真波形变红80%以上是因为复位时序或初始化没做好而不是逻辑错误本身。所以看到红线第一步不是去改逻辑而是先检查testbench的激励部分。4.2 信号可见性和优化问题为什么你拖进波形的信号是空的有一种情况会让你怀疑人生波形窗口打开了信号也添加了但里面一片空白或者显示“No data”。这个问题通常不是你的逻辑有错而是仿真时信号被优化掉了。前面提到的-voptargsacc是解决办法。但还有一种更隐蔽的情况你已经加了acc但某些层次的信号还是看不到。这时候建议在vsim命令后再执行一次log -r /*这个命令的作用是把仿真过程中所有信号的变化记录到WLF文件中。不要小看这条命令默认情况下ModelSim只记录你添加了波形窗口的信号如果你中间想补充添加其他信号可能因为前面没有记录而得不到完整数据必须重新跑一遍仿真。加上log -r /*之后所有信号变化都被记录下来后面怎么加波形都行。4.3 实用调试技巧用断言和打印辅助定位波形调试不是全靠眼睛看。对于复杂的UART、SPI、I2C这类协议用波形肉眼看位对齐容易看花眼我一般在testbench里加打印或者断言。以UART接收为例在rx_done拉高时打印接收到的数据always (posedge clk) begin if (rx_done) $display([%0t] RX done, data 0x%02X, $time, rx_data); end这样在ModelSim的Transcript窗口就能直接看到每次接收完成时的数据不需要盯着波形去数位。如果仿真结果和你发送的数据对不上再结合波形去看是哪一位出了问题效率高很多。另外ModelSim也支持assert断言对于检查时序关系很有用。比如你想确保rx_done拉高时rx_data必须是有效数据可以这样写property p_rx_valid; (posedge clk) rx_done |- (rx_data inside {[0:255]}); endproperty assert property (p_rx_valid);虽然这个例子比较基础但它的意义在于当工程变复杂之后靠人眼确认信号正确性是不可能的断言是仿真自动化的基础。我在做稍微正式一点的仿真时都会在testbench里埋一些断言让ModelSim帮我盯着关键时序。4.4 UART仿真中的波形对齐实例这里分享一个我调UART接收模块时的真实经历。当时收到的波形显示rx_done脉冲宽度只有一个时钟周期但设计文档里说应该是高电平保持到下一个字节开始。一开始我以为是代码里的计数器问题后来仔细看波形才发现rx_data在rx_done拉高之前就已经变化了也就是说数据有效窗口和标志信号没有对齐。这个问题的根因在接收模块里数据移位寄存器在停止位采样完成后立刻将数据输出但rx_done因为经过了一级寄存器延迟晚了一个时钟周期才拉高。解决方法是把数据输出也打一拍或者提前一拍拉高rx_done让数据和标志信号对齐。这种问题如果不用波形看光靠打印信息根本发现不了因为你打印的时候看到的数据可能是对的。5. 常见报错速查与排查链路复盘5.1 编译阶段的报错错误一vlog-7Failed to open ...这个错误几乎都是路径问题。检查三个方面路径里是否有反斜杠、路径里是否有中文、路径最后是否有空格。我见过有人把工程放在D:\我的工程\下面ModelSim对中文路径支持很差编译报错还找不到原因。建议所有FPGA工程路径和安装路径都使用纯英文。错误二vlog-2892Module ... is not defined这个报错信息很直白就是在当前编译单元里找不到某个模块定义。首先检查被例化的模块文件是否加入了工程其次检查编译顺序。如果例化的模块是安路原语那基本可以确定是库映射问题重新执行vmap并且确保vsim命令里的-L参数包含对应库名。5.2 仿真运行阶段的报错错误三vsim-3033Instantiation of ... failed这个错误是例化失败ModelSim会在后面附上原因最常见的是找不到库里的模块或者模块名称不匹配。以安路原语为例有些人会在代码里写EG_PLL但实际库里的模块名可能是EG_PHY_PLL或者带前缀的其他名字这时候就要在库里搜一下确切的模块名而不是想当然。搜索方法是在ModelSim控制台执行vdir -lib anlogic_prim这个命令会列出该库中所有已编译的模块搜一下就能找到正确的名字。5.3 仿真结果异常异常一仿真开始后波形完全没有变化最常见的原因是testbench里没有时钟生成或者时钟生成被$finish提前终止了。另外如果你在testbench里写了$stop但没有加run -all仿真会停在中间状态波形看起来也是静止的。异常二仿真可以跑但结果是全X这种通常是初始化问题设计中某个寄存器没有复位或者复位信号根本没有生效。检查复位信号是否在testbench中被正确拉高再检查设计中复位逻辑写法是否规范。这里特别提醒一个安路FPGA容易踩的坑安路的原语模块有些复位是高电平有效有些是低电平有效如果搞反了复位一直处于无效状态所有寄存器都会是X。异常三仿真速度极慢甚至鼠标都卡先检查timescale是否设置的过细。比如你用timescale 1ps/1ps仿真一个需要毫秒级时间的UART帧仿真步进数量是巨大的卡是正常的。解决方法是系统级仿真用1ns/1ps需要精细观察的部分再局部使用更细的时间尺度。还有一个容易忽略的原因是acc参数开了全量可观测性之后所有信号都被记录工程大时仿真速度会明显下降。调试完成后做回归仿真时可以去掉acc速度能提升不少。5.4 快速诊断对照表现象可能原因排查顺序波形显示红线信号未驱动/未初始化1. 检查testbench赋初值 2. 检查模块例化 3. 检查复位时序波形空白信号被优化/日志未记录1. 检查acc参数 2. 执行log -r /*vsim报找不到模块库未映射/模块名不匹配1. 执行vmap2. 用vdir确认模块名仿真结果全X初始化或复位逻辑问题1. 复位极性 2. 复位时序 3. 时钟是否正常仿真卡死/太慢时间尺度或可观测性设置1. 检查timescale2. 去掉acc6. 少量覆盖率数据合并你可能没注意到的ModelSim实用功能既然热搜里有人问modelsim 覆盖率 txt 怎么合并这里顺带说一嘴。ModelSim跑覆盖率仿真时会生成UCDB格式的覆盖率数据库。当你分别跑了多个不同用例的仿真之后希望把覆盖率合并起来评估整体代码覆盖率就需要用到vcover merge命令。假设你跑了两个用例生成的文件分别是test1.ucdb和test2.ucdb合并方法如下vcover merge combined.ucdb test1.ucdb test2.ucdb合并之后再用vcover report查看vcover report -html combined.ucdb这样会在当前目录生成一个HTML格式的覆盖率报告方便其他同事在浏览器里查看。有一点要注意UCDB文件只能在相同工具版本下合并不同版本的ModelSim生成的UCDB合并时可能报格式错误。另外如果两次仿真的设计代码有改动做覆盖率合并就没有意义了合并前确保代码版本一致。7. 联仿流程中我个人的一些习惯和补充最后分享一下我在日常工作中积累的几个小习惯不一定适用于所有人但确实帮我省了很多时间。第一个习惯是所有仿真都用脚本驱动不在GUI里点按钮。DO脚本的好处不只是可复用更重要的是能放进Git仓库做版本管理。我今天写了一个testbench改了三个版本如果靠手动操作根本没法知道上一版是怎么仿真的。有了脚本每次修改都有记录出问题可以回退。第二个习惯是建一个独立的lib_build.sh脚本专门用来重新编译库而不是在ModelSim里手动敲命令。因为库编译一次之后可能很久都不会再动时间一长就忘了当时怎么编的。写成脚本放好下次换电脑或者换TD版本直接跑一遍脚本就行。Windows下可以用批处理Linux下用Shell脚本。第三个习惯是仿真之前先检查modelsim.ini里的库映射。因为ModelSim的库映射状态会因为当前工作目录不同而变化有时候你以为引用的是安路库结果实际指向的是之前某个工程留下的旧库。在vsim之前执行vmap -n可以列出当前所有映射关系一眼就能看出问题。别问我怎么知道的有一次我排查了一晚上报错最后发现就是库路径指向了另一个版本的TD目录。第四个习惯是把波形配置文件也脚本化。比如你每次都要添加时钟、复位、数据总线这几个信号可以写成add wave -hex /tb_top/clk add wave -hex /tb_top/rst_n add wave -hex /tb_top/bus_data然后每次仿真时执行这个配置省去重复拖拽信号的时间也避免漏掉某个关键信号。关于联仿调通之后真正让我觉得这套环境值得的原因其实是后面做复杂模块验证的时候。ModelSim的批处理能力、脚本化控制、覆盖率合并这些都是完整验证流程的基础。虽然安路官方工具做得越来越好但多用一套主流仿真工具既不亏也能让团队协作更顺畅。至少在我目前的项目里ModelSim安路FPGA这套组合已经稳定跑了多个模块UART、SPI、自定义总线协议都验证到位了。希望这篇实战解析能帮你少走点弯路把时间花在真正需要调通的功能逻辑上。
返回列表