The Microarchitecture of Superscalar Processors

Sept. 26, 2024, 11:23 p.m. · 16 min read · 🌐︎ ko

architecture paper review

Smith, James E., and Gurindar S. Sohi. "The microarchitecture of superscalar processors." Proceedings of the IEEE 83.12 (2002): 1609-1624.

Abstract

1. Introduction

Superscalar processor의 등장은 RISC movement의 연장선상에 있는 것으로 여겨지지만, 그 구현은 점점 더 복잡해지고 있음.

Historical Perspective

1960년대의 notable한 프로세서들:

The Insturction Processing Model

Processor design에 있어서는 binary compatibility를 고려해야 함.

현대의 computer designer들도 binary compatibility와 sequential execution model을 유지해야 함.

Elements of High Performance Processing

성능 = 더 적은 시간에 수행을 마치는 것. 수행시간을 줄이기 위해서는 두 가지 방법

  1. instruction 각각의 latency를 줄이는 것
  2. insturction들을 더 parallel하게 실행 -> superscalar는 여기에 집중

Parallel instruction processing은 다음을 필요로 함:

HW의 측면에서 보면, superscalar processor는

  1. 필요에 따라 conditional branch의 결과도 예측 할 수 있는, 한번에 여러 instruction을 읽을 수 있는 fetch unit
  2. register 값의 true dependence를 파악하고 이들의 값을 필요에 따라 가져올 수 있는 방법
  3. 여러 Instruction을 병렬적으로 issue(실행의 시작)할 수 있는 방법
  4. 여러 instruction을 병렬적으로 execute할 수 있는 자원.

    • 여러 개의 pipelined functional unit들
    • 동시에 여러 개의 memory reference를 처리할 수 있는 memory hierarchy
  5. load/store로 메모리와 값을 서로 전달할 수 있는 방법과 dynamic하고 예측불가능한 memory hierarchy의 performance behavior를 지원하는 memory interface

  6. process state를 올바른 순서대로 commit하여 바깥에서 보기에는 sequential execution처럼 보이도록 하는 방법
    을 구현하고 있어야 한다. 실제 시스템에서 각각은 독립적인 것들이 아니고, seamless하게 통합되어 있다.

Paper Overview

2장에서는 superscalar의 general problem인, 겉보기에는 sequential한 프로그램을 parallel하게 바꾸는 문제에 대해 다룬다.
3장에서는 전형적인 superscalar microprocessor에서 사용되는 구체적인 테크닉을, 4장에서는 세 가지 최신 superscalar processor를 소개하면서 그러한 테크닉들의 스펙트럼을 살펴본다.
5장에서는 결론을 제시하고 미래에 ILP의 발전 방향에 대해 다룬다.

2. Program Representation, Dependences and Parallel Execution

위의 코드를 working example로 삼음. (a)가 C 코드, (b)가 어셈블리로 컴파일한 버전.

ILP를 향상시키는 첫 단계는 control dependence를 극복하는 것

같은 window of execution에 있는 Instruction들은 data dependence에 놓여있을 수 있음

Control/data dependence가 해결되면 instruction들은 issue된 후 병렬적으로 실행됨

3. The Microarchitecture of a Typical Superscalar Processor

위는 일반적인 superscalar processor의 microarchitecture를 나타낸 것

3.1 Instruction Fetching and Branch Prediction

Instruction Fetching
Fetch 단계: instruction을 processor의 뒷부분에 공급해주는 역할

Branch Prediction

  1. Conditional Branch의 인식

    • pre-decoding: i-cache에 decode 정보가 미리 들어있으면 더 빠르게 conditional branch 여부를 파악할 수 있음
      • extra bit 몇 개를 넣어놓는 식. i-cache의 전에 pre-decoding logic을 배치해 계산하게 함
  2. Branch outcome의 결정(taken/not taken)

    • branch가 그 앞에 있는 uncompleted instruction에 dependent한 경우 fetch 시에는 branch 여부 결정이 불가
      • 이러한 경우 branch outcome을 predict
    1. static methods: static binary만 보고 결정가능한 정보들을 이용

      • e.g. 특정 opcode는 taken branch를 자주 발생시킨다든지, loop 때문에 backward branch가 더 잦다든지
      • 컴파일러가 high-level code에 기반해, prediction을 위한 flag를 붙여주는 경우도 있음
      • profiling information(이전 실행에서 수집한 통계 정보)를 이용하는 경우도
    2. dynamic methods: run-time에 알 수 있는 정보들을 이용해 예측에 활용

      • branch history(prediction) table: branch history를 저장
        • branch instruction의 주소를 가지고 access
        • 비트 몇 개 사용하여 최근 몇 번의 execution result를 저장하는 식
        • prediction이 틀린 것으로 판명되면 IF는 실제 올바른 path로 redirect되고 틀린 경로의 실행 결과는 무효화
  3. Branch Target의 계산

    • branch target 계산은 주로 정수의 덧셈. PC + offset인 경우가 많기 때문
      • 이 경우 register 읽을 필요가 없음. 다만 register를 branch target에 사용하는 경우도 존재
    • branch target buffer를 도입해 최근에 branch가 향한 주소들을 저장하도록 하면 branch target computation을 더 빠르게 만들 수 있음
  4. Control Transfer
    branch take시 보통 한 cycle 이상의 딜레이가 발생 -> pipeline delay를 발생시킴

    • 가장 일반적인 솔루션은 instruction buffer에 비축해둔 instruction을 이 때 실행하는 것
      • 복잡한 buffer들은 taken/not taken 경로의 instruction들을 둘 다 비축해놓기 때문
    • 초기 RISC 프로세서들은 delayed branch를 사용하기도 했음.
      • 그러나 이는 pipelined structure에 대한 가정을 깔고 있으며 superscalar에서는 이러한 가정이 성립하지 않는 경우가 많음 -> 최근에는 사용 X

3.2. Instruction Decoding, Renaming, and Dispatch

insturction이 fetch buffer에서 나와 examine되고, control & data dependence 관계를 포착해 이후의 pipeline stage를 위해 set up되는 과정

Decode 단계의 역할은 각 instruction에 대해 한 개 또는 여러 개의 execution tuple을 세팅하는 것

Register Renaming

  1. physical register file이 logical register file보다 큰 경우: mapping table로 동적으로 매핑

    • mapping table이 서로 대응되는 logical/physical register 이름을 기록
    • decode 단계에서 sequential order로 register renaming이 수행.
      • free list에서 physical register를 하나 골라 대응시키고 mapping table을 업데이트
    • physical register가 마지막으로 읽힌 후에는 free되어야 함
      • 방법 중 하나는 counter를 사용하는 것.
        • source register가 해당 physical register로 rename될 때마다 counter를 증가, 실제 issue되어 읽기가 일어날 때마다 counter를 감소. counter=0이 되면 free
          ![[The Microarchitecture of Superscalar Processors-20240926210844806.webp|605]]
  2. physical register file이 logical register file과 같은 크기인 경우: 둘 사이 대응관계가 고정됨, 그러나 reorder buffer로 renaming을 함.

    • e.g. logical $r13는 무조건 physical RF의 13번째에 대응
    • reorder buffer: active instruction(dispatch됐지만 commit되지 않은 것)마다 한 개씩의 entry가 존재.

      • FIFO queue처럼 작동. precise interrupt를 위해 reordering하는 역할도 하기 때문에 이렇게 명명
      • instruction은 dispatch시마다 ROB의 끝에 entry를 할당받음, 실행이 끝나면 ROB entry에 result를 기록
      • ROB의 head에 도달한(=앞의 inst들은 다 이미 commit) instruction이 실행 끝나면 ROB entry는 지워지고, 기록해뒀던 실행 결과를 register file에 씀
    • Logical register의 값은 physical register에 있을 수도 있지만, reorder buffer에 있을 수도 있음.

      • 이 경우 mapping table이 해당 logical register의 값이 physical RF에 있는지, 없다면 reorder buffer의 어느 entry에 있는지 알려줌
      • reorder buffer가 가득 차면 dispatch는 일시 중단됨
        ![[superscalar_fig7.webp|468]]
        Fig. 7은 add r3, r3, 4가 어떻게 rename되는지 보여줌

두 방법 모두 artificial dependence를 해소 -> WAW와 WAR hazard를 방지.
$\therefore$ RAW dependence만 처리하면 됨

3.3. Instruction Issuing and Parallel Execution

앞선 단계(decode/rename dispatch)에서 execution tuple이 만들어졌음

이상적으로는 input operand가 전부 도착시 바로 issue되어야 함

간단한 것부터 instruction issue buffer의 구성 방법을 소개함

  1. Single Queue Method

    • 하나의 queue로 모든 functional unit에 명령어를 배분.
    • FIFO queue 하나가 끝이므로 out-of-order 실행이 불가능 -> register renaming을 할 필요가 없음
    • operand의 가용 여부는 reservation bit 하나로 확인이 가능.
      • 해당 register에 쓰고 있는 Instruction이 있으면 reserved, 해당 instruction이 끝나면 clear
    • operand 중 reserved인 것이 없으면 issue가 가능함
  2. Multiple Queue Method

    • 각각의 queue에서는 in-order로 issue됨. 서로 다른 queue끼리는 순서가 바뀔 수 있음
    • 어느 queue에 들어갈지는 Instruction type에 따라 분류됨. queue는 각 FU에 대응되기 때문
    • register renaming은 제한적인 형태로만 가능함. 예를 들어, load의 대상이 되는 register만 rename되는 식
      • load operation이 다른 instruction들보다 앞으로 감으로써 해당 값이 필요하기 전에 memory data를 미리 읽어오는 것이 가능해짐1
  3. Reservation Station

    • Tomasulo's algorithm의 일부로 처음 제안됨. FIFO ordering 없이 out-of-order로 issue됨.
    • operand availability를 각각의 reservation station이 계속 체크해야 함
    • RS로 instruction이 dispatch될 때,
      • 이미 가용한 operand는 값을 그대로 가져와 entry에 넣음
      • 그렇지 않은 operand는 끝나는 instruction들의 result designator들과 비교
    • 내부에서 instruction type에 따라 partition되어 있을 수도, 단일 저장소에 저장될 수도 있음
    • 최신 RS들은 실제 source data를 저장하지 않고 저장 장소를 가리키는 pointer로 저장.
      • e.g. register file 또는 ROB entry

3.4. Handling Memory Operations

Memory operation은 superscalar에서 특별한 취급이 필요

시간을 줄이기 위해서는

Multiport

Non-blocking
메모리 연산과 다른 연산의 overlapping을 허용하려면 hierarchy가 non-blocking이어야 함

Store Address Buffer
Memory operation들이 overlap되거나 out-of-order로 실행될 수 있도록 하려면

Memory hierarchy로 요청이 전송되면, d-cache hit 또는 miss가 발생할 수 있음

3.5. Commiting State

instruction의 lifetime에서 마지막은 commit 또는 retiring이라고 불리는 과정

Commit 단계에서 해야 할 일은 precise state를 구현하는 방법에 따라 달라짐

  1. machine state를 history buffer 또는 checkpoint라는 곳에 백업해놓기

    • 즉 백업을 계속 해놓는거 말고는 특별한 일을 하지 않음
    • precise state가 필요한 시점이 오면 history buffer에서 꺼내와 복원하면 됨
    • 이 경우, commit state에서는 더 이상 필요 없는 history state들을 삭제해주는 일만 하면 됨
  2. machine state를 둘로 나눔: implemented physical state와 logical (architectural) state

    • physical state: 명령어가 완료되면 즉시 업데이트되는 상태
    • architectural state: sequential model로 실행된다고 가정 시에 업데이트되는 상태
      • 즉, instruction에서 "speculative" 태그가 떼지고 확정된 명령어들의 결과를 프로그램 순서대로 반영
    • speculative state(즉 speculative execution의 결과물)를 ROB에서 관리함
      • commit시에는 instruction의 결과가 ROB에서 architectural RF로 옮겨지면서 ROB entry가 할당해제됨
        • 마찬가지로 store instruction인 경우 ROB에 있던 store data가 메모리에 옮겨짐

ROB는 renaming의 physical register method에서도 사용된다
[[#3.2. Instruction Decoding, Renaming, and Dispatch|3.2절]]에서 register renaming을 살펴보면서, ROB method(2번)을 알아보았음

3.6. SW의 역할

최적화된 binary는 같은 일을 더 빠르게 처리할 수 있음.

4. Three Superscalar Microprocessors

위에서 살펴본 바를 바탕으로 최신 superscalar processor들을 살펴봄

4.1. MIPS R10000

위에서 설명한 것과 매우 유사한 구조로, dynamic scheduling을 많이 함.

4.2. Alpha 21164

![[superscalar_fig12.webp]]

4.3. AMD K5

나머지 둘과는 달리 x86으로 구현 -> fast, pipelined implementation을 염두에 둔 ISA가 아님!

5. Conclusion: What Comes Next?

Superscalar model은 성능과 호환성이 모두 중요.

Parallel hardware가 추가 투입 시에는 "한계효용이 체감"될 수밖에 없음

System level issue들

혁신적인 새로운 방법이 나오지 않는다면, 8-way superscalar 정도가 실질적인 한계일 것으로 생각됨. 그 다음은 어디인가?


  1. 예를 들어서, r1<-r2+r3; r1<-r4-r5; r1<-MEM[addr]이 있다고 가정해보자. ALU 연산들은 어차피 같은 queue에 들어가 in-order로 실행되니까 첫 두 inst은 renaming을 하는 의미가 없다. 하지만 세 번째 연산의 경우, 나머지 둘과 다른 queue에 들어가므로 renaming만 해준다면 순서를 바꿔서 제일 먼저 실행해줘서 latency를 가릴 수 있다. 

  2. translation 과정에서는 어차피 높은 자리 bit들만 갈아끼워지는 거니까 untranslated virtual address로도 cache 접근이 가능. cache도 lower bit들로 접근하는 것이기 때문 

  3. static code는 뭘 말하는거지? 

  4. 한 line에 여러 개 branch instruction이 있을 수도 있긴 하지만 이런 경우는 걍 포기하는듯 

Ctrl+K
Start typing to search...