公司新项目要用紫光同创的FPGA搭RISC-V软核做控制面,但紫光的ICB总线和标准AXI4协议差异挺大,网上资料少得可怜。我自己写了个转换桥,跑裸机程序没问题,一上Linux系统就偶发死机。想问问各位大佬,ICB转AXI4桥接的outstanding和ID映射怎么处理?有没有现成的参考设计或者国产FPGA移植RISC-V的坑总结?
2026年了,FPGA工程师用国产紫光FPGA做RISC-V软核,ICB总线转AXI4桥接到底怎么设计才不丢性能?
6 个回答

先说结论:你裸机没事、上Linux偶发死机,八成不是AXI4协议本身写错,而是outstanding没处理好导致Linux的DMA/中断路径出现饿死或乱序。ICB本质上是简单的请求-应答握手,没有ID概念,而AXI4的outstanding能力全靠ID区分多个在途事务。你转桥时最省事的做法是:ICB侧不支持多 outstanding,那就在桥里做串行化,即一次只允许一个AXI事务在飞,等它回来再发下一个。这样裸机当然没问题,但Linux下AXI总线上可能同时有CPU、DMA、外设多个master发事务,你桥一旦把它们的请求串行化,某个master长时间拿不到总线就把自己饿死了。所以至少要给每个master映射一个独立ID,然后在桥里用FIFO排队,每来一个请求就分配一个ID,用计数器跟踪在途数量,别超过AXI从设备能接受的max outstanding。紫光这边没有现成文档,我当初是直接参考芯来开源的ICB转AXI桥代码,把它的异步FIFO换成紫光原语,记得紫光的FIFO IP要看是FWFT还是标准模式,两者时序不一样,容易踩坑。另外提醒一点:ID重映射别只做数值搬运,要保证同一master的写响应顺序不能乱,Linux对写响应乱序容忍度比读低很多。你有试过用逻辑分析仪抓AXI的AW和B通道时序吗?如果能抓到死机前最后一笔事务,基本就能定位是饿死还是ID冲突。你现在用的是紫光哪个型号,软核是自研还是移植的蜂鸟E203?这个会影响FIFO深度怎么定。

嗯,其实没那么玄,八成就是outstanding没做,Linux下DMA请求跟CPU请求撞一起就死。桥里放个ID映射表,按master分配ID,深度设成4或者8够用。紫光那个ICB转AXI的例程在官网论坛有人贴过,搜一下就有。

这个问题我前后折腾过两个项目,给你讲一条比较深的线。你现在的症状是裸机OK、Linux偶发死,先说为什么会这样。裸机程序通常访问外设是同步的,CPU发一个读请求就等响应,中间不会有别的master插进来,所以即使你的桥不支持多 outstanding,也只是慢,不会错。但Linux起来后,DMA、网卡、SD控制器都挂在总线上,它们发请求是异步且连续的,你的桥如果每次只转发一个事务,那么当DMA在等数据时CPU又发一个请求,桥就得排队,而DMA中断处理往往依赖CPU先把某个状态寄存器读完,这就形成了环路等待,时间一长就触发watchdog或者page fault,表现为死机。所以解决的核心不是协议转换本身,而是让桥具备同时处理多个master请求的能力,也就是outstanding。做法上,我建议你抛弃那种「来一个请求就同步转发」的写法,改成三段式流水线:第一段是ICB侧接口,把请求打包成统一的内部结构体,里面带上master编号、地址、写数据、写strobe;第二段是ID分配器,每收到一个请求就从这个master的ID池里取一个未使用的ID,同时把master编号和ID的映射存进一个RAM表,等响应回来再查表还原;第三段是AXI4 master接口,按ID把请求发出去,用AW/AR通道的ready信号来控制背压。关键点在于ID池的管理,不能简单用计数器循环,因为master可能同时发多个请求,你要保证同一个master的两个请求拿到的ID不同,但响应回来时还能按原顺序返回——AXI4规范里同一master的写响应必须保序,读响应可以乱序。所以常见做法是每个master单独维护一个FIFO,存空闲ID,请求来了弹出,响应回来推回。紫光这边你还要注意,它家PLL和FIFO IP的复位时序比较讲究,异步FIFO的两端时钟如果相位差较大,建议在RTL里加两级同步器后再做空满判断,否则会有亚稳态导致偶发丢包。另外,如果你不想自己写ID映射,可以看看芯来那个开源桥,它已经把ID重映射和FIFO隔离做进去了,你只需要把它的时钟域交叉部分改成紫光的原语。你提到上Linux才死,我建议你先在桥里加个计数器,统计每个master的在途事务数,跑一段时间看是不是某个master突然飙高,这样能快速定位是DMA还是CPU的问题。顺便问下,你软核跑Linux用的内存是DDR还是SRAM?如果是DDR,还要检查AXI桥有没有做burst合并,不然每次单拍访问DDR性能会很难看。

其实你这个问题,核心不在协议转换本身,而在你给Linux搭的这条路上有没有留出足够的「并发余量」。裸机程序通常是单master、单线程的访问模式,CPU发一个请求就等结果,桥里即使没有outstanding能力,也只是慢一点,不会出错。但Linux起来之后,DMA、网卡、SD控制器都是独立master,它们和CPU的请求会在总线上交错出现,如果你的桥把每个事务都串行化处理,那某个master长时间等不到总线,就会触发超时或者中断丢失,表现出来就是偶发死机。所以设计时第一优先级不是把ICB的握手时序翻译对,而是让桥能同时跟踪多个在途事务。具体做法上,我建议你给每个master映射一个固定ID,在桥里做一个ID到AXI ID的重映射表,然后用一个计数器跟踪当前在途数量,深度至少设到8,有条件就上16。ICB侧没有ID概念,所以你要在桥的入口处给每个请求打上来源标记,比如用master编号作为ID的高位,这样AXI返回时你就能根据ID把数据路由回正确的请求方。另外,如果ICB和AXI时钟域不同,中间一定要用异步FIFO隔离,别图省事直接打拍,否则跨时钟域采样亚稳态会在高负载下随机触发,这种问题最难查。芯来科技开源过一款ICB转AXI的桥接代码,用的就是ID映射加FIFO队列的思路,你可以直接拉下来对着改,比自己从头写快很多。最后提醒一句,调试时别只盯着死机那一刻的波形,建议在桥里加一个计数器统计每个master的等待周期数,如果某个master经常超过几百周期才拿到总线,那基本就是outstanding深度不够或者调度优先级设错了。你现在用的紫光具体是哪款芯片?不同系列的ICB时序细节有差异,这个会影响FIFO深度和ID位宽的选择。

先别急着在协议转换的细节上死磕,你裸机OK、Linux偶发死,这个现象基本能锁定问题不在握手时序,而在你的桥没有把ICB的同步请求模型映射成AXI的异步多事务模型。ICB本质上是一个master发请求、slave回响应的简单同步接口,它天然不带ID,也不支持多个在途事务;而AXI4的性能核心恰恰是outstanding和乱序返回。你上Linux之后,DMA、网卡、SD控制器这些master会同时发起访问,如果桥里只有一个事务在飞,那后到的master就得等前一个完成,一旦某个master等太久,Linux的驱动就会超时,表现为偶发死机。所以设计的重心应该是:给每个master分配一个固定的ID,在桥里做一个ID重映射表,把ICB侧的请求映射成AXI的独立ID,然后用一个计数器跟踪当前在途事务数量,深度至少8,有条件上16。这样多个master的请求可以并行在AXI总线上跑,不会互相阻塞。时钟域方面,如果ICB和AXI不在同一个时钟域,建议用异步FIFO做隔离,读写指针用格雷码同步,避免亚稳态。参考设计的话,芯来科技的开源ICB转AXI桥接代码是很好的起点,虽然是面向蜂鸟E203的,但思路通用,你可以在它的基础上改成紫光FPGA的时序约束。另外提醒一个坑:AXI的写响应通道和读数据通道是独立的,你的桥里如果只处理了请求转发而没把响应路径也做成多路并行,那还是会在响应侧形成瓶颈。建议你把ICB侧的请求和响应分别做成独立的FIFO,这样读写可以并行处理。你现在的偶发死机,大概率是某个master在等响应时被另一个master的请求堵住了,形成环路等待。先检查一下你的桥里有没有对每个master的请求做独立的FIFO排队,如果没有,优先把这个补上,比优化握手时序更关键。你们现在ICB时钟和AXI时钟是同一个吗?如果是异步的,那还得先确认异步FIFO的深度够不够,不然跨时钟域丢数据也会造成偶发异常。

其实核心就一句话:Linux下DMA和CPU的请求会撞车,你的桥必须支持多个在途事务,否则谁等谁就死。给每个master固定ID,深度8起步,再参考芯来的开源桥接改一改。别自己闷头调握手时序,先看ID映射和FIFO。你们ICB和AXI是同频吗?不同频的话异步FIFO也得先搞定。
发表回答
登录后可在本页底部提交回答