☰
AXI4协议实战:从机写通道RTL实现与仿真验证
2026/10/29 12:11:49 网站建设 项目流程

简介:这份资源围绕AXI4总线协议展开,面向正在学习SOC设计与AMBA总线体系的工程师及高校学生,帮助读者系统梳理AXI4的通道结构、信号定义与握手机制等核心知识点。压缩包内共1个docx文档,约3.27MB,内容以图文结合的方式组织,便于按章节查阅与做笔记。文档从五个独立通道入手,逐一说明写地址、写数据、写回应、读地址、读数据通道的信号方向与作用,并重点讲解VALID与READY的握手时序、ID标签与乱序收发的关系,以及突发传输、非对齐传输、outstanding地址等特性,还结合ZYNQ中ARM与FPGA交互的实例加以说明。目前已有425人学习,适合作为总线协议入门与查阅的参考材料。

1. 一份能直接跑仿真的 AXI4 协议拆解包,到底值不值得下

做 FPGA 或 SoC 验证的朋友大概都经历过这种场面:手册翻了三遍,五个通道的信号表背得滚瓜烂熟,可真让你写一个 AXI4 从机,awready那一行always块敲下去就开始心虚——这样写对吗?outstanding 怎么算?WLAST 到底跟谁同步?我见过太多人卡在“看得懂协议、写不出 RTL”这道坎上。这份来自网络精选的 AXI4 总线协议资料包,价值不在于又复述了一遍 AMBA 手册,而在于它把“通道信号定义 → 握手机制 → 从机写过程 RTL 实现 → VIP 仿真验证”这条链路完整串了起来,还附带了可运行的 RTL 设计与仿真环境。它适合两类人:一是刚接触 AXI4、需要一份能照着敲的入门范本的验证新手;二是写过 AHB/APB、想搞清楚 AXI4 通道分离和乱序机制到底怎么落地的一线工程师。下面我按自己拆包复现的顺序,把这份资料里真正能抄作业的部分和几个血泪坑摊开讲。

2. 五个通道与信号位宽:先把端口表读成一张地址映射图

2.1 通道分离带来的不是复杂度,是流水线自由度

很多人第一次看 AXI4 信号表会觉得“怎么这么多信号”,其实把读写两列并排放在一起看,规律非常明显:写地址通道(AW)和读地址通道(AR)几乎一一对应,写数据通道(W)和读数据通道(R)对应,唯独写回应(B)是单独拎出来的。资料里对“为什么 B 通道独立”给了一个挺有意思的解释:写回应是以传输任务为单位回应的,一次 16 拍的突发写只在最后一拍给一个 BRESP,使用频率是 1/16;而读回应是跟着每一拍读数据走的,16 拍读就有 16 个 RRESP,因为它承载的是从机对每一拍数据的状态反馈(OKAY/EXOKAY/SLVERR/DECERR)。这个视角比单纯说“数据流向相反”更有工程说服力,也直接解释了为什么 RRESP 塞在 R 通道里而 BRESP 要独立。

通道分离真正的收益在于:地址通道和数据通道没有固定相位关系,主机可以在没收到回应前连续发多个地址,这就是 outstanding 的物理基础;不同 ID 的任务完成顺序可以不同,这就是乱序收发的物理基础。理解这一点,后面写 RTL 时你才知道哪些“看起来可以省”的信号其实不能省。

2.2 位宽不是随便定的,每个都对应一个地址空间决策

资料里对位宽的选择讲得比较实在,我整理成一张表,方便你直接对着改参数:

信号组资料推荐位宽决策依据
AWID/BID/ARID/RID4 bit(模块参数)标识传输任务序号,同 ID 顺序执行,不同 ID 可乱序
AWADDR/ARADDR8 bit(模块参数)决定从机地址映射区大小,8 位即 64 个 32 位寄存器
WDATA/RDATA32 bit常用总线位宽,等于每个地址下的寄存器大小
WSTRB4 bit每 bit 对应 8 bit 数据段,32 位总线正好 4 位
AWLEN/ARLEN8 bitINCR 下 1~256 拍,LEN=0 表示 1 拍
AWSIZE/ARSIZE3 bit字节数 = 2^AxSIZE,3 位覆盖 1~128 字节
AWBURST/ARBURST2 bitFIXED/INCR/WRAP 三种类型
AWCACHE/ARCACHE4 bit存储类型,Device/Normal/Write-through/Write-back 组合
AWPROT/ARPROT3 bit特权/安全/指令数据三类访问属性
AWQOS/ARQOS4 bit服务质量,值越高优先级越高,手册未固定用途
AWREGION/ARREGION4 bit区域标记,可标 16 个区域,最小分割单元 4KB

这里有个容易被忽略的硬约束:一个 burst 传输不能跨越 4KB 边界。因为主机寄存器块的最小分块单元是 4KB,每个从机起始映射地址都对齐在 4KB 边界上,一旦 burst 越过0xXXXX_X000这条线,就可能把数据错误写进隔壁从机的地址空间。写 RTL 时如果地址自增逻辑没做边界保护,仿真里可能看不出来,上板就是玄学错误。

2.3 从端口声明反推地址映射

资料给的实现思路很实用:先声明端口,S2M 方向设 output,M2S 方向设 input,然后从 output 信号入手倒推中间信号。以 8 位地址、32 位数据为例,从机实际就是 64 个 32 位寄存器,地址0x00到0xFC按 4 字节步进。你在写awready之前,心里得先有这张映射图,否则后面WSTRB的片选逻辑根本没法写。这一步别急着敲代码,先在纸上把“哪个地址对应哪个寄存器、哪些地址是保留区”画清楚,能省掉后面一半的调试时间。

3. 握手机制与写过程 RTL:从 awready 那一行开始往下推

3.1 VALID/READY 的三种时序与通道间顺序约束

握手的本质很简单:VALID 和 READY 同时为高的那个时钟上升沿,握手成功。按谁先拉高分三种情况——VALID 先到、READY 先到、同时到,协议对这三种都接受,所以从机设计里“等 VALID 来了再拉 READY”和“READY 常拉高”都是合法实现,区别只在吞吐和缓冲策略。

真正需要记牢的是通道间的顺序约束,资料用单箭头/双箭头描述得很清楚:

  • 读传输:ARVALID 与 ARREADY 无指定先后;但 RVALID 必须在 ARVALID 和 ARREADY 都有效之后才能拉高;RVALID 与 RREADY 之间无指定先后。
  • 写传输:AWVALID/AWREADY 与 WVALID/WREADY 四者之间无必须的时间前后关系;但 BVALID 必须在写地址通道握手完成、写数据通道握手完成且 WLAST 有效之后才能拉高;BREADY 可在 BVALID 前后任意时刻有效。

翻译成 RTL 语言就是:写回应不能抢跑,必须等地址和数据都握完、且最后一拍数据到了才发。这条约束是后面写bvalid逻辑的直接依据。

3.2 一个能跑的最小从机写过程实现

资料里从awready入手的那段代码是整份资源的“可抄作业”核心,我把它补全成一个带注释的完整写通道骨架,你可以直接放进自己的仿真环境里跑:

// AXI4 从机写通道最小实现,参数化 ID/ADDR/DATA 位宽 module axi4_slave_wr #( parameter ID_W = 4, parameter ADDR_W = 8, parameter DATA_W = 32 )( input wire aclk, input wire aresetn, // 写地址通道 input wire [ID_W-1:0] awid, input wire [ADDR_W-1:0] awaddr, input wire [7:0] awlen, input wire [2:0] awsize, input wire [1:0] awburst, input wire awvalid, output reg awready, // 写数据通道 input wire [DATA_W-1:0] wdata, input wire [DATA_W/8-1:0] wstrb, input wire wlast, input wire wvalid, output reg wready, // 写回应通道 output reg [ID_W-1:0] bid, output reg [1:0] bresp, output reg bvalid, input wire bready ); // 地址与数据握手完成标志,用于门控 bvalid reg addr_done, data_done; // awready:VALID 到来后拉高一拍完成握手 // 注意:这里未做 outstanding 缓冲,一次只能接一个写地址 always @(posedge aclk or negedge aresetn) begin if (!aresetn) awready <= 1'b0; else if (awvalid && !awready) awready <= 1'b1; else awready <= 1'b0; end // 记录写地址握手完成 always @(posedge aclk or negedge aresetn) begin if (!aresetn) addr_done <= 1'b0; else if (awvalid && awready) addr_done <= 1'b1; else if (bvalid && bready) addr_done <= 1'b0; // 回应完成后清标志,准备下一次 end // wready:与 awready 类似,VALID 到来后拉高一拍 always @(posedge aclk or negedge aresetn) begin if (!aresetn) wready <= 1'b0; else if (wvalid && !wready) wready <= 1'b1; else wready <= 1'b0; end // 记录最后一拍写数据握手完成 always @(posedge aclk or negedge aresetn) begin if (!aresetn) data_done <= 1'b0; else if (wvalid && wready && wlast) data_done <= 1'b1; else if (bvalid && bready) data_done <= 1'b0; end // bvalid:地址和数据都握完才拉高,符合协议顺序约束 always @(posedge aclk or negedge aresetn) begin if (!aresetn) begin bvalid <= 1'b0; bid <= {ID_W{1'b0}}; bresp <= 2'b00; // OKAY end else if (addr_done && data_done && !bvalid) begin bvalid <= 1'b1; bid <= awid; // 回应 ID 必须与写地址 ID 一致 bresp <= 2'b00; end else if (bvalid && bready) bvalid <= 1'b0; end endmodule

逻辑说明:awready和wready都采用“VALID 到、READY 拉高一拍”的保守策略,牺牲一点吞吐换实现简单,适合入门验证。addr_done和data_done两个标志是bvalid的门控条件,严格对应协议里“BVALID 必须在写地址握手、写数据握手且 WLAST 有效之后”的约束。bid直接取awid,因为 AXI4 取消了 WID,写通道的 ID 只能来自写地址通道,这一点资料里专门纠正了 AXI3 的旧认知,是个容易翻车的细节。

参数说明:ID_W决定能区分多少个并发写任务,入门验证 4 位够用;ADDR_W决定从机地址空间,8 位对应 64 个寄存器;DATA_W固定 32 位时wstrb正好 4 位。如果你要做 outstanding 缓冲,需要把awready改成基于 FIFO 余量的组合逻辑,而不是现在这种单拍脉冲。

3.3 WSTRB 的两种典型用法:窄传输与非对齐传输

WSTRB是新手最容易写错的地方。资料给了两个场景:一是 Narrow Transfer,当 burst size 小于总线位宽时(比如 32 位总线上传 8 位数据),用4'b0001 → 4'b0010 → 4'b0100 → 4'b1000循环选通;二是 Unaligned Transfer,起始地址0x07这种没对齐到 4 字节边界的传输,第一拍只写高字节,后续拍再补齐。写从机寄存器时,wstrb的每一位对应wdata的一个字节,wstrb[3]管wdata[31:24],这个映射关系在写case语句时一定要对齐,否则仿真波形看着数据对、实际寄存器写进去是错位的。

4. 突发类型与回环地址计算:WRAP 那套公式别硬背,拿笔算一遍

4.1 FIXED/INCR/WRAP 的适用边界

三种 burst 类型里,FIXED 是对同一地址反复写,常用于 FIFO 推入;INCR 是地址自增,最常用,1~256 拍;WRAP 是回环,地址涨到边界后回到起始位置继续,只允许 2/4/8/16 拍。资料里 WRAP 的公式列了一串,看着吓人,其实核心就三行:

  • 对齐地址Aligned_Addr = (Start_Addr / Number_Bytes) * Number_Bytes
  • 回环界Wrap_Boundary = (Start_Addr / (Number_Bytes * Burst_Len)) * (Number_Bytes * Burst_Len)
  • 地址涨到Wrap_Boundary + Number_Bytes * Burst_Len时,当前拍地址回到Wrap_Boundary,下一拍从Start_Addr + (N-1)*Number_Bytes - Number_Bytes*Burst_Len继续

拿资料里的例子验算:起始0x02,size=2 字节,len=8 拍。Number_Bytes=2,Burst_Len=8,Wrap_Boundary = (2/(2*8))*16 = 0,所以地址序列是0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x00,第 8 拍回到 0 而不是 0x10。这个例子我建议你手算一遍再写 RTL,因为 WRAP 的边界判断如果写成addr >= Wrap_Boundary + Burst_Len*Number_Bytes就会多走一拍,仿真里表现为最后一拍地址越界,属于典型的“公式抄对了、比较符写错了”。

4.2 用 VIP 验证从机:先看波形再谈覆盖率

资料提到用 VIP 验证设计,但没展开。我的做法是:先把上面那个从机挂到一个 AXI4 master VIP 下,跑一个 INCR 8 拍写、一个 WRAP 4 拍写、一个带wstrb部分选通的窄传输,三个用例过了再谈覆盖率。看波形时重点盯三处:awvalid && awready和wvalid && wready是否都在bvalid之前完成;bvalid拉高时bid是否等于awid;wlast是否和最后一拍wvalid同步。这三处对了,写通道基本就立住了。读通道可以照同样的骨架推,把rvalid的门控条件换成arvalid && arready即可,rlast跟最后一拍rvalid同步。

5. 避坑与排查:五个我复现时真踩过的坑

5.1 现象:仿真里 bvalid 永远不拉高

原因:data_done只在wvalid && wready && wlast时置位,但入门实现里wready是单拍脉冲,如果 VIP 的wvalid在wready拉高那拍恰好没保持,握手就丢了。解决:把wready改成在awvalid握手后常拉高直到wlast握手完成,或者用 skid buffer 缓存一拍,别用单拍脉冲硬扛。

5.2 现象:写数据写进了隔壁寄存器

原因:地址自增没做 4KB 边界保护,INCR 长 burst 越过了0xXXXX_X000。解决:在地址自增逻辑里加if (next_addr[11:0] == 12'h000 && 已过首拍) 报错或截断,仿真阶段就把越界用例加进去,别等上板。

5.3 现象:WRAP 传输最后一拍地址跳到 0x10

原因:边界比较用了>=而不是==,或者Wrap_Boundary算错。解决:按 4.1 的公式手算一遍期望序列,写个$display把每拍地址打出来对,比看波形快。

5.4 现象:bid 和 awid 对不上,乱序场景下回应错位

原因:AXI4 取消了 WID,写通道 ID 只能来自 AW 通道,如果从机内部用 W 通道的某个信号去拼 bid 就会错。解决:bid 严格锁存 awid,且 outstanding 大于 1 时要用 FIFO 按序存 ID,回应时按完成顺序取。

5.5 现象:WSTRB 部分写后寄存器值不对

原因:wstrb位和wdata字节的映射写反了,或者窄传输时没按awsize循环移位。解决:写个 8 位窄传输用例,wstrb依次0001/0010/0100/1000,仿真后直接读寄存器值比对,别只看波形高低。

6. 进阶:把单从机扩成多 ID outstanding 的验证骨架

单从机写通道跑通只是起点,真正体现 AXI4 价值的是 outstanding 和乱序。资料里提到“从机 outstanding 为 N 时可缓冲 N 个指令”,但没给实现。我的做法是在awready前挂一个深度为 N 的地址 FIFO,awready = ~fifo_full,地址握手后压入 FIFO;写数据通道按wlast切包,每包数据配一个 ID 标签存入数据 FIFO;回应通道从两个 FIFO 里按完成顺序取 ID 拼bid。这样awready不再是一拍脉冲,主机可以连续发 N 个地址,仿真里能看到awvalid连续多拍、awready常高的流水线效果。

验证时加一个乱序用例:发两个不同 ID 的写任务,让第二个先完成,检查bvalid的bid顺序是否和完成顺序一致而不是和地址发送顺序一致。这一步过了,你才算真正摸到 AXI4 乱序收发的门槛。另外AWCACHE和AWPROT这两个信号在入门从机里可以直接接常量(比如4'b0011Normal Non-cacheable Bufferable、3'b000特权数据访问),但如果你后面要接带 cache 的主机或做安全隔离,就得回头细读手册 A4.4.1 那部分,资料里也说了这块能单独写一篇,别在入门阶段陷进去。

最后说个习惯:从那以后我每次拿到一份总线协议资料,都强制先跑通一个最小从机写过程、再补读过程、最后才看 cache/prot 这些高级信号,顺序反了就容易在细节里迷路。这份资料的价值就在于它把这条最小路径铺好了,RTL 和仿真环境都在包里,照着敲一遍比读十遍手册管用。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询