덧셈기
프로세서가 하는 일의 대부분은 결국 덧셈이다. 주소 계산, 분기 목표, 비교, 곱셈의 부분곱 합치기까지 모두 덧셈기를 지난다. 그런데 64비트 덧셈은 64개의 1비트 덧셈을 나란히 하는 일이 아니다. 맨 아래 비트에서 생긴 캐리 하나가 맨 위 비트까지 번질 수 있기 때문이다. 이 장에서는 전가산기 하나에서 출발해 캐리가 비트를 따라 번지는 모습을 직접 보고, 그 사슬을 끊어 지연을 \(N\)에서 \(\sqrt{N}\), 다시 \(\log_2 N\)으로 줄이는 구조들을 하나씩 만져 본다. 덧셈기 설계는 결국 "캐리를 얼마나 빨리, 얼마나 싸게 알아내는가"의 문제다.
- 반가산기·전가산기의 진리표와 게이트 회로를 만들고, 생성·전파·소멸 신호로 캐리 식 \(c_{i+1} = g_i + p_i c_i\)를 쓴다.
- 리플 캐리 덧셈기의 지연이 \(N\)에 비례하는 이유와 최악 입력을 설명한다.
- 캐리 예측·캐리 건너뛰기·캐리 선택 구조의 지연을 식으로 세우고 최적 블록 크기를 구한다.
- (G,P) 결합 연산자 ∘로 병렬 접두사 덧셈기를 이해하고, Kogge–Stone·Brent–Kung·Sklansky·Han–Carlson의 단 수·면적·팬아웃·배선을 비교한다.
반가산기와 전가산기
초등학교에서 배운 세로 덧셈을 이진수로 하면 각 자리에서 하는 일은 같다. 그 자리의 두 비트와 아랫자리에서 올라온 올림수를 더해, 그 자리의 합 한 비트와 윗자리로 보낼 올림수 한 비트를 낸다. 올림수를 캐리(Carry)라 부른다.
두 비트만 더하는 회로가 반가산기(Half adder)다. 합은 두 비트가 다를 때 1이므로 XOR, 캐리는 둘 다 1일 때만 생기므로 AND다. 아랫자리 캐리까지 세 비트를 더하는 회로가 전가산기(Full adder, FA)다. 세 비트를 더한 결과는 0~3이고, 이를 두 비트 \((C_{out}, S)\)로 쓴다. 반가산기 두 개와 OR 하나면 된다.
식으로 쓰면 다음과 같다. 캐리 출력은 세 입력 중 두 개 이상이 1이면 1이므로 다수결 함수(Majority, MAJ)이기도 하다.
트랜지스터 수준에서는 게이트를 그대로 이어 붙이지 않는다. 정적 CMOS XOR는 비싸고(인버터 포함 약 12개), 그림처럼 XOR 2개·AND 2개·OR 1개로 지으면 40개가 넘는다. 실제 표준 셀 라이브러리의 전가산기는 다수결 함수를 한 번에 계산하는 복합 게이트로 \(\overline{C_{out}}\)을 먼저 만들고, 그것을 다시 써서 합을 만든다. 이것이 Weste & Harris가 소개하는 미러 가산기(Mirror adder)로, 트랜지스터 28개다.
| 구현 | 트랜지스터 수(대략) | 특징 |
|---|---|---|
| XOR 2 + AND 2 + OR 1 (정적 CMOS 게이트 조합) | 약 42 | 그림 11-1 그대로. 교재용, 느리고 크다 |
| NAND 9개 | 36 | 범용 게이트만으로 짓기(2장) |
| 미러 가산기 | 28 | 풀업·풀다운이 거울 대칭, 캐리 경로는 2단 반전 |
| 전송 게이트(TG) 가산기 | 약 24 | XOR를 전송 게이트로, 출력 구동력이 약해 버퍼 필요(7장) |
첫째, 캐리 회로의 풀업과 풀다운이 같은 모양이다. \(\overline{C_{out}} = \overline{AB + C_{in}(A+B)}\)는 자기 쌍대 함수라서 pMOS 쪽을 nMOS 쪽과 똑같이(직렬·병렬을 뒤집지 않고) 배치해도 맞는 함수가 나온다. 그래서 "거울"이다. 둘째, 캐리 경로에 반전을 허용한다. 출력 \(\overline{C_{out}}\)을 그대로 다음 비트로 보내고, 홀수 비트에서는 입력도 반전해 받으면 비트마다 인버터 하나를 아낄 수 있다((Inverting adder)). 리플 캐리에서는 캐리 경로의 게이트 하나가 곧 비트당 지연이므로 이 차이가 크다. 또 캐리 게이트의 \(C_{in}\) 트랜지스터를 출력에 가장 가깝게 두어 늦게 도착하는 신호의 기생 커패시턴스를 줄인다(5장의 입력 순서).
생성·전파·소멸
전가산기의 캐리 출력을 다르게 읽어 보자. 아래 캐리 \(c_i\)가 아직 도착하지 않았을 때, 이 자리의 두 비트만 보고도 미리 알 수 있는 것이 있다.
- 두 비트가 모두 1이면 아래 캐리와 상관없이 캐리가 생긴다. 생성(Generate) \(g_i = a_i b_i\).
- 두 비트가 모두 0이면 아래 캐리와 상관없이 캐리가 사라진다. 소멸(Kill) \(k_i = \overline{a_i}\,\overline{b_i}\).
- 둘 중 하나만 1이면 아래 캐리를 그대로 위로 넘긴다. 전파(Propagate) \(p_i = a_i \oplus b_i\).
세 경우는 서로 배타적이고 빠짐없다. 즉 모든 자리는 G·P·K 셋 중 하나다. 캐리가 오래 기다려야 하는 곳은 P 자리뿐이다. G와 K는 입력이 들어오자마자 답이 정해진다.
자리 \(i\)를 "들어온 캐리 → 나가는 캐리" 함수로 보면 가능한 1비트 함수는 셋뿐이다. 상수 1(G), 상수 0(K), 항등(P). 이 관점이 4절의 캐리 예측과 6절의 병렬 접두사 덧셈기의 출발점이다. 여러 자리를 이어 붙인 블록도 결국 상수 1, 상수 0, 항등 중 하나의 함수가 되기 때문이다.
리플 캐리 덧셈기
전가산기 \(N\)개를 캐리 선으로 줄줄이 이으면 \(N\)비트 덧셈기가 된다. 각 FA는 아래 캐리가 도착해야 자기 캐리를 확정할 수 있으므로 캐리가 물결처럼 아래에서 위로 번진다. 그래서 리플 캐리 덧셈기(Ripple-carry adder, RCA)다. 가장 작고 단순하지만 가장 느리다.
캐리가 번지는 리플 캐리 덧셈기
시뮬레이터에서 최악 입력을 눌러 보자. \(A = 0111\ldots1\), \(B = 0\ldots01\)이면 비트 0이 G, 비트 1부터 \(N-2\)까지가 모두 P라서 캐리가 맨 아래에서 맨 위까지 한 칸씩 걸어 올라간다. 정적 타이밍 분석(10장)은 입력 값을 모르므로 언제나 이 최악 경로를 기준으로 클럭을 정한다.
64비트 두 수를 무작위로 골랐을 때 가장 긴 전파 사슬의 길이는 대략 얼마일까? 64에 가까울까, 훨씬 짧을까? 시뮬레이터의 무작위를 여러 번 눌러 16비트에서 먼저 감을 잡아 보자.
답 보기
훨씬 짧다. 각 자리가 P일 확률은 1/2이므로 P가 \(m\)개 연속할 확률은 \(2^{-m}\)이다. \(N\)비트에서 가장 긴 연속은 평균 약 \(\log_2 N\), 64비트면 약 5~6비트다(Burks–Goldstine–von Neumann, 1946). 그래서 "캐리가 끝났는지 감지해 일찍 끝내는" 비동기 덧셈기도 연구되었다. 그러나 동기식 칩은 최악의 경우에 맞춰 클럭을 정해야 하므로 평균이 짧다는 사실을 쓸 수 없다. 아래의 모든 구조는 최악 지연을 줄이는 방법이다.
캐리 예측 덧셈기
캐리 식 \(c_{i+1} = g_i + p_i c_i\)를 자기 자신에 계속 대입하면 모든 캐리를 \(c_0\)와 \(g, p\)만으로 쓸 수 있다. 캐리가 도착하기를 기다리지 않고 "미리 내다보는" 셈이다. 이것이 캐리 예측(Carry-lookahead, CLA)이다.
4비트 블록이 \(G_{3:0}, P_{3:0}\)를 내면 블록을 다시 1비트 자리처럼 다룰 수 있다. 블록 4개의 \((G,P)\)를 2단 CLA 유닛에 넣으면 16비트의 블록 캐리 \(c_4, c_8, c_{12}, c_{16}\)이 같은 식으로 나오고, 다시 4개를 묶으면 64비트가 된다. 단 수는 \(\log_4 N\)에 비례한다. 신호는 트리를 올라가며 \((G,P)\)를 모으고, 내려오며 블록 캐리를 나눠 준다.
캐리 건너뛰기와 캐리 선택
CLA처럼 큰 논리를 쓰지 않고도 리플 캐리를 빠르게 하는 값싼 방법이 둘 있다. 둘 다 \(N\)비트를 \(k\)비트짜리 블록으로 자른다.
캐리 건너뛰기(Carry-skip, Carry-bypass)는 블록 안 모든 자리가 P이면(\(P_{\text{blk}} = p_{k-1}\cdots p_0 = 1\)) 들어온 캐리를 MUX 하나로 블록 끝까지 곧장 보낸다. 블록 안에서 캐리가 생기는 경우는 블록 안의 리플로 처리된다. 최악 경로는 첫 블록에서 생긴 캐리가 리플로 빠져나와, 가운데 블록들을 MUX로 건너뛰고, 마지막 블록에서 다시 리플하는 길이다.
캐리 선택(Carry-select)은 더 대담하다. 각 블록을 두 벌 만들어 하나는 "들어올 캐리가 0", 다른 하나는 "1"이라고 가정하고 미리 계산한다. 진짜 캐리가 도착하면 MUX로 둘 중 하나를 고르기만 한다. 면적은 거의 두 배지만 블록마다 MUX 한 단만 지나면 된다.
캐리 건너뛰기에서 최악 경로는 첫 블록과 마지막 블록에서만 리플한다. 블록 크기를 자리마다 다르게 하면 더 빨라질 수 있을까? 어떤 모양이 좋을까?
답 보기
그렇다. 가운데 블록은 건너뛰기만 당하므로 크게, 양 끝 블록은 리플을 해야 하므로 작게 만들면 된다. 예를 들어 2, 3, 4, …, 4, 3, 2처럼 가운데가 불룩한 가변 블록이 균일 블록보다 빠르다. 캐리 선택도 같은 생각으로 블록 크기를 1비트씩 늘려(\(k, k+1, k+2,\ldots\)) 앞 블록의 MUX 출력과 다음 블록의 계산이 동시에 끝나게 맞추면 지연이 약 \(\sqrt{2N}\)으로 준다. 어느 쪽이든 규모는 여전히 \(\sqrt{N}\)이다.
병렬 접두사 덧셈기
2절의 관점으로 돌아가자. 자리 \(i\)부터 \(j\)까지(\(i \ge j\))의 연속된 비트 구간은 들어온 캐리를 상수 1로 만들거나(블록 생성 \(G_{i:j}=1\)), 그대로 넘기거나(블록 전파 \(P_{i:j}=1\)), 상수 0으로 만든다. 두 인접 구간을 이어 붙인 구간의 \((G,P)\)는 다음 연산자 \(\circ\)로 구한다.
\(\circ\)의 핵심 성질은 결합 법칙이다. \((x\circ y)\circ z = x\circ(y\circ z)\). 순서(교환 법칙)는 바꿀 수 없지만, 괄호는 아무렇게나 쳐도 된다. 괄호를 왼쪽부터 차례로 치면 리플 캐리(\(N-1\)단), 균형 잡힌 트리로 치면 \(\log_2 N\)단이다. 덧셈의 고속화는 "모든 접두사를 얕은 트리로 동시에 계산하는" 병렬 접두사(Parallel prefix) 문제가 된다.
병렬 접두사 덧셈기는 세 부분으로 된다. ① 자리마다 \(g_i, p_i\)를 만든다. ② 접두사 네트워크가 모든 \(G_{i:0}\)을 계산한다. ③ \(s_i = p_i \oplus G_{i-1:0}\). 구조의 차이는 모두 ②에 있다. 네트워크는 점(∘ 셀)과 선(배선)으로 그린 그래프이고, 설계자가 맞바꾸는 것은 네 가지다. 단 수(지연), 셀 수(면적·전력), 팬아웃(한 셀이 구동하는 부하), 배선(가로로 길게 뻗는 선의 수와 길이).
접두사 네트워크 탐험기
네 가지 고전 구조의 성격을 정리하면 다음과 같다(\(N\)비트, 접두사 네트워크만).
| 구조 | 단 수 | ∘ 셀 수 | 최대 팬아웃 | 64비트 (단 / 셀 / 팬아웃 / 총 배선) | 성격 |
|---|---|---|---|---|---|
| 직렬(리플) | \(N-1\) | \(N-1\) | 2 | 63 / 63 / 2 / 63 | 가장 작고 가장 느림 |
| Sklansky (1960) | \(\log_2 N\) | \(\tfrac{N}{2}\log_2 N\) | \(\tfrac{N}{2}+1\) | 6 / 192 / 33 / 1104 | 분할 정복. 단 수 최소지만 팬아웃이 크다 |
| Kogge–Stone (1973) | \(\log_2 N\) | \(N\log_2 N - N + 1\) | 2 | 6 / 321 / 2 / 2667 | 단 수·팬아웃 최소, 셀과 배선이 가장 많다 |
| Brent–Kung (1982) | \(2\log_2 N - 1\) | \(2N - 2 - \log_2 N\) | 2 | 11 / 120 / 2 / 321 | 올라갔다 내려오는 트리. 작지만 단이 두 배 |
| Han–Carlson (1987) | \(\log_2 N + 1\) | \(\tfrac{N}{2}\log_2 N\) | 2 | 7 / 192 / 2 / 1365 | 홀수 비트만 Kogge–Stone, 마지막에 짝수 비트 한 단 |
64비트에서 Kogge–Stone과 Sklansky는 둘 다 6단이다. 그런데 Kogge–Stone의 셀은 321개, Sklansky는 192개다. 시뮬레이터에서 두 구조를 64비트로 번갈아 보며, Kogge–Stone이 단 수 말고 무엇을 얻으려고 셀을 더 쓰는지, 그 대가로 무엇이 늘어나는지 찾아보자.
답 보기
Kogge–Stone은 모든 셀의 팬아웃을 2로 묶는다. Sklansky는 마지막 단에서 노드 하나가 32개 셀을 구동해야(팬아웃 33) 하므로 6장의 논리적 노력으로 보면 그 단이 크게 느려지거나 버퍼를 넣어야 한다. 대신 Kogge–Stone은 단마다 \(N\)개 가까운 선이 \(2^l\)비트씩 가로로 뻗어 총 배선이 2667 비트 피치로 Sklansky의 두 배가 넘고, 마지막 단에는 32비트를 가로지르는 선이 32개 나란히 지나간다. 배선 트랙과 배선 커패시턴스(14장), 전력이 대가다. Han–Carlson은 한 단을 더 쓰고 이 배선을 절반으로 줄인 절충이다.
Knowles(1999)는 Kogge–Stone과 Sklansky 사이를 잇는 구조군을 팬아웃 매개 변수로 정리했고, Harris(2003)는 모든 고전 구조를 세 축, 즉 단 수 \(\log_2 N + l\), 팬아웃 \(2^f + 1\), 배선 트랙 \(2^t\)로 나타낼 수 있으며 \(l + f + t = \log_2 N - 1\)이라는 관계를 보였다. Brent–Kung은 \(l\)을, Sklansky는 \(f\)를, Kogge–Stone은 \(t\)를 최대로 쓴 꼭짓점이고 Han–Carlson은 그 사이다. 실제 고성능 덧셈기는 이 공간에서 공정과 배선 여유에 맞는 점을 고른다. 4비트마다 하나의 캐리만 계산하고 나머지는 캐리 선택으로 마무리하는 희소 트리(Sparse tree), 첫 단을 한 단 줄이는 Ling 덧셈기도 널리 쓰인다.
표의 단 수는 모든 ∘ 셀이 같은 시간이 걸린다고 가정한다. 실제로는 팬아웃이 큰 셀이 느리고, 긴 배선은 RC 지연을 더하며, 셀 수가 많으면 전체가 커져 배선이 더 길어진다. 다음 절의 비교 시뮬레이터는 이 효과를 간단한 모델로 넣어 본다.
덧셈기 비교: 지연 대 면적
지금까지 본 구조를 한자리에 놓아 보자. 가로축은 지연, 세로축은 면적이다. 왼쪽 아래로 갈수록 좋다. 어느 구조도 두 축에서 동시에 최고는 아니다. 좋은 구조들은 파레토 경계(Pareto frontier) 위에 늘어서고, 설계자는 목표 클럭과 면적 예산에 맞춰 그 경계 위의 한 점을 고른다.
덧셈기 구조 비교
두 그래프에서 읽을 것은 세 가지다. 첫째, 리플 캐리는 \(N\), 건너뛰기·선택은 \(\sqrt N\), CLA와 접두사 구조는 \(\log N\)으로 자란다. 오른쪽 그래프의 기울기가 그 차수다. 둘째, \(N\)이 16 이하로 작으면 차이가 크지 않아 작은 구조가 합리적이다. 셋째, 64비트 이상에서는 \(\log N\) 구조가 아니면 한 사이클에 넣기 어렵다. 고성능 CPU의 정수 덧셈기가 거의 모두 접두사 구조의 변형인 이유다.
뺄셈과 오버플로
덧셈기가 있으면 뺄셈기는 거의 공짜다. 2의 보수(Two's complement)에서 \(-B = \overline{B} + 1\)이므로, B의 모든 비트를 반전(XOR 게이트에 "빼기" 신호를 넣으면 된다)하고 캐리 입력에 1을 넣으면 된다.
오버플로 규칙을 말로 하면 단순하다. 부호가 같은 두 수를 더했는데 결과의 부호가 다르면 오버플로다. 부호가 다른 두 수의 덧셈은 절대 넘치지 않는다. \(c_N \oplus c_{N-1}\)은 이 규칙을 게이트 하나로 계산한 것이다. 같은 덧셈기 출력을 부호 없는 수로 읽을지, 부호 있는 수로 읽을지는 회로가 아니라 명령어가 정한다. 그래서 ALU는 두 종류의 플래그 \(C\)(캐리)와 \(V\)(오버플로)를 함께 낸다. 비교 명령은 뺄셈을 하고 결과는 버린 채 이 플래그와 영(Z), 부호(N) 플래그만 남긴다(12장).
신호 처리나 신경망 가속기에서는 오버플로가 났을 때 값을 감아 돌리지(wrap) 않고 최댓값·최솟값에 붙잡는 포화(Saturation) 산술을 자주 쓴다. 100 + 50이 −106이 되는 대신 127로 멈추면 소리가 "딱" 하고 튀지 않는다. 회로는 \(V\)와 부호 비트로 MUX를 하나 더 두면 된다.
실제 수치: 덧셈 한 번은 한 사이클
최신 CPU에서 정수 덧셈은 1사이클 명령이고, 결과는 다음 사이클의 명령이 바로 쓴다(바이패스). 그러니 덧셈기 하나에 바이패스 MUX와 배선까지 더한 루프가 한 클럭 주기 안에 들어와야 한다. 이 "ALU + 바이패스 루프"는 고성능 코어의 대표적인 임계 루프다.
| 항목 | 대략값 | 비고 |
|---|---|---|
| 64비트 고속 덧셈기 지연 | 약 7~10 FO4 | 병렬 접두사(희소 트리, Ling 등), 동적 논리면 더 짧다 |
| 같은 덧셈기, DG-7 환산 | 약 50~75 ps | FO4 ≈ 7.5 ps |
| 64비트 리플 캐리 덧셈기 | 약 60 FO4 이상 | DG-7에서 약 450 ps 이상, 2 GHz 주기에도 못 들어간다 |
| 5 GHz 클럭 한 주기 | 200 ps ≈ DG-7 약 27 FO4 | 2020년대 데스크톱 CPU는 약 5~6 GHz(부스트) |
| 고성능 코어 한 사이클의 논리 깊이 | 약 15~25 FO4 | 2000년대 중반 이후. 플립플롭 오버헤드 약 2~4 FO4 포함 |
| Pentium 4 (약 2000년) | 코어 클럭 2배로 도는 ALU | 16비트씩 나눠 반 사이클마다 계산하는 "double-pumped" ALU |
여러 수를 연달아 더할 때는 매번 캐리를 끝까지 전파할 필요가 없다. 캐리를 따로 한 줄로 저장해 두는 캐리 저장(Carry-save) 표현을 쓰면 전가산기 한 단(3입력 → 2출력)만으로 세 수를 두 수로 줄일 수 있고, 마지막에 한 번만 빠른 덧셈기를 쓴다. 곱셈기의 월리스 트리가 이 원리다. 다음 장 곱셈기·시프터·ALU에서 이어진다.
핵심 정리
- 전가산기는 \(S = A\oplus B\oplus C_{in}\), \(C_{out} = \mathrm{MAJ}(A,B,C_{in})\)이다. 실제 셀은 28트랜지스터 미러 가산기처럼 캐리 경로를 짧게 만든다.
- 자리마다 생성 \(g=ab\), 전파 \(p=a\oplus b\), 소멸 중 하나이고 \(c_{i+1} = g_i + p_i c_i\)다. 캐리가 기다리는 곳은 P 자리뿐이다.
- 리플 캐리 덧셈기는 가장 작지만 최악 입력(0111…1 + 1)에서 지연이 \(N\)에 비례한다. 동기 회로는 평균이 아니라 최악에 맞춘다.
- 캐리 예측은 블록 \((G,P)\)를 미리 계산해 트리로 쌓는다. 캐리 건너뛰기·선택은 블록 크기 \(\sqrt N\) 근처에서 지연 \(\propto\sqrt N\)을 얻는다.
- (G,P) 결합 연산자 ∘는 결합 법칙을 따르므로 모든 캐리를 \(\log_2 N\)단 트리로 동시에 구할 수 있다. 이것이 병렬 접두사 덧셈기다.
- Kogge–Stone(단·팬아웃 최소, 배선 최대), Sklansky(팬아웃 큼), Brent–Kung(셀 최소, 단 2배), Han–Carlson(절충)은 단 수·팬아웃·배선의 맞바꿈이다.
- 뺄셈은 \(A+\overline B+1\), 부호 있는 오버플로는 \(c_N\oplus c_{N-1}\)이다. 64비트 고속 덧셈기는 약 7~10 FO4로 한 사이클 안에 든다.
확인 퀴즈
Q1. 전가산기의 캐리 출력 \(C_{out}\)과 같은 식은?
Q2. 16비트 리플 캐리 덧셈기에서 합의 최상위 비트가 가장 늦게 확정되는 입력은?
Q3. 64비트 Kogge–Stone 덧셈기의 접두사 네트워크는 몇 단인가?
Q4. 모든 단위 지연을 같게 둘 때, 균일 블록 캐리 선택 덧셈기의 지연을 최소로 하는 블록 크기 \(k\)는 대략?
Q5. 8비트 부호 있는 덧셈 100 + 50의 결과에 대한 설명으로 옳은 것은?
Q6. Brent–Kung 구조를 Kogge–Stone과 비교한 설명으로 옳은 것은?