资讯动态

Windows下FMQL100TAI模型仿真与FPGA实现实践

发布时间:2026/9/9 14:55:40 来源:尧图企业网站定制
最近被一个朋友拉着看了一个FMQL100TAI平台的模型仿真项目核心方向是在Windows环境里用Icraft工具配合Python把算法模型先跑通再落到FPGA硬件上。我负责把工具链、仿真流程和上板验证之间的衔接捋顺。整体走下来最大的感受是国产异构SoC平台的开发模式已经越来越成熟了但Windows环境下模型仿真这个环节网上能直接照抄的完整资料还是太少很多坑都是自己一步步踩出来的。所以这篇整理一下我的完整实践过程给正在做FMQL100TAI或者类似国产FPGA平台开发的同学一个参考尤其是那些准备用Python做算法模型、用Icraft做工程衔接、又不想一开始就切到Linux环境下干活的人。1. 项目背景FMQL100TAI平台与模型仿真的定位1.1 国产异构SoC平台FMQL100TAI到底是一颗什么样的芯片FMQL100TAI是复旦微电子推出的国产化异构SoC FPGA单芯片里同时集成了ARM处理器系统和FPGA可编程逻辑。这样的架构最大的价值在于ARM侧可以跑比较复杂的控制和协议栈比如Linux系统、网络协议、应用逻辑FPGA侧则可以承担高速数据通路、实时信号处理、自定义接口逻辑这类对时序要求很苛刻的任务。两个处理器域之间通过芯片内部的高性能总线互联数据搬运延迟比外置CPUFPGA的方案低得多而且整体系统的体积、功耗、物料成本都能压下来。这颗芯片我最看重的是“纯国产链路”这件事。在一些有国产化率要求的项目里FPGA、处理器、工具链、甚至配套的DDR、电源、时钟方案都要求体系化自主可控FMQL100TAI就是冲着这个需求来的。FPGA部分属于中等容量规模适合做图像预处理、通信基带、电机控制、数据采集这类常见应用。ARM部分以Cortex-A系列为核心性能跑裸机或者轻量级Linux都是够用的。我用下来最大的体会是它并不追求单点性能极致而是强调“异构整合”带来的系统灵活性这一点和主流的Zynq类架构思路是相通的因而很多既有开发经验可以迁移过来。不过用这种芯片开发复杂度比纯FPGA或者纯ARM要高一个量级。你不仅要同时考虑硬件逻辑设计和软件程序还要考虑两个域怎么协同、数据怎么交互。如果不提前把仿真验证流程做扎实直接上板联调经常会出现你分不清问题是出在硬件逻辑、软件配置还是两者之间的接口。这正是模型仿真要解决的问题。1.2 为什么做模型仿真以及IcraftPython在这个流程里扮演的角色硬件开发最怕的其实就是“算法逻辑错了但没及时发现”。我见过太多项目算法工程师在Python或MATLAB里验证得好好的结果RTL实现出来一跑数据对不上又得从头排查是算法理解错了、位宽不够、还是时序不对。模型仿真的核心价值就是在写RTL之前先把算法的“行为模型”定义清楚并且让它成为整个验证流程的参照基准。我这里说的模型仿真并不是指简单的RTL仿真而是“先用高级语言建立算法模型再用这个模型去牵引整个硬件设计验证过程”。具体到这套项目里做法是在Windows主机上用Python把算法功能实现一遍生成测试激励和期望结果然后再用Icraft工具将这些模型和RTL设计管理起来跑完整的仿真闭环最后把验证通过的逻辑部署到FMQL100TAI上。Icraft这套工具在我理解里承担的是“模型和硬件工程之间的桥梁”角色。它一方面能识别和加载你用Python生成的测试激励、期望数据另一方面又能管理RTL仿真工程调用后端仿真器完成仿真最后把仿真结果拿回来和Python模型的期望值做对比。换句话说它把“算法模型验证”和“RTL仿真验证”这两件事拉到了同一个工作流里。这事听起来简单但没有工具支撑的时候你往往要手动维护一堆脚本还要自己写对比逻辑效率很低也容易出错。至于为什么选Windows环境最直接的原因就是方便。算法工程师的日常开发机基本都是WindowsIcraft等工具也提供了Windows版本。在Windows上把模型仿真做完Linux服务器只用来跑大规模回归或者最终的综合实现这样一个团队里的分工就清晰多了。2. Windows环境下的开发底座搭建2.1 工具链组成盘点和版本匹配要跑通整套流程Windows环境下至少需要三样东西Icraft工具、Python环境、以及一个能跑RTL仿真的后端仿真器。Icraft通常会和器件支持包一起安装里面带了FMQL100TAI的器件数据库和仿真模板。Python这边建议装3.8到3.10之间的版本太新的版本有时候会碰到第三方库还没跟上、或者和工具内置的Python接口不兼容的情况没必要赶这个时髦。版本匹配是Windows环境下最容易踩的坑。Icraft的版本、器件库版本、以及仿真器版本这三者之间要能对上。官方发布说明里一般会注明兼容矩阵安装之前一定要先看一遍。我遇到过的情况是仿真器版本太新Icraft调仿真器的时候参数传递方式变了导致仿真起不来后来换个和工具匹配的仿真器版本就一切正常了。所以做这套环境时先把版本矩阵截图存下来后面排查问题能省很多时间。除了这三个核心件还有一些辅助依赖比如Python的numpy算法建模基本离不开、pytest做回归测试比较方便、matplotlib偶尔看波形数据或者调试中间结果要用。这些直接用pip装就行不需要额外购买什么。2.2 安装步骤和IDE配置我一般的安装顺序是先装Python再装Icraft最后装仿真器。为什么要这个顺序因为Icraft安装过程中可能会检测系统里已有的Python环境并自动关联如果Python装在后面就得手动去配置关联路径多一步麻烦事。Python安装时有一个很关键的小细节勾选“Add Python to PATH”。这步漏了的话命令行里敲python会提示找不到命令。虽然也可以手动加环境变量但何必给自己找麻烦。装完之后在命令行里执行python --version确认一下版本号再执行pip --version确认pip可用基础环境就算ready了。接下来是Icraft安装。装的时候选择完整安装让它把器件库、模板、示例工程、Python接口一起装上。装完之后打开一次IDE确认License没问题。如果提示找不到License通常是环境变量没配好把License文件所在路径加到系统环境变量里重启IDE就行。有些版本还要求在用户目录下放一个配置文件来指定License路径这个在安装包里会有模板照着改一下即可。最后装仿真器。装完仿真器之后在Icraft的偏好设置里把仿真器路径指过去这样Icraft才能正确调用它。路径配置完成后建议先用Icraft自带的示例工程跑一个最简单的仿真确认整条链路是通的再开始建自己的工程。很多新手一上来就搭复杂工程结果环境问题和工作流问题混在一起排查起来非常痛苦。我一直建议把开发工程放在纯英文路径下比如D:\work\fmql_proj。因为Icraft工具、Python脚本、仿真器之间要传递文件路径如果路径里出现中文或者空格经常会出现“文件找不到”或者“编码解析错误”之类的奇怪问题。这一步看起来不起眼但在Windows环境下做工具链集成时属于最能避免系统性困扰的措施。2.3 让Icraft和Python真正配合起来工具都装好之后还有一个关键的配置动作让Icraft能加载到你的Python模块。不同版本的Icraft做法不太一样有的是在IDE里设置脚本搜索路径有的是在工程配置文件里指定Python环境路径。我项目里整理了一套工程目录结构专门为“Python模型RTL仿真回归比对”这个工作流定制D:\work\fmql_proj |-- model | |-- sobel.py # 算法行为模型 | |-- gen_stim.py # 测试激励生成脚本 | |-- golden_ref.py # 期望值生成脚本 |-- rtl | |-- tb_top.sv # 顶层testbench | |-- sobel_top.sv # 顶层RTL模块 | |-- sobel_core.sv # 核心逻辑 |-- sim | |-- stimuli # 仿真激励文件保存目录 | |-- results # 仿真结果输出目录 |-- tool | |-- icraft_prj # Icraft工程文件目录 |-- script | |-- run_sim.py # 仿真调度/结果比对脚本这套结构的好处是模型、RTL、仿真产物三者互不干扰回归测试时只需要固定输入输出路径脚本逻辑不用反复改。Icraft工程直接放在tool目录下工程文件记录了RTL源文件路径和仿真配置Python模型放在model目录运行时通过相对路径引用。这样整个工程可以整体拷贝到别的机器上哪怕换一个人接手也能很快上手。3. Python模型仿真流程算法到硬件验证的桥梁3.1 从实际需求出发建行为级模型模型仿真的第一步是把算法用Python建立成行为级模型。这里的关键不是“实现算法”而是“定义清楚硬件接口”。我以自己做过的一个图像边缘检测项目为例来说明目标是处理512x512的8bit灰度图在FPGA上实现3x3的Sobel边缘检测要求处理完一帧图像的时间控制在可接受的实时范围内。在Python里实现Sobel算法本身并不复杂核心代码如下import numpy as np def sobel_edge_detect(img_gray): h, w img_gray.shape out np.zeros((h-2, w-2), dtypenp.int16) kernel_x np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtypenp.int16) kernel_y np.array([[-1, -2, -1], [ 0, 0, 0], [ 1, 2, 1]], dtypenp.int16) for i in range(1, h-1): for j in range(1, w-1): block img_gray[i-1:i2, j-1:j2].astype(np.int16) gx np.sum(block * kernel_x) gy np.sum(block * kernel_y) out[i-1, j-1] int(np.sqrt(gx*gx gy*gy)) return out这段代码在功能上没有问题但作为硬件模型它还缺了很多信息。比如FPGA处理图像时数据并不是整帧一次性交给算法模块的而是按照行扫描顺序一个像素一个像素地流入。3x3窗口意味着至少需要缓存两行数据才能凑齐第三行形成完整的3x3窗口。这些硬件时序信息必须在模型阶段就定义清楚否则后面写RTL时会有很大的理解偏差。所以我实际在Python建模型时会把它改造成“像素流”风格的接口输入按行顺序一个像素一个像素地给内部维护三行行缓存输出也是像素流。这样一来Python模型的调用方式就和RTL模块的时序行为对齐了后面做结果比对时数据流顺序就是一致的不用做复杂的重排。3.2 用Python生成测试激励和期望值模型定下来之后下一步是生成测试激励和期望值。测试激励就是输入图像数据期望值就是模型针对这些输入计算出的正确结果。注意这两份数据要保存成RTL仿真器能够直接读取的格式。我通常的做法是用Python生成一幅合成的测试图像里面包含不同方向的边缘、渐变区域、纯色区域。这样能比较全面地覆盖算法边界情况。然后调用Sobel模型函数把输出结果保存成十六进制文本文件每个像素一行方便仿真器用系统函数逐行读取。代码大致这样import numpy as np # 生成512x512的8bit灰度测试图 img np.zeros((512, 512), dtypenp.uint8) img[100:200, 100:400] 255 img[300:400, 50:200] 128 # ... 可以继续叠加各种图形 # 保存输入激励供testbench读取 with open(../sim/stimuli/input_img.txt, w) as f: for row in img: for val in row: f.write(f{val:02x}\n) # 调用模型得到期望值 out sobel_edge_detect(img) with open(../sim/stimuli/golden_ref.txt, w) as f: for row in out: for val in row: f.write(f{val:04x}\n)这里有一个细节输入像素是8bit所以用两位十六进制而Sobel输出经过梯度幅值计算后最大值会超过255所以我用int16保存用四位十六进制。位宽和格式的定义必须和RTL设计的端口位宽严格对应否则仿真没问题、比对的时候也会出问题。这个低级但常见的错误我在很多项目里见过不止一次。3.3 在Icraft里跑通一次RTL仿真并自动比对激励和期望值准备好之后就是Icraft的主场了。在Icraft里建立一个仿真工程把RTL源文件、testbench、激励文件路径都关联进来。顶层testbench我建议直接用Icraft的模板生成再在模板基础上做扩展而不是纯手写。因为Icraft模板里通常已经包含了它和仿真器之间交互所必需的初始化逻辑自己手写容易漏掉细节导致仿真器调用失败。testbench的核心逻辑是读入输入激励文件一个时钟周期向被测模块送入一个像素数据等到所有像素都送完之后把模块输出的像素数据写入结果文件全部结束后通过一个完成信号通知Icraft仿真结束。跑完仿真之后Icraft可以配置一个后处理命令自动执行Python对比脚本将sim/results下的RTL输出和sim/stimuli下的golden_ref.txt做逐像素比对输出一份简单的pass/fail报告。这一步是整个模型仿真流程里最有价值的地方它把人工核对结果的步骤自动化了RTL一有改动全量回归测试就能自动跑一遍发现问题时还能通过报告中的像素坐标快速定位到具体是哪一行数据出错。4. 从Python仿真模型落地到FMQL100TAI硬件平台4.1 软硬划分哪些代码放PS跑哪些放PL跑仿真验证通过之后算法逻辑的正确性有了保障下一步就是考虑怎么把它落到FMQL100TAI的硬件架构上。第一个要做的决策是软硬划分哪些功能放在ARM侧PS哪些放在FPGA侧PL。我一般会按两条原则来划分。第一条是实时性要求凡是需要逐时钟周期处理的数据通路比如图像像素流、高速ADC采样数据、通信基带信号必须放在PL里凡是事件触发、对延迟不敏感的控制流程比如初始化配置、结果解析、协议交互放在PS里跑。第二条是数据量大小数据量特别大、需要高带宽搬运的放到PL里用专用逻辑处理数据量小、偶尔才发生的事务放到PS里跑软件就行。以边缘检测这个项目为例图像数据从DDR3读出来之后要连续不断地流入Sobel模块中间不能有断流。这块就一定要在PL里实现。ARM侧的任务是把图像数据从DDR3搬到PL的输入接口以及处理PL处理完之后的结果数据。如果你把Sobel算法放到ARM上跑虽然也能实现但每处理一个像素就要从内存取数、计算、写回性能会差很多而且会占用ARM的大量CPU时间影响其他任务的执行。软硬划分完成后要做一个专门的接口文档明确数据流的方向、数据类型、位宽、帧格式、握手信号。这个文档比代码本身还重要因为它是PS和PL两个开发域之间的“协议”任何一边改接口另一边都要同步改没有文档约束很容易出现版本不一致的情况。4.2 把模型行为映射到RTL模块把Python模型变成可综合的RTL是整个开发流程中最需要经验的部分。并不是说把Python代码逐行翻译成Verilog就行而是要把模型中的计算行为映射到硬件结构上。拿Sobel算子来说Python实现里用的是二维数组切片和逐元素乘法求和但硬件里没有“数组切片”这种操作。真正的实现方式是用移位寄存器构建一个3行3列的滑窗每个时钟周期窗口滑动一个像素窗口数据到位后用组合逻辑做乘法求和运算运算结果经过归一化处理后输出。这里面的行缓存可以用FPGA内部的Block RAM或FIFO实现两个行缓存加上当前行数据就能凑齐3行数据。另一个核心问题是数据类型和位宽。Python的float在硬件里没法直接用必须定点化。比如图像像素是8bit无符号数Sobel卷积核的值是整数卷积结果理论上最大可能达到正负4000多简单估计为256乘卷积核绝对值和所以中间累加器至少要12bit到13bit最终输出经过幅值计算和归一化后可以截断成8bit。这里面每一步的位宽选择都需要仔细算留太多余量浪费资源留太少则可能溢出导致图像出现明显的错误条纹。Icraft在这部分能帮上忙的是它支持从模型接口定义生成一部分RTL骨架代码比如模块端口、数据流握手逻辑、寄存器声明。核心的计算逻辑还是需要自己写但骨架已经把时序框架搭好了能省掉不少重复工作也能减少因为端口定义不一致导致的低级错误。4.3 PS与PL通信及上板调试模块级RTL写完并通过仿真后就进入系统集成阶段。这个阶段要把PL逻辑和ARM系统真正连接起来在FMQL100TAI上跑通完整的软硬件协同流程。PS和PL之间的通信最常用的是AXI总线接口。数据要从ARM侧送到PL可以定义一个AXI从接口让ARM写入PL处理完成的数据可以再通过AXI接口写回DDR3ARM轮询或中断获知结果。无论用哪种方式接口协议处理都是一大块工作量好在FMQL100TAI的配套工具和Icraft会提供一些基本的接口模板在这个基础上做修改比自己从零开始写要高效得多。上板调试时我强烈建议先做一个“回环测试”再做实际算法。所谓回环测试就是ARM写一个已知数据到PL的输入寄存器PL不做任何处理直接返回ARM再读回来对比。这个测试能确认AXI通路、时钟复位、地址映射这些基础环境是否正常。如果回环测试都不通过先别急着调算法逻辑先把通路问题解决。通路上最常见的坑是地址不匹配ARM侧访问的物理地址必须和PL侧分配的地址空间一致差一个偏移量都不行。回环测试通过之后再换成实际算法模块。这时如果数据不对优先检查位宽是否对齐、数据流握手时序是否正确。很多时候PL处理完的数据需要等一个“数据有效”信号才能被ARM读取如果ARM不管有效信号直接读拿到的一定是乱数据。这个小问题在仿真里很容易跑出来但在上板调试时如果不注意会卡很久。5. 常见问题与排查技巧实录5.1 Windows环境下的乱码与路径问题在Windows上跑这套工具链最频繁遇到的就是乱码问题。Python程序的输出里有中文时控制台经常显示乱码Icraft的日志文件有时候是UTF-8编码有时候是系统本地编码Python脚本去读日志的时候也会因为编码不一致报错。我的处理方式是统一定义环境。在Windows系统环境变量里增加一个PYTHONUTF81强制Python使用UTF-8模式平时在命令行里执行chcp 65001把控制台代码页切到UTF-8。这样Python脚本、工具日志、控制台输出基本能统一到同一种编码上。还有一条所有代码文件保存为UTF-8 without BOM格式。如果文件里带上了BOM头某些工具读文件的时候会把BOM字符解析成正常字符导致语法错误或路径拼接异常。路径问题前面也提到了再强调一次工程路径不要有中文、不要有空格、不要有特殊符号。Windows环境的常见翻车现场就是C:\Users\张三\Projects这种路径Python的某些标准库在处理时可能没问题但Icraft底层如果是C实现的文件操作对非ASCII路径的支持可能就不那么友好了。统一用纯英文路径是从根源上规避问题。5.2 仿真结果与Python模型不一致这类问题几乎每个做软硬协同仿真的人都会遇到。我经历过的最典型场景是RTL仿真跑出来的结果和Python模型的期望值对比前99%的数据都一致只有某些特定像素点的数据出现偏差。排查方法是逐层缩小范围。首先看是否是位宽截断问题Python模型用float计算梯度幅值RTL里用整数运算并做了移位归一化这两种方式在数值边界上一定会有细微差别。如果差异只有正负1那基本就是量化误差可以接受但如果差异达到几十甚至上百那就是逻辑错误。其次看复位时序。仿真的第一步必须先让RTL进入复位状态然后延时几个时钟周期再释放复位。有些testbench省掉了这个步骤或者复位释放的时间不够导致内部寄存器在初始状态就处于随机值后面处理的数据自然不对。我在RTL里添加了一个计数器记录复位释放后的时钟周期数确保在第一个有效像素到来之前内部状态已经完全稳定。这个做法很笨但排查这种问题真的管用。5.3 引入DDR3等仿真模型时的坑FMQL100TAI平台经常用到DDR3作为大容量存储做完整系统级仿真时就需要把DDR3的FPGA仿真模型引入工程。DDR3仿真模型通常由芯片厂商提供体积庞大行为复杂直接用的时候很容易出状况。最常见的问题是仿真模型初始化时间太长。真实的DDR3芯片上电后要经过一段很长的初始化序列才能正常工作仿真模型会把这段时序真实地模拟出来。如果你在仿真的第0个时钟周期就去访问DDR3结果必然是读回全F或者全0。解决方法是在testbench里等待DDR3模型的初始化完成信号例如init_done或类似信号拉高之后再开始执行读写操作。另一个坑是地址位宽和内存大小的配置不匹配。DDR3模型支持配置不同的容量和位宽如果模型的配置和你的AXI接口地址映射不一致读写地址会对不上。每次新建工程后先把DDR3模型自带的例化模板仔细看一遍确认容量、位宽、Burst长度、时序参数都和设计一致再开始连线。还有一个细节DDR3仿真模型输出的有效数据信号必须要等到一定的读延迟CAS Latency之后才会带上数据。如果你的testbench在读地址发出后下一个周期就去采样数据得到的必然不对。要严格按照AXI接口的握手时序来等待。5.4 Icraft工具自身License和许可问题排查Icraft工具的License问题也值得单独说。安装过程中如果License环境变量没有配好IDE启动时会弹出错误提示有时候是直接无法启动有时候是启动后部分功能被锁定。比较常见的情况是IDE能启动但做仿真时提示“Feature not available”。这个通常是License文件里没有包含该模块对应的Feature或者License绑定的机器码和你当前机器不一致。解决方案是重新申请License确认覆盖了所有需要的功能模块。还有一个容易被忽视的点Icraft的License有时候是浮动的需要网络连接到License服务器。如果你在办公网络环境下能正常使用拿回家连了不同的网络就提示License不可用先检查一下是否能访问到License服务器。这个不是环境变量配置问题是网络权限问题。另外升级Icraft版本时旧的License文件大概率不能直接用于新版本最好的做法是先去官方渠道重新申请一份对应版本的License。不要抱侥幸心理去测试旧License能不能用白折腾半天最后还是要换。5.5 问题速查表现象可能原因处理方式控制台中文乱码编码不统一设置PYTHONUTF81控制台执行chcp 65001仿真器调用失败Icraft和仿真器版本不匹配按官方兼容矩阵选择版本中文路径导致文件读取异常路径包含非ASCII字符工程全部放在纯英文路径下RTL输出和Python模型差1浮点转定点的量化误差合理设置位宽和归一化参数复位后前几帧数据全错复位时序未满足确保复位释放后再开始送数据DDR3读回全F/全0初始化未完成就访问等待DDR3模型init_done信号License不可用环境变量或Feature缺失检查环境变量及License覆盖范围说实话这套流程走下来真正难的不是某一个单独的工具或者某一段代码而是把这些环节串联成一条顺畅的流水线。模型仿真本身不直接解决你的算法问题但它能让你在写RTL之前就把算法行为和接口定义钉死后面每一步都有对照物心里特别踏实。我个人在实际操作中最看重的是那条“Python模型即真值”的纪律——RTL和模型不一致时先在模型侧确认是否正确再着手查RTL不要两头猜。这个习惯帮我避免了好几次重复排查也希望这篇整理能让你在FMQL100TAI平台上少走一些弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价