[테크월드=양대규 기자] 포터블 스티뮬러스(Portable Stimulus)는 지난 1~2년 사이에 검증 분야의 인기 유행어가 됐다. 하지만 대부분의 ‘새로운’ 개념들이 그렇듯이 이 또한 이미 확립돼 있던 일부 툴과 방법론들로부터 발전돼 나온 개념이다. 예를 들어, 서로 다른 수준의 설계 추상화 간에 공통의 스티뮬러스 모델을 갖는 일은 오래 전부터 Questa inFact와 같은 그래프 기반의 스티뮬러스 자동화 툴을 이용함으로써 가능했다. SystemC/C++를 RTL로 합성하는 상위 수준 합성(HLD: High-Level Synthesis)도 자체 구축한 환경이나 유도 C 테스트를 혼용해 C 레벨에서 기능 검증을 수행하는 사용자 대부분이 수년 전부터 사용해온 기능이다. 

하지만 이제는 HLS가 아주 큰 규모의 계층적 설계를 수행할 수 있게 됨에 따라, SystemC/C++를 위한 고성능이며 생산가치가 있는 제한적 무작위 스티뮬러스를 가능케 해주는 검증 방법론의 필요성이 커지고 있다. 그래야만 C 레벨에서 커버리지 클로저를 달성한 뒤 정확히 똑같은 스티뮬러스를 재현해 합성된 RTL을 테스트함으로써 확신을 가질 수 있다. 본 글에서는 스티뮬러스 모델을 정의해 C++ HLS DUT의 코드 커버리지가 백 퍼센트에 도달하도록 도운 뒤, 이를 합성된 RTL을 이용해 SystemVerilog나 UVM 테스트벤치에 재사용할 수 있는 방법을 설명한다. 진정한 공통 모델이 주어질 경우에는 두 환경 간에 무작위적 안정성을 유지할 수도 있으므로 어떤 문제들은 하나의 도메인에서 발견한 뒤에 다른 도메인에서 디버깅할 수 있게 된다.

서론

10여 년 전에는 ESL(Electronic System-level) 방법론이 엄청나게 유행했으며, 설계와 검증 양 분야의 추상화 수준을 높여줄 것으로 전망되던 언어 옵션들이 다수 있었다. 그 중에도 특히 압도적인 우위를 차지하고 있던 것은 C/C++, SystemC 그리고 SystemVerilog였다. 추상적인 하드웨어와 시스템 모델링에 가장 널리 사용되는 언어는 C/SystemC이지만, 제한적 무작위(Random) 스티뮬러스, 기능(Function) 커버리지와 같이 한층 더 나아간 검증에 필요한 특징들을 표준화한 것은 SystemVerilog다.

이와 동시에 많은 사용자들은 스티뮬러스를 보다 효율적으로 기술할 수 있는 방법을 찾아 왔다. 특히 간결한 기술을 통해 자동화할 수 있는 검증 시나리오 수를 확장하고 생성 프로세스의 효율성도 높일 수 있는 방법을 모색해왔다.

Questa inFact는 바로 이런 기능을 룰/그래프 기반의 접근방법을 이용해 제공해 왔는데, 이는 소프트웨어 테스팅 기법을 차용해 하드웨어 검증용으로 향상시킨 것이었다. 룰 기반의 모델은 목표 언어에 대해 독립적이므로(최소한 7 개의 서로 다른 환경에 적용돼 왔다) 항상 포터블 스티뮬러스 솔루션으로 이용돼 왔다.

HLS 사용자들은 설계를 위해 C++/SystemC로 옮겨갈 경우 얻게 되는 주요 이점들 가운데 하나는 검증 성능으로서, 덕분에 훨씬 더 많은 테스트를 실행할 수 있다고 말하는 경우가 많다.  그러나 C 환경에는 첨단 검증 기능에 있어서 SystemVerilog와 같은 역량을 갖는 표준이 없다. 그 같은 예 중 하나로서 무엇보다도 무작위/자동화 스티뮬러스 모델링 기능을 들 수 있다. 포터블 시티뮬러스(Portable Stimulus) 솔루션은 이런 역량과 기능을 제공할 뿐만 아니라, C 기반의 시뮬레이션 환경을 위한 스티뮬러스 모델 작성에 투입된 노력을 보존해줌으로써 RTL이 SystemVerilog로 랩핑되는(Wrapped) 다운스트림에서 이를 이용할 수 있게 해주며, 그 반대의 경우도 마찬가지다.

공통 스티뮬러스 모델

룰 기반의 스티뮬러스 모델은 우리가 예상할 수 있듯이 대개는 디폴트 템플릿과 별로 다를 것이 없는 최상위 수준의 주요 룰 파일로부터 계층적으로 생성되며, 한두 개의 모듈식 룰 세그먼트 파일로 구성된다. 최상위 수준의 룰 파일은 주요 룰 그래프(rule_graph)를 선언해 그래프 모델에 이름을 부여하며, 선택된 코드 아키텍처에 따라서는 적용될 스티뮬러스의 세부사항 정의에 필요한 룰 세그먼트 파일의 임포트 명령문 말고는 다른 어떠한 것도 포함시킬 필요가 없다.

[그림 1] 계층구조적인 룰 코드 아키텍처

[그림 1]의 예제에서 네 개의 독립적인 파일을 볼 수 있는데, 이 중에 test_data_C.rules 와 test_data_SV.rules는 둘 다 test_data_gen이라고 하는 그래프 객체를 정의한다. 이 두 개의 최상위 수준 룰 파일은 실제 스티뮬러스 모델의 특정 언어용 랩퍼(Wrapper)인 그래프 구성요소에 상응한다. 다시 말해서, inFact 자동화 생성기는 test_data_C라고 하는 C++ 클래스와 test_data_SV라고 하는 SV 클래스를 각각 생성하며, 이들 각자는 룰 그래프 모델인 test_data_gen을 정의하게 된다.

이 최상위 수준 룰 파일은 둘 다 행동(Behavior)을 실제로 정의하는 룰 세그먼트 파일의 공통 계층구조를 가져온다. 룰 계층구조에 대한 모든 정의를 공통 파일 내에 유지함으로써 컴파일된 그래프 모델들은 동일하게 행동하게 된다.

test_data_C.rules 파일은 추가적인 구성물을 갖는데, 이는 생성되는 코드의 특정언어 요건을 명시하는 속성이다. 이 경우에는 생성된 C++ 클래스 정의 파일에 include 명령을 추가하기 위해 필요한 코드를 명시한다. 이 언어는 생성된 HVL 파일의 맞춤화에 사용할 수 있는 다른 속성들을 지원하지만, 이는 기저 그래프 모델에는 아무런 영향도 미치지 않는다.

test_data_gen.rseg 파일은 그래프가 생성할 수 있는 시나리오의 풀을 정의하는데, 이 경우에는 [그림 2]에서 보듯이 단지 test_data 객체 내용의 랜덤화를 반복 실행하는 것뿐이다.

[그림 2] test_data_gen 룰 그래프

: 시나리오 룰에는 다수의 객체가 포함될 수 있다. 이는 동일한 객체 유형의 인스턴스들이거나 혹은 다수의 상이한 유형들은 물론 다른 그래프 토폴로지 구성물의 인스턴스들이다. 이에 대해서는 나중에 간략히 설명한다.

test_data 객체 자체는 inFact 룰 언어에 struct로 선언되며, 별도의 룰 세그먼트 파일에 정의돼 모듈성과 재사용을 가능케 해준다. 이러한 struct는 추가적인 계층구조를 갖고 있어 packedArray0와 packedArray1이라고 하는 다른 struct를 정의한다. 이들은 C++ 테스트벤치 내에서 DUT 스티뮬러스를 위해 정의되고 사용되는 C++ struct를 미러링한다.

이것은 해당 방법론의 또 다른 주요 요소다. 즉, 동일한 이름과 계층구조를 갖는 룰 그래프 레퍼런스 객체이며, 상응하는 C++, SV 유형에 매핑할 수 있는 데이터 유형들을 사용한다. inFact 언어를 통해 모든 변수의 비트 폭을 정의할 수 있으므로, 멘토의 알고리즘적 비트 정확도 데이터 유형과 SystemC 데이터 유형을 목표할 수 있다.

이 예제에서 테스트 데이터 객체의 형태는 구성 가능한 벡터 곱셈-누산 기능을 구현하는 C 모델을 중심으로 하는 C++ 테스트벤치로부터 파생됐다. 따라서 이 방법론을 구현함에 있어서 첫 번째 단계는 DUT 입력 중 어느 것을 그래프 모델로 랜덤화할 것인 지와 이들의 비트 폭은 어느 정도인지를 결정하고, 이들을 하나의 테스트 데이터 struct나 클래스로 모으는 것이다. 이 경우에 packedArray0 는 10비트 값의 8-요소 고정 길이 배열(Element fixed-size array)을, packedArray1은 7비트 값의 유사한 배열을 포함하고 있다. 여기에 num_col이라고 하는 단일 4비트 수량이 추가된다. 이 structs에 사용되는 데이터 유형들은 멘토 AlgorithmicC 비트 정확도 데이터 유형을 이용해 정의되므로 디자인을 임의의 정밀도(Arbitrary precision)로 모델화 할 수 있다.

이는 비록 C++ 테스트벤치를 이용해 시작되지만, SystemVerilog 도메인에도 이와 유사한 객체가 있어야 한다. 이 객체의 SystemVerilog와 C++ 버전들은 [그림 3]에서 보는 바와 같다.

[그림 3] SV와 C++의 테스트 데이터 유형

SystemVerilog 모델은 inFact 모델처럼 대수적 제약을 포함할 수 있으며, 전통적인 SystemVerilog .randomize() 호출을 이용해 랜덤화 하고자 한다면 아마도 이를 포함해야만 할 것이다. 그러나 항상 inFact 그래프를 이용해 랜덤화를 수행한다면 그럴 필요가 없다.

: 이 단순한 예제의 단일 제약사항은 num_col의 값을 1에서 최대 8까지의 범위로 제한한다. 반면에 inFact 언어는 사소한 구문 상의 차이점이 몇 가지 있긴 하지만 SystemVerilog에 사용되는 모든 공통 제약 연산자를 지원한다. 추가로 SystemVerilog 구문에 익숙한 이들은 SystemVerilog 그래프 모델로부터 inFact 그래프 모델을 생성하거나 업데이트 가능한 유틸리티를 사용할 수 있다

C 테스트벤치에서의 실행

일단 테스트 데이터를 정의하고 나면, 포터블 스티뮬러스 모델을 C 테스트벤치에 통합시키기는 매우 간단하다. 앞서 언급했듯이, C++ 클래스는 공통 룰 모델로부터 자동 생성되며, 이 클래스에는 룰 언어에서 정의되는 인터페이스에 상응하는 메소드가 내장돼 있다. test_data_gen.rseg 파일은 fill이라고 하는 인터페이스를 선언하는데, 이것은 test_data 유형의 모든 인스턴스에 대해 연산한다. 이는 생성되는 HVL 객체에 메소드나 태스크 또는 함수를 생성하는데, 단순히 ifc_라는 접두어를 붙여 ifc_fill이라고 한다.

이 메소드나 태스크 또는 함수는 인수를 하나 취하는데, 이것은 동일한 이름을 갖는 상응하는 HVL 객체에 대한 핸들(Handle)이다. 즉, 앞서 보았던 test_data 클래스나 struct이다.

따라서 통합 메커니즘은 단지 포터블 스티뮬러스 모델을 포함하고 있는 클래스의 인스턴스를 구축한 뒤 그 ifc_fill 메소드를 테스트벤치 test data 컨테이너에 대한 핸들을 이용해 호출하기 위한 것이다.

[그림 4]는 C++ 테스트벤치의 코드를 발췌한 내용으로서, test_data struct – td_h –에 대한 핸들과 inFact 모델을 포함하고 있는 클래스에 대한 핸들의 생성을 보여준다. 여기에서는 후자의 생성자 호출이 inFact가 내부적으로 사용하기 위한 인스턴스명을 정의한다. 이러한 inFact 인스턴스명은 중요한데, 이에 대해서는 나중에 살펴본다.

[그림 4] C++ 테스트벤치의 코드 조각

C++ 테스트의 for 루프 내부에서 ifc_fill 메소드에 대한 호출을 볼 수 있으며, 이어서 로컬 변수들에 대한 td_h struct 인스턴스의 내용 할당이 따른다. 해당 로컬 변수들은 이 테스트벤치의 DUT인 C 함수에 적용된다.

이 아키텍처는 SystemVerilog 랜덤 클래스나 시퀀스 아이템과 .randomize() 또는 ‘next’ 메소드를 갖는 SystemC/SCV 클래스를 이용하는 것과 사실상 다를 게 없다. 유일한 차이점은 랜덤화를 수행하는 모델이 inFact 그래프 모델이라는 것뿐이다.

이 단계에서 inFact 포터블 스티뮬러스 모델에 의해 추가적으로 주어지는 가치는 여러 가지 수치 값들을 랜덤화 하면서도 이런 값들이나 이들 간의 관계에 대해 정의될 수 있는 모든 대수적 제약을 준수할 수 있다는 점이다.

커버리지의 고려

inFact 모델의 추가적인 가치는 스티뮬러스 모델에 오버레이 시킬 수 있는 또 다른 유형의 입력이 있다는 것인데, 이를 커버리지 전략(Coverage strategy)이라고 부른다. 이 전략은 관심을 두고 있는 변수와 이런 값들의 원하는 구간(Bins)은 물론 이 변수들의 교차를 정의한다는 점에서 SystemVerilog 커버그룹과 다소 유사한 것으로 여길 수 있다. 차이점은 이것이 랜덤화 프로세스에 대한 입력으로서 무작위적 분포를 변경시켜 전략 내의 목표들을 효율적으로 망라한다는 점이다.

이 경우에 측정되는 커버리지 메트릭은 기능 커버리지 커버포인트/크로스가 아니라 C/C++ 환경에서 보다 흔한 코드 커버리지이다(물론 기능 커버리지도 구현할 수 있다). 따라서 커버리지 전략에서 정의되는 목표들은 이름이 시사하듯이 높은 코드 커버리지를 달성하거나 다른 전략들에 포함되지 않은 특정 영역들을 목표할 것으로 기대되는 전략의 인코딩이 돼야 한다.

이 예제의 DUT인 멀티플라이어(Multiplier)는 매우 간단하므로 꽤 간단한 전략으로도 충분할 것이다. inFact 툴 세트에는 다양한 입력들로부터 커버리지 전략을 생성할 수 있는 유틸리티들이 포함돼 있는데, 사전 정의된 유형의 자동화된 전략, CSV 파일이나 스프레드시트를 이용해 정의되는 맞춤화 전략 그리고 그래픽 에디터가 그러한 것들이다. 이 예제에서는 자동화된 전략을 사용할 수 있다. 이 전략은 고립돼 있는 각 스티뮬러스 변수를 겨냥하므로 교차가 없다. test_data 계층구조(각 어레이 요소를 포함한) 내의 각 변수에 대해 해당 유틸리티는 제약사항들의 분석을 채택해 모든 합법적 값들을 확인하고 이들을 정의된 구간 수로 분할한다. 이 예제에서는 총 128개의 구간이 지정됐는데, 그렇게 함으로써 이 배열 내의 각 7비트 요소에 대해 모든 coeff 값들이 망라되기 때문이다. 원한다면 뚜렷한 가장자리 구간들(해당 범위의 최상단과 최하단에 있는 개별 값들)을 추가할 수 있다. 이 경우에는 보다 큰 수량들(10비트 데이터 값들)이 두 극단부의 각각에서 생성된 두 개의 단일 값 구간을 갖고 있었다.

희망했던 대로, 자동화 전략이 완료될 때까지 실행한 뒤의 코드 커버리지 결과는 매우 좋아서 [그림 5]에서 보듯이 백 퍼센트에 이르렀다(초기의 순수한 무작위 테스트 접근방법에 따른 결과는 이보다 20퍼센트 정도 낮았다).

[그림 5] 코드 커버리지 결과

: C++ 소스에서 백 퍼센트의 코드 커버리지를 달성할 수 있는 능력은 동일한 스티뮬러스를 이용해 HLS로부터 합성된 RTL에 대한 커버리지 클로저를 손쉽게 달성할 수 있도록 하는 데 필수적이다. 이는 C++ 커버리지 문제를 디버깅하는 편이 HLS로부터 출력되는 RTL을 디버깅 하는 것보다 훨씬 쉽기 때문이다.

무작위적 안정성을 갖는 포터블 스티뮬러스

높은 코드 커버리지를 달성하는 것도 좋지만, 여기에서는 스티뮬러스 모델과 이에 수반되는 하나 이상의 커버리지 전략을 하나의 도메인에서 개발한 뒤 다른 도메인에서 재실행 하는 방법을 설명하는 것이 목적이다. inFact 스티뮬러스 모델을 위한 시드는 사용자가 정의하거나 간단히 원래의 실행으로부터 파일로 출력할 수도 있다.

이 모델을 SystemVerilog로 랩핑한 버전을 SV 테스트벤치에 투입해 RTL DUT를 C 버전에서와 같은 방식으로 구동할 수 있다. 즉, 단순히 이를 포함하고 있는 SV 클래스 객체를 인스턴스화 한 뒤에 [그림 6]에서 보듯이 여기에 내장된 태스크인 ifc_fill 을 이용해 SystemVerilog test_data 클래스의 내용을 랜덤화 하는 것이다.

[그림 6] SystemVerilog 테스트벤치 코드에서 발췌된 내용

이 경우, test_data 클래스에 사용되는 팩형 배열은 여기서는 DUT 입력에 해당되는 폭 넓은 reg 객체들에 맞도록 재포맷 돼야 한다. 이는 매우 간단해서, {arrEl[0], ... , arrEl[N]}와 같은 연결 연산자를 이용해 달성된다.

이 예에서 커버리지 전략의 상태는 이 전략에 내장돼 있는 다른 사용 가능한 기능인 allCoverageGoalsHaveBeenMet()을 통해 질의할 수 있으며, 이는 새로운 입력을 생성하거나 테스트의 루프 탈출 조건을 정의하기 위한 지정자(qualifier)로 사용된다.

SystemVerilog 테스트벤치가 실행될 때는 RTL DUT를 위해 생성되는 코드 커버리지 또한 높아서, [그림 7]에서 보듯이 커버리지 전략이 완료될 때까지 실행되는 이 같은 경우에는 97.11%에 이른다.

[그림 7] RTL 코드 커버리지 결과

이 예제는 간단하긴 하지만 Questa inFact 툴 스위트가 제공하는 공통적인 portable 스티뮬러스 모델의 재사용성을 분명하게 보여준다. 물론 합성된 RTL에 추가되는 행동(Behavior)을 다루기 위해 RTL 버전의 DUT에는 항상 추가적인 테스트가 필요할 가능성이 높다. 이는 HLS 프로세스가 지연 가능한 인터페이스 프로토콜, 컨트롤 FSM 그리고 클럭과 리셋 로직과 같이 시간정보가 없는 C++ 소스 기술에는 존재하지 않는 구조를 추가하기 때문이다. 그러나 inFact 포터블 스티뮬러스 모델을 이용해 C++에서 백 퍼센트의 커버리지 클로저를 달성하면 RTL 검증을 실행할 경우에도 설계 기능성에 대해 동일한 커버리지를 보장 받게 된다. 그리고 나서는 단지 몇 가지 테스트만 추가해주면 HLS에 의해 추가되는 나머지 구조물을 처리할 수 있다.

보다 복잡한 시나리오의 생성

이런 방식으로 룰 그래프 시나리오 모델을 얼마든지 생성하여 어느 쪽 도메인에든 적용할 수 있다. 예를 들어 테스트 데이터 객체의 두 가지 인스턴스인 td1과 td2를 생성하는 새로운 룰 세그먼트를 추가해 새로운 시나리오를 생성한 뒤, 이들을 룰 내의 동일한 fill 인터페이스에 직렬로 사용할 수 있다. 이를 통해 test_data 내의 필드 중 하나(예컨대, num_col 변수)에 대해 전이(Transition) 커버리지를 달성하게 될 커버리지 전략을 생성할 수 있다. [그림 8]에서 이런 새로운 룰 그래프와, 교차 커버리지를 위한 목표 필드로서 td1과 td2에서 num_col 변수가 선택됨을 볼 수 있다.

[그림 8] 두 개의 테스트 데이터 인스턴스를 갖는 시나리오

이 방법의 또 다른 이점은 C와 SystemVerilog 테스트벤치를 변경할 필요가 없다는 점이다. 이들은 단지 ifc_fill 메소드/태스크를 계속해서 호출하기만 하면 되기 때문이다. 스티뮬러스 모델에서 각각의 랜덤화가 서로 다른 객체라는 사실을 인식할 필요도 없다.

또 다른 가능성은 test_data 객체의 상이한 특화 버전을 생성하는 일이다. 이는 각 인스턴스에 추가적인 제약을 가해 이 같은 특화 버전들의 특정 시퀀스가 시나리오 내에서 서로를 따르도록 강제함으로써 달성된다. 이 역시 각 인스턴스에 대해 동일한 인터페이스를 사용하는 한은 테스트벤치를 수정할 필요가 전혀 없다.

요약

포터블 스티뮬러스 솔루션의 존재는 C 기반의 고급 검증(HLV) 환경에 진보된 검증 기능들을 구현하도록 도울 수 있을 뿐만 아니라, 이를 통해 이미 구현된 다른 추상화 수준의 스티뮬러스 모델과 커버리지 정보를 재사용할 수 있도록 한다.

특히 HLS 사용자들이 그 혜택을 누릴 수 있다. 생성되는 스티뮬러스가 시드 기반의 무작위적 안정성을 통해 양쪽 환경에 모두 미러링 될 수 있다면 특히 그렇다. 해당 디자인의 C 소스에 보다 익숙한 이들에게는 이 수준에서 일련의 포괄적인 스티뮬러스 모델들을 개발하기가 보다 쉬울 것이기 때문이다. HLS 사용자에게 있어서 포터블 스티뮬러스는 C로부터 RTL에 이르는 커버리지 클로저를 예측 가능하고도 신속하게 달성할 수 있는 표준 기반의 방법론을 제공한다.

글: 마이크 앤드루스(Mike Andrews) 검증 기술 전문가,

마이크 핑거로프(Mike Fingeroff) HLS 기술 전문가

자료제공: 멘토 지멘스비즈니스

이 기사를 공유합니다
저작권자 © 테크월드뉴스 무단전재 및 재배포 금지