本页是介绍多吉比特收发器(MGT)的系列文章中的第八篇,也是最后一篇。
简介
流量控制(flow control)是一种常用于各种数据链路的机制。这一机制的用途是防止出现发送数据量超出接收端处理能力的情况。
与 MGT 相关的协议对流量控制功能提供不同级别的支持。例如,实现 PCIe 协议的逻辑在接收方缓冲区已满时绝不会接受要发送的数据包。使用 PCIe 协议的应用逻辑在这方面无需实现任何内容。确切地说,应用逻辑与 PCIe 协议逻辑之间的接口允许协议逻辑临时拒绝接受新数据(例如借助 AXI-S 的 VALID、READY 信号)。同样,应用逻辑也允许临时拒绝接收对端发来的数据。这样,应用逻辑便保护了自己免受溢出(overflow)的影响。
有一点很重要:不要把流量控制和 MGT 自身弹性缓冲器(elastic buffer)的防溢出机制混淆。流量控制与关于 MGT 自身缓冲器的页面中提到的跳过符号毫无关系。流量控制保护的是应用逻辑的缓冲器。而且,由于常有多个数据通道把 MGT 当作共享资源来使用,流量控制是对每个通道分别独立实施的。
一般说来,凡是与计算机通信有关的协议都会自行处理流量控制机制。应用逻辑只需要借助简单的握手端口与协议逻辑对接即可,就像与许多其他类型的逻辑模块交互一样。PCIe、SuperSpeed USB 和 SATA 便是这样的协议:它们都会自行处理流量控制。
遗憾的是,用于 FPGA 间通信的协议通常无法在这个粒度上支持流量控制。唯一把流量控制完全处理好的协议是 Xillyp2p。其他面向 FPGA 的协议只提供少量有助于在应用逻辑中实现流量控制的功能。
本页将介绍几种实现流量控制的技术,稍后也会简要总结 Aurora 和 Interlaken 在流量控制方面提供了哪些协助。不过请记住:除了使用 Xillyp2p 的情况外,防止溢出的责任仍然在应用逻辑这边。
流量控制技术
流量控制的终极目标是防止出现这种情况:接收数据的一方收到的数据超出了它能处理的范围。接收端通常把到达的数据存入缓冲器(或 FIFO),所以问题最终归结为:要确保这些缓冲器始终有足够的空间容纳即将到达的数据。
在 MGT 上实现流量控制要困难一些,主要是因为物理信道有可观的延迟。因此,“停止发送”的请求需要一定时间才能到达发送端。此外,即使发送端真的停止了发送,信道中已经存在的数据也还要再花一些时间才能全部到达接收端。
MGT 的另一个难点是:物理信道上的比特错误可能导致“停止发送”的请求丢失。
除了上述两个难点之外,MGT 的数据速率很高,而且人们通常还希望高效地利用这条物理数据信道。
与更简单的通信信道(例如 RS-232 串口)相比,MGT 的流量控制必须更加精密,才能保证不发生溢出。下面将介绍以下三种技术:
- XON / XOFF
- 限时 XOFF(Pause)
- 信用额度(credits)
在考虑为项目选择某种协议时,有两个问题值得问一问:
- 关于流量控制,应用逻辑需要实现什么?答案可能是:什么也不需要实现,例如 PCIe、SuperSpeed USB 和 Xillyp2p 就是这样。
- 如果应用数据被划分到多个不同的通道:流量控制机制能否单独暂停每个通道的通信?还是它会影响到物理信道上所有流量?
带内与带外
在分别讨论这三种技术之前,先做一个重要区分:带内流量控制(in-band flow control)与带外流量控制(out-of-band flow control)。
在任何流量控制机制中,接收端都必须向发送端发送一些请求或信息,以便调节数据流。在许多实际使用场景中,数据的物理信道是双向的。换句话说,接收端也有一个等效的反向物理信道,可以用来发送数据。
如果确实存在这样的反向物理信道,问题就是:要不要用它来发送流量控制请求?如果这样用,这种机制就叫带内流量控制。否则,采用的就是带外流量控制。
一般说来,带外流量控制不够优雅,尤其是它需要为此设置额外的物理导线。但如果 MGT 的信道是单向的,这将是唯一的选择。
这个话题会在下面讨论 Interlaken 如何实现流量控制时再作进一步说明。
下面进入这三种技术的介绍。
XON / XOFF
XON / XOFF 代表 transmit on / off,即“发送开 / 发送关”。这是最简单的流量控制机制,但存在几个明显缺点。
这种方法的实现方式多种多样,但思想始终如一:当接收端无法再接收更多数据时,就向发送端发送某种表示“立即停止发送”的消息,这就是 XOFF。之后,当接收端又能接收数据时,再发送一个 XON,请求恢复数据流。由于这些请求很简单,它们既适合带内发送,也适合带外发送。
前面提到的所有与 MGT 流量控制有关的难点,在 XON / XOFF 方法中都会出现。第一个难点是:XOFF 请求到达发送端需要时间。在到达之前,发送端会继续发送数据。此外,当请求到达时,已经位于物理信道上的数据还会继续到达接收端。
因此,接收端必须在足够早的时刻发送 XOFF,以便随后到达的数据仍能被处理。但提前多少才算“足够早”呢?这取决于物理信道的往返时间(round-trip time)。在某些场景中,这个参数是已知的,或者至少知道往返时间很短。
例如,SATA 协议就使用 XON / XOFF。这很合理,因为该协议用于与硬盘通信,而硬盘在物理上离 SATA 控制器不远。如果链路两端之间的距离未知,而且可能很大,就很难定义发出 XOFF 的最佳时机。
另一个因素是发送端收到 XOFF 后能否立即停止。例如,如果正在发送的内容以数据包为单位,协议可能不允许在数据包中间暂停发送。在确定发送 XOFF 或 XON 的触发条件时,必须把所有这类场景都考虑进去。
还有一个问题是:如果 XOFF 消息因比特错误或物理信道的其他故障而丢失,会发生什么?如果这种情况发生,发送端可能会继续发送数据,最终引发溢出。协议必须保证:当存在 XOFF 请求可能丢失的可能性时,发送端仍然会停下来。关于 Interlaken 如何处理这个问题,下文会介绍。
总结一下这种机制:XON / XOFF 概念简单,但要用它保证永不发生溢出,却很困难,需要仔细考虑各种意外情况。下面要讨论的信用额度机制恰好是它的镜像:理解起来复杂,却容易达成目标。
限时 XOFF(Pause)
限时 XOFF 是 XON / XOFF 方法的一种变体:在发送流量控制请求时,附带一个数字。这个数字表示发送端在收到该请求后应暂停数据发送多长时间。更准确地说,它是发送端不应发送数据的时钟周期数。这种方法类似于以太网中的 Pause 帧。
这种方法的优点是之后不再需要 XON。这个特性在某些特定情境下可能有用,但即便如此,其优势也相当有限。
这里之所以提到限时 XOFF,唯一原因是:这个方法是 Aurora 协议的一部分。
信用额度(credits)
信用额度(credits)是对通信信道初始化以来允许发送多少数据元素的一种限制。接收端通过某类由协议定义的控制通道,把这个数字发给发送端。就这样,接收端控制着发送给它的数据量。
用一个简单的例子最容易说明:假设接收端缓冲器一开始可以接收 1000 个数据元素。于是它向发送端发消息说,信用额度为 1000 个数据元素。发送端可以立刻发送 1000 个数据元素,也可以稍后发送。但只要信用额度没有被更新,发送端累计发送的数据元素总数就不会超过 1000。
过了一段时间,500 个数据元素到达了接收端。在此期间,应用逻辑消费了 100 个数据元素。此时,缓冲器里有 400 个数据字,因此还空着 600 个数据元素的位置。
这时,接收端再发一条消息,把信用额度更新为 1100。这反映的事实是:已经有 500 个数据元素到达,而且,即使应用逻辑不从缓冲器取走数据,也还允许再发 600 个。因此,如果发送端自开始以来累计发送了 1100 个数据元素,缓冲器就会正好填满。也可以换一个角度看:接收端始终按照从缓冲器中消费掉的数据元素数量来增加信用额度。
这种机制能够保证不发生溢出,因为发送端永远不会发送超过接收端许可数量的数据。不过,这种方法有两个微妙的小问题。
第一个问题是:收发双方必须就起点达成一致,也就是说,把“已发送的数据元素数为零”作为计数起点。这需要某种初始化过程,让双方都把计数器复位。所有使用信用额度的协议都有某种启动过程。这使协议变得复杂,因为双方必须能够同时完成状态切换。
第二个问题是:数据流应当能够无限持续下去,而信用额度却在不断增大。怎么可能用有限位去表示这个不断增长的信用额度呢?答案是:只需要发送信用额度二进制表示的低位部分就够了。发送端则以相同位数来表示自己已经发送的数据元素数量。
这样做之所以足够,是因为发送端使用信用额度只是为了计算还允许发送多少数据元素。这个数是信用额度减去已经发送的数据元素数。计算结果不可能大于接收端缓冲器的大小。如果该数小于 2n,那么 n 个最低位以上的位全部为零,因此没有必要计算它们。只用 n 位做减法就够了。因此,流量控制请求中只需要携带信用额度二进制表示的低 n 位。
例如,PCIe 的流量控制根据所保护的缓冲器类型,用 8 位或 12 位的二进制字来发送信用额度。
使用信用额度有不少优点:
- 如果一条流量控制请求丢失,也不会引发溢出风险。信用额度给出的是一种“允许发送更多数据”的许可,而 XON / XOFF 则是“要求停止”。因此,如果信用额度更新丢失,发送端只会发送得比允许的更少,不会导致溢出。
- 信用额度机制即使面对未知且可能很大的物理信道延迟,也依然高效。它不需要为了补偿流量控制请求的延迟而提前暂停数据流。
- 发送端能够提前知道,自己是否可以完整地发送一个数据包,而不至于中途因为流量控制而停下来。因此可以确保数据包连续发送。
Interlaken 的流量控制
Interlaken 协议在另一页中已经做过简要介绍。
为了讨论流量控制,请注意:该协议为每个数据突发分配一个通道号(通常为 0 到 255)。换句话说,该协议基于这样的概念:多个应用数据流共享同一条物理信道。因此,它的流量控制机制对每个应用数据流独立进行控制。
该协议提供两种流量控制机制:带内流量控制和带外流量控制(out-of-band flow control,OOBFC)。两者都属于 XON / XOFF 机制。每个通道的 XON / XOFF 状态由一个比特表示:当该通道准备好接收数据时,这一位为“1”(XON),否则为“0”(XOFF)。
这两种机制的差别在于这些状态位如何传输。带内流量控制依赖这样一个事实:每个突发的两侧各有一个控制字。这个控制字共 64 位,其中 16 位用于流量控制。这样一来,每个控制字最多可以携带 16 个 XON / XOFF 请求。然而,这不足以支撑最多 256 个通道——每个通道都有自己的 XON / XOFF 位。为了解决这个问题,流量控制信息被拆分到多个控制字中。这种方法叫作“日历”(calendar)机制。控制字中的第 56 位称为 Reset Calendar。当该位为“1”时,当前控制字中的 XON / XOFF 请求对应通道 0 至 15。紧接着的下一个控制字包含通道 16 至 31 的请求,依此类推。当所有通道都覆盖过一次后(也可能一个控制字就够了),再借助 Reset Calendar 重新开始循环。
如果通过突发的 CRC24 检测到比特错误,所有通道都会进入 XOFF 状态。原因是:CRC24 同样覆盖了控制字,所以一旦检测到错误,控制字中的流量控制部分就不能再被信任。因此,在发生这种事件之后,在任何通道上发送数据都不安全。正常操作随后会依据后续控制字中的流量控制请求逐渐恢复。
带内流量控制的主要缺点是:XON / XOFF 请求能否及时送达,取决于同方向发送的数据突发。如果这些突发很长,或者一段时间内完全没有发送任何突发,XON / XOFF 请求的送达就可能延迟。这在某种程度上情有可原,因为同一物理信道上传输的数据本来就会与流量控制消息竞争带宽。不过,其他协议通常会给予流量控制消息一定优先权,以保证一个稳定的最大延迟。
带外流量控制(OOBFC)则避免了与数据突发竞争带宽的问题。这种方法通过三根额外的物理导线(FC_CLK、FC_DATA 和 FC_SYNC)来传输 XON / XOFF 请求。所有通道的 XON / XOFF 位都在一个长帧中传送。
FC_SYNC 在帧的第一个比特期间为高电平。FC_CLK 的频率在 0 至 100 MHz 之间,并允许 DDR 时钟。CRC-4(4 位 CRC)每传输 64 个 XON / XOFF 请求之后插入一次,或在最后一个请求之后插入。如果通过 CRC 检测到错误,所有通道进入 XOFF 状态。
至于选择带内流量控制还是带外流量控制,当然取决于项目自身的需求。
Interlaken 不包含任何通道数据复用机制。因此,仲裁各通道的发送请求、决定由哪个通道获得发送一个突发(或整个数据包)的机会,都是应用逻辑的职责。当一个通道因 XOFF 而需要暂停发送时,也由应用逻辑负责执行暂停。实现 Interlaken 协议的逻辑只负责发送和接收 XON / XOFF 请求。
该协议还把信用额度列为实现流量控制的一种可能方法,但这只是一句开放性的建议:可以通过专用通道来实现这种方法。协议并没有给出任何实现细节。
Aurora 的流量控制
Aurora 协议在另一页中已经做过简要介绍。该协议提出了两种有助于实现流量控制的机制:NFC 和 UFC。下面分别说明。
第一种是原生流量控制(Native Flow Control,NFC)。在这种机制下,接收端的应用逻辑有一个向发送端发送流量控制请求的接口。这些请求由两个独立部分组成:一个 XOFF 位和一个限时 XOFF(“pause”)部分(这两个概念上面都已解释过)。发送端的协议逻辑负责执行这些请求:如果 XOFF 位为“0”,发送端将暂停数据发送若干时钟周期,具体数值由流量控制请求中所携带的 8 位数决定。若该数为零,则立即恢复数据发送。请求中的数值不会累加;每个 NFC 请求都用一个新值更新暂停倒计时。
如果 XOFF 位为“1”,发送端将无限期暂停数据发送。只有收到 XOFF 为“0”的流量控制请求后,发送端才会恢复发送。对这类请求的处理方法与上面所说相同。
注意,这种流量控制机制控制的是物理数据信道上的全部数据流量。因此,如果实现了多个通道,NFC 并不适合用来对每个通道单独进行控制。
第二种机制是用户流量控制(User Flow Control,UFC)。实际上,它是一个向对端发送最长 256 字节消息的独立通道。UFC 消息的优先级高于普通数据,因此能够以较低延迟到达对端。
由于该协议不定义这些消息的格式,你可以用它们实现任何类型的流量控制。这种实现完全由应用逻辑来完成。UFC 消息当然也可以用来传递任何其他种类的状态信息。
注意,以上两种流量控制机制所用的消息,都不受保护以抵御物理链路上的比特错误。流量控制请求因此可能错误地到达,甚至根本不能到达,从而可能导致接收端发生溢出(overflow)。
这两种流量控制机制都要依赖反方向的数据链路,因此只能在全双工模式下使用。
总结
本页主要介绍了两种流量控制技术:XON / XOFF 和信用额度。对于与计算机有关的协议(例如 PCIe、SuperSpeed USB 和 SATA),流量控制由协议逻辑负责实现。与之相反,在用于 FPGA 间通信的协议中,全部或大部分流量控制实现工作都落在应用逻辑肩上。唯一的例外是 Xillyp2p,它负责处理数据通信的所有方面,包括流量控制、差错检测和重传。
本页还介绍了两种面向 FPGA 的协议——Interlaken 和 Aurora——在流量控制方面的相关特性。如你所见,尽管这些协议自身的某些功能在特定使用场景下能承担一部分工作,但实现流量控制的主要负担仍然留给了应用逻辑。
关于 MGT 的本系列文章到这里就全部结束了。