8.1. Pipeline 개요
- Pipelining은 여러 작업들을 병렬(pallerism)성을 이용해서 빠르게 작업하는 방법을 말한다.
- 위 그림에서, 4가지 소작업으로 구성된 4개 작업을 순차적으로 실행한다면 총 8시간이 소요된다.
- 하지만, pipelining을 사용해서, 각 작업의 소작업이 끝날 때마다 다른 작업을 수행한다면 3.5시간이 소요된다.
- 속도는 2.3배 증가하고, 만일 작업이 4개가 아니라 n개로 늘어난다면, 속도는 최대 4배까지 늘어난다.
- 즉, 작업 4개를 1개 수행할 시간에 마칠 수 있다.
8.2. RISC-V Pipeline
8.2.1. Background
RISC-V의 pipelining을 배우기 앞서, instruction이 수행되는 과정을 단계별로 나눠보면 아래 다섯 가지로 나눌 수 있다.
IF
: Instruction Fertch from memory
Instruction memory에서 instruction을 fetch하는 cycle을 의미한다.
ID
: Instruction decode & register read
Instruction을 decode해서 operand register를 읽는 cycle을 의미한다.
EX
: Execute operation or calculate address
Operation을 수행하고 address를 계산한다. (PC 계산이라던지…)
MEM
: Access memory operand
Operand 또는 destination에 address가 있을 때 memory에 access하는 cycle을 의미한다.
WB
: Write result back to register
register에 연산 결과를 기록하는 cycle을 의미한다.
Datasheet에 따르면, pipeline을 사용하지 않을 경우 대표적인 4가지 연산에 소요되는 시간은 위와 같다.
ld
는 모든 과정을 전부 거치게된다. (그래서 최대한 ld
를 줄이기 위해 register에서 연산하려고 하는거다.)
sd
는 rd
같이 destination register에 data를 기록하지 않기 때문에 Register Write
이 빠진다.
R-format
은 rs1
, rs2
사이의 연산이기 때문에 memory에 load하는 과정이 생략된다. 바로 이게 load & store 아키텍처의 장점이다!
beq
와 같은 branch 연관 연산 역시 memory access가 불필요하고 register에 write하는 과정이 필요없으며 ALU의 sub 연산 결과로 zero
핀이 high
인지 여부만 확인하므로 빠르다.
- Throughtput : 단위시간동안에 몇 개의 instruction을 처리했는지를 나타내는 지표.
- Delay(=latency) : Instruction이 queue에 들어와서 기다리다가 처리되기까지의 시간.
8.2.2. RISC-V는 Pipelining 하기 좋다!
이유는 아래와 같이 3가지 있다.
- 모든 instruction이 32-bit로 고정돼있다.
- Instruction의 fetch-decode-execute-store을 1-cycle에 수행하기 좋다.
- 다음에 올 instruction의 길이를 확정적으로 알고 있으므로 어디서 부터 어디까지 읽어야 하는지 매번 알아봐야 할 필요가 없다.
- Instruction의 개수가 적으로 format이 일반화돼있다.
- 단일 step으로 decode할 수 있다.
- 사람도 읽기 편하고 컴파일러도 효율적으로 컴파일 할 수 있다.
- Load-store architecture을 선택했다.
- memory access에 address를 ALU로 계산하게 된다.
- 따라서 계산 후 접근하는 구조가 만들어져서 pipelining이 용이하다.
- 왜냐하면, 한쪽에서 memory에 접근하는 동안 한쪽에서는 계산하고 한쪽에서는 decode하고 한쪽에서는 fetch하고 있으면 되기 때문이다.
즉, ISA를 설계할 때 pipelining을 고려했기 때문에 RISC-V가 이런 특징을 가질 수 있었던 것이다.
8.3. Hazards
8.3.1. Hazards 정의
Pipelining은 instruction을 쉬지않고 매 cycle마다 pipe에 넣어줘야 한다. 이게 불가능할 때 우리는 ‘Hazard가 발생했다.’고 표현한다. Hazard는 resource confilict situation 이라고도 부르며, 시스템의 throughput을 떨어트린다.
8.3.2. Hazards의 종류
-
Structure Hazard
-
동시의 두 하드웨어가 같은 address의 data에 access 하려는 hazard다.
-
Fetch-decode-execute-store 구조에서 Fetch와 store은 memory에 access 하게 된다.
1, 2, 3, 4
로 끝나면 문제없지만, 1, 2, 3, 4, 1, 2, 3 ...
이라면, 4
와 1
이 연속하게 된다.
-
즉, memory access가 store에서, fetch에서 연속적으로 발생한다.
-
이번 instruction의 store은 다음 instruction의 fetch를 stall 시킬 가능성이 있다.
-
그러므로, pipelined datapath는 서로 독립적인 instruction, data memory(or cache)에 접근해야 한다.
-
Data Hazard
- 이번 instruction을 수행하기 위해 이전 instruction의 결과가 필요한 경우다. 의존성이 있다고 말한다.
-
+ `x19`를 계산해야만 아래 명령어의 operand로 사용할 수 있는 전형적인 data hazard다.
+ `EX` 단계에서 `x19`가 계산되고 나서야 다음 instruction의 `IF`가 시작된다.
+ **Fowarding (=Bypassing)**을 사용하면 이 문제를 해결할 수 있다.
+
+ 다음 instruction의 `EX`가 시작되기 전에 이전 instruction의 `EX` 결과를 받아올 방법이 있다면 data hazard를 극복할 수 있다.
+ 비록 이번 instruction의 `ID`는 의미없는 쓰레기값을 갖겠지만 `EX` 실행 전에 제대로 된 값을 갖게 될 테니까 문제없다.
+
+ 하지만, 제한시간 내에도 이전 instruction의 `EX`가 계산되지 않았을 경우에는 forwarding을 사용하더라도 어쩔 수 없이 bubble이 생기게 된다. (그래도 사용 안하는 것보다는 빠르다.)
-
Control Hazard
- Branch나 Jump 같이 실행흐름을 변경하는 명령어를 수행할 땐 target address를 계산한 뒤 PC에 저장하게 되는데, 이전 instruction에 의해 PC값이 변경되는 경우 원치 않는 곳으로 실행흐름이 변경될 수 있다.
- 따라서 RISC-V에서는 다음 instruction의 decode 단계를 의도적으로 한 번 쉬어준 다음 (bubble 하나 추가), 안정적으로 target address가 ALU에 의해 계산됐을 때 비로소
ID
를 수행한다.
8.3.3. 결론
- Pipelining은 throughput을 올리기 위한 아주 좋은 방법이다.
- 여러 instruction은 병렬로 수행한다.
- 하지만, latency는 그대로라는 점을 잊지말자.
- “Pipelining을 사용하면 언제나 성능향상이 가능하다”라는 말을 함부로 할 수 없다.
- Hazard가 있기 때문이다 - Structure, Data, Control
8.4. RISC-V Pipelined Datapath and Controlpath
- Pipelining을 구현하기 위해서는 당연히 각 단계의 combinational circuit 사이에 flip flop같은 sequential circuit이 들어가야하고 hardware의 complexity가 증가하는 것은 불가피하다.