명령어 파이프라인은 명령어를 읽어 순차적으로 실행하는 프로세서에 적용되는 기술로, 한 번에 하나의 명령어만 실행하는 것이 아니라 하나의 명령어가 실행되는 도중에 다른 명령어를 실행하는 식으로 동시에 여러개의 명령어를 실행하는 기법이다
현대의 프로세서는 single-cycle을 사용하지 않는데, 왜냐하면 single-cycle에서는 clock period가 가장 긴 delay(load)에 맞추어야 하기 때문에, 실행시간이 짧은 연산도 이 시간동안 기다려야 하기 때문에 시간이 낭비된다. 이를 해결하기 위해 현대의 프로세서는 병렬처리를 통해 동시에 여러개의 작업을 수행하는 방식으로 성능을 개선하였다.
파이프라이닝을 적용하면 각각의 작업에 소요되는 시간은 줄일 수 없지만 여러 작업을 수행할 때 짧은 시간동안 처리되는 작업의 수를 높일 수 있어 throughput이 향상된다.
MIPS에서 명령어의 실행은 5가지 스테이지로 나눌 수 있다.
Pipelining을 적용하였을 때 프로그램이 동작하는 방식은 아래 그림과 같다
이 때 전체 작업에 소요되는 시간은
가장 긴 stage의 소요시간 * (명령의 수 + 스테이지의 수 - 1)
이지만, pipeline을 적용하지 않았을 때 소요되는 시간은
가장 긴 instruction의 소요시간 * 명령의 수
이다. 현대에서 프로그램이 돌아가는데 필요한 instruction의 수는 셀 수 없이 많기 때문에 결과적으로 pipeline을 적용하면 instruction time / stage time만큼의 성능향상이 일어나게 된다.
MIPS의 구조는 파이프라인을 통한 이점을 극대화 할 수 있도록 설계되었는데, 세부적인 내용은 다음과 같다.
파이프라이닝 과정에서 몇가지 이유로 다음 스테이지로 진행이 불가능한 경우가 발생하는데, 이를 Pipeline Hazard라고 한다.
다른 명령에서 사용중인 리소스가 필요한 경우 발생
MIPS에서 instruction과 data는 같은 메모리에 저장되어있기 때문에 IF와 MEM 스테이지는 동시에 같은 리소스에 접근하려는 요청을 보낼 수 있는데, 이 때 pipline bubble이 발생할 수 있다.
이를 해결하기 위해서는 Instruction memory와 Data memory를 분리하여 설계해야 한다.
명령이 이전 명령의 결과로 나오는 데이터에 의존하는 경우
위의 그림에서 sub연산은 $s0에 저장된 값을 일어와야 하는데 이는 add연산의 WB stage이후에 가능하므로 그 사이에 bubble이 생긴다.
이를 해결하기 위해서는 Forwarding이 필요하다
하지만 특정 상황에서는 Forwarding을 해도 bubble이 발생하는 경우가 있다
이 경우 메모리로부터 $s0에 데이터를 불러온 다음 sub연산을 진행해야 하는데, 이는 MEM stage이후에 Forwarding이 가능하므로 bubble이 발생하게 된다.
이 문제는 Code Scheduling을 통해 해결이 가능하다
code scheduling 이전에는 $t2에 대한 lw연산 이후에 바로 sw연산이 이어서 나오기 때문에 forwarding을 해도 bubble이 발생했다
하지만 code scheduling을 적용하여 두 sw연산의 순서를 바꾼 결과 bubble이 발생하지 않고 진행되는 것을 볼 수 있다.
Branch instruction에서 이전 명령의 결과가 나와야 다음 명령의 IF stage를 진행할 수 있는 경우
위 상황에서 beq명령의 결과가 나와야 다음 명령을 불러올 수 있기 때문에 bubble이 발생한다.
이를 해결하기 위해 Branch Prediction기법을 활용한다.
Single-cycle datapath에 pipeline을 적용하기 위해서는 각 스테이지 사이에 레지스터를 두어 이전 스테이지의 상태를 저장하는 과정이 필요하다
각 스테이지에서는 작업을 마친 뒤 레지스터에 값을 저장하고 다음 스테이지에서 이 값을 사용하는 방식으로 작업을 이어나간다. 이러한 구조를 통해 각 스테이지는 bubble이 발생하지 않는 한 쉬지않고 작업을 수행할 수 있다.
또한, instruction을 decoding하여 얻은 control정보는 control정보가 사용되는 스테이지의 레지스터까지 순차적으로 전달되어 작업을 수행한다.
Pipelined Datapath에서 Data Hazard를 처리하기 위해서는 hazard가 발생하였을 때 이를 감지하고 forwading을 수행하여야 하는데, 구체적인 과정은 다음과 같다.
Data Hazard는 발생하는 스테이지에 따라 EX Data Hazard와 MEM Data Hazard로 나눌 수 있는데, 두 스테이지에서 동시에 Hazard가 발생하는 경우 Double Data Hazards라고 한다.
Double Data Hazard는 가장 최근의 값(EX/MEM 레지스터에 저장된 값)을 사용함으로써 해결할 수 있다.
Forwarding을 활용해도 특정 상황에서는 bubble을 피할 수 없다. 주로 load-use hazard가 발생할 때 bubble이 발생하는데, Pipelined Datapath에서 load-use hazard를 감지하여 bubble을 발생시키는 과정은 다음과 같다
또한 Load-use data hazard를 방지하기 위해서는 Code Scheduling을 통해 명령의 순서를 재구성해야 하는데, 이때 명령의 순서를 재구성하여도 원래 버전과 결과가 달라지면 안된다.
Control Hazard는 Branch Prediction을 통해 방지할 수 있는데, 구체적으로 두 가지 방법이 있다.
다만, Branch 안에서 Data Hazard가 발생 할 수도 있는데 그 경우는 Code Scheduling을 통해 해결한다.
Load-use Data Hazard 혹은 Branch문 안에서 Data Hazard가 발생한 경우 Code Scheduling을 통해 명령의 순서를 재구성하여 bubble을 방지해야하는데, Code Scheduling이 작동하는 기본적인 순서는 다음과 같다.
Exception이란 프로세서에서 발생한 예상치 못한 이벤트로 별도의 처리가 필요하다.
참고로 Exception과 Inturrupt 모두 프로세서가 예상하지 못한 이벤트라는 점에서 비슷하지만 이벤트가 발생하는 위치에 따라 구분된다.
프로세서에서 Exception이 발생하면 아래의 순서에 따라 예외를 처리한다
혹은 2번과 3번을 합쳐 다음과 같이 동작할 수도 있다.
Pipelined Datapath에서 예외처리를 하기 위해서 flush를 제어하는 control signal과, EPC 및 Cause register를 추가한다
Hazard와 Forwarding, Exception을 처리하는 최종적인 Pipelined Datapath의 모습은 아래 그림과 같다.