
做RISC-V AI芯片开发最恼火的事情不是写算子而是环境。手里没有开发板流片又遥遥无期想验证一个向量指令优化到底行不行总不能每次都去抢服务器。所以我从很早就开始盯着QEMU想把它变成一块RISC-V AI芯片的实验台。最近这几个月我试着让AGENT全程参与环境搭建从生成启动脚本到编写算子验证代码基本跑通了一套流程。这篇就把我的完整做法、踩过的坑和几个关键设计思路写出来给同样想用QEMU做RISC-V AI实验的朋友做个参考。先说清楚这套东西能干什么用QEMU模拟一个RISC-V 64位虚拟平台在上面跑一个精简Linux装好交叉编译工具链然后用RISC-V向量扩展RVV写AI算子比如矩阵乘法、卷积这类最基础的运算再通过QEMU的CPU模拟直接验证指令行为。整个过程不需要真实硬件也不需要昂贵的FPGA开发板一台普通x86电脑就够了。适合三类人刚入门RISC-V想熟悉指令集的做AI加速器软件栈想提前做功能验证的以及准备做芯片前端验证但暂时没有RTL环境的。1. 为什么要用QEMU搭一块RISC-V AI芯片实验台1.1 芯片开发等不起真实流片仿真平台才是常态芯片行业有个很尴尬的错位硬件还没回来软件就得先跑起来。跑在什么上面答案通常是模拟器。QEMU是其中应用最广的一个它不仅能模拟完整的CPU指令集还能模拟串口、网卡、存储控制器这些外设等于把一个可以启动Linux的SoC搬进了软件里。对于AI芯片开发来说QEMU的价值在于它的指令级精确性——它执行RISC-V指令的方式和硬件流水线不完全一样但它能忠实地反映出指令的行为和最终结果这就足够用来验证算子的数学正确性了。过去我身边很多人喜欢用RISC-V官方的Spike模拟器Spike更轻量适合跑裸机程序。但AI算子一旦涉及操作系统、内存管理、多线程调度Spike就不太够用了。QEMU可以完整启动Linux跑起来就是一个真实的“芯片平台”你在里面编译、运行、调试的流程将来在真实硬件上几乎可以原样搬过去。这也是为什么我把QEMU作为实验台的首选。1.2 为什么选RISC-V而不是ARM或x86这和AI芯片的现状直接相关。近年很多AI加速器设计都采用了RISC-V作为控制核心或可编程加速器原因在于RISC-V指令集是开放的你可以在基础指令上扩展自定义指令比如给特定张量运算加一条专用指令而不需要像Arm那样获得架构授权。RISC-V的向量扩展RVV则是做AI算子的天然基础它对标的是ARM的SVE和AVX512但设计上更灵活可配置向量长度从128位一直到2048位这让它在做数据并行计算时有很好的可伸缩性。更现实的一点是我手头很多开源AI编译器比如LLVM、GCC、OpenBLAS都已经支持RISC-V后端。用QEMU搭一个RISC-V环境能直接吃到这些工具链的完整生态AGENT要生成代码的时候也不需要从零写汇编直接基于标准C和向量内置函数就行。1.3 AGENT在这里到底解决什么问题说实话QEMU的手动搭建流程网上已经很多但大部分是“照着敲命令”的教程一旦环境有差异新手就卡死。我引入AGENT不是为了让搭建显得高级而是要解决三个实际问题。第一AGENT能帮我把散落在各个角落的信息汇总成交互式的工作流。比如“生成一个能启动Linux的最小rootfs”正常人可能要查一两小时AGENT可以在几秒内给出可执行的步骤而且它会说明每一步在干什么。第二AGENT能在我报错的时候自动缩小范围。QEMU的报错信息有时候很暧昧AGENT会基于上下文判断是内核参数问题、rootfs缺少init还是virtio设备没驱动这比一遍遍搜索日志高效太多。第三AGENT可以充当“代码生成器 审查者”的角色。它并不总是对的所以我让AGENT生成的每一条命令、每一段代码都必须经过我的理解后才执行这算是人机协作的基本底线。2. AGENT辅助环境搭建的整体设计2.1 把AGENT当成搭档而不是命令生成器很多人用AGENT的方式是“让它给我一条命令”然后复制粘贴出了问题再回来问。我的实践下来更靠谱的方式是先和AGENT一起理清目标再让它给出方案。比如我会这样说我的目标是在一台x86 Ubuntu宿主机上用QEMU启动一个riscv64的Linux系统系统里要能运行C程序并且需要支持RISC-V向量扩展(vlen128)。请帮我拆解出所有需要准备的组件并且标注每一步为什么需要。AGENT给出的回答通常包含QEMU模拟器本身qemu-system-riscv64、RISC-V内核内核镜像vmlinux、启动固件opensbi、rootfs内含最小化busybox或alpine、交叉编译工具链gcc-riscv64-linux-gnu、以及一个启动脚本。这些组件缺一不可。这种和AGENT交互的方式本质上是把一个大任务拆成“组件清单—依赖关系—执行顺序—验证方法”四个部分。AGENT很擅长做这种规划因为它见过的套路很多。但作为使用者我要保持主导权尤其要对AGENT给出的版本号、路径、参数保持警惕。2.2 环境拆分宿主机、QEMU系统模拟、RISC-V工具链、AI算力模拟整套实验台可以看成四层。第一层是宿主机通常是我本地一台装有Linux的x86电脑也可以是云上的服务器。第二层是QEMU系统模拟它提供一个叫virt的虚拟RISC-V机器支持串口、PCIe、virtio设备、中断控制器等足够跑一个完整Linux。第三层是RISC-V工具链我在宿主机的构建阶段用来交叉编译内核和rootfs也可以直接编译我们写的AI算子生成riscv64的可执行文件后丢到QEMU里运行。第四层是AI算力模拟这个最抽象QEMU不像gem5那样能建模AI加速器内部的流水线和存储层次但它能模拟RISC-V的向量扩展指令所以对于“算子在目标架构上能否正确计算出结果”这个问题QEMU是有效的。不要指望QEMU给出性能数据。它的基本模式是二进制翻译不是周期精确的模拟。我做性能预评估时会换用gem5但在日常功能验证上QEMU的启动速度和易用性远远超过gem5所以我把它作为实验台的主平台。2.3 方案对比完整发行版镜像 vs 最小rootfs搭建Linux环境有两条路。一条是直接下载Fedora、Debian等官方提供的riscv64 qcow2镜像启动简单但镜像体积大、内部预装的东西多对于只跑AI算子这种场景显得很笨重。另一条是使用Buildroot或Alpine构建一个最小rootfs体积可以压到十几MB启动时间短干净可控。我最终选择的是最小rootfs。选择最小rootfs的另一个原因是我想让AGENT充分参与构建过程。如果直接用官方大镜像AGENT能做的事情就只剩下“改一改启动参数”这样太无聊也学不到东西。自己构建rootfs时AGENT可以帮我在Buildroot里勾选需要的包、调整内核配置、生成init脚本每一步都能看到反馈。而且最小镜像更容易暴露问题比如缺动态链接器、缺init、缺设备节点这些问题在真实芯片移植中同样会遇到提前踩一遍非常值得。3. 实操从零拉起RISC-V AI虚拟平台3.1 宿主机准备与AGENT工作台配置我用的宿主机是Ubuntu 22.04基本依赖如下sudo apt update sudo apt install -y qemu-system-misc build-essential flex bison \ libncurses-dev libssl-dev device-tree-compiler \ gcc-riscv64-linux-gnu cpio unzip file rsync这里qemu-system-misc包里包含qemu-system-riscv64。如果你想要更新版本的QEMU可以自行编译但apt版本通常足够用。Buildroot用来生成rootfs我下载长期支持版本wget https://buildroot.org/downloads/buildroot-2024.02.3.tar.bz2 tar xf buildroot-2024.02.3.tar.bz2AGENT工作台我用的是命令行CLI环境的配置给它定义了一个系统提示你是RISC-V/QEMU虚拟化专家。你只能使用本机的bash命令和文件访问工具。所有操作必须给出完整命令并标注执行后预期输出。遇到错误时先分析可能原因再给出修复方案。禁止使用本机环境之外的模拟数据。这样把AGENT的活动范围限定在宿主机内避免它凭空捏造已经不存在的文件路径。我还会为每一个子任务单独创建临时目录避免AGENT把乱七八糟的产物搞到系统目录里。3.2 编译最小Linux内核与rootfs我不直接用发行版内核而是用Linux内核官方仓库的sifive/virt相关配置编译一个精简内核。下载内核源码wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.9.tar.xz tar xf linux-6.6.9.tar.xz cd linux-6.6.9生成RISC-V配置make ARCHriscv CROSS_COMPILEriscv64-linux-gnu- defconfig此时内核默认配置已包含virt machine的驱动但我们需要把向量扩展相关的支持打开。运行make ARCHriscv CROSS_COMPILEriscv64-linux-gnu- menuconfig在“CPU features”里确保“Support for the RISC-V Vector Extension”开为y。同时把“VirtIO驱动”全部编入内核避免启动阶段加载模块失败make ARCHriscv CROSS_COMPILEriscv64-linux-gnu- -j$(nproc)构建完成后产物在arch/riscv/boot/Image这就是我们要用的内核镜像。rootfs我用Buildroot来构建。在Buildroot目录下执行make qemu_riscv64_virt_defconfig make -j$(nproc)它会生成output/images/rootfs.ext2和一个启动脚本start-qemu.sh。为了方便我在Buildroot的config里额外开启了BR2_TARGET_ROOTFS_EXT2y并设为ext4格式。生成的rootfs自带busybox和基本的linux工具已经足够用了。如果不想用Buildroot也可以直接下载alpinedisk镜像但后续自定义RVV测试程序的体验会差一些我最后选择的还是Buildroot。3.3 用AGENT生成QEMU启动脚本这一步是我和AGENT协作最密集的部分。我让AGENT根据我刚生成的内核和rootfs路径生成一个启动脚本。AGENT给出如下内容#!/usr/bin/env bash KERNEL./linux-6.6.9/arch/riscv/boot/Image ROOTFS./buildroot/output/images/rootfs.ext2 qemu-system-riscv64 -M virt -m 2G -smp 2 \ -cpu rv64gcsu -nographic \ -kernel $KERNEL -append root/dev/vda rw consolettyS0 earlycon \ -drive file$ROOTFS,formatraw,ifvirtio \ -netdev user,idnet0 \ -device virtio-net-device,netdevnet0这里有几个关键点-cpu rv64gcsu表示CPU支持通用指令集、浮点、原子、压缩、超级用户和用户模式。如果要做AI向量计算我们需要显式指定带V扩展的CPU。QEMU 6.2之后可以直接用-cpu rv64gcv但也要看编译时的支持。我最常用的是-cpu rv64gcv,zbatrue,zbbtrue,zbstrue,vlen128,vext_specv1.0这样CPU就带有完整的RVV 1.0向量扩展向量长度128位指定多个标量位操作扩展方便后面跑向量化的AI内核。3.4 验证RISC-V AI算子一个最小的向量矩阵乘法实验台能不能跑起来最终要看能不能完成一个AI任务。我在QEMU里写一个最简单的SGEMM核心就是单精度矩阵乘法分别用普通三重循环和RISC-V向量扩展实现然后对比结果是否正确。在宿主机上用riscv64交叉编译器编译程序。代码中关键的向量部分是使用RISC-V内置函数通过riscv_vector.h#include riscv_vector.h void sgemm_vec(int M, int N, int K, float *A, float *B, float *C) { for (int i 0; i M; i) { for (int k 0; k K; k) { float av A[i * K k]; vfloat32m4_t a_vec vfmv_v_f_f32m4(av, 8); for (int j 0; j N; j 16) { vfloat32m4_t b_vec vle32_v_f32m4(B[k * N j], 8); vfloat32m4_t c_vec vle32_v_f32m4(C[i * N j], 8); c_vec vfmacc_vv_f32m4(c_vec, b_vec, a_vec, 8); vse32_v_f32m4(C[i * N j], c_vec, 8); } } } }当然这个实现比较粗暴8是vfloat32m4对应的lanes数因为vlen12832位浮点数m4表示4个向量寄存器组合实际上一次可以处理64个32位元素这里我简化了。更严谨的做法是让向量长度自适应用vsetvl动态计算。这样写只是为了说明程序能使用向量指令并且能在QEMU中正常运行。在宿主机交叉编译riscv64-linux-gnu-gcc -O3 -marchrv64gcv -mabilp64d -static \ -o sgemm_test sgemm_test.c注意-static很重要因为QEMU里的rootfs可能缺少动态库静态编译省掉一大堆依赖问题。然后把可执行文件通过-virtfs共享目录方式传进QEMU或者直接挂在rootfs里。我用virtfs-cvirtfs local,path$PWD,mount_taghost0,security_modelnone,idhost0进入QEMU后mount -t 9p -o transvirtio host0 /mnt cd /mnt ./sgemm_test如果看到结果与参考计算完全一致就说明这块“虚拟RISC-V AI芯片”可以正常执行向量AI算子。4. 踩坑记录与排查技巧4.1 AGENT生成的命令和真实环境不匹配这是最常见的坑。AGENT的知识库里可能有不同版本的QEMU、Buildroot、内核补丁给出的命令在我机器上不一定能跑。比如它给我建议过qemu-system-riscv64 -nographic -M virt-canonical-a0这个machine在我用的QEMU版本里根本不存在。解决方式很简单问AGENT之前先让它输出qemu-system-riscv64 -M help的结果让它从本机实际支持的项目中选择而不是凭记忆生成。同样Buildroot的defconfig名称也容易过期我的第一条指令都会让AGENT先列出目录下的configs文件再让它找qemu_riscv64_virt开头的配置。4.2 内核启动卡在init started或者串口无输出如果启动后只看到OpenSBI字符串然后就没有后续大概率是-append里面的console参数不对。RISC-V virt默认串口是ttyS0但有些内核配置会把它命名为ttyS0还是hvc0要看使用的virtio console。解决办法是先不追求earlycon只用consolettyS0试一下。如果还不行进入QEMU的-S模式启动后再用gdb查看PC寄存器位置这个调试方法比较硬核但确实有效。4.3 rootfs无法挂载根文件系统报错信息通常是VFS: Unable to mount root fs on unknown-block(254,0)。这意味着内核中的virtio块驱动没有编进去。我踩过最深的坑是Buildroot生成的rootfs.ext2是raw格式但QEMU的-drive参数如果不指定ifvirtio默认可能走ideRISC-V virt根本没有ide所以必须用-device virtio-blk-device,drivehd0这样的device指定。同时确认内核里CONFIG_VIRTIO_BLKy不能是m因为rootfs阶段还加载不了模块。4.4 从宿主机往QEMU传文件失败使用9p共享目录时经常出现9pnet_virtio: no virtio transport detected。很大原因是在启动参数里没有加载9p内核支持或者rootfs里没有mount命令对9p文件系统的检验。可以在内核menuconfig里开启CONFIG_NET_9Py、CONFIG_9P_FSy以及CONFIG_VIRTIO_9Py。另外Buildroot的BusyBox默认可能没包含9p的mount helper没关系直接mount -t 9p即可。如果不折腾9p也可以用最笨的办法先制作rootfs镜像用guestmount把文件直接写进去再umount或者用virt-make-fs工具。实际项目中我一般直接用Buildroot把需要的测试程序预先拷贝到output/target/root/下重新make镜像一了百了。4.5 向量扩展指令未启用程序非法指令如果你的内核编译时没有开启RVV或者QEMU的CPU没有指定V扩展运行向量指令就会直接报Illegal instruction (core dumped)。排查顺序查看/proc/cpuinfo是否有rv64gcv字符串没有就是CPU指定不对。查看内核启动日志是否包含“enable vector extension”。用gcc -marchnative在QEMU宿主机内部编译一下试试但一般交叉编译环境下没有“native”所以建议我上面那样显式传入-marchrv64gcv。另外一个不太容易注意到的点RISC-V工具链版本要支持vfloat32m4等RVV内置函数。如果gcc版本太老riscv_vector.h都找不到。我用的Ubuntu 22.04的gcc-riscv64-linux-gnu是GCC 11RVV支持还不完整所以后来我改用Sifive的预编译工具链或直接从LLVM项目里安装clang/riscv支持这才解决。5. 让AGENT把验证流程自动化5.1 从“一次搭建”到“可重复实验”手动搭建成功后我想的是怎么把整套流程固化下来。AGENT在这里又能发挥很大作用我让它把之前的所有命令整理成一个Makefile或者shell脚本集合all: qemu kernel: make -C linux-6.6.9 ARCHriscv CROSS_COMPILEriscv64-linux-gnu- -j$(nproc) rootfs: make -C buildroot -j$(nproc) qemu: kernel rootfs ./start-qemu.sh test: riscv64-linux-gnu-gcc -O3 -marchrv64gcv -o sgemm_test sgemm_test.c ./start-qemu.sh -append root/dev/vda rw consolettyS0 -virtfs ...这样以后每次改一行代码只需要执行make testQEMU就会自动启动并运行里面的测试程序。AGENT还帮我在脚本里加入了超时保护防止QEMU里程序死循环导致整个CI卡死。5.2 验证矩阵乘法正确性我在测试程序里加入了随机数据和参考结果校验算法是这样的随机初始化A、B矩阵避免全零导致误判。用标准三重循环计算参考C_ref。用向量版本计算C_vec。比较C_ref和C_vec每个元素误差小于1e-4视为通过。AGENT帮我生成了这部分代码并主动加了一个细节由于浮点加法顺序不同参考结果和向量结果会有微小误差所以比较时使用相对误差阈值而不是绝对相等。这个建议很关键避免了误判。在QEMU里运行后输出类似Vector SGEMM: PASS diff max 6.8e-08就说明整个链路是通的。5.3 小技巧把AGENT的结论和log保存下来我在每个核心步骤都记录下AGENT当时给出的关键结论以及实际执行结果比如QEMU版本、内核config、Buildroot config、启动命令。这些记录在后续换机器、升级版本时非常有用。AGENT是一个会遗忘的“外置大脑”你只要每次都把上下文重新喂给它它又能很快重建认知但如果我把关键决策点写进了项目README那么即使换一个AGENT工具也能无缝继续。6. 这套实验台的边界和后续扩展6.1 QEMU模拟AI芯片的边界在哪必须坦白说QEMU不是万能的。它虽然支持RVV指令集模拟但无法模拟矩阵单元、脉动阵列、片上SRAM带宽这些AI芯片特有的硬件结构。如果你想验证一个自定义的AI指令QEMU也可以通过修改源码来支持那工程量会很大。更合适的做法是使用QEMU配合插件或者转向gem5这类体系结构模拟器。另外QEMU的向量指令性能不能作为参考。它运行一条RVV指令可能要翻译成多条宿主机指令实际速度比真实硬件慢很多。我在QEMU上只做正确性验证性能验证我会用快速的纯C模拟器做一个粗略的周期计数更精确就需要gem5了。尽管如此QEMU作为整机环境的价值依然不可替代它的外设模型、Linux生态、GDB调试支持让它成为“可拼装芯片平台”的最佳底座。我后面计划把QEMU与一个简单的自定义NPU模型通过virtio连接模拟CPU发送指令给NPU的完整流程这块工作就是基于现在已经搭好的实验台继续延展。6.2 从QEMU实验台到真实RISC-V芯片很多朋友问在QEMU上验证的算子能不能无缝跑到真实芯片上。答案是基本可以但有前提。你要确保编译时使用的-march和真实芯片支持的指令集完全一致。比如真实芯片可能只支持rv64gc不支持向量扩展那么你在QEMU里用v算子跑出来的代码到了真芯片上就会非法指令。所以我会在项目里维护一个统一的CPU配置头文件AGENT也必须遵循这个配置去生成代码。真实芯片还有cache一致性、内存访问延迟等特性这些QEMU无法模拟。但算法思路、指令选择、编译器优化level都是可以直接迁移的经验。也就是说实验台的价值主要是“预演软件架构”而不是“预测硬件性能”。6.3 下一阶段我会怎么改进这套流程我当前用的是命令式AGENT后续打算把AGENT接入一个自建的知识库把我们团队积累的RISC-V踩坑记录、QEMU编译参数、算子优化模式全部喂进去让AGENT的初始上下文更接近真实项目。同时准备增加一个自动化的回归测试每天定期在QEMU上跑一遍AI算子验证一旦发现宿主机升级导致虚拟环境变更立刻报警。这块实验台虽然现在还比较朴素但我个人觉得它有潜力成为团队里一个低成本AI芯片软件验证平台每个工程师都能在几分钟内拉起一个虚拟riscv64环境不再为硬件资源排队。如果你也想试试建议从今天的QEMU启动脚本开始先跑通一个hello world再尝试跑一个向量加法然后一步步扩展成你自己的AI算子实验场。