Control Hazard
제어 해저드는 분기를 할 때, 분기 여부의 결정을 MEM 단계에서 결정하게 되므로 MEM 단계 이후에 fetch 하는 명령어 이전까지의 명령어들을 모두 실행할 수 없게 되는 (실행하면 안되는) 해저드를 말한다.
지금 회로를 설계할 때는 branch prediction을 항상 하고 있다고 생각하고 회로를 고쳐볼 것이다.
이때 예측하는 방식은 not taken 할 것이라고 예측한다고 하자.
만약 not taken을 예측하고 있는데, taken 하게 된다면 그 사이에 fetch 해서 실행중이던 명령어는 모두 flush 처리할 것이다.
위 그림에서 beq명령어가 실행되는 시점의 PC 값은 40이고, MEM 단계까지 가고나면 분기 여부와 이때의 주소값을 알 수 있게 된다.
그리고 WB 단계를 지나 다음 사이클이 되어 클럭신호가 들어오면 PC + 4로 실제 업데이트가 이루어질 것이다.
우선 beq 명령어가 실행된 시점에서는 PC+4 든, PC + 4 + 레이블에 적힌 명령어 간격 값 중 하나로 pcnext 에 값이 들어오고 있는 상황이다.
위 그림에서는 만약 beq의 조건이 참이라면, pc + 4 + 7개 명령어 뒤로 가라는 의미가 되어, pc + 4 + 28 = pc + 32로 건너뒤어 72 라는 pc 주소 값으로 가게 될 것이다.
거짓이라면 아무 문제 없이 이후 실행하고 있던 파이프라인을 그대로 이어서 실행하면 된다.
그런데 지금 구조에서는 flush를 생각보다 많이 해야한다.
왜냐하면 브랜치 결과를 판별하는 시점이 MEM 스테이지라서 그 이전 3개 명령어 실행분을 모두 flush 해야하기 때문이다.
따라서 성능을 1사이클로 줄이기 위해 하드웨어를 보완해보자.
바로 분기 여부 결정을 MEM 스테이지가 아니라 ID 스테이지에서 완료하도록 하는 것이다!
(전에 control hazard 정리할 때, 버블을 2개에서 1개로 줄였던 그 테크닉이다.)
예전에는 ALU의 뺄셈기와 같은 하드웨어를 썼었는데, 물론 그렇게 해도 되지만, 지금 추가할 하드웨어는 그보다 간단하게 bit by bit 으로 비교하는 비교기를 넣으려고 한다.
그리고 추가적으로는 덧셈기도 추가해야 한다.
만약 taken일 경우, 주소값을 빠르게 계산해서 바로 pc next 값 (target address) 을 업데이트 해야하기 때문이다.
이 모든 회로 역시, 예측이 틀렸을 때 손해를 최소화하기 위해서 ID 스테이지에 넣는다고 하자.
마지막으로 예측이 틀렸을 경우, 어쨌든 ID 단계에서 틀렸음을 감지한 것이므로, 그 시점에 Fetch 해온 명령어는 flush 해야한다. (즉, 1사이클의 패널티가 존재한다.)
따라서 flush를 해소하기 위해, IF.flush 라는 시그널이 있다고 생각해보자.
만약 이 시그널이 활성화 되었다면, 기존에 fetch한 명령어가 있다고 하더라도, all zero 로 처리해서 내보내도록 한다.
이렇게 생각을 한다면, 이제 beq 명령어는 사실 IF, ID 두 단계만 거치면 모든 기능을 다 하는 것으로 볼 수 있다.
( 위 그림에서 색칠된 부분만 보면 2개 스테이지를 사용하고 있지만, 실제로는 5스테이지를 거치는 것이 맞다.)
그리고 이때 나의 예상이 틀렸을 것이라고 예측해서 add 명령어도 가져온 상태이다.
만약 예측이 맞았다면 이대로 진행할 것이고, 예측이 틀려서 분기를 하게 된다면, add 명령어는 flush 하게 된다.
예제를 하나 봐보자.
PC가 40이라는 값을 갖고 있을 때 beq 명령어가 존재하고, 분기하게 되면 PC는 72라는 값으로 건너가게 될 것이다.
PC 값이 36일때의 Clock Cycle을 1이라고 했을 때, beq 명령어가 ID 스테이지에 들어가는 클럭 사이클은 3이다.
위 이미지는 clock cycle 3일 때의 모습이다.
beq 명령어를 해석하면서, 동시에 and 명령어를 fetch 한 상태이다.
이제 이 시점에서 모든 branch는 참인지 거짓인지 판별이 되어야 한다.
그림을 보면 adder 가 추가되어서 44 + 28 = 72 라는 taken address를 계산해두고 mux에 넣은 상황이다.
그리고 레지스터에서 값을 읽은 직후에 = 기호로 표시된 comparator를 진행하여 분기 여기부를 최종 결정한다.
지금 예제에서는 알고봤더니 taken 이라고 하자.
그러면 IF.flush 시그널이 1이 되면서, and 라는 명령어를 비록 fetch 했지만, fetch한 결과를 다음 사이클에 보내주는게 아니라 다 0으로 환산해서 보내주게 된다.
즉, IF.flush가 활성화되어 있으면 기존에 fetch 한 명령어가 있더라도 강제로 0을 decode 단계로 뿌려주기 때문에 nop화 하는 효과를 갖게 된다.
그리고 그 다음 fetch 단계에서는 내가 원하는 72를 가져오게 된다.
Clock 4 일 때 회로도 모습으로 나타낸 것이 위와 같다.
(여기에서 IF/ID에서 나오는 값이 72인게 맞나? 나오는 값은 0 이어야 하지 않나?)
'CS > 컴퓨터 구조' 카테고리의 다른 글
[컴퓨터 구조] 23. Cache (1) - 개요 (0) | 2024.06.03 |
---|---|
[컴퓨터 구조] 22. Pipeline MIPS (6) - 성능 측정 (0) | 2024.06.03 |
[컴퓨터 구조] 20. Pipeline MIPS (4) - 회로 개선 (Data Hazard) (0) | 2024.06.02 |
[컴퓨터 구조] 19. Pipeline MIPS (3) - Datapath & Control (0) | 2024.06.01 |
[컴퓨터 구조] 18. Pipeline MIPS (2) - Hazard (0) | 2024.05.30 |