안녕하세요, 이번에는 CPU에서 특정 상황에 대해서 발생하는 Hazards에 대해서 알아보겠습니다.
우선 Hazard는 총 세가지 종류가 있는데요, Structure hazards, Data hazards, Control hazards가 있습니다.
1. Structure hazard
Structure hazard는 access하려고 하는 하드웨어가 바쁘게 수행중일 때 발생합니다. 하드웨어를 늘리면 해결이 가능하지만,
하드웨어를 늘림에 따라서 발생하는 전력소모 등 Trade off가 발생한다는 것은 인지하고 계셔야합니다.
2. Data hazard
쉽게 이야기해서, 첫 명령어가 ADD이고 두번째 명령어가 SUB이라고 할 때, ADD의 destination register를 SUB instruction
에서 operand register로 사용할 때 발생하게 됩니다. 두 desination과 operand register는 서로 의존성을 갖기 때문입니다.
3. Control hazard
Control hazard는 branch와 같은 명령어를 수행할 때 발생합니다. 그 이유는 예를 들어서, 40번째 줄에 있는 CBZ 명령어를
통해서 72번째 줄로 branch한다고 했을 때, 40번째 줄과 72번째 줄 사이에 있는 instruction들을 해결할 수 없기 때문에 발생
하는 hazard입니다.
이제 Hazard의 대략적인 이유에 대해서 알아봤으니까, Data hazard에 대해서 먼저 살펴본 이후에
다음 시간에 Control Hazard에 대해서 살펴보도록 하겠습니다.
Data Hazards
위에서 살펴봤던 것과 같이, Data Hazards는 연산에 필요한 register의 주소끼리 의존성 문제가 발생할 때 일어납니다.
아래 그림을 보면서 이해를 해보자면,
$ADD\; X19, \; X0, \; X1 $
$SUB \; X2, \; X19, \; X3$
위의 명령어에서 ADD의 Destination register와 SUB instruction의 operand의 register가 서로 같은데,
이 때 의존성이 발생하게 되는 것 입니다.
생각해보자면, 파이프라이닝에서 EX단계에 가야 계산이 완료되고 WB단계에 가야 destination register에 작성할 텐데
그 단계가 다 수행되지 않고 뒤따라온 SUB명령어가 Decodde stage에서 X19에서 잘못된 값을 가져올 수 있기 때문입니다.
그래서 위의 그림과 같이, EX단계에서 Data forwarding(또는 Forwarding)을 통해서 해결할 수 있습니다.
그림에서 보는 것과 같이, R-foramt instruction의 경우 의존성을 가지고 있는 register가 존재한다면, EX가 수행결과를
그 다음 명령어의 ID단계로 가져오면 문제는 간단히 해결됩니다.
만약에 Data forwarding을 하지 않으면, ADD가 WB가 수행될 때가 되어야 destination register의 값이 바뀌기 때문에
SUB은 WB를 수행할 때 ID단계를 수행해야 합니다.
그럼 여기서 bubble이 총 1개가 발생하게 되는 것 입니다.
여기서 잠깐 버블에 대해서 설명하고 넘어가자면,
hazard를 방지하기 위해 파이프라인을 잠시 멈추는 것을 bubble이라고 합니다.
다시 돌아가서, destination register와 operand register간의 의존성 문제로 발생하는 것도 있지만,
Pipeline stall이라고 불리는 버블을 피할 수 없는 경우도 생기게 됩니다.
$LDUR \; X1, \; [\text{X2}, \; \text{#0}] $
$SUB \; X4, \; X1, \; X5$
위의 명령어를 보면, LDUR은 MEM단계가 되어야 결과를 가지고 data forwarding을 할 수 있습니다.
왜 그런 문제가 발생할까요? LDUR은 memory에서 읽은 데이터를 register에 저장하는 instruction입니다.
당연히 memroy에서 값을 읽어와야 register에 저장할 수 있기 때문에 memory에 접근하는 MEM단계가 수행되어야 합니다.
그럼 아예 pipeline install을 없앨 수 있는 방법은 없는 것일까요?
Compiler가 reordering하는 방법으로 없앨 수 있습니다.
위는 reordering하는 방법으로, reordering이 있기 전에는 13cycle이 걸리는 것을 11cycle로 줄일 수 있게 됩니다.
이 부분은 설명이 복잡하니 아래의 그림을 우선 참고하고 난 이후에 설명해보겠습니다.
위의 그림은 왼쪽 13clock에 대한 예시 그림입니다. clock을 세는 방법에 대해서 헷갈려 하시는 분이 있는 것 같아서,
직접 그림으로 보면 직관적으로 이해가 가실것이라고 생각합니다.
이 그림은 오른쪽에 11clock에 대한 예시입니다. 여기서 LDUR이 올라갈 수 있는 이유는 원래 Compiler가 reordering하기
이전의 instruction을 보면, instruction간에 서로 의존성이 없기 때문에 Compiler가 reordering으로 최적화를 한 것 입니다.
당연히 instruction간에 의존성을 가지고 있다면, 문제가 발생하게 됩니다.
여기서CPI를 보면, 성능에 대해서 알 수 있겠죠?
왼쪽의 경우에는 $CPI\; = \; \frac{13}{7}$이고, 오른쪽의 경우에는 $CPI\; = \; \frac{11}{7}$ 으로
오른쪽의 reordering한 경우가 성능이 더 좋아졌다는 것을 알 수 있습니다.
위는 Data hazard의 모든 instruction에서 의존성을 가지고 있습니다.
해당 경우 뿐만 아니라 만약에 data forwarding이 필요하다면 해야하는데, 저희야 instruction의 순서를 다 알고 있기 때문에
Data hazard를 미리 알 수 있지만, 하드웨어의 경우에는 이를 미리 알 수 없고,
IF단계를 지나고 ID단계가 되어야 instruction을 알 수 있습니다.
그렇기 때문에 저희는 Forwarding unit이라는 것을 만들어서 미리 예측해줘야 합니다.
pipeline의 각 stage는 다음 단계로 레지스터 번호를 전달하면서 명령어를 처리하게 됩니다.
예를 들어, ID/EX단계에서는 RegisterRs라는 레지스터 번호가 다음 단계로 전달되게 됩니다.
이는 명령어가 진행됨에 따라 어떤 레지스터가 사용되고 있는지를 파악하기 위함입니다.
EX단계에서 사용될 ALU operand의 레지스터 번호는 ID/EX.RegisterRn1 및 ID/EX.RegisterRm2로 전달됩니다.
결론적으로 Data hazard은 주로 이전 명령어가 연산을 완료하기 전에 다음 명령어가 동일한 레지스터를 읽으려고 할 때,
발생하게 되며, 이를 탐지하게 되는 조건은 다음과 같습니다.
EX/MEM.RegisterRd = ID/EX.REgisterRn1
이는 이전 명령어의 결과를 저장할 레지스터가 EX/MEM 단계에서 RegisterRd에 있고, 다음 명령어에서 EX단계의 첫 번째
operand로 사용되는 ID/EX.RegisterRn1이 동일할 때입니다.
즉, Rd인 destination register가 MEM단계에 있고, 다음 명령어에서 연산에 필요한 operand register인 Rn이
EX단계에 있으면 Data hazard를 예측하게 된다는 것 입니다.
이 설명을 기준으로 아래의 조건을 확인해보면 Data hazard가 어느 순간 예측되는지 알 수 있습니다.
1. EX/MEM.RegisterRd = ID/EX.RegisterRn1
2. EX/MEM.RegisterRd = ID/EX.RegisterRm2
3. MEM/WB.RegisterRd = ID/EX.RegisterRn1
4. MEM/WB.RegisterRd = ID/EX.RegisterRm2
1번과 2번의 경우에는 operand가 앞선 명령어가 MEM단계에 있을 때 가져오는지 확인하는 것이며,
3번과 4번의 경우에는 WB단계가 수행중에 있을 때, 다음 명령어가 WB단계에 있는 register를 참고해서 연산을 수행하려고
하는것으로, 위의 네 가지 경우가 될 때에는 Data hazard가 일어난다고 판단하게 됩니다.
또한 Forwarding이 반드시 필요한 상황이 아니거나 잘못된 상황에서 발생하지 않도록 조건을 두는데, 아래의 두 조건입니다.
1. Forwarding이 필요한 경우
Forwarding을 통한 데이터 전달로, EX/MEM단계에서 나오는 결과를 WB 단계까지 기다리지 않고, ALU에서 바로 사용하는
경우 입니다. 이때 Forwarding Unit은 EX/MEM 단계에서 결과가 저장될 레지스터가 다음 명령어의 피연산자로 사용되는지
확인하게 됩니다. 이 과정에서 RegWrite 신호가 활성화 됩니다.
$EX/MEM.RegWrite, \; MEM/WB.RegWrite$
2. XZR는 X31번 register로 어떤 값을 저장하지 않고 항상 0을 반환하게 됩니다. 그러므로 Forwarding을 하더라도, 해당 register
에는 데이터를 저장하지 않기 때문에 Forwarding이 불필요하게 됩니다.
$EX/MEM.RegisterRd \neq 31$
$MEM/WB.RegisterRd \neq 31$
그렇다면, 아래와 같이 Double Data hazard가 발생하는 경우의 조건은 무엇일까요?
$ADD \; X1, \; X1, \; X2$
$ADD \; X1, \; X1, \; X3$
$ADD X1, \; X1, \; X4$
우선, Double Data Hazard는 동일한 레지스터에 여러 명령어가 순차적으로 결과를 사용하는 경우
발생할 수 있는 Data hazard를 설명하는 개념입니다.
해당 상황에서는 두 가지 데이터 위험이 동시에 발생할 수 있는데,
EX Data hazard는 앞선 명령어의 결과를 EX단계에서 전달받아 해결할 수 있습니다.
두 번째 MEM Data hazard는 MEM 단계에서 전달된 값을 사용하는데, 이때 주의할 점은
가장 최근에 계산된 값을 사용해야 한다는 것 입니다.
Double Data Hazard가 발생하는 조건은 아래와 같게 됩니다.
if (MEM_WB.RegWrite && (MEM_WB.RegisterRd != 31) # MEM/WB 단계에서 RegWrite가 활성되어 있고, RegisterRd가 XZR이 아님을 확인
&& !(EX_MEM.RegWrite && EX_MEM.RegisterRd != 31 && EX_MEM.RegisterRd == ID_EX.RegisterRn1) # EX/MEM단계에서 RegWrite가 활성화되어 있고, RegisterRd가 XZR이 아니며 RegisterRd가 현재 ID/EX 단계의 RegisterRn1과 동일한지 확인
&& (MEM_WB.RegisterRd == ID_EX.RegisterRn1)) # MEM/WB단계의 REgisterRd가 현재 ID/EX단계의 RegisterRn1과 동일한지 확인
ForwardA = 01;
if (MEM_WB.RegWrite &&
(MEM_WB.RegisterRd != 31) &&
!(EX_MEM.RegWrite && EX_MEM.RegisterRd != 31 && EX_MEM.RegisterRd == ID_EX.RegisterRm2) &&
(MEM_WB.RegisterRd == ID_EX.RegisterRm2))
ForwardB = 01;
위의 ForwardA와 ForwardB등은 아래와 같습니다.
Mux control | Source | Explanation |
ForwardA = 00 | ID/EX | Register file에서 넘어오는 첫 ALU operand |
ForwardA = 10 | EX/MEM | 첫 번째 ALU operand는 이전 ALU 결과에서 전달됩니다. |
ForwardA = 01 | MEM/WB | 첫 번째 ALU operand는 Data memory 또는 이전 ALU 결과에서 전달됩니다. |
ForwardB = 00 | ID/EX | Register file에서 넘어오는 두번째 ALU operand |
ForwardB = 10 | EX/MEM | 두 번째 ALU operand는 이전 ALU 결과에서 전달됩니다. |
ForwardB = 01 | MEM/WB | 두 번째 ALU 피연산자는 Data memory 또는 이전 ALU 결과에서 전달됩니다. |
이제는 Load-Use Hazard Detection에 대해서 배워보도록 하겠습니다.
Load-Use Hazard는 pipeline에서 load 명령어와 바로 그 다음에 해당 데이터를
사용하는 명령어 사이에서 발생하는 Data hazard입니다.
load 명령어가 Decode되는 ID stage단계에서 체크해야 합니다.
ALU operand register number는 ID stage에서 주어지기 때문에 해당 operand를 체크합니다.
$IF/ID.RegisterRn1, \; IF/ID.RegisterRm2$
위와 더불어서 Load-use hazard가 발생하는 상황을 살펴보자면,
ID/EX.MemRead && # EX 단계에서 메모리 읽기 작업이 진행중인지 확인한다.
((ID/EX.RegisterRd == IF/ID.RegisterRn1) || (ID/EX.RegisterRd == IF/ID.RegisterRm1))
# EX단계에서 결과값을 저장할 register(ID/EX.RegisterRd)가 ID 단계에서 첫 번째 operand로 사용되는
# register(IF/ID.RegisterRn1)이 서로 같은지 확인한다.
# 마찬가지로 EX 단계에서 결과값을 저장할 레지스터(ID/EX.RegisterRd)가 ID단계에서 두 번째
# register(IF/ID.RegisterRm1)와 같은지 확인한다.
위의 조건을 만족하게 되면 Load-use hazard가 발생한 것을 알 수 있게 됩니다.
위의 Load-use hazard를 예측하는 Hazard detection unit을 추가적으로 삽입하게 되면
아래와 같이 회로가 설계되게 됩니다.
지금까지 Data hazard에 대해서 살펴봤습니다.
다음에는 Control hazard에 대해서 알아보도록 하겠습니다.
감사합니다.
'전자전기공학 > 컴퓨터구조' 카테고리의 다른 글
[컴퓨터구조][Cache] (0) | 2024.10.16 |
---|---|
[컴퓨터구조][Hazards #2 Control Hazard] (0) | 2024.10.13 |
[컴퓨터구조][CPU#3 Pipelining기본] (1) | 2024.10.02 |
[컴퓨터구조][CPU#2 Single Cycle CPU, Dual Port SRAM] (4) | 2024.10.01 |
[컴퓨터구조][CPU의 기본역할 및 성능] (0) | 2024.09.27 |