논문요약: The Microarchitecture of Superscalar Processors

마이크로 아키텍처 설계에서 고려해야할 점
  • binary compatibility: 같은 아키텍처를 사용해서 이전 제품과 호환이 되야 한다
  • sequential execution: 프로그램이 순서대로(in-order) 실행되야 한다
슈퍼스칼라 아키텍처
  • 파이프라인을 여러개 두어서 성능을 높임.
    하지만 in-order로 실행할 경우 hazard가 있는 instruction 때문에 파이프라인이 많이 비게 되고, 그래서 순서를 어기더라도 hazard가 없는 instruction을 먼저 수행하는 out-of-order 방식을 적용하게 됨
  • 그 과정에서 sequential execution을 어기게 된다.
    하지만 precise interrupt나 branch execution을 수행하려면 sequential execution을 반드시 지켜야 하므로,
    슈퍼스칼라는 마이크로아키텍처에서는 sequential execution model을 포기하지만, 아키텍처 관점에서는 sequential execution을 지키는 것 같아 보이게 만듦

슈퍼스칼라 아키텍처의 구조


1. instruction fetch

캐쉬에서 instruction을 fetch한다. 이때 branch instruction은 branch predictor가 관여해서 다음 PC값을 예측하고 fetch한다.
MIPS R10000에서는 resume cache를 둬서 prediction한 instruction 뿐만 아니라 prediction이 fail할 경우의 instruction도 fetch해서 resume cache에 저장한다.
이는 prediction이 틀릴 경우 오버헤드를 줄이기 위한 전략이다.

슈퍼스칼라는 파이프라인이 여러 개이므로 동시에 여러 개의 instruction이 execute된다.
그러므로 instruction의 처리 속도가 굉장히 빠른데 이를 캐쉬가 완전히 소화하기 힘들어서(miss, throughput 등에 의해) instruction buffer를 두어 캐쉬에서 받은 instruction들을 미리 쌓아 놓는다.
instruction buffer는 prediction이 fail할 경우를 대비해서 미리 fetch한 instruction도 저장할 수 있으므로(resume cache를 buffer에 같이 두는 걸 의미하는 듯) misprediction에 의한 오버헤드도 크게 줄일 수 있다.

2. decode, rename, dispatch

decode 단계에서 instruction이 1)어떤 연산을 하는지, 2)누구를 operand로 쓰는지, 3)값을 어느 레지스터에 저장하는지 등을 파악한다. (simple pipeline CPU와 동일하다)

중요한 건 rename 단계이다.
프로그램의 성능을 저하시키는 요인을 hazard라고 하고, instruction에는 2가지 헤저드가 있다(data hazard, control hazard)
control hazard는 branch prediction으로 극복한다.
data hazard는 레지스터간의 dependency 때문에 발생하는데,
레지스터를 write하는 instruction이 다 처리되기 전에 해당 레지스터를 read하는 경우 write instruction이 끝날 때 까지 기다려야 하고 이를 true dependency 혹은 RAW (read after write) hazard라고 한다.
원래 data hazard는 RAW밖에 없지만 만일 프로그램이 OoO(out-of-order)로 수행될 경우,
WAR, WAW 또한 문제가 되기 시작한다. 하지만 WAR과 WAW는 해결할 수 있는 hazard이고 이를 해결하는 방식이 rename이다.

operation의 결과로 write되는 레지스터의 이름을 바꿔준다. rename하는 방식은 2가지가 있다

  1. large physical registers
    register file에 레지스터를 architectural register보다 많이 둔다.
    mapping table을 갖고 어떤 physical register에 어떤 logical register가 mapping되어 있는지 기록한다.
    register를 write하는 operation마다 free list (mapping되지 않고 놀고 있는 physical register를 기록해두는 list) 에서 놀고 있는 register를 할당한다.

    예) [mov r3, r5 / free list: r7, r10] 일 경우 r7을 r5에 mapping 시킨다. (GAS 기준)

    이렇게 renaming해서 새로 만들어진 인스트럭션을 ROB와 issue queue에 넣는다.
    free list에 register가 없을 경우 인스트럭션을 renaming하지 못하므로 free register가 생길때 까지 halt한다.
  2. use ROB as registers
    ROB를 레지스터처럼 쓴다.
    ROB에 register를 하나씩 두고 그걸 1)에서의 free register처럼 쓴다.
    free list를 유지할 필요가 없다는 장점이 있다.
    mapping table은 유지해야 한다. (어떤 ROB에 어떤 logical register가 mapping되어 있는지 기록)
이렇게 renaming된 인스트럭션을 ROB와 issue queue에 넣는다. 이를 dispatch라고 한다.

3. issue queue / ROB

슈퍼스칼라는 파이프라인을 여러개 두고 여러 인스트럭션을 동시에 처리하는 아키텍처이다. 만일 program order상 나중에 실행해야되는 인스트럭션이지만 앞의 인스트럭션과dependency가 없어 언제 실행해도 상관 없는 인스트럭션이 있다면, 그리고 그 때 파이프라인이 놀고 있다면 순서와 상관없이 먼저 처리하는게 유리하다. 이렇게 OoO(Out of Order)로 실행할 때 생기는 fault dependency(WAR, WAW hazard)를 해결하는 방식은 앞에서 다루었다(renaming).

하지만 OoO는 sequential execution을 만족하지 않는다. 이게 왜 문제가 되냐면,
OoO로 수행 중 interrupt가 걸렸을 때 precise interrupt를 하기 힘들어진다.
이를 위해 OoO로 수행은 하지만 interrupt가 걸렸을 때 sequential execution으로 실행된 것 처럼 보이게 아키텍처를 설계해야한다.

그래서 우리는 버퍼를 2개 둔다. issue queue는 프로그램을 내부적으로 OoO로 수행하기 위한 instruction buffer이고, ROB(ReOrder Buffer)는 프로그램이 sequential하게 수행되는 것 처럼 보이게 하는 용도의 buffer이다.



  1. Issue queue
    인스트럭션은 dispatch되서 issue queue에 들어가 있다가, 1)pipeline에 들어가서 이미 실행중인 instruction과 dependency가 없고, 2)pipeline에 인스트럭션이 실행될 자리가 있을 경우 파이프라인으로 전달된다.
    issue queue는 3가지 형태가 존재하는데 OoO를 궁극으로 활용할 수 있는 issue queue의 형태는 c)에 해당된다.
    issue queue는 pipeline에 있는 instruction들을 CAM search하고(주로 instruction에 대한 정보는 ROB에 저장되어 있으므로 ROB를 CAM search한다) dependency가 없어서 가공할 데이터가 준비가 되면 program order와 상관없이 pipeline으로 들어가는데 이를 issue라고 한다. (그래서 issue queue가 파이프라인으로 instruction을 쏘는걸 issuing이라고 함)
  2. ROB

    인스트럭션은 dispatch되어 ROB와 issue queue에 들어간다.
    ROB는 FIFO queue와 같다. 들어간 순서대로 밖으로 나온다.
    ROB 맨 앞에 있는 인스트럭션은 매 사이클마다 파이프라인에서 자신이 이미 다 execution되었는지 확인하고, 자신이 이미 다 execute됬다면 ROB에서 나간다.
    이렇게 ROB head에서 인스트럭션이 밖으로 나가는 것을 commit이라고 한다.
    commit되는 방식은 renaming하는 방식에 따라 다르다.

    1) large physical registers를 이용하는 경우
    commit하기 전에 수정할 값을 저장해놓을 레지스터가 physical register밖에 없으므로 commit하기 전, execute할 때 이미 physical register를 수정하게 된다.
    그래서 commit할 때 달리 할게 없다.

    2) ROB를 register처럼 쓰는 경우
    commit하기 전에 수정할 값이 ROB에 저장되어 있다. 이 값은 아직 logical register에 적용되지 않았으므로 commit할 때 ROB에 저장된 값을 mapping된 logical register에 덮어씌운다.

    ROB는 precise interrupt를 지원하기 위해 사용한다.
    interrupt가 걸리면 register와 renaming table을 instruction이 수정되기 전으로 되돌려야 한다.
    interrupt에 대처하는 방식은 renaming 방식에 따라 달라진다.

    1) large physical register를 이용하는 경우
    commit되기 전에 이미 renaming table과 register가 수정되있으므로 register를 원래대로 되돌려야 한다.
    그래서 ROB에는 이 인스트럭션이 (1)renaming table의 값을 어떻게 수정했고, (2)physical register의 값을 어떻게 수정했는지 저장하고 있어야 한다.

    2) ROB를 register처럼 쓰는 경우
    commit되기 전에 renaming table만 수정하고 logical register는 건들지 않는다. 그래서 ROB에는 이 인스트럭션이 renaming table을 어떻게 수정했는지만 저장해둔다.

    interrupt가 어떤 인스트럭션에 걸리면,
    interrupt가 걸린 인스트럭션보다 sequential order상 먼저 실행된 인스트럭션은 전부 commit되야 하고, 나중에 실행된 인스트럭션은 전부 무효로 돌려야 한다.|
    그래서 ROB에 인터럽트가 걸린 인스트럭션만 남아있도록 renaming table과 register를 수정하고 interrupt를 처리해야 한다.


슈퍼스칼라는 굉장히 어려운 개념인 것 같다.
그래서 그런지 대학교에서도 학부때는 다루지 않고 파이프라인 정도 까지만 다루고 넘어가는 경향이 있다. (우리학교에서 이렇게 얼렁뚱땅 넘어갈 정도면 다른 학교는 말 다했지...)
사실상 우리나라 학교에서는 제대로 안배우고 넘어가는 개념이라고 생각해도 된다.

근데 우리가 쓰는 대부분의 아키텍처는 슈퍼스칼라가 적용되어 있다.
구닥다리 스마트폰이나 임베디드 디바이스 정도가 아니면 모든 CPU엔 슈퍼스칼라가 적용되어 있다고 봐도 무방하다.
그래서 그런지 컴퓨터시스템을 제대로 배우고 써먹으려면 슈퍼스칼라 정도는 반드시 알아야 한다고 생각한다.

미국에서는 학부때 슈퍼스칼라까지 다 배운다고 들었다.
한국회사에 들어가려면 크게 상관 없을 것 같은데, 구글이나 아마존, 페이스북 본사에 취직하고자 한다면 전세계를 무대로 전세계 학생들과 경쟁해야 할텐데 슈퍼스칼라에 대해 잘 모르면 이런 회사에 들어갈 수 있을까 싶다.
그래도 이젠 슈퍼스칼라에 대해 어느정도 이해하게 됬으니 다행이라 생각한다.

댓글

이 블로그의 인기 게시물

시스템프로그래밍: gprof (gnu 프로파일러) 소개 및 사용법

칩 설계에 관한 전반적인 내용들

나는 반드시 구글에 들어간다!