SoC 개발자를 위한 RTL 린트 디자인 룰 Top 20
합성·시뮬레이션 이전에 잡아야 할 치명적 결함 — 원리·위반 결과·해결책 완전 가이드 🔧
수억 개의 게이트를 집적하는 SoC(System-on-Chip) 설계에서, 문법 오류 하나 없는 깔끔한 코드가 정작 실리콘에서는 오작동하는 일이 흔하다. RTL 코드는 결국 물리적 회로로 합성(Synthesis)되기 때문에, 시뮬레이션에서는 멀쩡해 보여도 실제 칩에서는 전혀 다르게 동작할 수 있다. 이 간극을 코딩 단계에서 미리 메우는 1차 방어선이 바로 린트(Lint)다. 현업에서 통용되는 RTL 린트 룰과 코딩 가이드라인을 공식 자료 기반으로 종합해 핵심 20가지로 정리했다.
린트란 무엇이며 왜 1차 방어선인가
린트는 잠재 결함을 합성·시뮬레이션 이전에 정적 분석(Static Analysis)으로 걸러내는 검증 단계다. 핵심 사상은 결함 해결을 개발 파이프라인의 앞단으로 당기는 “Shift-Left”다. 늦게 발견할수록 수정 비용이 기하급수적으로 커지는 칩 설계의 특성상, 가장 싼 단계에서 버그를 잡자는 전략이다.
현장에서 권위를 갖는 룰셋과 툴
일본 반도체 컨소시엄(STARC)이 제정한 사실상의 업계 표준. 항공우주 고신뢰성 표준(DO-254)의 베이스라인으로도 쓰인다. 규칙을 아래 4등급으로 분류한다.
Recommended 권장
Reference 참조
Prohibited 금지
Michael Keating & Pierre Bricaud 공저. IP 재사용 설계의 바이블로, 네이밍·구조화 관행의 출처다.
상용 정적 검증 툴. 규칙을 접두사+코드로 식별한다 — W=Lint, SYNTH_=합성, CDC_=클럭 도메인.
Verilator는 UNOPTFLAT·LATCH·WIDTH 같은 경고 식별자로, Google ChipsAlliance의 Verible은 .rules.verible_lint 구성 파일로 CI/CD에 통합한다.
반드시 준수해야 할 디자인 룰 Top 20
20개 룰은 성격에 따라 네 갈래로 묶인다. 각 묶음이 막아내는 위험의 종류가 다르다.
[A] 클럭 및 리셋 시스템 (룰 1~5)
⚙️ 원리 — 클럭 신호에 AND/OR 게이트를 직접 물려 클럭을 끄고 켜면, 게이트 지연으로 클럭 스큐가 생기고 입력 변화 순간 글리치(짧은 가짜 펄스)가 발생한다.
⛔ 위반 시 — 의도치 않은 가짜 클럭 엣지가 플립플롭을 트리거 → 칩 전체 상태 오염.
✅ 해결 — 클럭은 만지지 말고 Clock Enable 핀을 가진 FF를 쓰거나, 합성 툴이 ICG(Integrated Clock Gating) 셀을 삽입하도록 둔다.
⚙️ 원리 — 비동기 리셋은 거는 것(Assertion)은 즉각적이어야 하나, 푸는 것(Deassertion)은 클럭 엣지와 동기화되어야 한다.
⛔ 위반 시 — 클럭 엣지 부근에서 리셋이 풀리면 FF가 리셋도 정상도 아닌 메타스테빌리티(Metastability)에 빠진다(Reset Recovery/Removal 위반).
✅ 해결 — 2단 FF 리셋 동기화기(Reset Synchronizer) 사용. 리셋은 비동기로 직결하되, 해제만 두 클럭에 걸쳐 동기화된다.

🔗 다이어그램 요약: 리셋 동기화기는 비동기 리셋을 두 FF의 CLR 핀에 직결해 거는 순간은 즉시 반영하되, 1을 D로 받는 2단 FF가 클럭에 맞춰 해제 시점만 동기화한다 — 메타스테빌리티 없이 안전하게 리셋을 푸는 표준 회로다.
⚙️ 원리 — 리셋 트리는 클럭 트리에 준하는 중요 신호다. 중간에 게이트가 끼면 글리치가 전파된다.
⛔ 위반 시 — 짧은 글리치로 SoC 일부 블록만 무작위 리셋 → 원인 불명 시스템 다운.
✅ 해결 — 동기화기를 거친 리셋은 다른 로직 없이 FF의 리셋 핀에 직결.
⚙️ 원리 — 한 블록 안에서는 posedge 또는 negedge 중 하나만 쓴다(DDR 등 특수 인터페이스 제외). 그래야 정적 타이밍 분석(STA)과 테스트 회로(DFT/ATPG)가 깔끔하다.
⛔ 위반 시 — 한 주기에 반(半)주기 타이밍 패스가 생겨 셋업 여유가 절반으로 줄고 고속 동작이 불가능해진다.
✅ 해결 — 원칙적으로 posedge clk만 사용.
⚙️ 원리 — 주파수·위상이 다른 도메인 간 신호를 직결하면 수신 FF의 셋업/홀드를 보장할 수 없다.
⛔ 위반 시 — 수신 FF 메타스테빌리티 → 데이터 손실·쓰레기 값 전파.
✅ 해결 — 1비트 제어 신호는 2-stage Synchronizer(Double Flop), 다중 비트 데이터는 비동기 FIFO 또는 핸드셰이크. SpyGlass CDC_ 시리즈가 이를 전담 검증한다.
[B] 순차 및 조합 논리 (룰 6~10)
⚙️ 원리 — 조합 블록(always_comb)의 if/case에서 모든 분기(else/default)를 채우지 않으면, 이전 값을 유지하려 합성 툴이 레벨 민감 래치를 만든다.
⛔ 위반 시 — 래치는 클럭 엣지가 아닌 레벨에 반응하여 enable High 구간의 입력 변화·글리치를 그대로 통과시키고, STA를 극도로 어렵게 만든다.
✅ 해결 — 모든 분기에 값을 할당하거나, 변수 기본값을 블록 첫 줄에 선언.
아래 파형은 동일한 입력 D에 대해 레벨 민감 래치와 엣지 트리거 FF의 결정적 차이를 보여준다.

📊 다이어그램 요약: enable이 High인 구간에서 래치 출력 Q_latch는 D를 즉시 추종해 D의 1→0 변화를 그대로 통과시키고, enable이 Low로 떨어지면 직전 값을 유지(hold)한다. 반면 FF는 클럭 상승 엣지 순간의 D=1만 포착해 끝까지 유지하므로 이후의 글리치에 흔들리지 않는다. 래치가 위험한 이유가 바로 이 “투과성”에 있다.
⚙️ 원리 — FF를 거치지 않고 출력이 자기 입력으로 되돌아오는 구조.
⛔ 위반 시 — 무한 발진(Oscillation) 또는 상태 고착, 타이밍 툴 무한 루프·합성 중단. Verilator에서는 시뮬레이션 속도를 급격히 떨어뜨린다.
✅ 해결 — 피드백 경로에 최소 1개 이상의 레지스터를 삽입해 클럭 단위로 끊는다.
⚙️ 원리 — 순차회로(FF)는 Non-blocking(<=), 조합회로는 Blocking(=)을 쓴다.
⛔ 위반 시 — 혼용 시 시뮬레이터 이벤트 평가 순서에 따른 경합 조건(Race Condition) 발생 → 합성된 실제 HW와 다른 동작.
✅ 해결 — always_ff에는 <=, always_comb에는 =만.
⚙️ 원리 — 구식 always @(a or b) 방식에서 우변 변수를 목록에서 빠뜨리는 실수.
⛔ 위반 시 — 시뮬레이터는 래치처럼 동작하나 합성 툴은 완전한 조합회로로 만들어 시뮬–합성 불일치.
✅ 해결 — always_comb를 쓰면 툴이 목록을 자동 추론.
⚙️ 원리 — 서로 다른 always/assign이 같은 신호에 값을 쓰는 행위.
⛔ 위반 시 — 시뮬레이션에서 충돌 값이 X로 출력되고, 실제 칩에서는 VDD–GND 단락(Short)으로 물리적 손상 위험.
✅ 해결 — 신호당 드라이버는 단 하나로 한정.
[C] 합성–시뮬레이션 불일치 방지 (룰 11~15)
⚙️ 원리 — #1 같은 시간 지연은 시뮬레이터 전용 구문이다.
⛔ 위반 시 — 합성 툴(Design Compiler 등)은 #을 완전히 무시한다. 딜레이에 의존한 로직은 합성 후 오작동.
⚙️ 원리 — logic [7:0] cnt = 0; 같은 선언 동시 초기화는 FPGA에선 유효하나 ASIC에선 무시된다.
⛔ 위반 시 — 시뮬레이션은 0으로 시작하지만 실제 칩은 전원 인가 시 임의의 쓰레기 값에서 출발 → 디버깅 난해한 불일치.
✅ 해결 — 반드시 명시적 리셋으로 초기화.
⚙️ 원리 — 선언된 폭/크기를 넘는 인덱스 접근.
⛔ 위반 시 — 시뮬레이션은 X 반환, 합성 툴은 0으로 고정하거나 해당 로직을 통째로 최적화 제거할 수 있다.
⚙️ 원리 — // synopsys full_case는 “나머지 조건은 무시해도 좋다”는 강제 지시다.
⛔ 위반 시 — 처리되지 않은 예외가 실제로 발생하면 시뮬은 래치, 합성은 Don’t-care 최적화 → 둘이 갈라지는 끔찍한 논리 오류.
✅ 해결 — 프라그마 대신 SystemVerilog unique/priority case를 사용해 의도를 안전하게 전달.
⚙️ 원리 — Don’t-care에 1'bx를 할당하면 면적 최적화 이점이 있으나, X가 제어문으로 흘러들면 X-Pessimism/Optimism이 발생한다.
⛔ 위반 시 — RTL 시뮬레이션이 실제 HW 버그를 은폐(낙관)하거나 과도 비관으로 오탐을 만든다.
✅ 해결 — 통제된 환경에서만, 검증 의도가 분명할 때만 사용.
[D] 설계 강건성 및 SoC 통합 (룰 16~20)
⚙️ 원리 — 좌·우변 폭이 다르면(assign a[3:0] = b[4:0];) 툴이 임의로 잘라내거나(Truncation) 0으로 채운다(Zero Padding).
⛔ 위반 시 — 의도치 않은 데이터 손실·부호(Sign) 왜곡. 가장 흔하면서 추적이 어려운 버그군.
✅ 해결 — 명시적 슬라이싱(b[3:0])·캐스팅으로 폭을 일치시킨다.
⚙️ 원리 — 인스턴스화 시 연결하지 않은 입력 핀.
⛔ 위반 시 — CMOS에서 입력이 부유(Floating)하면 전압이 0~1 사이를 떠돌며 PMOS·NMOS가 동시 도통 → 관통 전류(Crowbar Current) 누설.
✅ 해결 — 미사용 입력은 반드시 0/1로 고정.
⚙️ 원리 — 과거엔 다수 모듈이 한 버스를 공유하려 내부 Z를 썼다.
⛔ 위반 시 — 현대 ASIC/DFT 툴은 내부 트라이스테이트 라우팅을 잘 못 다루며 버스 경합(Contention) 위험이 크다.
✅ 해결 — 내부 데이터 라우팅은 멀티플렉서(MUX)로.
⚙️ 원리 — FF 사이에 게이트(가산기·곱셈기·다단 MUX)가 지나치게 많은 경우.
⛔ 위반 시 — 전파 지연이 클럭 주기를 초과 → 셋업 타임 위반 → 목표 주파수 미달.
✅ 해결 — 중간에 파이프라인 레지스터를 삽입해 로직을 분할.
⚙️ 원리 — 폭·상태 값을 8, 4'b0010처럼 하드코딩.
⛔ 위반 시 — 스펙 변경 시 코드 곳곳을 수동 수정 → 휴먼 에러.
✅ 해결 — localparam/parameter로 의미 있는 이름을 부여해 재사용성 확보.
룰북에 없는, 선배 개발자들의 실무 스킬
린트 룰셋에 명문화돼 있지는 않지만, 유지보수성과 협업 효율을 위해 시니어 엔지니어들이 강하게 권장하는 관행이다. 룰이 “버그를 막는 법”이라면, 이쪽은 “팀이 빨리 읽고 고치는 법”에 가깝다.
직관적 네이밍 컨벤션
신호 이름만으로 특성을 알 수 있게 접미사를 붙이면, 팀 단위 디버깅 시간을 크게 줄인다.
| 접미사 | 의미 |
|---|---|
| _i / _o | 입력 / 출력 포트 |
| _n | 액티브 로우 (예: rst_n) |
| _q / _r / _ff | FF를 거친 동기화 레지스터 출력 |
| _d / _nxt | FF로 들어가는 다음 상태(조합) 신호 |
RMM은 여기에 더해 ▲기본 클럭은 반드시 clk ▲다중 클럭은 clk_ 접두사(clk_sys, clk_core) ▲일반 신호는 소문자, 상수·사용자 정의 타입은 대문자를 규정한다. SDC/STA에서 get_ports clk* 같은 와일드카드로 클럭 도메인을 일괄 제어하기 위함이다. Verible도 parameter-name-style=ALL_CAPS로 이를 기계적으로 강제할 수 있다.
FSM의 3-블록 구조화
모든 로직을 하나의 always에 욱여넣는 스파게티 코딩을 피하고 기능별로 분리한다.

🔁 다이어그램 요약: FSM은 세 블록으로 나눈다 — ① 입력과 현재 상태로 다음 상태를 계산(조합, =), ② 클럭마다 그 상태를 갱신(순차, <=), ③ 상태별 출력을 만든다. 상태 전이도와 코드가 1:1로 맞아떨어져 디버깅이 직관적이 된다.
제어부와 데이터부의 물리적 분리
연산을 담당하는 데이터 패스(가산기·곱셈기·카운터)와 이를 지휘하는 컨트롤 패스(FSM·조건문)를 같은 블록에 섞지 않는다. 분리하면 합성 툴이 데이터 패스에 대해 자원 공유(Resource Sharing) 최적화를 수행하기 쉬워지고 타이밍 클로저 달성이 수월해진다.
SystemVerilog 기능의 적극 활용
▶ wire/reg 혼란을 없애는 logic 타입 일괄 사용
▶ 모듈 간 다수 배선을 묶어 연결 오류를 차단하는 interface
▶ 의도를 툴에 명확히 전달하는 always_ff(순차)·always_comb(조합)
린트의 한계 — “Lint Clean ≠ Correct”
린트를 맹신하면 안 된다. 통과는 좋은 코드의 필요조건이지 충분조건이 아니다. 다음 한계를 반드시 숙지해야 한다.
린트는 정적 분석이라 런타임 동적 상태나 설계자의 아키텍처 의도를 모른다. 구조적으로 절대 도달 불가능한 데드 코드 분기에서 래치 경고를 띄우는 식이다.
오탐은 외부 Waiver 파일(Verilator .vlt, SpyGlass .awl/TCL)로 관리하는 것이 표준이다. 코드 내 인라인 // lint_off는 가독성을 해치므로 지양하고, ▲특정 모듈·신호만 정밀 타겟팅 ▲무시해도 안전한 이유(Justification)를 문서화 ▲리뷰 승인을 받는다. 핵심은 “Waiver 최소화”다.
가독성을 위한 괄호 확장 강제 룰과 길이 축소를 위한 괄호 제거 룰이 충돌하는 경우, 수석 설계자가 프로젝트 .lint_config에 우선순위를 명문화해 해결한다.
린트는 “A 포트가 B 포트에 문법에 맞게 연결됐는가”는 보지만 “그 연결이 논리적으로 맞는가“는 모른다. 코너 케이스 데드락은 UVM 동적 시뮬레이션·정형 검증(Formal Verification)을 거쳐야만 드러난다.
결론 — 검증 파이프라인의 첫 단추
20대 디자인 룰과 실무 스킬을 관통하는 사상은 Shift-Left다. RTL 코딩 단계에서 린트 규칙을 엄격히 지키면 합성 타이밍 에러, 시뮬–칩 동작 불일치, 양산 후 원인 불명 다운을 상당 부분 사전 차단할 수 있다.
다만 본문에서 분류한 룰셋·툴 식별자(Verilator UNOPTFLAT/LATCH/WIDTH, SpyGlass W/CDC_/SYNTH_ 시리즈)는 각 툴 버전과 프로젝트 설정에 따라 코드 번호·기본 활성 여부가 달라질 수 있다. 실제 적용 시에는 해당 툴의 현행 Reference Guide와 STARC/RMM 최신판을 함께 확인할 것을 권한다.
📌 본 글은 STARC RTL Design Style Guide, Synopsys RMM, SpyGlass Rules Reference, Verilator·Verible 공식 문서를 종합한 교육·참고용 자료입니다. 구체적 룰 번호와 적용 기준은 사용 중인 툴 버전과 프로젝트 정책에 따라 달라질 수 있으니, 실무 적용 전 해당 공식 문서를 확인하시기 바랍니다.
본 글은 공개된 데이터와 출처를 바탕으로 작성했습니다. 최종 업데이트: 2026-06-18