# 4장 : 컴퓨터 내부 구조

이제까지 배운 내용을 바탕으로 **실제로 컴퓨터를 구성하는 전체 구조**를 다루는 거라서, 지금까지 배운 비트, 논리회로, 메모리 등이 실제로 **어떻게 유기적으로 연결되는지**를 이해하는 게 핵심

### 🖥️ 4장. 컴퓨터는 어떻게 구성되어 있을까?

#### 1) 비트 → 회로 → 컴퓨터

우리가 지금까지 배운 흐름을 잠깐 복습해볼게:

* **1장:** 비트와 이진수, 전기 신호로 정보를 표현하는 방법을 배웠고
* **2장:** 조합 논리를 통해 ‘계산하는 회로’를 만들었고
* **3장:** 순차 논리를 통해 ‘기억하는 회로’를 만들었지?

이제는 이 회로들을 조합해서 **'컴퓨터'라는 하나의 시스템**을 만든다는 거야.\
즉, 지금부터는 진짜로 **전기 신호로 작동하는 컴퓨터 전체의 뼈대**를 만들기 시작하는 거지.

#### 2) 컴퓨터의 세 가지 핵심 구성 요소

컴퓨터를 크게 나누면 이렇게 세 가지 구성으로 설명할 수 있어:

| 구성 요소                             | 설명                         |
| --------------------------------- | -------------------------- |
| **CPU** (Central Processing Unit) | 계산과 판단을 담당하는 '두뇌' 역할       |
| **Memory (메모리)**                  | 데이터를 '기억'해 두는 공간           |
| **I/O (Input/Output)**            | 바깥세상과 소통하는 창구 (입력장치와 출력장치) |

이 세 가지가 유기적으로 연결되면, 우리가 아는 컴퓨터가 되는 거야.

#### 3) 이 장에서는 ‘간단한 컴퓨터’를 만든다

이번 4장에서는 **설명이 쉬운 구조의 간단한 컴퓨터**를 만드는 게 목적이야.\
이 컴퓨터가 시장에서 팔리는 제품처럼 강력하거나 최적화되어 있는 건 아니야.\
하지만 **컴퓨터의 핵심 개념**은 담겨 있기 때문에, 이 구조를 이해하면 어떤 컴퓨터든 구조가 눈에 들어오기 시작할 거야.

> "간단한 구조로 본질을 꿰뚫고, 이후 복잡한 최적화를 이해하는 것"\
> 이게 우리가 이 장에서 추구하는 방향성이야.

#### 4) 컴퓨터 구조의 중심: 도심(CPU)

이 책에서는 CPU를 `도심(City Center)`에 비유하고 있어.\
**모든 정보와 계산이 집중되는 중심지라는 의미지.**\
CPU는 <mark style="color:red;">메모리에서 정보를 받아와서 계산하고, 그 결과를 다시 메모리나 I/O로 보내줘.</mark>

> 도심(CPU)이 메모리(창고)와 I/O(도로망, 창구)를 통해 정보들을 모으고,\
> 계산해서 다시 내보내는 구조라고 생각하면 돼.

## 1. 메모리 : **메모리는 어떻게 쓰일까?**

* 메모리는 물리적으로 어떻게 구성되나
* 메모리 주소체계
  * 여러 bit를 어떤 식의 주소로 쓰나
  * 페이징할 때의 주소는 어떤 형식
  * 프로그램 -> 메모리로 주소가 어떻게 변환되나
* 메모리 할당
  * protection memory
  * 메모리를 할당하는 방법과 단편화 문제
  * 페이징 시 주소변환, 효율적인 변환방법들
  * 프로세스 context switching 때 페이징 관리 , demanding page보완
  * 페이징 교체 알고리즘

1\) 메모리는 물리적으로 어떻게 구성되나?

메모리(메인 메모리)는 ram(random access memory)이라고도 하며 디스크보다 빠른 속도의 i/o가 가능하지만 휘발되는 저장공간이다.\
(주로 d-ram이 사용됨)

아래와 같이 생겼으며, 저 칩 하나하나에 몇 억 개의 셀이 존재한다.

![](/files/Xw1ICGdBUymdc2UC8pLH)\
`셀`은 **캐패시터와 트랜지스터로 전하를 저장/제어하는 단위**이다.\
이 셀에 전하가 저장되있냐 아니냐로 0,1을 표현해 기계어를 저장한다.

#### 2) 메모리는 ‘정렬된 집들’이다

<figure><img src="/files/IHRSlieqVoUFpiPvfAqg" alt=""><figcaption></figcaption></figure>

컴퓨터에서 **메모리**는 마치 **일렬로 정렬된 집들** 같아.

* 각각의 ‘집’은 **고정된 크기(보통 1바이트)**&#xB97C; 가지고 있고 **정해진 개수만큼 비트를 저장할 수 있는 방**이 있는 거지
* 모든 집에는 <mark style="color:red;">주소(address)</mark>가 붙어 있어!

예를 들어 컴퓨터에 64MiB의 메모리가 있으면,\
주소는 `0번`부터 `67,108,863번`까지 차례대로 붙어 있는 거지.\
👉 이걸 우리는 **메모리 주소 공간**이라고 불러.

#### 3) 메모리는 바이트 단위로 나뉘지만, CPU는 더 크게 읽기도 해

컴퓨터는 실제로 1 바이트 하나만 읽기보다는, 더 큰 단위로 데이터를 다루는 경우가 많아:

* **32비트 시스템**은 보통 4바이트씩 (긴 워드)
* **64비트 시스템**은 8바이트씩 데이터를 다뤄

그 이유는?

> 🚗 도로에 비유하자면, **한 차선으로 가는 것보다 4차선, 8차선 도로**가 훨씬 빠르게 데이터를 전송할 수 있기 때문이야.

메모리를 주소로 지정할 때는 <mark style="color:blue;">어떤&#x20;대상(1바이트, 4바이트, 8바이트 등)을 원하는지 지정</mark>해야 한다

#### 4) 포플렉스(Pourplex) 구조로 생각해보자!

<figure><img src="/files/83sjV3T7KPecXOIWFEqc" alt=""><figcaption><p><a href="https://techblog-history-younghunjo1.tistory.com/507">https://techblog-history-younghunjo1.tistory.com/507</a></p></figcaption></figure>

> 바이트 → 듀플렉스(2바이트) → 포플렉스(4바이트) → 8바이트 구조까지…

이렇게 집을 묶어서 더 큰 단위의 블록으로 생각하면,\
CPU는 **한 번에 포플렉스(4바이트)** 단위로 메모리에 접근한다고 보면 돼!

* 32비트 컴퓨터의 메모리 구조를 포플렉스fourplex(집이 4개 연달아 붙어 있는 땅  \
  콩집)가 늘어서 있는 길로 생각할 수도 있어
  * 각 포플렉스에는 다시 `듀플렉스(집이 2개 붙어 있는     땅콩집)`가 **2개** 들어 있고, 각 듀플렉스에는 유닛(한 세대)이 2개 들어 있어.
  * &#x20;이 말은 우리가 각 유    **닛이나 듀플렉스 또는 건물 전체(포플렉스)의 주소를 지정할 수 있다**는 뜻

#### 5) 정렬된 접근 vs 정렬되지 않은 접근

<figure><img src="/files/WYscV22qaT6cLaF9eeHh" alt=""><figcaption></figcaption></figure>

메모리를 읽을 때도 규칙이 있어.

**📌 정렬된 접근 (Aligned Access)**

* 예: 4바이트 데이터를 주소 0번에서 읽는다 → 0,1,2,3번 바이트 딱 맞게 읽기
* 빠르고 효율적

**❌ 정렬되지 않은 접근 (Non-Aligned Access)**

* 예: 4바이트 데이터를 주소 5번부터 읽는다 → 5,6,7,8번 바이트인데 **건물 2개에 걸침**
* 비효율적 & 전통적으로 금지되는 경우가 많았음

> 📌 요약: **“정렬된 접근이 빠르고 안전하다”**&#xB294; 개념이 컴퓨터 설계의 중요한 원칙이야!

#### 🔄 5) 리틀 엔디안 vs 빅 엔디안

> 도심을 오가는 버스의 각 자리에는 누가 앉을까?  긴 단어의 경우 가장 왼쪽 자리에 0번 바이트> \
> 가 들어갈까, 3번 바이트가 들어갈까?

사용하는 프로세서에 따라 답이 달라져.

&#x20;두 방식이 모두 사용되기 때문에, 어느 방식을 사용하느냐는 프로세서를 설계하는 사람의 마음에 달렸음

<figure><img src="/files/W4eGJMvUrD0ugBUaAHoD" alt=""><figcaption></figcaption></figure>

큰 데이터를 여러 바이트에 나눠서 저장할 때, **순서를 어떻게 저장할까?**

<figure><img src="/files/2OVBiPNe6ttue2sI12L8" alt=""><figcaption></figcaption></figure>

**📌 리틀 엔디안 (Little Endian) – 인텔 방식**

* 인텔 프로세서
* **덜 중요한 바이트가 먼저**
* 예: `0x12345678` → 저장 순서: `78 56 34 12`

**📌 빅 엔디안 (Big Endian) – 모토로라 방식**

* 모토로라Motorola 프로  세서
* **더 중요한 바이트가 먼저**
* 예: `0x12345678` → 저장 순서: `12 34 56 78`

이 차이 때문에 시스템 간 데이터 전송 시에는 **엔디안 변환**을 신경 써야 해. . 엔디안을 무시하면 데이터 순서가 뒤섞일 수 있어.

> 유닉스를 다른 컴퓨터로 옮겼다가 `Unix`가 `nUxi`로 출력된 걸 "눅시 신드롬(Nuxi syndrome)"이라고 불러 😅

#### 🧭 6) 메모리 주소는 프로그램이 직접 지정하지 않는다!

우리가 코드를 작성할 때는 `int a = 12345;`처럼 변수 이름만 쓰잖아?\
근데 컴퓨터 입장에서는 **“a가 어디 있는지”** 알아야 해.

이 과정은 다음과 같이 진행돼:

| 단계                      | 설명                      |
| ----------------------- | ----------------------- |
| **Symbolic Address**    | 사람이 쓴 변수 이름 (예: `a`)    |
| **Relocatable Address** | 컴파일러가 만든 가상의 주소         |
| **Absolute Address**    | 링커가 배치한 프로그램 내의 실제 주소   |
| **Physical Address**    | 운영체제가 RAM 안에서 배정한 물리 주소 |

> 이 주소 변환을 실제로 수행하는 하드웨어가 **MMU (Memory Management Unit)**&#xC57C;!

<mark style="color:red;">프로그램은 0x00000000 ... 0xFFFFFFFF의 물리주소를 그대로 사용할까?</mark>\
**결론은 아니야**

```c
int a = 12345;
```

이는 실행되기 전까지는 디스크에 저장된 일종의 .exe파일일 뿐이야.

* 이게 실행될 때 a라는 변수가 메모리의 어느 주소에 있는지를 할당하는 것은 정해져있지 않아.(symbolic할 뿐이다)
* 이를 지정은 컴파일러가 수행
* 코드의 변수 a와 같은 **symbolic한 변수를 컴파일러가 메모리에 할당가능한 가상의 주소로 바꿔서 어셈블리어로 서술**하는 것야.
* 이 relocable한 주소는 **링커(linker)** 를 통해 absolute address가 정해지고, **로더(loader)**&#xB97C; 통해 프로세스가 물리적으로 어디부터 어디의 메모리 주소(physical address)를 사용할지 할당을 os의 커널을 통해 수행해

<figure><img src="/files/tev8MlMPQtFlL1f83Fcl" alt=""><figcaption></figcaption></figure>

> symblic address -> relocable address -> absolute address -> physical address

**즉 이 logical한 가상의 주소를 물리주소로 바꿔서 할당하고 접근하는 작업을 os가 수행하면서 주소변환,프로세스간의 메모리 보호 기능도 수행해**

#### 🛡️ 5) 메모리는 보호되어야 한다

> mmu(memory management unit)이라고 하는 물리적인 장치를 이용해 수행해

<figure><img src="/files/W62ycg1j5ztllLn4g3Qi" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/GMQ2PIfSkQQC2wyvO2Jt" alt=""><figcaption><p><a href="https://velog.io/@ttomy/%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%9D%98-%EC%A3%BC%EC%86%8C%EC%B2%B4%EA%B3%84%EC%99%80-%ED%8E%98%EC%9D%B4%EC%A7%80">https://velog.io/@ttomy/%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%9D%98-%EC%A3%BC%EC%86%8C%EC%B2%B4%EA%B3%84%EC%99%80-%ED%8E%98%EC%9D%B4%EC%A7%80</a></p></figcaption></figure>

한 도시 안에 여러 사람이 살아도 서로 **집 안에 무단 침입**하면 안 되잖아?\
컴퓨터도 마찬가지야!

* **메모리 보호 기능: 프로세스가 서로 별도의 메모리 공간을 사용해야하므로 이를 위한 제약을 구현해야 해**
* **프로세스 A가 프로세스 B의 메모리**를 건드리면 안 돼
* 이를 막기 위해 CPU는 **base register와 limit register**를 사용해
  * base: 시작 주소
  * limit: 사용 가능한 범위
  * &#x20;base register와 limit register를 통해 cpu에서 메모리로의 접근 범위를 제한해서 가능하게 해.

> 메모리 보호 기능의 핵심이야! (이 개념은 페이징에서도 다시 나와)

<figure><img src="/files/UTMfIYsxdaM41lcyOloG" alt=""><figcaption></figcaption></figure>

#### 📌 요약 정리

| 개념              | 설명                                 |
| --------------- | ---------------------------------- |
| **메모리 주소**      | 바이트 단위로 나열된 ‘집’에 붙은 번호             |
| **워드 단위 접근**    | 32비트 → 4바이트, 64비트 → 8바이트 단위        |
| **정렬된 접근**      | 메모리 블록 경계에 맞게 읽는 것 (속도 ↑)          |
| **리틀 vs 빅 엔디안** | 바이트를 저장하는 순서 차이                    |
| **주의사항**        | 다른 시스템과의 통신이나 네트워크 전송 시 엔디안 고려 필수! |

## 2. 입력과 출력

### 🧃 **컴퓨터는 왜 I/O가 필요할까?**

컴퓨터가 혼자 계산만 하고 외부와 소통하지 않으면 무슨 의미가 있을까?\
우리에게 필요한 건…

* 키보드, 마우스, 센서처럼 정보를 **입력하는 장치**
* 화면, 스피커, 프린터처럼 정보를 **출력하는 장치**

👉 이런 장치들을 통틀어 I/O Device (입출력 장치)라고 해!\
그리고 컴퓨터와 I/O 장치가 소통하는 방법을 **I/O (Input/Output)**&#xC774;라고 부르지.

이들은 컴퓨터의 주변부에 위치하기&#x20;때문에 주변장치peripheral device라고 부르며, 영어로는 퍼리퍼럴peripheral이라고 해.

### 🛣️ **I/O도 메모리처럼 '거리'가 있다!**

이제 도시 비유로 설명해볼게!

* 📍 도시 중심: CPU (도심이라고 했지!)
* 🏠 메모리 거리: RAM 주소들이 줄줄이 있는 구역
* 🏭 I/O 거리: 주변기기들이 위치한 별도의 산업 지구 같은 곳

과거엔 컴퓨터의 물리적 구조가 커서, 메모리 거리와 I/O 거리를 **분리**해서 관리했어.\
\&#xNAN;**→ 각자의 버스를 탔던 셈이지!**

그림 4-5처럼 이렇게 나눠져 있었지:

<figure><img src="/files/arkJfte7HDbtMv3qtGBM" alt=""><figcaption></figcaption></figure>

```
[도심]───[메모리 거리 버스]───[메모리들]
     └───[I/O 거리 버스]───[I/O 장치들]
```

과거에는 **메모리 거리에 집이 많지 않았기 때문에**, 제한된 주소를 I/O를 지원하느라고 낭비하는 일은 바람직하지 않았어!

### 💡 그런데 요즘은 왜 I/O도 메모리 거리 안에 들어오게 됐을까?

#### 이유는 바로… “주소 공간이 너무 많아졌기 때문”!

요즘 컴퓨터는 32비트, 64비트라서 주소 공간이 **엄청 넓어졌어.**\
그래서 메모리 거리에 `빈 집(주소)`이 많아졌고,\ <mark style="color:red;">그 빈 집 일부를</mark> <mark style="color:red;"></mark><mark style="color:red;">**I/O 장치에게 할당**</mark><mark style="color:red;">해버린 거야.</mark>

이걸 **Memory-Mapped I/O (메모리 매핑 입출력)**&#xC774;라고 해.

### 🧭 **Memory-Mapped I/O vs Port-Mapped I/O**

| 구분                    | 설명                                      | 비유                        |
| --------------------- | --------------------------------------- | ------------------------- |
| **Memory-Mapped I/O** | I/O 장치도 메모리처럼 주소를 가짐                    | 산업 지구를 도시 안에 포함시킨 느낌      |
| **Port-Mapped I/O**   | I/O 주소를 따로 관리 (예: x86의 `in`, `out` 명령어) | 메모리와 I/O를 아예 다른 거리로 구분한 것 |

요즘 대부분의 시스템은 메모리 맵 방식으로 설계돼. 효율적이니까!

### 🧱 **I/O 슬롯 = I/O를 꽂는 자리**

<figure><img src="/files/GioUWSYktTtsMYsxjnej" alt=""><figcaption></figcaption></figure>

* 컴퓨터에는 표준화된 `I/O 슬롯(slot)`이 있어.
* 이 슬롯마다 일정한 주소 범위가 할당되고,
* **그 범위 내에서만 해당 장치가 활동 가능**해!

> 마치 도시의 산업 구역을 “공장용”, “발전소용”처럼 구역을 나눈 셈이야.

### 🧩 정리하면!

1. **컴퓨터는 외부와 통신하기 위해 I/O 장치가 필요하다.**
2. 과거에는 I/O와 메모리 주소 공간을 분리했지만,
3. 요즘은 **메모리 공간에 I/O 장치를 포함**시켜 관리한다.
4. 이를 **Memory-Mapped I/O**라고 하며,
5. 표준화된 **I/O 슬롯** 구조를 통해 일관성 있게 연결된다.

## 3. 중앙 처리 장치(CPU)

<figure><img src="/files/jjz8tUszYDswrfppiyvX" alt=""><figcaption><p>CPU</p></figcaption></figure>

중앙 처리 장치(CPU)는 실제 계산을 처리하는 컴퓨터 부품이야. 우리가 사용하는 비유에

* CPU는 도심에 해당
* 다른 모든 요소 : CPU를 지원하는 역할

### 1️⃣ 산술 논리 장치(ALU) : 컴퓨터 도심 한가운데의 계산소

<figure><img src="/files/1iujd1brow2MCh5aHDem" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/UqS1Xg2YZzxVHtFqQIBW" alt=""><figcaption></figcaption></figure>

이제 우리가 드디어 도달한 곳은 바로 **CPU의 심장부**, 계산의 중심인 ALU(산술 논리 장치)야!

#### ✅ ALU란?

<figure><img src="/files/ngmN0jyhCoGuyz9JdwSu" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/yKyF7LS9Aar8tjofcWqC" alt=""><figcaption></figcaption></figure>

ALU(Arithmetic Logic Unit)는\
산술 연산(+, -, shift)과 논리 연산(AND, OR, XOR 등)을 담당하는 장치야.

🧮 쉽게 말해, **컴퓨터 안의 계산기**!\
→ 연산을 수행하는 건 이 ALU가 도맡아 해.

#### 🎯 ALU는 어떤 일을 할까?

ALU는 기본적으로 다음 3가지를 입력으로 받아:

<figure><img src="/files/T8p4eqsX7NIJjKSbDpwx" alt=""><figcaption></figcaption></figure>

1. **피연산자 A : 수를 표현하는 비트**
2. **피연산자 B**
3. **연산 코드 (opcode)**: 어떤 연산을 할지 지정, 연산자 지정

**A와 B는 어디서 오냐면?**

* 보통은 **레지스터**나 **메모리**에 저장된 값을 A와 B에 넣어서 계산해.
* CPU 내부에 있는 `레지스터들(R1, R2, ...)`에서 값을 꺼내서
  * A ← R1
  * B ← R2\
    이런 식으로 ALU에 넣는다고 보면 돼.

그리고 두 가지 출력을 만들어:

* **연산 결과 (result)**
* **조건 코드 (condition code) : 결과에 대한 추가 정보**
  * 보통 조건 코드 레지스터라는 레지스터에 조건 코드 저장

<figure><img src="/files/K0MCVuaExDb4re3GmLrp" alt=""><figcaption></figcaption></figure>

#### 🛠️ 연산 코드: ALU에게 내리는 명령

ALU는 연산 코드를 보고 어떤 연산을 수행할지 결정해.\
이건 마치 요리사에게 “볶아줘”, “끓여줘” 같은 지시를 내리는 것과 같아.

**예제 연산 코드들 (표 4-1 요약):**

| 명령코드   | 명령어 (니모닉) | 의미                        |
| ------ | --------- | ------------------------- |
| `0000` | `clr`     | 결과를 전부 0으로 (초기화)          |
| `0001` | `set`     | 결과를 전부 1로                 |
| `0010` | `not`     | A의 모든 비트 반전               |
| `0011` | `neg`     | A의 2의 보수 → -A             |
| `0100` | `shl`     | A를 B만큼 왼쪽으로 쉬프트           |
| `0101` | `shr`     | A를 B만큼 오른쪽으로 쉬프트          |
| `1000` | `load`    | B값을 결과로 복사                |
| `1001` | `and`     | A AND B                   |
| `1010` | `or`      | A OR B                    |
| `1011` | `xor`     | A XOR B                   |
| `1100` | `add`     | A + B                     |
| `1101` | `sub`     | A - B                     |
| `1110` | `cmp`     | B - A → 결과는 버리고 조건 코드만 설정 |

#### 📦 조건 코드 레지스터 (CCR, 그림 4-8)

ALU는 결과 외에도 “이 연산 결과가 어떤 특성을 가졌는지” 알려줘.\
이게 바로 **조건 코드**이고, **조건 코드 레지스터**에 저장돼.\
\
**레지스터는 일종의 메모리이긴 하지만 메모리 거리**에 존재하지 않는 메모리이야.\
**메모리 거리에 있는 일반적인 집**보다는 **더 특별하고 비싼 좋은 집**이라고 할 수 있어.

조건 코드 레지스터는 아래처럼 표현할 수 있다:

<figure><img src="/files/7evgMlvQgBVE3XqMAbVr" alt=""><figcaption><p><a href="https://techblog-history-younghunjo1.tistory.com/507">https://techblog-history-younghunjo1.tistory.com/507</a></p></figcaption></figure>

* 이 중 **오른쪽 3개 비트(2, 1, 0)**&#xAC00; 조건 코드 비트이다.
* 나머지 비트(3\~7)는 **사용하지 않는 비트**이다.

조건 코드 레지스터에서 조건을 표현하는 비트 설명:

* **N 비트**: 마지막으로 수행한 연산 결과가 **음수인 경우** 1로 설정된다.
* **Z 비트**: 마지막 연산 결과가 **0인 경우** 1로 설정된다.
* **O 비트**: 마지막 연산에서 \*\*오버플로(또는 언더플로)\*\*가 생긴 경우 1로 설정된다.

| 비트             | 의미       | 설명                 |
| -------------- | -------- | ------------------ |
| `N` (Negative) | 음수면 1    | 결과가 음수면 1          |
| `Z` (Zero)     | 0이면 1    | 결과가 0이면 1          |
| `O` (Overflow) | 오버플로 시 1 | 덧셈/뺄셈 중 자리 넘침 발생 시 |

💡 조건 코드는 `if`, `jump` 같은 조건 분기문에서 사용돼!

#### 🧩 그림 4-9: ALU 내부를 들여다보면?

사실 ALU는 그렇게 신비한 존재가 아니야.

> 그냥 **논리 게이트 + 셀렉터의 조합**일 뿐이야!

그림을 보면 이렇게 구성되어 있어:

* 명령코드에 따라 특정 회로만 활성화되고,
* 선택된 회로가 A, B에 대해 연산을 수행하고,
* 결과가 출력돼!

예를 들어:

* `clr`: 무조건 0을 출력하는 회로
* `not`: A를 반전시키는 회로
* `and`, `or`: A와 B를 논리 연산하는 회로

→ 이 중 하나만 선택돼서 **출력선으로 연결**되는 구조야!

#### 🧠 요약하면?

| 구성 요소     | 설명                             |
| --------- | ------------------------------ |
| 피연산자 A, B | 계산에 사용될 입력 비트                  |
| 연산 코드     | 어떤 연산을 할지 지정하는 코드              |
| 결과        | 연산 후 나오는 비트                    |
| 조건 코드     | 연산 결과에 대한 추가 정보 (음수? 0? 오버플로?) |
| ALU 내부    | 논리 게이트 + 셀렉터 조합                |

#### 🎓 Q\&A로 정리해볼까?

* **Q: ALU는 무슨 일을 해?**\
  A: 숫자 계산, 논리 연산 등 CPU가 계산해야 하는 모든 걸 수행해.
* **Q: 조건 코드 레지스터는 왜 필요해?**\
  A: 결과가 0인지, 음수인지, 오버플로가 났는지를 기록해서 분기문에 쓰기 위함이야!
* **Q: ALU가 신비해 보여. 복잡한 회로일까?**\
  A: 아니, 사실은 `AND`, `OR`, `NOT` 같은 논리 게이트를 잘 조합한 구조일 뿐이야!<br>

> 읽어 보면 좋은 자료 : <https://techblog-history-younghunjo1.tistory.com/507>

### 2️⃣ 시프트

> **시프트**는 말 그대로 **비트들을 좌우로 밀어내는 연산**이야.

#### ✅ 왼쪽 시프트 (Left Shift)

* 비트를 왼쪽으로 한 칸씩 밀고, 가장 오른쪽엔 `0`을 넣는다.
* 맨 왼쪽 비트(MSB)는 **버려진다**.
* **2의 배수 곱셈 효과**가 있다!

```plaintext
예시:
입력  : 01101001 (10진수 105)
왼쪽 시프트 → 11010010 (10진수 210)
```

#### ✅ 오른쪽 시프트 (Right Shift)

* 비트를 오른쪽으로 한 칸씩 밀고, 가장 왼쪽엔 `0`을 넣는다.
* 맨 오른쪽 비트(LSB)는 **버려진다**.
* **2로 나눈 몫(버림 나눗셈)**&#xC774; 된다.

```plaintext
예시:
입력  : 01101001 (10진수 105)
오른쪽 시프트 → 00110100 (10진수 52)
```

#### 💡 시프트 연산에서 중요한 것!

시프트하면서 **버려지는 비트**가 중요한 경우가 있어.

* 왼쪽 시프트 → `MSB (가장 왼쪽 비트)`가 버려짐
* 오른쪽 시프트 → `LSB (가장 오른쪽 비트)`가 버려짐
* <mark style="color:red;">이 비트들을</mark> <mark style="color:red;"></mark><mark style="color:red;">**O 비트(Overflow) 같은 조건 코드 레지스터에 저장**</mark><mark style="color:red;">해서 나중에 참조할 수 있어</mark>

#### 🧰 시프트 회로 구성 방식 2가지

#### 1) ⏱ 순차적 시프트 레지스터 (Shift Register)

<figure><img src="/files/I6L2ICQ9QBe7HxpQ3gdl" alt=""><figcaption></figcaption></figure>

* **한 번에 1비트씩**만 시프트 가능
* **클록 신호**가 있어야 작동 (순차적 구조)
* 여러 비트를 시프트하려면 시간이 많이 걸림

```
[ Q D ]─[ Q D ]─[ Q D ] ...
입력은 D, 출력은 Q, 클록을 타고 한 칸씩 이동
```

#### 2) ⚡ 조합 논리 기반 배럴 시프터 (Barrel Shifter)

* **조합 회로**로 즉시 결과를 산출 (훨씬 빠름!)
* <mark style="color:red;">여러 비트를 한 번에 시프트 가능</mark>
* 입력 비트마다 8:1 실렉터(MUX)를 연결해 구현

  <figure><img src="/files/id6DF6plwwtwKdC4MYOK" alt=""><figcaption></figcaption></figure>

```plaintext
입력: I0~I7
출력: O0~O7
선택 신호: S0, S1, S2 → 몇 비트를 오른쪽으로 시프트할지 결정
```

* 실렉터 8개를 병렬로 배치한 구조
* 입력에 따라 바로 **0\~7비트**까지 시프트된 결과를 만들어냄

#### ✨ 응용

* 시프트 연산은 **간단한 곱셈/나눗셈**을 빠르게 처리할 수 있는 방법이야!
* 특히 **부동소수점 연산**에서도 소수점을 정렬할 때 꼭 필요해.
* 곱셈기도 시프트 + 덧셈 조합으로 만들 수 있어!

#### 🧩 요약 정리

| 개념      | 설명                          |
| ------- | --------------------------- |
| 왼쪽 시프트  | 2의 곱 (LSB에 0 삽입, MSB 버림)    |
| 오른쪽 시프트 | 2로 나눈 몫 (MSB에 0 삽입, LSB 버림) |
| 순차 시프터  | 클록 따라 1비트씩 밀기               |
| 배럴 시프터  | 실렉터 조합으로 여러 비트 즉시 시프트       |
| 조건 코드   | 시프트 중 버려지는 비트는 O 비트에 저장 가능  |

방금 본 간단한 **ALU**에 곱셈과 나눗셈 연산이 없어. 그 이유는 이런 연산이 더 복잡하고, <mark style="color:blue;">실제로 새로운 내용을 알려주지도 않기 때문</mark>이야.&#x20;

* 곱셈은 덧셈  을 반복하면 되고, 이 방식을 택하면 순차 논리로 곱셈을 구현할 수 있어.&#x20;
* 또는 `왼쪽 시프트`는  어떤 수를 2로 곱하는 것과 같다는 점에 착안해, 배럴 시프터와 가산기를 조합하는 조합 논리 곱  셈기를 만들 수도 있지 <br>

### 3️⃣ ALU에게 명령을 수행하는 주체 : 실행장치

🧠 **실행 장치 (Execution Unit, Control Unit)는 컴퓨터의 '대장'**

ALU는 계산을 해주는 똑똑한 도구지만, **혼자선 아무것도 못 해.**\
ALU가 뭘 계산해야 할지 알려주는 존재, 바로 **실행 장치 (또는 제어 장치)**&#xAC00; 필요해.

> 실행 장치는 ALU에게 “얘랑 얘 더해봐!”라고 명령하고,\
> 계산 결과를 다시 메모리에 잘 넣어주는 **감독관** 같은 역할이야.

실행 장치는 `메모리`에서 정해진 장소로부터 **명령코드와 피연산자들을 가져와서 ALU에게 어떤 연산을 수행할지 알려주고**, ALU가 연산을 수행한 결과를 내놓으면 이것을 `메모리`로 돌려줌

#### 🧾 **명령어란?**

실행장치가 위의 일을 할 수 있는 이유는?

실행 장치는 스스로 생각하지 않아. 대신, **명령어(instruction)**&#xB77C;는 쪽지를 하나하나 읽고\
그 지시에 따라 움직이는 거야. 예를 들면:

> "주소 10에 있는 값을, 주소 12에 있는 값과 더해서, 주소 14에 저장해!"

이런 쪽지가 바로 **명령어**야.\
그리고 이 명령어들은 **메모리 안에 저장돼 있어.특정 주소 안에 저장되어 있지.**

#### 🧠 **Stored-Program Computer = 프로그램 저장 방식 컴퓨터**

이 구조를 **Stored-program computer**라고 해.\
즉, **프로그램(=명령어 모음)을 메모리에 저장하고**,\
**CPU가 한 줄씩 읽어가며 실행**하는 방식이야.

* 이 개념의 시초는 **앨런 튜링**이고,
* 이 아이디어를 **전자식 컴퓨터에 적용한 사람은 존 폰 노이만**이야.
* 그래서 지금 우리가 쓰는 컴퓨터 구조를 **폰 노이만 구조**라고 불러!

#### 🧭 **프로그램 카운터 (Program Counter, PC) = 현재 읽을 쪽지 위치**

실행 장치가 명령어를 어떻게 읽냐고?

`프로그램 카운터(PC)`라는 레지스터가 **현재 다음 읽을 명령어의 주소**를 가리키고 있어.\
CPU는 PC가 가리키는 메모리 주소에서 명령어를 가져오고,\
**명령어 하나를 읽고 나면 PC를 자동으로 증가시켜서 다음 명령어를 준비**하는 거야.

> 🧭 비유하자면:\
> 컴퓨터는 ‘보물찾기’하듯 명령어 쪽지를 차례차례 따라가며 일을 수행해.

#### 🧱 프로그램 카운터의 특징

<figure><img src="/files/dqanuiM8ozRsLaLIdtDv" alt=""><figcaption></figcaption></figure>

* PC도 **레지스터**다 (특별한 기억 장소).
* 프로그램 카운터는 메모리 위치를 가르키며 참조라고 해
  * 실행장치는 프로그램 카운터가 가리키는 주소에서 명령어를 읽어와
  * CPU마다 정해진 초기 프로그램 카운터 값이 있고 CPU 전원이 들어오면 PC는 이 값으로 설정돼
* 내부적으로는 **카운터 회로**로 되어 있어서,\
  명령어를 하나 실행할 때마다 +1 증가되는 구조야.
* 특수한 명령어(JMP, CALL 등)가 PC 값을 바꾸면 **프로그램 흐름을 바꿀 수도 있어.**

#### 💬 정리하면!

| 구성 요소         | 역할                              |
| ------------- | ------------------------------- |
| ALU           | 계산, 논리 연산                       |
| 실행 장치 (제어 장치) | ALU에게 일을 시키고, 명령어를 읽고, 실행 흐름 제어 |
| 명령어           | "무엇을 어떻게 할지"를 적은 쪽지             |
| 메모리           | 명령어들이 저장된 장소                    |
| 프로그램 카운터(PC)  | 다음 읽을 명령어의 주소를 저장하는 레지스터        |

## 4. 명령어 집합

컴퓨터가 보물찾기를 하는 중에 메모리에서 찾는 쪽지를 `명령어`라고 불러

#### 📍 1. 메모리 주소 공간이 부족해지면 어떻게 할까?

컴퓨터가 사용하는 주소는 **비트 수로 표현**돼.\
예를 들어, 32비트 주소는 \*\*최대 4GiB(2³²)\*\*까지만 메모리를 쓸 수 있어.

그런데 컴퓨터 성능이 좋아지면서 4GiB보다 훨씬 많은 메모리를 다뤄야 하는 상황이 생겼어.\
**그러면 주소가 부족하지 않을까?** 그렇지! 그래서 **해결책**이 필요했어.

### 1️⃣ 명령어 : 컴퓨터에게 주는 '작업 지시서'

&#x20;**CPU에서 명령어를 어떻게 구성하고 처리하는지**, 그리고 **왜 명령어를 단순화해야 하는지**에

컴퓨터가 무언가를 계산하려면, 사람처럼 명확한 지시가 필요해.\
바로 **명령어(instruction)**&#xB77C;는 형태로 말이야.

#### 📦 **명령어는 보통 다음과 같은 정보들을 포함해**

| 필드     | 설명                        |
| ------ | ------------------------- |
| 명령코드   | 어떤 연산을 할 건지 (ex. 더하기, 빼기) |
| 피연산자 A | 연산 대상이 되는 첫 번째 값의 위치      |
| 피연산자 B | 연산 대상이 되는 두 번째 값의 위치      |
| 결과     | 결과를 저장할 위치                |

그래서 초기에는 다음과 같은 **3주소 명령어 구조**를 썼어:

<figure><img src="/files/T19J3hIf0NlgGQ9y141V" alt=""><figcaption></figcaption></figure>

#### ❗️ 그런데 이 필드를 나누는  구조는 현실적으로 문제가 있어!

1. **주소 공간이 너무 작다**
   * 4비트로 표현하면 한 필드당 **16개의 주소밖에 못 씀**
   * 요즘 컴퓨터는 수십\~수백 기가 메모리를 쓰기 때문에 **주소 부족 현상 발생**
2. <mark style="color:red;">**3개의 메모리 위치에 동시에 접근해야 함**</mark>
   * CPU는 **주소 버스와 데이터 버스가 하나씩만 있어**
   * 한 번에 하나의 메모리 위치에만 접근할 수 있어
3. **하드웨어 구현이 복잡해지고 느려짐**
   * **하드웨어 구조가 비현실적** → 동시에 3개의 메모리 주소에 접근하려면
     * **주소 버스 3개**
     * **데이터 버스 3개**
     * **입출력 핀 수 3배** 이걸 CPU가 다 감당할 수 없어
   * **결국 같은 핀을 나눠 써야 하고, 동작은 순차적으로 해야 함**\
     → 그러면 굳이 3주소 명령어를 쓸 필요가 없어짐

#### 🧰 또  다른 해결책: **주소 확장 레지스터 (Address Extension Register)**

> 상위 주소 비트를 따로 보관해두는 레지스터

#### 예시: PAE (Physical Address Extension)

* 인텔이 32비트 CPU에서 4GiB 이상 메모리를 다루기 위해 사용한 기법
* **추가적인 명령어**로 상위 주소 비트를 먼저 설정하고,\
  그 후 기존 명령어로 메모리에 접근함
* 동적 RAM인DRAM 주소 지정 기법을 활용하는 것이다

#### 장점

* 기존 32비트 명령어 구조를 바꾸지 않고도 **64GiB 이상 접근 가능**

#### 단점

* **주소 확장 값을 레지스터에 넣는 데 시간 소모**
* **메모리 접근이 여러 번의 작업으로 분할됨 → 복잡하고 느림**

#### ❌ 즉, 3주소 명령어는 왜 실용적이지 않을까?

#### ✅ 먼저, 3주소 명령어란?

* 명령어 안에 **피연산자 A, B, 그리고 결과 주소**까지 3개의 메모리 주소가 들어 있는 구조야.

#### ❗️문제점

<figure><img src="/files/QBmggzWKBiE1mfadLCRi" alt=""><figcaption></figcaption></figure>

&#x20;<mark style="color:blue;">세 가지의 메모리 블록</mark>은 각기 다른 장치에 존재하는데 이렇게 하면 **하나의 명령어 수행하는데도 회로가 복잡해짐...**

#### 🔁 그래서 대안은? → 누산기(Accumulator) 구조 사용!

> 피연산자 A와 누산기에 있는 값을 연산해서, 다시 누산기에 결과를 넣는 방식!

<mark style="color:red;">한 번에 한 메모리 위체에만 접근할 수 있다는 사실</mark>에 맞춰 레지스터 거리에 **누산기라는 레지스터를 하나 추가**하는 시도를 하는 거야

#### 🏗 누산기란?

ALU 옆에 붙어 있는 **특별한 레지스터 하나**야.\
계산의 결과를 잠깐 담아두고, 다음 연산에 이어서 쓰는 **작은 창고** 같은 느낌이지!

누산기는 ALU가 계산한 결과를 저장해. 우리는 두 메모리 위치에 있는 값 사이에 연산을 수행해서 결과를 다른 메모리에 넣는&#x20; 대신, **한 메모리 위치에 있는 값과 누산기에 있는 값에 대해 연산을 수행하고 결과를 누산기에&#x20;넣어.**

물론 이 과정에서 누산기에 있는 값을 메모리에 저장하기 위한 `저장 명령어`를 추가해줘야 해

#### 🧱 **1주소 명령어 구조의 장점**

<figure><img src="/files/PXDMJBTtZHIbo0xShRI4" alt=""><figcaption></figcaption></figure>

* 단 하나의 주소만 지정하면 됨
* 나머지는 누산기가 알아서 처리
* 하드웨어가 단순해지고, 주소 공간을 훨씬 넓게 쓸 수 있어!

💡 예제 비교

#### 예시: `C = A + B` 계산

| 구조          | 명령어 시퀀스                                     |
| ----------- | ------------------------------------------- |
| **3주소 명령어** | `C = A + B` ← 한 줄로 끝                        |
| **1주소 명령어** | `누산기 = A` `누산기 = 누산기 + B` `C = 누산기` ← 3줄 필요 |

이러면 더 비효율적인 것처럼 보이지만…!

#### 예시: `D = A + B + C` 처럼 피연산자가 3개 이상일 경우

> 네 가지 주소가 연관되기 때문에, 3주소 명령어를 사용해도 <mark style="color:red;">이 식을 한 명령어로 처리할 수 없> 어.</mark>

<table><thead><tr><th width="82.4444580078125">구조</th><th>명령어 시퀀스</th><th>총 명령어 비트 수 (예시)</th><th width="107.4444580078125">좀 더 자세히</th><th width="116.7777099609375">비트</th><th>계산법</th></tr></thead><tbody><tr><td><strong>3주소</strong></td><td>중간값 = A + B D = 중간값 + C</td><td>2개 명령어 × 40비트 = 80비트</td><td>세 가지 주소와 명령코드를 저장하기 위해</td><td>피연산자  A 주소 :  12비트<br>피연산자 B 주소 : 12비트<br>결과 주소 : 12비트<br>명령 코드 :  4비트</td><td><p></p><ul><li>주소 하나 지정하는 데 12비트 필요</li><li>피연산자 2개 + 결과 1개 = <strong>12비트 × 3</strong></li><li>명령코드는 예를 들어 4비트 정도 필요</li><li><strong>총합 = 36 + 4 = 40비트</strong>가 되는 거지!</li></ul></td></tr><tr><td><strong>1주소</strong></td><td>누산기 = A 누산기 = 누산기 + B 누산기 = 누산기 + C D = 누산기</td><td>4개 명령어 × 16비트(1주소 명령어비트 수) = 64비트</td><td>4개의 명령어</td><td>명령코드 : 4비트<br>주소 : 12비트</td><td><p></p><p><strong>1주소 명령어 방식 (16비트짜리 명령어 4개 필요)</strong></p><ol><li><code>LOAD A</code></li><li><code>ADD B</code></li><li><code>ADD C</code></li><li><code>STORE D</code><br>→ 명령어 4개, 총 <strong>64비트</strong></li></ol></td></tr></tbody></table>

➡️ 오히려 **1주소 명령어가 더 효율적일 수 있음!**

#### 📌 마무리 요약

1. **명령어는 명령코드 + 피연산자 + 결과로 구성됨**
2. 3주소 구조는 표현은 간단하지만 **주소 제한 & 하드웨어 복잡성 문제** 있음
3. **1주소 구조 + 누산기 레지스터**를 쓰면 더 많은 주소를 활용 가능
4. 실제 명령어 실행은 **1줄을 여러 단계로 나눠 수행**하고,\
   프로그램 카운터와 함께 쭉 실행됨
5. <mark style="color:red;">누진기는 왜 피연산자가 필요없는걸까?</mark>
   1. **명령어에 모든 피연산자를 다 안 써도 되도록 만들어진 구조**
   2. &#x20;**1주소 명령어 시스템에서는** 명령어 안에 **피연산자 하나만 지정**해.
      1. <pre><code><strong>ADD B   ← 누산기 + B를 계산함!
         </strong></code></pre>
   3. 바로 **나머지 하나의 피연산자는 항상 ‘누산기’에 있기 때문이야.**

      즉, 연산은 이렇게 되는 거지:

      ```
      ALU 결과 = 누산기 값 + 주소 B의 값
      ```

      여기서:

      * 누산기 = **항상 존재하는 내부 피연산자**
      * 주소 B의 값 = **명령어로 지정된 외부 피연산자**

### 2️⃣ 주소 지정 모드

> 이건 "명령어 안에 들어 있는 피연산자 정보를 **어떻게 해석해서 데이터를 가져올지**"를 정하는 방식

#### 🧠 주소 지정 방식이란?

CPU가 명령어를 읽고 실행할 때,\
\&#xNAN;**"어디서 값을 가져올까?"** 또는 "값을 어떻게 해석할까?"를 결정하는 방식이야.

```plaintext
LOAD 12
```

위처럼 명령어에 `12`라는 숫자가 들어 있을 때,\
이걸 **어떻게 해석하느냐에 따라 결과가 달라져!**

#### 🔧 대표적인 주소 지정 방식 3가지

<figure><img src="/files/mep0p3InAIfpWwJT5RM5" alt=""><figcaption></figcaption></figure>

| 모드 이름                    | 해석 방식                                    | 설명                  | 예시 (LOAD 12)                                 |
| ------------------------ | ---------------------------------------- | ------------------- | -------------------------------------------- |
| **직접 주소 지정 (Direct)**    | 주소 `12`에 있는 값을 읽는다                       | 명령어에 적힌 숫자를 주소로 사용함 | 주소 12의 값인 `4321`을 로드                         |
| **간접 주소 지정 (Indirect)**  | 주소 `12`에 있는 값을 주소로 해석해서, 그 주소에 있는 값을 읽는다 | 주소 안에 주소가 들어 있는 방식  | 주소 12의 값이 `4321`, 4321의 값이 `345` → `345`를 로드 |
| **즉시 주소 지정 (Immediate)** | 명령어에 있는 숫자를 그대로 값으로 사용                   | 주소 아님, 그냥 '값'       | `12`를 그대로 로드                                 |

#### 1) 직접 주소 지정 (Direct Addressing)

```
[명령코드][345]
         ↓
     메모리[345] → 12
                ↓
         누산기에 12
```

* **의미**: 명령어 안에 *직접* 사용할 **메모리 주소**가 포함되어 있어.
* **예시**: `load 12` → 메모리 주소 12에 있는 값을 가져와서 누산기에 넣는다.
  * 명령어에 들어있는 **숫자(345)는 주소야!**
  * 즉, **"메모리 345번지를 열어봐!"**
  * 그 주소 안에 들어 있는 값을 **누산기에 넣는** 방식이야.
* **특징**:
  * 직관적이고 빠르지만
  * 명령어 비트 수가 한정돼 있기 때문에 **주소 공간이 제한됨** (ex. 12비트면 0\~4095까지만 가능)

#### 2) 간접 주소 지정 (Indirect Addressing)

```
[명령코드][4321]
         ↓
     메모리[4321] → 345
                   ↓
             메모리[345] → 12
                            ↓
                    누산기에 12

```

* **의미**: 명령어 안의 값은 실제 주소가 아니라, **진짜 주소를 담고 있는 메모리 주소**를 의미해.
* **예시**:
  * `load @12` (12는 간접 주소)
  * 메모리 주소 12에 저장된 값이 **4,321**이라면
  * 최종적으로 <mark style="color:red;">**메모리 주소 4,321에 있는 값**</mark><mark style="color:red;">을 누산기에 가져와</mark>
    * 명령어에 들어있는 \*\*숫자(4321)\*\*는 **주소를 가리키는 주소야!**
    * 즉, **"메모리 4321번지를 열어봐!" → 거기엔 345라는 값**
    * 그 다음, **345번지를 열어봐! → 거기엔 12**
    * 그래서 누산기에는 최종적으로 **12**가 들어가!
* **비유**:
  * “<mark style="color:red;">열쇠가 있는 상자의 위치(12번)</mark>”를 알려주고
  * 상자를 열면 **진짜 금고(주소 4321)**&#xC758; 위치가 나오는 느낌.
* **장점**:
  * 메모리 공간을 훨씬 많이 사용할 수 있음
* **단점**:
  * 메모리에 **세 번 접근해야 하므로 느림**\
    (명령어 읽기 + 주소 얻기 + 값 읽기)

#### 3) 즉시 주소 지정 (Immediate Addressing)

```
[명령코드][12]
         ↓
      누산기에 12

```

* **의미**: 명령어 안의 값이 **주소가 아니라 값 자체(상수)**&#xC57C;!
* **예시**: `load #12` → 숫자 12 자체를 누산기에 넣는다.
  * 이때는 **메모리를 접근하지 않고**, 명령어에 있는 숫자 자체를 쓰는 거야.
  * 명령어 안의 값(12)을 **그냥 값 자체로 해석**해!
* **특징**:
  * 메모리를 거치지 않기 때문에 **가장 빠름**
  * 상수 값을 다룰 때 유용

#### 🔁 핵심 정리

| 주소 지정 방식 | 해석 방식            | 예: 명령어 안에 12가 있으면          |
| -------- | ---------------- | -------------------------- |
| 즉시       | 12는 값 자체         | 누산기에 12 저장                 |
| 직접       | 12는 메모리 주소       | 메모리\[12]의 값을 누산기에 저장       |
| 간접       | 12는 주소를 담고 있는 주소 | 메모리\[메모리\[12]]의 값을 누산기에 저장 |

> 🧠 **비유로 정리하자면:**

* 즉시: “쪽지에 있는 번호가 답이야!”
* 직접: “쪽지에 적힌 집 주소로 가면 답이 있어!”
* 간접: “쪽지에 적힌 집 주소에 또 다른 주소가 있고, 거기로 가야 진짜 답이 있어!"

#### ⚡ 속도 비교

메모리에 몇 번 접근하느냐에 따라 속도가 달라져!

| 방식 | 특징             | 장점                        | 단점          | 접근 횟수                  |
| -- | -------------- | ------------------------- | ----------- | ---------------------- |
| 직접 | 주소를 명령어에 직접 포함 | <p>간단하고 빠름<br>⭐ 가장 빠름</p> | 주소 공간 제한    | 1번 (명령어만 읽음)           |
| 간접 | 주소를 담은 주소 사용   | <p>주소 공간 확장 가능<br>보통</p>  | 느림 (3단계 접근) | 2번 (명령어 + 데이터)         |
| 즉시 | 값 자체를 명령어에 포함  | <p>빠름<br>⛔ 가장 느림</p>      | 값 크기에 제한    | 3번 (명령어 + 주소 해석 + 데이터) |

#### 🧩 이걸 왜 쓰냐면?

각 방식은 상황에 따라 유용해:

* **즉시 모드**: 상수 값 (ex: `MOV A, 10`)
* **직접 모드**: 일반적인 변수 접근
* **간접 모드**: 포인터처럼 간접 참조할 때 (동적 구조 등)

#### 🔖 정리하면!

* **주소 지정 방식은 "값을 어디서 읽을지 결정하는 규칙"이다.**
* 명령어에 숫자 하나가 있어도, **해석 방식에 따라 완전히 다른 결과**를 낸다.
* 각각의 모드는 **성능**, **유연성**, **사용 목적**이 다르다.

### 3️⃣ 조건 코드 명령어

#### 조건 코드 레지스터를 직접 다루는 명령어

우리는 앞에서 **덧셈(add), 뺄셈(sub), 비교(cmp)** 등을 수행하면\ <mark style="color:red;">조건 코드 레지스터(N, Z, O)가 자동으로 설정</mark>된다는 걸 배웠어.

하지만 때때로 **프로그래머가 직접 조건 코드를 보고, 바꾸고** 싶을 때가 있어.\
그럴 때 필요한 명령어가 바로 이거야:

#### &#x20;1. `cca` (Condition Code to Accumulator)

* **조건 코드 레지스터 → 누산기(A 레지스터)** 로 복사하는 명령어
* 즉, **N, Z, O 비트를 읽어서 누산기에 담는다.**
* 조건 분기 전 확인용으로 자주 쓰일 수 있음!

> 📌 예: 조건 코드가 `N=1, Z=0, O=0`이면\
> `cca` 실행 후 누산기에는 `100` (즉, 4) 가 들어감.

#### 2. `acc` (Accumulator to Condition Code)

* **누산기 → 조건 코드 레지스터**로 복사하는 명령어
* 즉, 누산기 안의 값을 조건 코드 레지스터의 **N, Z, O 비트**에 복사하는 거야.
* 조건 코드 테스트를 흉내내거나 강제 조건 설정할 때 사용 가능!

> 📌 예: 누산기에 `001`이 들어있었다면\
> `acc` 실행 후 조건 코드는 `N=0, Z=0, O=1`

#### 🔁 간단 요약표

| 명령어   | 설명               | 비고          |
| ----- | ---------------- | ----------- |
| `cca` | 조건 코드 값을 누산기로 복사 | 조건 코드 확인용   |
| `acc` | 누산기 값을 조건 코드로 복사 | 조건 코드 강제 설정 |

### 4️⃣ 분기 명령어

이제 CPU가 **조건에 따라 프로그램의 흐름을 바꾸는 방법**,\
즉 **"분기(branch)"** 에 대해 배우고 있어!

#### 🧭 분기 명령어란?

지금까지의 CPU는 명령어를 **무조건 순서대로** 실행했어.\
하지만 현실의 프로그램은 항상 이렇지 않지?

* 조건에 따라 다른 동작을 하기도 하고
* 같은 명령어를 반복해서 실행하기도 해!

👉 이런 걸 가능하게 하는 게 바로 **분기(branch) 명령어**야!

#### 분기 명령어의 핵심

**분기란?**\
➡ 현재 실행 중인 명령어 다음으로 가지 않고\
➡ **다른 메모리 주소로 "점프"하는 것**

이걸 하려면 CPU 안에 있는 **프로그램 카운터(PC)**&#xC758; 값을 바꿔야 해!\
그 역할을 하는 명령어들이 바로 **분기 명령어**들이야.

#### 🔁 분기 명령어 종류 (표 4-2 기준)

> 분기 명령어에는 조건을 저장하기 위한 비> 트

| 코드  | 명령어   | 조건 코드 검사 | 분기 조건                    |
| --- | ----- | -------- | ------------------------ |
| 000 | `bra` | 검사 안 함   | 항상 분기                    |
| 001 | `bov` | O (오버플로) | O = 1일 때                 |
| 010 | `beq` | Z (Zero) | Z = 1일 때                 |
| 011 | `bne` | Z        | Z = 0일 때                 |
| 100 | `blt` | N, Z     | N = 1 AND Z = 0 (음수)     |
| 101 | `ble` | N, Z     | N = 1 OR Z = 1 (음수 또는 0) |
| 110 | `bgt` | N, Z     | N = 0 AND Z = 0 (양수)     |
| 111 | `bge` | N, Z     | N = 0 OR Z = 1 (0 또는 양수) |

> 🤔 **왜 조건 코드(Z, N, O)를 보는 걸까?**\ <mark style="color:red;">바로 이전에 했던 연산의 결과(덧셈, 뺄셈 등)를 기반으로 분기할 수 있기 때문</mark>이야!

#### 🧩 PC를 직접 다루는 명령어

CPU가 흐름을 바꾸는 또 다른 방법은\
**프로그램 카운터 자체를 직접 조작**하는 거야.

| 명령어   | 설명                         |
| ----- | -------------------------- |
| `pca` | **현재 PC 값을 누산기(A)에 복사**    |
| `apc` | **누산기 값을 PC로 설정** (점프 효과!) |

> 📌 예를 들어, `pca`는 “지금 어디 실행 중이야”를 알게 해주고\
> `apc`는 “지금부터 이 주소로 가!” 라는 직접적인 점프 명령이야.

#### ✨ 요약

* **분기 명령어는 프로그램 흐름을 바꾼다.**
* 조건 코드(`N`, `Z`, `O`) 값을 보고 조건에 따라 점프함
* `bra`는 항상 점프하는 무조건 분기
* `pca` / `apc`는 PC(프로그램 카운터)를 직접 다루는 명령어

### 5️⃣ 최종 명령어 구성

<figure><img src="/files/ZmCAgJJfIB2wVS62T2K2" alt=""><figcaption></figcaption></figure>

* 위에서 ‘**모드**’는 2비트를 추가적으로 차지한다.
* 이때의 모드는 위에서 배운 \*주소 지정 모드’이다.
* 2비트는 4가지로 표현이 가능하고,\
  이 중 3가지는 위에서 배운 **즉시 / 직접 / 간접 주소 지정 모드**이다.
* 마지막으로 표현할 수 있는 1가지 방식이 남는데,\
  이는 **메모리와 관계없는 연산**을 표현한다고 한다.

| 비트 범위  | 이름              | 설명                  |
| ------ | --------------- | ------------------- |
| 15\~14 | 주소 지정 모드 (2비트)  | 어떤 방식으로 주소를 사용할지 선택 |
| 13\~10 | 명령어 코드 (4비트)    | 무슨 연산을 할지 결정        |
| 9\~0   | 주소 or 상수 (10비트) | 주소 값 또는 상수 값        |

#### 🧭 주소 지정 모드 3가지&#x20;

| 주소 지정 모드     | 값    | 의미                               |
| ------------ | ---- | -------------------------------- |
| 직접 Direct    | `00` | 명령어에 들어있는 주소에서 데이터를 직접 읽음        |
| 간접 Indirect  | `01` | 명령어의 주소에 들어있는 값이 **진짜 주소**       |
| 즉시 Immediate | `10` | 주소 칸에 있는 값을 **그냥 상수로** 사용        |
| 없음 None      | `11` | 주소가 필요 없는 명령어 (ex: `set`, `shl`) |

#### 🧠 명령코드 정리 (표 4-3)

| 명령어 코드         | 의미      | 모드 00 | 모드 01 | 모드 10 | 모드 11 (주소 없음)      |
| -------------- | ------- | ----- | ----- | ----- | ------------------ |
| `0000`         | `load`  | ✅     | ✅     | ✅     | ❌                  |
| `0001`         | `and`   | ✅     | ✅     | ✅     | ❌                  |
| `0010`         | `or`    | ✅     | ✅     | ✅     | ❌                  |
| `0011`         | `xor`   | ✅     | ✅     | ✅     | ❌                  |
| `0100`         | `add`   | ✅     | ✅     | ✅     | ❌                  |
| `0101`         | `sub`   | ✅     | ✅     | ✅     | ❌                  |
| `0110`         | `cmp`   | ✅     | ✅     | ✅     | ❌                  |
| `0111`         | `store` | ✅     | ✅     | ❌     | `cca` (조건코드 → 누산기) |
| `1000`\~`1111` | `분기 명령` | ✅     | ✅     | ✅     | `apc`, `pca` 등     |

> 모드 11은 주소 없이 작동하는 ALU나 분기 보조 명령들을 포함!

#### 🔁 시프트 연산 (왼쪽/오른쪽)

시프트 명령은 `shl`, `shr`이고,\
**주소 필드(하위 10비트)를 시프트할 비트 수로 사용**해.

예: `shl 3`이면 3비트 왼쪽 시프트한다는 뜻

#### 🎯 정리하면

* 이제 **주소 지정 모드 + 명령어 코드**로 굉장히 유연한 구조가 됐어!
* 간단한 연산부터 조건 분기, 반복문까지 모두 구현 가능!
* ALU, 누산기, 프로그램 카운터 등 CPU 구조를 조합해서 "진짜 프로그램"을 만들 수 있게 됨

## 5. 마지막 설계

### 1️⃣ 명령어 레지스터

<figure><img src="/files/ScxbGgmie56yqsZWMS5V" alt=""><figcaption></figcaption></figure>

**컴퓨터가 명령어를 실제로 실행할 때 내부에서 어떤 일이 벌어지는지** 설명하고 있어.\
많이 들었던 ‘**페치-실행 사이클(Fetch-Execute Cycle)**’

#### 🧠 **컴퓨터는 명령어를 어떻게 실행할까?**

혹시 이렇게 생각하지 않았어?

> “컴퓨터는 프로그램을 그냥 한 줄씩 착착 실행하겠지?”

실제로는 **훨씬 더 복잡한 과정**이 내부에서 진행돼.\
이걸 한마디로 요약하면 바로:

#### 👉 **“페치-실행 사이클 (Fetch-Execute Cycle)”**

#### 🌀 **페치(Fetch)** 단계:

* `프로그램 카운터(PC)`가 가리키는 메모리 주소에 접근해서\
  **명령어를 하나 가져온다.**

#### ⚙️ **실행(Execute)** 단계:

* 가져온 명령어를 해석하고,\
  ALU나 메모리를 이용해 **해당 명령을 실행**한다.

> 이 두 단계가 계속 반복되면서 프로그램이 한 줄 한 줄 실행돼!

#### 💾 **명령어는 어디에 저장될까?**

명령어를 실행하려면 **잠시 기억해 둘 공간**이 필요해.\
왜냐하면 실행 도중에도 **메모리를 다시 접근**해야 할 수도 있으니까.

그래서 등장한 장치가 바로:

#### 📍 **명령어 레지스터 (IR, Instruction Register)**

* **현재 실행 중인 명령어**를 저장해 두는 곳이야.
* PC가 명령어를 메모리에서 가져오면,\
  그 명령어는 명령어 레지스터(IR)에 저장돼.
* 그리고 CPU는 **IR에 저장된 명령어**를 해석해서 실행해!

```
[프로그램 카운터(PC)] ──> 주소 버스 ──> [메모리] ──> 데이터 버스 ──> [명령어 레지스터(IR)]
```

* PC는 **다음 실행할 명령어의 주소**를 가지고 있고,
* 그 주소를 따라가서 **메모리에서 명령어를 읽고**,
* 읽어온 명령어를 **IR에 저장한 후 실행**하는 구조야!

이제 진짜 컴퓨터가 명령어를 한 줄씩 실행할 때,\
그냥 ‘줄 따라 실행’하는 게 아니라:

> **PC → 메모리 → IR → 실행 → PC 증가 → 다음 명령어**

### 2️⃣ 데이터 경로와 제어 신호

<figure><img src="/files/p8pEIs5zoge5RvMCJcWO" alt=""><figcaption></figcaption></figure>

이제 컴퓨터 내부의 **진짜 복잡한 연결 구조**가 등장했어!

#### 🧭 **우리가 지금까지 배운 컴퓨터 부품들**

> 전부 한 도시의 구성요소라고 보면 돼!

| 컴포넌트          | 비유        | 역할                        |
| ------------- | --------- | ------------------------- |
| 프로그램 카운터 (PC) | 우편배달부의 지도 | 다음 명령어의 주소를 기억            |
| 명령어 레지스터 (IR) | 작업지시서     | 현재 실행 중인 명령어 저장           |
| 데이터 버스        | 도로        | 데이터가 오가는 통로               |
| 주소 버스         | 우편주소 전선망  | 어디로 갈지를 알려주는 주소 전달 통로     |
| ALU           | 계산기       | 연산 수행                     |
| 누산기           | 계산기 옆 노트  | 결과를 임시로 저장                |
| 조건 코드 레지스터    | 조건 기록장    | 연산 결과가 어떤 상태였는지 저장        |
| 메모리           | 창고 거리     | 실제 데이터나 명령어들이 저장됨         |
| 간접 주소 레지스터    | 보조 주소 기록장 | 메모리 주소를 한 번 더 저장해야 할 때 사용 |

이제 우리가 만든 모든 부품들을 **어떻게 연결할지** 결정해야 해.\
그게 바로 **그림 4-22: 데이터 경로와 제어 신호**야!

> 즉, 컴퓨터가 ‘명령어를 가져와서 실행하는 모든 경로’를 그려놓은 거야.

#### 🛣️ 이 구조의 핵심 포인트

1. **프로그램 카운터(PC)**
   * 현재 실행할 명령어의 주소를 **주소 버스**에 보냄
   * → 메모리에서 명령어를 가져옴
2. **명령어 레지스터(IR)**
   * 메모리에서 가져온 명령어를 임시 저장
   * → 연산 코드(opcode)와 피연산자 주소, 모드 등을 해석
3. **피연산자 A / B**
   * 명령어에 나오는 주소를 따라가서 **값을 읽어올 버스 라인**
   * 각각의 데이터를 ALU로 보내서 계산에 활용
4. **ALU + 누산기**
   * A와 B 값을 받아서 계산 수행
   * 결과는 **누산기**에 임시 저장하고
   * **조건 코드 레지스터**에 상태(N/Z/O 등)를 기록
5. **간접 주소 레지스터**
   * 간접 주소 지정을 위해 필요!
   * 예: 명령어가 가리키는 주소 안에 또 다른 주소가 있을 때
   * 메모리 → 레지스터에 임시로 저장 → 다시 메모리 접근

#### 📌 클록과 제어 신호

* 컴퓨터는 **모든 동작을 클록(tick)**&#xC5D0; 맞춰 실행해.
* 하지만 그림 4-22에서는 **간단히 하려고 클록 선을 생략**했을 뿐이야.
* 대부분의 레지스터, 메모리 등은 **클록에 따라 데이터 저장**/수정이 일어나.

#### ✨ 정리하면!

| 구성요소           | 하는 일                                   |
| -------------- | -------------------------------------- |
| **주소 실렉터**     | 명령어에서 가져온 주소를 기준으로 어디로 갈지 정함 (3방향 교차로) |
| **데이터 실렉터**    | 어떤 데이터를 보낼지 선택 (4방향 교차로)               |
| **간접 주소 레지스터** | 간접 주소 모드를 위해 주소를 한 번 더 저장해두는 역할        |
| **명령어 레지스터**   | 현재 실행할 명령어 저장소                         |
| **누산기 / ALU**  | 계산 + 결과 임시 저장 + 조건 기록                  |
| **프로그램 카운터**   | 다음 명령어 위치 알려줌                          |

### 데이터 흐름 제어

**CPU의 핵심 두뇌, 바로 '제어 장치(Control Unit)'** 이야.

> 여기선 **명령어 실행을 위한 전체 흐름**, 즉 **명령어를 가져오고(Fetch), 실행(Execute)하는 과정을 실제로 어떻게 제어 신호로 만드는지** 설명하고 있어.

#### 컴퓨터가 명령어를 실행하기 위해 필요한 단계

#### 1. **Fetch (명령어 가져오기 단계)**

CPU가 명령어를 실행하려면, 먼저 메모리에서 명령어를 **읽어와야 해.**

이때 필요한 **제어 신호들**은 다음과 같아:

| 신호 이름             | 역할                                           |
| ----------------- | -------------------------------------------- |
| `address source`  | 주소 버스에 **어디의 주소를 넣을지** 결정 (이 경우는 `프로그램 카운터`) |
| `메모리 enable`      | 메모리 활성화                                      |
| `r/w`             | 읽기(1)인지 쓰기(0)인지 설정 (여기선 읽기니까 1)              |
| `명령어 레지스터 enable` | 읽어온 명령어를 저장할 레지스터(IR)를 활성화                   |

➡ 그리고 클록이 ‘틱!’ 하고 발생하면\
**IR(명령어 레지스터)에 명령어가 저장**돼. 이게 **Fetch 단계**야!

#### 🧩 실행 단계 예시: 누산기 값을 간접 주소에 저장하기

우리가 예로 든 동작은 이거야:

> "명령어가 가리키는 메모리 주소 안에 들어있는 주소에, 누산기의 값을 저장하라!"

간접 주소 지정을 활용하는 복잡한 케이스야!\
단계별로 **제어 신호**를 어떻게 설정하는지 보자 👇

#### 2. **간접 주소 불러오기 단계**

> 명령어의 주소 필드 = 100번지 → 그곳에 들어 있는 값 = 4321 → 여기에 실제 데이터를 쓸 거야

| 신호 이름                   | 역할                    |
| ----------------------- | --------------------- |
| `address source = IR`   | 명령어의 주소 부분을 주소 버스로 보냄 |
| `메모리 enable = 1`        | 메모리 읽기                |
| `r/w = 1`               | 읽기 모드                 |
| `간접 주소 레지스터 enable = 1` | 메모리에서 읽어온 주소를 저장      |

➡ 이렇게 하면 **간접 주소 레지스터**에 4321이 들어감

#### 3. **누산기 값 쓰기 단계**

> 이제 실제 주소인 4321에 **누산기 값**을 저장하자!

| 신호 이름                         | 역할                  |
| ----------------------------- | ------------------- |
| `address source = 간접 주소 레지스터` | 실제 주소 4321로 설정      |
| `data bus enable = 1`         | 누산기 값이 데이터 버스에 나가도록 |
| `r/w = 0`                     | 쓰기 모드               |
| `메모리 enable = 1`              | 메모리에 쓰기 수행          |
| `프로그램 카운터 증가(ld/cnt)`         | 다음 명령어 실행 준비        |

#### 🔄 그런데 이걸 어떻게 자동으로 하게 만들까?

#### 🤖 상태 기계 (State Machine)!

1. **각 단계(Fetch, Execute)를 나누고**
2. **그때마다 어떤 제어 신호를 줄지 지정해주면**
3. **자동으로 명령어를 실행할 수 있어!**

이걸 구현하는 방식은 두 가지:

🧠 방법 1: **랜덤 논리(random logic)**

> 게이트와 논리 회로를 복잡하게 얽어서 **하드코딩된 방식**으로 제어 신호를 만드는 방법

<figure><img src="/files/ehkD4CtaCfDP93v9Fuj2" alt=""><figcaption></figcaption></figure>

➡ **그림 4-23**: 랜덤 논리로 구현된 제어 장치 회로\
회로 복잡도는 높지만 빠르고 하드웨어적으로 직관적임

💾 방법 2: **마이크로코드 방식**

<figure><img src="/files/YrKgP5E6aM3AHcPsd3Z2" alt=""><figcaption></figcaption></figure>

> 제어 신호를 메모리에 저장해두고, **그걸 읽어서 제어**하는 방식\
> (일종의 ‘작은 프로그램’이 큰 프로그램을 실행하게 돕는 구조)

* **카운터 + 명령어 + 모드** ⇒ 주소
* 해당 주소에 저장된 제어 신호를 메모리에서 읽음
* 각 제어 신호는 19비트로 구성됨

➡ **그림 4-24\~26**은 이 마이크로코드 방식의 예시야

#### 🎯 마이크로코드 방식의 장점과 단점

| 장점                     | 단점                   |
| ---------------------- | -------------------- |
| 명령어 집합을 소프트웨어처럼 정의 가능  | 속도가 느릴 수 있음          |
| CPU 구조를 더 유연하게 바꿀 수 있음 | 보안/안정성 문제로 사용자 변경 불가 |

> 예: 인텔 CPU는 버그 패치를 위해 **마이크로코드 업데이트** 기능을 일부 모델에 탑재함!

#### ✨ 마무리 정리

* 컴퓨터는 **명령어마다 어떤 신호를 언제 줄지** 정해야 하고
* 그걸 상태 기계나 마이크로코드로 구현한다
* 오늘날 CPU는 대부분 **마이크로코드 방식**을 사용하며,
* CPU 안에 또 다른 **작은 컴퓨터**가 들어있는 셈이다!

## 6. RISC와 CISC 명령어 집합

### 💡 명령어 집합 설계: RISC vs CISC

#### 1) 왜 명령어가 단순한 게 좋을까?

과거에는 많은 CPU 설계자들이 **복잡한 명령어**를 만들고 싶어했어.\
왜냐면, 한 줄에 많은 일을 하면 더 "똑똑한 컴퓨터"처럼 보였거든.

그런데 1980년대 초, 미국의 **데이비드 패터슨**과 **존 헤네시**라는 과학자들이 통계 분석을 통해\
놀라운 사실을 밝혀냈어:

> “복잡한 명령어의 대부분은 실제로 거의 쓰이지 않는다!”

그래서 두 사람은 새로운 아이디어를 제안했어:\ <mark style="color:red;">👉</mark> <mark style="color:red;"></mark><mark style="color:red;">**자주 쓰는 단순한 명령어만 남기자!**</mark>\
이 아이디어가 바로 **RISC**의 시작이야.

### 🔍 RISC란? (Reduced Instruction Set Computer)

* **자주 쓰이는 단순한 명령어만 사용**
* 복잡한 명령어는 여러 단순한 명령어로 **쪼개서** 처리
* 모든 연산은 **레지스터** 안에서 수행 (load-store architecture)
* **명령어는 고정된 길이**, 디코딩이 쉽고 빠름

> 즉, "간단한 것만 빠르게 하자" 철학이야!

### 🔎 CISC란? (Complex Instruction Set Computer)

* **복잡하고 다양한 명령어**가 있음 (예: 메모리 접근 + 연산을 한 번에)
* 레지스터/메모리 등 어디서든 연산 가능
* 디코딩이 복잡하고, 명령어 길이도 제각각

> 즉, "한 줄로 똑똑하게 많은 걸 하자" 철학이야!

### 📚 예시: 메모리 복사 프로그램 (PDP-11)

과거 **CISC 시스템인 PDP-11**에서는 아래 같은 구조도 가능했어:

```
주소 0: [원본 주소]의 데이터를 [대상 주소]로 복사 + 둘 다 1 증가 (자동증가 모드)
주소 1: [복사한 크기] - 1 해서 0인지 비교 (자동감소 모드)
주소 2: 아직 안 끝났으면 다시 0번 주소로 분기
```

이처럼 PDP-11은 **자동증가 / 감소 모드**를 지원해서\
단순한 루프를 아주 짧은 명령어로 구현할 수 있었어!

### 💬 왜 C 언어에 영향을 줬을까?

* **C언어는 PDP-11을 위해 처음 개발**된 언어야!
* 그래서 C의 **포인터(pointer)** 개념도 PDP-11의 **간접 주소 지정**과 잘 어울려
* 자동증가/감소 모드도 C의 배열 처리와 찰떡!

> 그래서 C는 자연스럽게 **하드웨어 구조와 잘 맞는 언어**가 됐고,\
> 이후 C++ → Java → JavaScript 등 수많은 언어에 영향을 줬어.

### 💡 요즘 RISC는 정말 단순할까?

그렇지 않아!

시간이 지나면서…

* RISC에도 복잡한 명령어들이 일부 도입되고
* 반대로 CISC도 내부적으로 단순한 방식(RISC 방식)으로 동작하기도 해

즉, 이제는 완벽하게 RISC vs CISC로 나누기보다는\
`혼합형 구조(Hybrid architecture)`가 많아졌어.

### ✅ 요약 정리

1. RISC: 자주 쓰이는 **단순 명령어**만, 레지스터 중심, 빠르고 효율적
2. CISC: **복잡한 명령어**로 한 번에 많은 일, 메모리 접근 연산도 가능
3. PDP-11: CISC의 대표, 자동증가/감소 모드로 효율적인 코드 작성 가능
4. C 언어: PDP-11을 위해 설계되어 포인터나 주소 지정을 자연스럽게 추상화
5. 요즘은 RISC와 CISC의 구분보다 **성능과 유연성을 섞은 구조**가 많아짐

## 7. GPU

### 🧠 GPU란? (그래픽 처리 장치의 본질)

GPU(Graphics Processing Unit)는 요즘 가장 핫한 장치 중 하나야.\
원래는 **그래픽 처리**에 특화되어 만들어졌지만,\
지금은 **병렬 연산**을 수행하는 데도 널리 사용되고 있어.

#### 🎨 GPU는 '초대형 컬러링북 색칠기'!

GPU의 그래픽 작업은 마치 **컬러링북에 번호에 맞춰 색칠하는 작업**과 비슷해.

* 번호가 적힌 칸들이 수백만, 수천만 개나 있고,
* 각 칸에 **정해진 색깔**을 칠해야 해.
* 이 수많은 칸들을 **동시에 색칠해야 빠르게 그림이 완성**되지!

> 즉, **GPU는 매초 수억 번 이상 메모리에 접근**해서, 동시에 많은 계산을 수행하는 구조가 필요한 거야!

### ✅ GPU가 CPU와 다른 이유 (병렬성과 메모리 접근)

| 구분     | GPU                     | CPU                  |
| ------ | ----------------------- | -------------------- |
| 목적     | 병렬 연산에 최적화 (그래픽, AI 등)  | 일반 연산에 최적화           |
| 구조     | 단순한 계산 유닛이 **엄청 많이** 있음 | 복잡한 계산 유닛이 **소수** 존재 |
| 메모리 버스 | **폭이 넓음** (데이터 이동 빠름)   | 비교적 좁음               |
| 비유     | 컬러링북에서 동시에 색칠           | 색연필 하나로 한 칸씩 칠함      |

그래서 GPU는 **병렬화하기 아주 좋은 작업**, 예를 들어:

* 3D 그래픽 렌더링
* 이미지 처리
* 딥러닝 학습과 추론
* 과학/공학 계산\
  에 탁월하게 쓰이게 된 거야!

### 💥 GPU의 두 가지 큰 특징 요약

1. **수많은 단순한 처리 장치**로 구성되어 있어\
   → 동시에 여러 작업을 처리하는 데 적합!
2. **메모리 버스의 폭이 넓다**\
   → 많은 데이터를 빠르게 주고받을 수 있어! (예: 소방 호스 비유)

### 📉 그런데 왜 CPU처럼 범용으로 못 쓰였을까?

GPU는 원래는 그래픽 전용으로 등장했지만,\
너무 특화된 구조 때문에 다음과 같은 제약이 있었어:

* **정수 연산, 조건 분기, 복잡한 논리 연산**에 약함
* **유연한 메모리 접근**이 어려움
* 그래서 범용 계산용으로는 **초기엔 잘 안 쓰였음**

하지만 요즘은 CUDA, OpenCL, ROCm 등 덕분에\
**Python이나 C 언어 등으로 GPU도 쉽게 프로그래밍**할 수 있게 되었고,\
**AI, 머신러닝, 비트코인 채굴 등 범용 계산**에도 폭넓게 쓰이고 있어.

### 🎯 요약 정리

1. **GPU는 대규모 병렬 작업에 특화된 장치**다.
2. 그림 색칠처럼 **수많은 점을 동시에 처리**하는 그래픽에 특히 유용함.
3. **단순한 처리 유닛이 많고, 메모리 버스 폭이 넓어** 처리량이 크다.
4. **컬러링북 비유**는 GPU의 동작 방식을 아주 잘 설명해준다!
5. 요즘은 **그래픽뿐 아니라 AI, 공학 연산 등에도 광범위하게 사용**된다.

## 딥다이브&#x20;

## 1) 정렬되지 않은 접근이 문제가 되는 이유

### 🏙️ 비유로 이해하는 메모리 정렬

#### 🏘️ 건물(포플렉스) = 메모리 블록

컴퓨터 메모리는 일정 크기의 **블록(= 건물)**&#xC73C;로 나뉘어 있어.\
예를 들어, 32비트 컴퓨터라면 하나의 건물(블록)은 **4바이트 = 32비트**로 되어 있고, 이게 <mark style="color:red;">하나의 워드</mark>라고 보면 돼!

즉,

* 주소 0 \~ 3번 바이트 → 0번 건물 (포플렉스 0)
* 주소 4 \~ 7번 바이트 → 1번 건물 (포플렉스 1)
* 주소 8 \~ 11번 바이트 → 2번 건물 (포플렉스 2)\
  이런 식으로 나뉘는 거죠.

#### 🚌 버스 = 데이터 버스

“도심을 오가는 버스”는 **CPU와 메모리 사이를 오가는 데이터 버스**를 말해

> 한 번 버스를 태우면 <mark style="color:red;">한 건물(포플렉스)에서 4바이트를 한꺼번에 실어올 수 있어</mark>\
> \&#xNAN;**= 정렬이 맞을 때**의 이상적인 상황!

### 🧩 문제 상황: 정렬이 맞지 않을 때

이제, 어떤 프로그램이 **주소 5번부터 8번까지 4바이트**를 읽으려 한다고 해보자

> 주소 58은 1번 건물(47)과 2번 건물(8\~11)에 걸쳐 있어\
> 즉, **한 건물에서만 데이터를 읽을 수 없어.**

그래서 CPU는…

1. 1번 건물에서 5\~7번 바이트를 읽고
2. 2번 건물에서 8번 바이트를 다시 읽어서
3. 두 번을 나눠 읽고 조립해야 해.

### 🐢 왜 이게 문제일까?

* 메모리 접근이 **두 번** 일어나서 느려짐
* **CPU 내부에서도 조립** 과정이 필요 → 복잡함
* 일부 아키텍처는 이런 접근 자체를 **허용하지 않음**

그래서 요즘 대부분의 시스템은 다음과 같은 규칙을 따른다:

> **"4바이트 데이터를 읽을 땐 반드시 4의 배수 주소에서 시작해야 한다!"**

\= 즉, 주소가 **0, 4, 8, 12…** 여야 한다는 거\
이걸 `정렬(alignment)`이라고 부른다.

### 📌 요약하면!

* 메모리는 워드 단위(4바이트 등)로 정렬된 구조를 가짐
* 버스는 한 번에 하나의 포플렉스(4바이트 블록)만 접근 가능
* **정렬이 맞지 않으면 버스를 두 번 왕복해야 하므로 비효율적**
* → 정렬된 접근이 더 빠르고 안정적이다!


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mellona-log.gitbook.io/log/cs/undefined-1/4.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
