1. 为什么要在 QEMU 上搭一块 RISC-V AI 芯片的实验台做 RISC-V AI 芯片验证这行最头疼的从来不是写 RTL而是“流片之前怎么把软件栈跑通”。一块真实的硅片从 tapeout 到回片要几个月中间软件团队不能干等着。这时候 QEMU 就是救命稻草——它能在一台普通 x86 笔记本上虚拟出一套完整的 RISC-V 系统让固件、内核、驱动、运行时提前跑起来。但问题在于通用 QEMU 只模拟标准外设而 AI 芯片往往挂着一堆自定义的加速器、DMA 引擎、PCIe 端点设备这些在默认的qemu-system-riscv64里根本不存在。所以这个项目的核心目标很明确用 AGENT 辅助的方式从零搭建一个能承载 RISC-V AI 芯片验证的 QEMU 实验台。所谓“实验台”不是简单跑个qemu-system-riscv64 -M virt就完事而是要构建一套可复现、可扩展、能挂载自定义 PCIe 设备、能跑 AI 推理负载的虚拟平台。AGENT 在这里扮演的角色是帮你自动化处理那些重复性极高的环境配置、设备树生成、启动脚本编排、日志分析工作让你把精力集中在芯片逻辑本身。这套东西适合谁三类人最需要一是 RISC-V 芯片前端设计工程师需要在流片前验证驱动和固件二是 AI 加速器软件栈开发者需要在不依赖硬件的情况下调试 runtime 和编译器三是系统架构师想快速评估 PCIe 拓扑、内存映射、中断路由对整体性能的影响。哪怕你只是对 RISC-V 和 QEMU 感兴趣跟着走一遍也能把整个启动链路摸清楚。我踩过的坑先放前面很多人一上来就想着改 QEMU 源码加自定义设备结果编译一次二十分钟调试全靠 printf效率极低。更合理的路径是先用标准 virt 机器把 RISC-V Linux 跑通再逐步引入 PCIe 设备模型最后才考虑自定义加速器。AGENT 的价值就在于帮你把“跑通”这一步压缩到最短把“改设备”这一步的重复劳动自动化。2. 实验台的整体设计与关键选型思路2.1 为什么选 QEMU 而不是其他模拟器RISC-V 生态里模拟器不少Spike、Renode、QEMU 各有侧重。Spike 是官方 ISA 模拟器指令级精度高但外设模型极其简陋连 PCIe 都没有Renode 擅长多节点系统仿真但 RISC-V AI 芯片常用的 PCIe 端点设备支持不够成熟。QEMU 的优势在于它有一套完整的 PCIe 子系统实现支持 PCIe 枚举、BAR 空间映射、MSI/MSI-X 中断、甚至 PCIe 热插拔。这些恰好是 AI 芯片作为 PCIe 端点设备时最需要验证的功能。另一个关键因素是 QEMU 的virt机器已经内置了 RISC-V 的 AIAAdvanced Interrupt Architecture支持能模拟 IMSIC 和 APLIC这对多核 AI 芯片的中断路由验证至关重要。你不需要从零写中断控制器只需要在设备树里正确描述就行。2.2 AGENT 在环境搭建中的具体分工这里说的 AGENT 不是某个特定产品而是一套自动化代理思路。我把它拆成三个角色环境探测 AGENT自动检测宿主机架构、QEMU 版本、交叉编译工具链是否齐全缺什么补什么。配置生成 AGENT根据目标芯片的 PCIe 拓扑、内存映射、中断号分配自动生成 QEMU 命令行参数和设备树片段。日志分析 AGENT启动失败时自动抓取 QEMU 的-d日志、内核 dmesg、PCIe 枚举记录定位是 BAR 冲突还是中断没连上。这套分工的好处是你改一个芯片参数AGENT 重新生成整套配置不用手动改十几个文件。实测下来从修改参数到重新启动验证时间从半小时压缩到三分钟以内。2.3 实验台的分层架构整个实验台分四层从下到上依次是层级组件作用宿主层x86_64 Linux KVM提供硬件加速QEMU 跑得接近原生速度模拟层QEMU RISC-V virt 自定义 PCIe 设备虚拟出 CPU、内存、PCIe 总线、AI 加速器固件层OpenSBI U-Boot初始化硬件传递设备树引导内核系统层RISC-V Linux 驱动 AI Runtime跑真实负载验证端到端功能这个分层的关键在于每一层都可以独立替换和调试。比如你怀疑是 OpenSBI 的中断初始化有问题可以单独换一个版本重新跑不用动上面的内核和驱动。3. 核心细节解析与实操要点3.1 QEMU 版本选择与编译参数QEMU 对 RISC-V 的支持在 7.0 之后才比较完善特别是 PCIe 相关的特性。我建议直接用 8.2 或更新的版本原因有三一是 8.x 对 AIA 的支持更完整二是 PCIe 热插拔在 8.x 上稳定很多三是 8.x 的virt机器默认支持更多 CPU 扩展。编译时这几个参数必须开./configure \ --target-listriscv64-softmmu \ --enable-kvm \ --enable-debug \ --enable-trace-backendslog \ --disable-werror--enable-debug不是让你去调 QEMU 本身而是让-d日志更详细PCIe 枚举过程能打出每个设备的 vendor ID、device ID、BAR 分配情况。--enable-trace-backendslog配合-trace参数可以追踪特定事件比如pci_config_read、pci_config_write排查枚举失败时非常有用。注意不要用发行版自带的 QEMU版本往往太老PCIe 热插拔和 AIA 支持都不全。自己编译一次后面省心很多。3.2 设备树的关键节点设计设备树是 QEMU 和 Guest 内核之间的契约。对于 AI 芯片实验台设备树里必须准确描述三类信息第一类是PCIe 主机桥。RISC-V virt 机器默认的 PCIe 控制器是pcie-host-generic它的reg属性要覆盖 ECAM 配置空间和 MMIO 窗口。ECAM 大小决定了能挂多少设备一般给 256MB 足够。MMIO 窗口要留足AI 芯片的 BAR 空间往往很大我通常给 1GB 起步。第二类是中断路由。PCIe 设备的 INTx 中断通过interrupt-map映射到 PLIC 或 APLIC。如果芯片用 MSI/MSI-X则需要在msi-parent里指向 IMSIC。这里最容易出错的是中断号冲突建议在设备树里用注释标清楚每个设备占用的中断号。第三类是DMA 一致性。AI 芯片通常需要访问系统内存做数据搬运设备树里的dma-coherent属性必须和硬件设计一致。如果硬件不支持缓存一致性就要在驱动里手动做 cache flush否则数据对不上。3.3 PCIe 设备模型的参数计算假设我们要模拟一块 AI 加速卡它的 PCIe 配置空间需要这几个关键字段Vendor ID / Device ID用自定义值避免和真实设备冲突。我一般用0x1234 / 0x5678这种测试专用值。Class CodeAI 加速器通常归为0x020000网络控制器或0x0B4000协处理器具体看驱动怎么匹配。BAR 大小假设芯片有 64KB 寄存器空间和 4GB 显存窗口那么 BAR0 给 64KBBAR2 给 4GB64 位 BAR。计算 BAR 大小时注意QEMU 要求是 2 的幂次且不能小于 4KB。QEMU 命令行里这样写-device pcie-root-port,idrp0,slot1 \ -device my-ai-accel,busrp0,addr0,\ vendor-id0x1234,device-id0x5678,\ bar0-size0x10000,bar2-size0x100000000pcie-root-port是必须的因为 AI 芯片通常挂在根端口下面而不是直接挂根总线。这样更接近真实拓扑也方便测试热插拔。3.4 启动脚本的自动化编排手动敲 QEMU 命令很容易漏参数我习惯写一个run.sh把变量抽出来QEMUqemu-system-riscv64 KERNELImage INITRDrootfs.cpio.gz BIOSfw_jump.bin MEM8G SMP4 $QEMU -M virt,aiaaplic-imsic \ -cpu rv64,x-vtrue,vlen256 \ -smp $SMP -m $MEM \ -bios $BIOS \ -kernel $KERNEL \ -initrd $INITRD \ -append consolettyS0 root/dev/ram \ -nographic \ -device pcie-root-port,idrp0,slot1 \ -device my-ai-accel,busrp0 \ -d guest_errors,pci_config \ -D qemu.logaiaaplic-imsic开启高级中断架构vlen256给向量扩展留空间AI 负载经常用得上。-d pci_config会把每次配置空间读写打到qemu.log枚举失败时直接搜这个文件。4. 实操过程与核心环节实现4.1 宿主机环境准备先确认宿主机是 x86_64 Linux内核 5.15 以上KVM 模块已加载。用lsmod | grep kvm检查如果没有就modprobe kvm_intel或kvm_amd。交叉编译工具链用riscv64-linux-gnu-前缀的版本Ubuntu 下直接apt install gcc-riscv64-linux-gnu。QEMU 编译安装到/opt/qemu-riscv避免污染系统路径。编译时用-j$(nproc)加速实测 16 核机器大约 8 分钟编完。4.2 构建最小根文件系统用 BusyBox 构建 initramfs 是最快的方式mkdir -p rootfs/{bin,sbin,etc,proc,sys,dev} cd rootfs cp /usr/riscv64-linux-gnu/bin/busybox bin/ ln -s busybox bin/sh cat init EOF #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs none /dev echo RISC-V AI Lab Ready exec /bin/sh EOF chmod x init find . | cpio -o -H newc | gzip ../rootfs.cpio.gz这个 initramfs 只有几 MB启动到 shell 只要两秒。后面要跑 AI 负载时再把 runtime 和模型文件塞进去。4.3 编译 OpenSBI 和内核OpenSBI 用fw_jump.bin就够编译时指定PLATFORMgeneric。内核配置里必须开这几个选项CONFIG_PCIy CONFIG_PCI_HOST_GENERICy CONFIG_PCIE_ECAM_GENERICy CONFIG_PCI_MSIy CONFIG_RISCV_IMSICy CONFIG_AI_ACCELm最后一项是自定义 AI 加速器驱动先编成模块方便反复加载卸载调试。4.4 启动验证与 PCIe 枚举检查启动后第一件事是lspci -vv看 AI 设备有没有被枚举到。正常输出应该能看到1234:5678这个设备BAR 地址已经分配好。如果没看到检查qemu.log里的pci_config记录看是配置空间读返回全 F还是 BAR 分配失败。第二件事是cat /proc/interrupts确认 MSI 中断已经注册。AI 设备的中断号应该出现在列表里且计数会随着驱动加载增加。第三件事是dmesg | grep -i pcie看内核有没有报 BAR 冲突或链路训练失败。QEMU 模拟的 PCIe 链路不会真的训练但内核的枚举逻辑会走一遍任何资源分配问题都会在这里暴露。4.5 跑一个最小 AI 推理负载在 initramfs 里放一个简单的矩阵乘法程序通过/dev/ai-accel提交任务。驱动收到 ioctl 后通过 DMA 把数据搬到设备 BAR 空间触发加速器计算完成后发 MSI 中断。整个过程在 QEMU 里跑一遍能验证从用户态到设备模型的完整链路。实测下来一个 256x256 的矩阵乘法在 QEMU 里大约 200ms 完成比真实硬件慢但功能验证足够了。5. 常见问题与排查技巧实录5.1 PCIe 设备枚举失败最常见的原因是 BAR 大小没对齐。QEMU 要求 BAR 大小是 2 的幂且不能小于 4KB。如果你给bar0-size0x18000QEMU 会直接报错退出。改成0x20000就好了。另一个原因是pcie-root-port的slot参数冲突。每个根端口占一个 slot设备挂在根端口下面用addr区分。如果两个设备用了同一个addr枚举会卡住。5.2 中断收不到先确认msi-parent指向正确。RISC-V virt 机器里 IMSIC 的 phandle 通常是固定的但如果你改了设备树可能对不上。用dtc -I dtb -O dts反编译 QEMU 生成的设备树搜msi-parent看指向哪个节点。如果用的是 INTx 中断检查interrupt-map里的中断号有没有和别的设备冲突。PLIC 的中断号是全局分配的冲突了内核会报irq 15: nobody cared。5.3 内核启动卡在 PCIe 枚举这种情况通常是 ECAM 窗口太小。默认pcie-host-generic的 ECAM 是 256MB能挂 32 个设备。如果你模拟的芯片有多个功能multi-function每个功能占一个设备号很容易超。把 ECAM 调到 512MB 或 1GB。还有一种可能是 MMIO 窗口和内存重叠。检查 QEMU 命令行里的-m和 PCIe MMIO 基地址确保不冲突。RISC-V virt 的 MMIO 通常从0x30000000开始内存从0x80000000开始一般不会撞但自定义机器类型时要留意。5.4 常见问题速查表现象可能原因排查方法lspci 看不到设备BAR 大小非法检查 qemu.log 里的配置空间读驱动加载失败Vendor/Device ID 不匹配对比驱动里的 id_table中断计数不增加MSI 未使能检查 PCI_COMMAND 寄存器的 bit 10DMA 数据错乱缓存不一致确认 dma-coherent 属性启动卡死ECAM 窗口不足增大 ECAM 或减少设备数5.5 独家避坑技巧第一永远保留一份能跑通的最小配置。每次改参数前先备份run.sh和设备树出问题了能快速回滚。我习惯用 git 管理这些配置文件每次改动都有记录。第二QEMU 的-d日志要分级开启。平时只开guest_errors排查 PCIe 问题时再加pci_config否则日志文件几分钟就上 GB。第三设备树里的中断号用宏定义。不要直接写数字用#define AI_ACCEL_IRQ 15这种改的时候一处生效避免漏改。第四AGENT 生成的配置要人工复核一遍。自动化工具再聪明也可能理解错你的意图特别是 PCIe 拓扑这种强约束的东西生成后一定要对着芯片手册核对一遍。6. 实验台的扩展方向与个人体会这套实验台跑通之后扩展空间很大。往小了说可以加更多 PCIe 设备模拟多卡场景验证 PCIe Switch 的枚举和均衡往大了说可以把 QEMU 的设备模型换成 SystemC 或 Verilator 协同仿真让 RTL 和软件栈在同一个环境里联调。我个人在实际操作中的体会是QEMU 实验台的价值不在于跑得多快而在于把“软件能不能跑”这个问题从流片后提前到流片前。很多驱动 bug、设备树错误、中断路由问题在 QEMU 里暴露出来改起来成本几乎为零。等真实芯片回来软件栈已经基本就绪剩下的就是性能调优。最后分享一个小技巧如果你要模拟的 AI 芯片有多个 PCIe 功能比如一个计算功能加一个管理功能用multi-function设备模型比挂两个独立设备更接近真实。QEMU 里通过addr的低三位区分功能号配置空间里Header Type的 bit 7 要置 1。这个细节很多文档不写但实际芯片经常这么设计提前在 QEMU 里验证能省不少事。