
拿到复旦微FMQL45T900开发板的第一周我差点以为自己买了个“砖头”——串口无输出、JTAG连不上、交叉编译工具链版本不对折腾了三天才把Hello World跑起来。这块芯片把双核ARM Cortex-A9和可编程逻辑阵列封装在同一个器件里属于典型的异构SoC架构也是目前做全国产化ARMFPGA平台时绕不开的一块板子。如果你正准备上手FMQL45T900或者手里已经有板子但卡在环境搭建这一步这篇内容就是给你写的。我从选型逻辑讲起把工具链、交叉编译、启动镜像、SD卡制作这些环节的完整搭建过程写清楚最后把那些真正让人头疼的坑一条条列出来附上我自己的排查链路希望能帮你少走几天弯路。1. FMQL45T900这颗芯片先搞懂它“是什么”再动手不迟1.1 双核A9加可编程逻辑为什么这种组合是国产化替代的香饽饽FMQL45T900 这个名字拆开看里面信息量不小。FMQL是复旦微的SoC产品线代号45T指的是可编程逻辑部分的资源规模900大概率和主频等级或者封装形式有关。这颗芯片内部集成了两个ARM Cortex-A9处理器核心主频能跑到接近1GHz带NEON SIMD指令集和浮点运算单元在PS处理系统之外还有一整块中等规模的可编程逻辑阵列也就是俗称的PL端或者FPGA部分官方资料里给出的逻辑单元规模、DSP Slice数量、BRAM容量这些参数都按选型手册里那张表为准。很多人第一次接触FMQL45T900第一反应是“这不就是国产版的Zynq-7000系列吗”。这个类比在思路上确实成立。Zynq架构的核心思想就是把ARM处理器和FPGA放在同一个芯片里PS和PL之间通过AXI高速总线互联这样既能跑Linux这种复杂操作系统又能用硬件逻辑做高速并行处理。在实际工程项目里这种异构SoC的价值体现在三个地方。一是集成度高一颗芯片替代原来“ARM主控FPGA协处理”的两颗甚至三颗芯片方案BOM清单和PCB面积都能压下来。二是PS和PL之间的数据通路不再是传统的SPI或者并口而是AXI这种高带宽的内部互联数据搬运延迟低很多。三是供应链可控在当前国产化替代的大趋势下很多电力保护、工业控制、医疗器械项目在立项时就直接卡死了海外器件FMQL45T900这种芯片就成了少数能满足“逻辑资源够用、ARM性能不差、国产可量产”三个条件的选项。1.2 一个典型应用场景拆解采集、处理、通信拿一个常见的工业数据采集终端举例。现场传感器出来的模拟信号先经过ADC变成高速数字流假设采样率是100MSPS每样本16bit那每秒就要产生200MB的数据。如果把这些原始数据全都丢给ARM处理双核A9直接被打满Linux系统基本没法干别的活了。用FMQL45T900做这个项目正确的姿势是让PL端吃掉高速数据流在FPGA内部完成滤波、抽取、FFT或者特征值提取把100MSPS的原始数据降成几十Kbps的特征结果然后通过AXI总线把结果放到DMA缓冲区通知ARM来取。ARM这边只需要跑一个几十KB的小程序把数据打包成网络报文发出去就行。这种“PL做预处理、PS做协议和交互”的分工模式是FMQL45T900这类芯片最典型的用法。同理机器视觉里的传感器数据预处理、通信基站的数字中频处理、电机驱动的多轴同步控制本质都是同一个套路哪里需要低延迟、高吞吐就往PL端挪哪里需要跑协议栈、需要复杂调度就留给PS端。2. 动手前的全局规划启动链路、软件清单和目录规划2.1 一条完整的启动链路先背下来搭建环境之前必须先对整个启动过程有清晰认识否则后面排错的时候会非常痛苦。FMQL45T900的启动链路大概是这样的BootROM是芯片出厂固化的第一段代码上电后它先做最基本的硬件初始化然后根据启动模式引脚的电平状态决定从SD卡、QSPI Flash还是JTAG去加载下一段代码。下一段叫FSBLFirst Stage Boot Loader它的作用是初始化DDR内存、时钟、串口这些基本外设把PS端需要的bit文件也就是PL端的硬件配置和后面要用到的U-Boot引导程序搬运到DDR里然后跳转执行U-Boot。U-Boot起来之后再从存储介质里加载Linux内核和设备树最后挂载根文件系统。打个比方BootROM相当于电脑主板上出厂固化的BIOS引导块FSBL相当于装系统时的引导分区U-Boot相当于GRUBLinux内核就是操作系统本体根文件系统就是C盘。这条链路里有一个FPGA工程师很容易忽略的点bit文件不一定是在上电最开始就加载的。PL端的bit文件可以在FSBL阶段加载也可以在U-Boot阶段用命令加载甚至可以在Linux起来之后用设备树覆写或者直接操作寄存器的方式来加载。选择在哪一步加载取决于你的PL逻辑是否需要参与启动早期流程。2.2 需要准备的软件清单我把自己搭环境时用到的软件列了个表每一样都标注了用途照着准备就行软件用途备注官方PL开发工具编写Verilog/VHDL、综合、实现、生成bit文件需要正版License按MAC地址申请arm-linux-gnueabihf-gcc交叉编译U-Boot、内核、应用程序建议用Linaro版本或官方SDK自带版本U-Boot源码编译引导程序版本要跟官方BSP匹配Linux内核源码编译zImage内核镜像和设备树dtb官方推荐版本最稳根文件系统镜像提供Linux运行环境和命令可以用Buildroot生成或官方rootfs串口调试工具连接开发板串口查看启动日志minicom、putty都行注意波特率SD卡读卡器制作启动SD卡备两张一张系统一张备份这些软件看起来多但真正需要你自己编译的其实只有U-Boot、内核和应用程序。PL开发工具和交叉编译器都是安装好就能用的。2.3 我自己习惯的目录规划很多人拿到板子第一件事就是“先翻个例程跑一下”这话没错但建议先把工作目录规划好。我习惯在Linux主机上建一个工作根目录比如~/fmql45/下面分几个子目录~/fmql45/ ├── tools/ # 交叉编译工具链、烧录工具 ├── bsp/ # 官方BSP包解压目录包括U-Boot、内核、FSBL工程 ├── images/ # 编译产物统一输出目录zImage、dtb、uImage、 rootfs ├── projects/ # 自己的FPGA工程和APP工程 ├── sd_card/ # 制作SD卡时的挂载点 └── docs/ # 数据手册、勘误表、自己的笔记这么分区的好处是项目做到后面你会发现编译产物散落得到处都是统一放到images目录下写烧录脚本的时候路径清晰不会出现“明明编译了却找不到镜像在哪儿”的问题。这一条看起来是小习惯但做项目的时候能省下大量时间。3. 环境搭建三阶段工具链、内核、根文件系统3.1 第一阶段安装PL开发工具和License配置FMQL45T900的PL端开发工具官方一般会随板卡资料一起提供或者从复旦微官网下载区按器件型号选型下载。安装过程在Linux环境下比较顺手工具包解压后通常是一个安装脚本执行后默认安装到/opt/目录下。如果你用的是Ubuntu这类发行版装之前先把基础依赖库补齐否则安装器很容易在中途报错退出。具体缺哪些库跟工具版本有关官方安装手册里有一个依赖清单照着apt install装一遍就行。装完之后不要急着建工程先去申请License。这一步卡住过不少人。License申请一般需要你机器网卡的MAC地址工具运行时也必须在联网或者License文件可访问的状态下。实际操作时用ifconfig查到网卡MAC填到官方License申请页面邮件收到License文件后放到指定目录然后设置环境变量让工具能找到它。License配好之后新建工程器件型号选择FMQL45T900后面的整个流程跟主流FPGA工具基本一致写RTL代码、添加管脚约束和时钟约束、综合、实现、生成bit文件。唯一要特别注意的是工程里涉及PS端时需要配置好AXI接口的地址映射因为后面Linux设备树里访问PL外设的地址必须跟这里配置的地址保持一致。地址对不上这个坑我后面专门讲。3.2 第二阶段安装ARM交叉编译工具链ARM交叉编译工具链就是在一台x86主机上编译出ARM芯片能运行的二进制文件的“翻译官”。FMQL45T900的PS端是Cortex-A9架构属于32位ARMv7所以选型时要选择支持ARMv7硬浮点的工具链最常见的就是arm-linux-gnueabihf-前缀的GCC。装工具链最省事的方式是直接下载官方SDK自带的交叉编译器解压后设置环境变量就能用。如果你自己从Linaro官网下载我建议选带gnueabihf字样的版本。安装步骤很简单cd /opt sudo tar xjf gcc-linaro-arm-linux-gnueabihf-*.tar.bz2 export PATH/opt/gcc-linaro-arm-linux-gnueabihf/bin:$PATH为了方便把export PATH这行写进~/.bashrc里以后每次打开终端自动生效。验证工具链是否正常写一个最小的C文件#include stdio.h int main(void) { printf(FMQL45T900 Hello World!\n); return 0; }编译并查看文件类型arm-linux-gnueabihf-gcc -o hello hello.c file hello输出里如果显示ELF 32-bit LSB executable, ARM, EABI5说明生成的就是ARM指令集的程序。这里有一个实用小技巧初期调试阶段建议加-static参数做静态编译也就是把C库打进可执行文件里。这样做出来的文件大一点但不需要目标板上有对应的动态库很多Linux起不来或系统不完整的情况下静态编译的Hello World仍然能跑是验证串口和内核是否活着的重要手段。3.3 第三阶段编译内核、制作启动SD卡接下来是环境搭建的核心环节编译Linux内核并制作一张能启动的SD卡。先编译内核。从BSP包里找到内核源码目录先加交叉编译工具链到环境变量然后执行export CROSS_COMPILEarm-linux-gnueabihf- export ARCHarm make fmql45t900_defconfig make -j8 zImage make -j8 dtbs这里fmql45t900_defconfig是官方BSP里针对这一款开发板预置的内核配置如果没有这个名字就用make menuconfig手动配置或者找BSP的README确认正确的defconfig名字。编译完后在arch/arm/boot/目录下找到zImage在arch/arm/boot/dts/或相应子目录下找到xxx.dtb设备树文件。然后制作SD卡。FMQL45T900支持从SD卡启动SD卡一般分成两个分区第一个分区是FAT32格式放BOOT.bin、uImage或zImage、设备树.dtb文件第二个分区是ext4格式放根文件系统。分区用fdisk或者gparted都能做注意FAT32分区建议1GB-2GB就够ext4分区用剩下所有空间。这里的BOOT.bin是一个打包文件包含FSBL、bit文件和U-Boot。在官方SDK工具里选择创建Boot Image把FSBL的elf、PL端的bit、U-Boot的elf按顺序加进去生成BOOT.bin然后拷贝到SD卡FAT分区。根文件系统如果是官方提供的rootfs.tar解压到ext4分区的根目录sudo mount /dev/sdb2 /mnt/rootfs sudo tar xf rootfs.tar -C /mnt/rootfs sudo umount /mnt/rootfs至此SD卡上有了完整的启动材料BOOT.bin负责引导zImage和dtb负责拉起Linuxrootfs负责提供用户空间环境。开发板拨码开关拨到SD启动模式上电串口终端应该能看到完整启动日志。4. 避坑指南那些能卡你三天的细节4.1 串口什么输出都没有先别怀疑板子查这三件事拿到板子上电后最让人慌的就是终端里一个字都没有。这时候先别怀疑“板子是不是坏了”按下面顺序排查。第一串口连接线是不是交叉线还是直连线。很多开发板调试串口用的是USB转TTL有些用的是RS232电平接线方式不一样。USB转TTL小板和开发板之间一般是TXD对RXD、RXD对TXD交叉如果用的是DB9串口线要确认是不是交叉线。第二终端软件配置。波特率一般为115200数据位8停止位1无校验无流控。很多人乱码或没输出其实就是终端软件里流控被默认打开了关掉就好了。第三上电时序和电源指示灯。FMQL45T900这类SoC对上电时序有严格要求的场景很常见如果设计上单独给PS和PL供电两个电源域之间的上电顺序不对芯片可能一直处于复位状态。板卡出厂图纸里一般标注了电源设计要求先量一下各路电压是否正常。经验之谈如果是自己画的底板配核心板串口没输出的概率最大的是“板子压根就没跑起来”。而如果你用的是官方全套开发板第一优先怀疑的还是串口连接和终端配置。4.2 启动日志报FATAL: kernel too old这个报错我当年第一次见到也懵了。系统能启动内核起来了根文件系统挂载之后执行/sbin/init时报“FATAL: kernel too old”仔细看后面的描述是“this binary expects a newer kernel”。明明刚编译完最新内核怎么会说内核太老这个坑的本质是根文件系统里的应用程序用了太高版本的glibc。交叉编译工具链的glibc版本 2.28时编译出来的动态链接器会要求内核版本不低于某个阈值一般通过内核的__NR_*系统调用号判断。而FMQL45T900官方BSP里的老版本内核比如3.x或4.x不满足这个要求于是动态链接器直接拒绝运行。解决办法有两个。一是换用与BSP匹配的官方交叉编译器不要自己到网上下最新版工具链。另一个是如果必须用新版工具链就换一个新版本的内核或回退。这个坑说明一个原则一套BSP里给什么工具链版本就尽量用什么版本不要凭感觉升级。4.3 bit文件加载了但PL端不工作FPGA工程师最容易在这个问题上卡住。SD卡里明明放了带bit文件的BOOT.bin系统启动日志也没报错但PL端就是没有预期输出。先理清一个概念bit文件不是一定会在启动时加载的。如果你用的BOOT.bin是你自己生成的必须在SDK工具里确认添加了bit文件。如果FSBL阶段加载bit失败启动日志里一般会有对应打印但有些BSP把打印级别压得很低不容易看到。更常见的坑是PL的逻辑正常工作但PS端访问不到。我在一个例程里配置了AXI GPIO外设逻辑上LED应该能被点亮点灭但通过Linux的 /dev/mem 去读地址读回来全是0xFFFFFFFF像是总线挂了。最后定位到原因是PL工程里AXI外设的地址配置跟设备树里写的不一致。PL端配置基地址是0x40000000而设备树里写的是0x80000000两边各说各话数据通路当然建立不起来。排查方式就一条把PL工程的地址配置列出来和设备树的reg字段、U-Boot里设置的地址对齐。地址映射关系对上了问题解决80%。4.4 QSPI启动模式下的常见问题SD卡启动跑通之后很多人会想把镜像烧进QSPI Flash实现“上电即启动不需要SD卡”。但QSPI启动的坑和SD卡启动完全不同。首先是启动模式拨码和烧录模式的差异。QSPI启动模式下要先用SD卡启动Linux然后用烧录工具把BOOT.bin写入QSPI Flash的0地址同时把内核和设备树写到指定的偏移地址。这里地址是分段的BootROM会从头部读FSBLFSBL再去读不同偏移处的U-Boot和bit。如果偏移地址没对上启动就会失败。其次是擦除和校验。QSPI Flash写入前必须先擦除而且擦除是按扇区进行的你写4KB数据可能要先擦掉64KB的扇区。有些烧录工具不支持自动擦除烧完直接覆盖在旧数据上导致校验失败或启动崩溃。我的习惯是烧录前先全片擦除一次再逐段写入并回读校验。4.5 两套ARM工具链混用的大坑在很多项目里ARM开发不止一种环境。FPGA工程师用官方SDK里的交叉编译器做裸机开发的可能用Keil MDK如果设备上还跑了RTOS可能又是一套工具链。“arm compiler 5.06”这类关键词搜得多就是因为有人在Keil里用ARMCC编译Cortex-A9的裸机代码然后拿arm-linux-gnueabihf的工具链编译应用两边混用。ARMCC和GCC编译出来的代码ABI规范基本兼容但库、链接脚本、启动文件完全不同。最典型的错误是“用GCC的启动文件去配ARMCC的工程”导致程序一跑就hardfault。我的建议是一个项目里工具链选型要统一。跑Linux就用GNU工具链全家桶裸机也尽量用同一个编译器的裸机版本不要图方便混着来。4.6 License和工具版本不匹配最后一条是国产FPGA工具的常见毛病License过期比你想的快申请流程比你想象的慢。有些版本的License还限制只能在某个网段、某块网卡下用换电脑之后原有License直接失效。解决方案有两个方向一是尽早申请License拿到板子的第一天就申请别等编译工程时才发现没有License二是申请前仔细看License类型有些是浮动的需要服务器有些是锁本机的根据自己办公环境选择。之前一个朋友把浮动License的服务器路径配错了连续折腾一天最后发现就是路径里多了一个斜杠。5. 第一个PSPL协同实验点亮LED并让Linux读到底层状态5.1 PL端的点灯逻辑环境通通就绪之后用一个最简单但有代表性的实验验证整个通路在PL端实现一个计数器分频驱动LED在PS端通过GPIO读取分频器的溢出标志。这个实验麻雀虽小五脏俱全涉及PL逻辑、AXI接口、Linux访问底层设备三个环节。PL端逻辑先用Verilog写一个简单的LED闪烁模块module led_blink( input wire clk, input wire rst_n, output reg led ); reg [26:0] counter; always (posedge clk or negedge rst_n) begin if (!rst_n) begin counter 27d0; led 1b0; end else begin counter counter 1b1; if (counter 27d50000000) begin counter 27d0; led ~led; end end end endmodule如果板卡上的LED是低电平点亮就把输出取反。这个模块在综合工具里做约束时把led对应到板卡原理图上标注的LED管脚时钟管脚连接到开发板的PS侧输出时钟或者板上有源晶振。实现生成bit之后在SDK里把bit跟FSBL、U-Boot打包成BOOT.bin烧进SD卡。5.2 通过AXI GPIO接入PS端如果只是想验证PL逻辑上面的流程已经能用了。但真正的异构SoC玩法是PS和PL交互所以更好的方案是用工具自带的AXI GPIO IP核把LED的控制权交给PS。在PL工程里添加AXI GPIO配置输出宽度1位连到LED再添加一个AXI接口需要和PS互联配置好基地址。编译整个工程后Linux里边设备树里要加对应的节点axi_gpio_0: gpio40000000 { compatible xlnx,xps-gpio-1.00.a; reg 0x40000000 0x10000; #gpio-cells 2; gpio-controller; };当然如果你用的BSP设备树里已经包含该节点就省去手动添加。5.3 从Linux寄存器寻址到底层状态Linux起来之后验证PS访问PL外设的方式有两条路一条是写一个简单的GPIO子系统驱动用标准的gpiolib接口另一条是直接操作寄存器用devmem工具读写物理地址这种方式在调试初期最直观。# 把物理地址映射到内存直接写寄存器 devmem 0x40000000 32 0x00000001 # 拉高LED devmem 0x40000000 32 0x00000000 # 拉低LED如果LED跟着变化说明PS到PL的数据通路完全打通AXI总线、地址映射、设备树配置全都正确。再用一条小命令连续读写做一轮简单的压力测试确认总线稳定for i in $(seq 1 1000); do devmem 0x40000000 32 0x$((i % 2)); done这个过程走完整个开发环境的验证就算闭环了交叉编译工具链没问题Linux启动没问题PL加载没问题PS和PL的互联没问题。后面再往上加真正的业务逻辑就只是时间问题。6. 这一路走下来的几个实用习惯FMQL45T900这类国产化ARMFPGA平台最大的问题不是芯片本身难用而是资料分散、例程风格不统一、网上讨论少。所以我把几个实用习惯留在这里都是实际操作中沉淀下来的。第一详细的笔记记录要留一套。好记性不如烂笔头国产芯片的BSP版本更新快不同版本的工程结构和配置项差异很大今天踩过的坑三个月之后大概率还会踩。每次解决问题后花十分钟记录问题现象、根因、解决办法、涉及的文件和版本后面需要回溯时省力很多。第二配置文件尽量用脚本固化。交叉编译工具链路径、环境变量、SD卡挂载点、编译命令这些内容经常用到手敲容易出错特别是环境变量忘记导出导致编译出来的内核是用x86工具链编译的情况。写一个build.sh一键脚本放在工程根目录把环境变量、编译命令、产物拷贝全写进去能显著减少低级错误。第三官方BSP不要乱改。拿到BSP之后先白盒跑一遍确认没改动的情况下能正常工作再做增量修改。如果直接改BSP源码出了问题很难说清是BSP的问题还是自己改出来的问题。实在要改的话每个改动点都用Git提交一个独立commit注释写清楚改动原因。我这块板子从最初的串口无输出到现在跑着完整的Linux系统上了AXI总线外设前后大约花了一周这中间有一半时间都花在环境和工具的排查上。把这个过程写出来就是希望你拿到板子的时候能比我少浪费几天。环境通了之后后面做真正的业务功能反而是一件顺手的事。