https://mp.weixin.qq.com/s/-pjCLzzincJz0Z66orx8kg
介绍Broadcast的实现。
1. 基本介绍
TLBroadcast实现的是TL-C等级,支持Transfer操作。主要功能是根据输入的请求类型,广播Probe请求进行权限调整,维持缓存一致性(cache coherency)。
2. Tracker
跟踪为了实现缓存一致性而发出的Probe请求的过程。Tracker根据配置参数bufferless,决定是否保存上游节点下发的请求。
3. TLBroadcast
1) 类参数
a. lineBytes
每个Tracker支持的字节数:
限制条件:
i. 值大于0;
i. 值为2的幂;
i. 值大于数据带宽(beatBytes);
b. numTrackers
支持的Tracker的数量。
c. bufferless
是否保存上游节点下发的请求。
2) node
用于与其他diplomacy节点连接并协商传递参数的diplomacy节点。
A. clientFn
用于向下传递参数的方法:
因为要对source增加两位进行编码,所以这里乘以4。
B. managerFn
用于向上传递参数的方法:
a. endSinkId = numTrackers;
b. 限定下游节点不能支持Acquire操作,即下游节点不支持TL-C:
c. 只处理区域类型为UNCACHED的manager,把UNCACHED转换为TRACKED。即我们看到的下游节点的区间类型为UNCACHED,而上游节点看到的我们支持的这个区间的类型为TRACKED。因为我们做了TRACK的工作。
所谓UNCACHED区间类型,是指当前节点对该区间的支持不包含CACHE,但不妨碍上游节点为之添加缓存的功能。
这里not cached yet,不是指时间上,而是指结构上。
就时间上而言,一个区域是否缓存是一个动态的过程。而这里区域类型是一个静态的属性,不会随时间的变化而变化。即某个节点对某个区域的支持是否可缓存,不随时间的变化而变化。
但可以随结构的变化而变化。节点不是鼓励的节点,而是DAG中的一个节点。DAG中某个节点对某个区域的支持是不缓存,不代表其上游的节点不可以代为缓存。
所以可以理解几个区域类型的意义:
UNCACHED:当前节点没为之缓存,上游节点可为之缓存;
TRACKED:当前节点为下游节点增加了缓存的支持;
UNCACHEABLE:不可缓存,当前节点不缓存,上游节点也不可以缓存,因为内容会变(比如IO编址区间)。这样的区间每次使用都要重新读取;
CACHED:当前节点已为之缓存;
d. 支持的传输大小变化:
e. 响应不必FIFO顺序返回,也就是说先来的请求的响应可以后面才返回;
3) module
A. node.in & node.out
因为所有节点组成的是DAG,所以只有上游节点可以缓存下游节点的数据,兄弟节点不能缓存。所以当上游节点发起的请求触发权限相关的Probe操作时,只需要向上游节点(通过node.in)发出Probe请求即可。
B. clients/managers
取出这一条线上的clients/managers:
C. lineShift
用于处理地址,因为缓存的数据大小为lineBytes,所以地址相应的低位可以忽略:
D. caches
取出缓存数据的clients:
E. 创建Trackers
创建numTrackers个Trackers:
其中:log2Up(caches.size + 1)为记录probe个数所需要的比特数。
F. 处理channel e
channel e只有1种message,即GrantAck:
a. 一直接收channel e的输出;
b. 选择与in.e.bits.sink对应的tracker,输入e_last的值;
G. in.a:上游节点的请求
a. 请求类型
左侧列出了各种请求类型。
b. Acquire
Acquire操作请求提升权限:
i. NtoB:从None提升到Branch,Branch节点具有只读权限,所以向其他缓存了数据的节点发送Probe请求,把权限设定为Branch,即只读权限,超过的需要降权。
i. NtoT:从None提升到Trunk,Trunk节点可读,Trunk节点具有读权限,并且缓存了可能是脏数据的数据。所以如果一个节点要提升至Trunk节点,需要向其他节点发送Probe请求,把权限设定为None,以促使其把数据回写。
i. BtoT:同NtoT。
c. 其他操作
i. PutFullData:上游节点发来写数据请求,需要其他cache节点回写数据,若有需要之后再重新缓存。所以Probe其他cache节点的权限至None;
i. PutPartialData:同PutFullData;
i. ArithmeticData:需要先读、计算、再回写,同PutFullData;
i. LogicalData:同PutFullData;
i. Get:为了读取到最新数据,需要回写脏数据,如果没有脏数据则直接从缓存中返回即可。即如果是Trunk权限,则需要降权为Branch。如果是Branch权限,则无需再降权。
i. Hint:预读取和预写入与读取和写入同理;
d. selectTracker
其中:
i. freeTrackers:各个tracker是否空闲的标志;
i. freeTrack:是否存在空闲的Tracker;
i. matchTrackers:各个tracker跟踪的是否是当前访问的地址的标志;
i. matchTracker:是否有一个tracker跟踪者当前访问的地址;
i. allocTracker:从空闲的tracker中分配一个tracker用于跟踪当前访问的地址;
i. selectTracker:如果有tracker正在跟踪当前访问的地址,则仍然使用这个tracker。否则,使用重新分配的新tracker;
e. trackerReady
trackerReady是各个tracker是否可以接收输入的标志:
f. in.a.ready
是否接收channel a的请求,取决于如下条件:
i. 选中的tracker可以接收:(selectTracker & trackerReady).orR();
i. burst请求的后续beat(!a_first)或者已经全部probe完成;
g. 请求存入tracker
其中:t.probe表示要probe的client的个数。如果没有caches则为0。否则要看请求是否来自于某一个cache节点,如果是则为caches.size - 1,如果不是则为caches.size。
H. in.b
channel b用于向上游节点发送Probe请求:
其中:
i. probe_todo:待probe的cache节点;
i. probe_line:probe的address;
i. probe_perms:param中cap的权限;
i. probe_next:下一个要probe的对象;
i. probe_busy:是否probe完所有要probe的cache节点;
i. probe_target:当前要probe的对象;
i. in.b.fire()时切换下一个probe对象;
I. in.c
a. 消息类型
i. ProbeAck:我方发出Proe的响应消息,说明某client Probe完成。递减tracker中的Probe计数器。DROP是指收到ProbeAck无需其他动作。
i. ProbeAckData:除了递减计数器之外,还需要把返回的数据回写。即通过out.a发出PutFull消息。DROP是指无需再有其他动作。
i. ReleaseData:不同于ProbeAckData的地方在于,ReleaseData是上游节点发起,是一个请求消息,需要我方回复。回复的动作需要在PutFull完成之后,把PutFull的响应消息即AccessAck转换(transform)成为ReleaseAck返回给上游节点。
i. Release:直接使用in.d回复ReleaseAck即可。
b. 甄别消息类型
c. 消息与tracker的关系
d. releaseack
针对Release消息的回复ReleaseAck:
i. valid:channel c发送release时回复;
i. 使用in.c.bits组装releaseack;
通过in.d返回:
e. putfull
针对ProbeAckData和ReleaseData消息,需要把Data回写:
其中:
i. put_what:标识PutFull是为了ReleaseData还是为了ProbeAckData,如果是ReleaseData,就标识为TRANSFORM_B,如果是ProbeAckData就标识为DROP。
i. put_who:如果是ReleaseData消息,那么source在in.c.bits.source;如果是ProbeAckData,这里source使用的是tracker中存着的in.a中取的source,与ProbeAckData中的Source即Probe消息的目标不一样,后续并没有使用这个source,用意不做分析。
i. Cat(put_what, put_who):修改Put操作的source,以方便响应消息AccessAck返回时处理;因为put_what有四种可能(TRANS_B/TRANS_T/DROP/PASS)占两位,所以下游节点看到的client的endSourceId相比Broadcast节点看到的上游节点cliet的endSourceId,扩大了四倍。这就是diplomacy node的clientFn中cp.endSourceId乘以4的原因:
通过out.a发出:
f. probenack
如果in.c返回ProbeAck消息,则告诉tracker递减probe计数器:
J. out.d/in.d
如何根据out.d生成in.d要回复的消息。channel d支持的消息类型如下:
a. 取出编码在source中的信息:
b. 判断是否ProbeAckData消息衍生成出的Put消息的响应消息AccessAck:
有个assert限定:
如果是这个AccessAck消息,就允许发送:
然而这个消息不会被传往in.d:
c. d_hasData:是否包含数据:
d. d_normal:使用out.d生成的消息,发往in.d
a) 使用in.d克隆的Wire:
这意味着d_normal中各个域的位宽与in.d一致,而不是out.d。
实际上,out.d的source域比in.d的source域多两位,所以把out.d连接到d_normal时,会把这两位丢掉:
b) TRANSFORM
TRANSFORM的信息从out.d.bits.source中取出,即do_what。
这个信息从两处传入:
i. put_what:
- ReleaseData需要把Put的响应消息AccessAck变换为ReleaseAck;
- ProbeAckData需要把Put的响应消息AccessAck丢弃;
i. in.a的Acquire消息:
in.a发出的Acquire消息,会被转换为Get消息,从下游节点请求数据,然后返回给上游节点。
c) do_what(1):TRANSFORM_B/TRANSFORM_T
如果do_what(1)为真,那么编码在source中的信息是TRANSFORM_B/TRANSFORM_T:
进而表明tracker中跟踪的是一个Acquire消息或ReleaseData消息。
如果d_hasData为真,out.d返回的是Acquire变换成的Get消息的响应消息AccessAckData,应该被转换为GrantData消息返回;
如果d_hasData为假,out.d返回的是ReleaseData变换成的Put消息的响应消息AccessAck,应该被转换为ReleaseAck消息返回;
GrantData消息的param:
ReleaseAck消息的param为0:
d) probedack
标志着ProbeAckData处理完成:
如果ProbeAckData衍生的Put消息返回,则ProbeAckData消息处理完成。
e) d_last
响应消息是否发送完成:
d_normal.fire() && d_last代表d_normal发送完成。
d_response指除ProbeAckData的AccessAck消息和ReleaseData的ReleaseAck消息之外的其他消息。
为什么要排除这两个消息?
因为这两个消息的响应消息虽然都是从channel d返回,但他们都是从channel c发送的请求消息。而tracker跟踪的是channel a发出的请求消息。
4. TLBroadcastTracker
1) 参数
a. id:tracker的编号;
b. lineBytes:tracker缓存的字节数;
c. probeCountBites:需要probe的cache节点计数所需的比特位数;
d. bufferless:是否保存上游节点下发的请求;
2) io
其中:
a. in_a_first:是否in_a的第一个beat;
b. in_a:保存的channel a的输入;
c. out_a:向channel d输出的信号;
d. probe:要probe的cache节点的数目;
e. probenack:处理完成一个ProbeAck消息;
f. probedack:处理完成一个ProbeAckData消息;
g. d_last:是否channel d的最后一个beat;
h. e_last:是否channel e的最后一个beat;
i. source:提取in_a中的source域,输出用于比对;
j. line:提取in_a中的address域,处理后输出用于比对;
k. idle:表示该tracker是否空闲;
l. need_d:是否需要channel d;
3) 临时变量
其中:
a. idle = got_e && sent_d:表示已经完成全部消息交互:channel d已经发送完成,并收到了channel e的消息。
b. count:probe请求的计数器。
4) 存入channel a的请求消息
a. sent_d:假,刚收到in.a的消息,还没有返回响应消息;
b. got_e:只有Acquire/Grant/GrantAck交互需要用到channel e置为假,其他用不到的置为真。
c. count:值等于io.probe;
5) sent_d && need_d
当channel d的最后一个beat发出后,sent_d置为真:
channel d的响应消息已发出,则不再需要channel d:
6) e_last && got_e
当channel e的最后一个beat(实际上channel e有且只有一个beat)发出后,got_e置为真:
7) line
根据存的数据大小,地址的低若干位没有意义可以移除:
8) probe计数
a. 处理完一个ProbeAck减1;
b. 处理完一个ProbeAckData减1;
c. 同时处理完则减2;
9) probe_done:标志着probe已经完成:
10) Queue
创建一个队列用于输入、存储、输出数据:
11) 输入规则:
12) 输出规则:
其中:
a. Acquire消息被转换为Get消息,其余透传;
b. source进行编码,Acquire消息对应的source编入transform信息,其他的透传(PASS=0b00);
out_a输出到out.a:
5. Broadcast主动发出的消息
无。
6. Broadcast被动发出的消息
1) 向上游发出Probe请求消息;
2) 向上游直接回复ReleaseAck消息;
3) 向上游回复transform后的ReleaseAck消息;
4) 向下游发出transform后的Get消息;
5) 向下游发出transform后的Put消息;
7. Broadcast透传的消息
channel a支持的消息如下:
除Acquire需要transform外,其余的都会透传。