接上文:PCIe学习(一)
2 事务层
事务层需要完成的事情:
- TLP的生成(发送)和解析(接收)
- TLP排序机制
- 基于信用值的流量控制机制
- 数据中毒(Data Poisoning)和ECRC完整性检测
事务层向上要与应用层通信,不同设备的应用层天差地别,在做芯片设计时要根据具体情况而定。PCIe规范没有对应用层提出约定。
2.1 地址空间
PCIe协议中有四类地址空间,对应四种请求事务,如下表:
前三种是PCI协议中已有的事务类型,最后一种是PCIe协议中增加的事务类型。
2.1.1 存储事务
存储事务最常用,支持32-bit和64-bit两种地址格式,包括以下这些事务:
- 读取请求,读取完成(Read Request/Completion)
- 写入请求(Write Request)
- 原子请求,原子完成(Atomic Request/Completion)
2.1.2 I/O事务
I/O事务是PCI时代的产物,PCIe规范中支持I/O事务只是因为需要兼容已有的PCI设备。PCIe原生设备本身不再支持I/O事务,既不会发出I/O请求,也不会响应主机的I/O请求。
I/O事务仅支持32-bit地址格式,包括以下这些事务:
- 读取请求,读取完成(Read Request/Completion)
- 写入请求,写入完成(Write Request/Completion)
在后续的学习中,可以暂时忽略I/O事务。
2.1.3 配置事务
配置事务是主机(也就是RC)用来配置Function,访问Function配置寄存器。在系统平台上电后,所有的PCIe链路自动完成链路训练(Link Training);随后主机软件(不区分固件/设备驱动/操作系统,统称为软件)对PCIe拓扑结构进行探索,已发现所有的可用设备,并分配唯一标识ID,这一过程称为“枚举(Enumeration)”;必要时,主机软件需要通过配置事务来请求PCIe设备开启或禁用某些功能,配置设备参数等等。
配置事务包括以下这些事务:
- 读取请求,读取完成(Read Request/Completion)
- 写入请求,写入完成(Write Request/Completion)
只有RC才有权限发出配置请求事务;EP或者Switch只可以接收配置请求事务,不允许发出配置请求。
2.1.4 消息事务
PCI时代有大量的引脚信号用于设备通信,这些引脚信号称作“边带(Side-Band)”信号。PCIe协议为了简化接口的引脚,将这些边带信号全部拿掉,设备间需要靠“带内(In-Band)”消息来通信。
消息事务是PCIe规范中新增加的事务类型,也就是说PCI设备不支持此类事务,如果系统中有PCI设备,则需要PCIe-PCI桥接设备做转换。
除去PCIe规范定义的消息类型,还支持厂商自定义消息,需要厂商自行定义和实现,并在其驱动软件中加以支持。
2.2 事务类型
上面是从地址空间出发,对PCIe事务进行了分类。接下来我们从事务发送和完成的角度看一下这些事务。
先区分几个概念。事务的发起者叫做请求者(Requester),事务的响应者叫做完成者(Completer)。有些事务需要完成者给请求者一个完成响应(Completion),将事务完成的状态通知请求者;而有些事务则不需要完成者返回完成响应,请求者发出事务以后即认为事务已经完成。不需要完成响应的事务是Posted类型,需要完成响应的事务是Non-Posted类型。所谓Posted,就像是寄信件一样,当我们把邮件放入邮筒的那一刻起,就标志着信件已发出。至于信件最终是不是会送达到收件人手中,需要邮局系统来保证。在PCIe协议中,这一保证机制是由数据链路层的ACK/NAK协议来完成。对于Non-Posted类型来说,如果请求者在发送完请求事务后一直等待完成者返回完成事务,必然造成链路上的停顿,白白浪费了链路资源。因此PCIe协议规定,请求者发送完一个Non-Posted类型的事务后,可以继续发送其它的事务,而不必一直等待完成者的完成事务。这一过程也称为分离事务(Split Transaction),即请求和完成分离开来。
需要注意的是,请求者不一定就是RC,完成者不一定就是EP或Switch。RC也可以是完成者,EP或Switch也可以是请求者。只有在发送配置事务时,RC与请求者是一致的,EP/Switch与完成者是一致的。
用表格总结一下事务的类型:
为了简化学习,可以忽略表中的IO Read/Write。
对于存储读取请求(MRd)和配置读取请求(CfgRd),必然需要有相应的完成响应,否则完成者怎么返回给请求者需要的数据呢。也就是说MRd和CfgRd是Non-Posted类型。参考下图,是一个存储读取请求的例子,首先是EP发起MRd,经过Switch B和A到达RC;RC从相应的内存中将数据取出,并通过CplD将数据发送给EP。
配置写入请求(CfgWr),是RC发给Switch或者EP的,因为Switch或EP的功能取决于该设备是否被配置成功,所以Switch或EP必须返回完成响应给RC,这样RC才能进行后续的操作。CfgWr也是Non-Posted类型。
IORd和IOWr是Non-Posted类型,不再展开了。下图是IOWr的例子。
最后,只有存储写入请求(MWr)和消息请求(Msg/MsgD)是Posted类型,请求者发出请求即认为请求已完成,这样可以大大提高链路效率。至于请求是否真正被发送到了完成者,需要其它的机制保证,后面会介绍。
细心的读者可能发现了一个问题,如果请求者发送了多个MWr或者是多个Msg,那么完成者是以什么顺序接收到的呢?这涉及到PCIe规范中的事务顺序问题。其实这个问题牵扯到所有事务的顺序,后面会详细讲。
2.3 事务层协议-数据包格式
TLP的串行格式如下图所示,包括TLP前缀(Prefixes),TLP包头(Header),有效数据负载(Data Payload)和TLP摘要(Digest)。其中只有TLP Header是必选项,其它三项都是可选项,根据数据包的实际需求而定。PCIe协议是串行总线,单比特传输,也就说发送端的物理层完成TLP并/串转换后,从TLP Prefixes(如果有的话)开始传输,最后传输TLP Digest(如果有的话)。这样做的一个好处是接收端可以提前知道这个数据包的类型,而不用等待全部接收完数据包。
构成TLP的四部分里面,除了TLP Header,其它的都不是必选项,甚至是数据部分。TLP里面最为复杂的也是Header。
TLP的并行格式如下图所示,PCIe协议里面用DW(Double Word,32-bit)来表示并行结构。后面会反复用到DW。
从上图可以看出,长度固定只有Digest部分,为1-DW。其它的三部分,长度都不固定,但都是以DW对齐的。
2.3.1 TLP Header
前面提到过,存储读取/写入事务有两种地址格式:32-bit和64-bit。这个地址包含在TLP Header中,所以对应的有两种TLP Header格式。32-bit地址的Header是3-DW长度,64-bit地址的Header是4-DW(多出来的1-DW正好对应多出来的32-bit地址),如下图。
所有类型的TLP,其Header中的第一个DW格式都是相同的,而第二个DW中的字段随TLP类型不同而变化。
2.3.1.1 通用的Header字段
不管是哪种类型的请求,TLP的第一个DW,也就是前4 Byte格式是通用的,见下图。
其中各字段的含义如下:
- Fmt[2:0]:TLP的格式,指示TLP的地址格式,或者是否为Prefix
- Type[4:0]:TLP的类型,Type字段通常与Fmt字段一起编码,来表示TLP的格式
- TC[2:0]:流量等级(Traffic Class),PCIe协议支持最多8个TC值,通过TC来区分TLP的优先级,从而完成TLP保序和QoS的功能
- T8 & T9:Tag[8]和Tag[9],最初的Tag只有8-bit,后来增加了2-bit。Tag[7:0]在Header的第二个DW中。此字段由请求者分配,用于区分不同的具体请求,完成者返回完成响应时需带上Tag,这样请求者就知道哪些发出去的请求已经完成,哪些尚未完成
- Attr[2:0]:3-bit的属性定义,这三个字段有各自的含义,并没有一起做编码,其中Attr[2]表示基于ID排序(ID-Based Ordering),Attr[1]表示松散排序(Relaxed Ordering),Attr[0]表示非监听(No Snoop)。
- LN:轻量级通知(Lightweight Notification),这是Gen 4中加入的特性,在Gen 6中被取消
- TH:TLP处理提示(Processing Hints)
- TD:TLP Digest,指示TLP中是否存在有ECRC部分
- EP:错误中毒(Error Poisoned),指示TLP Data部分的数据负载是否为“中毒”
- AT[1:0]:地址类型(Address Type),即地址是否经过转换,仅对带有地址的事务有效,如MRd,MWr,AtomicOp。对其它的事务,该字段保留。在轻量级通知中,该字段有其它含义
- Length[9:0]:TLP Data部分的大小,数据是DW对齐的,如果实际数据是Byte,则需要借助Header中的其它字段指示。
本节先详细分析一些常用字段,有些不常用字段跟某些特性功能有关,留待后面章节分析。
Fmt[2:0]的编码如下表所示:
Fmt字段通常和Type字段一起,表示TLP类型,这两个字段是Header中最重要的字段。
链路双方可以借助TC字段实现事务的保序要求。PCIe协议规定,相同TC值的事务需要遵守严格的先后顺序(不开启基于ID排序和松散排序,即不使能Attr[2:1])。同时,PCIe协议还规定,不同TC值的事务之间不需要保序。如果事务的发起方对于事务有顺序要求,则需要将这些事务分配相同的TC值。借助TC,还可以实现QoS功能,不过这需要硬件逻辑配合,放到QoS章节详细讲解。
前面说过,为了提高链路性能,PCIe将Non-posted类型的请求拆分,请求者发出一个请求后即可以继续发送请求,而不必等待完成响应。也就是说请求者可以连续发送多笔请求,比如可以连续发送若干的MRd。至于具体数目,取决于硬件设计的超发(Outstanding)缓冲区深度。此时的一个问题是,这么多超发请求,完成者返回完成响应时,请求者怎么知道对应的是哪笔请求。这就需要请求者给每个请求事务分配一个唯一的标识符,完成响应中带上这个标识符,请求者就知道对应哪笔请求了。这个标识符就是Tag。可能有的人会问,对于Posted类型的请求,比如MWr,不需要完成响应,是不是Tag字段就没有用了呢?的确如此,不过这时PCIe协议对Tag字段另有用处。
AT[1:0]字段表示地址类型,编码如下表。后面讲地址翻译服务ATS时会用到。
Length[9:0]字段的意思比较简单,就是TLP Data的长度。数据是以DW对齐的,PCIe协议规定,数据最大可以是4KB。在实际应用中,Data的最大长度需要链路双方协商而定,很多时候并不是4KB。
2.3.1.2 存储事务的Header
存储事务支持32-bit地址和64-bit地址两种格式,其完整的TLP Header格式定义如下图。
DW 1,2,3中增加的字段有:
- Requester ID[15:0]:请求者ID,这是主机分配的PCIe拓扑结构中的唯一值。Requester ID字段与Tag字段合起来组成了事务ID(Transaction ID)
- Tag[7:0]:事务标签,前面介绍过了
- Last DW BE[3:0]:数据负载中最后一个DW中的字节使能,用于存储,I/O,配置请求
- First DW BE[3:0]:数据负载中第一个DW中的字节使能,用于存储,I/O,配置请求
- Address[64:2]:地址,最低两位强制是00b
- PH[1:0]:处理提示(Processing Hint),需要配合TH字段使用。如果TH无效,则PH[1:0]保留;TH有效,则PH字段编码表示处理提示,具体含义放到TPH功能部分讲解
这里需要解释一下Last DW BE[3:0]字段和First DW BE[3:0]字段。前面一直在说,TLP Data是DW对齐的,如果实际数据不是DW对齐,则可以借助这两个字段,指出实际数据的起始和结束位置。细心的人可能会问,对于一大段中间有空洞的数据段,该怎么处理?有两个办法,一是将这段数据拆开,用多个TLP传输;二是主机软件或设备软件知道哪些数据无效,在一个TLP将整个数据段传输,然后通过在软件中处理这段数据。
2.3.1.3 I/O事务的Header
I/O事务仅支持32-bit地址(在PCI时代不需要64-bit地址),其完整的TLP Header格式定义如下图。
PCIe协议对I/O事务TLP Header中的字段有一定规则:
- TC[2:0]字段必须是000b
- LN字段不适用,保留
- TH字段不适用,保留
- Attr[2]保留
- Attr[1:0]必须是00b
- AT[1:0]必须是00b
- Length[9:0]必须是00 0000 0001b
- Last DW BE[3:0]必须是0000b(I/O事务只有一个DW的数据负载,这个字段没有意义)
2.3.1.4 配置事务的Header
配置事务采用ID路由,因此不需要地址信息,其完整的TLP Header格式定义如下图。
第三个DW中增加的字段有:
- Bus Number[7:0]:总线编号
- Device Number[4:0]:设备编号
- Fcn Number[2:0]:Function编号,与上面两组编号合起来称为BDF,是PCIe拓扑结构中的唯一标识
- Register Number[5:0]:寄存器编号,与下面的字段一起使用,指示要访问的配置空间中的寄存器地址
- Extended Register Number[3:0]:扩展寄存器编号
配置事务对TLP Header中各字段的限制:
- TC[2:0]必须是000b
- LN字段不适用,保留
- TH字段不适用,保留
- Attr[2]保留
- Attr[1:0]必须是00b
- AT[1:0]必须是00b
- Length[9:0]必须是00_0000_0001b
- Last DW BE[3:0]必须是0000b
2.3.1.5 消息事务的Header
消息事务的路由方式有多种,可以是地址路由,ID路由,隐式路由,其完整的TLP Header格式定义如下图。
新增加的字段是Message Code[7:0],表示消息编码。对于不同的消息类型,DW 2和3(即Byte 8-15)的含义不同。
Type字段的后三位,是消息事务的路由方式,编码见下表。
消息编码整理如下:
对于每种类型的消息,TLP header的Byte 8-15各不相同,这里不再赘述,后面用到哪种消息再具体分析。
2.3.1.6 完成事务的Header
对于Non-Posted类型请求,需要完成者返回完成响应事务。完成事务是ID路由方式,其完整的TLP Header格式定义如下图。
新增的字段有:
- Cpl Status[2:0]:完成状态
- BCM(Byte Count Modified):仅对PCI完成者有意义
- Byte Count[11:0]:请求的剩余字节数
- Lower Address[7:0]:低位地址,对于存储读取请求,该字段是完成事务返回的第一个数据的第一个启用字节的地址
Cpl Status字段的编码见下表:
2.3.2 TLP Prefix
TLP Prefix分为两种,Local TLP Prefix和End-End TLP Prefix。
Prefix的用途是扩展TLP。在PCI时代,TLP的格式就规定好了,随着技术的不断进步,协议中需要添加更多的扩展字段以用于扩展功能。聪明的协议制定者们想出了给TLP加前缀这种方式。在TLP前面添加扩展字段,既不影响旧有的设备和驱动软件,又能增加新特性。
一个Prefix长度是固定1 DW,格式定义如同TLP Header的第一个DW的格式定义。
Fmt字段和Type字段编码指示是哪种Prefix。
Type[4]用来区分是本地Prefix(0b)还是端到端Prefix(1b)。
目前在Gen 5.0中,本地Prefix有以下几种:
端到端的Prefix有以下几种:
除去上面的几种端到端Prefix,还有一种IDE Prefix,用于数据完整性和加密功能。IDE(Integrity and Data Encryption)功能是Gen 5中以ECN(Engineering Change Notice)形式发布的,在Gen 6的基本规范中正式引入。
不同类型的Prefix,Byte 1-3的含义不同,以后用到的时候再具体分析。
2.3.3 TLP Data
Data部分格式很简单,前面已经讲过很多了。Header中的Length[9:0]字段指示数据的大小,编码如下:
大家可以想想,为什么00_0000_0000b表示的是1024 DW,而不是0 DW呢?因为带不带数据是事务类型决定的,所以不需要表示0 DW。
在实际的设备中,很可能不会支持4KB这么大的数据负载。而且,对于存储读取事务的完成事务,PCIe协议规定数据为64-Byte或128-Byte,称为“读取完成边界(Read Completion Boundary,RCB)”。
关于Data的具体规则,这里不详细列出了。
2.3.4 TLP Digest
TLP Digest也叫做ECRC(End-End Cyclic Redundancy Check),如果启用此功能,则PCIe设备对所有发出的TLP增加一个DW,用于端到端的循环冗余校验。
端到端连接是一个网络连接术语,在PCIe中指的是请求者与完成者的连接,不管中间是不是有Switch。点到点连接指的是链路两端的直接连接,比如RC与Switch,Switch与EP的连接。
ECRC顾名思义,完成者负责最后用CRC对TLP其余部分做校验,中间的Switch会将ECRC直接转发(虽然允许Switch检查ECRC,如发现错误后上报错误,但Switch一般不会这么做)。
能看出来,ECRC解决的不是链路传输错误问题,实际上链路传输错误是通过链路CRC(Link CRC,LCRC)来检查的。这会放到数据链路层中介绍。
那么ECRC是用来干什么的呢?在事务层生成TLP一直到送给物理端口发出,中间的过程可能会存在逻辑错误,ECRC就是用来检测这个错误的。
具体的CRC算法就不介绍了。
【待续】
作者:老秦谈芯
文章来源:老秦谈芯
推荐阅读
- 高性能CPU微架构应该具有哪些特性?
- 讲个SystemVerilog disable语句的坑
- PCIe链路初始化和训练:FTS (Fast Training Sequence)
- vim进阶:200个终身受益的命令
- 芯片开发必备工具 | 正则表达式(RegularExpression)使用指南
更多IC设计干货请关注IC设计专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。