RTL freeze가 끝나면 RTL 엔지니어의 일도 거의 끝났다고 생각했습니다. 합성과 P&R은 백엔드의 몫이고, 저는 다음 블록을 준비하면 된다고요.
그런데 tape-out을 몇 주 앞두고 메일 한 통이 왔습니다. “이 조건에서 동작이 틀립니다. 수정 가능할까요?” 이미 넷리스트가 나온 상태라 RTL만 고쳐서 다시 합성할 수는 없었습니다. 그렇게 저의 첫 ECO(Engineering Change Order)가 시작됐습니다.
이 글은 “3년 차가 프로젝트에서 배운 것들” 시리즈의 첫 글입니다. ECO 교과서라기보다, 처음 ECO를 맡은 사람이 실제로 헤맸던 순서대로 정리한 기록입니다.
1. ECO란 무엇이고, 왜 RTL 엔지니어가 하나요?
ECO는 이미 만들어진 넷리스트를 최소한으로 직접 고쳐서 기능을 바꾸는 작업입니다. 처음부터 다시 합성하면 타이밍, 배치, 배선 결과가 전부 바뀝니다. 그러면 백엔드가 몇 주 동안 해 온 일을 다시 해야 합니다. 그래서 freeze 이후에는 “바뀐 부분만 게이트 몇 개로 꿰매는” 방식을 씁니다.
ECO는 크게 두 갈래로 나뉩니다.
- Functional ECO: 로직 자체를 바꾸는 작업입니다. 이 글의 주제입니다.
- Timing ECO: 버퍼 삽입이나 셀 사이즈 조정처럼 타이밍을 맞추는 작업이며, 주로 백엔드가 맡습니다.
Functional ECO에서 RTL 엔지니어가 빠질 수 없는 이유는 간단합니다. 무엇을, 왜, 어디까지 바꿔야 하는지 아는 사람이 RTL을 쓴 사람이기 때문입니다.
2. 범위부터 정한다: “어디까지 바뀌는가”
처음에 가장 크게 한 실수는 요청을 받자마자 RTL부터 고친 것입니다. ECO에서는 고치기 전에 범위를 먼저 정해야 합니다.
제가 겪은 수정 요청은 대부분 아주 작았습니다. 예를 들면 이런 것들입니다.
- 카운터 종료값이 조금 어긋나 있음
- 인덱스 경계 조건이 하나 차이(off-by-one)
- select 신호를 1사이클 지연한 버전으로 써야 함
- 특정 설정의 특수 케이스에 대한 조건 누락
- ready 신호의 reset 조건 누락
RTL로는 한 줄이지만 넷리스트에서는 이야기가 다릅니다. 그래서 범위를 정할 때 아래 세 가지를 꼭 적었습니다.
- 영향 받는 신호: 이 수정이 어떤 출력까지 퍼지는가?
- 새 플립플롭이 필요한가: 1사이클 지연처럼 상태가 추가되면 조합 로직 수정보다 훨씬 무겁습니다.
- 버스 폭: 한 비트만 바꾸는지, 버스 전체를 바꾸는지 확인합니다. 이 항목은 뒤에서 다시 나옵니다.
3. Pre-net과 RTL을 나란히 놓고 수정 지점 찾기
범위가 정해지면 수정할 RTL과 pre-layout 넷리스트(pre-net) 에서 같은 위치를 찾습니다. 합성 툴은 신호 이름을 바꾸고 로직을 합치기 때문에, RTL의 cnt_q가 넷리스트에서 그대로 보이지 않을 때가 많습니다.
아래는 설명을 위해 만든 가상의 예시입니다. RTL에서 카운터 종료값을 바꾸는 경우입니다.
// RTL (예시) : 종료 조건 수정
assign cnt_done = (cnt_q == LAST_VAL); // before
assign cnt_done = (cnt_q == LAST_VAL-1); // after넷리스트에서는 이 비교기가 XOR/AND 게이트 묶음으로 풀어져 있습니다. 그래서 저는 보통 이 순서로 찾았습니다.
- 이름이 보존되는 플립플롭 출력(
cnt_q_reg_*_/Q)에서 출발합니다. - fan-out을 따라가며 비교 로직이 끝나는 지점(
cnt_done에 해당하는 net)을 찾습니다. - 그 net에 연결된 게이트의 입력을 바꾸거나, spare cell로 새 게이트를 만들어 끼웁니다.
// Netlist ECO (예시) : LAST_VAL이 홀수라 LAST_VAL-1과 LSB 하나만 다르다고 가정
// 비교기에서 LSB 입력의 극성만 뒤집으면 됨
INV_X1 eco_inv_0 ( .A(cnt_q_reg_0_/Q), .Y(eco_n0) );
AND4_X1 U123 ( .A(eco_n0), .B(n45), .C(n46), .D(n47), .Y(cnt_done) );넷리스트를 읽는 요령, 예를 들어 이름이 어떻게 바뀌는지와 clock gating 셀이 어떻게 보이는지는 따로 다루겠습니다.
4. 넷리스트 ECO 적용: 손으로 할까, 툴로 할까
ECO는 손으로 게이트를 직접 넣을 수도 있고, Conformal ECO나 Formality ECO 같은 툴로 패치를 생성할 수도 있습니다. 실무에서는 둘을 섞어 쓰는 경우가 많습니다.
처음 할 때 지켰으면 좋았을 원칙은 이렇습니다.
- RTL도 반드시 같이 고칩니다. 넷리스트만 고치면 RTL과 넷리스트가 어긋나고, 다음 단계의 등가성 검사가 통과할 수 없습니다.
- 수정 한 건마다 기록을 남깁니다. 무엇을 왜 바꿨고, 어떤 게이트를 넣었는지 적습니다. ECO가 여러 차례 이어지면 어느 라운드에서 무엇이 들어갔는지 헷갈리기 시작합니다.
- spare cell 위치와 종류는 백엔드와 먼저 확인합니다. 넷리스트에서 논리적으로 맞아도 물리적으로 쓸 셀이 없으면 소용없습니다.
5. 검증: 등가성 검사와 재시뮬레이션
ECO의 검증은 두 축으로 이루어집니다.
(1) 등가성 검사 (LEC) 수정한 RTL과 수정한 넷리스트가 논리적으로 같은지 Formality나 Conformal로 확인합니다. 제 경우에는 먼저 RTL과 pre-net을 비교했습니다. 이게 통과한 뒤 같은 수정을 post-layout 넷리스트에 반영하고, 넷리스트끼리(net-to-net) 다시 비교했습니다.
처음 돌렸을 때 바로 통과하지는 않았습니다. 지연 신호를 버스의 일부 비트에만 연결했거나, clock gating enable 조건이 하나 빠진 경우가 있었습니다. 이 디버깅 과정은 다음 글의 주제입니다.
(2) 재시뮬레이션 LEC는 “RTL과 같다”는 것만 보장합니다. RTL 수정 자체가 맞는지는 알려 주지 않습니다. 그래서 수정 요청을 재현하는 테스트를 RTL에서 먼저 돌리고, 가능하면 ECO 넷리스트로 GLS까지 돌려 확인했습니다.
6. 처음이라 했던 실수들
- “한 줄이니까 금방”이라고 일정을 말했습니다. RTL 한 줄이 넷리스트에서는 반나절이 되기도 합니다.
- 버스 폭을 확인하지 않았습니다. 일부 비트만 바뀌는 실수는 LEC에서 잡혔지만, 처음부터 체크했다면 한 라운드를 아꼈을 겁니다.
- ECO가 끝났다고 안심했습니다. 굵직한 라운드가 끝난 뒤에도 아날로그 인터페이스 타이밍 동작 때문에 작은 ECO가 한 번 더 있었습니다. ECO 기록을 잘 남겨 둔 덕분에 그때는 훨씬 빨리 끝냈습니다.
7. 첫 ECO 체크리스트
- [ ] 수정 요청을 재현하는 테스트가 RTL 시뮬레이션에 있는가?
- [ ] 영향 받는 출력 신호와 버스 폭(전체 비트 / 일부 비트)을 적었는가?
- [ ] 새 플립플롭이 필요한 수정인가? 필요하다면 spare cell을 백엔드와 확인했는가?
- [ ] RTL과 넷리스트를 둘 다 고쳤는가?
- [ ] 수정 지점을 플립플롭 출력처럼 이름이 보존된 기준점에서 추적했는가?
- [ ] RTL과 pre-net 사이 LEC가 통과했는가?
- [ ] post-layout 반영 후 net-to-net LEC가 통과했는가?
- [ ] 재시뮬레이션(가능하면 GLS)으로 기능을 확인했는가?
- [ ] 라운드별 수정 내역을 기록했는가?
마무리: ECO는 RTL을 끝까지 책임지는 일
처음 ECO를 맡았을 때는 “freeze 이후에 RTL을 또 고친다”는 게 부담스러웠습니다. 지금 돌아보면 ECO는 제가 쓴 RTL이 게이트가 되고 칩이 되는 과정을 처음으로 끝까지 따라가 본 경험이었습니다. 넷리스트를 읽게 되었고, 백엔드와 같은 언어로 이야기하게 되었습니다.
다음 글에서는 이 과정에서 가장 오래 붙잡고 있었던 문제, 등가성 검사(LEC)가 실패했을 때 원인을 좁혀 가는 디버깅 순서를 다뤄 보겠습니다. 일부 비트만 수정이 빠진 경우와 clock gating 조건이 누락된 경우를 예로 들어, 실패 리포트를 어떻게 읽는지 정리하겠습니다.
References: wiki