곱셈기·시프터·ALU
덧셈기(11장)가 데이터패스의 심장이라면, 곱셈기는 그 심장을 수십 개 겹쳐 놓은 근육이다. 64비트 곱셈 하나에는 부분곱 비트가 4096개 생기고, 이것을 한 클럭 남짓 안에 하나의 수로 합쳐야 한다. 비결은 의외로 단순하다. 캐리를 기다리지 않는 것이다. 이 장에서는 손 곱셈의 모양에서 출발해 부분곱을 나무처럼 줄이는 월리스·다다 트리, 부분곱 수를 절반으로 줄이는 부스 부호화를 만지고, 이어서 시프터와 ALU, 비교기, 그리고 AI 가속기가 즐겨 쓰는 BF16·FP8까지 이르는 부동소수점 회로를 차례로 짓는다.
- 부분곱이 AND 행렬임을 알고, 배열 곱셈기와 트리 곱셈기의 지연이 \(O(N)\)과 \(O(\log N)\)으로 갈리는 이유를 설명한다.
- 캐리 저장 덧셈(3:2 압축기)으로 월리스·다다 트리를 짓고, 단계 수·전가산기 수·최종 CPA 폭을 계산한다.
- radix-4 부스 부호화로 부호 있는 곱셈의 부분곱을 절반으로 줄이는 원리를 익힌다.
- 로그 시프터, 플래그(Z·N·C·V)를 내는 ALU, 비교기·LZC를 짓고, IEEE 754 부동소수점의 형식과 덧셈기 구조를 이해한다.
곱셈은 덧셈의 묶음: 부분곱
초등학교에서 배운 손 곱셈을 떠올려 보자. 13 × 11을 할 때 승수의 자리마다 피승수에 그 자리 숫자를 곱해 한 칸씩 밀어 적고, 마지막에 세로로 더한다. 각 줄을 부분곱(Partial product)이라 한다. 10진수에서는 줄마다 구구단이 필요하지만, 2진수에서는 승수의 자리가 0 아니면 1이다. 그래서 부분곱은 피승수 그대로이거나 0이고, 한 비트로 보면 그냥 AND 게이트다.
아래 그림에서 A와 B의 비트를 눌러 보자. 승수 비트 \(b_j\)가 1인 줄에는 A가 그대로 복사되고, 0인 줄은 통째로 0이 된다. 각 점은 AND 게이트 하나의 출력이며, 같은 세로줄(같은 자리값 \(2^{i+j}\))의 점끼리 더해야 곱이 된다. 이 "점을 세로로 더하는 문제"가 곱셈기 설계의 전부다.
부분곱 행렬은 평행사변형 모양이다. 가운데 자리(자리값 \(2^{N-1}\))에 점이 가장 많아 N개가 쌓이고, 양 끝으로 갈수록 줄어든다. 점을 만드는 데는 AND 게이트 한 단(약 5 ps)이면 충분하다. 비용은 점을 더하는 데서 생긴다.
가장 작은 곱셈기는 덧셈기 하나와 레지스터로 만든다. 매 클럭 B의 한 비트를 보고 A를 더할지 말지 정한 뒤 누산기를 한 칸 민다. N비트 곱에 N 클럭이 걸리지만 면적은 덧셈기 하나다. 작은 마이크로컨트롤러나 암호 가속기의 큰 정수 곱셈에서 지금도 쓰인다. 반대로 고성능 CPU는 N² 개의 점을 한꺼번에 더하는 병렬 곱셈기를 쓰고, 그 방법이 이 장의 나머지 절반이다.
배열 곱셈기: 가산기 격자
점을 더하는 가장 직접적인 방법은 부분곱 줄을 하나씩 차례로 더하는 것이다. 덧셈기 N−1개를 위아래로 쌓으면 손 곱셈과 똑같이 생긴 격자가 된다. 이것이 배열 곱셈기(Array multiplier)다. 각 줄을 리플 캐리 덧셈기로 만들면 캐리가 옆으로도 흘러 경로가 길어지므로, 보통은 캐리를 옆이 아니라 아래 줄로 넘긴다. 줄마다 전가산기 출력이 합(sum)과 캐리(carry) 두 벡터로 남고, 맨 아래에서 한 번만 캐리를 전파하는 덧셈기로 마무리한다.
아래 격자에서 시간을 흘려 보자. 각 칸은 반가산기(HA) 또는 전가산기(FA)이고, 색이 칠해지는 순간이 그 칸의 출력이 확정되는 시각이다. 대각선으로 내려가는 선은 합, 수직선은 캐리다. 신호가 격자를 사선으로 훑으며 내려가고, 마지막 줄의 리플 덧셈에서 다시 옆으로 흐른다.
배열 곱셈기의 장점은 규칙성이다. 같은 셀을 바둑판처럼 깔면 되고 배선이 이웃끼리만 이어진다. 단점은 64비트라면 줄 63개를 지나야 한다는 것이다. DG-7에서 대략 63×15 + 63×10 ≈ 1.6 ns, 4 GHz 클럭 여섯 주기가 넘는다. 줄을 차례로 더하지 말고 한꺼번에 여러 줄씩 줄여야 한다.
부분곱 줄이기: 캐리 저장 덧셈과 트리
열쇠는 전가산기를 다시 보는 것이다. 전가산기는 같은 자리의 세 비트를 받아 합 비트(같은 자리)와 캐리 비트(한 자리 위)를 낸다. 즉 세 개의 비트를 두 개로 줄이는 장치, 3:2 압축기(3:2 compressor)다. 전가산기를 자리마다 하나씩 나란히 놓고 캐리를 옆 칸에 넘기지 않으면, 세 개의 수가 합 벡터 S와 캐리 벡터 C 두 개로 줄어든다. 이것이 캐리 저장 덧셈기(Carry-save adder, CSA)다.
CSA를 나무처럼 엮으면 N개의 부분곱 줄을 \(N \to \tfrac{2}{3}N \to \tfrac{4}{9}N \to \cdots \to 2\)로 줄일 수 있다. 두 줄이 남으면 그때 단 한 번, 빠른 캐리 전파 덧셈기(Carry-propagate adder, CPA)(11장의 프리픽스 덧셈기)로 더한다. 1964년 월리스 트리(Wallace tree)는 매 단계 가능한 한 많은 비트를 3개씩 묶어 줄였다. 1965년 다다(Luigi Dadda)는 반대로 필요한 만큼만 줄이는 방법을 제안했다. 높이 목표를 \(2, 3, 4, 6, 9, 13, 19, \ldots\)(앞 값의 1.5배 내림)으로 정하고, 각 단계에서 열 높이가 다음 목표를 넘지 않을 만큼만 전가산기·반가산기를 쓴다. 단계 수는 같지만 반가산기가 훨씬 적다. 이것이 다다 트리(Dadda tree)다.
4×4 곱셈기의 다다 트리는 2단계로 부분곱을 두 줄로 줄인다. 16×16 곱셈기는 몇 단계가 필요할까? 8단계(2배), 6단계, 16단계 중 무엇일까? 아래 시뮬레이터로 확인하기 전에 예측해 보자.
답 보기
6단계다. 다다 목표 수열 2, 3, 4, 6, 9, 13, 19에서 16을 넘지 않는 가장 큰 값은 13이고, 16 → 13 → 9 → 6 → 4 → 3 → 2로 6번 줄인다. 한 단계마다 높이를 약 2/3로 줄이므로 단계 수는 \(\log_{1.5}(N/2)\)에 비례한다. 폭이 4배가 되어도 단계는 3배밖에 늘지 않고, 64비트에서도 10단계면 된다.
점 다이어그램으로 보는 부분곱 축소
세 방식을 비교해 보면 성격이 뚜렷하다. 배열은 단계가 N−2개로 길지만 매 단계 이웃끼리만 연결된다. 월리스는 처음부터 공격적으로 줄여 최종 CPA가 짧아지는 대신 반가산기를 많이 쓴다. 다다는 전가산기 수가 같으면서 반가산기를 최소로 쓰고 대신 최종 CPA가 조금 넓다. 실제 설계에서 셀 수만큼 중요한 것은 배선이다. 트리는 서로 멀리 떨어진 열과 단계를 엮어 배선이 불규칙하고 길어지므로, 레이아웃을 정돈하기 쉬운 4:2 압축기 기반 트리나 단계를 열 단위로 재배치하는 방법이 쓰인다.
전가산기 두 개를 엮으면 같은 열의 비트 4개(+옆 열에서 오는 캐리 1개)를 2개로 줄이는 4:2 압축기가 된다. 옆 열로 나가는 캐리가 들어오는 캐리에 의존하지 않도록 짜면 지연이 FA 두 개가 아니라 XOR 약 3단(≈ 1.5 FA)으로 줄고, 무엇보다 트리가 이진 트리 모양이 되어 배선이 규칙적이다. 64비트 곱셈기의 부분곱 32줄(부스 부호화 후)은 4:2 압축기 4단(32→16→8→4→2)으로 줄일 수 있다. 많은 상용 곱셈기가 이 구조를 쓴다.
부스 부호화: 부분곱을 절반으로
트리의 크기와 깊이는 부분곱 줄 수가 정한다. 줄 수 자체를 줄일 수는 없을까? 승수를 2비트씩 묶어 4진수로 보면 각 자리는 0, 1, 2, 3이다. 0·1·2배는 쉽다(2배는 한 칸 시프트). 문제는 3배다. 3A = 2A + A는 그 자체로 덧셈이 필요한 "어려운 배수"다. 부스 부호화(Booth encoding)는 3을 4 − 1로 바꿔 쓴다. 다음 자리에 1을 올리고 이 자리에서는 −1을 쓰면, 모든 자리가 \(\{-2, -1, 0, +1, +2\}\) 안에 들어온다.
회로로는 더 깔끔하다. 승수 비트를 겹치는 3비트 창 \((b_{2i+1}, b_{2i}, b_{2i-1})\)으로 훑고(\(b_{-1} = 0\)), 각 창에서 바로 자리 숫자를 읽는다.
| \(b_{2i+1}\) | \(b_{2i}\) | \(b_{2i-1}\) | 자리 숫자 \(d_i\) | 부분곱 | 회로 동작 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 선택 |
| 0 | 0 | 1 | +1 | +A | A 선택 |
| 0 | 1 | 0 | +1 | +A | A 선택 |
| 0 | 1 | 1 | +2 | +2A | A를 한 칸 시프트 |
| 1 | 0 | 0 | −2 | −2A | 2A 반전 + 1 |
| 1 | 0 | 1 | −1 | −A | A 반전 + 1 |
| 1 | 1 | 0 | −1 | −A | A 반전 + 1 |
| 1 | 1 | 1 | 0 | 0 | 0 선택 |
부스 부호화의 대가는 부분곱 생성기다. 각 줄에서 0, ±A, ±2A 중 하나를 고르는 5:1 MUX와 반전 회로가 필요하고, 음수 부분곱의 "+1"은 다음 줄의 빈자리에 끼워 넣는다. 그래도 트리의 전가산기가 거의 절반으로 줄고 단계도 하나 이상 줄어 이득이 크다. 64비트 곱셈기는 거의 모두 radix-4 부스를 쓴다. radix-8(4비트 창)은 줄을 1/3로 줄이지만 3A라는 어려운 배수를 미리 덧셈으로 만들어 두어야 해서, 지연보다 면적·전력이 중요한 곳에서만 쓰인다.
음수 부분곱은 2의 보수라서 맨 위 비트를 결과 폭(2N)까지 복사해야 한다. 그림의 흐린 비트들이다. 그대로 두면 왼쪽 열이 부호 비트로 가득 차 트리가 커진다. 실제 곱셈기는 각 줄의 부호 비트 s를 반전해 \(\bar{s}\)로 두고 앞에 상수 1을 몇 개 붙이는 기법(sign-extension prevention)으로, 모든 줄의 부호 확장을 미리 계산한 상수 하나로 바꾼다. 같은 원리가 부호 있는 배열 곱셈기의 Baugh–Wooley 방식에도 쓰인다.
시프터: 한 번에 여러 칸 밀기
시프트는 곱셈·나눗셈의 2의 거듭제곱 버전이자, 부동소수점의 정렬과 정규화, 비트 필드 추출에 쓰인다. 조합 회로로 0~N−1칸을 한 번에 미는 방법은 두 가지다. 배럴 시프터(Barrel shifter)는 출력 비트마다 N:1 MUX를 두어 시프트 양을 디코드한 신호로 한 번에 고른다. 지연은 전송 게이트 하나 수준으로 짧지만 MUX 입력이 \(N^2\)개이고 배선이 격자로 교차한다. 로그 시프터(Logarithmic shifter)는 시프트 양을 2진수로 보고, 비트 \(k_j\)가 1이면 \(2^j\)칸 미는 2:1 MUX 단을 \(\log_2 N\)개 잇는다.
한 회로로 네 연산을 다 하는 것도 어렵지 않다. 오른쪽 시프트는 입력과 출력의 비트 순서를 뒤집은 왼쪽 시프트이고, 회전은 빈자리에 0 대신 밀려난 비트를 넣는 것이며, 산술 시프트는 부호 비트를 넣는 것이다. 각 MUX 단의 "빈자리 채움" 입력만 고르면 된다. 고성능 코어의 시프터는 1클럭 안에 들어가며(64비트 6단 MUX ≈ 6~8 FO4), 부동소수점 덧셈기의 정렬·정규화 시프터도 같은 구조다.
ALU: 덧셈기 + 논리 + MUX, 그리고 플래그
산술 논리 장치(Arithmetic logic unit, ALU)는 새로운 회로가 아니다. 덧셈기 하나, 비트별 논리 게이트 묶음, 시프터를 나란히 놓고 결과 MUX로 하나를 고른다. 뺄셈은 따로 만들지 않는다. \(A - B = A + \bar{B} + 1\)이므로 B 앞에 XOR(조건부 반전)를 두고 캐리 입력에 1을 넣으면 같은 덧셈기가 뺄셈기가 된다.
결과 옆에는 네 개의 상태 플래그(Status flags)가 나온다. 분기 명령은 이 플래그만 보고 판단한다.
플래그를 내는 ALU
8비트에서 112 + 80 = 192 = 0xC0이다. 부호 없는 수로는 255 이하이니 문제없다. 그렇다면 플래그 C와 V는 각각 무엇일까? 부호 있는 수로 읽으면 결과는 몇인가?
답 보기
C = 0, V = 1이다. 맨 위 자리에서 캐리가 나가지 않았으므로 부호 없는 덧셈은 정상(192)이다. 그러나 양수(+112) + 양수(+80)인데 결과의 부호 비트가 1이라 부호 있는 수로는 −64가 되었다. 같은 비트 패턴, 같은 덧셈기인데 어떤 플래그를 볼지는 프로그램이 수를 어떻게 해석하는지에 달려 있다. 그래서 C 언어의 unsigned 비교와 signed 비교는 서로 다른 분기 명령(예: ARM의 BHI와 BGT)으로 번역된다.
| 비교 (A − B 후) | 부호 없는 수 | 부호 있는 수 |
|---|---|---|
| A = B | Z = 1 | |
| A < B | C = 0 (빌림 발생) | N ⊕ V = 1 |
| A ≥ B | C = 1 | N ⊕ V = 0 |
| A > B | C = 1 그리고 Z = 0 | Z = 0 그리고 N = V |
논리 연산은 게이트 한두 단, 결과 MUX도 두세 단이다. ALU의 임계 경로는 거의 항상 B 반전 XOR → 덧셈기의 캐리 체인 → 결과 MUX → Z 플래그의 OR 트리로 이어진다. 그래서 고성능 코어는 64비트 덧셈기를 프리픽스 구조로 짓고, Z 플래그는 결과가 나오기를 기다리지 않고 A, B에서 직접 "A + B = 0인가"를 계산하는 회로로 앞당기기도 한다. 이 경로가 1클럭(약 15~20 FO4 이내)에 들어가야 연속된 덧셈 명령이 매 클럭 결과를 넘겨받을 수 있다. 데이터패스가 파이프라인과 바이패스 안에서 어떻게 쓰이는지는 SoCBook의 CPU 장에 있다.
비교기, 제로 검출, 선행 0 세기
크기 비교는 ALU의 뺄셈으로 할 수 있지만, 비교만 필요할 때는 더 싼 회로가 있다. 같음 비교는 비트마다 XNOR를 하고 전부 AND하면 된다. 크기 비교는 맨 위 비트부터 보다가 처음으로 다른 비트에서 결정된다. 그 자리에서 A가 1이면 A가 크다. 이 "처음 다른 자리 찾기"는 덧셈기의 캐리 전파와 같은 모양이라 프리픽스 트리로 \(O(\log N)\)에 끝난다.
제로 검출(Zero detection)은 N비트를 모두 OR한 뒤 반전하는 것, 즉 OR 트리다. 선행 0 개수(Leading zero count, LZC)는 맨 위에서부터 처음 1이 나오기 전까지 0이 몇 개인지 센다. 부동소수점 덧셈 후 결과를 정규화할 때 "왼쪽으로 몇 칸 밀어야 하는가"가 바로 LZC다. LZC도 트리로 짓는다. 두 반쪽의 (모두 0인가, LZC)를 합치는 규칙이 단순해서 16비트는 4단이면 된다.
2의 보수에서 부호 비트는 \(-2^{N-1}\)의 가중치를 가진다. 그래서 부호 있는 비교기는 두 수의 맨 위 비트를 반전한 뒤 부호 없는 비교기에 넣으면 된다(−128~127 범위가 0~255로 순서를 유지하며 옮겨진다). 같은 원리로 IEEE 754 부동소수점도 부호 비트만 따로 처리하면 나머지 비트를 정수처럼 비교해 크기 순서를 알 수 있다. 지수가 가수보다 위에 있도록 형식을 설계한 이유 중 하나다.
고정소수점과 부동소수점
정수 회로로 실수를 다루는 가장 간단한 방법은 소수점을 한 자리에 고정해 두는 고정소수점(Fixed point)이다. Q1.15 형식이라면 16비트 정수를 \(2^{15}\)으로 나눈 값으로 읽는다. 덧셈·곱셈 회로는 정수와 똑같고 곱한 뒤 시프트만 하면 되므로, DSP와 저전력 신호 처리에서 널리 쓰인다. 단점은 범위다. 아주 큰 수와 아주 작은 수를 같은 형식에 담을 수 없다.
부동소수점(Floating point)은 과학 표기법(1.23 × 10⁵)의 2진 버전이다. IEEE 754 형식은 비트를 부호 s, 지수 e, 가수 f 세 칸으로 나눈다. 지수는 바이어스를 더해 부호 없는 정수로 저장하고, 정규화된 수의 맨 앞 1은 항상 1이므로 저장하지 않는다(숨은 비트).
같은 16비트라도 FP16과 BF16은 성격이 정반대다. FP16은 가수가 10비트라 정밀하지만 최대가 65504에 불과해, 신경망 학습 중 기울기가 쉽게 넘치거나 0으로 사라진다. Google이 TPU에 도입한 BF16(Brain floating point)은 FP32의 지수 8비트를 그대로 두고 가수를 7비트로 잘랐다. 범위는 FP32와 같고 정밀도는 낮다. 신경망은 정밀도보다 범위에 민감해서, BF16은 FP32 학습을 거의 그대로 흉내 내며 곱셈기 면적은 가수 폭의 제곱에 비례하므로(8×8 대 24×24) 약 1/9로 줄어든다.
2022년 이후 GPU·NPU는 8비트 부동소수점을 지원한다. E4M3(정밀도 우선, 최대 448)는 추론과 순전파에, E5M2(범위 우선, FP16과 같은 지수)는 역전파 기울기에 쓰는 식이다. 더 나아가 MX(microscaling) 형식은 32개 원소가 지수 하나를 공유하게 해 4~6비트 원소로도 정확도를 지킨다. 형식이 작아질수록 MAC 하나의 면적과 에너지가 빠르게 줄어 같은 칩에 더 많은 곱셈기를 넣을 수 있다. 이런 저정밀 형식을 쓰는 NPU의 MAC 배열과 데이터 흐름은 SoCBook의 NPU 장에서 다룬다.
부동소수점 덧셈기: 정렬, 덧셈, 정규화, 반올림
부동소수점 곱셈은 의외로 쉽다. 가수끼리 곱하고(정수 곱셈기) 지수끼리 더하면 된다. 어려운 쪽은 덧셈이다. 지수가 다른 두 수는 자리를 맞춰야 더할 수 있다. 덧셈기는 네 단계로 일한다. ① 지수를 비교해 작은 수의 가수를 차이 Δe만큼 오른쪽으로 민다(정렬 시프터). 밀려 나가는 비트는 버리지 않고 가드(G)·라운드(R)·스티키(S) 비트로 요약한다. ② 가수를 더하거나 뺀다. ③ 결과의 맨 앞 1이 제자리에 오도록 민다(캐리가 나면 오른쪽 1칸, 상쇄되어 앞이 0이면 LZC만큼 왼쪽). ④ G·R·S를 보고 가장 가까운 짝수로 반올림(Round to nearest, ties to even)한다.
FP16의 가수는 숨은 비트까지 11비트다. 2048 = 2¹¹에 1을 더하면 2049가 될까? 2048 + 3은?
답 보기
2048 + 1 = 2048이다. 2048 근처에서 FP16의 간격(ulp)은 2라서 2049는 2048과 2050의 정확히 한가운데이고, 짝수 쪽(가수 끝 비트가 0인 2048)으로 반올림된다. 2048 + 3 = 2051도 한가운데지만 이번에는 2052가 짝수 쪽이라 2052가 된다. 반올림 방향이 매번 같으면 오차가 한쪽으로 쌓이므로, IEEE 754는 "짝수로"를 기본으로 정해 평균 오차를 0으로 만든다. 큰 수에 작은 수를 계속 더하는 누산이 저정밀 형식에서 위험한 이유이기도 하다. 그래서 MAC은 곱은 FP8·BF16으로 하더라도 누산은 FP32로 하는 경우가 많다.
현대 프로세서는 곱셈과 덧셈을 따로 하지 않고 \(A\times B + C\)를 반올림 한 번으로 계산하는 융합 곱셈-덧셈(Fused multiply-add, FMA)을 기본 연산으로 쓴다. 곱셈기 트리의 마지막 두 줄에 정렬된 C를 세 번째 줄로 넣어 3:2 압축을 한 번 더 하면 되므로, 곱셈 뒤 덧셈을 따로 하는 것보다 빠르고 정확하다. 행렬 곱셈, 즉 AI 연산의 기본 단위인 MAC이 바로 이것이다.
실제 수치: 지연과 에너지
지금까지의 구조가 실제 칩에서 어느 정도 비용인지 대략값으로 보자. 먼저 연산 지연이다. 64비트 정수 곱셈은 부스 부호화 + 압축기 트리 + 128비트 CPA로, 4~5 GHz 코어에서 파이프라인 3~4단(약 0.7~0.9 ns)에 나뉘어 들어간다. 그래도 매 클럭 새 곱셈을 시작할 수 있다(처리량 1/클럭).
| 연산 (64비트 범용 코어) | 지연 (클럭, 약) | 처리량 (약) | 비고 |
|---|---|---|---|
| 정수 덧셈·논리·시프트 | 1 | 클럭당 4개 내외 | ALU 여러 개, 바이패스로 다음 명령이 바로 사용 |
| 정수 곱셈 (64×64 → 64) | 3~4 | 클럭당 1개 | x86 서버·데스크톱 코어 약 3 (2015~), ARM 고성능 코어 약 2~4 |
| 부동소수점 곱셈·FMA (FP64) | 4~5 | 클럭당 2개 | FMA 유닛 두 개가 흔하다 (2015~) |
| 정수 나눗셈 (64비트) | 약 15~90 | 수십 클럭당 1개 | 반복형(SRT 등)이라 값에 따라 다름. 최근 코어일수록 짧다 |
다음은 에너지다. Horowitz가 2014년 ISSCC 기조 강연에서 정리한 45 nm 공정 수치는 지금도 "연산은 싸고 데이터 이동은 비싸다"를 설명할 때 가장 많이 인용된다. 곱셈 에너지가 비트 폭의 제곱에 가깝게 늘고, 부동소수점이 정수보다 비싸며, 그보다 메모리 접근이 수십~수백 배 비싸다는 것을 보자.
| 연산 (45 nm, 0.9 V) | 에너지 (약) | 면적 (약) |
|---|---|---|
| 8비트 정수 덧셈 | 0.03 pJ | 36 µm² |
| 32비트 정수 덧셈 | 0.1 pJ | 137 µm² |
| 8비트 정수 곱셈 | 0.2 pJ | 282 µm² |
| 32비트 정수 곱셈 | 3.1 pJ | 3495 µm² |
| 16비트 부동소수점 덧셈 | 0.4 pJ | 1360 µm² |
| 16비트 부동소수점 곱셈 | 1.1 pJ | 1640 µm² |
| 32비트 부동소수점 덧셈 | 0.9 pJ | 4184 µm² |
| 32비트 부동소수점 곱셈 | 3.7 pJ | 7700 µm² |
| 64비트 캐시(SRAM 8 KB) 읽기 | 10 pJ | — |
| 64비트 DRAM 읽기 | 1300~2600 pJ | — |
출처: M. Horowitz, "Computing's Energy Problem (and what we can do about it)," ISSCC 2014 기조 강연의 45 nm 추정치. 7 nm급(DG-7 수준) 공정에서는 연산 에너지가 대략 3~5배 줄었지만 상대적인 순서는 그대로다. 8비트 곱셈이 32비트 FP 곱셈보다 약 18배 싸다는 사실이 AI 가속기가 INT8·FP8로 내려가는 이유다.
핵심 정리
- 2진 곱셈의 부분곱은 AND 행렬이다. N×N 곱은 부분곱 비트 \(N^2\)개를 만들고, 곱셈기 설계는 이 점들을 세로로 더하는 문제다.
- 배열 곱셈기는 규칙적이지만 지연이 \(O(N)\)이다. 전가산기를 3:2 압축기로 쓰는 캐리 저장 덧셈은 캐리를 기다리지 않아 폭과 무관하게 FA 하나의 지연이다.
- 월리스·다다 트리는 높이를 단계마다 약 2/3로 줄여 \(O(\log N)\) 단계(16비트 6단, 64비트 10단) 뒤 최종 CPA 한 번으로 끝낸다. 다다는 반가산기를 최소로 쓴다.
- radix-4 부스 부호화는 겹치는 3비트 창으로 자리 숫자 \(\{-2,\dots,+2\}\)를 만들어 부분곱을 N/2줄로 줄이고, 부호 있는 곱셈을 보정 없이 처리한다.
- 로그 시프터는 \(\log_2 N\)단의 2:1 MUX로 모든 시프트·회전을 한다. ALU는 덧셈기 + 논리 + 시프터 + MUX이며, 플래그 Z·N·C·V로 부호 없는/있는 비교를 모두 판단한다.
- 비교·제로 검출·LZC는 모두 트리로 \(O(\log N)\)에 끝나며, LZC는 부동소수점 정규화의 시프트 양이다.
- IEEE 754는 부호·지수·가수로 범위와 정밀도를 나누고, FP 덧셈은 정렬·덧셈·정규화·반올림(짝수로)을 거친다. BF16·FP8은 범위나 면적을 위해 가수를 줄인 형식이며, 메모리 접근이 연산보다 훨씬 비싸다.
확인 퀴즈
Q1. 부호 없는 8비트 × 8비트 병렬 곱셈기(부스 부호화 없음)가 만드는 부분곱 비트는 몇 개이고, 곱의 폭은?
Q2. 캐리 저장 덧셈기(CSA)의 지연이 비트 폭과 무관한 이유는?
Q3. 열 최대 높이가 9인 점 배열을 다다 방식으로 2줄까지 줄이려면 몇 단계가 필요한가?
Q4. radix-4 부스 부호화에서 창 \((b_{2i+1}, b_{2i}, b_{2i-1}) = (1, 1, 0)\)이 고르는 부분곱은?
Q5. 8비트 ALU에서 0x90 + 0x90을 계산했다. 플래그 C와 V는?
Q6. BF16이 FP16보다 신경망 학습에 유리한 가장 큰 이유는?