[Computer Architecture] Assignment 5 - Cache
"Cache"
[Objective]
Direct-Mapped Data Cache에 Victim Cache Buffer 추가를 통해 Conflict Miss를 줄여 Cache Memory의 효율을 높이기
CPU → Data Cache → Victim Cache → Memory
[Implementation]
▣ Remove
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | block_t victim_cache_t::remove(uint64_t m_addr) { block_t block; num_accesses++; uint64_t tag = m_addr >> 5; for (uint64_t i = 0; i < num_entries; i++) { if (blocks[i].valid && (blocks[i].tag == tag)) { num_hits++; block = blocks[i]; for (uint64_t j = i; j < num_entries - 1; j++) { blocks[j] = blocks[j + 1]; } blocks[num_entries - 1].valid = 0; num_entries--; return block; } } block.valid = 0; return block; } | cs |
· 주소 "m_addr"에 해당하는 Block이 Victim Cache에 존재 → Victim Cache에서 제거해서 반환(없는 경우 Invalid Block 반환)
2 : 나중에 Victim Cache에서 못 찾았을 때 Invalid Block을 반환할 용도
3 : Victim Cache가 검색 된 횟수 증가(Data Cache Hit : Victim Cache 검색 X / Data Cache Miss : Victim Cache 검색 O)
4 : Victim Cache에서 찾기 위해 Block Offset을 나타내는 Bit를 제거하여 Victim Cache용 Tag 형식으로 전환
→ Direct-Mapped Data Cache = [Tag][Set Index][Block Offset]
→ Victim Cache = Fully Associative Cache = [Tag' = {Tag, Set Index}]
5 : 일치하는 Block을 찾기 위해 전체 Entry에서 검사
6 : Entry가 유효 & Tag가 일치하는 Block 존재
7 : Victim Cache Hit 횟수 증가
8 : Hit Block을 "block"에 저장
9 : Victim Cache의 FIFO 순서를 유지하기 위해 Hit Block Index부터 반복
10 : Hit Block Index부터 뒤의 값을 앞으로 당겨서 Hit Block 제거 후 FIFO 유지
12 : 앞으로 당긴 후 마지막 Entry는 기존 값이 중복으로 남아 있을 수 있으므로 Invalid Block으로 처리
13 : Valid Block 개수 감소
14 : Hit Block 반환
17 : Victim Cache Miss → Invalid Block
18 : Invalid Block 반환
** block_offset : "data_cache_t" Class의 Member 변수 → remove( ) : "victim_cache_t" Class 함수
∴ block_offset을 직접 사용 불가(32B이므로 5로 대체)
▣ Insert
1 2 3 4 5 6 7 8 9 10 11 | void victim_cache_t::insert(block_t m_block) { if(num_entries == size) { if(blocks[0].dirty) { num_writebacks++; } for (uint64_t i = 0; i < size - 1; i++) { blocks[i] = blocks[i + 1]; } blocks[size - 1] = m_block; } else { blocks[num_entries++] = m_block; } } | cs |
· Data Cache에서 쫓겨난 Block을 Victim Cache에 저장
→ Victim Cache가 꽉 찬 경우 : 오래된 Block 제거 후 추가 / Victim Cache가 꽉 안 찬 경우 : Block 추가
2 : Victim Cache가 꽉 찬 경우
3 : 가장 오래된 Block이 수정된 적이 있는 경우 Memory에 변경 사항을 반영해야 하므로 Writeback 발생 횟수 증가
4~6 : 가장 오래된 Block에 해당하는 0번 Index부터 뒤의 값을 한 칸씩 앞당김
7 : 마지막 Index에 Data Cache에서 쫓겨난 Block을 추가
8 : Victim Cache가 꽉 안 찬 경우
9 : 현재 Victim Cache에 들어있는 데이터 개수 + 1에 해당하는 Index에 Data Cache에서 쫓겨난 Block 추가
** Victim Cache가 꽉 찬 경우 : 이미 Size개가 들어 있었고, 하나 빼고 하나 넣으면 여전히 Size 만족(∴ num_entries 증가 X)
▣ Read
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | void data_cache_t::read(inst_t *m_inst) { uint64_t addr = m_inst->memory_addr; if(addr & 0b111) { cerr << "Error: invalid alignment of memory address " << addr << endl; exit(1); } uint64_t set_index = (addr & set_mask) >> block_offset; uint64_t tag = addr >> set_offset; block_t *block = &blocks[set_index][0]; if(!block->valid || (block->tag != tag)) { block = 0; } if(block) { block->last_access = *ticks; m_inst->rd_val = *(block->data + ((addr & block_mask) >> 3)); #ifdef DATA_FWD m_inst->rd_ready = true; #endif num_accesses++; num_loads++; } else { block_t victim_block = victim_cache->remove(addr); if (victim_block.valid) { block_t old_block = blocks[set_index][0]; if (old_block.valid) { old_block.tag = (old_block.tag << (set_offset - block_offset)) | set_index; victim_cache->insert(old_block); } victim_block.tag = tag; blocks[set_index][0] = victim_block; read(m_inst); } else { missed_inst = m_inst; memory->load_block(addr & ~block_mask, block_size); num_misses++; } #ifdef DEBUG cout << *ticks << " : cache miss : addr = " << addr << " (tag = " << tag << ", set = " << set_index << ")" << endl; #endif } } | cs |
· Load 명령어를 Data Cache에서 처리(Data Cache Miss인 경우 Victim Cache 먼저 확인)
2 : Load 명령어가 요구하는 Target Memory 주소를 Load
3 : 주소가 8-Byte 단위로 맞아 떨어지는지 검사
8 : Block Offset 제거 후, Set Mask를 이용해 Tag와 분리된 Set Index 계산
9 : Block Offset과 Set Offset을 제거하여 Data Cache용 Tag 계산
11 : Data Cache는 Direct-Mapped 방식이므로 확인할 위치는 하나
12 : Block이 Invalid 또는 Tag가 불일치 → Data Cache Miss → "block = 0"
14 : Data Cache Hit
15 : 마지막으로 접근한 시간을 현재 시간으로 갱신
16 : Block 내부에서 정확한 위치로 찾아가 8-Byte 단위로 데이터를 읽어 Register에 저장
20~21 : 통계용 Counter 증가
23 : Data Cache Miss
24 : Victim Cache에서 확인
26 : Victim Cache Hit
27 : Victim Cache Hit Block이 Data Cache로 이동 → Data Cache이 Victim Cache Block으로 쫓겨나므로 "old_block"에 복사
28 : Valid한 Data Cache Block
29 : Victim Cache에서 저장하기 위해 Block Offset을 제외한 Tag와 Set Index를 결합하여 Tag 형식 변환
→ Direct-Mapped Data Cache = [Tag][Set Index][Block Offset]
→ Victim Cache = Fully Associative Cache = [Tag' = {Tag, Set Index}]
30 : Data Cache Block을 Victim Cache FIFO 끝에 추가
32 : Victim Cache Hit Block을 Data Cache로 이동하려면 [Tag]만 저장
33 : Data Cache에 추가
34 : read 함수를 다시 실행하여 Data Cache Hit로 빠지도록 함
35 : Victim Cache Miss
36~38 : Block 시작 주소를 만들어 Main Memory에 block_size만큼 요청 후 Miss Counter 증가
▣ Write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | void data_cache_t::write(inst_t *m_inst) { uint64_t addr = m_inst->memory_addr; if(addr & 0b111) { cerr << "Error: invalid alignment of memory address " << addr << endl; exit(1); } uint64_t set_index = (addr & set_mask) >> block_offset; uint64_t tag = addr >> set_offset; block_t *block = &blocks[set_index][0]; if(!block->valid || (block->tag != tag)) { block = 0; } if(block) { block->last_access = *ticks; block->dirty = true; *(block->data + ((addr & block_mask) >> 3)) = m_inst->rs2_val; num_accesses++; num_stores++; } else { block_t victim_block = victim_cache->remove(addr); if (victim_block.valid) { block_t old_block = blocks[set_index][0]; if (old_block.valid) { old_block.tag = (old_block.tag << (set_offset - block_offset)) | set_index; victim_cache->insert(old_block); } victim_block.tag = tag; blocks[set_index][0] = victim_block; write(m_inst); } else { missed_inst = m_inst; memory->load_block(addr & ~block_mask, block_size); num_misses++; } #ifdef DEBUG cout << *ticks << " : cache miss : addr = " << addr << " (tag = " << tag << ", set = " << set_index << ")" << endl; #endif } } | cs |
· Store 명령어가 Data Cache에 값을 Write(Data Cache Miss인 경우 Victim Cache 먼저 확인)
2 : Store 명령어가 요구하는 Target Memory 주소를 Load
3 : 주소가 8-Byte 단위로 맞아 떨어지는지 검사
8 : Block Offset 제거 후, Set Mask를 이용해 Tag와 분리된 Set Index 계산
9 : Block Offset과 Set Offset을 제거하여 Data Cache용 Tag 계산
11 : Data Cache는 Direct-Mapped 방식이므로 확인할 위치는 하나
12 : Block이 Invalid 또는 Tag가 불일치 → Data Cache Miss → "block = 0"
14 : Data Cache Hit
15 : 마지막으로 접근한 시간을 현재 시간으로 갱신
16 : CPU가 Cache에 있는 데이터를 수정했으므로 Dirty Bit On
17 : "m_inst"에 담겨 있는, CPU가 저장하라고 보낸 새로운 데이터 값을 Cache Block의 정확한 위치에 저장
18~19 : 통계용 Counter 증가
21 : Data Cache Miss
22 : Victim Cache에서 확인
24 : Victim Cache Hit
25 : Victim Cache Hit Block이 Data Cache로 이동 → Data Cache가 Victim Cache Block으로 쫓겨나므로 "old_block"에 복사
26 : Valid한 Data Cache Block
27 : Victim Cache에서 저장하기 위해 Block Offset을 제외한 Tag와 Set Index를 결합하여 Tag 형식 변환
→ Direct-Mapped Data Cache = [Tag][Set Index][Block Offset]
→ Victim Cache = Fully Associative Cache = [Tag' = {Tag, Set Index}]
28 : Data Cache Block을 Victim Cache FIFO 끝에 추가
30 : Victim Cache Hit Block을 Data Cache로 이동하려면 [Tag]만 저장
31 : Data Cache에 추가
32 : write 함수를 다시 실행하여 Data Cache Hit로 빠지도록 함
33 : Victim Cache Miss
34~36 : Block 시작 주소를 만들어 Main Memory에 block_size만큼 요청 후 Miss Counter 증가
▣ Handle Response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void data_cache_t::handle_response(int64_t *m_data) { uint64_t addr = missed_inst->memory_addr; uint64_t set_index = (addr & set_mask) >> block_offset; uint64_t tag = addr >> set_offset; block_t *victim = &blocks[set_index][0]; if(victim->valid) { #ifdef DEBUG cout << *ticks << " : cache eviction : addr = " << addr << " (tag = " << tag << ", set = " << set_index << ")" << endl; #endif block_t victim_block = *victim; victim_block.tag = (victim->tag << (set_offset - block_offset)) | set_index; victim_cache->insert(victim_block); } *victim = block_t(tag, m_data, /* valid */ true); if(missed_inst->op == op_ld) { read(missed_inst); } else { write(missed_inst); } missed_inst = 0; } | cs |
· Main Memory에서 Miss가 발생한 Block이 도착했을 때 처리하는 함수
2 : Miss가 발생했던 Load/Store 명령어 Load
3 : Set Index 계산
4 : Tag 계산
6 : Direct-Mapped Cache라서 들어갈 위치는 하나
7 : 해당 Cache Line에 이미 유효한 Block 존재(해당 Block을 Victim Cache로 이동)
12 : Data Cache에 있던 Block을 복사
13 : Victim Cache에 넣기 위해 Tag 변환
14 : Victim Cache FIFO 끝에 추가
16 : Memory에서 온 Block을 Data Cache에 배치
18~19 : Cache Access 다시 실행(ld : read / sd : write)
20 : Miss 초기화
[Result]
[Reference]
· cache (Computer Architecture) - William J. Song
댓글
댓글 쓰기