4、地址映射与路由:地址解码逻辑、Slave选择机制、地址对齐与边界处理
地址映射与路由,说白了就是回答一个问题:Master 发出来的地址,到底该去找哪个 Slave?
我刚开始做互联验证的时候,觉得这玩意儿不就是查个表嘛,有什么难的。结果第一次跑通仿真,发现数据跑到隔壁模块去了,整个系统乱成一锅粥。嗯,从那以后我再也不敢小看地址解码了。
4.1 地址解码逻辑
AXI 互联的核心任务之一,就是把 Master 发出来的地址,映射到对应的 Slave 地址空间。这个映射关系,通常由一个 地址映射表 来定义。
我个人习惯把地址解码分成两步:
- 基地址匹配:检查地址是否落在某个 Slave 的地址范围内。
- 偏移量计算:将全局地址转换为 Slave 内部的本地地址。
地址解码的核心公式
Slave 基地址 ≤ Master 地址 < Slave 基地址 + 地址范围
本地地址 = Master 地址 - Slave 基地址
举个例子,假设我们有三个 Slave:
| Slave | 基地址 | 地址范围 | 结束地址 |
|---|---|---|---|
| Slave_0 | 0x0000_0000 | 1 GB | 0x3FFF_FFFF |
| Slave_1 | 0x4000_0000 | 1 GB | 0x7FFF_FFFF |
| Slave_2 | 0x8000_0000 | 1 GB | 0xBFFF_FFFF |
当 Master 发出地址 0x4000_1000 时,解码逻辑会:
- 发现它落在 Slave_1 的范围内(0x4000_0000 ~ 0x7FFF_FFFF)
- 计算出本地地址:
0x4000_1000 - 0x4000_0000 = 0x1000 - 将请求路由到 Slave_1,并传递本地地址
0x1000
我的经验:在原型验证中,地址解码逻辑最好用参数化的方式实现。把基地址和范围定义成宏或者参数,这样换平台或者改映射的时候,改一处就行。我曾经见过一个项目,地址映射硬编码在 RTL 里,换 FPGA 板子的时候改得想哭。
4.2 Slave 选择机制
地址解码完了,接下来就是怎么把请求送到正确的 Slave 手上。这里涉及到 Slave 选择机制,也就是互联怎么把 Master 的请求分发到对应的 Slave 端口。
常见的实现方式有两种:
- 地址译码器 + 多路选择器:地址译码器产生选择信号,控制多路选择器把请求送到对应的 Slave。
- 分布式解码:每个 Slave 自己判断地址是否命中,命中就响应,不命中就忽略。
我个人更倾向于第一种,原因很简单——可控性强。在原型验证中,你能清楚地看到每个请求去了哪里,调试起来方便。
Slave 选择的核心逻辑可以用下面的伪代码表示:
// 地址译码逻辑
always_comb begin
casez (addr[31:30])
2'b00: slave_sel = 4'b0001; // Slave_0
2'b01: slave_sel = 4'b0010; // Slave_1
2'b10: slave_sel = 4'b0100; // Slave_2
2'b11: slave_sel = 4'b1000; // Slave_3
default: slave_sel = 4'b0000; // 无命中
endcase
end
注意:当地址没有命中任何 Slave 时,互联必须返回一个 DECERR(解码错误)响应。千万不要让请求悬空,否则 Master 会一直等下去,整个总线就卡死了。我在项目中遇到过这种情况,排查了半天才发现是地址映射漏了一段。
4.3 地址对齐与边界处理
地址对齐和边界处理,是 AXI 协议里最容易出坑的地方。说白了就是:你的地址和传输大小,必须匹配。
4.3.1 地址对齐规则
AXI 协议要求:
- 突发传输的起始地址必须对齐到传输大小
- 比如 4 字节传输,地址必须 4 字节对齐(低 2 位为 0)
- 16 字节传输,地址必须 16 字节对齐(低 4 位为 0)
为什么会这样?你想想看,如果地址不对齐,一次传输可能会跨越两个数据总线宽度,那就需要两次访问,效率就低了。
| 传输大小 | 对齐要求 | 地址低 bit 要求 |
|---|---|---|
| 1 字节 | 1 字节对齐 | 无要求 |
| 2 字节 | 2 字节对齐 | addr[0] = 0 |
| 4 字节 | 4 字节对齐 | addr[1:0] = 2'b00 |
| 8 字节 | 8 字节对齐 | addr[2:0] = 3'b000 |
| 16 字节 | 16 字节对齐 | addr[3:0] = 4'b0000 |
4.3.2 边界处理
边界处理比对齐更隐蔽。AXI 协议规定:一次突发传输不能跨越 4KB 边界。
为什么是 4KB?因为很多 Slave 的地址空间就是以 4KB 为页的。跨越边界意味着可能访问到不同的页,甚至不同的 Slave,这会让互联的逻辑变得极其复杂。
边界检查公式
起始地址[11:0] + 突发长度 × 传输大小 ≤ 4KB
如果超过,就需要把这次突发拆分成多次。
举个例子:
- 起始地址:0x0000_0FFC
- 突发长度:4
- 传输大小:4 字节
- 总传输量:4 × 4 = 16 字节
- 结束地址:0x0000_0FFC + 16 = 0x0000_100C
- 检查:0x0FFC + 16 = 0x100C > 0x1000(4KB 边界)
- 结论:跨越边界,需要拆分
避坑指南:我曾经在验证一个 DMA 控制器的时候,发现它发出来的突发总是会跨越 4KB 边界。查了半天,原来是 DMA 的地址生成逻辑没有做边界检查。后来我在互联里加了一个边界检查模块,一旦发现跨越边界,就自动把突发拆成两笔。这个模块后来成了我们团队的标配。
4.4 地址映射与路由的整体流程
下面这张图展示了地址映射与路由的完整流程:
4.5 原型验证中的常见问题
在 FPGA 原型验证中,地址映射和路由的问题往往比仿真中更容易暴露。我总结了几条经验:
- 地址映射表一定要和文档一致:我见过好几次,RTL 里的地址映射和架构文档对不上,结果验证了半天才发现是文档没更新。
- 边界检查不能省:有些设计为了省资源,把边界检查去掉了。结果在 FPGA 上跑的时候,数据写到了不该写的地方,整个系统崩溃。
- DECERR 响应必须实现:这是 AXI 协议的要求,也是系统稳定性的保障。没有 DECERR,Master 可能会永远等下去。
重要提醒:在原型验证中,地址映射的调试往往是最耗时的。我建议在互联中加一个 地址监控模块,可以实时抓取所有地址请求,并显示对应的 Slave 选择结果。这样一旦出问题,你能立刻知道是地址解码错了,还是路由逻辑错了。
地址映射与路由,看似简单,实则是 AXI 互联的基石。搞懂了这部分,后面的 QoS、一致性、死锁避免等问题,才能有坚实的基础。