AI 에이전트로 RTL 만드는 실전 구성: AGENTS.md, spec, tasks, testbench, 검증 루프를 어떻게 나눌 것인가
AI 에이전트로 RTL을 만들 때 필요한 실전 구성을 공개한다. AGENTS.md, spec, tasks, testbench, 검증 루프를 어떻게 나누면 에이전트가 헤매지 않는지 정리했다.
핵심 결론 AI 에이전트에게 "FIFO 하나 만들어줘"라고 던지면 데모는 나오지만, 재사용 가능한 RTL은 나오지 않는다. 차이는 모델 성능이 아니라 폴더 구조와 문서 분리에서 갈린다. AGENTS.md에는 변하지 않는 대전제만, spec에는 무엇을 만들지, tasks에는 이번에 무엇을 할지를 분리해야 에이전트가 흔들리지 않는다. testbench와 검증 루프가 없는 RTL generation은 문법이 맞는 코드를 양산할 뿐이며, lint, simulation, synthesis까지 닫힌 루프를 만들 때 비로소 실무에 쓸 수 있다.
왜 프롬프트 한 줄로는 부족한가
생성형 AI가 Verilog와 SystemVerilog를 그럴듯하게 쓰는 것은 이미 확인됐다. 문제는 그 다음이다. 파라미터 이름이 파일마다 달라지고, reset 극성이 섞이고, ready/valid handshake의 가정이 모듈마다 달라지고, 에이전트가 지난번에 만든 인터페이스를 다음번에는 기억하지 못한다. 코드가 길어질수록 모순은 누적된다. 사람이 리뷰해서 고치는 비용이 직접 쓰는 비용을 넘어서는 순간, 그 구성은 실패한 것이다.
해법은 프롬프트를 길게 쓰는 것이 아니다. 해법은 역할을 분리하는 것이다. 반도체 설계에서 요구사항 문서, 마이크로아키텍처 문서, 작업 지시서, 검증 계획이 따로 있는 이유와 같다. 에이전트에게도 같은 분리가 필요하다. 어떤 파일이 진실의 원천인지, 어떤 파일을 에이전트가 수정해도 되고 어떤 파일을 건드리면 안 되는지, 완료의 정의가 무엇인지를 파일 구조 자체로 강제해야 한다.
이 글의 구성은 특정 툴에 종속되지 않는다. CLI 기반 코딩 에이전트든 IDE에 붙은 에이전트든, 원리는 같다. 폴더와 문서의 분리가 먼저이고, 모델 선택은 그 다음이다.
실전 폴더 구조: 최소 단위는 이렇게 잡는다
아래 구조는 작은 IP 하나를 기준으로 한 최소 구성이다. 규모가 커지면 모듈 단위로 같은 패턴을 반복한다.
my_ip/
├── AGENTS.md # 대전제: 모든 작업에 적용, 자주 바꾸지 않음
├── README.md # 사람용 개요, 빌드/실행 방법
├── spec/
│ ├── spec.md # 기능 요구사항: 무엇을 만들 것인가
│ ├── interface.md # 포트, 프로토콜, 타이밍 다이어그램 설명
│ └── params.md # 파라미터, 기본값, 허용 범위
├── tasks/
│ ├── TASK-001-fifo-core.md # 이번 작업 단위: 범위, 완료 조건
│ └── TASK-002-cdc-check.md # 다음 작업 단위
├── rtl/
│ ├── my_fifo.sv
│ └── my_ip_top.sv
├── tb/
│ ├── tb_my_fifo.sv # self-checking testbench
│ ├── drivers/ # bus driver, monitor
│ └── tests/ # 시나리오별 테스트
├── constraints/
│ └── my_ip.sdc # synthesis / STA용 제약
├── scripts/
│ ├── run_lint.sh
│ ├── run_sim.sh # 컴파일 + 시뮬레이션 + pass/fail 판정
│ └── run_synth.sh # Yosys 또는 상용 툴 합성 확인
├── build/ # 생성물 전용, 에이전트 수동 편집 금지
└── docs/
└── decisions.md # 설계 결정과 이유의 기록
핵심 규칙은 세 가지다. 첫째, spec/은 에이전트가 임의로 바꾸지 않는다. 요구사항 변경은 사람이 spec을 고친 뒤 tasks를 새로 발행하는 방식으로만 한다. 둘째, build/ 아래 생성물은 손으로 고치지 않는다. 고치고 싶으면 입력 파일을 고치고 다시 실행한다. 셋째, 모든 실행은 scripts/의 진입점으로만 한다. 에이전트가 매번 다른 명령어를 발명하면 재현성이 사라진다.
AGENTS.md에 넣을 대전제: 길게 쓰지 말고, 어기면 깨지는 것만 쓴다
AGENTS.md는 에이전트를 위한 헌법에 가깝다. 프로젝트 소개를 길게 쓰는 곳이 아니다. 어떤 작업을 하든 항상 지켜야 하고, 위반하면 결과물이 틀어지는 규칙만 넣는다. 자주 바꾸면 헌법이 아니다. 분량은 한 페이지 안쪽이 적당하다.
넣어야 할 항목은 다음과 같다.
- 언어와 스타일: SystemVerilog 사용 범위, 네이밍 규칙(클록은
clk, reset은rst_n처럼 극성까지 명시),always_ff와always_comb사용 원칙, latch 생성 금지 같은 코딩 규칙 - 인터페이스 대전제: handshake 신호의 의미, ready가 valid 없이 assert될 수 있는지 여부, backpressure 조건에서 데이터 유지 규칙
- 파라미터 원칙: 파라미터는
params.md에 없는 값을 임의로 만들지 않기, 기본값을 바꾸지 않기 - 수정 범위:
spec/수정 금지, 기존 포트 삭제 금지, 작업 범위 밖 파일 수정 시 먼저 보고하기 - 완료의 정의: lint 통과, 지정된 테스트 전체 통과, 합성 에러 없음, 이 세 가지가 모두 확인되기 전에는 "완료"라고 쓰지 않기
- 출력 형식: 변경 파일 목록, 실행한 명령어, 테스트 결과를 작업 보고에 포함하기
반대로 AGENTS.md에 넣으면 안 되는 것도 있다. 이번 모듈의 상세 기능, 특정 버그의 수정 방법, 일회성 일정 같은 내용은 tasks로 보내야 한다. 대전제와 일회성 지시가 섞이면 에이전트는 무엇이 더 우선인지 판단하지 못하고, 오래된 일회성 지시가 계속 남아 이후 작업을 오염시킨다.
spec과 tasks를 분리하는 이유
spec과 tasks를 나누는 이유는 변경 속도가 다르기 때문이다. spec은 느리게 변한다. FIFO의 깊이 정책, overflow 동작, 동시 read/write 시의 의미 같은 것은 설계의 본질이라 자주 바뀌지 않는다. tasks는 빠르게 변한다. "오늘은 core만 만들고 CDC 검사는 다음 작업으로 미룬다" 같은 범위 조절이 tasks의 역할이다.
두 문서가 합쳐져 있으면 두 가지 사고가 난다. 에이전트가 작업을 빨리 끝내려고 spec의 요구를 슬쩍 완화하는 경우, 그리고 반대로 사소한 작업 변경 때문에 spec 전체를 다시 쓰면서 기존 결정이 유실되는 경우다. 분리하면 리뷰도 쉬워진다. 사람은 spec 변경은 엄격하게, tasks 변경은 가볍게 리뷰하면 된다.
task 파일 하나에는 반드시 네 가지가 있어야 한다. 목표 한 줄, 건드릴 파일의 범위, 명시적인 완료 조건, 그리고 하지 말 것 목록이다. 특히 "하지 말 것"이 중요하다. 에이전트는 빈칸을 채우려는 경향이 강해서, 범위를 명시하지 않으면 관련 없어 보이는 리팩터링과 인터페이스 변경을 함께 들고 온다. 반도체 설계에서 무단 인터페이스 변경은 검증 전체를 다시 돌려야 하는 사고다.
docs/decisions.md도 이 분리를 돕는다. 왜 비동기 reset을 골랐는지, 왜 gray code 포인터를 쓰는지 같은 결정의 이유를 남기면, 다음 에이전트 세션이 그 결정을 번복하려 할 때 근거를 보고 멈출 수 있다. 코드만 있고 이유가 없으면 에이전트는 매번 같은 논쟁을 처음부터 다시 한다.
testbench와 검증 루프가 없으면 왜 무너지는가
RTL generation에서 가장 위험한 착각은 "컴파일이 되면 동작한다"는 생각이다. 컴파일은 문법 검사일 뿐이다. handshake 타이밍이 한 사이클 어긋나도, full 플래그가 경계 조건에서 잘못되어도 컴파일은 통과한다. 에이전트가 쓴 코드를 에이전트 스스로 눈으로 읽고 "맞아 보인다"고 판단하는 루프는 검증이 아니다. 같은 가정을 공유하는 검토자는 같은 blind spot을 공유한다.
그래서 testbench는 RTL과 별도로, 가능하면 spec에서 직접 도출해야 한다. self-checking 구조가 필수다. 입력 드라이버, 예상 출력을 계산하는 reference model 또는 scoreboard, 그리고 pass/fail을 기계적으로 판정하는 checker가 있어야 에이전트가 결과를 해석하지 않고 사실로 받아들일 수 있다. 테스트 결과는 "대체로 동작한다"가 아니라 "테스트 12개 중 12개 통과, assertion 위반 0건"처럼 숫자로 나와야 한다.
검증 루프는 단계적으로 닫는다. 1차는 lint로 구조적 실수를 잡고, 2차는 simulation으로 기능을 확인하고, 3차는 synthesis로 합성 불가능한 구문과 의도하지 않은 latch, 타이밍 구조를 확인한다. 오픈소스 흐름에서는 Yosys 합성과 OpenROAD-flow-scripts(ORFS) 같은 RTL-GDSII 흐름으로 면적과 타이밍의 1차 감각을 얻을 수 있고, 상용 환경에서는 정식 synthesis와 STA로 넘어간다. 어느 쪽이든 핵심은 같다. 에이전트에게 코드 수정 권한과 함께 실행 권한을 주되, 판단 권한은 테스트 결과에만 준다.
| 구성 요소 | 없으면 생기는 일 | 최소 기준 |
|---|---|---|
| AGENTS.md | 세션마다 스타일, 극성, 가정이 바뀜 | 대전제 1페이지, 변경은 드물게 |
| spec/ | 요구가 작업마다 슬쩍 완화됨 | 기능, 인터페이스, 파라미터 분리 |
| tasks/ | 범위 밖 수정, 무단 리팩터링 | 목표, 범위, 완료 조건, 금지 목록 |
| testbench | 컴파일만으로 완료 선언 | self-checking, 숫자 pass/fail |
| 검증 루프 | 합성 불가 코드, 경계 조건 버그 누적 | lint → sim → synth 자동 실행 |
| decisions.md | 같은 설계 논쟁을 매번 반복 | 결정과 이유를 한 줄씩 기록 |
운영하면서 지켜야 할 실전 수칙
첫째, 에이전트에게 한 번에 하나의 task만 준다. 여러 기능을 동시에 시키면 실패했을 때 원인 분리가 불가능해진다. 둘째, 실패 로그는 요약하지 말고 원문을 보여 준다. 에이전트는 로그의 정확한 줄에서 원인을 찾는 데 강하고, 사람이 요약한 설명에서는 약하다. 셋째, 사람이 spec과 testbench를 소유한다. RTL 본문은 에이전트 비중을 높여도 되지만, 요구와 검증 기준까지 에이전트에게 맡기면 시스템 전체가 자기 확증으로 흐른다. 넷째, 주기적으로 에이전트 없이 빌드가 도는지 확인한다. 스크립트가 특정 세션의 임시 파일에 의존하고 있으면 그 구성은 재현 가능하지 않다.
마지막으로, 에이전트 구성은 한 번에 완성하지 않는다. 작은 IP 하나로 루프를 닫고, 실패 패턴을 AGENTS.md와 체크리스트에 반영하면서 키우는 방식이 가장 빠르다. 처음부터 거대한 monorepo 전체를 맡기는 것은 검증 루프가 아니라 도박이다.
FAQ
Q1. AGENTS.md와 README는 무엇이 다른가요? README는 사람 독자를 위한 개요와 실행 방법이고, AGENTS.md는 에이전트가 작업 중 지켜야 할 규칙이다. 독자가 다르면 문서도 나눠야 에이전트가 규칙과 설명을 혼동하지 않는다.
Q2. spec을 코드 주석으로 대체하면 안 되나요? 안 된다. 주석은 코드와 함께 에이전트가 수정해 버릴 수 있고, 인터페이스와 파라미터처럼 여러 파일에 걸친 요구를 한곳에서 볼 수 없다. spec은 코드 밖에 독립 파일로 있어야 한다.
Q3. testbench도 에이전트에게 쓰게 해도 되나요? 초안은 맡길 수 있지만, 검증 기준과 경계 조건 목록은 사람이 spec에서 정해야 한다. RTL을 쓴 같은 세션에 testbench까지 맡기면 같은 가정의 오류를 테스트가 잡지 못할 위험이 커진다.
Q4. 오픈소스 툴만으로도 검증 루프가 되나요? 1차 루프로는 충분하다. lint, 시뮬레이션, Yosys 합성, ORFS를 통한 배치 이후 타이밍 확인까지 연결하면 대부분의 구조적 오류를 조기에 잡을 수 있다. signoff 수준의 판단은 별도 환경이 필요하다.
Q5. 에이전트가 자꾸 범위 밖 파일을 고치면 어떻게 하나요? task 파일에 허용 파일 목록과 금지 목록을 명시하고, AGENTS.md에 범위 밖 수정 시 중단하고 보고하라는 규칙을 넣는다. 그래도 반복되면 스크립트와 권한으로 물리적으로 제한한다.
행동 체크리스트
- ☐ 프로젝트 루트에 AGENTS.md를 만들고 대전제만 한 페이지로 정리한다
- ☐ spec을 기능, 인터페이스, 파라미터 세 파일로 분리한다
- ☐ 진행 중인 작업을 tasks 파일로 만들고 완료 조건과 금지 목록을 적는다
- ☐ rtl, tb, scripts, build 폴더를 분리하고 생성물 수동 편집을 금지한다
- ☐ lint, simulation, synthesis를 각각 한 명령어로 실행되게 만든다
- ☐ testbench를 self-checking 구조로 바꾸고 결과를 숫자로 출력한다
- ☐ 설계 결정이 생길 때마다 decisions.md에 이유와 함께 한 줄씩 남긴다
함께 읽으면 좋은 글
- RTL, AI에 대체될까 — 숫자가 보여 주는 진짜 경계와 커리어 생존 전략
- AI 쓰는데 왜 일이 줄지 않고 늘까 — 생산성 역설의 구조와 반도체 설계 현장
- ChatGPT에 물으면 틀리는 EDA 실무 질문: VCS -mfcu, Vivado hold 진단, 툴 버전 혼용 대처의 확인 순서
📩 VLSI Korea 뉴스레터 구독하기
매일 발행되는 반도체 심층 분석을 이메일로 가장 먼저 받아보세요. 구독은 무료입니다.