NI-700 互连技术全面解析:架构、组件与工作机制
摘要
本文深入解析了 Arm NI-700 非一致性、分组化底板互连技术的完整架构与工作机制。NI-700 作为 AMBA 5 生态系统的重要组成部分,专为连接系统中的各种外设和管理器而设计,特别适用于构建高效、可扩展的 SoC 互连系统。
核心特性概览
1. 基础架构
- 非一致性设计:不支持完全缓存一致性,但可传输 IO 一致性事务
- 分组化传输:将 AMBA 协议转换为内部通用传输格式(generic transport)
- 基于信用的流控:减少阻塞,需要缓冲支持
- 资源平面隔离:实现不同流量类别的隔离,避免相互阻塞
2. 关键组件
- 节点接口:ASNI/AMNI(AXI/ACE5 Lite)、HSNI/HMNI(AHB)、PMNI(APB)
- 路由器:支持 8 输入/8 输出,采用虫孔路由和 LRU/QoS 混合仲裁
- PCDC:处理时钟/电源/电压域跨越,支持同步和异步边界
- SerDes:实现 flit 位宽转换和合并,优化通道利用率
3. 工作机制
- 事务流程:读/写事务在 AMBA 接口与通用传输格式间转换,支持 flit 拆分与合并
- 数据位宽调整:根据内存类型和事务属性自动优化数据宽度
- IDM(互连设备管理):提供设备故障检测、隔离和恢复机制
- QoS 调节:支持硬调节(未完成事务/TSPEC)和软调节(BQV 带宽调节器)
4. 时钟与电源管理
- 层次化域结构:电压域 → 电源域 → 时钟域的严格层次
- 电源状态:支持 ON、OFF、CONFIG、Full Retention 四种状态
- 时钟门控:通过 Q channel 实现安全时钟控制
- 复位策略:至少 40 个时钟周期的复位要求,支持 P channel 集成
5. 配置与编程
- 可发现的寄存器映射:基于电压域-电源域-时钟域-接口的层次化配置
- 配置网络:独立于数据网络的轻量级配置通道
- 地址重映射:支持 8 位重映射向量,实现灵活的地址空间管理
设计权衡与优化
NI-700 在设计中贯穿了多个关键权衡:
- 连线数量 vs 性能:通过 flit 宽度调整、high-wire/low-wire 模式选择优化
- 带宽 vs 面积:credit 数量与 flit buffer 深度的平衡
- 灵活性 vs 复杂度:支持多种协议、域跨越和 QoS 机制
- 可恢复性 vs 开销:IDM 功能增加逻辑开销但提升系统可靠性
适用场景
NI-700 特别适用于:
- 作为一致性互连(如 CMN)的下游底板
- 连接多种非一致性外设和管理器
- 需要细粒度 QoS 控制和流量隔离的系统
- 支持热插拔和故障恢复的可靠系统
- 多时钟域、多电源域的复杂 SoC 设计
通过本文的详细解析,读者可以全面理解 NI-700 的架构原理、配置选项和最佳实践,为实际系统设计提供坚实的技术基础。
下文为详细技术文档,按章节深入探讨 NI-700 的各个方面...
目录:
- [NI-700 互连技术全面解析:架构、组件与工作机制](#NI-700 互连技术全面解析:架构、组件与工作机制)
- Feature
- [第一章 · Introduction(导论)](#第一章 · Introduction(导论))
-
- [1. NI700 Agenda(议程)](#1. NI700 Agenda(议程))
- [2. NI-700 Fundamentals --- Overview(基本概念总览)](#2. NI-700 Fundamentals — Overview(基本概念总览))
- [3. NI-700 Terminology(术语)](#3. NI-700 Terminology(术语))
- [4. NI-700 Read Transaction Flow(读事务流程)](#4. NI-700 Read Transaction Flow(读事务流程))
- [5. NI-700 Write Transaction Flow(写事务流程)](#5. NI-700 Write Transaction Flow(写事务流程))
- [第二章 · Concepts(概念)](#第二章 · Concepts(概念))
-
- [6. NI-700 Concepts(概念:high-wire / low-wire)](#6. NI-700 Concepts(概念:high-wire / low-wire))
- [7. NI-700 Flit Sizing(flit 尺寸)](#7. NI-700 Flit Sizing(flit 尺寸))
- [8. NI-700 Credit(信用机制)](#8. NI-700 Credit(信用机制))
- [第三章 · Components(组件)](#第三章 · Components(组件))
-
- [9. NI-700 --- Node Interfaces(节点接口)](#9. NI-700 — Node Interfaces(节点接口))
- [10. NI-700 Router(路由器)](#10. NI-700 Router(路由器))
- [11. NI-700 PCDC(电源/时钟域跨越)](#11. NI-700 PCDC(电源/时钟域跨越))
- [12. NI-700 SERDES(位宽转换/合并)](#12. NI-700 SERDES(位宽转换/合并))
- [第四章 · Operation(运行机制)](#第四章 · Operation(运行机制))
-
- [13. NI-700 Data width resizing(数据位宽调整)](#13. NI-700 Data width resizing(数据位宽调整))
- [14. NI-700 read-reorder-buffer(读重排序缓冲)](#14. NI-700 read-reorder-buffer(读重排序缓冲))
- [15. NI-700 CDAS(循环依赖/单从机每ID)](#15. NI-700 CDAS(循环依赖/单从机每ID))
- [16. NI-700 ID Widths(ID 宽度)](#16. NI-700 ID Widths(ID 宽度))
- [17. NI-700 Security(安全)](#17. NI-700 Security(安全))
- [18. NI-700 Address map(地址映射)](#18. NI-700 Address map(地址映射))
- [19. NI-700 Duplicate Links(重复链路)](#19. NI-700 Duplicate Links(重复链路))
- [第五章 · IDM(互连设备管理,Interconnect Device Management)](#第五章 · IDM(互连设备管理,Interconnect Device Management))
-
- [20. NI-700 --- Interconnect Device Management(互连设备管理)](#20. NI-700 — Interconnect Device Management(互连设备管理))
- [21. NI-700 --- IDM example flow(IDM 示例流程)](#21. NI-700 — IDM example flow(IDM 示例流程))
- [22. NI-700 --- IDM example function(IDM 示例时序)](#22. NI-700 — IDM example function(IDM 示例时序))
- [第六章 · QoS(服务质量)](#第六章 · QoS(服务质量))
-
- [23. NI-700 QoS(QoS 调节)](#23. NI-700 QoS(QoS 调节))
- [25. NI-700 QoS --- BQV(带宽调节器)](#25. NI-700 QoS — BQV(带宽调节器))
- [26. NI-700 QoS --- Resource Planes(资源平面)](#26. NI-700 QoS — Resource Planes(资源平面))
- [第七章 · Clocks and Power(时钟与电源)](#第七章 · Clocks and Power(时钟与电源))
-
- [27. NI-700 Voltage Power Clock Hierarchy(电压/电源/时钟域层次)](#27. NI-700 Voltage Power Clock Hierarchy(电压/电源/时钟域层次))
- [28. NI-700 Power States(电源状态)](#28. NI-700 Power States(电源状态))
- [29. NI-700 Power Control Network(电源控制网络)](#29. NI-700 Power Control Network(电源控制网络))
- [30. NI-700 Clock Overview(时钟总览)](#30. NI-700 Clock Overview(时钟总览))
- [31. NI-700 Clock Control(时钟控制)](#31. NI-700 Clock Control(时钟控制))
- [32. NI-700 Reset(复位)](#32. NI-700 Reset(复位))
- [33. NI-700 Config Network(配置网络)](#33. NI-700 Config Network(配置网络))
- [第八章 · Programming(编程)](#第八章 · Programming(编程))
-
- [34. NI-700 Programming(编程:可发现的寄存器映射)](#34. NI-700 Programming(编程:可发现的寄存器映射))
- [35. NI-700 Tooling(工具链)](#35. NI-700 Tooling(工具链))
Feature



第一章 · Introduction(导论)
1. NI700 Agenda(议程)

和往常一样,我们先来概览一下今天要讲的内容。希望这能让大家对今天涉及的主题有更清楚的认识。我们会先讲 NI-700 的概念,包括术语、事务(transaction)的行为方式,以及 NI-700 内部大概是什么样子。接着我们会讲解构成 NI-700 的各个组件,以及一些特定功能,比如互连设备管理(IDM)、QoS、时钟和电源。最后,我们会非常简要地谈一谈你可能需要做的编程工作。所以这节课的核心,就是把 NI-700 硬件的功能行为和组成部件都过一遍
2. NI-700 Fundamentals --- Overview(基本概念总览)

我们先简要概述一下 NI-700 互连到底是什么。NI-700 被定义为一种非一致性(non-coherent)、分组化(packetized)的底板互连(backplane interconnect)。逐个解释这些术语:
首先是"非一致性"------这意味着如果你想管理一个缓存一致性的系统,NI-700 并不是合适的互连。不同于基于 CHI 的互连(如 CMN)或基于 ACE 的互连,在 NI-700 上你无法把一个完全缓存一致性的管理器接进去让它管理一致性。不过,你仍然可以让 IO 一致性事务穿过 NI-700。
其次是"分组化"------意思是你会把各种管理器和从属设备连接到互连的外部,NI-700 会把进入的消息转换成内部的一种分组化格式。由于是分组化的,你还可以为了优化而改变互连内部连线的位宽。
再就是"底板互连"------其思路是 NI-700 让你能把各种外设连接到一起。比如,你可能在中间有一个基于 CHI 的一致性互连,然后在它的下游挂一个 NI-700,再由 NI-700 连接到你所有的从属设备以及各种非一致性的管理器。换句话说,NI-700 实际上把系统里各种其他外设连接到了一起。
NI-700 主要是支持 AMBA 5 的互连。如果你不熟悉 AMBA 5 或 AMBA 整体:AMBA 分成若干代,5 是最新一代。NI-700 并不支持所有 AMBA 5 协议------比如属于 AMBA 5 家族的 ACE5 和 CHI,NI-700 并不直接支持。NI-700 所支持的 AMBA 5 协议包括:AXI5、ACE5 Lite 和 AHB5(注:口述为 AHP5)。每个协议的"5"版本本质上只是在 AMBA 3 或 AMBA 4 基础上的扩展。也就是说,如果你不使用这些扩展,每个接口都可以向后兼容旧版本的协议。例如,AXI5 在不启用任何可选 AXI5 扩展时,实际上与 AXI4 完全相同。所以我们在配置工具里把这些扩展关掉,就相当于得到一个 AXI4 兼容的接口。ACE5 Lite 和 AHB5 也是同理(AHB5 与 AHB-Lite 兼容)。在 NI-700 的输出端,你还可以让它兼容 AXI3,同时还支持 APB3 和 APB4。
NI-700 内部是基于信用(credit)的,也就是说,要在内部各跳之间传递分组,这些分组必须先获得 credit,才能被目的端接收。这样做的好处是减少了阻塞(blocking)------阻塞是 AXI 互连中常见的问题;代价是需要更多的缓冲(buffering),因为没有缓冲空间就发不出 credit。最后,NI-700 还支持资源平面(resource planes)。这个概念会在本模块末尾详细讲,简单说就是:它让我们能够把不同类型的流量分离开,这些不同类型的流量可以使用网络中同一条连接,但它们彼此之间不会相互阻塞。
3. NI-700 Terminology(术语)

好,我们先过一遍 NI-700 中会用到的一些术语。需要说明的一点是:遗憾的是,NI-700 目前仍然在使用 slave/master(从/主)这套说法。我会尽量采用 AXI 的新术语------subordinate(从属)和 manager(管理器),但产品文档本身还在用旧词,所以我讲的时候会两者混用。供参考:我说的 subordinate 就是从接口(slave),manager 就是主接口(master)。
先看从属接口(输入侧)。它们的命名规则是:协议的首字母 + SNI(Slave Node Interface,从节点接口)。所以 AXI 和 ACE5 Lite 接口是 ASNI,AHB 从节点接口是 HSNI。在右侧可以看到,我们有三个 AXI/ACE5 Lite 输入接口,它们都连到 ASNI;而 AHB5 接口连到 HSNI。
在 NI-700 的输出侧,概念一样:协议首字母 + MNI(Master Node Interface,主节点接口)。输出端还多一个选项------我们为 AXI/ACE5 Lite 提供 AMNI,为 AHB 提供 HMNI,此外还因为有 APB,所以支持 PMNI(APB 主节点接口)。右侧也展示了这些输出。
NI-700 内部有四类主要组件:
-
Config NI(配置节点接口):系统中所有对 NI-700 寄存器的编程访问都会被路由到 Config NI,由它负责把配置值分发到 NI-700 内部的各个组件。
-
Router(路由器) :当我们需要把一个或多个输入连接到一个或多个输出时,就会用到路由器。此外路由器还能充当重定时级(re-timing stage)------所以即使是一对一的连接也可能用它,因为路由器本身是基于 credit 的,可以在长路径上起到重定时的作用。
-
PCDC(Power and Clock Domain Crossing,电源/时钟域跨越):任何时候只要有时钟域、电源域或电压域的跨越,对应路径上就要插入 PCDC。比如这两个路由器如果处在不同的域(不同时钟域、电源域或电压域),只要域不同,工具就要求插入 PCDC。
-
SerDes :通常被称作串并转换器(serializer/deserializer),但更好的理解方式是------它是用来改变 flit 宽度的。我们还没细讲 flit,简单说:你可以为每个通道配置连接各模块的连线宽度,目的是在可以接受的前提下减少连线数量、换取更低的带宽。当两个组件的连线宽度不同时,工具会强制要求在它们之间插入 SerDes,由它来完成不同位宽之间的放大/缩小转换。
这些内部组件(如 PCDC、SerDes)有工具内置的各种规则,叫做 DRC 检查(Design Rule Checks),会在需要的地方强制插入它们。所以你不会"漏插" SerDes 或 PCDC------因为不插的话配置就无法通过校验。
4. NI-700 Read Transaction Flow(读事务流程)

好,我们来看读事务的流程。NI-700 的基本思路是:事务从一个 AMBA 接口进来,连接到某个网络接口------比如 AXI 就连到 ASNI。然后 NI-700 内部会把一切转换成一种叫做 generic transport(通用传输) 的格式。generic transport 是 NI-700 内部对所有协议使用的统一格式。后面几页会详细讲它长什么样,简单概括就是:我们把 AXI 的传输转换成可以在网络里传送的分组(packet)。
对于读请求,这里展示的只是分组传输的一种可能示例。你会发现:如果 flit 宽度较窄,读请求可能会被拆分到多个 flit 上。比如,如果你把 ASNI 输出端的 flit 宽度配置得比较窄,一个请求可能装不进一个 flit,NI-700 就会自动把它切成若干个大小合适的 flit 发送过去;到了 NI-700 的输出端,再把所有 flit 拼回,生成一个合适的 AXI 请求发给从属设备。
这里可以看到,我们有一个 length=4 的 AR 请求,在这个例子里被转成一个读请求 flit,发往 AMNI,AMNI 再把它还原成一个 AR 请求。
对于返回的数据,NI-700 的做法是:一旦读数据就绪就立即返回读响应 。虽然把多个读数据项打包进同一个 flit 是可行的,但 NI-700 假定读延迟通常很关键,所以一拿到读数据就立刻转成一个读响应 flit,发回事务的源端,再由源端把读响应 flit 还原成 AXI 事务,呈现给 AXI 管理器。
还有一点刚才没提:读响应和请求一样,如果你的 flit 比读数据窄------比如读数据是 128 位、而 flit 只有 32 位------那么响应也会被切成更多个 flit 发回。这同样会牺牲一些带宽;但如果你这条连接并不需要最大带宽,就能省下大量连线,还能省面积(比如 credit buffer 可以更小)。这些后面会更详细地讲。
5. NI-700 Write Transaction Flow(写事务流程)

好,接下来看写事务的流程。基本思路其实一样:以 AMBA(这里是 AXI)进来,转换成 generic transport,再以正确的协议格式输出。这里有一些细微差别:在 NI-700 中,写请求和写数据被合并到同一个写请求 flit 里------也就是说 NI 内部没有独立的"写请求"和"写数据",只有一个写请求通道,AW 和 W 数据合在一起。
同样,如果 flit 较窄,这个请求可能会被拆到很多个周期上去;但如果 flit 较宽,ASNI 会尝试把这些写数据聚合起来。比如写请求通道的 flit 宽度很宽,我们就会把 AW 和尽可能多的 W 数据合并成一个 flit,一次性发到端点。到了输出端,AMNI 再把这一切拆开,还原成正确的 AXI 格式。
至于返回的写响应(B 响应),会通过写响应通道返回。B 响应通常很小,一般就是一个 flit 沿响应通道回到管理器;当然,如果你把 flit 宽度设得非常窄,它也可能被拆分。
第二章 · Concepts(概念)
6. NI-700 Concepts(概念:high-wire / low-wire)

好,我们继续更详细地看 generic transport格式类型。前面提到过一些术语:事务总是会被拆分成 flit,也就是 NI-700 内部实质上的分组。NI-700 给内部通道提供了两种配置选择:high-wire(高带宽/多线)模式或 low-wire(低带宽/少线)模式------这同样是在"节省连线"和"可能损失带宽"之间权衡。
先看 high-wire 侧。我们有 AXI 的输入和输出通道,中间可以看到它们如何映射到 generic transport:AW 和 W 映射到写请求通道(W request),AR 映射到读请求,B 映射到写响应,R 映射到读响应。所以几乎和 AXI 一一对应,只是把 AW 和 W 合并了。
在 low-wire 侧,我们做了类似的合并,但更进一步:所有同方向的通道------即所有从主到从的通道------都合并成一个请求通道(AR、AW、W 合在一起);反方向上,读响应和写响应也都合并到同一个响应通道上。这里的权衡是:low-wire 模式下可能损失一些带宽,因为如果你同时做读和写,读写就要竞争同一条通道的带宽。
有些情况下工具会强制使用 low-wire:如果你有 AHB 接口或 APB 接口,它们被强制用 low-wire------因为这些协议同一时刻只支持一个读或一个写处于未完成状态,给它们 high-wire 没有任何好处,因为读写带宽你永远只会用到其中之一。而 Config NI 则会被强制设为 high-wire 模式(不过这对它来说影响不大,只算一个限制)。
除此之外,其他所有组件你都可以自行选择 high-wire 还是 low-wire。AXI 接口可以选 low-wire,各种路由器也可以;你还可以在网络中边走边在 high-wire 和 low-wire 之间切换------比如某个 ASNI 配成 high-wire,但到了网络的某一段,因为那里总是低带宽,就转成 low-wire。这种切换是通过路由器完成的。同样,如果你要做 high-wire / low-wire 的设置,工具会强制要求路径上有路由器来完成转换。
7. NI-700 Flit Sizing(flit 尺寸)

我们谈了一些 flit,也多次提到它的尺寸怎么定。这又是一个在"连线数量与面积"和"性能"之间权衡的问题:flit 越窄,性能越低,但连线和面积更省;flit 越宽,理论上性能越好(只要别过大),但成本更高。
在工具里,你可以为两个组件之间的每个通道、或某个具体组件定义各自的 flit 宽度。flit 宽度用"单位数(units)"来描述------比如你在工具里把 flit 宽度设成 10,就是 10 个 flit 单位。每个 flit 单位大约相当于 32 位有效载荷(useful payload)------你可以这样理解。实际上 flit 单位会比 32 位略宽,但当你想计算一个 flit 能装多少读数据时,简单地按"每单位 32 位有效数据"来算就行。
flit 本身由头部(header)和有效载荷(payload)组成 。每个 flit 都有头部,其中一部分还带有 payload。你配置 flit 宽度时,配置的是整个 flit 的大小------头部和 payload 都包含在内。
不同通道里实际装的东西略有不同,最简单的办法是看这几个例子:
- 写请求通道 :由头部(含 AW 信息和路由信息)加上写数据 payload 组成。所谓"路由信息",就是 flit 从一个路由器到下一个路由器、直到目的地的寻路信息,里面还包含 QoS 之类的东西。所以写请求 flit = 头部(AW + 路由信息)+ 写数据 payload。在最下面的例子里:写请求上有 AW 部分、一些路由信息,然后是 W 数据加写选通(write strobe)。我说它实际上不正好是 32 位,是因为你还得携带 strobe 位,可能还要携带 user 位等,所以真实大小会变------但你可以按"32 位有效数据"来理解。
- 读请求通道:只有头部,没有 payload。所以你配置的 flit 宽度实际上等于头部宽度,由 AR 请求加路由信息构成。
- 写响应通道:类似读请求通道,基本没有数据 payload,只有几个响应位加上路由信息,都放进头部。
- 读响应通道:有点像写请求通道------有头部(含响应和路由信息,比如 decode error 之类会放在响应里,路由信息告诉它往哪走),但读响应还要携带数据 payload,因为要把读数据送回上游。
比较棘手的一点是:头部宽度会随你的配置而变化 。比如路由器很多时,路由向量(routing vector)就会变大;同理,如果你启用了很多 AXI 特性------比如 cache stashing(缓存暂存)、atomics(原子操作)等等------需要带到 AW/AR 通道上的东西变多,头部也会变大。要精确知道头部有多大并不容易。我的经验是:最简单的办法是加载性能模型(performance model)------你搭好一个配置,加载性能模型,跑最基础的流量,性能模型会 dump 出一大堆关于你设计的统计信息,其中就包括每个通道、每个接口的头部大小。比如你会看到某个 ASNI 的读请求通道头部是 4 个单位、写请求通道头部是 5 个单位。这样你就知道头部多大,然后再据此来决定你的 flit 该设多宽、想带多少 payload。
8. NI-700 Credit(信用机制)

下一个你可能要配置的、和 flit 相关的东西,就是组件之间的 credit(信用)数量 。NI-700 的做法和一般基于 credit 的协议一致:发送方在给接收方发 flit 之前,必须先持有 credit,这个 credit 保证了该 flit 能被接收方的 flit buffer 接住。
某条链路上、某个通道的 credit 数量是可配置的,可以**按通道(per channel)和按资源平面(per resource plane)**分别配置。所以即使在单个接口上,你最多可能有 16 个不同的 credit 配置------因为有 4 个通道、最多 4 个资源平面(4×4=16)。
credit 的取值其实非常重要。要在一条链路上获得足够的带宽,你需要的 credit 数量必须能覆盖这条链路的延迟。如果这些接口上没有寄存器切片(register slice),那么 2 个 credit 就能让你在一条链路上获得满带宽------因为一次传输花 1 个周期、返回 credit 又 1 个周期,所以 2 个 credit、无寄存器切片时,两个组件之间就能做到背靠背(back-to-back)传输。
关于这些 credit buffer(更准确地说应该叫 flit buffer)的重要一点是:增加 credit 数量就等于增加 buffer 的深度。我一直在叫它们 credit buffer,其实叫 flit buffer 更准确------因为你要给出 5 个 credit,你的 flit buffer 里就得有 5 个表项(5 个 credit 意味着能接收 5 个 flit)。所以如果你把 credit 数量设得过大,网络的规模会迅速膨胀,因为这些 buffer 会变得非常大;而每个 buffer 表项的宽度又由 flit 宽度决定。所以我们见过有人为了"系统有很多带宽可用",把 credit 数设到接近最大、把 flit 宽度设得很高,但代价是面积爆炸------比如你把每个 flit 设成 500 位宽,再设 8 个 credit,那这个 flit buffer 实际上就有 4000 个寄存器表项;而且别忘了这还能按通道、按资源平面各做一份,单个模块或单个接口上可能就有 16 个这样的 buffer。所以把 credit 和 flit 宽度都设到尽量最优的值非常重要------这往往意味着要接受某些部分没有满带宽,因为你本来就用不到满带宽。
最后要说的是:每个接收方都会有这种输入 buffer 结构------它可能是路由器、SerDes、PCDC、AMNI,对于返回通道也可能是 ASNI。也就是说,凡是需要接收 flit 的组件,都会有这种 flit buffer 的输入结构,其深度都是可参数化配置的。
第三章 · Components(组件)
9. NI-700 --- Node Interfaces(节点接口)
接下来这一节,我们会更详细地讲讲各个模块。前面的幻灯片里我们做过一些介绍、也看了术语,现在就把这些模块逐一过一遍。
先看 ASNI 和 AMNI。ASNI 的作用是把 AMBA 转换成 generic transport;AMNI 正好相反,把 generic transport 转回 AMBA 协议。这里补充一个之前没提的:我们还支持 ACE5 Lite ACP。ACE5 Lite ACP 是 ACE5 Lite 的一个变体,对事务属性(transaction attributes)等有额外的限制。它有非常特定的用途------当你把 NI-700 接到处理器的 ACP 接口时就会用它,因为 NI-700 会保证不会向那个接口发送非法的事务类型。如前所述,它兼容 AXI4 和 ACE-Lite;至于 AXI3,视你需要的支持程度,可能原生支持,也可能需要做一点桥接,但一般无需桥接就能连 AXI3。下方列出了接口上可用的一些附加属性(properties),我之前提过其中一些,比如 atomics(原子操作)和 cache stashing------总之这是一份 AMBA 所支持、你可以选择在某个接口上启用与否的属性清单。

接着看 AHB 接口。在从属侧(互连的输入侧),我们有 HSNI(AHB 从节点接口)。如果你熟悉 NIC400,它有类似选项------HSNI 有两种不同形态,取决于你怎么连 AHB 设备。
- 第一种是 AHB5 mirrored master interface(镜像主接口):当你把 HSNI 直接接到一个 AHB5 管理器/主机时用它。
- 另一种是 AHB5 slave node interface(从节点接口):当你的 HSNI 作为一个从属/从设备,接入一个更大的 AHB 子系统时用它------也就是说它接在某个 AHB5 总线矩阵(bus matrix)的输出端。
- 两者的区别在于:从节点接口同时拥有 HSEL 信号和 HREADYIN 信号(因为这些是 AHB5 总线矩阵的输出),而镜像主接口没有这两个信号(AHB5 管理器不会有这些输出)。
两种 HSNI 都支持一个输入 buffer,这个 buffer 用来接到一个常开时钟(always-on clock)。它的作用是让你能对 HSNI 做时钟门控(clock gating),因为 AHB 本身不提供背压(back pressure)机制------如果你没有这个接到常开时钟的 buffer,就不能对 HSNI 做时钟门控,否则一旦门控,AHB5 事务的一部分就会被丢掉。前面说过 AHB5 接口向后兼容 AHB-Lite。它有用于时钟门控的输入 buffer,还有一些类似 AXI 的可配置选项------比如支持一些 AHB5 的附加选项,还能支持 broken bursts(断 burst)和 early write response(提前写响应),后者能让 HSNI 更快地响应 AHB 写、从而提升性能

至于 NI-700 的 AHB 输出接口,就是 HMNI(AHB 主节点接口)。和 HSNI 类似,HMNI 也有两种类型,取决于你把它接到什么样的 AHB 设备上。
- 第一种是 AHB5 master node interface:把 HMNI 接到一个 AHB5 总线矩阵或开关时用。
- 另一种是 AHB5 mirrored slave interface(镜像从接口):把 HMNI 直接接到一个 AHB5 从设备时用。
- 区别在于:镜像从接口会呈现 HSEL 和 HREADYIN 信号(总线矩阵的输出会有),而主节点接口则没有这两个信号。
HMNI 完全向后兼容 AHB-Lite------前面说过,AHB-Lite 和 AHB5 的主要差别就是 AHB5 多了一些附加功能,不启用这些功能就是 AHB-Lite 兼容的接口。HMNI 支持 sparse write strobes(稀疏写选通):在 AXI 里有 write strobe 的概念,可以选择一次访问里哪些字节被写入;AHB 原生不支持 write strobe,所以 HMNI 会把一个进来的稀疏 AXI 写拆成多个更小的 AHB-Lite/AHB5 写,以保证正确的字节被写入。HMNI 同样有可配置选项(AHB5 属性),前提是你的从属设备支持。

最后是 APB 输出接口------PMNI(APB 主节点接口) 。在 NI-700 上,APB 只能作为互连的输出。NI-700 的做法是:一个 PMNI 最多可以支持 16 个 APB 端口------也就是说,有一块 generic transport 到 APB 的转换逻辑,这一块就能连多达 16 个 APB 从设备,用 PSEL 信号来指示当前访问的是哪一个。每个端口都可以单独配置成 APB3 或 APB4。同一个 PMNI 上的所有东西都在同一个时钟域里。如果你向 PMNI 发了不支持的事务(比如发了一个 shareable 的写访问),它可能会产生一个错误响应返回去。

10. NI-700 Router(路由器)
下一个要看的组件是路由器(router) 。它的作用是把 flit 在互连里进行路由------每当我们需要把多条链路连到一起时,就要在它们之间插入一个路由器,由它来仲裁并把 flit 在这些链路之间调度。
-
对于进来、QoS 值相同(服务质量相同)的访问,路由器用 LRU(least recently used,最近最少使用) 来仲裁;QoS 值不同的访问,则 QoS 值高的赢得仲裁。不过为了避免饿死(starvation)------即一连串高 QoS 值的访问把低 QoS 值的访问堵住、让它无法推进------我们设有一个"防饿死周期(starvation avoidance period)"。我们可以把它设成 0,意味着路由器对所有访问都只用 LRU、实质上忽略 QoS;也可以设成 1~15 之间的某个值,表示接下来的 16 笔事务中有多少笔会使用 QoS 值来仲裁,其余的则不用 QoS 而用 LRU------这样理论上就能让低 QoS 的事务有机会推进。
-
路由器还能用作重定时级(retiming stage) 。比如你可以在一条长路径中间放一个单输入单输出的路由器,它实质上就加了一个基于 credit 的寄存器切片。你也可以给路由器每个通道的输入和输出额外加寄存器。
-
路由器采用的是所谓的虫孔路由(wormhole routing):一个进来的分组(packet)可能被切成多个 flit;虫孔路由的做法是,一旦第一个 flit 赢得了仲裁,同一个分组里其余的 flit 就会直接穿过路由器,期间其他请求无法抢占仲裁器。
-
路由器还用于网络中 high-wire 和 low-wire 部分之间的转换。
-
它最多可配置成 8 个输入、8 个输出 。

11. NI-700 PCDC(电源/时钟域跨越)
好,现在来看 PCDC 组件。前面说过,PCDC 是我们对所有需要的"域跨越(domain crossing)"的统称;根据你定义的时钟关系,工具会渲染出不同类型的 PCDC。
先看**同步(synchronous)**的情况:用于 1:N 或 M:1 的跨越------即来自同步时钟、但要做比例(ratio)转换。也包括前面提到的 1:1 跨越------时钟频率和相位完全相同,但我们人为引入一个时钟域边界,这样就能做更细粒度的时钟门控,或者简单地切分设计,这也是一种用途。这种 1:1 或同步 PCDC 有上游半边和下游半边,会在两者之间建立一个干净利落的时钟域边界:两个方向上都有 clock enable,数据从上游到下游,credit 从下游返回上游。所有这些都自动驱动,clock enable 也是;在实现上你要把它们设成多周期路径(multi-cycle path)。所以同步边界的实现并不复杂。

另一种、往往也是更常见的情况------异步(asynchronous)时钟域边界 。如果你有电压域(voltage domain),那它一定是异步的;而且在绝大多数情况下,电源域(power domain)边界也被视为异步。我们用于这种情况的结构要稍复杂些,但如果你熟悉异步时钟域跨越,右边这张图应该看着挺眼熟。本质上它是一种"载荷指针(payload pointer)"结构:上游把数据写进 payload FIFO,把写指针(write pointer)传播过桥,与读指针(read pointer)比较,比较结果用作 mux 的选择信号,决定把哪份数据转发过域边界。据我所知,这是相当标准的异步时钟域跨越做法,应该不陌生。在 PCDC 中,每个通道实际上有两个这样的跨越:一个用于数据跨越,另一个反方向用于 credit 跨越------也就是说,我们用同样的结构把 credit 往上游回传,就像把数据往下游传一样。
PCDC 有若干可配置选项:你可以选这些 payload buffer 有多深(数据 payload 和 credit payload 各自的深度),也可以选指针的同步级数(pointer synchronization stages,即在两个域之间同步指针的级数)。权衡在于:如果你想在某个 PCDC 上获得满数据带宽、满背靠背传输,就需要足够的 payload buffer 来覆盖指针的往返延迟(round-trip latency)。默认情况下,每个域有 2 级同步,所以往返延迟是 6 个周期(每个方向有 1 个发起周期 + 2 个同步周期)。所以默认放下去的 PCDC,其 payload buffer 深度就是 6、同步级数是 2,从而能获得满带宽。但和往常一样,如果想省连线、省面积,可以减少 buffer 数量,代价是损失一些带宽------很多时候某条连接并不需要满带宽。
我们常收到的一个反馈是:跨边界的连线数量太多。理论上这条边界可能最终有几千根线,因为有多个 payload,而每个 payload 的所有线都要跨过对面域的 mux。遗憾的是当前版本的 NI-700 就是这样设计的,不过这是我们未来会改进、希望能给大家更多选项的地方。

12. NI-700 SERDES(位宽转换/合并)
好,SerDes。SerDes 理论上是个挺简单的模块,而且正如前面所说,它其实不应该叫 SerDes------它本质上只是 generic transport flit 的一个位宽转换(resizer)模块。当两个组件之间的输入 flit 宽度和输出 flit 宽度不一样时------比如两个路由器,你在其中一个或多个通道上设了不同的 flit 宽度------跑工具时,工具就会告诉你需要在它们之间放一个 SerDes 来做转换。这是一种简单用法。
更有意思的一种用法是:SerDes 还可以用来把 flit 合并(merge)。当这是"放大(upsizing)"时就很有意义------比如这里 flit 宽度是 5 个单位、输出 flit 宽度是 15 个单位,那我们就有机会通过合并 flit 来优化通道的利用率。我们可以通过一个叫 tide mark(水位线) 的功能来控制这种合并行为:用 tide mark 来设定"先把多少个 flit 攒起来,再向下游释放"。这带来的好处是------尤其是当设备的上游和下游时钟速度不同时------我们可以用它把来自较慢时钟域的 flit 先攒起来,避免它们把下游网络用得很差。
flit 会在以下几种情况下被释放:
- 当攒到的数量超过了设定的 tide mark 值;
- 当你看到了 flit 的"最后一个(flit last)"------因为一个分组是一串 flit,当最后一个到了,我们基本就会释放,因为没有理由再攥着它了;
- 另外,SerDes 也可能在它满了的时候开始释放------因为 SerDes 里的 buffer 表项有限,满了就得往下游释放,否则就会卡住。

第四章 · Operation(运行机制)
13. NI-700 Data width resizing(数据位宽调整)
好。上一页讲的是 flit 尺寸/flit 宽度调整,这一页我们看的是外部接口上的数据位宽调整(data width resizing)。
外部接口在可能的情况下,一般会尽量针对输出数据宽度做优化。
-
第一种:内存类型(memory type)会决定是否需要保持 size 和 length。这是通过 AxCACHE1 来判断的:复习一下,AxCACHE1 基本决定了一个事务是访问普通内存(normal memory)还是设备内存(device memory)。
- 普通内存可以做性能优化、可以改变属性而不影响结果;
- 设备内存则必须保持属性,因为你是发给某个对 size 很敏感的寄存器或外设。所以如果是设备内存,就不能优化------我们正是用 AxCACHE1 这一位来判断一次访问能不能优化。
-
第二种不优化的情况是:如果输入数据小于输入数据宽度------比如我们有一个 64 位的 ASNI,但事务被标成 16 位,那我们也不会去优化它,因为这里假设上游设备选择更小的数据宽度是有原因的。
除以上之外,如果我们访问的是普通内存、并且用的是完整的输入数据宽度,那么放大(upsizing)就会自动发生。看这个例子:我们有一个 64 位的 AXI ASNI,向网络发出一个 8 字节、length=8 的事务,这被作为 64 字节的读请求发给 AMNI。AMNI 会把它看作 64 字节的读请求,然后针对它自己的输出数据宽度做优化------这里输出数据宽度是 128 位,于是我们这个 8 字节×8 的事务可以被发成一个 16 字节、length=4 的事务,也就是发得更快了。数据回来时,和前面一样的逻辑------立即返回读响应;ASNI 再把读数据转回成被请求的格式。这相当直接,因为我们可以把每个读响应简单拆开、转成每个响应 32 或 64 位的读数据。所以,位宽调整这件事------如果你用类似 NIC-400 的东西,你得自己加 resizer;而在 NI-700 里,它是 ASNI 和 AMNI 正常行为的一部分。

上面是放大(upsizing)。另一种是缩小(downsizing),概念基本一样。同样,AxCACHE 用来判断是否需要保持属性------不过我认为缩小的情况下基本没有"能优化"的情形。看这个例子:我们从 128 位 AXI 到 64 位 AXI,请求进来是 16 字节、length=4,被转成 64 字节的通用读请求,AMNI 把它缩小并把读数据返回。这时 ASNI 的一项功能是:需要把这些读数据重新打包(repack)回正确的格式 ------因为每个返回项是 64 位,但连在 ASNI 上的设备请求的是每个读数据项 128 位,所以 ASNI 要把这些返回的读数据重新打包成 128 位的包。它借助的是读重排序缓冲(read reorder buffer)。前面非常简略地提过这个概念,下一页会专门讲。这里只需要记住:当你面对一个缩小的目标时,每一笔未完成事务都会在读重排序缓冲里预留一个表项,用于重新打包数据。比如你从一个较宽的 ASNI 向一个较窄的 AMNI 发了 10 笔事务,就需要在读重排序缓冲里预留 10 个表项,才能正确地重新打包。所以读重排序缓冲的大小,和你对一个缩小目标能有多少未完成事务,是直接相关的

14. NI-700 read-reorder-buffer(读重排序缓冲)
好,我们更详细地看一下读重排序缓冲(read reorder buffer,ROB)。它有两个主要用途。
第一个就是我们刚讨论过的------用于合并返回的读数据,每笔事务需要预留一个表项;如果读重排序缓冲的空间用完了,就stall(按规则回退,见后文视频 15)。
第二个用途是:它允许我们对相同 ID、发往不同目的地的读事务,同时保持未完成状态。逻辑是这样的:如果没有读重排序缓冲,你就不能让相同 ID 的读事务同时发往不同目的地------因为它们有可能乱序返回,而 ASNI 必须按照发出的顺序把数据还给原始设备。读重排序缓冲的好处,就是让 ASNI 能把数据重新排回正确的顺序,从而在返回时不违反协议。
这个 buffer 位于 ASNI 内部,可配置为 1~256 个表项。它的工作方式是:当一笔新事务进来,且它和一笔已处于未完成状态、相同 ID 但不同目的地的事务匹配时,我们就会在读重排序缓冲里预留表项,数量等于这笔事务的大小。举例:假设我们先后发出了两笔相同 ID 的事务,先发 1 号、再发 2 号。如果 2 号先返回,我们就把它的读数据放进 buffer 先存着,等 1 号回来------1 号可以直接返回给上游管理器,然后再返回 2 号------这样就不会违反相同 ID 的顺序要求。

15. NI-700 CDAS(循环依赖/单从机每ID)
这里讲一点循环依赖(cyclic dependency)。如果你不熟悉:循环依赖指的是,在 AXI 互连里,由于排序规则的存在,有可能产生死锁(deadlock)。几乎所有的 AXI 互连都有某种机制来确保这些死锁不会发生。我们不会细讲死锁具体怎么形成的,而是重点说明 NI-700 是怎么实现的------因为这会影响你往系统里发事务的方式。
对 NI-700 而言,写侧这条规则很简单:相同 ID 的写事务,总是可以同时发往不同目的地而保持未完成状态。NI-700 会创建一个 B(写响应)buffer,其大小始终等于前面讲过的"最大写(max write)/写跟踪器(write tracker)"的大小。它之所以总是建这么大的 B buffer,是因为 B buffer 很便宜------每个表项只需要存几个比特。所以相同 ID 的写可以同时未完成地发往不同目的地。
这条规则的例外 是:如果你启用了 ordered write observation(有序写观察)。ordered write observation 是一个 AMBA 属性,它让接口表现得像在使用 PCIe 那种排序模型。本质上,它是为了防止"相同 ID 的写"在发出顺序之外被观察到。比如有两个目的地,你用相同 ID 先后向 A 和 B 发事务;如果不启用 ordered write observation,B 有可能在 A 被观察到之前就被观察到------这通常会破坏生产者-消费者(producer-consumer)排序模型。如果你需要这种排序保证(即只有 A 被观察到之后才能观察到 B),就可以在某个接口上启用 ordered write observation。这会打破上面第一条规则------也就是说,它会阻止相同 ID 的写同时未完成地发往不同目的地。我们实现 ordered write observation 的方式是:如果你发了 A,就把 B 拖住(stall),直到 A 完成,然后再发 B------这样就保证了 A 比 B 先被观察到。前提是这两笔事务用的是相同的 ID 值。
这种机制在 ARM 内部通常被称为 single slave per ID(每 ID 单从机)------即每个 ID 同一时刻只能对单个从机有未完成事务。和另一种情况类似,它的代价是可能产生停滞(stall):如果你发了 A,紧接着想用相同 ID 向 B 发写(且启用了 ordered write observation),那么 B 就会在 ASNI 里被拖住,直到 A 完成,这可能还会堵住排在它后面的事务。
至于读侧 ,前面其实已经讲过了:读侧我们用读重排序缓冲(read reorder buffer)来实现相同 ID 的读发往不同目的地。如果读重排序缓冲没有空间(要么是你把它配到了最小,要么是空间用完了),我们就回退到 single slave per ID------即每个 ID 同一时刻只能对一个从机有该类型事务处于未完成状态。如前所述,这同样可能产生停滞,会堵住后面的事务,因为 single slave per ID 生效了,在 ASNI 里拖住了事务

16. NI-700 ID Widths(ID 宽度)
这里的思路是:每个 ASNI 都有一个唯一的 source ID(源 ID),它在配置时被自动分配------你没有控制权。你可以通过寄存器查询它:读某个设备的 node ID,它就等于 source ID。
然后我们会根据"能访问该 AMNI 的最大输入 ID 宽度"以及"我们需要的 source ID 位数"来算出输出 ID 宽度,二者会被**拼接(concatenate)**在一起------即输出 ID = 输入 ID 后面追加 source ID。
理解这个最容易的方式是看底部的例子。左侧是我们的输入 ID 宽度:第一个 ASNI 是 3 位,第二个是 5 位,最后一个是 8 位。
- slave 0 只能被前两个 ASNI 访问,所以要考虑的最大输入 ID 宽度是 5 位;它再用 source ID 的最低位(bit 0)来区分这两个 source ID。所以输出端需要 1 位 source ID + 5 位输入 ID = 总输出 ID 宽度 6 位。
- slave 1 能被每个 ASNI 访问,所以要用最大的输入 ID 宽度 8 位;为了区分这三个 source ID,需要用 2 位 source ID。所以输出 ID = 2 位 source ID + 8 位输入 ID = 10 位。
- slave 2 只能被最下面这个 ASNI 访问,所以用满 8 位输入 ID 宽度。这里的优化规则是:source ID 这部分不能优化到 0,最多只能优化到 1 位。所以这个 slave 的输出 ID 宽度 = 8 + 1 = 9 位。
还要说明一点:NI-700 没有所谓的 ID 压缩 之类的机制,所以你不能把输出 ID 宽度随便设成你想要的任意大小。

17. NI-700 Security(安全)
这里简要讲一下 NI-700 的安全(security)。NI-700 对安全信息其实做得不多,除了在必须检查的地方做检查。在 AXI 接口上,我们直接使用 AxProt1 的值;如果是 AXI 到 AXI,就直接透传过去。其他情况下,如果我们转向的协议原生不支持安全、或可选支持安全,NI-700 就会在事务穿过系统时对安全值做一些设置或检查。
这里主要涉及的就是 AHB 和 APB------它们可能有、也可能没有安全支持。
对于进入的 AHB 接口(HSNI),有四种选项:
- 让 AHB 设备自己设置安全(AHB5 里用 HNONSEC 表示安全/非安全);
- 覆盖成"始终安全(always secure)";
- 覆盖成"始终非安全(always non-secure)";
- 设成"可编程(programmable)"------可以在运行时通过写寄存器来设置安全状态。
也就是说,对于进入的事务,要么由管理器自己决定,要么由 NI-700 来设置。
对于输出侧,我们用安全值来控制对从机的访问,同样有四个类似的选项:
- 指定"pin"方式,把安全通过 HNONSEC 或 PPROT 信号向下导出给从机------即让从机设备自己根据安全决定接受还是拒绝某次访问;
- 由 NI-700 强制安全------设成"只允许安全访问"到达该从机;
- 设成"非安全"------即放行一切;
- 设成"可编程"------复位默认为安全,但你可以在运行时改变该 HMNI 或 PMNI 的安全状态。
最后一点:对于访问从机、却未通过安全检查的事务------比如我们把某从机设成了"安全",或设成"可编程"且都保持在复位值------那么非安全事务会被拒绝访问,并向原始管理器返回一个 decode error(解码错误)。NI-700 在这种情况下会返回 decode error。

18. NI-700 Address map(地址映射)
这里讲一点地址映射(address map)。NI-700 的地址映射理论上相当简单,需要遵循的规则不多。
每个 ASNI/SNI 都可以有自己的地址映射 ------也就是说,每个管理器设备都可以有自己略有不同的内存视图(如果你需要的话)。地址映射的基本规则是:区域(region)必须对齐到 4KB 边界------你定义的每个区域都必须是 4KB 的整数倍,并且起始和结束都要落在 4KB 边界上。除此之外,你可以随意设置地址映射,没有别的规则。
地址映射里可以有"洞(holes)"。比如左下角第一个例子:某些区域没有映射到任何从机,如果你向这些区域发事务,互连会返回一个 decode error。
NI-700 还支持条带化(striping):可以跨 2 个或 4 个目标做条带化,它会对低位应用一个条带化 hash------准确说是用 XOR(异或)而非真正的 hash------来决定事务去哪个目标。如果启用地址条带化,会有一些额外的限制,这些在 TRM(技术参考手册)里有说明:有些设置方式是必须的,有些东西能或不能被重映射到。这些限制都不算太苛刻,而且工具会强制执行这些规则;但如果你在配置条带化时遇到报错,值得查一下 TRM,确认自己没有意外违反某条规则。
当你启用了地址条带化,工具会强制你在 ASNI 上启用一个 burst splitter(突发分割器)。burst splitter 的作用是确保一笔事务不会跨越条带化边界。举例:假设我们以 128 字节粒度做条带化,发进来一笔 512 字节的事务------问题在于,按 128 字节条带化时,这 512 字节实际要被分散到 2 个或 4 个不同接口上去。所以 NI 会把这笔 512 字节的事务切成 4 笔 128 字节的事务,让它们能安全地分别去往各个目标。burst splitter 的粒度是可编程的,但默认(复位后)会被设成目标条带化的粒度。这就是 NI-700 内的条带化------可以跨 2 个或 4 个接口。
地址映射里另一个更有意思的功能是 remap(重映射)。地址映射是可编程的,就是通过这个 remap 功能实现的。它的思路是:在配置时,你预设若干份地址映射;在运行时,你可以编程这个 remap 寄存器,在这些不同的 remap 状态之间切换。设置不同地址映射的方式,本质上就是把区域换进换出(swapping regions in and out)。
举例:假设我们有两个 remap 状态,一个对应 remap bit 0、一个对应 remap bit 1。在 remap 0 里,我们有这么几个 slave 区域;在另一个 remap 状态里,可以看到:顶部的区域大小不变,但映射从原来的 slave 1 换成了 slave 0;中间的区域被移除了(从 slave 0 改成了所谓的 no target);底部的区域被加进来了(原来是没有 target,现在改成映射到 slave 1)。
remap 的工作方式是:它是一个 8 位向量(8 位寄存器) ,通过设置其中的每一位,你就能在这些不同的地址映射之间切换。稍微复杂一点的是:你可以同时置位多个 bit。这种情况下,如果你的地址映射之间发生冲突,低位(lower order)的 remap bit 优先。比如顶部那个区域:remap bit 0 时是 slave 1,remap bit 1 时是 slave 0;如果你同时置两个 bit,这个区域显然不能同时映射到 slave 0 和 slave 1,于是较低位的 remap bit 胜出,结果映射到 slave 1。也就是说,你有 8 个 remap 状态,还可以把这些状态组合起来使用。可以这样理解:就像是把一份地址映射叠放在另一份之上,从而改变目标。
这个功能一个非常常见的用例是启动(boot)阶段 :比如你可能在地址空间底部放一大块映射到 boot ROM 的内存;一旦脱离了启动阶段,就不再需要访问 boot ROM 了------你可以出于安全原因把它移除,或者干脆把它重映射到 DRAM 之类,于是这段地址空间就能用作 DRAM 而不是 ROM。

19. NI-700 Duplicate Links(重复链路)
下一个要讲的功能叫 duplicate links(重复链路)。它指的是在两个网络节点之间------具体说是两个路由器之间------复制一个或多个通道的能力。
思路是:你可能某一条通道需要的带宽远大于其余通道。我们四个通道是:读请求、写请求、读响应、写响应。比如说,假设我们需要大量的读数据带宽,那我们可能只想在这一条通道上启用更多带宽,而不是所有通道。
如果没有这个选项,我们可能只能做完全并行的通道------但那样意味着我们把读请求、写请求、写响应也一起复制了,而它们其实没被充分利用,我们复制它们的唯一原因只是为了把读响应做大。所以 NI 允许我们只挑选其中某一个通道来复制。在配置时,你针对某条路由(比如从某个 ASNI 到某个 AMNI)静态地分配:这条路由将始终使用两个路由器之间的某条特定链路。
看这个例子最直观:我们有两个 128 位的 master,以满带宽向两个 128 位的内存控制器请求 64 字节的事务。在 AXI 里,128 位接口做 64 字节,意味着每 4 个数据拍(data beat)才发出 1 个读请求------这相当典型。绝大多数 AXI 场景下,你请求的数据量都远多于你的请求数量。也就是说,这里所需的带宽是不匹配的:读请求带宽远低于读数据带宽。

这种情况下,我们可以像图里那样,只针对响应通道再加一条链路。我们分配:比如从某个 ASNI 到某个 AMNI,始终走这"另一条"响应通道;而上面的 AMNI 到另一个 ASNI,则始终走上面的响应通道。这样一来,我们尽量减少了新增的连线、缓冲、面积等等,但仍然通过"把特定通道的连接数翻倍"获得了所需的带宽。
所以对于高带宽系统来说,这个功能的意义在于:简化你的系统、拿到所需的带宽,同时不浪费连线、不浪费面积。

!在这里插入图片描述(https://i-blog.csdnimg.cn/direct/90a81d03ad5d4f7ab4bc30656260d3bf.png
第五章 · IDM(互连设备管理,Interconnect Device Management)
20. NI-700 --- Interconnect Device Management(互连设备管理)
好,下一个概念是一个叫做 Interconnect Device Management(互连设备管理,IDM) 的功能。IDM 的目的,是处理系统中某个外部设备停止正常工作的情况。典型情况是:发起事务的 master 因为某种原因没把它声明要发的数据全发出来------比如它说要发 8 拍写数据,结果只发了 3 拍就停住、再也不发了;也可能是 slave 不响应或不接受事务------比如 slave 拒绝接受写请求、拒绝接受写数据,或者不返回任何读响应、只返回一半读响应。
IDM 的意义在于:让网络能从这种状况中恢复过来。因为在 AXI 里,所有设备都默认所有事务都会正常完成(所有被请求的传输都会完成)。而我们见到的系统挂死(hang),最常见的原因就是某个设备因故不响应------比如处理器执行了一个 barrier,要等所有事务完成,而这些事务永远完不成,系统就崩溃了。所以绝大多数系统挂死,根因都是事务没有正常完成。
IDM 的思路就是:把恢复机制就位 。正如顶部概括的那样------我们能够独立地配置、管理和复位单个系统组件,从而避免你为了修复问题而不得不给整个系统断电重启。
IDM 主要由这些部分组成:
- 一批额外的配置寄存器和用于报告错误的中断引脚;
- 用于记录哪些事务没完成的错误记录寄存器(error logging registers);
- 超时检测(timeout)------过一段时间后,我们能判定系统不会自行恢复了;
- 访问控制(access control)------你可以阻止事务发往已被隔离的东西;
- 以及对该设备触发复位的能力------比如某个 slave 只返回了半个事务就不再响应,一个很好的恢复办法(恢复流程中的一步)往往是复位该设备,让它回到一个已知良好(known good)的状态。
所有这些都包含在加到每个支持 IDM 的 AMNI/ASNI 里的 IDM logic------也就是一些额外的 tracker 和所需的额外逻辑。所以启用 IDM 确实有逻辑开销。但对于支持热插拔(hot plugging,可能移除某个没正常完成事务的设备)、或者仅仅是为了可恢复性的系统来说,IDM 相当强大。

21. NI-700 --- IDM example flow(IDM 示例流程)
好,我们从高层看一下 IDM 的工作方式,这样可能会更容易理解。整个过程可以分成三个阶段。
第一阶段:检测(detection) 。逻辑里有一个会超时的定时器(timer)。在本例中,它检测到 slave 没有返回读响应。它首先会触发一个中断,相关信息会被记录到寄存器里,并向系统标记"有东西不响应了"。
第二阶段:隔离(isolate) ------这是典型情况。软件会去读错误记录寄存器,然后由软件手动启动这个恢复流程 。这是一种做法,大概也是最典型的用法,因为大家通常不希望系统自己复位东西------那可能引发各种奇怪的问题。所以一般而言,软件会响应中断、介入进来、尝试复位相关东西。在隔离阶段,我们会隔离该设备 ------所谓隔离,就是我们停止向它发送任何新事务。我们还可以选择通过写寄存器来**软复位(soft reset)**该设备:软复位就是你写一个寄存器,它会拉起一个输出信号;你可以把这个输出信号接到该设备的复位树(reset tree)里,于是这个复位信号最终就会复位该设备。然后你可以清除错误寄存器。
第三阶段:稳定(stabilise) 。为了恢复系统,我们做这一步------本质上是伪造/合成(fake/synthesise)一个响应 。这一步的力量在于:它保证管理器不会崩溃、清除 ASNI 里的 tracker,从而让系统在某种程度上恢复。当然可能还需要其他软件步骤------比如你做的是读,却收到了一个垃圾(junk)响应,那你得对此做点处理。但即便如此,这也比不得不复位整个系统要好。

22. NI-700 --- IDM example function(IDM 示例时序)
为了把 IDM 收尾,用一张时序图(time-space diagram)来看一下流程会很有帮助。这里还是同样的情形------我们的 slave 没能把所有读数据返回。图里有:我们的 manager、AMNI、subordinate(从属设备)、控制寄存器,以及系统处理器(system processor,最终会去读这些错误寄存器的角色)。
流程是这样的:我们的 manager 向互连发出一个 4 拍的 burst,到达 subordinate;subordinate 因为某种原因只返回了 2 个读数据项。在此之前,你已经编程了一个超时值(timeout)------这个超时要足够长,不能因为 subordinate 只是慢了一点就触发;理想情况下,它得长到只有真正的崩溃才会触发。
一旦这个定时器到期,我们就触发一个中断 ,中断会被送给系统处理器;系统处理器随后去读 NI-700 的控制寄存器,弄清楚为什么产生了中断、是谁导致了这个问题。系统处理器随后可以写 NI-700 的寄存器来启动一次软复位 ------这会拉起一个外部复位信号(即这个接口的复位输出,前面说过你可以把它合并进从属设备的复位树)。NI-700 会合成响应 :返回全 0 的数据,并标记为错误响应------这对 manager 来说仍然比完全没有响应要好。然后系统处理器清除中断,并在适当时间后撤销(de-assert)软复位------所以确保这个复位引脚拉高足够多的周期来完成复位,是系统处理器的职责。
整个流程结束时,我们的系统至少部分地从"事务未被响应"中恢复了。如果我们遇到的是"没返回写响应"、或者"manager 没把所有写数据发出来"等情况,流程是完全一样的。
这就是 IDM 的概览。和所有这些东西一样,TRM 里有更多信息------这里没法细讲所有可选项,内容还挺多的,但归根结底,整体行为就是这张概览图所示。IDM 的核心就是:当某笔事务得不到响应时,系统能够恢复 。

第六章 · QoS(服务质量)
23. NI-700 QoS(QoS 调节)
这里有几页讲 QoS 调节(regulation) 。NI-700 里基本上有三种不同的调节器可供实例化。注意:这些你在配置时至少要先启用,对应的 RTL(调节器)才会物理存在。
我们有两种所谓的硬调节(hard regulation)。硬调节是指通过限制未完成事务(outstanding transaction)的数量,来阻止某个接口占用过多带宽。
- 第一种是简单版本------未完成事务调节(outstanding transaction regulation):你只是编程规定这个接口最多只能发出比如 16 个未完成事务,这就相当于限制了带宽。
- 第二种是 TSPEC 调节器(traffic specification,流量规格) :它也是硬调节,但不是简单设一个上限,而是让你编程一个平均值(average)、峰值(peak)和突发性(burstiness)。也就是说,你可以临时超出你的未完成事务上限、直到峰值,并以突发性的频率来超出。它比未完成事务调节器更聪明,但反过来也更难配置------因为这些值该设多少比较难算。
最后,右侧是我们所谓的 BQV(bandwidth regulator,带宽调节器) 。这是软调节(soft regulation) ------所谓软调节,是指它会改变 QoS 值 ,但不限制发出的事务数量 ;它是根据你为某接口编程的分配带宽来做的。后面有几页会更详细地讲这个。

如前所述,这是硬调节,你可以设置三个值:平均速率(average rate)、峰值速率(peak rate)和突发性(burstiness)。
如果你把突发性设成 0、或者把峰值速率设成和平均速率一样,那么你得到的东西就和未完成事务调节器非常接近------因为平均速率和峰值速率相同时,我们基本就有了一个发请求的最大速率,同样限制了带宽。
但有了突发性这个值,思路就是:你可以临时超出你的速率、直到峰值速率。这正是左边这张图所示:我们有一条平均速率线,表示随时间能发多少请求;但我们可以在所编程的突发性范围内,把斜率(速率)提升到峰值速率。也就是说,我们可以以一定的频率临时超出平均速率------这是可编程的。右边的图展示的是随时间的效果:我们可以按峰值速率尽快发,然后出于某种原因降低发送速率;在未来的某个时刻,按照突发性的节奏,我们又可以临时再次超出平均速率、直到峰值速率,但过一会儿又会被压回平均速率。
这背后的想法是:某个设备可能临时需要更多带宽,但我们仍然会限制它能多拿多少、以及它能多频繁地超出平均速率 。

25. NI-700 QoS --- BQV(带宽调节器)
(这一页讲)带宽调节器,也就是 BQV 调节器。它的思路是:你决定某个接口被允许拥有多少带宽、当我们超出分配带宽时 QoS 下降有多快,以及 QoS 值可以在哪个区间内浮动。对于某个接口,我们通常不希望 QoS 跌到某个水平以下,也不希望它升到某个水平以上。
这个调节器的工作方式是:当你在带宽配额之下时,你从最大 QoS 值 开始;只有当你超出带宽配额之后,QoS 值才开始下降。下降的速度由"excess bytes per QV"(每降一档 QoS 对应的超额字节数)决定。
举例:我们说这个接口最大 QoS 是 12、最小 QoS 是 8;我们给它分配了每周期 4 字节,在 1GHz 下相当于 4GB/s;并且设定"excess bytes per QV"为 4KB。也就是说:每超出配额请求 4KB,QoS 就下降 1,最低降到 8。所以如果我们开始远超配额地请求,这个接口的 QoS 就会一路掉到最小值。
总体思路是:你可以给不同接口设置相互重叠的 QoS 区间 。比如我们常说:CPU 的 QoS 值可能在 14~10 之间,而实时设备的 QoS 值可能在 12~8 之间。于是,如果 CPU 变得过于贪图带宽,它的 QoS 就会跌到实时管理器的 QoS 之下;而如果实时管理器变得太贪,它的 QoS 最终也始终会低于 CPU 所请求的值。

26. NI-700 QoS --- Resource Planes(资源平面)
现在来看资源平面(resource planes) ,这里引入资源平面的概念。资源平面的意义在于:我们能获得所谓的流量类别隔离(traffic class separation) 。我们的目标不是为每一类设备都搞一条完全独立的路径,而是把设备分组------把流量需求相近的设备放进同一个资源平面。比如这个例子里:所有 CPU 放在资源平面 0,GPU 放在资源平面 1,IO 放在资源平面 2,LCD 放在资源平面 3。
这里的要点是:不同资源平面上的流量不应直接干扰彼此------比如不应造成阻塞。之所以这样划分,是因为我们预期每个资源平面有不同的需求。经典例子:CPU 带宽相对较低,但需要非常低的延迟;而 GPU 带宽很高,但对延迟更宽容。因为 GPU 可能产生大量持续流量、可能堵住内存控制器,我们不想让它和 CPU 在同一个资源平面------我们希望当 GPU 试图占用过多资源时,CPU 流量能绕过 GPU 流量。
这就是资源平面的核心意义。我们在所有设备之间用的是相同的路由器、相同的路径 ,但不同资源平面上的流量,即使走同一条路径、经过同一个路由器,也能在遇到拥堵时彼此绕过(bypass)。其底层机制是:每个资源平面都有自己独立的 credit、自己的 flit buffer------也就是说,NI-700 内部为不同资源平面准备了相互独立的 credit 和 flit buffer,让它们能在各个设备里彼此绕过。
这样做的目的是消除队头阻塞(head-of-line blocking)。举个例子(用更贴切的那个例子):假设 GPU 流量在某处被堵住了------比如下方那条路径已经饱和、内存控制器不想再接收任何东西。这时,IO 流量即使此刻用的是同一条连接,也能绕过去------因为它用的是不同的 flit buffer,可以走另一条路径、到达另一个输出接口。所以在 NI-700 内部,资源平面非常有用:它让我们能复用同样的路径,同时让流量彼此绕过、去往不同目的地。
不过,在 NI-700 的输出端,你始终可能存在队头阻塞------因为我们这里用的 AXI 不是基于 credit 的,也没有"资源平面"的概念,所以所有东西最终都不得不汇聚到单一输出(如果去往同一族目标)。对此我们有一个部分解决方案:**QoS accept **。它的思路是:你的内存控制器可以告诉设备"我现在有多忙"------也就是设置它愿意接受的最低 QoS 级别。比如,随着内存控制器越来越忙,它的队列会被填满,它就应该驱动越来越高的 QoS accept 值。这个值越高,就表示它只想接受达到该 QoS 级别或更高的事务 ------于是高优先级的东西被处理,低优先级的东西则被存在接口里。NI-700 据此会查看各个进来的资源平面数据流,选出 QoS 最高的那一条放行到下游 ,而把其他进来的流存起来。

-
用户可以定义某个xSNI属于哪个RP
-
路由器在每个端口上支持多个RP (Resource Planes)
-
每个RP都可以配置credit
- rp间流量可以通过不同的信用资源进行隔离
-
仲裁
- LRU用于不同rp之间。
- 基于qos的LRU,用于同一RP内的数据包

第七章 · Clocks and Power(时钟与电源)
27. NI-700 Voltage Power Clock Hierarchy(电压/电源/时钟域层次)
这一页我们想看看 NI-700 是如何**概念化地建立电压域(voltage domain)、电源域(power domain)和时钟域(clock domain)**的。
对 NI-700 而言,正确的方式是把它想成一个层次结构(hierarchy) :最顶层是电压域------不同的电压域彼此完全独立,而且按定义是异步的,所以两个独立电压域之间的时钟域跨越必须是异步的。
在电压域之下,你可以定义一个或多个电源域。也就是说,电源域总是从属于某一个电压域,一个电压域内可以有多个电源域。右边这个例子里,我们有两个电压域,每个电压域里又各有两个电源域。
再往下一层是时钟域:时钟域存在于电源域之内,你可以为每个电源域定义一个或多个时钟域。如图,在电源域 0 里,我们定义了两个不同的时钟域。
这就是我们实际使用的层次结构,也是你在工具里最终定义东西的方式。这里额外两个要点其实是对前面内容的总结:时钟域唯一地属于某个电源域------一个时钟域只能存在于一个电源域里,不能在多个电源域之间共享。如果你确实有一个时钟跨越了多个电源域,你就必须把它们定义成两个独立的时钟域,并且会发现它们之间会出现一个 PCDC------本质上是一个能处理电源域跨越的异步桥。
所以在当前设计、即 NI-700 现行的工作方式下,这些关系始终成立:时钟域只存在于一个电源域内,电源域只存在于一个电压域内。如果在你的实际实现里某样东西确实跨越了域,正如我所说,你就要在工具里把它们分别定义,于是不同模块之间就会出现额外的边界。

28. NI-700 Power States(电源状态)
这里再讲几页关于电源的内容,涉及各种电源门控(power gating)和时钟门控(clock gating)。
NI-700 支持 P channel(P 通道) 。P channel 是 ARM 架构上定义的另一种标准低功耗接口。你会发现,大多数较新的 IP 只要支持多种电源状态,都会支持某种形式的 P channel。如图所示,NI-700 支持四种不同的电源状态:
- ON(开启)
- OFF(关闭) :进入 OFF 状态时,该电源域会把自己与其他电源域逻辑隔离(logically isolate),避免在它断电时还有人发事务给它。
- CONFIG(配置状态) :这个状态会阻止任何 ASNI/SNI 访问 NI-700,除了那些把 config access 引脚拉起的接口。思路是:你可以从 OFF 转到 CONFIG,然后只允许一两个接口访问 NI,让这些接口在其他人获得访问权之前先把 NI-700 编程配置好。所以这是一个特殊状态,只有特定接口能访问。
- Full Retention(完全保留):我们也支持这个状态,但实际上目前 full retention 基本等同于 OFF------因为 NI-700 里没有 RAM、没有需要保留的存储器,所以在实际行为上,full retention 和 OFF 几乎一模一样。
右边这张图给出了所有合法的状态转换:从 OFF 可以到 CONFIG,从 OFF 也可以到 ON;从 ON 还可以回到 CONFIG;从 CONFIG 可以到 OFF。如果我们暂时忽略 full retention(它作为可用状态没什么意义),可以把这些状态看成一个三角形。
P channel 的工作方式是:设备会输出一个 P_ACTIVE 位 ,表示设备为了执行它想做的事、需要处于什么电源状态;然后你向设备驱动一个 P_STATE 值,指定你希望它转入哪个电源状态。举例:如果你不想用 P channel、只想让它永远处于 ON,那你就把 P_STATE 接到一个固定值------这里就是接成 8,意味着一退出复位就直接进入 ON 状态。

29. NI-700 Power Control Network(电源控制网络)
这一页展开讲一下电源域和电源控制大致是怎么工作的。我们有这些电源控制块(power control block),我们预期它们位于一个相对"常开(always-on)"的电源域里。思路是:你不能把这些关掉、还指望 NI-700 能以某种方式自己唤醒自己------因为如果电源控制块本身都关了,你就没法向它发送任何信息来表明某个域需要电源。所以如图,这些电源控制块位于那个会被真正开关的电源域边界之外,跨越点另一侧的那个也一样。
底部可以看到 PCDC 的两半。一般思路是:当事务进入 PCDC 时,它们会拉起这些内部的 Q channel 。这些信号会表明"这个域需要电源",以及"这个域一旦恢复电源、还需要时钟"------目的是让你可以完全由硬件来管理所有的电源和时钟。也就是说,模块/域自己会表明它忙不忙,然后你就可以完成低功耗握手;如果你把它关掉了,又有事务进入这个模块,那么各种 Q channel 信号就会被拉起,表示你需要把时钟和电源还给这个模块,好让事务继续推进。
所以这里看到的是:当有事务进来,我们会拉起这些内部 Q channel 信号,送给电源控制器和时钟控制器;我们还会拉起一个跨到另一侧域(接收域)的信号,告诉接收域它也该唤醒了------因为如果有事务到了 PCDC 这半边,显然我们最终需要把这个事务跨越边界送到另一半。也就是说,这种情况下两侧的域都需要被唤醒 。

30. NI-700 Clock Overview(时钟总览)
现在看时钟门控(clock gating) 。首先,我们有哪些时钟?对于我们定义的每个时钟域,我们都会得到一个 clockname_clock 时钟和一个 clockname_reset 复位;还会为每个时钟域得到一个 Q channel。所以如果你想做时钟门控,就去翻转 Q channel,这样就能安全地关掉时钟。对于每个含有 HSNI 的域,你还会得到这个域专用的 **always-on clock(常开时钟)**和 always-on reset(常开复位)------这就是用于 HSNI 那个输入 buffer 级的额外时钟。
再往下看,这是所谓的顶层/层次化时钟门控(top-level / hierarchical clock gating) ------也就是由你在 NI-700 外部控制时钟门控的地方。除此之外,还有内部时钟门控,它不管你对外部接口做什么都会发生:
- 区域时钟门控(regional clock gates):由 RTL 控制,会把不活跃的模块关掉。比如 ASNI 的某一部分完全不活跃,区域时钟门就会自动把那个模块门控掉。
- 局部时钟门控(local clock gates):最底层的东西,比如从 RTL 推断出来的 clock enable------例如触发器上的 clock enable。
最下面这两种,除了在做物理实现(implementation)时要进行 clock gate 替换之外,基本不需要你操心或去想。和大多数 ARM IP 一样,做物理实现时,你需要把 clock gate 单元替换成库特定的单元(clock gate replacement)。这对 ARM IP 来说不算什么特别的事,相当标准。
Q channel 只在两个主要状态下工作:要么是 Q_RUN (时钟在跑),要么是 Q_STOPPED(时钟已停)。如果你处于 Q_STOP 状态,那么时钟域之间就会出现逻辑隔离 ------这样就不可能向一个处于 Q_STOP 状态的域发事务(那不安全)。我们用这种逻辑隔离来防止这种事发生。

31. NI-700 Clock Control(时钟控制)

时钟控制和电源控制的思路类似,只是更简单一些。当一笔事务进入时钟域跨越的某一侧时,它会插入一个内部 Q_ACTIVE 信号 ,告诉时钟控制"这个域需要时钟"。Q_ACTIVE 信号在这里是组合逻辑(combinatorial)直通的,并从 NI-700 输出。所以,即便整个域(包括时钟控制器本身)都被时钟门控了,Q_ACTIVE 信号仍然能传播出去、到达你的外部时钟控制器、告诉它这个域需要时钟------直到时钟被送回来。
类似地,我们还有一个跨域的信号:因为如果上游域变忙了,我们可以推断下游域很快也会变忙,所以有一些内部连线会送到下游域的时钟控制器里。那就是唤醒信号(wake-up signals)。wake-up 是 AMBA 定义的一种信号,它告诉设备"你的接口上有一笔待处理的事务(pending transaction)"。通常你会期望 wake-up 信号也会以组合逻辑的方式送入 Q_ACTIVE,进而送到你的外部时钟控制器,告诉它这个域需要时钟。

32. NI-700 Reset(复位)
在 NI-700 上,每个时钟引脚都有对应的复位引脚。每个复位都会在内部用**双触发器同步器(double-flop synchronizer)**进行同步,复位的要求是:至少要拉起 40 个时钟周期。之所以需要这么长的复位时间,是因为 NI-700 内部的复位实际上可以是多周期的(multi-cycled);为了让复位安全地传播到设计中的所有组件,我们必须把它保持至少 40 个周期。复位拉起期间的要求是:该复位时钟域的所有输入节点接口都必须不活跃(inactive),所有相关的配置输入都必须保持不变。
涉及到断电(power down)、尤其是有不同电源域时,复位该怎么控制,取决于我们是否使用 P channel。
先看不使用 P channel 的情况:如果我们不用 P channel,那么异步边界两侧的复位必须始终同时拉起,之后则可以彼此在任意时刻撤销。原因是:边界内部有那种 FIFO 指针结构,如果我们只复位了桥的一侧,那些指针就会失去同步,从而可能产生虚假事务(spurious transactions)。
如果我们使用 P channel,那么把 P channel 从 ON 状态移到 OFF 状态这个动作本身,就会确保指针被正确复位,从而不会产生虚假事务。不过,即便用 P channel,仍然要看我们是否实现了电源隔离(power isolation):
- 如果两个电源域之间实现了电源隔离,那么在加上电源之后、撤销电源隔离之前,必须先拉起复位。
- 如果没有任何电源隔离单元(isolation cell),那么两个时钟域的复位必须在"加电的同时或之前"被拉起------也就是说,加电之前我们必须确保已经处于复位状态,然后再以任意相对时序去(重新)操作复位。





33. NI-700 Config Network(配置网络)
再讲一点配置网络(configuration network)是怎么工作的。每个电源域都必须有恰好一个配置网络接口(config NI)。在下面的图里,我们有三个电源域,可以看到每个都有一个 config NI;电源域边界上有 PCDC,这些虚线就是不同的边界。
NI-700 呈现这件事的方式是:它有一个所谓的数据网络(data network) ------你实际的 AXI 流量数据就走这里;在它之下还有一个配置网络。配置网络有点像数据网络的一个"部分镜像(partial mirror)",它本质上是 NI-700 内部传递配置信息的通道。
所以,如果我们有这样一个数据网络,它下面实际上就有一个配置网络。配置网络你可以手动设置------在工具里你可以修改它;但默认情况下,它也会自动给你生成,而且通常相当优化。如图所示,配置网络会把所有不同的寄存器和所有不同的接口彼此连接起来,并连到 config NI。
可以看到,配置网络可以比数据网络更简单------它并不总是完全镜像。比如这里,数据网络在这个域里有三个路由器,而配置网络只简单地用了一个路由器------通常因为它性能要求低、而且出于时序考虑觉得不需要更多。当然,如我所说,在某些情况下你也可以选择修改它,但总体上我们力求让它在默认情况下就生成一个相当优化、面积很小的网络。

第八章 · Programming(编程)
34. NI-700 Programming(编程:可发现的寄存器映射)
现在从时钟和电源转到 NI-700 的编程(programming) 。NI-700 的设计是:它给你一个可发现的内存映射(discoverable memory map) 。所以理论上,你需要给软件的全部信息,就是配置寄存器的基地址------也就是 ARM 里常说的 PERIPHBASE。
做 NI-700 配置时,你会为配置寄存器选一个基地址;从这个基地址出发,你的软件应该能够遍历这些不同层级的寄存器指针,从而发现整个配置、找到所有可配置寄存器的位置。
简要走一遍:每个这样的寄存器区段(虽然不完全是"页",但可以说是寄存器段)的格式始终相同。第一个表项会告诉你下一层级有多少个域 。在最顶层,它会告诉你有多少个电压域(voltage domain),下面跟着一串电压域指针------每个指针指向该电压域寄存器的起始位置(比如 VD0 指针指向属于电压域 0 的寄存器)。于是你根据电压域数量就知道有多少个指针;在这串指针的末尾,是全局寄存器(global registers),即适用于寄存器映射这一层的寄存器。
如果你顺着某个指针往下走------比如顺着电压域 0 的指针------它首先会指向"电压域 0 里有多少个电源域",这些都是与电压域 0 相关的寄存器;同样,下面有一串指针指向该电压域里的各个电源域;指针串末尾是电压域特有的寄存器。电源域(power domain)的概念完全一样,只是电源域下面是一串时钟域指针。最后,从时钟域段往下,会有一串接口(interface)指针;一直到最末端,你才到达接口节点(interface node)。每个接口的第一个寄存器是一个 type 寄存器,它会告诉你这个节点是什么类型、node ID 是多少------比如它可能表明"这是一个 ASNI,node ID 为 5"。然后在这个段里,就有该接口特有的寄存器。
所以这里的关键是:它是可发现的。我们基本上有一系列指针,把你引向层次结构中的下一层,而这个层次结构正是"电压域 → 电源域 → 时钟域"的层次。每个段里还有属于整个该域的寄存器------也就是说,你越往下走,寄存器就越具体。
从这些信息可以看到:随着你改变配置,可编程模型的地址空间也会变化。如果你增加了接口、时钟域、电源域或电压域,配置所需的地址空间就会变大。
此外,配合 IDM 还能做设备发现(device discovery) :IDM 可以为每个接口设置一个 device ID,所以如果你有 IDM、又有某种映射,就能做一些设备发现。最后,这些段(section/region)每个都是 4KB 大小------也许用"region(区域)"来形容最合适,每个 region 都是 4KB。

35. NI-700 Tooling(工具链)
