[Computer Architecture] Assignment 4 - Branch
"Branch Prediction"
[Objective]
Kite Processor Pipeline에서 2-Level Local Branch Predictor와 BTB(Branch Target Buffer)를 구현하여 Branch Prediction Accuracy를 99.9%까지 향상시키기
[Background]
▣ Local 2-Level Branch Predictor
> BHT(Branch History Table)
· 각 Entry는 해당 Branch의 최근 h번 결과를 Bit로 저장
· LSB : 가장 최근 Branch Outcome / MSB : 가장 오래된 Branch Outcome
> PHT(Pattern History Table)
· Branch History Pattern마다 2-bit Saturating Counter를 저장(2p x 2h = 23 x 23 = 64개의 Counter 존재)
▣ Prediction Flow
① PC
② (PC >> 2)의 하위 b-bit : BHT Index / 하위 p-bit : PHT Row Index / BHT[Index]의 h-bit History : PHT의 Column Index
③ PHT[Row][Column]의 2-bit Counter 확인 (0, 1 : Not Taken / 2, 3 : Taken)
▣ Update
· Branch가 Write-Back Stage에 도달하면 실제 결과를 알 수 있음
> Saturating Counter
· 실제 Taken : PHT Counter + 1 (Until 최대 3)
· 실제 Not Taken : PHT Counter - 1 (Until 최소 0)
> BHT
· 기존 History를 왼쪽을 1-bit Shift 후, 실제 결과를 LSB에 삽입하여 h-bit만 유지
** 분기 명령이 Fetch Stage에서 BHT를 읽고, PHT Counter를 선택한 후, 해당 명령이 Write-Back Stage에 도달하기 전까지 앞선 Branch들이 같은 BHT Entry를 바꿀 수 있음
→ Write-Back에서 BHT를 다시 읽으면 Fetch 때 사용했던 PHT Counter와 다른 Counter를 Update
∴ "inst_t" Class에 새 변수를 추가해서 Fetch 당시 사용한 정보를 저장
▣ BTB(Branch Target Buffer)
· Branch Predictor는 "Taken인지, Not Taken인지"만 예측하고, Taken이라고 예측했을 때 "어디로 갈지"는 BTB가 담당
→ 이번 과제의 BTB는 Direct-Mapped Cache처럼 동작하고, Entry 수는 16개
· BTB Entry에는 Taken Branch의 Target Address가 저장
· Fetch Stage에서 Branch가 Taken으로 예측되면 BTB에서 Target을 Read
· Write-Back Stage에서 실제 Brach가 Taken이면 BTB를 Update
[Implementation]
▣ inst.h
uint64_t pht_index;
· "pht_index"를 "inst_t" Class에 추가
→ Branch Prediction에 사용된 PHT Index를 저장하고, Write-Back 단계에서 동일한 PHT Counter를 갱신하기 위함
▣ br_predictor.cc
> Branch Predictor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // Branch predictor br_predictor_t::br_predictor_t(unsigned m_bht_bits, unsigned m_pht_bits, unsigned m_hist_len) : bht(0), pht(0), b(m_bht_bits), p(m_pht_bits), h(m_hist_len) { // Create branch history table (BHT) and pattern history table (PHT). bht = new uint8_t[1 << b]; pht = new uint8_t[(1 << p) * (1 << h)]; // Initialize BHT = 0 for (uint64_t i = 0; i < (1 << b); i++) { bht[i] = 0; } // Initialize PHT Counter = 1(01 : Weakly Not Taken) for (uint64_t i = 0; i < ((1 << p) * (1 << h)); i++) { pht[i] = 1; } } | cs |
· "br_predictor_t" 객체가 생성될 때 실행되는 생성자
· bht(0) : BHT Pointer를 Null로 초기화 → bht = 0
· pht(0) : PHT Pointer를 Null로 초기화 → pht = 0
· b(m_bht_bits) : 변수 b를 BHT Index Bit 값으로 초기화 → b(m_bht_bits) = b(3) → b = 3
· p(m_pht_bits) : 변수 p를 PHT Index Bit 값으로 초기화 → p(m_pht_bits) = p(3) → p = 3
· h(m_hist_len) : 변수 h를 Branch History 길이로 초기화 → h(m_hist_len) = h(3) → h = 3
· bht : BHT 배열을 동적으로 생성 → BHT Entry 8개 생성
· pht : PHT 배열을 동적으로 생성 → 64개의 2-bit Counter 생성
· BHT의 모든 Entry를 0으로 초기화
· PHT의 모든 Counter를 1(01 : Weakly Not Taken)로 초기화
> is_taken( ) 함수
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Is a branch predicted to be taken? bool br_predictor_t::is_taken(inst_t *m_inst) { // Predict always not taken. H uint64_t pc_index = m_inst->pc >> 2; // Removed Lower 2-bit(4-Byte -> 2-Byte = 00 -> Elision) uint64_t bht_index = pc_index & ((1 << b) - 1); // Select BHT Entry Using Lower b-bit uint64_t pht_row = pc_index & ((1 << p) - 1); // Select PHT Row Using Lower p-bit uint64_t history = bht[bht_index] & ((1 << h) - 1); // Read h-bit Local Branch History uint64_t pht_index = pht_row * (1 << h) + history; // Convert 2D Array To 1D Array m_inst->pht_index = pht_index; // Save PHT Index Used At Prediction Time return pht[pht_index] >= 2; // Counter Value = 2 or 3 -> Predict "Taken" } | cs |
· Fetch Stage에서 호출되며, 현재 Branch Instruction이 Taken될지, Not Taken될지 예측
· pc_index : Instruction의 PC를 가져와서 오른쪽으로 2-bit Shift(4-Byte 단위로 정렬 → PC의 하위 2-bit는 항상 00 → 생략)
· bht_index : BHT Index 계산(PC Index의 하위 3-bit를 사용하기 위해 Masking)
· pht_row : PHT에서 PC에 해당하는 Row Index 계산(PC Index의 하위 3-bit를 사용하기 위해 Masking)
· history : Branch Instruction의 Local History를 BHT에서 가져옴(최근 Branch 결과들이 Bit 형태로 저장되어 있는 bht의 하위 3-bit를 사용)
· pht_index : 2차원 PHT Index를 1차원 배열 Index로 변경
· Fetch Stage에서 사용한 PHT Index를 Instruction 안에 저장
→ 나중에 Write-Back Stage에서 Predictor를 Update할 때, 같은 PHT Counter를 수정해야 하기 때문에 중요
· PHT Counter 값 = 0, 1 : False → Not Taken / PHT Counter 값 = 2, 3 : True → Taken
> update( ) 함수
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Update a prediction counter. void br_predictor_t::update(inst_t *m_inst) { uint64_t pc_index = m_inst->pc >> 2; // Removed Lower 2-bit(4-Byte -> 2-Byte = 00 -> Elision) uint64_t bht_index = pc_index & ((1 << b) - 1); // Select BHT Entry Using Lower b-bit uint64_t pht_index = m_inst->pht_index; // Use PHT Index Saved At Prediction Time // Update 2-bit Saturating Counter if (m_inst->branch_taken) { if (pht[pht_index] < 3) { pht[pht_index]++; } } else { if (pht[pht_index] > 0) { pht[pht_index]--; } } // Shift In Actual Branch Outcome To Update Local Branch History bht[bht_index] = ((bht[bht_index] << 1) | m_inst->branch_taken) & ((1 << h) - 1); } | cs |
· Write-Back Stage에서 호출되며, 실제 Branch 결과를 바탕으로 BHT와 PHT를 갱신
· pc_index : Instruction의 PC를 가져와서 오른쪽으로 2-bit Shift(4-Byte 단위로 정렬 → PC의 하위 2-bit는 항상 00 → 생략)
· bht_index : 갱신해야 할 BHT Index 계산(PC Index의 하위 3-bit를 사용하기 위해 Masking)
· pht_index : Fetch Stage에서 저장해 둔 PHT Index Load
** 그 사이에 BHT 값이 바뀌었을 수 있기 때문에 PHT Index를 다시 계산하지 않음
· Branch 결과 = Taken : Counter가 최댓값 3보다 작으면 Counter 증가 / Counter가 최댓값 3이면 Counter 유지
· Branch 결과 = Not Taken : Counter가 최솟값 0보다 크면 Counter 감소 / Counter가 최솟값 0이면 Counter 유지
· 최근 3번의 결과가 저장되어 있는 "bht"를 왼쪽으로 1칸 밀어 오래된 기록을 없애고 LSB에 Branch 결과 Load 후, 하위 3-bit를 사용
> BTB Constructor
1 2 3 4 5 6 7 8 9 10 11 12 | // Branch target buffer br_target_buffer_t::br_target_buffer_t(uint64_t m_size) : num_entries(m_size), buffer(0) { // Create a direct-mapped branch target buffer (BTB). buffer = new uint64_t[num_entries]; // Initialize BTB Entry = 0 for (uint64_t i = 0; i < num_entries; i++) { buffer[i] = 0; } } | cs |
· BTB 객체가 생성될 때 호출되는 생성자 ("m_size"는 BTB Entry 개수)
· num_entries(m_size) : 변수를 BTB Entry 개수로 초기화 → num_entries(m_size) = num_entries(16) → num_entries = 16
· buffer(0) : BTB 배열 Pointer를 Null로 초기화
· buffer : BTB Target Address를 저장할 배열 생성(각 Entry에는 Branch Target Address가 저장)
· BTB의 모든 Entry를 0으로 초기화
> get_target( ) 함수
1 2 3 4 5 | // Get a branch target address. uint64_t br_target_buffer_t::get_target(uint64_t m_pc) { // Return Target Address From Indexed BTB Entry(4-Byte -> 2-Byte = 00 -> Elision) return buffer[(m_pc >> 2) & (num_entries - 1)]; } | cs |
· Branch가 Taken으로 예측되었을 때 호출되며, 예측 Branch Target Address를 반환
· 하위 2-bit를 사용하지 않으므로 PC의 하위 2-bit를 제거 & BTB가 16개의 Entry를 가지므로 하위 4-bit를 이용하여 BTB Index 계산
> update( ) 함수
1 2 3 4 5 | // Update the branch target buffer. void br_target_buffer_t::update(uint64_t m_pc, uint64_t m_target_addr) { // Store Actual Target Address In BTB(4-Byte -> 2-Byte = 00 -> Elision) buffer[(m_pc >> 2) & (num_entries - 1)] = m_target_addr; } | cs |
· BTB를 갱신하는 함수로, 실제 Taken Branch의 PC와 실제 Target Address를 받음
· 하위 2-bit를 사용하지 않으므로 PC의 하위 2-bit를 제거 & BTB가 16개의 Entry를 가지므로 하위 4-bit를 이용하여 BTB Index 계산
· 해당 BTB Entry에 Branch Target Address를 저장 (다음에 같은 Branch가 Taken으로 예측되면 이 주소를 Target으로 사용)
** "get_target( )"에서 사용한 방식과 반드시 같아야 나중에 같은 Branch PC로 접근했을 때 같은 Entry Read 가능
[Result]
[Reference]
· branch (Computer Architecture) - William J. Song
댓글
댓글 쓰기