칩 설계를 크게 설계와 검증으로 나눌 수 있다면, 피상적으로 봤을 때 설계가 더 재밌어 보인다. 어쩌면 실제로도 그럴 수도 있다. 하지만 검증 분야도 꽤나 재밌는 부분이 있다. 설계에서는 하지 못했던 새로운 '소프트웨어'적인 시도들을 할 수 있었고 이는 2010년대, UVM이라는 방법론에서 꽃을 피운다. 검증 엔지니어의 삶도 진취적이고 흥미롭다는 것을 이야기하고자 한다.
빌딩을 지으려면 설계 도면이 있어야 한다. 건축 도면 설계는 주로 CAD(Computer Aided Design)와 같은 소프트웨어를 이용해서 3D 모델링하고, 정확한 수치를 재고, 내장된 여러 자동화 툴을 이용해 편하게 설계한다. 로비를 넣고, 지하주차장의 크기를 가늠하며 각 층의 층고와 구조를 생각하며 설계할 것이다. 빌딩 하나를 정교하게 설계하기에는 더할 나위 없는 수준이다. 하지만 빌딩 하나가 아니라 빌딩 수 천 개, 빌딩 수십만 개를 설계해야 한다면 어떻게 할 것인가? 빌딩의 필수 요소가 해가 다르게 바뀌는 조건일 수도 있다. 다문화 시대를 맞아 이슬람 예배당이 추가된다던지, VR관이 추가된다던지, 어느 해에는 둘 다 빠질 수도 있다. 이 때마다 수십만 개의 건물을 다시 설계할 것인가? 더 효율적인 방법이 필요할 것이다.
칩 설계 업계가 해왔던 고민이 그것이다. 회로는 갈수록 복잡해지는데, 그 복잡도는 지수함수적으로 증가한다. 앞서 말한 EDA와 같은 툴이 설계를 도와줬지만, 근본적인 문제를 해결하지 못했다. 더이상 회로 설계 엔지니어들은 물리적으로 회로를 그릴 수 없다는 것을 깨닫게 되었다. 그래서 등장한 Verilog 라는 HDL(Hardware Description Language)언어는 칩 설계를 완전히 다른 형태로 만들어 주었다. 기존에는 직접 AND와 같은 게이트를 직접 배치해가며 회로를 설계했다면, Verilog의 등장으로 마치 C코드를 짜듯이 회로를 ‘디지털’ 설계할 수 있게 되었다. 다만 결국 실제 찍혀 나오는 회로는 8대공정의 입력값이 되는 그림이기 때문에, 이 코드를 실제 칩으로 찍을 수 있게 회로 형식으로 바꿔주는 과정이 추가되었다. 이를 합성(Synthesize)라고 한다.
회로 설계 엔지니어들은 1995년 이래 Verilog의 등장으로 어쩌면 지금까지도, 무수히 증가하는 회로의 집적도를 감당하며 설계하고 있다. Verilog가 차용한 소프트웨어 언어인 C는 어떻게 되었는가? Objective-C, C#, C++ 등 온갖 버전이 생겼고 더 나아가 Swift, Python과 같은 현대적인 언어가 나와 원시적인 C를 대체했다. 소프트웨어 개발 방법론도 무수히 발전해서, OOP(Object Oriented Programming)와 같이 코드 개발의 생산성을 높여주는 방법론도 등장했다.
그런데 Verilog는 아직 그대로 있다. 2005년, SystemVerilog라는 새로운 국제 표준(IEEE)이 등장했지만 회사에서 설계에 그것을 쓰는 사람을 본 적이 없다. 미국 연구소에서 인텔 출신 엔지니어들이 SystemVerilog언어를 쓰는 것을 보긴 했지만, Verilog의 엄격한 문법(Lint) 체크를 피하기 위한 수단으로만 간단히 사용할 뿐이었다. SystemVerilog는 소프트웨어로 치면 C가 C++로 확장되었듯, Verilog에서 확장된 언어이다. SystemVerilog는 OOP가 가지고 있는 온갖 좋다는 기능을 지원하는데 아직 회로 설계 엔지니어들은 이 좋은 기능들을 가져다 쓰고 있지 않은 것이다.
왜 그럴까? 그 이유는 바로 칩의 가장 중요한 미션인 버그가 없어야 함과 동시에 칩은 효율을 생명처럼 추구하기 때문이다. 결국 Verilog는 물리적인 회로를 기술한 것이다. 소프트웨어 언어에서 밥먹듯이 쓰는 for 문조차도 Verilog에서는 조심하여 써야 할 정도로 (for문은 말 그대로 Chip에 반복되는 Logic이 찍힌다는 소리이고 이는 심각한 비효율일 가능성이 매우 높으므로) 하드웨어 설계에서는 소프트웨어적인 상상력을 펼치는 데 한계가 있다.
칩의 특성상 Verilog라는 언어에서 크게 벗어날 수 없다는 것을 인정하는 대신 설계 방법론의 진화는 있었다. 각각의 IP(Intellectual Property, 설계 자산)가 커지기도 하지만 그 크기가 커지는 것에 비해 훨씬 더 빠른 속도로 다양한 종류의 IP가 SoC를 구성하게 된다는 것에서 착안한 IP-XACT와 같은 설계 ‘방법론’이 산업계에서 널리 쓰이게 되었다. SoC(System on Chip)란 온갖 구성 요소가 한 칩에 들어간 형태를 말하는데, 스마트폰의 두뇌인 AP(Application Processor)가 대표적이다. 단순히 CPU, GPU뿐만 아니라 NPU, 메모리 컨트롤러, I/O 제어 컨트롤러, 비디오 인코더/디코더 등 수십 가지의 IP가 한데 모여 있는 것이 SoC의 형태이다. 이 각각의 IP는 외부에서 사 올 수도 있고 일부 디지털 로직보다 더 나아간 최적화를 원할 경우 커스텀 로직이라는 아날로그 형태로 설계하지만 SoC내부 대부분 IP의 경우 Verilog로 디지털 설계하고 있다. IP-XACT 표준은 이 각각 IP들이 어떻게 외부와 소통하는지에 대한 정보만 따로 xml파일로 보관한다. 외부에서 접근할 수 있는 주소, 그리고 물리적인 Input/Output 포트만 저장한다. 이렇게 세부 구현을 숨기고 인터페이스만 남게 된 추상화 수준을 높인 IP들은 SoC 내에서 다른 IP들과 이어 붙일 때(설계에서는 Integration이라고 한다) 보다 수월해진다. 이어붙이는 작업은 포트의 연결이고 IP-XACT는 이어붙이는 것 이외의 잡음들을 가지고 있지 않기 때문이다.
칩 설계에서 IP-XACT를 필두로 여러 설계 표준화 및 자동화 기법들이 나름 발전했지만 본질적으로 Verilog라는 상대적으로 저수준 (Low level)언어로 칩을 설계하고 이를 합성이라는 과정을 거쳐야 하는 것은 바뀌지 않았다. 다시 말하지만, 칩은 한번 찍혀나오면 더이상 수정할 수가 없다. 펌웨어로 그 실수를 어느정도는 우회하여 바로잡을 수 있지만 그 비용(돈, 성능 손해)과 난이도가 S/W의 패치에 비해 비교도 못하게 높다. 그렇기 때문에 처음 도면을 만들 때 정확하고 가장 효율적인 패턴을 찾아 그려야 하는 것이다. 만약 소프트웨어적인 개념을 도입하여 추상화 수준을 높인다면 칩의 효율이 개발의 편리에 반비례해서 떨어질 것이다. 과거 콘솔 게임기 시절 한정된 자원으로만 개발하여야 했을 때 C는 커녕 어셈블리어로 극한의 최적화를 한 이유가 그것이다. Verilog로도 엄밀함으로 오는 효율에 대한 갈망으로 아날로그 커스텀 설계를 하는 마당에 추상화는 어불성설이다. 이것이 Verilog에서 더이상 나아갈 수 없는 이유이다.
하지만 검증은 어떤가? SoC내의 IP들의 로직은 점점 커지고 어려워지고 그 IP들간의 상호작용에 대한 시나리오도 그 조합의 곱셈 연산만큼 복잡해졌다. SoC를 설계할 때 검증 엔지니어의 수가 점점 늘어나고 있다. 설계 엔지니어와 검증 엔지니어의 비율은 5:5가 되어가고 있다.
그럼, Verilog로 짜여진 로직을 검증해야하니 Verilog로 검증을 해야하나? 20년전 과거에는 그랬다. 그러다 Verilog 특유의 낮은 생산성에 갈증을 느낀 검증 엔지니어들이 각자의 소속마다 나름의 방법론을 만들어오다 Cadence사가 개발하던 SystemVerilog으로 통합되어 표준이 되었다. 왜 검증에서는 새로운 언어가 가능했던 것일까? Verilog 검증은, 다시말해 디지털 검증은 실제 칩이 찍히기 전 Simulation을 통한 검증을 의미한다. 설계야 결국 Simulation단계의 설계와 실제 칩의 설계가 동일해야함이 당연하지만 검증은 굳이 그럴 필요가 없다. 각 단계에서 최적의 방법을 쓰면 되는 것이다. 그렇다보니 컴퓨팅 리소스의 제약이 없다. 검증 코드는 Simulation단계까지만 쓰기 때문에 실제 로직에 들어가기 위해 합성하지 않아도 되기 때문이다. 굳이 Verilog의 Static한 개발을 따르지 않고 소프트웨어 개발처럼 동적으로 객체를 생성하여 재사용성을 올려도 상관 없다.
SystemVerilog는 소프트웨어의 많은 것을 차용했다. class 타입을 필두로 상속이 가능해졌고 int와 같은 덜 엄격한 데이터타입도 허용됐다. 하지만 Verilog라는 정적(Static)인 언어 위에 소프트웨어적인 개념을 억지로 얹으니 참 괴랄한 형태가 되었다. 일례로 SystemVerilog에는 module 이라는 Verilog와 동일한 컨테이너가 있고 이는 하드웨어 칩 단위이기 때문에 내부의 함수는 모두 정적이다. 반면에 class는 소프트웨어에서 온 컨테이너이고 내부의 함수는 모두 동적이다. 이런 구조 덕분에 초심자에게 SystemVerilog의 이해는 매우 어렵다. 하지만 효율적인 검증에 꼭 필요하며 검증 엔지니어에게는 SystemVerilog의 능숙함은 필수적인 덕목으로 여겨진다. 비단 객체지향성뿐만 아니라, 커버리지를 측정하는 covergroup 자료형이나 테스트 중간중간에 무언가 잘못되는 것을 사전에 걸러낼 수 있는 assert문의 지원 등 검증에 있어서 이런 고급 문법이 얼마나 유용한지는 직접 써 본 사람은 알 것이다.
UVM은 이 유용한 SystemVerilog 언어를 사용한다. UVM은 엄밀히 말하면 ‘클래스 라이브러리’이자 ‘방법론’이다. 벌써부터 어려운데, 특정 구조를 강제하고 사전 정의된 매크로들을 제공하는 것이다. 특정 구조를 강제해서 생기는 이득은, 방법론이 말 그대로 범용적이기 때문에 다른 UVM 코드를 봐도 쉽게 이해가 가능하다. 사전 정의된 클래스 라이브러리와 이 구조의 강제로 재사용성을 극대화할 수 있다. 논문의 언어를 공용어(UVM이 IEEE Standard인 것)인 영어 (UVM이 SystemVerilog로 작성된 것)로 작성하고 구성 양식을 통일(UVM이 그림과 같은 양식을 강요하는 것)해서 대충 마지막 페이지에는 ‘요약’을 기대하는 것과 비슷하다.
UVM은 특유의 재사용성을 위해 SystemVerilog에서 구현된, 소프트웨어에서 많이 활용하는 OOP의 개념을 적극 차용했다. 대표적으로 팩토리 메커니즘을 통해 변수를 등록한다. my_object = object::type::create() 와 같은 형식으로 UVM 내부의 매크로가 변수를 대신 등록해준다. 내부는 Singleton이라고 하는 디자인 패턴이 쓰여서 하나의 클래스에 하나의 인스턴스를 보장한다. 쉽게 말하면 특정 방식으로 변수 선언을 강제하여 특정 구조를 유지하여 일관성 있는 테스트환경을 만드는 것이다.
여러 장점들을 나열했지만 내가 생각하기에 이 UVM의 가장 큰 단점은 그 복잡성에 있다. SystemVerilog 자체가 소위 근본 없는 Verilog를 마개조하여 소프트웨어의 온갖 좋은 것을 갖다 붙힌 언어인데 여기다가 UVM이라고 하는 복잡한 클래스 라이브러리가 구현되어있고 이 라이브러리를 가져다 쓰려면 온갖 매크로의 향연 속에서 이해해야하는 것이다. 변수 하나를 등록하는데만 해도 Verilog에서는 형식과 사이즈, 가령 bit [31:0] my_var; 로 선언하면 됐는데 초심자 입장에서 UVM의 변수를 하나 선언하고 싶은데 팩토리 매소드이니, 싱글톤 패턴이니, Phase니 등 그 원리가 너무도 복잡할뿐더러 매크로로 되어있어 선언하면서도 찝찝하다.
하지만, UVM을 배우지 않기에는 그 방법론이 너무 강력하다. UVM의 도움 없이 SystemVerilog만으로 테스트벤치를 작성해 한계를 느껴보면 그 진가를 이해할 수 있다. 그러다 욕심이 생겨 SystemVerilog만으로 작성된 테스트벤치를 효율적으로 리팩토링하려고 하다 보면 재사용성을 고려하고, 테스트 시퀀스의 랜덤화를 고민하게 된다. 테스트가 어떻게 잘 진행됐는지 레퍼런스 모델과의 비교도 정교하게 가다듬어야 하고 Functional coverage 면에서도 정량적으로 지표화하고 싶을 것이다. 결국 UVM의 철학을 따라가게 된다. 그 재사용성과 체계적인 컴포넌트의 구성은 UVM을 공부할 수록 감탄하게 된다. 구조가 복잡하기는 하지만 최근에는 ChatGPT라는 훌륭한 과외선생님도 있고 (내가 경험해 본 바, GPT 4.0이라면 충분한 UVM 선생님이 될 수 있다) 구글링을 하면 국내에 여러 자료도 생겼다. 포기하지 않고 그 하나씩의 매크로가 왜 생겼는지를 음미하다보면, 어느 순간 눈이 트일 것이다.