1. Cache的访问

Cache作为一种高速存储资源,通过存储近期或频繁访问的数据,极大地减少了处理器与主存之间的数据传输时间,从而提升了系统的整体性能。本章主要介绍 GPGPU-Sim 如何模拟对 Cache 的访问过程。

1.1. Cache 基础

首先需要了解的就是 Sector CacheLine CacheCache 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的两种组织形式。

列表 1.1 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_szm_nset 分别是 Cache 的 line sizeset 的数量),这样可以允许更复杂的 set index 位 的计算,从而避免将 set index 位 不同但是 traditional tag 位 相同的地址映射到同一个 set。这里是把完整的 [traditional tag 位 + set index 位 + log2(m_line_sz)’b0] 来作为 tag 位

列表 1.2 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 的计算

列表 1.3 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 位的计算 所示:

列表 1.4 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 的地址映射。

列表 1.5 Cache Block的地址映射
 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 的状态,包含以下几种:

列表 1.6 Cache Block State
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 无效数据。需要注意的是,这里的无效与下面的 MODIFIEDRESERVED 不同,意味着当前 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 的访问状态有以下几种:

列表 1.7 Cache Request Status
 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_hitm_wr_missm_rd_hitm_rd_miss

列表 1.8 Data Cache Access Function
 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}
列表 1.9 tag_array::probe() 函数
  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}
列表 1.10 data_cache::process_tag_probe() 函数
 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}
列表 1.11 tag_array::access() 函数
  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}
列表 1.12 data_cache::rd_miss_base() 函数
 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}
列表 1.13 baseline_cache::send_read_request() 函数
  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}
列表 1.14 data_cache::send_write_request() 函数
 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}
列表 1.15 data_cache::wr_hit_wb() 函数
 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}
列表 1.16 data_cache::wr_hit_wt() 函数
 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}
列表 1.17 data_cache::wr_hit_we() 函数
 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}
列表 1.18 data_cache::wr_hit_global_we_local_wb() 函数
 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}
列表 1.19 data_cache::wr_miss_wa_naive() 函数
  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}
列表 1.20 data_cache::wr_miss_no_wa() 函数
 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}
列表 1.21 data_cache::wr_miss_wa_fetch_on_write() 函数
  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}
列表 1.22 data_cache::wr_miss_wa_lazy_fetch_on_read() 函数
  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}