Translate

2021년 9월 21일 화요일

4장. 인터페이스 합성(Interface Synthesis)

4장. 인터페이스 합성(Interface Synthesis)

개요
실습 1. 블럭 수준 입출력 프로토콜(Block-Level I/O Protocol)
실습 2. 포트의 입출력 핸드쉐이크 프로토콜(Port I/O Protocol)
실습 3. 배열로 주어진 인수의 RTL/FIFO 인터페이스 구현(Implementing Array as RTL/FIFO Interface)
    3-1. 메모리 인터페이스 합성
    3-2. FIFO 인터페이스 합성
    3-3. 핸드쉐이크 없는 함수의 호출과 종료
    3-4. 중단없는 파이프라인
    3-5. 내부구조 선택: 파이프라인 구조 vs 병렬 구조
실습 4. AXI4 인터페이스(Implementing AXI4 Interfaces)

-----------------------------------------------------------------------------------------------------

개요

C 언어에서 설계 단위는 함수(function)다. 함수들 사이의 통신, 값을 전달하고 결과를 받아오는 방법은 C/C++ 언어규범(LRM, Language Reference Manual)의 규정에 따른다. 하지만 하드웨어의 경우 이에 관한 정해진 규정은 없으며 설계 선택 사양에 속한다. C 언어 설계에서 명시되지 않은 하드웨어 모듈 사이의 인터페이스를 생성에 대하여 다뤄보자. 인터페이스(interface)는 입출력 프로토콜(I/O Protocol)라고 도 한다. 데이터 전송과 계산이 클럭의 동기에 맞춰 일어나는 RTL(Register Transfer Level)에서는 단지 데이터 선의 연결로 자료 전달이 이뤄지지 않는다. 계산에 소요되는 클럭의 갯수가 다른 두 모듈 사이에 안전하게 데이터를 주고 받으려면 클럭에 동기된 제어 신호가 필요하다. 전송의 개시권과 종료권을 어느 측이 갖는지의 여부에 따라 마스터(master)와 슬레이브(slave)가 결정되고 그에 따른  전송 개시 요구(request)와 준비(ready) 그리고 확인(acknowledge) 신호가 클럭과 함께 동반된다. 하드웨어의 인터페이스 방법이 매우 다양할 수 있더라도 모든 경우를 수용 할 수 없다. 인퍼테이스는 크게 두가지 범주로 나뉜다. 두 모듈간의 일대일 전송을 블럭 수준 입출력(Block-Level I/O)이라 하며 공통 버스에 복수의 전송 마스터와 슬레이브를 연결하는 경우를 온-칩 버스(On-Chip Bus)라 한다.

-----------------------------------------------------------------------------------------------------

실습 1. 블럭 수준 입출력 프로토콜(Block-Level I/O Protocol) [소스 다운로드]

HLS 도구는 블럭 수준 입출력 인터페이스 합성에 몇가지 규정을 하고 그에 맞는 제어선을 두고 있다. 블럭 수준 입출력 프로토콜(Block-Level I/O Protocol)의 기본 제어 신호와 그 절차적 규정은 다음과 같다.

ap_start

상대 블럭(=모듈)의 동작을 개시한다. (마치 C 언어에서 함수를 호출하는 것과 같다.) 상대가 전송의 종료를 알리는 핸드쉐이크 신호 ap_ready 를 표출할 때까지 1을 유지해야 한다. 이 제어 신호가 1로 떠 있는 동안 상대는 언제든 전송 값을 읽을 수 있음을 표시한 것이므로 무효한 값을 보이면 않된다.

상대편 모듈이 ap_ready 를 1로 띄우기 전(전송동작을 완료하기 전)에 ap_start 를 0으로 내릴 수 있다. 이때 상대는 입력을 읽어서는 않되며 다시 1이 뜰때 까지 기다려야 한다. ap_start 를 0 으로 내리는 이유는 상대에게 이쪽이 다른 일로 바쁘다는 표시를 하는 의미다. 전송완료 신호 전에 ap_start 를 내리려면 상대로부터 입력완료 확인(acknowledge) 신호를 받아두는 양방향 핸드 쉐이크 방식이 좋으나 인터벌 클럭 1개를 더 소모할 수도 있다.

상대가 전송완료 표시 ap_ready 를 1로 띄우면 ap_start 를 계속 유지 할지 다른 전송을 시도할지 결정할 수 있다. 현재 전송을 끝내고 새 전송의 시작을 알리기 위해 현재 전송의 끝에서 0으로 내렸다가 다시 1로 올릴 수 있다. 이는 상대의 동작 휴지 신호 ap_idle 이 뜨는 것을 확인 후 새 전송을 개시하는 경우다. 양방향 핸드 쉐이크 로서 전송을 장담할 수 있으나 추가 클럭을 소모하게 된다.

ap_ready

이 신호는 상대에게 새로운 입력을 받을 준비가 되었다는 표시를 한다. 현재 전송을 완료했고 새 전송을 기다린다는 표시로 1로 올린다. 현재 전송에서 읽기를 끝냈다는 표시이지 작업을 완료했다는 뜻은 아니다. 대개 작업 완료인 ap_done 과 같이 작동하나 항상 동일하진 않다.

만일 파이프라인 동작을 하는 모듈이라면 동작완료 이전에도 ap_start 가 1로 떠있는 한 ap_ready 는 여러번 뜰 수도 있다. 만일 ap_start 신호가 내려가 있다면 이 신호는 현재 전송을 마치고 0으로 떨어진다.

ap_done

이 신호는 현재 받은 전송을 가지고 모든 동작을 마쳤음을 표시한다. 따라서 상대 모듈에서 이 신호를 1로 띄우면 동작완료 이므로 ap_return 포트에 유효한 되돌림 값이 실렸다는 의미가 되므로  상대의 출력 데이터 포트를 읽어도 좋다. ap_return 은 C 함수에 되돌림(return)값이 있는 경우 HLS가 자동으로 생성하는 출력 포트다.

ap_idle

이 신호는 모듈이 작동 중인지 아닌지 표시한다. 만일 상대 모듈이 이 신호 출력을 1로 띄우면  휴지 상태에 있다는 표시다. 이 신호는 ap_start 를 1로 주어 전송을 개시하면 즉각 0으로 떨어진다.

단순 인터페이스(ap_none)는 외부와 데이터 전송 핸드쉐이크를 수행하지 않는다. 단지 기본 입출력 프로토콜 신호 만을 이용해 전송을 수행한다. 모듈에서 전송 받을 준비가 되었다는 신호를 내면 외부에서 입력 데이터를 주고 전송을 개시 한다. 양방에서 아무런 확인 신호 없이 일방적으로 입력을 주고 출력을 내보낸다.

C 설계를 보면 단순한 덧셈 연산으로 조합회로 만으로 합성 할 수도 있었다. 하지만 3입력 가산을 위해 2입력 가산기를 연속으로 사용하면 경로가 너무 길어져 전체 클럭 성능을 떨어 뜨릴 수 있다.

FPGA 를 목표로 하는 HLS 툴은 위험 경로(critical path)를 단축하기 위해 기본적으로 다단 레지스터 구조를 사용한다. ASIC과는 달리 FPGA는 어짜피 플립플롭 들이 LUT와 함께 사전 배치(placement)되어 있는 데다가 배선(route)의 경로 지연이 매우 크기 때문이다. 위험 경로가 늘어나 지연이 증가하느니 차라리 지역 배선에도 유리한 다단 레지스터 구조를 택한다. 다단 레지스터 구조가 되면 성능 조건(constraints)에 따라 인터벌 소요 클럭 수가 고정되지 않게 된다. 따라서 인터페이스의 역활이 매우 중요해 진다. 이 간단한 예에서도 2단 가산기 사용으로 한개이상의 클럭을 사용하도록 합성 되었으므로 외부와 인터페이스가 필요해 졌다.

단순 인터페이스(ap_none)를 지시하고 합성한 결과는 다음과 같다.

3입력 2단 가산기로 합성되어 인터페이스용 제어 신호들이 딸려 나왔다. 단순 인터페이스에서는 리셋(ap_reset)과 스타트(ap_start)는 각각 0 과 1로 고정 시켜도 된다. 다만 입력을 줄 때 입력 준비(ap_ready)가 1로 뜨는지 확인해야 한다. 그리고 인터벌 중간에 입력을 바꾸면 않된다. 가산기는 조합회로다. 세 입력이 첫번째 단 가산기에 직접 연결되어 있다는 점을 간과하면 않된다. 전체 가산기 동작이 완료되기 전에 첫번째 단 가산기에 입력을 주어봐야 소용 없다. 마치 조합회로에서 경로지연(path delay)을 감안하지 않고 입력을 주면 오동작을 일으키는 것과 같다.

합성으로 얻은 HDL과 C 설계를 통합한 Co-Simulation을 SystemC 테스트 벤치로 구성하였다. C 설계와 RTL/HDL에 싸개(wrapper)를 씌웠다. 

C/C++ 기반 테스트벤치의 강점을 보여주기 위해 파이썬(Python)도 내장 시켰다. 파이썬 인터프리터를 C 언어에 불러 들일 때 쓰이는 Python/C API 가 마련되어 있다. 이를 활용하면 수월하게 파이썬 함수를 C/C++ 로 내장하거나 그 반대도 가능하다. 위의 예에서 파이썬을 굳이 DLL(Dynamic Linking Library;Shared-Object)로 불러 들인 이유는 C-HDL-SC Co-Simulation에 사용한 MTI(ModelSim) 툴이 내장한 GCC를 사용하는데 외부의 컴파일러가 생성한 오브젝트 코드를 인식하지 못하기 때문이다. 이를 극복하기 위해 Windows API LoadLibrary()를 통해 파이썬을 불러 들였다.

Co-Simulation으로 얻은 출력 파형을 보면 다음과 같다. 2단 3입력 가산기의 출력이 레지스터 출력이 아니므로 ap_ready 가 1로 떠있을 때만 유효 출력이다. 조합회로인 두번째단 가산기에서 쓰레기 신호가 출력되는데 이를 막아주려면 레지스터 출력(registered output)으로 해주는 것이 좋다. 다만 레지스터 출력을 위해 한 클럭 더 소모한다.

레지스터 출력(registered output) 지시를 하고 합성한 결과는 다음과 같다.

출력에 레지스터를 단 만큼 플립플롭의 수가 늘었고 FSM에 출력 래치 스테이트가 추가 됐으므로 인터벌 클럭 수도 1 증가했다.

시뮬레이션으로 확인해보자. 출력에 쓰레기 신호가 사라졌다. 경로가 길어지면 클럭 성능을 저하시키는 것도 문제지만 불필요한 신호가 다음에 연결된 모듈로 전파될 경우 게이트 토글(toggle)이 늘어나 불필요하게 전력을 소모하게 된다. 따라서 단위 모듈은 출력에 레지스터를 반드시 달도록 설계 규칙으로 정하는 것이 일반적이다.

-----------------------------------------------------------------------------------------------------

실습 2: 포트의 입출력 프로토콜(Port I/O Protocol) [소스 다운로드]

C 언어에서 설계 단위는 함수다. 이 함수는 인수와 함께 호출 되면서 작동한다. 규정된 인수의 양식은 값(value)과 주소(address)다. 값으로 전달되는 인수는 함수의 입력이다. 주소로 전달된 인수는 입력과 출력을 겸할 수 있다. C의 함수에서 출력값을 얻는 방법은 되돌림(return) 을 취하는 것인데 오직 한 개의 객체를 취할 수 있다. 이는 수학의 함수 개념에서 비롯되었다. 인수에 대한 입출력의 개념은 C 언어 규칙에 규정 되어 있으며 값으로 전달된 인수는 함수내에서 지역 변수로 사용될 수 있다. C를 비롯한 고수준 언어에서 함수의  인수에 대한 방향을 따로 명시하지 않는다. 인수의 정의상 포인터(주소로 전달된) 인수가 함수 외부의 변수에 영향을 준다는 뜻에서 출력, 정확히는 입력과 출력을 겸한다는 의미가 부여됐다.

하드웨어에서는 설계 단위를 함수라 하지 않고 모듈(module)이라고 한다. 물리적 전기신호로 정보를 주고받는 하드웨어의 모듈은 함수의 개념과 전혀 다르다. 기호(symbol) 또한 소프트웨어 언어의 변수와 전혀 다르다. 소프트웨어 언어에서 변수와 값은 추상적 개념인데 반해 하드웨어 언어의 기호(symbol)은 물리적 실체다. 하드웨어의 입출력은 전기가 통하는 항상 놓여진 물리적인 전선이다. 하드웨어 언어에서 기호(symbol)는 사용도(coding style)에 따라 전선(wire)이거나 레지스터(register) 일 수 있다.

모듈은 방향이 명시된 임의 갯수 입력선과 출력선으로 연결될 뿐이며 되돌림 값의 개념은 있을 수 없다. 입력은 절대 할당문의 왼편(LHS;Left Hand Side)에 놓일 수 없다. 모듈내 신호선 또한 전기신호의 방향을 분명하게 구분하여 사용되어야 하며 다중 구동(multiple drive) 될 수 없다. 별도의 시작을 알리는 신호 ap_start를 띄우면 작동을 개시하고 ap_done 신호로 종료됐음을 알려온다. 그 사이에 ap_vld 로 입출력 선에 유효한 값이 실렸다는 표시를 해줘야 한다. 데이터 선 이외에 인터페이스, 즉 입출력 프로토콜(I/O Protocol)이 필요한 이유다.

함수의 되돌림 값은 외부로 노출되는 기호로 지정되지 않으므로 합성 지시자를 설정할 수 없다. 따라서 출력에 대한 인터페이스 같은 합성 지정자를 설정 하려면 포인터 변수 이어야 한다. 하드웨어 합성기는 되돌림 값에 대하여 ap_return 이라는 기호를 할당한다. 하지만 포인터 인수는 기호로 표현되었으므로 이 객체에 합성 지시자를 설정할 수 있다. 물리적 신호선으로 존재해야 하는 하드웨어는 값의 추상적 의미를  이해하지 못한다.

예제 C 소스 코드를 보자. 세개의 인수 중 하나는 주소(pointer)다. 다른 두 인수는 입력으로, 할당문 오른쪽에 놓였으며 포인터 인수는 할당문 양쪽에 놓였고 함수의 출력을 겸한다. 마치 양방향 신호선 처럼 보인다.

하드웨어도 양방향 신호선을 선언 할 수 있다. 하지만 하이 임피던스(Hi-Z)라는 특별한 회로로 구성되어야 한다. 하이-임피던스는 패드(pad)의 부가 회로로 구현 되며 코어(core)에 사용할 수 없다. 합성기는 어떤 모듈에서 양방향 포트를 접하면 이를 입력과 출력 포트로 각각 분할해낸다. 포트에 한해 양방향이 제한적으로 허용되지만 모듈 내에서 양방향성을 갖는 신호 기호(signal symbol)는 있을 수 없다. 하지만 이런 제약이 없는 C 언어에서는 할당문 양쪽에 같은 심볼이 놓일 수 있다. HLS 도구는 C 언어에서 포인터 인수가 구문에서 사용된 용도에 따라 입력과 출력으로 분리해 낸다.

예제의 adders_io()의 인수에 각각 다른 인터페이스를 지시한 후 합성 해보자.

합성된 각 포트의 인터페이스 신호들을 보자. 포트 마다 다른 인터페이스 제어선 들이 나왔다.

첫번째 인수 in1에 적용된 인터페이스는 ap_vld 다. 외부에서 유효한 입력이 준비되었다는 뜻으로 in1_ap_vld 신호를 adders_io에 준다. 모듈이 입력을 받았음을 확인해 주지 않으므로 외부에서는 동작이 완료될 때까지 입력을 유지해야 한다.

두번째 인수 in2에 적용된 인터페이스는 ap_ack 다. adders_io 모듈이 in2_ap_ack을 내서 입력을 받았다는 확인을 해주므로 외부 모듈은 입력을 계속 유지할 필요 없이 자기 할일을 수행할 수 있다.

세번째 인수 in_out1의 인터페이스는 핸드쉐이크 ap_hs 다. 포인터 인수는 입력 in_out1_i 와 출력 in_out1_o 로 분할 되었다. 입력 핸드쉐이크 인터페이는 외부에서 모듈로 유효 입력이라는 표시로 in_out1_i_ap_vld 을 띄워 주고 in_out_i_ap_ack 로 확인 받는다. 이후 외부 모듈은 작동이 끝났다는 ap_done을 받기 전까지 다른 일을 해도 좋다.

출력 핸드쉐이크는 모듈이 유효 출력이라는 표시로 in_out1_o_vld 신호를 내보낸다. 외부에서 이를 확인하는 in_out1_o_ack를 표시해 주어야 전송을 종료한다.

단순 인터페이스는 어느 한쪽의 일방적인 전송인데 반해 핸드쉐이크 인터페이스는 전송을 주고 받는 양측에서 확인 절차를 거친다. 유효값이 준비되었음(ap_vld)을 표시하고 이를 잘 받았다는 확인(ap_ack)을 해주는 양방향 절차다. 안전한 전송을 보장 할 수 있으나 핸드쉐이크를 위해 한 클럭 더 소모한다. 인터페이스에서 전송은 항상 ap_ready에서 시작한다. 시뮬레이션으로 예제의 인터페이스 절차를 확인해 보자. SystemC 테스트 벤치의 구성은 다음과 같다.

C 설계를 HLS 툴로 합성하여 얻은 RTL/HDL 과 C 모델 그리고 SystemC 테스트벤치의 혼합 시뮬레이션 결과는 다음과 같다.

-----------------------------------------------------------------------------------------------------


실습 3: 배열로 주어진 인수의 RTL/FIFO 인터페이스 구현(Implementing Array as RTL/FIFO Interface) [소스 다운로드]

-----------------------------------------------------------------------------------------------------

3-1. 메모리 인터페이스 합성

C 설계의 함수의 입출력 인수가 모두 배열인 경우 RTL 인터페이스 구현에 대해 살펴보자. 인수가 배열로 주어진 이유가 있을 것이며 이 배열이 함수 내부에서 어떻게 사용되는지 여부에 따라 적절한 인터페이스를 고를 수 있다. 예제를 살펴보자.

입력 d_i[] 와 출력 d_o[] 가 정방향 순차 관계이므로 파이프라인 방식의 고성능 하드웨어 구성이 가능하다. 단, 색인을 따져보면 8씩 간격이 떨어져 있다는 점에 유의 하자. 예를 들어 d_o[24]은 d_i의 0, 8, 16, 24번째 값에 의존한다. C 소스를 아무런 지시자를 주지 않은채 합성해 보자.

합성 보고서에 의하면 For-_Loop 가 파이프라인 구조로 나왔다. HLS 합성기는 일을 잘해냈다.


-----------------------------------------------------------------------------------------------------

3-2. FIFO 인터페이스 합성

For_Loop 가 파이프라인 구조가 된 덕에 인터벌이 37이다. 두 인수 d_i[N]와 d_o[N]는 메모리 인터페이스다. 함수 array_io()의 가 일을 마치기까지 32번의 입력 읽기가 필요한데 인터벌이 이에 근접해 있다. 이는 메모리 읽기가 직렬로 이뤄진 다는 뜻이므로(Random Access 가 아니므로 주소가 필요 없다.) 입출력에 FIFO 인터페이스를 적용해 보자. 메모리 인터페이스는 주소를 비롯해 메모리 접근용 읽기 및 쓰기 제어선이 더 필요한 반면 FIFO 인터페이스는 한결 단순하다.

합성 보고서를 보자. 인터페이스를 ap_fifo 로 지정한 탓에 제어선들이 없어져 프로토콜이 한결 단촐해졌다. 할일이 줄어든 만큼 동원된 자원도 줄었다. 인터벌은 여전히 37이다.

FIFO 인터페이스를 가진 RTL/HDL 설계물의 SystemC 테스트벤치를 구성해보자. SystemC의 sc_fifo<T >는 클럭 상세 없이 전송 위주의 시스템 수준 모델에 적합한 채널이다. HLS 인터페이스 합성된 입출력 포트를 보면 클럭 상세 수준의 FIFO 인터페이스는 읽기 제어(d_i_read) 및 비었음(d_i_empty_n), 그리고 쓰기제어(d_o_write) 및 꽉참(d_o_full_n) 신호가 딸려있다. 이에 반해 시스템 수준 비시간 sc_fifo 채널은 이런 제어 신호 없이 블로킹(blocking) 방식으로 읽기 쓰기로 접근한다. sc_fifo<> 채널에 읽기 접근하여 자료가 존재하면 바로 값을 읽어오지만 비었을 경우 채널내에서 접근 대기한다. 쓰기 접근의 경우 채널이 꽉 차면 공간이 빌 때 까지 대기 한다. sc_fifo<> 채널은 비 블로킹(non-blocking) 접근 방법도 제공한다. 채널이 비었거나 꽉차서 읽기 및 쓰기가 성공하지 못할 경우 곧바로 되돌려지는 접근 방법이다.

sc_fifo<> 채널에 대한 블로킹 접근은 정체(lag)가 걸리지 않도록 주의한다. 다른 모듈에서 sc_fifo<> 채널에 읽어가기 또는 써넣기 동작이 지체되면 비움과 꽉참이 해소되지 않아 채널내 대기가 무작정 길어져 시뮬레이션이 정체될 수 있다.  이런 정체를 방지할 목적으로  비 블로킹 접근을 활용 한다. 특히 클럭 상세 RTL 모델과 비 시간 시스템 모델을 함께 시뮬레이션 하는 경우 sc_fifo<> 채널에는 비 블록킹 접근으로 정체를 방지할 뿐만 아니라 RTL/FIFO 인터페이스에서 요구한 빔과 꽉참 제어에 대응해 줄 수 있다.

SystemC 테스트 벤치 구성은 다음과 같다. C 설계와 그로부터 고위 합성한 RTL/HDL을 Co-Simulation  한다.

RTL/HDL FIFO 인터페이스와 시스템 수준 sc_fifo<> 채널의 연결을 위한 절차는 다음과 같다. 비 블로킹 접근을 사용하여 시뮬레이션 정체가 걸리지 않도록 한다.

sc_fifo<> 채널의 FIFO 깊이를 각각 달리하여 체증(congestion)을 유도하였다.

HLS로 얻은 RTL/HDL과 C 설계의 Co-Simulation은 다음과 같다. 체증에 대해 시뮬레이션 정체는 없었고 RTL/FIFO 인터페이스와 sc_fifo<> 채널간 연결이 원만하였다.

C 함수의 경우 배열 입력 d_i[N]이 모두 준비 되어야 했으나 출력 d_o[N]과 전방향 의존성 만을 가진 탓에 HLS는 파이프 라인 구조로 합성 했었다. 파이프라인 구조에서는 매 클럭마다 유효 출력을 내고 있다. 파이프라인 구조임에도 32개의 데이터 처리에 37개의 인터벌 클럭이 사용된 이유는 함수의 시작과 종료에 핸드쉐이크(return value: ap_ctrl_hs)가 적용되었기 때문이다. 모듈이 ap_idle을 내보이고 ap_start 를 받아 작동을 개시하여 ap_ready와 ap_done을 냄으로써 종료하는 절차를 치룬다. 이런 함수단위 핸드쉐이크에 필요한 추가 상태를 처리하기 위해 여분의 클럭이 동원된다.


-----------------------------------------------------------------------------------------------------

3-3. 핸드쉐이크 없는 함수의 호출과 종료

HLS로 얻은 RTL 하드웨어에서 함수의 시작과 종료를 외부에 표시하고 확인하기 위해 네개의 제어선을 두고 있다. 이 제어선들은 함수의 시작(ap_idle에 이은 ap_start)과 끝(ap_ready 에 이은 ap_done)을 외부 모듈과 핸드쉐이크 한다. 만일 함수가 내부 동작을 비롯해 입출력까지 온전한 파이브라인 방식으로 작동 한다면 굳이 유효 입력(ap_start)과 출력(ap_ready)을 표시할 필요가 없다. 매 클럭 마다 입력과 출력이 이뤄지기 때문이다. 

HLS 툴은 함수단위의 합성 지시자를 줄 수 있다. 그중 핸드쉐이크도 포함된다. 파이프라인 입출력 인터페이스를 목표로 한 함수를 핸드쉐이크 없이 합성해보자.

파이프라인 구조에서 불필요한 블럭 수준 입출력 핸드쉐이크 신호들이 사라졌다. 시뮬레이션으로 확인해 보자.

매 클럭마다 입출력이 이뤄지는데 실행의 끝에 한 클럭 쉰다. 온전한 파이프라인을 원했다면 불필요한 동작이다.


---------------------------------------------------------------------------------------------------

3-4. 중단없는 파이프라인 [소스 다운로드]

함수의 시작과 종료를 표시할 필요 없는 온전한 파이프라인 구조를 원한다면 합성 지시자에 그 의도를 명확히 해주면 된다.

For_Loop를 PIPELINE으로 합성 하고 반복문을 자동으로 재시작 하라는 rewind 옵션을 주었다. 합성 보고서에 인터벌 클럭이 입출력 데이터의 갯수와 동일한 32다.

이제 시뮬레이션으로 확인 할 차례다. 중단없는 파이프라인 처리가 이뤄지고 있다. 단, acc 가 리셋될 틈이 없다는 점인데 이는 의도된 것으로 하자.



---------------------------------------------------------------------------------------------------

3-5. 내부구조: 파이프라인 구조 vs 병렬 구조

만일 C 설계가 파이프라인 구조 만을 목적으로 했다면 아래와 같이 명확히 의도를 기술하는것이 좋다.

그리고 SystemC 테스트 벤치 통합용 싸개(interface wrapper)의 모습도 달라진다. 메모리에 저장된 32개의 데이터를 통으로(bulk memory) 쓰는 대신 한개씩 데이터가 들어올 때마다(stream-in, pipeline) 함수를 부른다.

하지만 굳이 배열형 인수와 for-반복문을 사용한 것은 입출력 인터페이스와 For_Loop에 다양한 합성 지시를 함으로써 합성되어 얻는 내부구조(micro-architecture)를 선택 가능하기 때문이다. 아래처럼 For_Loop를 풀고 입출력 배열을 분할 하면 병렬 구조로 합성 하게되며 인터벌을 최소화 할 수 있다. 실제 합성하면 병렬 구조의 인터벌은 4 클럭으로 나온다.

입력 인수에 직렬 입력 대신 배열이 통으로 준비되었을 경우라 하고 배열분할(ARRAY_PARTITION)과 반복문은 풀림(UNROLL)을 지시하면 HLS는 병렬 구조(parallel architecture)로 합성해 내며 인터벌을 최대한으로 줄일 수 있다.

최소 인터벌을 얻는 대신 그 댓가로 하드웨어 자원 사용이 급격히 증가할 뿐만 아니라 입출력 선폭이 엄청나게 늘어난다.

반도체 내부 회로임을 감안 하더라도 이정도 폭의 선로를 배선하는 일은 수월치 않을 뿐더러 묶음으로 쓰이는 데이터 패쓰(data path)의 각 배선마다 도착시간(arrival time)이 같아야 한다는 조건도 무시할 수는 없다. (반도체 회로내 배선의 자동화 수준은 완성에 가깝기 때문에 적절한 배치와 옵션을 잘 잡아 주면 크게 어렵지 않게 목적을 이룰 수 있긴 하다.) 시스템의 주변장치로서 온-칩 버스에 연결되어 빠른 처리가 요구된다면 병렬 구조도 고려될 만 하다. 외부 모듈과 연결은 규격화된 온-칩 시스템 버스를 통하기 때문에 배선과 선로 지연의 문제에서 자유로울 수 있다.


-----------------------------------------------------------------------------------------------------


실습 4. AXI4 인터페이스(Implementing AXI4 Interfaces) [소스 다운로드]

AXI(Advanced eXtensible Interface) 는 다중 채널 온-칩 버스 규격으로 특허료 없이 사용할 수 있다. 버스의 선로 갯수에 제한을 덜받는 칩 내부(온-칩, on-chip)는 전송 속도와 전송 방식을 달리하는 다수의 마스터 마다 독립적으로 작동하는 채널을 둘 수 있다. ARM의 AMBA(Advanced Microcontroller Bus Architecture) 규격의 일부 사양인 AXI4는 참고 문서를 찾아보자.[1][2] [참고: Introduction to AXI4 Lite [En Link] [Kr Link]

AXI4 인터페이스(Implementing AXI4 Interfaces)의 C 설계는 앞선 예제와 동일 하다. 다만 함수 이름을 axi_interfaces()로 변경 되었다. 함수의 내용에서 앞서 거론하지 않은 특이 사항은 32개의 입력을 받아 처리 하는데 8개씩 나눠 4번에 걸쳐 누산 처리 한다는 점이다. 32개의 입력을 모두 펼쳤을 경우 16비트 폭의 입력및 출력이 각각 32개의 씩이었다. 과도하게 많다. 성능을 얻기 위해 For_Loop를 펼치되 8개씩 나눠 4번에 걸쳐 처리하도록 지시하고(UNROLL factor=8) 데이터 인터페이스를 AXI4-Stream 으로 지정했다(INTERFACE axis). 32개의 입출력 데이터는 8등분 구간 분할(ARRAY_PARTITION cyclic=8)하고 8개의 AXI-Stream 데이터 채널에 각각 4개씩 직렬 입출력(stream)이 되도록 하였다.

C 에서 합성된 RTL 모듈은 이웃 블럭(모듈)과 통신하지 않고 표준화된 시스템 버스에 슬레이브로 연결 할 것이다. 따라서 시작과 종료를 블럭 수준 입출력 인터페이스로 사용 할 수 없다. 모듈의 제어를 위한 핸드쉐이크는 AXI4-Lite 인터페이스를 사용 한다.

합성 결과를 보자. 인터벌과 자원 사용량 면에서 적절한 타협이다.

이제 시뮬레이션을 해보자. C 합성으로 생성된 RTL 은 AXI4 버스에 물릴 슬레이브다. 시뮬레이션을 위해 테스트벤치에 마스터 역활을 할 모형을 만들어야 한다. 슬레이브의 내부에 존재하는 제어 레지스터의 기능을 알아야 한다. 다행히 자일링스 HLS는 버스 마스터가 될 CPU의 드라이버 소프트웨어도 생성해 준다.

생성된 드라이버 소프트웨어 소스 파일 중 헤더파일에 핸드쉐이크 신호들이 컨트롤 레지스터에 매핑되어 있음을 알 수 있다. 이를 토대로 버스 마스터를 모델링 한다. SystemC 로 작성한 C-HDL Co-Simulation 테스트벤치의 구성은 다음과 같다.

마스터가 할 일은 컨트롤 레지스터를 읽어 슬레이브 모듈의 동작을 감시하고 입출력 데이터를 읽거나 쓰며 시작과 끝을 제어한다.

1. AXI-Lite: 마스터는 컨트롤 레지스터를 읽어 슬레이브가 AP_IDLE 임을 확인하고 AP_START 를 컨트롤 레지스터에 써 넣는다.

2. AXI-Stream: 마스터는 d_i_TREADY 를 읽어 슬레이브에서 입력 데이터를 받을 준비가 되어 있는지 확인 후 d_i_TDATA에 입력을 넣고 d_i_TVALID 로 유효 데이터 임을 알린다. 연속으로 4개의 데이터를 준다.

3. AXI-Stream: 마스터는 슬레이브에서 데이터를 받을 수 있다는 표시로 d_o_TREADY를 1로 올려 놓고 d_o_TVALID 를 감시하여 유효 출력을 받아 낸다. 유효 출력 4개를 연속으로 읽는다.

4. AXI-Lite: 마스터는 컨트롤 레지스터를 읽어 슬레이브가 READY 가 되는지 확인하면 현재 트랜잭션은 종료 된다.

계속 동작을 위해 위의 절차를 반복한다. 위 절차중 1과 2, 그리고 3 과 4는 동시에 작동해도 좋다.


---------------------------------------------------------------------------------
고위 합성 튜토리얼(High-Level Synthesis Tutorial)








2021년 9월 8일 수요일

3장. C 설계의 바름 검증 (C Validation)

 3장. C 설계의 바름 검증 (C Validation)

개요
실습 폴터구조
실습 1. GNUPLOT 를 이용한 도식화
실습 2: GNUPLOT, Windows GDI+, SDL Lib 그리고 Python을 내장한 SystemC 테스트 벤치
    2-1. sc_fifo<T> 채널을 이용한 시스템 수준(System Level) 테스트 벤치
    2-2. sc_signal<T*>
    2-3. sc_signal<T>, 클럭 상세(clock-level data transfer) 데이터 전송
실습 3. Arbitrary Precision Type

----------------------------------------------------------------------------------------------------

개요

합성이나 검증 도구들이 제아무리 우수 하더라도 최초 C 설계가 바르게 되었어야 한다는 점은 두말할 나위가 없다. 앞선 fir() 예에서 해봤듯이 검증 벡터 파일을 읽어 설계의 출력과 비교하는 방식이 비교적 수월하다. 하지만 미리 만들어 놓은 벡터 파일에 오류(error)와 손상(defect)으로부터 무결함과 모든 경우를 충분히 고려되었다고(completeness) 보장 되어야 한다.

검증용 참조 벡터 파일이 어짜피 알고리즘을 통해 만들어 졌다면 굳이 중간에 벡터 파일을 만들고 이를 다시 테스트 벤치에서 읽어 들이는 위험(또는 버거로움)을 감수할 필요는 없다. 차라리 알고리즘을 테스트 벤치에 포함 시킴으로서 검증 벡터 훼손의 위험을 차단할 수 있다. 멀티미디어 신호처리, 기계학습 응용처럼 대규모 시험용 입력 벡터와 검증 벡터를 요구하는 경우 파일 입출력 또한 시뮬레이션에 상당한 부담이 된다.

HDL 도 파일 입출력 같은 테스트 벤치 작성용 APl들을 갖추고 있으나 고수준 컴퓨팅 언어에 비할 바가 못된다. 특히 알고리즘 개발과 검증용의 우수한 시스템 소프트웨어 패키지들이 제공하는 API를 들여다 쓰려면 C/C++ 테스트 벤치의 필연성은 부정하기 어렵다. 다만 소프트웨어 개발용의 C 언어에는 하드웨어 묘사를 위한 시간(time management)과 사건처리(event process) 그리고 하드웨어 채널(HW channel)의 개념이 없다. 이를 극복하기 위해 HDL에 C 루틴을 연결해 주는 여러 기준(PLI/VPI, VHPI, FLI, DPI 등등)이 존재하지만 복잡하기도 하거니와 해당 HDL 이나 특정 툴 벤더에 종속되어서 알고리즘 코드를 변경해 주어야 하는 일까지 생긴다. 따라서 C 알고리즘과 합성으로 얻은 RTL/HDL을 변경없이 적용할 수 있고 충분히 검증된 시스템 툴 API 들과 라이브러리 들을 사용할 수 있는 SystemC를 사용해야 하는 이유라 할 것이다.

실습 폴더 구조

실습1. GNUPLOT 를 이용한 도식화 [다운로드]

신호처리에서 많이 쓰이는 해밍 윈도우(Hamming Window) 함수를 HLS를 목표로 작성한 C 설계와 순수 C 모델과 비교 검증하는 테스트벤치를 작성해 본다. 윈도우 함수는 단순 하지만 곱셈의 연산량이 매우 많아 실시간 처리를 위해 파이프라인 구조의 하드웨어를 구성하면 효과적이다. [참고: Window Function을 쓰는 이유 / Window Function ]

해밍 윈도우 함수의 효과를 도식적으로 확인 할 수 있도록 파워 스펙트럼으로 보여준다. 이를 위해 C 로 작성된 simple_fft2() 를 테스트 벤치에 포함 시켰고 입출력 데이터를 그림으로 확인하기 위해 gnuplot 툴을 테스트 벤치에 연동 시켰다. 결과를 그림으로 보면서 DSP에서 윈도우 함수의 효용성을 직관적으로 확인해 볼 수 있다. [참고: Simple DFT in C , gnuplot homepage ]


테스트 벡터와 연산의 결과를 파일로 생성하고 외부의 도구를 이용해 읽고 확인하는 번거로운 과정 없이 테스트벤치에 내장하므로서 검증의 생산성을 높인다. 수많은 신호처리 라이브러리와 소프트웨어를 검증에 사용 할 수 있다는 점은 설계 검증에 C 언어를 사용하는 가장 큰 장점 중에 하나다. Numerical RecipePython NumPy, MatLab 등 이미 이공계 뿐만 아니라 모든 산업의 개발업무에 적용되고 있다.

실습 2: GNUPLOTWindows GDI+SDL Lib 그리고 Python을 내장한 SystemC 테스트 벤치

2-1. sc_fifo<T> 채널을 이용한 시스템 수준(System Level) 테스트 벤치 [다운로드]

아직 하드웨어 구조가 구체적으로 정해지지 않았으므로 비트 상세(bit-wise detail) 및 클럭 상세(clock detail) 의 테스트 벤치 구성이 불가하다. 하지만 SystemC는 시스템 수준의 기술을 위해 모듈간 다양한 통신체널을 제공한다. 시스템 수준의 모델 기술을 위해 제공되는 FIFO 채널을 이용해 모듈간 통신 연결을 구성한다.

sc_fifo<> 채널로 연결된 모듈간 통신은 핸드쉐이크가 필요없다. 이 통신 채널 내에서 전송을 제어 한다. 테스트 벡터 생성 모듈 sc_stimulus에서 각 모듈로 데이더 전송의 동기가 별도의 핸드쉐이크 없이 채널 내에서 맞춰 질 수 있음을 보기 위해 sc_fifo 채널의 깊이가 달리 하였다.

2-2. sc_signal<T*> [다운로드]

모듈간 데이터 전송은 채널을 통해 이뤄진다. SystemC에서 제공하는 기본 채널로 sc_signal<T>가 있다. 채널에 실릴 수 있는 자료형(data type) T는 C/C++ 의 모든 자료형이 가능 하다. 값 뿐만 아니라 주소(포인터) 사용도 가능하다. sc_signal 채널은 sc_fifo 와는 달리 채널내에 전송 동기를 맞추는 기작이 포함되어 있지 않다. 따라서 별도의 핸드 쉐이크 신호가 필요하다. 시뮬레이션 개시를 위해 reset 이 사용 되었고 시뮬레이션 진행은 클럭 clock 신호를 따른다. 하지만 모듈간 데이터 전송은 여전히 클럭 상세는 아니다.

2-3. sc_signal<T>, 클럭 상세(clock-level data transfer) 데이터 전송 [다운로드]

아직 HLS 가 이루어 지기 전이지만 모듈 간 클럭 상세수준(clock-level)의 데이터 전송을 모델링 해보자. 매 클럭 마다 데이터가 채널에 실린다. 클럭 상세 모델의 경우 채널에서 데이터 전송 동기를 맞춰주지 않으므로 핸드 쉐이크(request-ready handshake) 과정이 필요하다.

앞의 예제에서 SystemC 테스트 벤치에 내장 시킨 파이썬(Python)은 Numpy와 matplotlib를 이용해 그래프를 그리는 용도로 사용 했었다. 앞으로 사용할 RTL 시뮬레이터인 ModelSim에 SystemC 테스트 벤치를 적용할 것이다. 유감 스럽게도 matplotlib 가 그래픽 드라이버 호환성이 떨어져 ModelSim 에서 사용 할 수 없었다. 다행히 gnuplot은 사용 가능 하다. 따라서 Python의 용도를 파워 스펙트럼 구하는 용도로 변경 하였다. C 로 작성된 fft 소프트웨어를 그대로 Python 함수로 만들었다.

아직 C 설계가 합성되기 전이지만 SystemC 테스트 벤치에서 모듈 간 데이터 전송은 클럭 상세 수준이다. Python을 내장한 SystemC 테스트 벤치가 HDL 시뮬레이터에서 실행 가능 하다. 이 SystemC 테스트 벤치는 향후 HLS 된 RTL Co-Simulation 에 사용될 것이다.


실습 3. Arbitrary Precision Type [다운로드]

고수준 추상화 언어에서 자료형의 기본 단위는 바이트(byte, 8-비트) 다. 컴퓨터 구조의 태생적으로 한개 문자를 표현하기 위한 비트폭을 기본 단위로 정한 데서 연유한다. 디지털 하드웨어에서 자료의 기본 단위는 2진수 한자리로서 비트(bit)다. 알고리즘이 적용될 분야에 필요한 만큼의 정밀도와 숫자의 가변 범위에 따라 자료의 폭을 비트 단위로 정해야 하드웨어의 낭비가 없다. C 언어에서 하드웨어를 설계 할 때 성능좋은 합성기라면 테스트 값으로 알고리즘을 적용하여 가변 비트 폭을 정해 줄 수도 있으나 아직 그 수준에 이르진 못했다. 사실 설계의 검증에 사용된 테스트 벡터가 실사용에 충분할 만큼 마련되었다고 장담하기도 어렵다. 따라서 C 설계 단계에서 부터 설계자가 오버플로우 같은 연산 오류를 방지하고 정밀도 측정을 바탕으로 구체적인 비트폭을 결정할 수 있어야 한다.

HLS 도구들은 이러 점을 감안 하여 자료의 비트폭을 구체적으로 표현한 기법을 제공한다. 다행히 C++의 템플릿은 임의의 자료형을 선언하는 방법을 제공한다. 예를 들어 16비트와 32비트 자료형을 C/C++에서 정의하면 다음과 같다.

typedef int16_t in_data_t;
typedef int32_t out_data_t;

이를 구체적으로 32비트 폭의 자료 객체임을 명시하는 방법으로 SystemC 에서는 다음과 같이 선언한다.

#include "systemc.h"
typedef sc_int<16> in_data_t;
typedef sc_int<32> out_data_t;

자료형의 비트단위 구체화는 비트 단위 상세의 하드웨어를 겨냥한 것이다. 

---------------------------------------------------------------------------------
고위 합성 튜토리얼(High-Level Synthesis Tutorial)