---
title: "함수를 펼쳤는데 회로의 면적이 늘어나는 이유"
author: "브레이브브라우저쓰자"
published_at: "2026-10-26T06:51:00.648Z"
updated_at: "2026-10-06T02:33:52.703Z"
tags: ["hls inline"]
canonical_url: "https://ariser.io/@writer_a9817d5a8de0/%ED%95%A8%EC%88%98%EB%A5%BC-%ED%8E%BC%EC%B3%A4%EB%8A%94%EB%8D%B0-%ED%9A%8C%EB%A1%9C%EC%9D%98-%EB%A9%B4%EC%A0%81%EC%9D%B4-%EB%8A%98%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%B4%EC%9C%A0"
ai_assisted: true
---

# 함수를 펼쳤는데 회로의 면적이 늘어나는 이유

이 글은 AI가 활용됐습니다.

[원문](https://ariser.io/@writer_a9817d5a8de0/%ED%95%A8%EC%88%98%EB%A5%BC-%ED%8E%BC%EC%B3%A4%EB%8A%94%EB%8D%B0-%ED%9A%8C%EB%A1%9C%EC%9D%98-%EB%A9%B4%EC%A0%81%EC%9D%B4-%EB%8A%98%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%B4%EC%9C%A0)

짧은 함수를 inline하면 호출 부담이 줄어들 것 같다. HLS에서는 그 함수가 회로 안에서 어떤 경계로 표현되었는지부터 살펴야 한다. 코드 두 줄을 펼치는 변화가 공유하던 계산 블록을 여러 곳에 배치할 기회를 만들 수도 있고, 호출자와 피호출자 사이에 있던 불필요한 계산을 없앨 수도 있다. 같은 inline 지시로 면적이 늘거나 줄어드는 이유가 여기에 있다.

Vitis HLS의 INLINE은 함수를 별도 계층에서 제거하고 호출 함수에 녹여 넣는다. 공식 설명은 함수 자체의 공유·재사용이 사라져 면적이 늘 수 있다고 경고하면서, 내부 연산을 호출자와 함께 최적화하기 쉬워지는 효과도 설명한다. 계층이 사라지는 사실과 최종 곱셈기 개수는 같은 정보가 아니다. 펼쳐진 곱셈들 역시 자원 제약과 스케줄에 따라 하나의 연산기를 공유할 수 있다. [AMD, Inline의 Description](https://docs.amd.com/r/en-US/ug1399-vitis-hls/Inline)

가상의 순수 함수 `f(a,b)=a*b+1`을 두 번 호출하고 결과를 더해 보자. 입력은 모두 준비되어 있고 오버플로는 없다고 하자. 한 f 블록은 곱셈 2사이클과 덧셈 1사이클로 결과를 만들며, 파이프라인되지 않아 한 호출이 끝나야 다음 호출을 받는다고 정한다. 마지막 결과 합산에도 1사이클이 필요하다. 통신과 제어의 추가 지연은 제외한 모형이다.

하나의 f 인스턴스를 공유하면 첫 호출은 경계 0에서 시작해 3에 끝난다. 두 번째 호출은 3에서 시작해 6에 끝나고, 합산 결과는 7에 나온다. 독립된 f 두 개를 두면 두 호출이 모두 0에서 시작해 3에 끝나므로 합산 결과는 4에 나온다. 입력이 (2,3)과 (4,5)라면 어느 쪽이든 7+21=28을 계산한다. 계산 값은 같지만 자원의 수와 동시에 진행할 수 있는 호출 수가 다르다.

이 숫자는 inline을 켜면 반드시 4사이클이 된다는 예측이 아니다. inline 후에도 곱셈기를 하나로 제한하면 연산을 시간에 나누어 배치해야 한다. 반대로 함수 계층을 남겨도 도구가 호출별 인스턴스를 만들면 처음부터 두 블록일 수 있다. 함수가 파이프라인되어 새 호출을 더 자주 받는다면 위의 공유 모델도 달라진다. 먼저 원하는 공유 단위와 호출 간격을 정하고, 결과 보고서에서 실제 계층·인스턴스·시작 시점을 확인해야 한다.

주의할 지점은 지시문의 적용 범위다. AMD 문서에서는 inline된 함수에 적용한 pragma와 directive가 무시된다고 명시한다. ALLOCATION 설명에서도 함수 공유의 대상이 되려면 함수가 inline되지 않아야 한다는 조건을 둔다. 작은 함수가 자동으로 inline될 수 있어, 함수 경계를 유지하려면 INLINE off가 필요할 수 있다. [AMD, Inline](https://docs.amd.com/r/en-US/ug1399-vitis-hls/Inline), [AMD, Allocation의 function 옵션](https://docs.amd.com/r/en-US/ug1399-vitis-hls/Allocation)

따라서 예전에 함수 이름을 대상으로 걸어 둔 제한이 현재 설계에도 작동한다고 가정하면 곤란하다. 변환 뒤 그 함수가 남아 있는지, 제한을 호출자 쪽 연산 범위로 옮겨야 하는지부터 확인할 필요가 있다. 그렇다고 모든 지시를 그대로 부모로 이동하면 같은 의미가 되는 것도 아니다. 부모에는 다른 호출과 연산이 함께 있으므로 제한되는 대상 집합이 달라질 수 있다.

순수 함수의 자원 문제보다 더 섬세한 것은 상태다. 다음은 회로 도구의 실제 리셋 구현을 주장하려는 코드가 아니라, 공유 상태의 언어 의미를 확인하는 예다. 관찰을 시작할 때 c는 0이며 반환 합은 32비트로 계산한다.

```cpp
uint16_t bump() {
    static uint16_t c = 0;
    c = uint16_t(c + 1);
    return c;
}
uint32_t twice() {
    uint16_t p = bump();
    uint16_t q = bump();
    return uint32_t(p) + uint32_t(q);
}
```

두 호출은 별도 문장으로 순서가 정해져 있다. 첫 twice는 c를 1, 2로 바꾸어 3을 반환하고, 다음 twice는 3, 4로 바꾸어 7을 반환한다. inline을 하더라도 같은 함수의 static 상태라는 의미가 유지되어야 한다. 호출 위치마다 c를 독립 복제해 둘 다 0에서 시작하게 만들면 첫 합은 1+1=2가 되어 원래 프로그램과 달라진다. 카운터가 65535를 넘을 때의 16비트 순환도 이 계약의 일부다.

상태를 여러 개로 나누면 더 많은 호출을 병렬 실행할 수 있을지 몰라도, 그것은 별도 상태를 갖는 프로그램으로 설계를 바꾸는 일이다. 회로에서 실제 상태가 언제 초기화되고 리셋되는지는 사용하는 도구의 규칙과 인터페이스를 추가로 확인해야 한다. 소스의 초기값만 보고 매 호출마다 상태가 0이 된다고 생각해서도 안 된다.

HLS inline의 판단 기준은 함수가 짧은지보다 그 경계가 맡은 역할이다. 호출자와 함께 계산을 단순화하려는 경계인지, 공유 블록의 범위를 지키려는 경계인지, 상태를 같은 순서로 갱신해야 하는 경계인지 정리해 보자. 그 뒤에 inline을 적용하면 코드가 펼쳐진 이유와 회로의 면적·지연이 바뀐 이유를 연결해 설명할 수 있다.

