ARTICLE DETAIL

资讯详情

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

用Agent自动化搭建RISC-V QEMU PCIe实验台

用Agent自动化搭建RISC-V QEMU PCIe实验台 1. 为什么我要用 Agent 来搭 RISC-V 实验台先说结论我最近在折腾一块 RISC-V 架构的 AI 芯片验证环境核心思路是用一个 Agent 去驱动 QEMU把整个拉起实验台的过程自动化。听起来有点绕但拆开看其实很朴素——QEMU 负责模拟一台带 PCIe 总线的 RISC-V 机器Agent 负责把装系统、配网络、挂设备、跑测试这一长串动作串起来人只需要在最后看结果。为什么不用现成的脚本一把梭因为脚本是死的环境是活的。QEMU 启动参数、PCIe 设备挂载顺序、内核镜像版本、根文件系统格式任何一个环节变了脚本就得改。而 Agent 的价值在于它能看着办——根据当前环境状态决定下一步做什么失败了能自己换个思路重试这在我反复重建实验台的过程中省了太多事。这篇文章适合三类人看一是手上有 RISC-V 或 AI 加速卡验证需求、想搭一套可复现实验环境的工程师二是对 QEMU 模拟 PCIe 设备感兴趣、想搞清楚设备是怎么被枚举出来的同学三是想了解 Agent 到底能干什么、怎么和底层工具链结合的开发者。我会把整个搭建过程、踩过的坑、参数怎么算、为什么这么选全部摊开讲。需要提前说明的是我用的 QEMU 是较新的版本RISC-V 的 virt 机器类型对 PCIe 的支持已经比较完整能模拟出 ECAM 配置空间、支持设备热插拔这对验证 AI 芯片的 PCIe 接口行为很关键。Agent 部分我用的是基于工具调用tool calling的架构让它能执行 shell 命令、读文件、判断返回结果本质上是一个会自己决策的自动化脚本。2. 整体设计思路Agent 和 QEMU 到底怎么分工2.1 为什么是 Agent 而不是纯脚本我一开始也是写 bash 脚本的一个run.sh里塞了二十几行 QEMU 参数再加一堆if-else判断镜像存不存在、端口有没有被占用。问题很快就来了QEMU 启动是异步的脚本sleep 5之后去连串口有时候机器还没起来有时候已经起来了但 SSH 还没就绪。我试过把 sleep 加到 15 秒结果每次重建环境都要干等效率极低。Agent 的介入点就在这里。我把启动 QEMU定义成一个工具把检查串口是否有登录提示定义成另一个工具Agent 的逻辑是启动后循环调用检查工具直到看到登录提示或者超时。这样等待时间从固定的 15 秒变成了实际需要的 3 到 8 秒而且不会因为机器慢就误判失败。更关键的是错误恢复。有一次我挂载的 PCIe 设备数量超过了 QEMU 默认的 bus 容量QEMU 直接报错退出。脚本模式下这就是个死局得人工去看日志改参数。Agent 模式下我给它预设了如果启动失败且日志里出现 bus 相关错误就减少一个设备重新启动的规则它自己就把问题绕过去了。2.2 QEMU 模拟 RISC-V 的关键能力盘点要搭 AI 芯片的实验台QEMU 得满足几个硬性条件我在选型时逐个确认过能力项具体要求QEMU 支持情况CPU 架构RV64GC支持向量扩展更佳virt 机器默认支持可加-cpu rv64,vtruePCIe 总线需要 ECAM 传统配置机制virt 机器内置 PCIe host bridge设备热插拔验证 AI 卡动态上下线支持通过 QMP 接口操作内存映射大 BAR 空间给 AI 卡可配置需注意地址窗口中断控制MSI/MSI-X 支持支持AI 卡常用 MSI-X这里重点说 PCIe。AI 芯片通常通过 PCIe 接口和主机通信验证环境必须能真实模拟枚举过程、BAR 空间分配、中断触发。QEMU 的 virt 机器提供了一个通用的 PCIe host bridge可以挂载多种模拟设备也能通过-device参数自定义 vendor ID 和 device ID这对模拟一块还没流片的 AI 卡特别有用。2.3 Agent 的架构选择工具调用型最实用Agent 的架构五花八门有基于规划planning的、有基于反思reflection的、有多 Agent 协作的。我这个场景不需要那么复杂工具调用型tool calling就够了。核心就三件事工具注册把 shell 执行、文件读写、串口交互封装成 Agent 能调用的函数状态判断Agent 根据工具返回结果决定下一步比如看到 login: 就认为启动成功循环控制设置最大重试次数和超时避免 Agent 陷入死循环我试过用多 Agent 的方案一个负责启动、一个负责测试结果两个 Agent 之间的状态同步反而成了新问题。单 Agent 加清晰的工具边界在这个场景下更稳。3. 环境准备从零把工具链装齐3.1 基础依赖安装与版本选择我用的宿主环境是 Ubuntu 22.04QEMU 版本选的是 8.2。为什么不用系统自带的 apt 版本因为 Ubuntu 22.04 仓库里的 QEMU 是 6.2RISC-V 的 PCIe 支持有 bug挂载多个设备时枚举会乱序。8.2 是我实测下来最稳的版本PCIe 枚举顺序和真实硬件一致。编译安装 QEMU 的命令如下关键是--target-list只编 RISC-V省一半编译时间wget https://download.qemu.org/qemu-8.2.0.tar.xz tar xf qemu-8.2.0.tar.xz cd qemu-8.2.0 ./configure --target-listriscv64-softmmu --enable-debug make -j$(nproc) sudo make install--enable-debug是我强烈建议加的后面排查 PCIe 枚举问题时QEMU 的调试日志能直接告诉你设备在哪个地址被扫描到比猜快得多。交叉编译工具链我用的是riscv64-linux-gnu-系列装完确认一下sudo apt install gcc-riscv64-linux-gnu riscv64-linux-gnu-gcc --version3.2 内核与根文件系统的准备RISC-V 的 Linux 内核我建议直接用主线版本编译配置里必须打开这几个选项否则 PCIe 设备认不出来CONFIG_PCIy CONFIG_PCI_HOST_GENERICy CONFIG_PCIE_DWy CONFIG_VIRTIO_PCIy CONFIG_SERIAL_8250y CONFIG_SERIAL_8250_CONSOLEy编译命令make ARCHriscv CROSS_COMPILEriscv64-linux-gnu- defconfig make ARCHriscv CROSS_COMPILEriscv64-linux-gnu- menuconfig make ARCHriscv CROSS_COMPILEriscv64-linux-gnu- -j$(nproc)根文件系统我用 Buildroot 生成比手搓 initramfs 省事。Buildroot 里选riscv64架构加上dropbear做 SSH、pciutils做lspci验证生成的 rootfs 大概 40MB启动快。注意Buildroot 默认的 RISC-V 配置可能没开 PCIe 相关的用户态工具一定要手动勾上pciutils和libpci否则进了系统连lspci都没有排查问题会很痛苦。3.3 Agent 运行环境的搭建Agent 我用 Python 写依赖就三个openai调用模型、pexpect串口交互、pyyaml读配置。为什么用 pexpect 而不是 pyserial因为 QEMU 的串口可以重定向到 stdiopexpect 直接管进程的输入输出就行不用额外开虚拟串口设备简单。pip install openai pexpect pyyamlAgent 的配置文件我单独放一个agent_config.yaml把 QEMU 路径、内核镜像、rootfs、PCIe 设备列表都写进去这样换环境只改配置不改代码。4. 核心实操一步步把实验台拉起来4.1 QEMU 启动参数的逐项拆解先看一条完整的启动命令我逐段解释qemu-system-riscv64 \ -M virt,pcion,acpion \ -cpu rv64,vtrue \ -smp 4 \ -m 4G \ -kernel Image \ -drive filerootfs.ext4,formatraw,idhd0 \ -device virtio-blk-pci,drivehd0 \ -device virtio-net-pci,netdevnet0 \ -netdev user,idnet0,hostfwdtcp::2222-:22 \ -device pcie-root-port,idrp0,slot1 \ -device pcie-root-port,idrp1,slot2 \ -nographic \ -serial mon:stdio-M virt,pcion,acpion里的acpion很关键AI 芯片的驱动经常依赖 ACPI 表来获取 BAR 地址和中断号不开 ACPI 的话驱动可能加载失败。-cpu rv64,vtrue打开向量扩展AI 推理的算子很多用到了 RVV。-device pcie-root-port这两行是给 AI 卡预留的插槽。为什么要显式建 root port 而不是直接挂设备因为真实 AI 卡是插在 root port 下面的验证热插拔时需要在 root port 层面操作直接挂到 host bridge 上模拟不出真实的拓扑。4.2 Agent 驱动启动的完整流程Agent 的主循环逻辑我用伪代码表示def bring_up_lab(): proc start_qemu(config) for i in range(MAX_RETRY): output read_serial(proc, timeout2) if login: in output: login(proc) return True if Kernel panic in output: analyze_panic(output) proc restart_qemu_with_fix(config) return Falseread_serial用 pexpect 实现每次读 2 秒读不到就继续循环。这里有个细节pexpect 的expect默认会阻塞我用timeout2加异常捕获来实现非阻塞轮询避免 Agent 卡死。登录之后Agent 会自动执行一串验证命令lspci -vvv cat /proc/interrupts ls /sys/bus/pci/devices/lspci -vvv的输出 Agent 会解析确认每个 PCIe 设备的 BAR 地址、中断模式MSI-X 还是 INTx、链路状态。如果发现某个设备 BAR 没分配Agent 会记录并尝试重新枚举。4.3 PCIe 设备模拟与枚举验证模拟一块 AI 卡我用的是 QEMU 的-device自定义参数-device pcie-root-port,idrp0,slot1 \ -device x-pcie-ai-card,vendor-id0x1234,device-id0x5678,bar0-size16Mx-pcie-ai-card是我基于 QEMU 的pci-testdev改的一个简单设备模型主要改了三处vendor/device ID 换成 AI 卡厂商的、BAR0 大小从默认的 4K 改成 16MAI 卡寄存器空间大、加上 MSI-X 中断支持。枚举验证的关键是看lspci能不能正确识别以及 BAR 地址有没有冲突。我遇到过 BAR 分配失败的情况原因是 root port 的bus号范围没配好两个设备的 BAR 落在了同一个地址窗口。解决办法是在 root port 上加bus-reserve参数给每个 port 预留独立的 bus 号段。实操心得QEMU 的 PCIe 枚举日志可以通过-d pci打开输出会告诉你每个设备的 bus/device/function 号和 BAR 分配结果。这个日志在排查设备认不出来时是救命稻草比在 guest 里瞎猜快十倍。4.4 热插拔功能的验证方法AI 芯片验证里热插拔是必测项。QEMU 通过 QMPQEMU Machine Protocol接口支持设备热插拔Agent 可以通过 socket 发 QMP 命令def hotplug_device(qmp_socket, device_id): cmd { execute: device_add, arguments: { driver: x-pcie-ai-card, id: device_id, bus: rp0 } } send_qmp(qmp_socket, cmd)热插拔之后guest 里应该能看到dmesg输出新设备被枚举lspci多出一个条目。我实测下来从发 QMP 命令到 guest 识别到设备延迟大概 200 到 500 毫秒取决于 guest 内核的轮询频率。拔出的命令是device_del但要注意如果 AI 卡的驱动还在使用设备直接拔会导致内核 oops。正确的做法是先在 guest 里echo 1 /sys/bus/pci/devices/.../remove再发 QMP 的device_del。Agent 里我把这个顺序固化成规则避免手滑。5. 踩坑记录与问题排查速查5.1 PCIe 枚举失败的三种典型情况我前后搭了七八次环境PCIe 枚举失败遇到过三种现象根因解决lspci 看不到设备root port 没建或 bus 号冲突检查-device pcie-root-port参数BAR 分配为 0地址窗口不够加大-m或调整 BAR 大小设备出现但驱动不加载vendor/device ID 不匹配确认驱动里的 ID 表和 QEMU 参数一致第一种最常见尤其是从别人那里抄的启动命令root port 的slot号可能和已有设备冲突。QEMU 启动时会报slot already occupied但如果你用了-nographic这个错误可能被串口输出淹没得仔细看启动日志的前几行。5.2 Agent 卡死与超时处理Agent 最怕的是卡死。我遇到过两次一次是 pexpect 的expect在等一个永远不会出现的字符串另一次是 QMP socket 连接后没设超时QEMU 挂了 Agent 还在等。解决办法是给所有阻塞操作加超时并且用signal.alarm做全局兜底import signal def timeout_handler(signum, frame): raise TimeoutError(Agent operation timed out) signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(300) # 5分钟全局超时全局超时设 5 分钟因为 QEMU 启动加内核引导最慢也就 2 分钟留一倍余量。超时后 Agent 会 dump 当前串口输出和 QEMU 进程状态方便事后分析。5.3 常见问题速查表问题排查命令可能原因QEMU 启动即退出qemu-system-riscv64 ... 21 | head -20参数错误或镜像路径不对串口无输出检查-nographic和-serial是否冲突串口被重定向到别处SSH 连不上ssh -p 2222 rootlocalhost -vhostfwd 端口被占用或 sshd 没起热插拔无反应QMP 里query-pci看设备状态设备没挂到正确的 bus内核 panic串口日志最后 50 行根文件系统挂载失败或驱动问题独家技巧QEMU 的-d参数可以同时开多个调试项比如-d pci,guest_errors,unimpguest_errors会打印 guest 访问非法地址的操作unimp会打印未实现的指令。这两个在调试 AI 卡驱动时特别有用因为驱动可能用了 QEMU 还没模拟的 PCIe 特性。6. 关于 Agent 与 QEMU 结合的一些个人体会这套环境我用了大概两个月最大的感受是Agent 不是让事情变简单了而是让事情变得可复现了。以前每次重建环境总有几个步骤是靠记忆和手速现在全部固化在 Agent 的工具调用里换台机器跑一遍结果一模一样。另一个体会是关于 PCIe 模拟的边界。QEMU 能模拟枚举、BAR 分配、MSI-X 中断但模拟不了真实的链路训练、信号完整性、AER 报错的具体时序。所以这套环境适合验证驱动的枚举逻辑和基本通信真要测链路稳定性还得上 FPGA 或者真实硬件。我一般用 QEMU 做快速迭代用硬件做最终验证两者互补。最后分享一个小技巧Agent 的日志一定要结构化存储我用 JSON Lines 格式每条记录包含时间戳、工具名、参数、返回结果。这样出问题时可以直接grep或者写脚本分析比翻纯文本日志高效得多。这个习惯是从一次连续三天的调试中逼出来的当时没有结构化日志全靠肉眼在几千行输出里找异常效率低到怀疑人生。
返回列表