1. Cache的访问
Cache作为一种高速存储资源,通过存储近期或频繁访问的数据,极大地减少了处理器与主存之间的数据传输时间,从而提升了系统的整体性能。本章主要介绍 GPGPU-Sim 如何模拟对 Cache 的访问过程。
1.1. Cache 基础
首先需要了解的就是 Sector Cache 和 Line Cache 的 Cache Block 的组织方式、 访存地址的映射 以及 Cache Block的状态。
1.1.1. Cache Block 的组织方式
Line Cache:Line Cache是最常见的缓存组织方式之一,它按固定大小的cache block来存储数据。这些行的大小取决于具体的GPU型号,在模拟器中的 Cache-Config 中进行配置。当LDST单元尝试读取或写入数据时,整个cache block(包含所需数据的那部分)被加载到缓存中。因为程序往往具有良好的空间局部性,即接近已访问数据的其他数据很可能很快会被访问,行缓存可以有效利用这一点,提高缓存命中率,从而提高整体性能。
Sector Cache:与Line Cache相比,Sector Cache提供了一种更为灵活的缓存数据方式。在Sector Cache中,每个cache block被进一步细分为若干个sector或称为“扇区”。这样,当请求特定数据时,只有包含这些数据的特定扇区会被加载到缓存中,而不是整个cache block。这种方法在数据的空间局部性不是非常理想的情况下尤其有效,因为它减少了不必要数据的缓存,从而为其他数据的缓存留出了空间,提高了缓存的有效性。
下面的代码块 Cache Block的组织形式 用一个 4-sets/6-ways 的简单Cache展示了Cache Block的两种组织形式。
0// 1. 如果是 Line Cache:
1// 4 sets, 6 ways, cache blocks are not split to sectors.
2// |-----------------------------------|
3// | 0 | 1 | 2 | 3 | 4 | 5 | // set_index 0
4// |-----------------------------------|
5// | 6 | 7 | 8 | 9 | 10 | 11 | // set_index 1
6// |-----------------------------------|
7// | 12 | 13 | 14 | 15 | 16 | 17 | // set_index 2
8// |-----------------------------------|
9// | 18 | 19 | 20 | 21 | 22 | 23 | // set_index 3
10// |--------------|--------------------|
11// |--------> 20 is the cache block index
12// 2. 如果是 Sector Cache:
13// 4 sets, 6 ways, each cache block is split to 4 sectors.
14// |-----------------------------------|
15// | 0 | 1 | 2 | 3 | 4 | 5 | // set_index 0
16// |-----------------------------------|
17// | 6 | 7 | 8 | 9 | 10 | 11 | // set_index 1
18// |-----------------------------------|
19// | 12 | 13 | 14 | 15 | 16 | 17 | // set_index 2
20// |-----------------------------------|
21// | 18 | 19 | 20 | 21 | 22 | 23 | // set_index 3
22// |-----------/-----\-----------------|
23// / \
24// / \--------> 20 is the cache block index
25// / \
26// / \
27// |---------------|
28// | 3 | 2 | 1 | 0 | // a block contains 4 sectors
29// |---------|-----|
30// |--------> 1 is the sector offset in the 20-th cache block
1.1.2. 访存地址的映射
本节主要介绍一个访存地址如何映射到一个 cache block。如何将一个特定的访存地址映射到这些 cache block 上,是通过一系列精确计算的步骤完成的。其中最关键的两步是计算 Tag 位` 和 Set Index 位。 Tag 位 用于标识数据存储在哪个缓存块中,而 Set Index 位 则用于确定这个地址映射到缓存中的哪一个 Set 上。不过,即使能够确定一个地址映射到了哪个 Set,要找到这个数据是否已经被缓存(以及具体存储在哪一路),还需进行遍历查找,这一步是判断缓存命中还是缓存失效的关键环节。接下来,将详细介绍 Tag 位 和 Set Index 位 的计算过程,这两者是理解地址映射机制的重点。
1.1.2.1. Tag 位的计算
下面的代码块 Tag 位的计算 展示了如何由访存地址 addr 计算 tag 位。
这里需要注意的是,最新版本中的 GPGPU-Sim 中的 tag 位 是由 index 位 和 traditional tag 位 共同组成的(这里所说的 traditional tag 位 就是指传统 CPU 上 Cache 的 tag 位 的计算方式: traditional tag = addr >> (log2(m_line_sz) + log2(m_nset)),详见 Cache Block的地址映射 示意图),其中 m_line_sz 和 m_nset 分别是 Cache 的 line size 和 set 的数量),这样可以允许更复杂的 set index 位 的计算,从而避免将 set index 位 不同但是 traditional tag 位 相同的地址映射到同一个 set。这里是把完整的 [traditional tag 位 + set index 位 + log2(m_line_sz)’b0] 来作为 tag 位。
0typedef unsigned long long new_addr_type;
1
2// m_line_sz:cache block的大小,单位是字节。
3new_addr_type tag(new_addr_type addr) const {
4 // For generality, the tag includes both index and tag. This allows for more
5 // complex set index calculations that can result in different indexes
6 // mapping to the same set, thus the full tag + index is required to check
7 // for hit/miss. Tag is now identical to the block address.
8 return addr & ~(new_addr_type)(m_line_sz - 1);
9}
1.1.2.2. Block Address 的计算
0// m_line_sz:cache block的大小,单位是字节。
1new_addr_type block_addr(new_addr_type addr) const {
2 return addr & ~(new_addr_type)(m_line_sz - 1);
3}
Block Address 的计算与 Tag 位的计算是一样的,都是通过 m_line_sz 来计算的。block_addr 函数会返回一个地址 addr 在 Cache 中的 block address,这里是把完整的 [traditional tag 位 + set index 位 + log2(m_line_sz)’b0] 来作为 tag 位。
1.1.2.3. Set Index 位的计算
GPGPU-Sim 中真正实现的 set index 位 的计算方式是通过 cache_config::set_index() 和 l2_cache_config::set_index() 函数来实现的,这个函数会返回一个地址 addr 在 Cache 中的 set index。这里的 set index 有一整套的映射函数,尤其是 L2 Cache 的映射方法十分复杂(涉及到内存子分区的概念),这里先不展开讨论。对于 L2 Cache 暂时只需要知道, set_index() 函数会计算并返回一个地址 addr 在 Cache 中的 set index,具体如何映射后续再讲。
这里仅介绍一下 GV100 架构中的 L1D Cache 的 set index 位 的计算方式,如 GV100 架构中的 L1D Cache 的 Set Index 位的计算 所示:
0// m_nset:cache set 的数量。
1// m_line_sz_log2:cache block 的大小的对数。
2// m_nset_log2:cache set 的数量的对数。
3// m_index_function:set index 的计算函数,GV100 架构中的 L1D Cache 的配置为
4// LINEAR_SET_FUNCTION。
5unsigned cache_config::hash_function(new_addr_type addr, unsigned m_nset,
6 unsigned m_line_sz_log2,
7 unsigned m_nset_log2,
8 unsigned m_index_function) const {
9 unsigned set_index = 0;
10 switch (m_index_function) {
11 // ......
12 case LINEAR_SET_FUNCTION: {
13 // addr: [m_line_sz_log2-1:0] => byte offset
14 // addr: [m_line_sz_log2+m_nset_log2-1:m_line_sz_log2] => set index
15 set_index = (addr >> m_line_sz_log2) & (m_nset - 1);
16 break;
17 }
18 default: {
19 assert("\nUndefined set index function.\n" && 0);
20 break;
21 }
22 }
23 assert((set_index < m_nset) &&
24 "\nError: Set index out of bounds. This is caused by "
25 "an incorrect or unimplemented custom set index function.\n");
26 return set_index;
27}
备注
Set Index 位 有一整套的映射函数,这里只是简单介绍了 GV100 架构中的 L1D Cache 的 Set Index 位 的计算结果,具体的映射函数会在后续章节中详细介绍。
1.1.2.4. 访问地址的映射示意图
下面的代码块 Cache Block的地址映射 用一个访存地址 addr 展示了访问 Cache 的地址映射。
0// 1. 如果是 Line Cache:
1// MSHR 的地址即为地址 addr 的 [tag 位 + set_index 位]。即除 offset in-line
2// 位以外的所有位。
3// |<----mshr_addr--->|
4// line offset
5// |-------------------------|
6// \ /
7// |<-Tr-->| \ /
8// |-------|-------------|-------------------| // addr
9// set_index offset in-line
10// |<--------tag--------> 0 0 0 0 0 0 0 0 0 0|
11// 2. 如果是 Sector Cache:
12// MSHR 的地址即为地址 addr 的 [tag 位 + set_index 位 + sector offset 位]。
13// 即除 offset in-sector 位以外的所有位。
14// |<----------mshr_addr----------->|
15// sector offset offset in-sector
16// |-------------|-----------|
17// \ /
18// |<-Tr-->| \ /
19// |-------|-------------|-------------------| // addr
20// set_index offset in-line
21// |<--------tag--------> 0 0 0 0 0 0 0 0 0 0|
提示
Cache Block的地址映射 中所展示的是最新版本 GPGPU-Sim 的实现, tag 位 是由 index 位 和 traditional tag 位 共同组成的。 traditional tag 位 如图中 Tr 的范围所示。
1.1.3. Cache Block的状态
Cache Block 的状态是指在 Line Cache 中 cache block 或者在 Sector Cache 中的 cache sector 的状态,包含以下几种:
0enum cache_block_state { INVALID = 0, RESERVED, VALID, MODIFIED };
这里会区分开 Line Cache 和 Sector Cache 的状态介绍,因为 Line Cache 和 Sector Cache 的状态是不同的。对于 Line Cache 来说,每个 line_cache_block *line 对象都有一个标识状态的成员变量 m_status,它的值是 enum cache_block_state 中的一种。具体的状态如下:
INVALID: cache block 无效数据。需要注意的是,这里的无效与下面的MODIFIED和RESERVED不同,意味着当前 cache block 没有存储任何有效数据。VALID: 当一个 cache block 的状态是VALID,说明该 block 的数据是有效的,可以为 cache 提供命中的数据。MODIFIED: 如果 cache block 状态是MODIFIED,说明该 block 的数据已经被其他线程修改。如果当前访问也是写操作的话即为命中;但如果不是写操作,则需要判断当前 cache block 是否已被修改完毕并可读(由bool m_readable确定),修改完毕并可读的话(m_readable = true)则为命中,不可读的话(m_readable = false)则发生SECTOR_MISS。RESERVED: 当一个 cache block 被分配以重新填充未命中的数据,即需要装入新的数据以应对未命中(MISS)情况时(如果一次数据访问 cache 或者一个数据填充进 cache 时发生MISS),cache block 的状态m_status被设置为RESERVED,这意味着该 block 正在准备或已经准备好重新填充新的数据。
而对于 Sector Cache 来说,每个 sector_cache_block_t *line 对象都有一个 cache_block_state *m_status 数组。数组的大小是 const unsigned SECTOR_CHUNCK_SIZE = 4 即每个 cache line 都有 4 个 sector,这个状态数组用以标识每个 sector 的状态,它的每一个元素也都是 cache_block_state 中的一个。具体的状态与上述 Line Cache 中的状态的唯一区别就是,cache_block_state *m_status 数组的每个元素标识每个 sector 的状态,而不是 Line Cache 中的整个 cache block 的状态。
1.2. Cache 的访问状态
Cache 的访问状态有以下几种:
0enum cache_request_status {
1 // 命中。
2 HIT = 0,
3 // 保留成功,当访问地址被映射到一个已经被分配的 cache block/sector 时,block/
4 // sector 的状态被设置为 RESERVED。
5 HIT_RESERVED,
6 // 未命中。
7 MISS,
8 // 保留失败。
9 RESERVATION_FAIL,
10 // Sector缺失。
11 SECTOR_MISS,
12 MSHR_HIT,
13 // cache_request_status的状态总数。
14 NUM_CACHE_REQUEST_STATUS
15};
1.3. Cache 的组织和实现
TODO
1.4. Cache 的访问状态
在访问 Cache 的时候,会调用 access() 函数,例如 m_L2cache->access(),m_L1I->access(),m_L1D->access() 等。
access() 函数主要完成以下几个步骤:
计算地址
addr映射到 Cache 的 Block 地址new_addr_type block_addr。根据
new_addr_type block_addr和访问 Cache 的数据包mem_fetch *mf判断访问 Cache 的返回状态。根据返回状态执行相应的操作,包括:
m_wr_hit、m_wr_miss、m_rd_hit、m_rd_miss。
0enum cache_request_status data_cache::access(new_addr_type addr, mem_fetch *mf,
1 unsigned time,
2 std::list<cache_event> &events) {
3 assert(mf->get_data_size() <= m_config.get_atom_sz());
4 bool wr = mf->get_is_write();
5 // Block Address 的计算与 Tag 位的计算是一样的,都是通过 m_line_sz 来计算的。
6 // block_addr 函数会返回一个地址 addr 在 Cache 中的 block address,这里是把
7 // 完整的 [traditional tag 位 + set index 位 + log2(m_line_sz)'b0] 来作为
8 // tag 位。
9 new_addr_type block_addr = m_config.block_addr(addr);
10
11 unsigned cache_index = (unsigned)-1;
12 // 判断对 cache 的访问(地址为 addr)是 HIT / HIT_RESERVED / SECTOR_MISS /
13 // MISS / RESERVATION_FAIL 等状态。且如果返回的 cache 访问为 MISS,则将需要
14 // 被替换的 cache block 的索引写入 cache_index。
15 enum cache_request_status probe_status =
16 m_tag_array->probe(block_addr, cache_index, mf, mf->is_write(), true);
17 // 主要包括上述各种对 cache 的访问的状态下的执行对 cache 访问的操作,例如:
18 // (this->*m_wr_hit)、(this->*m_wr_miss)、
19 // (this->*m_rd_hit)、(this->*m_rd_miss)。
20 enum cache_request_status access_status =
21 process_tag_probe(wr, probe_status, addr, cache_index, mf, time, events);
22 m_stats.inc_stats(mf->get_access_type(),
23 m_stats.select_stats_status(probe_status, access_status));
24 m_stats.inc_stats_pw(mf->get_access_type(), m_stats.select_stats_status(
25 probe_status, access_status));
26 return access_status;
27}
0enum cache_request_status tag_array::probe(new_addr_type addr, unsigned &idx,
1 mem_access_sector_mask_t mask,
2 bool is_write, bool probe_mode,
3 mem_fetch *mf) const {
4 // 这里的输入地址 addr 是 cache block 的地址,该地址即为由 block_addr() 计算
5 // 而来。
6 // m_config.set_index(addr) 是返回一个地址 addr 在 Cache 中的 set index。这
7 // 里的 set index 有一整套的映射函数。
8 unsigned set_index = m_config.set_index(addr);
9 // |-------|-------------|--------------|
10 // set_index offset in-line
11 // |<--------tag--------> 0 0 0 0 0 0 0 |
12 // 这里实际返回的是 {除 offset in-line 以外的所有位, offset in-line'b0},即
13 // set index 也作为 tag 位的一部分了。
14 new_addr_type tag = m_config.tag(addr);
15
16 unsigned invalid_line = (unsigned)-1;
17 unsigned valid_line = (unsigned)-1;
18 unsigned long long valid_timestamp = (unsigned)-1;
19
20 bool all_reserved = true;
21
22 // check for hit or pending hit
23 // 对所有的 Cache Ways 检查。需要注意这里其实是针对一个 set 的所有 way 进行检
24 // 查,因为给定一个地址,可以确定它所在的 set index,然后再通过 tag 位 来匹配
25 // 并确定这个地址在哪一个 way 上。
26 for (unsigned way = 0; way < m_config.m_assoc; way++) {
27 // For example, 4 sets, 6 ways:
28 // | 0 | 1 | 2 | 3 | 4 | 5 | // set_index 0
29 // | 6 | 7 | 8 | 9 | 10 | 11 | // set_index 1
30 // | 12 | 13 | 14 | 15 | 16 | 17 | // set_index 2
31 // | 18 | 19 | 20 | 21 | 22 | 23 | // set_index 3
32 // |--------> index => cache_block_t *line
33 // index 是 cache block 的索引。
34 unsigned index = set_index * m_config.m_assoc + way;
35 cache_block_t *line = m_lines[index];
36 // tag 位 相符,说明当前 cache block 已经是 addr 地址映射在当前 way 上。
37 if (line->m_tag == tag) {
38 // cache block 的状态,包含:
39 // enum cache_block_state {
40 // INVALID = 0, RESERVED, VALID, MODIFIED };
41 if (line->get_status(mask) == RESERVED) {
42 // 当访问地址被映射到一个已经被分配的 cache block 或 cache sector 时,
43 // cache block 或 cache sector 的状态被设置为 RESERVED。这说明当前
44 // block / sector 被分配给了其他的线程,而且正在读取的内容正是访问地址
45 // addr 想要的数据。
46 idx = index;
47 return HIT_RESERVED;
48 } else if (line->get_status(mask) == VALID) {
49 // 如果 cache block 或 cache sector 的状态是 VALID,说明已经命中。
50 idx = index;
51 return HIT;
52 } else if (line->get_status(mask) == MODIFIED) {
53 // 如果 cache block 或 cache sector 的状态是 MODIFIED,说明该 block
54 // 或 sector 的数据已经被其他线程修改。如果当前访问也是写操作的话即为命
55 // 中;但如果不是写操作,则需要判断当前 cache block 或 cache sector
56 // 是否已被修改完毕并可读(由 ``bool m_readable`` 确定),修改完毕并可
57 // 读的话(``m_readable = true``)则为命中,不可读的话(``m_readable
58 // = false``)则发生 SECTOR_MISS。
59 if ((!is_write && line->is_readable(mask)) || is_write) {
60 idx = index;
61 return HIT;
62 } else {
63 // for condition: is_write && line->is_readable(mask) == false.
64 idx = index;
65 return SECTOR_MISS;
66 }
67 } else if (line->is_valid_line() && line->get_status(mask) == INVALID) {
68 // line cache 不会走这个分支,在 line cache 中,line->is_valid_line()
69 // 返回的是 m_status 的值,当其为 VALID 时,line->get_status(mask) 也
70 // 是返回的 m_status 的值,即为 VALID,因此对于 line cache 这条分支无
71 // 效。但是对于sector cache, 有:
72 // virtual bool is_valid_line() { return !(is_invalid_line()); }
73 // 而 sector cache 中的 is_invalid_line() 是,只要有一个 sector 不为
74 // INVALID 即返回 false,因此 is_valid_line() 返回的是,只要存在一个
75 // sector 不为 INVALID 就设置 is_valid_line() 为真。所以这条分支对于
76 // sector cache 是可走的。
77 // cache block 有效,但是其中的 byte mask = cache block[mask] 状态无
78 // 效,说明 sector 缺失。
79 idx = index;
80 return SECTOR_MISS;
81 } else {
82 assert(line->get_status(mask) == INVALID);
83 }
84 }
85
86 // 每一次循环中能走到这里的,即为当前 cache block 的 line->m_tag != tag。
87 // 那么就需要考虑当前这 cache block 能否被逐出替换,请注意,这个判断是在对
88 // 每一个 way 循环的过程中进行的,也就是说,假如第一个 cache block 没有返
89 // 回以上访问状态,但有可能直到所有 way 的最后一个 cache block 才满足
90 // m_tag != tag,但是在对第 0 ~ way-2 号的 cache block 循环判断的时候,
91 // 就需要记录下每一个 way 的 cache block 是否能够被逐出。因为如果等到所有
92 // way 的 cache block 都没有满足 line->m_tag != tag 时,再回过头来循环所
93 // 有 way 找最优先被逐出的 cache block 那就增加了模拟的开销。因此实际上对
94 // 于所有 way 中的每一个 cache block,只要它不满足 line->m_tag != tag,
95 // 就在这里判断它能否被逐出。
96 // line->is_reserved_line():只要有一个 sector 是 RESERVED,就认为这个
97 // Cache Line 是 RESERVED。这里即整个 line 没有 sector 是 RESERVED。
98 if (!line->is_reserved_line()) {
99 // percentage of dirty lines in the cache
100 // number of dirty lines / total lines in the cache
101 float dirty_line_percentage =
102 ((float)m_dirty / (m_config.m_nset * m_config.m_assoc)) * 100;
103 // If the cacheline is from a load op (not modified),
104 // or the total dirty cacheline is above a specific value,
105 // Then this cacheline is eligible to be considered for replacement
106 // candidate, i.e. Only evict clean cachelines until total dirty
107 // cachelines reach the limit.
108 // m_config.m_wr_percent 在 V100 中配置为 25%。
109 // line->is_modified_line():只要有一个 sector 是 MODIFIED,就认为这
110 // 个 cache line 是MODIFIED。这里即整个 line 没有 sector 是 MODIFIED,
111 // 或者 dirty_line_percentage 超过了 m_config.m_wr_percent。
112 if (!line->is_modified_line() ||
113 dirty_line_percentage >= m_config.m_wr_percent)
114 {
115 // 因为在逐出一个 cache block 时,优先逐出一个干净的块,即没有 sector
116 // 被 RESERVED,也没有 sector 被 MODIFIED,来逐出;但是如果 dirty 的
117 // cache line 的比例超过 m_wr_percent(V100 中配置为 25%),也可以不
118 // 满足 MODIFIED 的条件。
119 // 在缓存管理机制中,优先逐出未被修改("干净")的缓存块的策略,是基于几
120 // 个重要的考虑:
121 // 1. 减少写回成本:缓存中的数据通常来源于更低速的后端存储(如主存储器)。
122 // 当缓存块被修改(即包含"脏"数据)时,在逐出这些块之前,需要将这些更
123 // 改写回到后端存储以确保数据一致性。相比之下,未被修改("干净")的缓
124 // 存块可以直接被逐出,因为它们的内容已经与后端存储一致,无需进行写回
125 // 操作。这样就避免了写回操作带来的时间和能量开销。
126 // 2. 提高效率:写回操作相对于读取操作来说,是一个成本较高的过程,不仅涉
127 // 及更多的时间延迟,还可能占用宝贵的带宽,影响系统的整体性能。通过先
128 // 逐出那些"干净"的块,系统能够在维持数据一致性的前提下,减少对后端存
129 // 储带宽的需求和写回操作的开销。
130 // 3. 优化性能:选择逐出"干净"的缓存块还有助于维护缓存的高命中率。理想情
131 // 况下,缓存应当存储访问频率高且最近被访问的数据。逐出"脏"数据意味着
132 // 这些数据需要被写回,这个过程不仅耗时而且可能导致缓存暂时无法服务其
133 // 他请求,从而降低缓存效率。
134 // 4. 数据安全与完整性:在某些情况下,"脏"缓存块可能表示正在进行的写操作
135 // 或者重要的数据更新。通过优先逐出"干净"的缓存块,可以降低因为缓存逐
136 // 出导致的数据丢失或者完整性破坏的风险。
137
138 // all_reserved 被初始化为 true,是指所有 cache line 都没有能够逐出来
139 // 为新访问提供 RESERVE 的空间,这里一旦满足上面两个 if 条件,说明当前
140 // line 可以被逐出来提供空间供 RESERVE 新访问,这里 all_reserved 置为
141 // false。而一旦最终 all_reserved 仍旧保持 true 的话,就说明当前 set
142 // 里没有哪一个 way 的 cache block 可以被逐出,发生 RESERVATION_FAIL。
143 all_reserved = false;
144 // line->is_invalid_line() 标识所有 sector 都无效。
145 if (line->is_invalid_line()) {
146 // 尽管配置有 LRU 或者 FIFO 替换策略,但是最理想的情况还是优先替换整个
147 // cache block 都无效的块。因为这种无效的块不需要写回,能够节省带宽。
148 invalid_line = index;
149 } else {
150 // valid_line aims to keep track of most appropriate replacement
151 // candidate.
152 if (m_config.m_replacement_policy == LRU) {
153 // valid_timestamp 设置为最近最少被使用的 cache line 的最末次访问
154 // 时间。
155 // valid_timestamp 被初始化为 (unsigned)-1,即可以看作无穷大。
156 if (line->get_last_access_time() < valid_timestamp) {
157 // 这里的 valid_timestamp 是周期数,即最小的周期数具有最大的被逐
158 // 出优先级,当然这个变量在这里只是找具有最小周期数的 cache block,
159 // 最小周期数意味着离他上次使用才最早,真正标识哪个 cache block
160 // 具有最大优先级被逐出的是valid_line。
161 valid_timestamp = line->get_last_access_time();
162 // 标识当前 cache block 具有最小的执行周期数,index 这个 cache
163 // block 应该最先被逐出。
164 valid_line = index;
165 }
166 } else if (m_config.m_replacement_policy == FIFO) {
167 if (line->get_alloc_time() < valid_timestamp) {
168 // FIFO 按照最早分配时间的 cache block 最优先被逐出的原则。
169 valid_timestamp = line->get_alloc_time();
170 valid_line = index;
171 }
172 }
173 }
174 }
175 } // 这里是把当前 set 里所有的 way 都循环一遍,如果找到了 line->m_tag ==
176 // tag 的块,则已经返回了访问状态,如果没有找到,则也遍历了一遍所有 way 的
177 // cache block,找到了最优先应该被逐出和替换的 cache block。
178 }
179 // all_reserved 被初始化为 true,是指所有 cache line 都没有能够逐出来为新访
180 // 问提供 RESERVE 的空间,这里一旦满足上面两个 if 条件,说明当前 line 可以被
181 // 逐出来提供空间供 RESERVE 新访问,这里 all_reserved 置为 false。而一旦最终
182 // all_reserved 仍旧保持 true 的话,就说明当前 set 里没有哪一个 way 的 cache
183 // block 可以被逐出,发生 RESERVATION_FAIL。
184 if (all_reserved) {
185 // all of the blocks in the current set have no enough space in cache
186 // to allocate on miss.
187 assert(m_config.m_alloc_policy == ON_MISS);
188 return RESERVATION_FAIL; // miss and not enough space in cache to
189 // allocate on miss
190 }
191
192 // 如果上面的 all_reserved 为 false,才会到这一步,即 cache line 可以被逐出
193 // 来为新访问提供 RESERVE。
194 if (invalid_line != (unsigned)-1) {
195 // 尽管配置有 LRU 或者 FIFO 替换策略,但是最理想的情况还是优先替换整个 cache
196 // block 都无效的块。因为这种无效的块不需要写回,能够节省带宽。
197 idx = invalid_line;
198 } else if (valid_line != (unsigned)-1) {
199 // 没有无效的块,就只能将上面按照 LRU 或者 FIFO 确定的 cache block 作为被
200 // 逐出的块了。
201 idx = valid_line;
202 } else
203 abort(); // if an unreserved block exists, it is either invalid or
204 // replaceable
205
206 // if (probe_mode && m_config.is_streaming()) {
207 // line_table::const_iterator i =
208 // pending_lines.find(m_config.block_addr(addr));
209 // assert(mf);
210 // if (!mf->is_write() && i != pending_lines.end()) {
211 // if (i->second != mf->get_inst().get_uid()) return SECTOR_MISS;
212 // }
213 // }
214
215 // 如果上面的 cache line 可以被逐出来 reserve 新访问,则返回 MISS。
216 return MISS;
217}
0enum cache_request_status data_cache::process_tag_probe(
1 bool wr, enum cache_request_status probe_status, new_addr_type addr,
2 unsigned cache_index, mem_fetch *mf, unsigned time,
3 std::list<cache_event> &events) {
4 // Each function pointer ( m_[rd/wr]_[hit/miss] ) is set in the
5 // data_cache constructor to reflect the corresponding cache configuration
6 // options. Function pointers were used to avoid many long conditional
7 // branches resulting from many cache configuration options.
8 cache_request_status access_status = probe_status;
9 if (wr) { // Write
10 if (probe_status == HIT) {
11 //这里会在cache_index中写入cache block的索引。
12 access_status =
13 (this->*m_wr_hit)(addr, cache_index, mf, time, events, probe_status);
14 } else if ((probe_status != RESERVATION_FAIL) ||
15 (probe_status == RESERVATION_FAIL &&
16 m_config.m_write_alloc_policy == NO_WRITE_ALLOCATE)) {
17 access_status =
18 (this->*m_wr_miss)(addr, cache_index, mf, time, events, probe_status);
19 } else {
20 // the only reason for reservation fail here is LINE_ALLOC_FAIL (i.e all
21 // lines are reserved)
22 m_stats.inc_fail_stats(mf->get_access_type(), LINE_ALLOC_FAIL);
23 }
24 } else { // Read
25 if (probe_status == HIT) {
26 access_status =
27 (this->*m_rd_hit)(addr, cache_index, mf, time, events, probe_status);
28 } else if (probe_status != RESERVATION_FAIL) {
29 access_status =
30 (this->*m_rd_miss)(addr, cache_index, mf, time, events, probe_status);
31 } else {
32 // the only reason for reservation fail here is LINE_ALLOC_FAIL (i.e all
33 // lines are reserved)
34 m_stats.inc_fail_stats(mf->get_access_type(), LINE_ALLOC_FAIL);
35 }
36 }
37
38 m_bandwidth_management.use_data_port(mf, access_status, events);
39 return access_status;
40}
0enum cache_request_status tag_array::access(new_addr_type addr, unsigned time,
1 unsigned &idx, bool &wb,
2 evicted_block_info &evicted,
3 mem_fetch *mf) {
4 // 对当前 tag_array 的访问次数加 1。
5 m_access++;
6 // 标记当前 tag_array 所属 cache 是否被使用过。一旦有 access() 函数被调用,则
7 // 说明被使用过。
8 is_used = true;
9 shader_cache_access_log(m_core_id, m_type_id, 0); // log accesses to cache
10 // 由于当前函数没有把之前 probe 函数的 cache 访问状态传参进来,这里这个 probe
11 // 单纯的重新获取这个状态。
12 enum cache_request_status status = probe(addr, idx, mf, mf->is_write());
13 switch (status) {
14 // 新访问是 HIT_RESERVED 的话,不执行动作。
15 case HIT_RESERVED:
16 m_pending_hit++;
17 // 新访问是 HIT 的话,设置第 idx 号 cache line 以及 mask 对应的 sector 的最
18 // 末此访问时间为当前拍。
19 case HIT:
20 m_lines[idx]->set_last_access_time(time, mf->get_access_sector_mask());
21 break;
22 // 新访问是 MISS 的话,说明已经选定 m_lines[idx] 作为逐出并 reserve 新访问的
23 // cache line。
24 case MISS:
25 m_miss++;
26 shader_cache_access_log(m_core_id, m_type_id, 1); // log cache misses
27 // For V100, L1 cache and L2 cache are all `allocate on miss`.
28 // m_alloc_policy,分配策略:
29 // 对于发送到 L1D cache 的请求:
30 // 如果命中,则立即返回所需数据;
31 // 如果未命中,则分配与缓存未命中相关的资源并将请求转至 L2 cache。
32 // allocateon-miss/fill 是两种缓存行分配策略。对于 allocateon-miss,需
33 // 为未完成的未命中分配一个缓存行槽、一个 MSHR 和一个未命中队列条目。相比
34 // 之下,allocate-on-fill,当未完成的未命中发生时,需要分配一个 MSHR 和
35 // 一个未命中队列条目,但当所需数据从较低内存级别返回时,会选择受害者缓存
36 // 行槽。在这两种策略中,如果任何所需资源不可用,则会发生预留失败,内存管
37 // 道会停滞。分配的 MSHR 会被保留,直到从 L2 缓存/片外内存中获取数据,而
38 // 未命中队列条目会在未命中请求转发到 L2 缓存后被释放。由于 allocate-on-
39 // fill 在驱逐之前将受害者缓存行保留在缓存中更长时间,并为未完成的未命中
40 // 保留更少的资源,因此它往往能获得更多的缓存命中和更少的预留失败,从而比
41 // allocate-on-miss 具有更好的性能。尽管填充时分配需要额外的缓冲和流控制
42 // 逻辑来按顺序将数据填充到缓存中,但按顺序执行模型和写入驱逐策略使 GPU
43 // L1D 缓存对填充时分配很友好,因为在填充时要驱逐受害者缓存时,没有脏数据
44 // 写入 L2。
45 // 详见 paper:
46 // The Demand for a Sound Baseline in GPU Memory Architecture Research.
47 // https://hzhou.wordpress.ncsu.edu/files/2022/12/Hongwen_WDDD2017.pdf
48 //
49 // For streaming cache: (1) we set the alloc policy to be on-fill
50 // to remove all line_alloc_fail stalls. if the whole memory is
51 // allocated to the L1 cache, then make the allocation to be on
52 // MISS, otherwise, make it ON_FILL to eliminate line allocation
53 // fails. i.e. MSHR throughput is the same, independent on the L1
54 // cache size/associativity So, we set the allocation policy per
55 // kernel basis, see shader.cc, max_cta() function. (2) We also
56 // set the MSHRs to be equal to max allocated cache lines. This
57 // is possible by moving TAG to be shared between cache line and
58 // MSHR enrty (i.e. for each cache line, there is an MSHR entry
59 // associated with it). This is the easiest think we can think of
60 // to model (mimic) L1 streaming cache in Pascal and Volta. For
61 // more information about streaming cache, see:
62 // https://www2.maths.ox.ac.uk/~gilesm/cuda/lecs/VoltaAG_Oxford.pdf
63 // https://ieeexplore.ieee.org/document/8344474/
64 if (m_config.m_alloc_policy == ON_MISS) {
65 // 访问时遇到 MISS,说明 probe 确定的 idx 号 cache line 需要被逐出来为新
66 // 访问提供 RESERVE 的空间。但是,这里需要判断 idx 号 cache line 是否是
67 // MODIFIED,如果是的话,需要执行写回,设置写回的标志为 wb = true,设置逐
68 // 出 cache line 的信息。
69 if (m_lines[idx]->is_modified_line()) {
70 // m_lines[idx] 作为逐出并 reserve 新访问的 cache line,如果它的某个
71 // sector 已经被 MODIFIED,则需要执行写回操作,设置写回标志为 wb = true,
72 // 设置逐出 cache line 的信息。
73 wb = true;
74 evicted.set_info(m_lines[idx]->m_block_addr,
75 m_lines[idx]->get_modified_size(),
76 m_lines[idx]->get_dirty_byte_mask(),
77 m_lines[idx]->get_dirty_sector_mask());
78 // 由于执行写回操作,MODIFIED 造成的 m_dirty 数量应该减1。
79 m_dirty--;
80 }
81 // 执行对新访问的 reserve 操作。
82 m_lines[idx]->allocate(m_config.tag(addr), m_config.block_addr(addr),
83 time, mf->get_access_sector_mask());
84 }
85 break;
86 // Cache block 有效,但是其中的 byte mask = Cache block[mask] 状态无效,说明
87 // sector 缺失。
88 case SECTOR_MISS:
89 assert(m_config.m_cache_type == SECTOR);
90 m_sector_miss++;
91 shader_cache_access_log(m_core_id, m_type_id, 1); // log cache misses
92 // For V100, L1 cache and L2 cache are all `allocate on miss`.
93 if (m_config.m_alloc_policy == ON_MISS) {
94 bool before = m_lines[idx]->is_modified_line();
95 // 设置 m_lines[idx] 为新访问分配一个 sector。
96 ((sector_cache_block *)m_lines[idx])
97 ->allocate_sector(time, mf->get_access_sector_mask());
98 if (before && !m_lines[idx]->is_modified_line()) {
99 m_dirty--;
100 }
101 }
102 break;
103 // probe函数中:
104 // all_reserved 被初始化为 true,是指所有 cache line 都没有能够逐出来为新访问
105 // 提供 RESERVE 的空间,这里一旦满足函数两个 if 条件,说明 cache line 可以被逐
106 // 出来提供空间供 RESERVE 新访问,这里 all_reserved 置为 false。
107 // 而一旦最终 all_reserved 仍旧保持 true 的话,就说明 cache line 不可被逐出,
108 // 发生 RESERVATION_FAIL。因此这里不执行任何操作。
109 case RESERVATION_FAIL:
110 m_res_fail++;
111 shader_cache_access_log(m_core_id, m_type_id, 1); // log cache misses
112 break;
113 default:
114 fprintf(stderr,
115 "tag_array::access - Error: Unknown"
116 "cache_request_status %d\n",
117 status);
118 abort();
119 }
120 return status;
121}
0/****** Read miss functions (Set by config file) ******/
1
2// Baseline read miss: Send read request to lower level memory,
3// perform write-back as necessary
4/*
5READ MISS 操作。
6*/
7enum cache_request_status data_cache::rd_miss_base(
8 new_addr_type addr, unsigned cache_index, mem_fetch *mf, unsigned time,
9 std::list<cache_event> &events, enum cache_request_status status) {
10 // 读 miss 时,就需要将数据请求发送至下一级存储。这里或许需要真实地向下一级存储发
11 // 送读请求,也或许由于 mshr 的存在,可以将数据请求合并进去,这样就不需要真实地向
12 // 下一级存储发送读请求。
13 // miss_queue_full 检查是否一个 miss 请求能够在当前时钟周期内被处理,当一个请求
14 // 的大小大到 m_miss_queue 放不下时即在当前拍内无法处理,发生 RESERVATION_FAIL。
15 if (miss_queue_full(1)) {
16 // cannot handle request this cycle (might need to generate two requests).
17 m_stats.inc_fail_stats(mf->get_access_type(), MISS_QUEUE_FULL);
18 return RESERVATION_FAIL;
19 }
20
21 // m_config.block_addr(addr):
22 // return addr & ~(new_addr_type)(m_line_sz - 1);
23 // |-------|-------------|--------------|
24 // set_index offset in-line
25 // |<--------tag--------> 0 0 0 0 0 0 0 |
26 new_addr_type block_addr = m_config.block_addr(addr);
27 // 标识是否请求被填充进 MSHR 或者 被放到 m_miss_queue 以在下一个周期发送到下一
28 // 级存储。
29 bool do_miss = false;
30 // wb 代表是否需要写回(当一个被逐出的 cache block 被 MODIFIED 时,需要写回到
31 // 下一级存储),evicted代表被逐出的 cache line 的信息。
32 bool wb = false;
33 evicted_block_info evicted;
34 // READ MISS 处理函数,检查 MSHR 是否命中或者 MSHR 是否可用,依此判断是否需要
35 // 向下一级存储发送读请求。
36 send_read_request(addr, block_addr, cache_index, mf, time, do_miss, wb,
37 evicted, events, false, false);
38 // 如果 send_read_request 中数据请求已经被加入到 MSHR,或是原先存在该条目将请
39 // 求合并进去,或是原先不存在该条目将请求插入进去,那么 do_miss 为 true,代表
40 // 要将某个cache block逐出并接收 mf 从下一级存储返回的数据。
41 // m_lines[idx] 作为逐出并 reserve 新访问的 cache line,如果它的某个 sector
42 // 已经被MODIFIED,则需要执行写回操作,设置写回的标志为 wb = true。
43 if (do_miss) {
44 // If evicted block is modified and not a write-through
45 // (already modified lower level).
46 // 这里如果 cache 的写策略为写直达,就不需要在读 miss 时将被逐出的 MODIFIED
47 // cache block 写回到下一级存储,因为这个 cache block 在被 MODIFIED 的时候
48 // 已经被 write-through 到下一级存储了。
49 if (wb && (m_config.m_write_policy != WRITE_THROUGH)) {
50 // 发送写请求,将 MODIFIED 的被逐出的 cache block 写回到下一级存储。
51 // 在 V100 中,
52 // m_wrbk_type:L1 cache 为 L1_WRBK_ACC,L2 cache 为 L2_WRBK_ACC。
53 // m_write_policy:L1 cache 为 WRITE_THROUGH。
54 mem_fetch *wb = m_memfetch_creator->alloc(
55 evicted.m_block_addr, m_wrbk_type, mf->get_access_warp_mask(),
56 evicted.m_byte_mask, evicted.m_sector_mask, evicted.m_modified_size,
57 true, m_gpu->gpu_tot_sim_cycle + m_gpu->gpu_sim_cycle, -1, -1, -1,
58 NULL);
59 // the evicted block may have wrong chip id when advanced L2 hashing
60 // is used, so set the right chip address from the original mf.
61 wb->set_chip(mf->get_tlx_addr().chip);
62 wb->set_partition(mf->get_tlx_addr().sub_partition);
63 // 将数据写请求一同发送至下一级存储。
64 // 需要做的是将读请求类型 WRITE_BACK_REQUEST_SENT放 入events,并将数据请
65 // 求 mf 放入当前 cache 的 m_miss_queue 中,等 baseline_cache::cycle()
66 // 将队首的数据请求 mf 发送给下一级存储。
67 send_write_request(wb, WRITE_BACK_REQUEST_SENT, time, events);
68 }
69 return MISS;
70 }
71 return RESERVATION_FAIL;
72}
0// Read miss handler. Check MSHR hit or MSHR available
1/*
2READ MISS 处理函数,检查 MSHR 是否命中或者 MSHR 是否可用,依此判断是否需要向下一
3级存储发送读请求。
4*/
5void baseline_cache::send_read_request(new_addr_type addr,
6 new_addr_type block_addr,
7 unsigned cache_index, mem_fetch *mf,
8 unsigned time, bool &do_miss, bool &wb,
9 evicted_block_info &evicted,
10 std::list<cache_event> &events,
11 bool read_only, bool wa) {
12 // 1. 如果是 Sector Cache:
13 // mshr_addr 函数返回 mshr 的地址,该地址即为地址 addr 的 tag 位 + set index
14 // 位 + sector offset 位。即除 single sector byte offset 位 以外的所有位。
15 // |<----------mshr_addr----------->|
16 // sector offset off in-sector
17 // |-------------|-----------|
18 // \ /
19 // \ /
20 // |-------|-------------|-------------------|
21 // set_index offset in-line
22 // |<----tag----> 0 0 0 0|
23 // 2. 如果是 Line Cache:
24 // mshr_addr 函数返回 mshr 的地址,该地址即为地址 addr 的 tag 位 + set index
25 // 位。即除 single line byte off-set 位 以外的所有位。
26 // |<----mshr_addr--->|
27 // line offset
28 // |-------------------------|
29 // \ /
30 // \ /
31 // |-------|-------------|-------------------|
32 // set_index offset in-line
33 // |<----tag----> 0 0 0 0|
34 //
35 // mshr_addr 定义:
36 // new_addr_type mshr_addr(new_addr_type addr) const {
37 // return addr & ~(new_addr_type)(m_atom_sz - 1);
38 // }
39 // m_atom_sz = (m_cache_type == SECTOR) ? SECTOR_SIZE : m_line_sz;
40 // 其中 SECTOR_SIZE = const (32 bytes per sector).
41 new_addr_type mshr_addr = m_config.mshr_addr(mf->get_addr());
42 // 这里实际上是 MSHR 查找是否已经有 mshr_addr 的请求被合并到 MSHR。如果已经被挂
43 // 起则 mshr_hit = true。需要注意,MSHR 中的条目是以 mshr_addr 为索引的,即来自
44 // 同一个 line(对于非 Sector Cache)或者来自同一个 sector(对于 Sector Cache)
45 // 的事务被合并,因为这种 cache 所请求的最小单位分别是一个 line 或者一个 sector,
46 // 因此没必要发送那么多事务,只需要发送一次即可。
47 bool mshr_hit = m_mshrs.probe(mshr_addr);
48 // 如果 mshr_addr 在 MSHR 中已存在条目,m_mshrs.full 检查是否该条目的合并数量已
49 // 达到最大合并数;如果 mshr_addr 在 MSHR 中不存在条目,则检查是否有空闲的 MSHR
50 // 条目可以将 mshr_addr 插入进 MSHR。
51 bool mshr_avail = !m_mshrs.full(mshr_addr);
52 if (mshr_hit && mshr_avail) {
53 // 如果 MSHR 命中,且 mshr_addr 对应条目的合并数量没有达到最大合并数,则将数据
54 // 请求 mf 加入到 MSHR 中。
55 if (read_only)
56 m_tag_array->access(block_addr, time, cache_index, mf);
57 else
58 // 更新 tag_array 的状态,包括更新 LRU 状态,设置逐出的 block 或 sector 等。
59 m_tag_array->access(block_addr, time, cache_index, wb, evicted, mf);
60
61 // 将 mshr_addr 地址的数据请求 mf 加入到 MSHR 中。因为命中 MSHR,说明前面已经
62 // 有对该数据的请求发送到下一级缓存了,因此这里只需要等待前面的请求返回即可。
63 m_mshrs.add(mshr_addr, mf);
64 m_stats.inc_stats(mf->get_access_type(), MSHR_HIT);
65 // 标识是否请求被填充进 MSHR 或者 被放到 m_miss_queue 以在下一个周期发送到下一
66 // 级存储。
67 do_miss = true;
68
69 } else if (!mshr_hit && mshr_avail &&
70 (m_miss_queue.size() < m_config.m_miss_queue_size)) {
71 // 如果 MSHR 未命中,但有空闲的 MSHR 条目可以将 mshr_addr 插入进 MSHR,则将数
72 // 据请求 mf 插入到 MSHR 中。
73 // 对于 L1 cache 和 L2 cache,read_only 为 false,对于 read_only_cache 来说,
74 // read_only 为true。
75 if (read_only)
76 m_tag_array->access(block_addr, time, cache_index, mf);
77 else
78 // 更新 tag_array 的状态,包括更新 LRU 状态,设置逐出的 block 或 sector 等。
79 m_tag_array->access(block_addr, time, cache_index, wb, evicted, mf);
80
81 // 将 mshr_addr 地址的数据请求 mf 加入到 MSHR 中。因为没有命中 MSHR,因此还需
82 // 要将该数据的请求发送到下一级缓存。
83 m_mshrs.add(mshr_addr, mf);
84 // if (m_config.is_streaming() && m_config.m_cache_type == SECTOR) {
85 // m_tag_array->add_pending_line(mf);
86 // }
87 // 设置 m_extra_mf_fields[mf],意味着如果 mf 在 m_extra_mf_fields 中存在,即
88 // mf 等待着下一级存储的数据回到当前缓存填充。
89 m_extra_mf_fields[mf] = extra_mf_fields(
90 mshr_addr, mf->get_addr(), cache_index, mf->get_data_size(), m_config);
91 mf->set_data_size(m_config.get_atom_sz());
92 mf->set_addr(mshr_addr);
93 // mf 为 miss 的请求,加入 miss_queue,MISS 请求队列。
94 // 在 baseline_cache::cycle() 中,会将 m_miss_queue 队首的数据包 mf 传递给下
95 // 一层存储。因为没有命中 MSHR,说明前面没有对该数据的请求发送到下一级缓存,
96 // 因此这里需要把该请求发送给下一级存储。
97 m_miss_queue.push_back(mf);
98 mf->set_status(m_miss_queue_status, time);
99 // 在 V100 配置中,wa 对 L1/L2/read_only cache 均为 false。
100 if (!wa) events.push_back(cache_event(READ_REQUEST_SENT));
101 // 标识是否请求被填充进 MSHR 或者 被放到 m_miss_queue 以在下一个周期发送到下一
102 // 级存储。
103 do_miss = true;
104 } else if (mshr_hit && !mshr_avail)
105 // 如果 MSHR 命中,但 mshr_addr 对应条目的合并数量达到了最大合并数。
106 m_stats.inc_fail_stats(mf->get_access_type(), MSHR_MERGE_ENRTY_FAIL);
107 else if (!mshr_hit && !mshr_avail)
108 // 如果 MSHR 未命中,且 mshr_addr 没有空闲的 MSHR 条目可将 mshr_addr 插入。
109 m_stats.inc_fail_stats(mf->get_access_type(), MSHR_ENRTY_FAIL);
110 else
111 assert(0);
112}
0// Sends write request to lower level memory (write or writeback)
1/*
2将数据写请求一同发送至下一级存储。这里需要做的是将写请求类型 WRITE_REQUEST_SENT 或
3WRITE_BACK_REQUEST_SENT 放入 events,并将数据请求 mf 放入 m_miss_queue中,等待下
4一时钟周期 baseline_cache::cycle() 将队首的数据请求 mf 发送给下一级存储。
5*/
6void data_cache::send_write_request(mem_fetch *mf, cache_event request,
7 unsigned time,
8 std::list<cache_event> &events) {
9 events.push_back(request);
10 // 在 baseline_cache::cycle() 中,会将 m_miss_queue 队首的数据包 mf 传递给下
11 // 一级存储。
12 m_miss_queue.push_back(mf);
13 mf->set_status(m_miss_queue_status, time);
14}
0/****** Write-hit functions (Set by config file) ******/
1
2// Write-back hit: Mark block as modified
3/*
4若 Write Hit 时采取 write-back 策略,则需要将数据单写入 cache,不需要直接将数据写入
5下一级存储。等到新数据 fill 进来时,再将旧数据逐出并写入下一级存储。
6*/
7cache_request_status data_cache::wr_hit_wb(new_addr_type addr,
8 unsigned cache_index, mem_fetch *mf,
9 unsigned time,
10 std::list<cache_event> &events,
11 enum cache_request_status status) {
12 // m_config.block_addr(addr):
13 // return addr & ~(new_addr_type)(m_line_sz - 1);
14 // |-------|-------------|--------------|
15 // set_index offset in-line
16 // |<--------tag--------> 0 0 0 0 0 0 0 |
17 // write-back 策略不需要直接将数据写入下一级存储,因此不需要调用miss_queue_full()
18 // 以及 send_write_request() 函数来发送写回请求到下一级存储。
19 new_addr_type block_addr = m_config.block_addr(addr);
20 // 更新 tag_array 的状态,包括更新 LRU 状态,设置逐出的 block 或 sector 等。
21 m_tag_array->access(block_addr, time, cache_index, mf);
22 cache_block_t *block = m_tag_array->get_block(cache_index);
23 // 如果 block 不是 modified line,则增加 dirty 计数。因为如果这个时候 block 不是
24 // modified line,说明这个 block 是 clean line,而现在要写入数据,因此需要将这个
25 // block 设置为 modified line。这样的话,dirty 计数就需要增加。但如果 block 已经
26 // 是 modified line,则不需要增加 dirty 计数,因为这个 block 在上次变成 dirty 的
27 // 时候,dirty 计数已经增加过了。
28 if (!block->is_modified_line()) {
29 m_tag_array->inc_dirty();
30 }
31 // 设置 block 的状态为 modified,即将 block 设置为 MODIFIED。这样的话,下次再有
32 // 数据请求访问这个 block 的时候,就可以直接从 cache 中读取数据,而不需要再次访问
33 // 下一级存储。当然,当有下次填充进这个 block 的数据请求时(block 的 tag 与请求的
34 // tag 不一致),由于这个 block 的状态已经被设置为 modified,因此需要将此 block
35 // 的数据逐出并写回到下一级存储。
36 block->set_status(MODIFIED, mf->get_access_sector_mask());
37 block->set_byte_mask(mf);
38 // 更新一个 cache block 的状态为可读。但需要注意的是,这里的可读是指该 sector 可
39 // 读,而不是整个 block 可读。如果一个 sector 内的所有的 byte mask 位全都设置为
40 // dirty 了,则将该sector 可设置为可读,因为当前的 sector 已经是全部更新为最新值
41 // 了,是可读的。这个函数对所有的数据请求 mf 的所有访问的 sector 进行遍历,这里的
42 // sector 是由 mf 访问的,并由 mf->get_access_sector_mask() 确定。
43 update_m_readable(mf,cache_index);
44
45 return HIT;
46}
0// Write-through hit: Directly send request to lower level memory
1/*
2若 Write Hit 时采取 write-through 策略的话,则需要将数据不单单写入 cache,还需要直
3接将数据写入下一级存储。
4*/
5cache_request_status data_cache::wr_hit_wt(new_addr_type addr,
6 unsigned cache_index, mem_fetch *mf,
7 unsigned time,
8 std::list<cache_event> &events,
9 enum cache_request_status status) {
10 // miss_queue_full 检查是否一个 miss 请求能够在当前时钟周期内被处理,当一个请求的
11 // 大小大到 m_miss_queue 放不下时即在当前拍内无法处理,发生 RESERVATION_FAIL。
12 if (miss_queue_full(0)) {
13 m_stats.inc_fail_stats(mf->get_access_type(), MISS_QUEUE_FULL);
14 // 如果 miss_queue 满了,但由于 write-through 策略要求数据应该直接写入下一级存
15 // 储,因此这里返回 RESERVATION_FAIL,表示当前时钟周期内无法处理该请求。
16 return RESERVATION_FAIL; // cannot handle request this cycle
17 }
18 // m_config.block_addr(addr):
19 // return addr & ~(new_addr_type)(m_line_sz - 1);
20 // |-------|-------------|--------------|
21 // set_index offset in-line
22 // |<--------tag--------> 0 0 0 0 0 0 0 |
23 new_addr_type block_addr = m_config.block_addr(addr);
24 // 更新 tag_array 的状态,包括更新 LRU 状态,设置逐出的 block 或 sector 等。
25 m_tag_array->access(block_addr, time, cache_index, mf);
26 cache_block_t *block = m_tag_array->get_block(cache_index);
27 // 如果 block 不是 modified line,则增加 dirty 计数。因为如果这个时候 block 不是
28 // modified line,说明这个 block 是 clean line,而现在要写入数据,因此需要将这个
29 // block 设置为 modified line。这样的话,dirty 计数就需要增加。但如果 block 已经
30 // 是 modified line,则不需要增加 dirty 计数,因为这个 block 在上次变成 dirty 的
31 // 时候,dirty 计数已经增加过了。
32 if (!block->is_modified_line()) {
33 m_tag_array->inc_dirty();
34 }
35 // 设置 block 的状态为 modified,即将 block 设置为 MODIFIED。这样的话,下次再有
36 // 数据请求访问这个 block 的时候,就可以直接从 cache 中读取数据,而不需要再次访问
37 // 下一级存储。
38 block->set_status(MODIFIED, mf->get_access_sector_mask());
39 block->set_byte_mask(mf);
40 // 更新一个 cache block 的状态为可读。但需要注意的是,这里的可读是指该 sector 可
41 // 读,而不是整个 block 可读。如果一个 sector 内的所有的 byte mask 位全都设置为
42 // dirty 了,则将该sector 可设置为可读,因为当前的 sector 已经是全部更新为最新值
43 // 了,是可读的。这个函数对所有的数据请求 mf 的所有访问的 sector 进行遍历,这里的
44 // sector 是由 mf 访问的,并由 mf->get_access_sector_mask() 确定。
45 update_m_readable(mf,cache_index);
46
47 // generate a write-through
48 // write-through 策略需要将数据写入 cache 的同时也直接写入下一级存储。这里需要做
49 // 的是将写请求类型 WRITE_REQUEST_SENT 放入 events,并将数据请求放入当前 cache
50 // 的 m_miss_queue 中,等待baseline_cache::cycle() 将 m_miss_queue 队首的数
51 // 据写请求 mf 发送给下一级存储。
52 send_write_request(mf, cache_event(WRITE_REQUEST_SENT), time, events);
53
54 return HIT;
55}
0// Write-evict hit: Send request to lower level memory and invalidate
1// corresponding block
2/*
3写逐出命中:向下一级存储发送写回请求并直接逐出相应的 cache block 并设置其无效。
4*/
5cache_request_status data_cache::wr_hit_we(new_addr_type addr,
6 unsigned cache_index, mem_fetch *mf,
7 unsigned time,
8 std::list<cache_event> &events,
9 enum cache_request_status status) {
10 if (miss_queue_full(0)) {
11 m_stats.inc_fail_stats(mf->get_access_type(), MISS_QUEUE_FULL);
12 return RESERVATION_FAIL; // cannot handle request this cycle
13 }
14
15 // generate a write-through/evict
16 cache_block_t *block = m_tag_array->get_block(cache_index);
17 // write-evict 策略需要将 cache block 直接逐出置为无效的同时也直接写入下一级存
18 // 储。这里需要做的是将写请求类型 WRITE_REQUEST_SENT 放入 events,并将数据请求
19 // 放入 m_miss_queue 中,等待baseline_cache::cycle() 将 m_miss_queue 队首的
20 // 数据写请求 mf 发送给下一级存储。
21 send_write_request(mf, cache_event(WRITE_REQUEST_SENT), time, events);
22
23 // Invalidate block
24 // 写逐出,将 cache block 直接逐出置为无效。
25 block->set_status(INVALID, mf->get_access_sector_mask());
26
27 return HIT;
28}
0// Global write-evict, local write-back: Useful for private caches
1/*
2全局访存采用写逐出,本地访存采用写回。这种策略适用于私有缓存。这个策略比较简单,即只
3需要判断当前的数据请求是全局访存还是本地访存,然后分别采用写逐出和写回策略即可。
4*/
5enum cache_request_status data_cache::wr_hit_global_we_local_wb(
6 new_addr_type addr, unsigned cache_index, mem_fetch *mf, unsigned time,
7 std::list<cache_event> &events, enum cache_request_status status) {
8 bool evict = (mf->get_access_type() ==
9 GLOBAL_ACC_W); // evict a line that hits on global memory write
10 if (evict)
11 return wr_hit_we(addr, cache_index, mf, time, events,
12 status); // Write-evict
13 else
14 return wr_hit_wb(addr, cache_index, mf, time, events,
15 status); // Write-back
16}
0/****** Write-miss functions (Set by config file) ******/
1
2// Write-allocate miss: Send write request to lower level memory
3// and send a read request for the same block
4/*
5GPGPU-Sim 3.x版本中的naive写分配策略。wr_miss_wa_naive 策略在写 MISS 时,需要先将
6mf 数据包直接写入下一级存储,即它会将 WRITE_REQUEST_SENT 放入 events,并将数据请求
7mf 放入 m_miss_queue 中,等待下一个周期 baseline_cache::cycle() 将 m_miss_queue
8队首的数据包 mf 发送给下一级存储。其次,wr_miss_wa_naive 策略还会将 addr 地址的数据
9读到当前 cache 中,这时候会执行 send_read_request 函数。但是在 send_read_request
10函数中,很有可能这个读请求需要 evict 一个 block 才可以将新的数据读入到 cache 中,这
11时候如果 evicted block 是 modified line,则需要将这个 evicted block 写回到下一级
12存储,这时候会根据 do_miss 和 wb 的值执行 send_write_request 函数。
13*/
14enum cache_request_status data_cache::wr_miss_wa_naive(
15 new_addr_type addr, unsigned cache_index, mem_fetch *mf, unsigned time,
16 std::list<cache_event> &events, enum cache_request_status status) {
17 // m_config.block_addr(addr):
18 // return addr & ~(new_addr_type)(m_line_sz - 1);
19 // |-------|-------------|--------------|
20 // set_index offset in-line
21 // |<--------tag--------> 0 0 0 0 0 0 0 |
22 new_addr_type block_addr = m_config.block_addr(addr);
23 // 1. 如果是 Sector Cache:
24 // mshr_addr 函数返回 mshr 的地址,该地址即为地址 addr 的 tag 位 + set index
25 // 位 + sector offset 位。即除 single sector byte offset 位 以外的所有位。
26 // |<----------mshr_addr----------->|
27 // sector offset off in-sector
28 // |-------------|-----------|
29 // \ /
30 // \ /
31 // |-------|-------------|-------------------|
32 // set_index offset in-line
33 // |<----tag----> 0 0 0 0|
34 // 2. 如果是 Line Cache:
35 // mshr_addr 函数返回 mshr 的地址,该地址即为地址 addr 的 tag 位 + set index
36 // 位。即除 single line byte off-set 位 以外的所有位。
37 // |<----mshr_addr--->|
38 // line offset
39 // |-------------------------|
40 // \ /
41 // \ /
42 // |-------|-------------|-------------------|
43 // set_index offset in-line
44 // |<----tag----> 0 0 0 0|
45 //
46 // mshr_addr 定义:
47 // new_addr_type mshr_addr(new_addr_type addr) const {
48 // return addr & ~(new_addr_type)(m_atom_sz - 1);
49 // }
50 // m_atom_sz = (m_cache_type == SECTOR) ? SECTOR_SIZE : m_line_sz;
51 // 其中 SECTOR_SIZE = const (32 bytes per sector).
52 new_addr_type mshr_addr = m_config.mshr_addr(mf->get_addr());
53
54 // Write allocate, maximum 3 requests (write miss, read request, write back
55 // request) Conservatively ensure the worst-case request can be handled this
56 // cycle.
57 // MSHR 的 m_data 的 key 中存储了各个合并的地址,probe() 函数主要检查是否命中,
58 // 即主要检查 m_data.keys() 这里面有没有 mshr_addr。
59 bool mshr_hit = m_mshrs.probe(mshr_addr);
60 // 首先查找是否 MSHR 表中有 block_addr 地址的条目。如果存在该条目(命中 MSHR),
61 // 看是否有空间合并进该条目。如果不存在该条目(未命中 MSHR),看是否有其他空间允
62 // 许添加 mshr_addr 这一条目。
63 bool mshr_avail = !m_mshrs.full(mshr_addr);
64 // 在 baseline_cache::cycle() 中,会将 m_miss_queue 队首的数据包 mf 传递给下一
65 // 级存储。因此当遇到 miss 的请求或者写回的请求需要访问下一级存储时,会把 miss 的
66 // 请求放到 m_miss_queue 中。
67 // bool miss_queue_full(unsigned num_miss) {
68 // return ((m_miss_queue.size() + num_miss) >= m_config.m_miss_queue_size);
69 // }
70
71 // 如果 m_miss_queue.size() 已经不能容下三个数据包的话,有可能无法完成后续动作,
72 // 因为后面最多需要执行三次 send_write_request,在 send_write_request 里每执行
73 // 一次,都需要向 m_miss_queue 添加一个数据包。
74 // Write allocate, maximum 3 requests (write miss, read request, write back
75 // request) Conservatively ensure the worst-case request can be handled this
76 // cycle.
77 if (miss_queue_full(2) ||
78 // 如果 miss_queue_full(2) 返回 false,有空余空间支持执行三次 send_write_
79 // request,那么就需要看 MSHR 是否有可用空间。后面这串判断条件其实可以化简成
80 // if (miss_queue_full(2) || !mshr_avail)。
81 // 即符合 RESERVATION_FAIL 的条件:
82 // 1. m_miss_queue 不足以放入三个 WRITE_REQUEST_SENT 请求;
83 // 2. MSHR 不能合并请求(未命中,或者没有可用空间添加新条目)。
84 (!(mshr_hit && mshr_avail) &&
85 !(!mshr_hit && mshr_avail &&
86 (m_miss_queue.size() < m_config.m_miss_queue_size)))) {
87 // check what is the exactly the failure reason
88 if (miss_queue_full(2))
89 m_stats.inc_fail_stats(mf->get_access_type(), MISS_QUEUE_FULL);
90 else if (mshr_hit && !mshr_avail)
91 m_stats.inc_fail_stats(mf->get_access_type(), MSHR_MERGE_ENRTY_FAIL);
92 else if (!mshr_hit && !mshr_avail)
93 m_stats.inc_fail_stats(mf->get_access_type(), MSHR_ENRTY_FAIL);
94 else
95 assert(0);
96
97 // 符合 RESERVATION_FAIL 的条件:
98 // 1. m_miss_queue 不足以放入三个 WRITE_REQUEST_SENT 请求;
99 // 2. MSHR 不能合并请求(未命中,或者没有可用空间添加新条目)。
100 return RESERVATION_FAIL;
101 }
102
103 // send_write_request 执行:
104 // events.push_back(request);
105 // // 在 baseline_cache::cycle() 中,会将 m_miss_queue 队首的数据包 mf 传递
106 // // 给下一级存储。
107 // m_miss_queue.push_back(mf);
108 // mf->set_status(m_miss_queue_status, time);
109 // wr_miss_wa_naive 策略在写 MISS 时,需要先将 mf 数据包直接写入下一级存储,即它
110 // 会将 WRITE_REQUEST_SENT 放入 events,并将数据请求 mf 放入 m_miss_queue 中,
111 // 等待下一个周期 baseline_cache::cycle() 将 m_miss_queue 队首的数据包 mf 发送
112 // 给下一级存储。其次,wr_miss_wa_naive 策略还会将 addr 地址的数据读到当前 cache
113 // 中,这时候会执行 send_read_request 函数。但是在 send_read_request 函数中,很
114 // 有可能这个读请求需要 evict 一个 block 才可以将新的数据读入到 cache 中,这时候
115 // 如果 evicted block 是 modified line,则需要将这个 evicted block 写回到下一级
116 // 存储,这时候会根据 do_miss 和 wb 的值执行 send_write_request 函数。
117 send_write_request(mf, cache_event(WRITE_REQUEST_SENT), time, events);
118 // Tries to send write allocate request, returns true on success and false on
119 // failure
120 // if(!send_write_allocate(mf, addr, block_addr, cache_index, time, events))
121 // return RESERVATION_FAIL;
122
123 const mem_access_t *ma =
124 new mem_access_t(m_wr_alloc_type, mf->get_addr(), m_config.get_atom_sz(),
125 false, // Now performing a read
126 mf->get_access_warp_mask(), mf->get_access_byte_mask(),
127 mf->get_access_sector_mask(), m_gpu->gpgpu_ctx);
128
129 mem_fetch *n_mf =
130 new mem_fetch(*ma, NULL, mf->get_ctrl_size(), mf->get_wid(),
131 mf->get_sid(), mf->get_tpc(), mf->get_mem_config(),
132 m_gpu->gpu_tot_sim_cycle + m_gpu->gpu_sim_cycle);
133
134 // 标识是否请求被填充进 MSHR 或者 被放到 m_miss_queue 以在下一个周期发送到下一
135 // 级存储。
136 bool do_miss = false;
137 // wb 变量标识 tag_array::access() 函数中,如果下面的 send_read_request 函数
138 // 发生 MISS,则需要逐出一个 block,并将这个 evicted block 写回到下一级存储。
139 // 如果这个 block 已经是 modified line,则 wb 为 true,因为在将其分配给新访问
140 // 之前,必须将这个已经 modified 的 block 写回到下一级存储。但如果这个 block
141 // 是 clean line,则 wb 为 false,因为这个 block 不需要写回到下一级存储。这个
142 // evicted block 的信息被设置在 evicted 中。
143 bool wb = false;
144 evicted_block_info evicted;
145
146 // Send read request resulting from write miss
147 send_read_request(addr, block_addr, cache_index, n_mf, time, do_miss, wb,
148 evicted, events, false, true);
149
150 events.push_back(cache_event(WRITE_ALLOCATE_SENT));
151
152 // do_miss 标识是否请求被填充进 MSHR 或者 被放到 m_miss_queue 以在下一个周期
153 // 发送到下一级存储。
154 if (do_miss) {
155 // If evicted block is modified and not a write-through
156 // (already modified lower level)
157 // wb 变量标识 tag_array::access() 函数中,如果下面的 send_read_request 函
158 // 数发生 MISS,则需要逐出一个 block,并将这个 evicted block 写回到下一级存
159 // 储。如果这个 block 已经是 modified line,则 wb 为 true,因为在将其分配给
160 // 新访问之前,必须将这个已经 modified 的 block 写回到下一级存储。但如果这个
161 // block 是 clean line,则 wb 为 false,因为这个 block 不需要写回到下一级存
162 // 储。这个 evicted block 的信息被设置在 evicted 中。
163 // 这里如果 cache 的写策略为写直达,就不需要在读 miss 时将被逐出的 MODIFIED
164 // cache block 写回到下一级存储,因为这个 cache block 在被 MODIFIED 的时候
165 // 已经被 write-through 到下一级存储了。
166 if (wb && (m_config.m_write_policy != WRITE_THROUGH)) {
167 assert(status ==
168 MISS); // SECTOR_MISS and HIT_RESERVED should not send write back
169 mem_fetch *wb = m_memfetch_creator->alloc(
170 evicted.m_block_addr, m_wrbk_type, mf->get_access_warp_mask(),
171 evicted.m_byte_mask, evicted.m_sector_mask, evicted.m_modified_size,
172 true, m_gpu->gpu_tot_sim_cycle + m_gpu->gpu_sim_cycle, -1, -1, -1,
173 NULL);
174 // the evicted block may have wrong chip id when advanced L2 hashing is
175 // used, so set the right chip address from the original mf
176 wb->set_chip(mf->get_tlx_addr().chip);
177 wb->set_partition(mf->get_tlx_addr().sub_partition);
178 // 将 tag_array::access() 函数中逐出的 evicted block 写回到下一级存储。
179 send_write_request(wb, cache_event(WRITE_BACK_REQUEST_SENT, evicted),
180 time, events);
181 }
182 // 如果 do_miss 为 true,表示请求被填充进 MSHR 或者 被放到 m_miss_queue 以在
183 // 下一个周期发送到下一级存储。即整个写 MISS 处理函数的所有过程全部完成,返回的
184 // 是 write miss 这个原始写请求的状态。
185 return MISS;
186 }
187
188 // 如果 do_miss 为 false,表示请求未被填充进 MSHR 或者 未被放到 m_miss_queue 以
189 // 在下一个周期发送到下一级存储。即整个写 MISS 处理函数没有将读请求发送出去,因此
190 // 返回 RESERVATION_FAIL。
191 return RESERVATION_FAIL;
192}
0// No write-allocate miss: Simply send write request to lower level memory
1/*
2No write-allocate miss,这个处理函数仅仅简单地将写请求发送到下一级存储。
3*/
4enum cache_request_status data_cache::wr_miss_no_wa(
5 new_addr_type addr, unsigned cache_index, mem_fetch *mf, unsigned time,
6 std::list<cache_event> &events, enum cache_request_status status) {
7 // 如果 m_miss_queue.size() 已经不能容下一个数据包的话,有可能无法完成后续动作,
8 // 因为后面最多需要执行一次 send_write_request,在 send_write_request 里每执行
9 // 一次,都需要向 m_miss_queue 添加一个数据包。
10 if (miss_queue_full(0)) {
11 m_stats.inc_fail_stats(mf->get_access_type(), MISS_QUEUE_FULL);
12 return RESERVATION_FAIL; // cannot handle request this cycle
13 }
14
15 // on miss, generate write through (no write buffering -- too many threads
16 // for that)
17 // send_write_request 执行:
18 // events.push_back(request);
19 // // 在 baseline_cache::cycle() 中,会将 m_miss_queue 队首的数据包 mf 传递
20 // // 给下一级存储。
21 // m_miss_queue.push_back(mf);
22 // mf->set_status(m_miss_queue_status, time);
23 // No write-allocate miss 策略在写 MISS 时,直接将 mf 数据包直接写入下一级存储。
24 // 这里需要做的是将写请求类型 WRITE_REQUEST_SENT 放入 events,并将数据请求放入
25 // m_miss_queue 中,等待baseline_cache::cycle() 将 m_miss_queue 队首的数据写
26 // 请求 mf 发送给下一级存储。
27 send_write_request(mf, cache_event(WRITE_REQUEST_SENT), time, events);
28
29 return MISS;
30}
0/*
1write_allocated_fetch_on_write 策略,在写入时读取策略中,当写入 sector 的单个字节
2时,L2 会读取整个 sector ,然后将写入的部分合并到该 sector ,并将该 sector 设置为已
3修改。
4*/
5enum cache_request_status data_cache::wr_miss_wa_fetch_on_write(
6 new_addr_type addr, unsigned cache_index, mem_fetch *mf, unsigned time,
7 std::list<cache_event> &events, enum cache_request_status status) {
8 // m_config.block_addr(addr):
9 // return addr & ~(new_addr_type)(m_line_sz - 1);
10 // |-------|-------------|--------------|
11 // set_index offset in-line
12 // |<--------tag--------> 0 0 0 0 0 0 0 |
13 new_addr_type block_addr = m_config.block_addr(addr);
14 // 1. 如果是 Sector Cache:
15 // mshr_addr 函数返回 mshr 的地址,该地址即为地址 addr 的 tag 位 + set index
16 // 位 + sector offset 位。即除 single sector byte offset 位 以外的所有位。
17 // |<----------mshr_addr----------->|
18 // sector offset off in-sector
19 // |-------------|-----------|
20 // \ /
21 // \ /
22 // |-------|-------------|-------------------|
23 // set_index offset in-line
24 // |<----tag----> 0 0 0 0|
25 // 2. 如果是 Line Cache:
26 // mshr_addr 函数返回 mshr 的地址,该地址即为地址 addr 的 tag 位 + set index
27 // 位。即除 single line byte off-set 位 以外的所有位。
28 // |<----mshr_addr--->|
29 // line offset
30 // |-------------------------|
31 // \ /
32 // \ /
33 // |-------|-------------|-------------------|
34 // set_index offset in-line
35 // |<----tag----> 0 0 0 0|
36 //
37 // mshr_addr 定义:
38 // new_addr_type mshr_addr(new_addr_type addr) const {
39 // return addr & ~(new_addr_type)(m_atom_sz - 1);
40 // }
41 // m_atom_sz = (m_cache_type == SECTOR) ? SECTOR_SIZE : m_line_sz;
42 // 其中 SECTOR_SIZE = const (32 bytes per sector).
43 new_addr_type mshr_addr = m_config.mshr_addr(mf->get_addr());
44
45 // 如果请求写入的字节数等于整个 cache line/sector 的大小,那么直接写入 cache,并
46 // 将 cache 设置为 MODIFIED,而不需要发送读请求到下一级存储。
47 if (mf->get_access_byte_mask().count() == m_config.get_atom_sz()) {
48 // if the request writes to the whole cache line/sector, then, write and
49 // set cache line Modified. and no need to send read request to memory or
50 // reserve mshr.
51 // 如果 m_miss_queue.size() 已经不能容下一个数据包的话,有可能无法完成后续动
52 // 作,因为后面最多需要执行一次 send_write_request,在 send_write_request 里
53 // 每执行一次,都需要向 m_miss_queue 添加一个数据包。
54 if (miss_queue_full(0)) {
55 m_stats.inc_fail_stats(mf->get_access_type(), MISS_QUEUE_FULL);
56 return RESERVATION_FAIL; // cannot handle request this cycle
57 }
58 // wb 变量标识 tag_array::access() 函数中,如果下面的 send_read_request 函数
59 // 发生 MISS,则需要逐出一个 block,并将这个 evicted block 写回到下一级存储。
60 // 如果这个 block 已经是 modified line,则 wb 为 true,因为在将其分配给新访问
61 // 之前,必须将这个已经 modified 的 block 写回到下一级存储。但如果这个 block
62 // 是 clean line,则 wb 为 false,因为这个 block 不需要写回到下一级存储。这个
63 // evicted block 的信息被设置在 evicted 中。
64 bool wb = false;
65 evicted_block_info evicted;
66 // 更新 tag_array 的状态,包括更新 LRU 状态,设置逐出的 block 或 sector 等。
67 cache_request_status status =
68 m_tag_array->access(block_addr, time, cache_index, wb, evicted, mf);
69 assert(status != HIT);
70 cache_block_t *block = m_tag_array->get_block(cache_index);
71 // 如果 block 不是 modified line,则增加 dirty 计数。因为如果这个时候 block 不
72 // 是 modified line,说明这个 block 是 clean line,而现在要写入数据,因此需要将
73 // 这个 block 设置为 modified line。这样的话,dirty 计数就需要增加。但若 block
74 // 已经是 modified line,则不需要增加 dirty 计数,这个 block 在上次变成 dirty
75 // 的时候,dirty 计数已经增加过了。
76 if (!block->is_modified_line()) {
77 m_tag_array->inc_dirty();
78 }
79 // 设置 block 的状态为 modified,即将 block 设置为 MODIFIED。这样的话,下次再
80 // 有数据请求访问这个 block 的时候,就可以直接从 cache 中读取数据,而不需要再次
81 // 访问下一级存储。
82 block->set_status(MODIFIED, mf->get_access_sector_mask());
83 block->set_byte_mask(mf);
84
85 // 暂时不用关心这个函数。
86 if (status == HIT_RESERVED)
87 block->set_ignore_on_fill(true, mf->get_access_sector_mask());
88
89 // 只要 m_tag_array->access 返回的状态不是 RESERVATION_FAIL,就说明或者发生了
90 // HIT_RESERVED,或者 SECTOR_MISS,又或者 MISS。这里只要不是 RESERVATION_FAIL,
91 // 就代表有 cache block 被分配了,因此要根据这个被逐出的 cache block 是否需要写
92 // 回,将这个 block 写回到下一级存储。
93 if (status != RESERVATION_FAIL) {
94 // If evicted block is modified and not a write-through
95 // (already modified lower level)
96 // wb 变量标识 tag_array::access() 函数中,如果上面的 m_tag_array->access
97 // 函数发生 MISS,则需要逐出一个 block,并将这个 evicted block 写回到下一级
98 // 存储。如果这个 block 已经是 modified line,则 wb 为 true,因为在将其分配
99 // 给新访问之前,必须将这个已经 modified 的 block 写回到下一级存储。但如果这
100 // 个 block 是 clean line,则 wb 为 false,因为这个 block 不需要写回到下一
101 // 级存储。这个 evicted block 的信息被设置在 evicted 中。
102 // 这里如果 cache 的写策略为写直达,就不需要在读 miss 时将被逐出的 MODIFIED
103 // cache block 写回到下一级存储,因为这个 cache block 在被 MODIFIED 的时候
104 // 已经被 write-through 到下一级存储了。
105 if (wb && (m_config.m_write_policy != WRITE_THROUGH)) {
106 mem_fetch *wb = m_memfetch_creator->alloc(
107 evicted.m_block_addr, m_wrbk_type, mf->get_access_warp_mask(),
108 evicted.m_byte_mask, evicted.m_sector_mask, evicted.m_modified_size,
109 true, m_gpu->gpu_tot_sim_cycle + m_gpu->gpu_sim_cycle, -1, -1, -1,
110 NULL);
111 // the evicted block may have wrong chip id when advanced L2 hashing is
112 // used, so set the right chip address from the original mf
113 wb->set_chip(mf->get_tlx_addr().chip);
114 wb->set_partition(mf->get_tlx_addr().sub_partition);
115 // 写回 evicted block 到下一级存储。
116 send_write_request(wb, cache_event(WRITE_BACK_REQUEST_SENT, evicted),
117 time, events);
118 }
119 // 整个写 MISS 处理函数的所有过程全部完成,返回的是 write miss 这个原始写请求
120 // 的状态。
121 return MISS;
122 }
123 // 整个写 MISS 处理函数没有分配新的 cache block,并将逐出的 block 写回,因此返
124 // 回 RESERVATION_FAIL。
125 return RESERVATION_FAIL;
126 } else {
127 // 如果请求写入的字节数小于整个 cache line/sector 的大小,那么需要发送读请求到
128 // 下一级存储,然后将写入的部分合并到该 sector ,并将该 sector 设置为已修改。
129
130 // MSHR 的 m_data 的 key 中存储了各个合并的地址,probe() 函数主要检查是否命中,
131 // 即主要检查 m_data.keys() 这里面有没有 mshr_addr。
132 bool mshr_hit = m_mshrs.probe(mshr_addr);
133 // 首先查找是否 MSHR 表中有 block_addr 地址的条目。如果存在该条目(命中
134 // MSHR),看是否有空间合并进该条目。如果不存在该条目(未命中 MSHR),看
135 // 是否有其他空间允许添加 mshr_addr 这一条目。
136 bool mshr_avail = !m_mshrs.full(mshr_addr);
137 // 如果 m_miss_queue.size() 已经不能容下两个数据包的话,有可能无法完成后续
138 // 动作,因为后面最多需要执行一次 send_read_request 和一次 send_write_request,
139 // 这两次有可能最多需要向 m_miss_queue 添加两个数据包。
140 // 若 miss_queue_full(1) 返回 false,有空余空间支持执行一次 send_write_request
141 // 和一次 send_read_request,那么就需要看 MSHR 是否有可用空间。后面这串判断条件
142 // 其实可以化简成:
143 // if (miss_queue_full(1) || !mshr_avail)。
144 // 即符合 RESERVATION_FAIL 的条件:
145 // 1. m_miss_queue 不足以放入一个读一个写,共两个请求;
146 // 2. MSHR 不能合并请求(未命中,或者没有可用空间添加新条目)。
147 if (miss_queue_full(1) ||
148 (!(mshr_hit && mshr_avail) &&
149 !(!mshr_hit && mshr_avail &&
150 (m_miss_queue.size() < m_config.m_miss_queue_size)))) {
151 // check what is the exactly the failure reason
152 if (miss_queue_full(1))
153 m_stats.inc_fail_stats(mf->get_access_type(), MISS_QUEUE_FULL);
154 else if (mshr_hit && !mshr_avail)
155 m_stats.inc_fail_stats(mf->get_access_type(), MSHR_MERGE_ENRTY_FAIL);
156 else if (!mshr_hit && !mshr_avail)
157 m_stats.inc_fail_stats(mf->get_access_type(), MSHR_ENRTY_FAIL);
158 else
159 assert(0);
160
161 return RESERVATION_FAIL;
162 }
163
164 // prevent Write - Read - Write in pending mshr
165 // allowing another write will override the value of the first write, and
166 // the pending read request will read incorrect result from the second write
167 if (m_mshrs.probe(mshr_addr) &&
168 m_mshrs.is_read_after_write_pending(mshr_addr) && mf->is_write()) {
169 // assert(0);
170 m_stats.inc_fail_stats(mf->get_access_type(), MSHR_RW_PENDING);
171 return RESERVATION_FAIL;
172 }
173
174 const mem_access_t *ma = new mem_access_t(
175 m_wr_alloc_type, mf->get_addr(), m_config.get_atom_sz(),
176 false, // Now performing a read
177 mf->get_access_warp_mask(), mf->get_access_byte_mask(),
178 mf->get_access_sector_mask(), m_gpu->gpgpu_ctx);
179
180 mem_fetch *n_mf = new mem_fetch(
181 *ma, NULL, mf->get_ctrl_size(), mf->get_wid(), mf->get_sid(),
182 mf->get_tpc(), mf->get_mem_config(),
183 m_gpu->gpu_tot_sim_cycle + m_gpu->gpu_sim_cycle, NULL, mf);
184
185 // m_config.block_addr(addr):
186 // return addr & ~(new_addr_type)(m_line_sz - 1);
187 // |-------|-------------|--------------|
188 // set_index offset in-line
189 // |<--------tag--------> 0 0 0 0 0 0 0 |
190 new_addr_type block_addr = m_config.block_addr(addr);
191 bool do_miss = false;
192 bool wb = false;
193 evicted_block_info evicted;
194 // 发送读请求到下一级存储,然后将写入的部分合并到该 sector ,并将该 sector 设
195 // 置为已修改。
196 send_read_request(addr, block_addr, cache_index, n_mf, time, do_miss, wb,
197 evicted, events, false, true);
198
199 cache_block_t *block = m_tag_array->get_block(cache_index);
200 // 将 block 设置为在下次 fill 时,置为 MODIFIED。这样的话,下次再有数据请求填
201 // 入 fill 时:
202 // m_status = m_set_modified_on_fill ? MODIFIED : VALID; 或
203 // m_status[sidx] = m_set_modified_on_fill[sidx] ? MODIFIED : VALID;
204 block->set_modified_on_fill(true, mf->get_access_sector_mask());
205 block->set_byte_mask_on_fill(true);
206
207 events.push_back(cache_event(WRITE_ALLOCATE_SENT));
208
209 // do_miss 标识是否请求被填充进 MSHR 或者 被放到 m_miss_queue 以在下一个周期
210 // 发送到下一级存储。
211 if (do_miss) {
212 // If evicted block is modified and not a write-through
213 // (already modified lower level)
214 // wb 变量标识 tag_array::access() 函数中,如果上面的 m_tag_array->access
215 // 函数发生 MISS,则需要逐出一个 block,并将这个 evicted block 写回到下一级
216 // 存储。如果这个 block 已经是 modified line,则 wb 为 true,因为在将其分配
217 // 给新访问之前,必须将这个已经 modified 的 block 写回到下一级存储。但如果这
218 // 个 block 是 clean line,则 wb 为 false,因为这个 block 不需要写回到下一
219 // 级存储。这个 evicted block 的信息被设置在 evicted 中。
220 // 这里如果 cache 的写策略为写直达,就不需要在读 miss 时将被逐出的 MODIFIED
221 // cache block 写回到下一级存储,因为这个 cache block 在被 MODIFIED 的时候
222 // 已经被 write-through 到下一级存储了。
223 if (wb && (m_config.m_write_policy != WRITE_THROUGH)) {
224 mem_fetch *wb = m_memfetch_creator->alloc(
225 evicted.m_block_addr, m_wrbk_type, mf->get_access_warp_mask(),
226 evicted.m_byte_mask, evicted.m_sector_mask, evicted.m_modified_size,
227 true, m_gpu->gpu_tot_sim_cycle + m_gpu->gpu_sim_cycle, -1, -1, -1,
228 NULL);
229 // the evicted block may have wrong chip id when advanced L2 hashing is
230 // used, so set the right chip address from the original mf
231 wb->set_chip(mf->get_tlx_addr().chip);
232 wb->set_partition(mf->get_tlx_addr().sub_partition);
233 // 写回 evicted block 到下一级存储。
234 send_write_request(wb, cache_event(WRITE_BACK_REQUEST_SENT, evicted),
235 time, events);
236 }
237 // 整个写 MISS 处理函数的所有过程全部完成,返回的是 write miss 这个原始写请求
238 // 的状态。
239 return MISS;
240 }
241 // 整个写 MISS 处理函数没有分配新的 cache block,并将逐出的 block 写回,因此返
242 // 回 RESERVATION_FAIL。
243 return RESERVATION_FAIL;
244 }
245}
0/*
1write_allocated_lazy_fetch_on_read 策略。
2需要参考 https://arxiv.org/pdf/1810.07269.pdf 论文对 Volta 架构访存行为的解释。
3L2 缓存应用了不同的写入分配策略,将其命名为延迟读取读取,这是写入验证和写入时读取
4之间的折衷方案。当收到对已修改扇区的扇区读请求时,它首先检查扇区写掩码是否完整,即
5所有字节均已写入并且该行完全可读。如果是,则读取该扇区;否则,与写入时读取类似,它
6生成该扇区的读取请求并将其与修改后的字节合并。
7*/
8enum cache_request_status data_cache::wr_miss_wa_lazy_fetch_on_read(
9 new_addr_type addr, unsigned cache_index, mem_fetch *mf, unsigned time,
10 std::list<cache_event> &events, enum cache_request_status status) {
11 // m_config.block_addr(addr):
12 // return addr & ~(new_addr_type)(m_line_sz - 1);
13 // |-------|-------------|--------------|
14 // set_index offset in-line
15 // |<--------tag--------> 0 0 0 0 0 0 0 |
16 new_addr_type block_addr = m_config.block_addr(addr);
17
18 // if the request writes to the whole cache block/sector, then, write and set
19 // cache block Modified. and no need to send read request to memory or reserve
20 // mshr
21
22 // FETCH_ON_READ policy: https://arxiv.org/pdf/1810.07269.pdf
23 // In literature, there are two different write allocation policies [32], fetch-
24 // on-write and write-validate. In fetch-on-write, when we write to a single byte
25 // of a sector, the L2 fetches the whole sector then merges the written portion
26 // to the sector and sets the sector as modified. In the write-validate policy,
27 // no read fetch is required, instead each sector has a bit-wise write-mask. When
28 // a write to a single byte is received, it writes the byte to the sector, sets
29 // the corresponding write bit and sets the sector as valid and modified. When a
30 // modified cache line is evicted, the cache line is written back to the memory
31 // along with the write mask. It is important to note that, in a write-validate
32 // policy, it assumes the read and write granularity can be in terms of bytes in
33 // order to exploit the benefits of the write-mask. In fact, based on our micro-
34 // benchmark shown in Figure 5, we have observed that the L2 cache applies some-
35 // thing similar to write-validate. However, all the reads received by L2 caches
36 // from the coalescer are 32-byte sectored accesses. Thus, the read access granu-
37 // larity (32 bytes) is different from the write access granularity (one byte).
38 // To handle this, the L2 cache applies a different write allocation policy,
39 // which we named lazy fetch-on-read, that is a compromise between write-validate
40 // and fetch-on-write. When a sector read request is received to a modified
41 // sector,it first checks if the sector write-mask is complete, i.e. all the
42 // bytes have been written to and the line is fully readable. If so, it reads
43 // the sector, otherwise, similar to fetch-on-write, it generates a read request
44 // for this sector and merges it with the modified bytes.
45
46 // 若 m_miss_queue.size() 已经不能容下一个数据包的话,有可能无法完成后续动作,因
47 // 为后面最多需要执行一次 send_write_request,有可能最多需要向 m_miss_queue 添加
48 // 两个数据包。虽然后面代码里有两次 send_write_request,但是这两次是不会同时发
49 // 生。
50 if (miss_queue_full(0)) {
51 m_stats.inc_fail_stats(mf->get_access_type(), MISS_QUEUE_FULL);
52 return RESERVATION_FAIL; // cannot handle request this cycle
53 }
54
55 // 在 V100 配置中,L1 cache 为 'T'-write through,L2 cache 为 'B'-write back。
56 if (m_config.m_write_policy == WRITE_THROUGH) {
57 // 如果是 write through,则需要直接将数据一同写回下一层存储。将数据写请求一同发
58 // 送至下一级存储。这里需要做的是将读请求类型 WRITE_REQUEST_SENT 放入 events,
59 // 并将数据请求 mf 放入当前 cache 的 m_miss_queue 中,等待 baseline_cache::
60 // cycle() 将队首的数据请求 mf 发送给下一级存储。
61 send_write_request(mf, cache_event(WRITE_REQUEST_SENT), time, events);
62 }
63
64 // wb 变量标识 tag_array::access() 函数中,如果下面的 send_read_request 函数发
65 // 生 MISS,则需要逐出一个 block,并将这个 evicted block 写回到下一级存储。如果
66 // 这个 block 已经是 modified line,则 wb 为 true,因为在将其分配给新访问之前,
67 // 必须将这个已经 modified 的 block 写回到下一级存储。但如果这个 block 是 clean
68 // line,则 wb 为 false,因为这个 block 不需要写回到下一级存储。这个 evicted
69 // block 的信息被设置在 evicted 中。
70 bool wb = false;
71 // evicted 记录着被逐出的 cache block 的信息。
72 evicted_block_info evicted;
73 // 更新 tag_array 的状态,包括更新 LRU 状态,设置逐出的 block 或 sector 等。
74 cache_request_status m_status =
75 m_tag_array->access(block_addr, time, cache_index, wb, evicted, mf);
76
77 // Theoretically, the passing parameter status should be the same as the
78 // m_status, if the assertion fails here, go to function `wr_miss_wa_lazy
79 // _fetch_on_read` to remove this assertion.
80 // assert((m_status == status));
81 assert(m_status != HIT);
82 // cache_index 是 cache block 的 index。
83 cache_block_t *block = m_tag_array->get_block(cache_index);
84 // 如果 block 不是 modified line,则增加 dirty 计数。因为如果这个时候 block 不
85 // 是 modified line,说明这个 block 是 clean line,而现在要写入数据,因此需要将
86 // 这个 block 设置为 modified line。这样的话,dirty 计数就需要增加。但若 block
87 // 已经是 modified line,则不需要增加 dirty 计数,这个 block 在上次变成 dirty
88 // 的时候,dirty 计数已经增加过了。
89 if (!block->is_modified_line()) {
90 m_tag_array->inc_dirty();
91 }
92 // 设置 block 的状态为 modified,即将 block 设置为 MODIFIED。这样的话,下次再
93 // 有数据请求访问这个 block 的时候,就可以直接从 cache 中读取数据,而不需要再次
94 // 访问下一级存储。
95 block->set_status(MODIFIED, mf->get_access_sector_mask());
96 block->set_byte_mask(mf);
97 // 如果 Cache block[mask] 状态是 RESERVED,说明有其他的线程正在读取这个 Cache
98 // block。挂起的命中访问已命中处于 RESERVED 状态的缓存行,这意味着同一行上已存在
99 // 由先前缓存未命中发送的 flying 内存请求。
100 if (m_status == HIT_RESERVED) {
101 // 在当前版本的 GPGPU-Sim 中,set_ignore_on_fill 暂时用不到。
102 block->set_ignore_on_fill(true, mf->get_access_sector_mask());
103 // cache block 的每个 sector 都有一个标志位 m_set_modified_on_fill[i],标记
104 // 着这个 cache block 是否被修改,在sector_cache_block::fill() 函数调用的时
105 // 候会使用。
106 // 将 block 设置为在下次 fill 时,置为 MODIFIED。这样的话,下次再有数据请求填
107 // 入 fill 时:
108 // m_status = m_set_modified_on_fill ? MODIFIED : VALID; 或
109 // m_status[sidx] = m_set_modified_on_fill[sidx] ? MODIFIED : VALID;
110 block->set_modified_on_fill(true, mf->get_access_sector_mask());
111 // 在 FETCH_ON_READ policy: https://arxiv.org/pdf/1810.07269.pdf 中提到,
112 // 访问 cache 发生 miss 时:
113 // In the write-validate policy, no read fetch is required, instead each
114 // sector has a bit-wise write-mask. When a write to a single byte is
115 // received, it writes the byte to the sector, sets the corresponding
116 // write bit and sets the sector as valid and modified. When a modified
117 // cache line is evicted, the cache line is written back to the memory
118 // along with the write mask.
119 // 而在 FETCH_ON_READ 中,需要设置 sector 的 byte mask。这里就是指设置这个
120 // byte mask 的标志。
121 block->set_byte_mask_on_fill(true);
122 }
123
124 // m_config.get_atom_sz() 为 SECTOR_SIZE = 4,即 mf 访问的是一整个 4 字节。
125 if (mf->get_access_byte_mask().count() == m_config.get_atom_sz()) {
126 // 由于 mf 访问的是整个 sector,因此整个 sector 都是 dirty 的,设置访问的
127 // sector 可读。
128 block->set_m_readable(true, mf->get_access_sector_mask());
129 } else {
130 // 由于 mf 访问的是部分 sector,因此只有 mf 访问的那部分 sector 是 dirty 的,
131 // 设置访问的 sector 不可读。但是设置在下次这个 sector 被 fill 时,mf->get_
132 // access_sector_mask() 标识的 byte 置为 MODIFIED。
133 block->set_m_readable(false, mf->get_access_sector_mask());
134 if (m_status == HIT_RESERVED)
135 block->set_readable_on_fill(true, mf->get_access_sector_mask());
136 }
137 // 更新一个 cache block 的状态为可读。如果所有的 byte mask 位全都设置为 dirty
138 // 了,则将该 sector 可设置为可读,因为当前的 sector 已经是全部更新为最新值了,
139 // 是可读的。这个函数对所有的数据请求 mf 的所有访问的 sector 进行遍历,如果 mf
140 // 所访问的所有的 byte mask 位全都设置为 dirty 了,则将该 cache block 设置为可
141 // 读。
142 update_m_readable(mf,cache_index);
143
144 // 只要 m_tag_array->access 返回的状态不是 RESERVATION_FAIL,就说明或者发生了
145 // HIT_RESERVED,或者 SECTOR_MISS,又或者 MISS。这里只要不是 RESERVATION_FAIL,
146 // 就代表有 cache block 被分配了,因此要根据这个被逐出的 cache block 是否需要写
147 // 回,将这个 block 写回到下一级存储。
148 if (m_status != RESERVATION_FAIL) {
149 // If evicted block is modified and not a write-through
150 // (already modified lower level)
151 // wb 变量标识 tag_array::access() 函数中,如果上面的 m_tag_array->access
152 // 函数发生 MISS,则需要逐出一个 block,并将这个 evicted block 写回到下一级
153 // 存储。如果这个 block 已经是 modified line,则 wb 为 true,因为在将其分配
154 // 给新访问之前,必须将这个已经 modified 的 block 写回到下一级存储。但如果这
155 // 个 block 是 clean line,则 wb 为 false,因为这个 block 不需要写回到下一
156 // 级存储。这个 evicted block 的信息被设置在 evicted 中。
157 // 这里如果 cache 的写策略为写直达,就不需要在读 miss 时将被逐出的 MODIFIED
158 // cache block 写回到下一级存储,因为这个 cache block 在被 MODIFIED 的时候
159 // 已经被 write-through 到下一级存储了。
160 if (wb && (m_config.m_write_policy != WRITE_THROUGH)) {
161 mem_fetch *wb = m_memfetch_creator->alloc(
162 evicted.m_block_addr, m_wrbk_type, mf->get_access_warp_mask(),
163 evicted.m_byte_mask, evicted.m_sector_mask, evicted.m_modified_size,
164 true, m_gpu->gpu_tot_sim_cycle + m_gpu->gpu_sim_cycle, -1, -1, -1,
165 NULL);
166 // the evicted block may have wrong chip id when advanced L2 hashing is
167 // used, so set the right chip address from the original mf
168 wb->set_chip(mf->get_tlx_addr().chip);
169 wb->set_partition(mf->get_tlx_addr().sub_partition);
170 // 写回 evicted block 到下一级存储。
171 send_write_request(wb, cache_event(WRITE_BACK_REQUEST_SENT, evicted),
172 time, events);
173 }
174 // 整个写 MISS 处理函数的所有过程全部完成,返回的是 write miss 这个原始写请求
175 // 的状态。
176 return MISS;
177 }
178 // 整个写 MISS 处理函数没有分配新的 cache block,并将逐出的 block 写回,因此返
179 // 回 RESERVATION_FAIL。
180 return RESERVATION_FAIL;
181}