본문 바로가기

명징직조

기계어와 회로 연산

1. 기계어는 어떻게 실제 회로 동작으로 바뀌는가

기계어는 계산 그 자체가 아니라 CPU 내부의 어떤 회로를 사용하고 데이터를 어디서 어디로 이동시킬지를 결정하는 비트 패턴이다.

예를 들어 다음 코드 작성

c = a + b;

컴파일러는 이를 CPU가 이해할 수 있는 명령어들로 바꾼다

개념적으로는 다음과 비슷

메모리에서 a를 읽어라
메모리에서 b를 읽어라
두 값을 더하라
결과를 c에 저장하라

실제 기계어는 문장이 아닌 비트열

01001011 11000001 ...

CPU 는 이 비트열을 한 명령어씩 읽고 실행한다.

전체 과정은 다음과 같다

메모리에 저장된 명령어
        ↓
명령어 가져오기
        ↓
명령어 해석
        ↓
제어 신호 생성
        ↓
데이터 이동
        ↓
연산 회로 작동
        ↓
결과 저장

 

1.1 명령어는 메모리에 저장되어 있다.

프로그램을 실행하면 기계어 명령어들이 RAM 에 올라간다

CPU 안에는 현재 실행할 명령어의 위치를 기억하는 레지스터가 있다.

이를 보통 프로그램 카운터 PC 라고 한다.

PC = 현재 실행할 명령어의 주소

예를 들어 PC 가 다음 값을 가지고 있다고 하자

PC = 1000

그러면 CPU 는 메모리 주소 1000 에 있는 명령어를 읽는다.

첫 번째 명령어를 가져오고 나면 PC 는 다음 명령어를 가리키도록 증가한다.

PC = 1000
↓
명령어 읽기
↓
PC = 1004

 

1.2 명령어 비트는 여러 부분으로 나뉜다

기계어 명령어는 하나의 숫자가 아닌, 내부적으로 여러 필드로 나뉜다.

가상의 16비트 명령어 생각,

0001 001 010 011 000

이를 다음처럼 해석할 수 있다.

0001 → ADD 명령
001  → 목적지 레지스터 R1
010  → 첫 번째 입력 레지스터 R2
011  → 두 번째 입력 레지스터 R3

전체 비트열은 다음 의미를 가진다.

R1 = R2 + R3

앞부분은 무슨 연산인지, 나머지는 어떤 데이터를 사용할지를 나타낸다.

 

1.3 디코더가 명령어를 해석한다

CPU 내부에는 명령어를 해석하는 디코더 회로가 있다.

디코더는 입력된 비트 패턴에 따라 서로 다른 내부 신호를 활성화한다.

opcode = 0001
        ↓
ADD 신호 활성화
SUB 신호 비활성화
MUL 신호 비활성화
LOAD 신호 비활성화

디코더는 일종의 선택 장치

입력 비트 패턴
      ↓
어떤 동작을 수행할지 선택

이 과정은 소프트웨어가 아니라 회로로 이루어진다.

즉 디코더도 트랜지스터와 논리 게이트들의 조합이다.

 

1.4 디코더가 만드는 것은 제어 신호다

명령어를 해석한 뒤 CPU 는 내부 제어 신호를 만든다

다음 명령어 존재

ADD R3, R1, R2

이 명령을 실행하려면 CPU 내부에서는 최소한 다음 동작이 필요

R1의 값을 읽는다
R2의 값을 읽는다
ALU를 덧셈 모드로 설정한다
ALU 입력에 R1과 R2를 연결한다
결과를 R3에 기록한다

이를 실제 제어 신호로 표현하면 개념적으로 다음과 같다

ReadRegister1 = R1
ReadRegister2 = R2
ALUOperation = ADD
WriteRegister = R3
RegisterWriteEnable = 1

중요한 점은

CPU가 “R1과 R2를 더해야겠다”고 이해하는 것이 아니라, 특정 전기 신호들이 활성화되면서 데이터 통로가 물리적으로 연결된다는 것이다.

 

1.5 데이터는 멀티플렉서를 통해 이동한다

CPU 내부에는 여러 데이터 경로가 존재한다. 

그러나 모든 회로를 동시에 아무렇게나 연결할 수는 없다.

그래서 멀티플렉서, MUX 라는 선택 회로를 사용한다.

멀티플렉서는 여러 입력 중 하나를 선택한다.

입력 A ─┐
입력 B ─┼→ 선택된 하나의 출력
입력 C ─┘

제어 신호가 다음처럼 주어질 수 있다.

선택 신호 00 → A 선택
선택 신호 01 → B 선택
선택 신호 10 → C 선택

ADD 명령에서는 다음과 같은 경로가 선택된다.

R1 ─────┐
        ├→ ALU 입력
R2 ─────┘

ALU 모드 = ADD

ALU 출력 → R3

반면 곱셈 명령이라면 다른 회로가 선택된다.

R1 ─────┐
        ├→ 곱셈기
R2 ─────┘

곱셈기 출력 → R3

즉 명령어는 내부 데이터가 어떤 길을 따라갈지를 결정한다. 

 

1.6 ALU 는 여러 연산 회로의 조합이다.

Arithmetic Logic Unit, 산술논리장치

보통 다음 기능을 수행한다.

덧셈
뺄셈
AND
OR
XOR
비교
비트 이동

ALU 내부에는 이러한 연산을 수행하는 회로들이 있다.

             ┌→ 덧셈 결과 ─┐
입력 A, B ───┼→ AND 결과  ─┼→ 선택 → 최종 출력
             ├→ OR 결과   ─┤
             └→ XOR 결과  ─┘

제어 신호가 어떤 결과를 선택할지 정한다.

ALUControl = 000 → ADD
ALUControl = 001 → SUB
ALUControl = 010 → AND
ALUControl = 011 → OR

ADD 명령이면 덧셈 회로의 출력이 선택된다.

 

1.7 결과는 레지스터에 기록된다

연산 결과가 나왔다고 자동으로 저장되는 것은 아님

어느 레지스터에 기록할지, 실제로 기록할지도 제어 신호가 결정한다.

ALU 결과 = 8
목적지 레지스터 = R3
WriteEnable = 1

그러면 특정 클럭 순간에 R3 가 8 로 변경된다.

연산 전:
R3 = 0

클럭 경계:
WriteEnable = 1

연산 후:
R3 = 8

WriteEnable 이 0 이면 ALU 결과가 존재해도 레지스터 값은 바뀌지 않는다.

 

1.8 클럭은 동작 시점을 맞춘다

CPU 는 수많은 회로가 동시에 작동하기 때문에, 언제 값을 읽고 언제 저장할지를 맞춰야 한다.

이를 위해 클럭 신호를 사용한다.

낮음 → 높음 → 낮음 → 높음

__|‾‾|__|‾‾|__|‾‾|__

클럭은 모든 계산을 직접 수행한느 신호가 아니라, 상태를 갱신할 순간을 정해준다.

한 클럭 주기 동안에는 조합논리 회로가 계산한다.

레지스터 출력
     ↓
조합논리 회로에서 계산
     ↓
다음 레지스터 입력 준비

클럭 경계에서 계산 결과가 레지스터에 저장된다.

클럭 경계
    ↓
레지스터 값 갱신

이 과정을 반복하면서 프로그램이 실행된다.

 

1.9 단순한 CPU에서는 한 명령어가 여러 단계로 실행된다.

한 ADD 명령도 실제로는 여러 내부 단계가 필요하다.

1. 명령어 가져오기
2. 명령어 해석
3. 레지스터 읽기
4. ALU 연산
5. 결과 저장

이것을 한 번에 모두 처리할 수도 있지만, 현대 CPU 는 보통 파이프라인을 사용한다

예를 들어

명령어 A: 실행 단계
명령어 B: 해석 단계
명령어 C: 가져오기 단계

여러 명령어의 서로 다른 단계를 겹쳐 처리한다.

시간 1: A 가져오기
시간 2: A 해석     / B 가져오기
시간 3: A 실행     / B 해석     / C 가져오기
시간 4: A 저장     / B 실행     / C 해석

따라서 현대 CPU 에서 명령어 하나가 회로 하나를 작동시킨다는 설명은 지나치게 단순하다

실제로는

명령어
→ 내부 마이크로 연산으로 분해
→ 여러 실행 장치에 배치
→ 결과를 원래 명령 순서에 맞게 정리

하는 일이 일어난다.

 

1.10 기계어와 회로는 반드시 일대일 대응하지 않는다

예를 들어 복잡한 명령어 하나가 있다고 하자

메모리에서 값을 읽고
곱한 뒤
더하고
결과를 다시 저장

겉으로는 하나의 기계어 명령어여도 내부에서는 여러 작은 동작으로 분해될 수 있다.

LOAD
MUL
ADD
STORE

이런 내부 단위를 흔히 마이크로 연산이라고 한다.

반대로 CPU 는 연속된 여러 명령어를 내부적으로 묶어서 더 효율적으로 처리하기도 한다.

기계어 명령어
     ↓
디코더
     ↓
하나 또는 여러 개의 내부 동작
     ↓
연산 회로와 데이터 이동 회로 사용