컴퓨터 구조 02 - CPU 내부 구조
ALU, 제어 유닛, 데이터패스의 내부 동작과 명령어가 CPU를 통과하는 과정
CPU를 열어 보면
이전 포스트에서 CPU가 인출-해석-실행 사이클을 반복한다는 것을 살펴보았다. 그런데 이 세 단계 각각에서 실제로 무슨 일이 일어나는 것일까? CPU 내부에는 어떤 부품들이 있고, 이들이 어떻게 협력하여 하나의 명령어를 처리하는 것일까?
CPU의 내부는 크게 세 가지 구성 요소로 나뉜다. 연산을 수행하는 ALU, 명령어를 해석하고 각 부품에 제어 신호를 보내는 제어 유닛, 그리고 이들 사이에서 데이터가 흘러가는 경로인 데이터패스이다. 이 세 요소의 상호작용이 CPU의 모든 동작을 만들어낸다.
ALU: 산술논리연산장치
ALU는 CPU에서 실제로 계산을 수행하는 부분이다. 이름 그대로 산술 연산(덧셈, 뺄셈, 곱셈, 나눗셈)과 논리 연산(AND, OR, NOT, XOR)을 처리한다.
ALU의 핵심 구성 요소 중 하나는 가산기(adder)이다. 가산기가 왜 그렇게 중요한가? 뺄셈은 2의 보수를 취한 후 더하는 것이고, 곱셈은 반복적인 덧셈과 시프트의 조합이며, 나눗셈은 반복적인 뺄셈과 시프트의 조합이기 때문이다. 결국 대부분의 산술 연산이 가산기를 기반으로 구현되는 셈이다.
가장 단순한 가산기는 리플 캐리 가산기(Ripple Carry Adder)이다. 이는 각 비트의 덧셈 결과로 발생하는 올림(carry)을 다음 비트로 전파하는 방식이다. 구현이 간단하지만, 최상위 비트의 결과를 얻으려면 최하위 비트부터 올림이 순차적으로 전파되어야 하므로 비트 수에 비례하여 느려진다.
리플 캐리 가산기 (4비트)
A3 B3 A2 B2 A1 B1 A0 B0
│ │ │ │ │ │ │ │
┌─┴──┴─┐ ┌─┴──┴─┐ ┌─┴──┴─┐ ┌─┴──┴─┐
│ FA │←│ FA │←│ FA │←│ FA │← C_in
└──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘
C_out S3 S2 S1 S0
← : 올림(carry) 전파 방향
이 문제를 해결하기 위해 캐리 선행 가산기(Carry Lookahead Adder)가 사용된다. 이 방식은 각 비트 위치에서 올림이 발생할 조건을 미리 계산하여, 이전 비트의 올림을 기다리지 않고 동시에 결과를 산출한다. 회로는 복잡해지지만 속도가 크게 향상되며, 현대 프로세서의 ALU는 대부분 이 방식이나 그 변형을 사용하고 있다.
곱셈기는 가산기보다 훨씬 복잡한 회로이다. 초기 프로세서에서는 시프트-앤-애드(shift-and-add) 방식으로 여러 사이클에 걸쳐 곱셈을 수행했지만, 현대 프로세서는 월리스 트리(Wallace Tree)나 부스 인코딩(Booth Encoding) 같은 기법을 사용하여 곱셈을 훨씬 빠르게 처리한다. 그럼에도 곱셈은 덧셈보다 여전히 비용이 크며, 이것이 컴파일러가 2의 거듭제곱 곱셈을 시프트 연산으로 치환하는 이유이다.
논리 연산은 산술 연산에 비해 단순하다. AND, OR, XOR 같은 연산은 각 비트를 독립적으로 처리할 수 있으므로 올림 전파 같은 문제가 없다. 그러나 단순하다고 해서 중요하지 않은 것은 아니다. 비트 마스킹, 플래그 검사, 조건 분기 등 CPU의 핵심 동작 상당수가 논리 연산에 의존한다.
제어 유닛
ALU가 CPU의 근육이라면, 제어 유닛은 두뇌에 해당한다. 제어 유닛의 역할은 명령어를 해석하여 CPU의 각 구성 요소에 적절한 제어 신호를 보내는 것이다. ALU에 어떤 연산을 수행할지, 어떤 레지스터에서 데이터를 읽을지, 결과를 어디에 저장할지 등을 결정하는 것이 모두 제어 유닛의 몫이다.
제어 유닛을 구현하는 방식은 크게 두 가지가 있다.
하드와이어드 제어(Hardwired Control)는 명령어의 비트 패턴을 입력으로 받아 조합 논리 회로가 직접 제어 신호를 생성하는 방식이다. 명령어의 오피코드와 현재 상태를 입력으로 받는 상태 머신이 그 핵심이다. 이 방식은 속도가 빠르지만 설계가 복잡하고, 명령어 집합을 변경하려면 회로 자체를 재설계해야 한다. RISC 프로세서가 주로 이 방식을 채택하는데, 명령어 형식이 규칙적이고 단순하여 조합 논리로 구현하기에 적합하기 때문이다.
마이크로프로그래밍 제어(Microprogrammed Control)는 각 명령어에 대응하는 마이크로 명령어들의 시퀀스를 제어 메모리(control store)에 저장하는 방식이다. 하나의 기계어 명령어가 실행되면, 제어 유닛은 제어 메모리에서 해당하는 마이크로 명령어들을 순차적으로 읽어 나가며 제어 신호를 생성한다. 이 방식은 느리지만 유연하다. 명령어 집합을 변경할 때 하드웨어를 수정할 필요 없이 마이크로코드만 갱신하면 되기 때문이다. x86 프로세서처럼 명령어 형식이 복잡하고 다양한 CISC 아키텍처에서 이 방식이 선호된다.
과연 현대 프로세서는 둘 중 어느 방식을 사용하는가? 실제로는 혼합 방식이 일반적이다. x86 프로세서의 경우, 단순한 명령어는 하드와이어드 방식으로 빠르게 처리하고, 복잡한 명령어만 마이크로코드로 처리한다. 이렇게 하면 자주 사용되는 단순한 명령어의 성능을 극대화하면서도 복잡한 명령어와의 호환성을 유지할 수 있다.
데이터패스
데이터패스는 데이터가 CPU 내부에서 이동하는 경로 전체를 가리킨다. 레지스터, ALU, 내부 버스, 멀티플렉서 등이 데이터패스를 구성하며, 제어 유닛이 생성하는 제어 신호가 데이터패스의 각 요소를 조작하여 원하는 동작을 수행한다.
간단한 데이터패스를 통해 ADD R1, R2, R3 (R1 = R2 + R3) 명령어가 어떻게 실행되는지 추적해 보자.
명령어: ADD R1, R2, R3
1. 인출(Fetch)
PC → 메모리 주소 → 메모리에서 명령어 읽기 → IR에 저장
PC = PC + 4 (다음 명령어 주소로 갱신)
2. 해석(Decode)
IR의 오피코드 → 제어 유닛 → 제어 신호 생성
IR의 레지스터 필드 → 레지스터 파일에서 R2, R3 값 읽기
3. 실행(Execute)
R2 값, R3 값 → ALU (덧셈 연산)
ALU 결과 → R1에 저장
상태 레지스터 갱신 (오버플로우, 제로 플래그 등)
이 과정에서 데이터패스의 각 경로를 제어 신호가 활성화한다. 레지스터 파일의 읽기 포트 선택, ALU의 연산 종류 선택, 레지스터 파일의 쓰기 포트 활성화 등이 모두 제어 신호에 의해 결정된다.
┌──────┐ ┌──────────────┐ ┌─────┐
│ PC │────→│ 메모리 │────→│ IR │
└──┬───┘ └──────────────┘ └──┬──┘
│ │
│ +4 오피코드 │ 레지스터 번호
│ ┌──────┐ │
│ │제어 │←──┘
│ │유닛 │
│ └──┬───┘
│ │ 제어 신호
│ ┌───────────────┼─────────────┐
│ ↓ ↓ ↓
│ ┌─────────┐ ┌──────────┐ ┌────┐
│ │레지스터 │───→│ ALU │──→│결과│
│ │ 파일 │───→│ │ │저장│
│ └─────────┘ └──────────┘ └────┘
데이터패스 설계는 CPU 성능에 직접적인 영향을 미친다. 경로가 길어지면 신호 전파 지연이 커지고, 이는 클럭 속도의 상한선을 결정한다. 그래서 프로세서 설계자들은 데이터패스의 임계 경로(critical path), 즉 가장 긴 신호 전파 경로를 최소화하기 위해 많은 노력을 기울인다.
클럭 신호와 타이밍
CPU의 모든 동작은 클럭 신호에 의해 동기화된다. 클럭은 0과 1을 주기적으로 반복하는 신호이며, 클럭의 상승 에지(0에서 1로 전환되는 순간) 또는 하강 에지에서 레지스터의 값이 갱신된다. 클럭 주기(clock period)는 클럭의 한 사이클에 걸리는 시간이며, 클럭 속도(clock frequency)는 그 역수이다.
클럭 속도를 높이면 단위 시간당 더 많은 명령어를 처리할 수 있다. 그렇다면 클럭 속도를 무한히 높이면 되지 않는가? 그렇지 않다. 클럭 주기 내에 데이터패스의 가장 긴 경로를 통한 신호 전파가 완료되어야 하기 때문이다. 클럭을 너무 빠르게 올리면 신호가 안정되기 전에 다음 연산이 시작되어 잘못된 결과가 나온다.
또한 클럭 속도가 높아지면 전력 소비가 급격히 증가한다. 동적 전력 소비는 클럭 속도에 비례하며, 전력이 증가하면 발열도 증가한다. 2000년대 초반에 클럭 속도 경쟁이 4GHz 부근에서 멈춘 이유가 바로 이 전력-발열 한계 때문이다. 이후 프로세서 성능 향상은 클럭 속도 대신 파이프라이닝 심화, 멀티코어, 마이크로아키텍처 개선 등의 방향으로 전환되었다.
CPU 설계의 트레이드오프
CPU 설계에서는 수많은 트레이드오프가 존재한다. 이미 살펴본 하드와이어드 대 마이크로프로그래밍 제어가 그 중 하나이고, 그 밖에도 여러 선택의 기로가 있다.
레지스터 수를 늘리면 메모리 접근 횟수를 줄일 수 있지만, 명령어에서 레지스터를 지정하는 데 더 많은 비트가 필요해져 명령어 크기가 커진다. ALU를 더 넓게(예: 32비트에서 64비트로) 만들면 한 번에 처리할 수 있는 데이터 크기가 커지지만, 회로의 복잡도와 면적이 증가한다. 임계 경로를 줄이기 위해 파이프라인 단계를 늘리면 클럭 속도를 높일 수 있지만, 분기 예측 실패 시 버리는 명령어가 많아진다.
이러한 트레이드오프를 이해하는 것이 중요한 이유는, 소프트웨어 성능 최적화의 방향이 결국 하드웨어의 설계 선택에 의해 결정되기 때문이다. CPU가 어떤 연산을 빠르게 처리하고 어떤 연산에 비용이 큰지를 알면, 더 효율적인 코드를 작성할 수 있다.
다음 글에서는 명령어 집합 아키텍처(ISA)를 다룬다.