저전력 AI 반도체 스타트업에서 NPU(Neural Processing Unit) 아키텍처를 설계하며 가장 뼈저리게 느끼는 점은, “소프트웨어 엔지니어의 세계와 하드웨어 엔지니어의 세계는 완전히 다르다”는 것입니다. 파이썬(Python) 기반의 AI 프레임워크는 무한대에 가까운 정밀도를 가진 실수(Floating-point, float32) 연산을 아무렇지 않게 수행합니다. 하지만 발열 문제를 촌각을 다투며 해결해야 하는 저전력 엣지(Edge) FPGA 환경에서 실수 연산기를 그대로 구현하는 것은 면적과 전력 측면에서 ‘자살 행위’나 다름없습니다.
결국 하드웨어 엔지니어의 핵심 역량은 이 무거운 실수 연산을 빠르고 가벼운 정수 연산(Integer / Fixed-Point Arithmetic)으로 얼마나 우아하게 변환해 내느냐에 달렸습니다. 이번 글에서는 MAC(Multiply-Accumulate) 연산기를 설계하면서 적용했던 ‘진짜 하드웨어 최적화’ 스킬 3가지를 정리해 보겠습니다.
1. 고정 소수점(Fixed-Point)과 시프트(Shift)
AI 모델의 양자화(Quantization) 파라미터는 보통 0.00379…와 같은 복잡한 실수입니다. 하드웨어에서 이 값을 곱하려면 어떻게 해야 할까요? 소수점을 없애기 위해 큰 수를 곱해서 정수로 만든 뒤, 나중에 다시 그 수만큼 나누어 스케일을 맞추는 Q-Format(고정 소수점) 방식을 사용합니다.
하지만 하드웨어에서 나눗셈기(Divider)는 곱셈기보다 훨씬 크고 느립니다. 그래서 우리는 항상 2N (주로 16이나 32) 단위로 스케일링을 합니다. 나눗셈 연산을 비용이 ‘0’에 수렴하는 비트 시프트(>> 16)로 대체하기 위해서입니다.
2. 하드웨어 해커의 반올림(Rounding) 기법
단순한 우측 시프트(>>)는 소수점 이하를 가차 없이 버리는 내림(Truncation/Floor) 연산입니다. 이 오차가 누적되면 AI 모델의 정확도(Accuracy)가 심각하게 떨어집니다. 따라서 우리는 반올림(Round Half Up)을 구현해야 합니다.
소프트웨어적인 사고방식으로 코드를 짜면 보통 이렇게 됩니다.
// 하드웨어 자원을 낭비하는 코드 (Bad)
shifted = (scaled + 64'sd32768) >>> 16;216의 절반인 32768을 미리 더해서 올림 효과를 내는 정석적인 방법입니다. 하지만 하드웨어 관점에서 이 코드는 끔찍합니다. 이 덧셈 하나를 위해 거대한 64비트 가산기(Adder)가 추가로 합성되기 때문입니다. 면적이 커지고 타이밍(Critical Path)이 나빠집니다.
진정한 하드웨어 최적화 코드:
// 면적과 타이밍을 모두 잡은 최적화 코드 (Good)
shifted = (scaled >>> 16) + scaled[15];우리가 216으로 나눌 때, 버려지는 소수 부분의 가장 큰 비트(0.5에 해당하는 자리)는 바로 15번째 비트(scaled[15])입니다. 이 비트가 1이라는 것은 소수점 이하가 0.5 이상이라는 뜻입니다.
따라서 32768을 더하는 무거운 덧셈기를 쓸 필요 없이, 시프트를 먼저 한 정수 결과값의 최하위 비트에 scaled[15]를 Carry-in처럼 쏙 더해주면 끝입니다. 수학적으로 100% 동일한 반올림 결과를 내면서도, 합성 툴은 이를 추가 덧셈기 없이 기존 DSP 내부의 기능만으로 깔끔하게 처리합니다. 이것이 바로 하드웨어 엔지니어의 코딩 스킬입니다.
3. DSP48E2 슬라이스 100% 매핑
로직을 짜다 보면 오버플로우(Overflow)가 두려워 습관적으로 변수를 logic signed [63:0] 처럼 64비트로 큼직하게 잡는 경우가 많습니다.
하지만 자일링스(Xilinx) FPGA에 내장된 고성능 수학 연산 블록인 DSP48E2 슬라이스의 기본 출력 폭은 48비트입니다. 만약 변수를 64비트로 선언해 버리면, Vivado는 이 연산을 처리하기 위해 값비싼 DSP 슬라이스를 2개 이상 이어 붙이거나, 수많은 LUT를 낭비해서 억지로 64비트 계산기를 만들어냅니다.
localparam logic signed [14:0] OUT_SCALE = 15'sd12700;
function automatic logic [26:0] compute_out(input logic [32:0] acc_in);
// 64-bit 대신 48-bit를 사용하여 DSP48E2에 완벽히 매핑 (Best)
logic signed [47:0] scaled, shifted;
begin
// 33-bit * 15-bit = 48-bit (안전하게 핏!)
scaled = $signed(acc_in) * OUT_SCALE;
shifted = (scaled >>> 16) + scaled[15];
compute_out = shifted[26:0];
end
endfunction실제 입력되는 데이터(acc_in)와 곱해지는 스케일 상수(OUT_SCALE)의 비트 폭을 수학적으로 엄밀하게 계산해 보세요. 최대값이 48비트 안에 들어간다면, 위와 같이 [47:0]으로 폭을 맞춰주어야 합니다. 이렇게 하면 정확히 1개의 DSP 슬라이스로 곱셈과 덧셈(반올림)이 완벽하게 합성되어, 전력 소모를 극단적으로 줄일 수 있습니다. 발열 제어가 생명인 저전력 AI 반도체에서는 선택이 아닌 필수입니다.
참고: AMD