接口技术【4】PCIe入门简介 -- PCIe数据传输模型
三层传输模型
网络传输有七层模型(现在一般叫几层不记得了),PCIe也定义了层级结构用来传输数据。每层都可以被分为两部分,发送和接收。这种分层结构对于硬件设计来说可以轻松升级某一层而不需要影响其他层,只要你把几层的逻辑区分的够干净。尽管如此,层只是定义了它的责任,并不要求必须在逻辑上把几层分离开。
下图是PCIe的传输层模型,各层的任务分别如下
- Device core设备核心是设备的核心功能,如果这个设备是终端,它最多有8个功能,每个功能有自己的配置空间。如果这个设备是switch,则它有数据包路由功能。如歌这个设备是root,它作为虚拟PCI总线0,所有嵌入式终端和虚拟总线都包含在其中
- Transaction Layer传输层,这层负责在发送端生成Transaction Layer Packet (TLP)传输层数据包,以及在接收端解码TLP数据包。这层同时负责服务质量,流控制和传输次序
- Data Link Layer数据链路层,这层负责在发送端包装TLP或者生成Data Link Layer Packet (DLLP)数据链路层数据包,以及在接收端解码DLLP数据包。这层也被称作Ack/Nak协议层
- Physical Layer物理层,这层负责在发送端生成Ordered-Set有序数据包,以及在接收端解码有序数据包。这层要同时处理三种数据包,TLP、DLLP和有序数据包。在发送端对数据包做byte striping字节剥离,scramble拼凑,8b/10b编码(第一代或者第二代0)或128b/130b编码(第三代),和数据包并转串,最终把数据送出。相应的,接收端做完全相反的步骤,不过多出一个Clock and Data Recovery (CDR)时钟和数据恢复电路,用来生成时钟。
每个PCIe接口都支持全部的层,包括如下图所示的switch。Switch需要支持所有层的原因是,它需要解码数据包的内容来决定要把数据包路由到它下面的哪个分支中。
原则上来说,每一层相当于在和另外设备的相应层联络,方法是数据包中的特定数位,数据包经由其他层来传送。
在继续深入之前,先看看各层如何互动。要输出的数据内容递交给传输层,加入目标地址等信息包装成TLP,然后传入一个缓存栈。当TLP被送到数据链路层时,添加更多的信息生成Link Packet,比如校验位,同时保存一份拷贝以免传送失败需要重传。最后传送到物理层,添加头尾识别,接受编码后并转串,随后通过差分信号输出。
接收方的物理层接收到差分信号,通过串转并和解码后恢复出物理层数据包,掐头去尾拿出有效信息传给数据链路层。数据链路层检查末尾的校验位,如果没有错误则再相应的掐头去尾传给传输层的缓存栈。传输层再检查相应的校验位后提取有效信息,最终传给设备核心。
从这里开始深入分解层结构。
设备核心/软件层
这是设备功能的核心,比如网络接口或者硬件驱动。软件层往往不被当做PCIe中的一层,不过我们可以把它当做最顶层。它提供需要传输出去的信息,比如传输类型、地址、数据量之类的,也是数据包传输的终点。
传输层
收到软件层的请求后,传输层会生成数据包传出,它也会检查接收到的数据包,然后将其中的内容传递给软件层。对于非报告传输non-posted transaction,它支持分离传输协议split transaction protocol,即数据没准备好时先返回一个split信号,之后再申请总线传回数据。
TLP数据包可以分为四种:
- Memory 存储
- IO 输入输出
- Configuration 配置
- Messages 消息
前三种是PCI和PCI-X支持的,第四种是PCIe新增的。一个完整的传输流程是从发送端到接收端的请求数据包,以及从接收端到发送端的完成数据包的组合。请求数据包分为下面几种:
请求类型 | 报告或非报告 |
---|---|
Memory Read | 非报告 |
Memory Write | 报告 |
Memory Read Lock | 非报告 |
IO Read | 非报告 |
IO Write | 非报告 |
Configuration Read (Type 0 and Type 1) | 非报告 |
Configuration Write (Type 0 and Type 1) | 非报告 |
Message | 报告 |
对于报告和非报告请求,它们的区别是,非报告请求的发送端送出一个数据包,接收端生成一个应答,传回一个完成数据包;报告请求的目标设备不会传回应答。
非报告请求很像PCI-X中的分离传输协议。所有读取操作都是非报告请求,其他需要验证是否收到并且无错误的写入操作也是非报告请求。
报告请求的效率更高一些,因为它不需要等待完成数据包,但他的缺陷是发送端收不到回馈。这种形式是从PCI继承下来的,可以保持兼容性,并且出错的概率并不高。
TLP数据包基础
PCIe请求数据包和完成数据包的类型如下所示
TLP数据包类型 | 简称 |
---|---|
Memory Read Request | MRd |
Memory Read Request - Locked access | MRdLk |
Memory Write Request | MWr |
IO Read | IORd |
IO Write | IOWr |
Configuration Read (Type 0 and Type 1) | CfgRd0, CfgRd1 |
Configuration Write (Type 0 and Type 1) | CfgWr0, CfgWr1 |
Message Request without Data | Msg |
Message Request with Data | MsgD |
Completion without Data | Cpl |
Completion with Data | CplD |
Completion without Data - associated with Locked Memory Read Requests | CplLk |
Completion with Data - associated with Locked Memory Read Requests | CplDLk |
TLP数据包组成
如下图所示,红色部分是设备核心(软件层)的数据传到传输层后包装成的TLP数据包。
每个TLP都有header,虽然有些是空的。末尾可选择的加入点对点校验位ECRC,CRC是Cyclic Redundancy Check循环冗余校验位,可以轻易并可靠地检测错误,使串行传输更可靠。
在传输过程中,TLP被传输给数据链路层,在那里数据包会再加上序列号和链路校验位LCRC,相邻的接收端会使用LCRC检查数据包是否正确。有一个问题是,如果强制的LCRC已经检查过数据包了,为什么还需要ECRC再检查一次?原因是,路由经过一些设备时,先检查了LCRC并去除,发送出时重新计算LCRC并加入数据包,这个过程中如果出现错误,就需要ECRC进行检测。
最终数据包被传到物理层,加入可以让接收端辨别头尾的start和end。在第一代和第二代,头尾只是写控制字符,到了第三代,头尾变成了和数据包相关的信息。在这之后数据包被编码并差分送出。
TLP数据包解构
如下图所示,相邻的接收端收到数据包时,相应层会识别其对应的头尾,并剥离出初始信息传给上层。
非报告传输
普通读取
如下图所示,读取的地址为32位或者64位,决定了数据包的路由路径。当总线解码出请求类型并识别地址,它会读取相应数据并把它传回到发送请求的设备。为了把数据送到相应设备,总线会生成相应数量的CplD数据包,每个最大为4kb,不过设备往往使用更小的数据包,这时为了传输大量数据就需要更多数据包。
这些CplD数据包包含路由信息,可以将它导向发送请求的设备;发送请求的设备也会在MRd中包含回传地址。这个回传地址就是设备的ID,包含PCI总线号,设备号和功能号。这个BDF(Bus, Device, Function number)信息就是CplD回传的根据。另外一个设备可能同时有多个分离回传的任务,会接到不同种类的CplD,因此CplD中还包含和发送的请求相应的任务号,如此接收设备就不会混淆不同读取请求传回的数据。
接到请求的completer会调高特定数位显示有错误情况发生,让发送请求的requester能做出相应调整。
锁定读取
锁定存储读取是为了支持Atomic Read-Modify-Write操作,这是一种不会被中断的传输方式,针对测试和设置信号一类的任务。在这类任务进行过程中,CPU会激活一个锁定信号,在这个锁定信号消失之前,其他传输不能进行。
在PCI早期,设计者预期PCI会取代处理器总线,因此锁定传输之类处理器需要的特性就被包含在其特性内。然而PCI很少有这样的用法,最终很多支持PCI取代处理器总线的特性被取消,锁定传输却被保留了下来,用来支持一些特殊情况。总之PCIe虽然继承了这个特性,但只有在很特殊的情况下才使用,
IO和配置写入
下图表示了非报告IO写传输,它从root发起请求,由switch路由到一个IO地址,完成后返回一个完成数据包Cpl,但其内部没有数据。
报告写入
存储写入
存储写入操作一直都是报告的,从来不会接受完成应答。一旦请求被发出,发送端就不会等待任何回馈而是直接进行下一个操作,如此可以让写入操作更快更有效。虽然没有回馈无法看到是否有错误报告,但completer会向root报告,只是requester看不到
Message写入
和其他的请求不同,message write有多种不同的路由方式,有些是路由到特定设备终端,有些是从root广播到所有终端。这种操作能帮助PCIe实现降低引脚数量的目的。它被用来传递中断、功率管理和错误信息,这些原先在PCI中是用特定引脚来传递的,如此就能较少引脚,因为这些信息可以通过普通数据引脚传递。
Quality of Service (QoS)
PCIe从设计之初就是为了传递对时间敏感的信息,比如音频流和视频流必须实时传递。
- 软件层会给每个数据包标定3个数位的优先级Traffic Class (TC)。一般来讲这个数字越高优先级就越高
- 系统中内嵌多个缓存栈Virtual Channels (VC),数据包根据其优先级TC放入不同的VC
- 由于有多个VC可用,需要一个仲裁决定将数据包放入哪个VC
- Switch在有多个输入时需要取舍进行哪一个
传输排序
在VC中,数据包通常以他们输入的顺序输出,但偶尔也有例外。PCIe继承了PCI的传输顺序模型,包括relaxed-ordering case。这些排序规则保证了使用同一路径的数据包在经过拓扑结构之后还保持正确顺序。由于排序规则只适用于VC中,并且不同优先级TC的数据包不会进入到同一个VC,因此不同优先级TC的数据包之间没有排序需求。
流控制
通常串口通信的协议都要求发送端只有在接收端的栈有充足空间时才传送,如此就可以省去一些影响性能的disconnect和retry数据包。这么做的缺陷是接收端必须经常报告自己缓存栈的剩余空间,占用一定的带宽。在PCIe中,这种栈空间报告是由DLLP数据链路层数据包完成的,下一段就会看到。之所以在数据链路层而不是传输层,是为了防止传输层因为VC满了而接受不到缓存栈空间信息。流控制是由硬件自行控制,并对软件透明的。
数据链路层
这一层的主要任务有三个:
- TLP误码纠正
- 流控制
- 链路功率控制
实现方式是通过DLLP数据包
DLLP数据包
DLLP数据包只在相邻设备的数据链路层之间传输,传输层不会看到DLLP
DLLP数据包组成
DLLP是由数据链路层产生的,不包含从传输层传下来的数据。尾部是16位的CRC校验位,用来检查DLLP传输是否正确。DLLP包装好后送到物理层,加上头尾后送到接收端
DLLP数据包解构
接收端的物理层收到后掐头去尾,然后就可以吃了,嘎嘣脆鸡肉味。送给数据链路层,用CRC检查是否有误就可以提取相应DLLP信息
Ack/Nak协议
纠错功能如果发现错误就会启动重传。就如下图所示,从传输层传出的TLP中包含LCRC和序列号,并在接收端被检查。发送端的重传缓存会保存一份TLP的拷贝,直到接收端收到该TLP,检查无误并传回一份确认。这个确认信息就是Ack DLLP,接收端把相应序列号和确认信息一起放进Ack中,传回给发送端;发送端看到后从重传缓存中把相应序列号及其之前的数据包的拷贝删除。
如果接收端检测到误码,它会消除这个TLP数据包并传回一个Nak数据包给发送端,要求其重传。这个过程就是Ack/Nak协议
下面是一个例子,requester发送一个数据读取请求MRd,经过switch以后到completer,传回数据CplD:
- 1a. requester发送一个MRd TLP给switch,同时在重传缓存中保存一份拷贝,switch收到后检查LCRC和序列号
1b. switch检查无误,传Ack DLLP回requester,requester从重传缓存中删除相应数据包 - 2a. switch根据地质把MRd TLP路由给相应的completer,同时在重传缓存中保存一份拷贝,completer收到后检查LCRC
2b. completer检查无误,传Ack DLLP回switch,switch从重传缓存中删除相应数据包 - 3a. 作为这个请求的终点,completer检查了MRd TLP中可选的ECRC。检查无误交给设备,设备把需求的数据准备好后包装传回一个CplD TLP数据包,同时在重传缓存中保存一份拷贝,switch收到后检查LCRC
3b. switch检查无误,传Ack DLLP回completer,completer从重传缓存中删除相应数据包 - 4a. switch从CplD TLP解码了requester ID,把数据包路由到正确的requester,同时在重传缓存中保存一份拷贝,requester收到后检查LCRC
4b. requester检查无误,传Ack DLLP回switch,switch从重传缓存中删除相应数据包。requester检查TLP中可能存在的ECRC,无误后交给设备
流管理
数据链路层的第二个功能是流管理。在启动或者复位后,流管理被硬件自动启动并实时更新。其内容就和前面说的一样,向相邻的发送端实时播报自己缓存栈的空余情况
功率控制
这部分以后再细讲,总之有这个功能
物理层
物理层是PCIe层结构中的最底层,其内部分为数字逻辑部分和模拟部分。数字逻辑部分包括数据包的编码和解码,模拟部分负责把数据包用差分形式输出
物理层逻辑
TLP和DLLP进入物理层被加上头和尾,方便接收端标定数据包的边界,也被称作框架字符。
在这一层中,数据包中的每个byte被分散到所有被使用的lane中(每个link包含多条lane),这个过程被称作byte striping字节分离。每个字节也被打乱(scramble)来减少重复性。对于第一代和第二代的PCIe,采用了8b/10b协议来编码,第三代采用了128b/130b协议编码,防止了有大量重复的0或者1出现。编码后由并行转为串行并查分输出。
在接收端,如果用的是8b/10b编码,则接收到的串行信号被分割成10bit为单位的symbol然后解码成8b的字节,128b/130b会更复杂一些。在经过反打乱(de-scramble)后,所有原始数据就被恢复出了
链路初始化
物理层的另一个任务是链路初始化。就像之前介绍的一样,一个link最多可以有32个lane,也有多个速度可选,链路初始化需要审视这些选项并选择最适合的。需要考虑的条件有很多,有链路宽度、链路数据速率等
物理层的电磁学
实际的link发送端和接收端是由下图所示的AC耦合连接的。AC耦合意味着两个设备之间有电容,只能通过交流信号而阻断直流信号。很多串口都用这种连接方式,因为它允许发送端和接收端拥有不同的基础电平。这个在设备比较小的时候问题不大,但当它们分布在两个不同的建筑中时就很有必要了。
顺序配置
最后一种在设备间传输的信号是顺序配置,只针对物理层。它没有头字节和尾字节,因此不能被叫做数据包。它始于发送端的物理层,终止于接收端的物理层。在第一代和第二代PCIe中,顺序配置以一个COM字符开始,接着3个或者更多字符。它的长度总是4byte的倍数,在链路初始化时会用到这个,用来检测发送端和接收端时钟的微小差别。另外也被用来控制链路进入或者退出低功率模式。
总结
到这里介绍了PCIe的结构和传输层模型。下一篇开始要更深入到PCIe的细节