4、地址映射与路由:地址解码逻辑、Slave选择机制、地址对齐与边界处理

地址映射与路由,说白了就是回答一个问题:Master 发出来的地址,到底该去找哪个 Slave?

我刚开始做互联验证的时候,觉得这玩意儿不就是查个表嘛,有什么难的。结果第一次跑通仿真,发现数据跑到隔壁模块去了,整个系统乱成一锅粥。嗯,从那以后我再也不敢小看地址解码了。

4.1 地址解码逻辑

AXI 互联的核心任务之一,就是把 Master 发出来的地址,映射到对应的 Slave 地址空间。这个映射关系,通常由一个 地址映射表 来定义。

我个人习惯把地址解码分成两步:

  1. 基地址匹配:检查地址是否落在某个 Slave 的地址范围内。
  2. 偏移量计算:将全局地址转换为 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 地址映射与路由的整体流程

下面这张图展示了地址映射与路由的完整流程:

地址映射与路由流程 Master 发出地址 地址解码 基地址匹配 + 偏移量计算 命中? 是 否 路由到对应 Slave 传递本地地址 返回 DECERR 错误响应 边界检查(4KB) 跨越则拆分突发 关键检查点 • 地址对齐检查 • 4KB 边界检查 • 地址范围检查

4.5 原型验证中的常见问题

在 FPGA 原型验证中,地址映射和路由的问题往往比仿真中更容易暴露。我总结了几条经验:

  • 地址映射表一定要和文档一致:我见过好几次,RTL 里的地址映射和架构文档对不上,结果验证了半天才发现是文档没更新。
  • 边界检查不能省:有些设计为了省资源,把边界检查去掉了。结果在 FPGA 上跑的时候,数据写到了不该写的地方,整个系统崩溃。
  • DECERR 响应必须实现:这是 AXI 协议的要求,也是系统稳定性的保障。没有 DECERR,Master 可能会永远等下去。

重要提醒:在原型验证中,地址映射的调试往往是最耗时的。我建议在互联中加一个 地址监控模块,可以实时抓取所有地址请求,并显示对应的 Slave 选择结果。这样一旦出问题,你能立刻知道是地址解码错了,还是路由逻辑错了。

地址映射与路由,看似简单,实则是 AXI 互联的基石。搞懂了这部分,后面的 QoS、一致性、死锁避免等问题,才能有坚实的基础。