"RTL Verilog-Verilator-SystemC TB 방법론 기초"
하드웨어 기술 언어의 코딩 스타일
목차:
1. 개요
2. 베릴로그로 작성하는 D-플립플롭
2-1. 모듈 (module ~ endmodule)
2-2. D-플립플롭의 행위 기술
3. SystemC 테스트벤치
3-1. SystemC 모듈의 기본구성
3-2. DUT의 사례화와 연결
3-3. 테스트 벡터 생성(test vector generation)
a. 클럭 객체 sc_clock
b. 사건구동 함수 지정
c. 하드웨어 객체 sc_signal<>
3-4. VCD 파형
3-5. int sc_main(int argc, char** argv)
4. 메이크(make) 유틸리티
4-1. 목표와 의존 관계
4-2. 내부 변수
4-3. 다중 목표
5. 하드웨어의 코딩 스타일
5-1. 감응 목록이 D-플립플롭에 미치는 영향
5-2. 동기 혹은 비동기 셋과 리셋을 가진 D 플립플롭
6. 맺음말
----------------------------------------------------
1. 개요
Verilog로 설계하고 SystemC로 검증 하는 설계 방법론을 소개한다. 설계의 예는 디지털 회로의 가장 단순한 D-플립플롭이다. D-플립플롭은 이미 이전 강좌에서 트랜지스터 회로 수준에서 설계 했었다[바로가기]. 디지털 저장장치(메모리 또는 플립플롭)를 일일이 트랜지스터의 지연된 스위칭 동작으로 구현했던 것과 비교하면 높은 추상화 수준의 설계 방법론이 생산적일 수 있다는 점을 알게될 것이다. 하드웨어의 행동을 언어로 표현 하므로서 얻는 장점을 확장하여 Verilog 설계와 SystemC 검증을 통하여 “시스템 수준”이라는 새로운 관점에서 살펴본다.
Verilog와 SystemC의 기본 구성요건과 시뮬레이터를 구성하는 과정을 소개하고 소프트웨어로 하드웨어의 병렬 실행을 모의하는 방법을 살펴볼 것이다. 설계자로서 도구의 사용자 이지만 도구가 작동하는 원리를 이해하면 학습진도를 가속화 할 수 있을 뿐만 아니라 높은 추상화 수준에서 시스템 모형화(system modeling)를 시작할 때 도움이 될 것이다.
본 문서에서 설명하는 예제의 파일들은 디자인 킷[바로가기]의 튜토리얼 중 아래 디렉토리에서 찾아볼 수 있다.
$ cd ~/ETRI050_DesignKit/Tutorials/2-3_Lab1_dff/dff
~/ETRI050_DesignKit/devel/Tutorials/2-3_Lab1_dff$ tree
.
├── dff
│ ├── dff_Config.txt
│ ├── dff.v
│ ├── Makefile
│ ├── sc_dff_TB.h
│ └── sc_main.cpp
├── dffrs
│ ├── dffrs.v
│ ├── Makefile
│ ├── sc_dffrs_TB.gtkw
│ ├── sc_dffrs_TB.h
│ └── sc_main.cpp
└── shifter
├── Makefile
├── sc_main.cpp
├── sc_shifter_TB.h
└── shifter.v
2. 베릴로그로 작성하는 D-플립플롭
Verilog는 하드웨어의 행동을 묘사하기 위해 등장한 컴퓨팅 언어다. 하드웨어를 다루는 만큼 병렬실행(concurrency) 구문을 기본으로 행위의 기술을 위해 순차실행(procedural)을 수용한다. 베릴로그 시뮬레이터는 하드웨어의 병렬성을 흉내내기 위해 사건 구동(event-driven)과 지연 할당(deferred assign) 기법을 활용한 소프트웨어다. Verilog 언어에서 이 기법들이 어떻게 동원되는지 살펴본다.
2-1. 모듈 (module ~ endmodule)
베릴로그의 설계(기술)단위는 모듈(module)이다. 이름이 "dff"인 모듈의 외형(boundary)을 기술하면 아래와 같다.
- 모듈을 정의하는 베릴로그의 키워드는 module 이다. 모듈 정의는 세미콜론(;)으로 끝난다.
- 모듈 기술의 끝은 endmodule 이다.
[주] 세미콜론(;)은 문장의 끝을 표시하는 마침 부호다. C 언어와 같다. 모듈의 끝을 표시하는 endmodule 은 문장의 끝이 아니므로 문장 마침표(;)가 없다. C 언어에서 제어 영역을 묶기 위해 중괄호 {...}를 사용 하듯 베릴로그는 예약어 begin ... end 를 사용한다.
- 모듈 dff 는 입출력 포트로 clk, d, q를 가지고 있다.
- 포트(port)는 입출력 방향(direction)을 명시해야 한다. 방향을 표시하는 예약어는 input, output, inout으로 3종류다.
[주] 하드웨어 언어는 전자회로를 기술하고 있다는 점을 항상 기억해 두자. 전류의 입력(current source)과 출력(current sink)을 반드시 구분해 주어야 한다. 입력 포트는 할당(=)의 오른편 rhs(right-hand side)에, 출력 포트는 왼편 lhs(left-hand side)에 만 놓일 수 있다.
- 주석문(comments)은 C 언어의 것과 동일하다.
2-2. D-플립플롭의 행위기술
D-플립플롭의 행동(behavior)는 클럭으로 지정된 신호의 에지 사건(edge event)에 의해서 만 반응한다. 아래의 예는 clk의 상승 엣지에 반응하는 플립플롭을 기술하였다.
- 순차적 행동의 묘사(procedural behavior description)는 always 구역 내에서 기술된다.
- 순차구문의 할당연산(<=) 왼편에 놓일 신호는 reg 속성을 가져야 한다. 출력 포드 q 는 네트(net 또는 wire)이지만 순차구문 구역 내에서 사용되기 위해 reg 로 속성이 부여되었다.
[주] 마치 중복 선언된 것처럼 보이지만 하드웨어 객체 속성을 지정한 것이다. 베릴로그는 선언(declare)과 속성부여(attribute)에 애매한 면을 가지고 있다.
[주] 레지스터 reg 속성을 가졌다고 해서 반드시 플립플롭을 의미하는 것은 아니다. 단지 언어적으로 순차할당(procedural assignment)의 왼편에 놓을 수 있다는 뜻이다.
- 행위의 기술(behavior description)로부터 D-플립플롭으로 해석될 수 있다.
[주] 묘사(행위 기술)를 해석하여 전자회로를 유추(inference)해 내는 일은 합성기(synthesizer)의 중요한 역할 중 하나다. 합성기의 또 다른 역할로 최적화(optimization)가 있다.
- 예약어 posedge 는 입력 신호 clk의 상태를 평가(evaluate)하여 상승 엣지 사건인지 판별해 주는 연산자다. 이 평가가 참이면 always 구역내의 순차구문이 실행된다.
[주] 사건에 의한 always 구역의 실행을 사건구동(event-driven)이라 한다. 소프트웨어 작성 기법에서 사건과 콜백(event & call-back function) 또는 마이크로프로세서의 인터럽트(interrupt) 메커니즘과 완벽히 같은 의미다.
3. SystemC 테스트벤치
우리는 정보화 사회에 살면서 컴퓨팅 언어를 어느 정도 들어 알고 있다. 대부분 컴퓨팅 언어들은 기본적인 대수 표현법을 차용하고 기초 영문 단어와 문법를 채택하고 있어서 따로 배울 필요가 있나 할 정도다. 컴퓨팅 언어에 의한 묘사는 결국 컴파일러라고 하는 자동 변환 소프트웨어에 의해 기계가 이해할 수 있는 형식으로 바뀐다. 따라서 컴퓨팅 언어는 단순 명료할 수 밖에 없다. 컴퓨팅 언어의 문법 보다 특정 컴퓨팅 언어가 묘사하려는 대상의 특성을 이해하는 것이 중요하다. 베릴로그는 하드웨어를 기술하려는 목적으로 발명된 컴퓨팅 언어다. 하드웨어 중에서도 디지털 회로 요소들의 행동을 묘사할 목적을 가지고 있다. 연산자와 제어 구문의 의미는 다른 컴퓨팅 언어와 다를 바 없다. 다만 문장의 실행 방식이 하드웨어의 성격대로 동시성(문장의 순서에 무관한)을 가진다는 점이 가장 큰 차이라 할 것이다. 우리가 알고리즘을 묘사한 원시 코드를 보면서 어렵다고 느끼는 것은 내용을 모르는 것이지 컴퓨팅 언어의 문법 탓이 아니라는 점을 알아야 한다.
역사적으로 묘사하려는 대상과 목적에 따라 다양한 컴퓨팅 언어가 발명 되었고, 목적이 분명히 나뉘는 소프트웨어와 하드웨어로 양분되어 발전해왔다. 하지만 설계의 규모가 기하급수적으로 증가함에 따라 생산성 문제의 해결 방안으로 높은 추상성이 요구 되었다. 이에 현존하는 컴퓨팅 언어 중 가장 광범위하게 사용되면서 높은 그리고 폭넓은 추상성을 가진 컴퓨팅 언어 C++로 합치려는 시도가 이뤄져 이에 탄생한 것이 SystemC 다. 자료구조와 알고리즘의 묘사를 보다 수월하고 재사용성을 높이기 위해 제정된 STL(Standard Template Library)이 있듯이 SystemC는 하드웨어의 동시성을 "시스템 수준"에서 묘사하기 위한 C++의 크래스 라이브러리로써 IEEE Std.1666 으로 제정되었다.
"시스템 수준(System Level)"이라는 문구가 매우 다양하게 인용 되어 때로 오해를 낳기도 한다. 한마디로 말하자면 하드웨어 묘사를 C++ 언어의 크래스 객체로 두고 이를 소속 함수로 다루겠다는 의미다. 하드웨어를 기술한 레지스터 트랜스퍼 수준(RTL)은 "시스템 수준"의 하위에 놓인다. 여러 알고리즘들이 모여 한 집합체를 다루는 시스템 수준에서 하드웨어의 동시성과 클럭 그리고 비트 폭은 관심없다. 시스템 수준에서는 각 알고리즘들이 어떻게 구현되었는지 알고 싶지 않다. 시스템 수준에서 검증(verification)은 단지 적절한 때에 원하는 값을 얻고자 할 뿐이며, 구조 탐색(architecture exploration)은 입력에 대하여 출력을 얻기까지 어느 정도 자원(소요 클럭 수, 하드웨어 량)을 사용하는지 따져보기 위함이다.
베릴로그로 작성된 D-플립플롭을 DUT(Design Under Test) 삼아 테스트벤치(testbench)를 SystemC로 작성한다. 비록 매우 단순한 DUT 이지만 앞으로 시스템 수준의 검증환경을 구축하는 입문이다. 일반적인 테스트벤치의 구성은 다음과 같다.
- DUT의 사례화(instantiate)
[주] '사례화'라는 용어가 생소하다. 조금 의미를 담아 표현하면 기술해 놓은 함수 또는 모듈 객체를 "존재하게 한다"고 하겠다.
- DUT의 입력에 줄 신호를 생성
- DUT의 출력 검토
SystemC 가 C++의 크래스 라이브러리라고 하지만 처음 접하면 마치 또다른 언어처럼 생소하다. C++의 크래스에 대한 기초 지식을 동원하여 이해 해보자. 당장 이해가 않되는 부분은 일단 받아들이자.
[주] SystemC를 C++를 이용하여 베릴로그를 흉내냈다는 식으로 소개 되곤 한다. 이런 이유로 굳이 SystemC를 해야 하나 하는 생각이 일견 들것이다. 이는 SystemC를 시스템 수준 모델링 능력을 제외한 채 매우 일부분인 RTL로 만 접했기 때문이다. 시스템 수준 모델링은 천천히 알아가기로 하고 기왕 배워놓은 C/C++를 하드웨어 설계에 활용한다고 여기자. C++도 익히고 하드웨어 설계도 배우면 일석이조 아닌가.
[주] SystemC를 설명하는 아주 좋은 동영상이 있다. 6편의 짧은 동영상으로 구성 되어 약 2시간 분량이다. SystemC가 생소하다면 잠시 시간을 내서 들어보길 권한다. Learn SystemC [바로가기]
3-1. SystemC 모듈의 기본구성
SystemC의 모듈의 구성은 아래와 같다. 외형적으로 보면 마치 또다른 언어체계 같지만 뜯어보면 C++의 크래스다.
- SystemC 크래스 라이브러리를 사용하기 위해 systemc.h 헤더 파일 인클루드(include)
- SC_MODULE 은 C++의 크래스를 재정의한 매크로다.
#define SC_MODULE(name) class name: ......
- 크래스 끝에 세미콜론(;) 이 있다.
[주] C++의 크래스는 C 의 구조체를 매우 확장한 것이다. 크래스는 자료의 정의뿐만 아니라 객체 자료형 정의 typedef 까지 수행한다.
- SC_CTOR() 은 C++의 구성자(constructor) 지정 매크로다. 당연히 모듈 크래스와 동일한 이름을 가져야 한다.
- 하드웨어 객체 sc_signal<>은 베릴로그의 wire 또는 reg 를 모사한 C++의 크래스다. 하드웨어의 비트 단위 선언을 위해 템플릿(template)을 사용하고 있다.
- sc_clock 은 특별한 하드웨어 객체로 클럭 신호를 생성해 준다.
[주] SystemC의 구문을 보면서 매우 생소하다면 C++에 익숙치 못한 탓이다. 이참에 C++의 크래스를 좀더 이해해 보는 기회가 되기 바란다. 어쨌든 아래 구문은 모두 C++ 라는 점을 기억하자.
3-2. DUT의 사례화와 연결
DUT의 사례화(instantiation)와 지역신호 연결(binding)은 두 언어의 문법적 차이만 있을 뿐이지 완전히 동일한 의미다. 아래와 같은 베릴로그의 신호 연결(binding)이 있다고 하자.
dff u_dff (
.clk(clk),
.d(d),
.q(q));
SystemC에서 연결을 수행하는 과정을 살펴보면 다음과 같다. C++의 포인터 매핑이다.
- Verilog로 작성된 DUT를 C++에 직접 불러올 수 없으므로 언어 변환을 통하여 C++ 언어체계로 들여온다. 변환된 DUT의 헤더 파일이 Vdff.h 다.
[주] 매우 넓은 추상성을 가진 SystemC는 Verilog의 RTL 표현과 등가적 표현이 가능할 뿐만 아니라 사건구동 병렬 시뮬레이터 커널이 라이브러리에 내장되어 있다. Verilator는 베릴로그를 SystemC/C++ 로 변환해 주는 오픈-소스 도구다. 베릴로그 모듈 명에 대문자 V 를 붙여 SystemC 모듈 크래스를 생성한다.
- 테스트벤치 모듈 크래스 내에 DUT를 선언하고 사례화 한다. DUT는 포인터로 선언되었고 C++의 객체 동적 할당 연산자 new로 사례화 한다.
- 동적 할당된 DUT의 포트들을 SystemC 테스트벤치의 지역 하드웨어 객체 sc_clock, sc_signal<>와 연결한다.
- DUT의 사례화와 연결은 모두 테스트벤치 크래스의 구성자 내에서 이뤄진다. 이 과정을 내부 구축(elaboration)이라고 한다. 베릴로그의 initial 에 해당하는 절차도 구성자 내에서 수행한다.
3-3. 테스트 벡터 생성(test vector generation)
DUT에 입력을 넣고 시뮬레이션을 실시하여 그 동작을 확인한다. 예제 D-플립플롭의 입력은 클럭 clk와 d 다.
a. 클럭 객체 sc_clock
SystemC는 주기적으로 끝없이 반복되는 클럭을 생성하는 특별한 크래스 객체 sc_clock 를 가지고 있다. 주기를 가지고 파형을 자동 발생시키기 위해 초기화 해준다. SystemC 간단 참고문서[1]를 보면 sc_clock 객체의 초기화 방법은 다음과 같다.
sc_clock("ID", period, duty_cycle, offset, first_edge_positive);
D-플립플롭 예제에서 클럭 객체는 모듈 크래스의 소속 데이터(member data)로 선언 되었다.
sc_clock clk;
이어 구성자가 실행될 때 클럭 객체를 초기화 한다.
SC_CTOR(sc_dff_TB): // constructor
clk("clk", 100, SC_NS, 0.5, 0.0, SC_NS, false)
{ ...... }
D-플립플롭의 다른 입력 d 는 시험 절차에 따라 발생 시킨다. 모듈 크래스의 소속함수 (member function) test_generator() 에 시험 절차(순서)에 따른 d 의 생성을 기술 하였다.
b. 사건구동 함수 지정
SystemC는 하드웨어 묘사를 위한 다양한 객체들의 크래스와 사건 구동 병렬실행기(event-driven simulation kernel)를 내장하고 있다. 사건탐지에 쓸 신호를 지정하고 이에 구동될 함수(콜백, call-back)를 지정한다. 이 지정은 시뮬레이션이 가동되기 전에 미리 구축 되어야 하므로 모듈 크래스의 구성자에서 수행한다. 시뮬레이션 전에 준비하는 상세화(내부구축, elaboration)과정의 일부다.
위의 SystemC예에서 보인 쓰레드 함수와 사건 감응신호 지정은 베릴로그의 always @(sensitivity_list) 에서 감응 리스트의 지정과 동일한 의미다.
[주] 하드웨어를 기술하는 언어(Verilog, VHDL 등)는 병렬 실행 구문을 기본으로 순차 실행 구문 구역을 지정하지만 C++ 는 병렬 실행의 개념을 가지고 있지 않다. SystemC는 병렬 실행을 모의하기 위해 사건 구동 시뮬레이션 커널을 를 내장하고 있다. 베릴로그에서 사건에 의하여 구동될 순차 구문 구역이 always 에 이어지지만 모두 순차구문인 C++로 사건 구동을 구현해야 하는 SystemC는 사정이 다르다. 감응(sensitive)과 이에 반응하여 호출될 함수(call-back function)를 따로 지정한다. 구동될 함수는 호출 인수와 되돌림 값이 모두 void 인 모듈 크래스의 소속함수(member function)다. 사건에 대하여 콜백 함수를 호출하는 주체는 사건구동 시뮬레이션 커널이다. 함수를 운영하는 방식은 메쏘드(method) 혹은 쓰레드(thread)가 있다. SC_METHOD() 로 지정된 함수는 감응 신호에 사건이 발생할 때마다 호출된다. SC_THREAD()로 지정된 함수는 준비과정(elaboration process)에서 한번 호출되므로 함수 내에 무한 반복 구간을 두고 사건 대기 구문을 가지고 있어야 한다. 소프트웨어에서 병렬 프로그래밍 기법으로 흔히 사용하는 멀티 쓰레딩(multi-threading)과 같다.
테스트 입력 d 를 발생 시키는 쓰레드 함수 test_generator()의 무한 반복 구문 while(true) {...} 내에 사건을 대기하는 wait(....) 를 두고 있다. SystemC 의 wait(...) 는 감응으로 지정한 신호에 사건이 발생할 때까지 대기한다. 이는 실행 제어권을 시뮬레이션 커널에 양보(yield)하는 역할을 한다. SystemC 간단 참고문서[1]에 wait(...)의 사용 방법을 찾아보면 다음과 같다.
wait(); // Wait for event as specified in static sensitivity list
wait(event_expression); // Temporary overrides the static sensitivitiy list
wait(time);
wait(time, event_expression);
하드웨어 객체 clk 에 상승 엣지(또는 하강엣지) 사건이 발생할 때까지 시뮬레이션을 대기 시킬 수 있다.
wait(clk.posedge_event());
wait(clk.negedge_event());
c. 하드웨어 객체 sc_signal<>
예제에서 DUT의 입력에 연결한 d 는 sc_signal<> 로 선언된 하드웨어 객체다. 이 객체에 접근하는 방법은 읽기 .read() 와 쓰기 .write() 가 있다. 소속함수로 접근 할 수 있고 할당 연산자 = 가 중복정의(오버로드, overload) 되어 있다. 예를 들어, d = 1; 과 d.write(1); 은 동일 하다. 하드웨어 언어에서 모든 할당은 하드웨어 객체를 상대로 하는 것이 기본 이지만 C++ 언어는 모두 변수 할당이다. 하드웨어 객체에 대한 접근과 변수 접근을 구분하기 위해서 라도 할당 연산자 대신 소속함수(메쏘드) .read()와 .write()를 통해 접근하는 방식을 쓰도록 한다.
[주] 하드웨어에서 부품간 연결에 단순히 "전선"이라 부르는 "와이어"를 사용한다. 이 "와이어"를 컴퓨팅 언어로 표현하려면 고려할 사항이 많다. 소프트웨어에서 "전선"의 개념 없고 "변수"만 있을 뿐이다. 게다가 전류가 흐르는 전선에는 시간적 순서가 없다. 디지털 회로에서 조합회로와 순차회로를 구분 하는 이유는 "순서" 때문이다. 하드웨어 객체 sc_signal<>는 C++라는 소프트웨어 개발 언어로 하드웨어의 "와이어"를 모사하기 위한 템플릿-크래스로서 단순한 변수 이상의 의미를 가지고 있으며 다양한 접근 방법(method)을 갖추고 있다. 이 하드웨어 객체 sc_signal<>는 사건 구동 병렬실행 시뮬레이션 커널의 중요 매개이기도 하다. 프로그래밍 언어의 변수와 하드웨어 객체를 구분하는 근본적인 이유는 소프트웨어로 하드웨어의 행동을 모의하려고 하기 때문이다. 하드웨어의 행동을 소프트웨어로 흉내 내기 위해 사용된 기법이 사건구동(event-driven)과 지연 할당(deferred assignment)이다. 이에 대해서는 하드웨어 시뮬레이터의 내부(simulator kernel)를 다룰 때 살펴보기로 한다.
3-4. VCD 파형
디지털 하드웨어 설계의 입출력을 관찰하는 고전적인 방법은 역시 파형보기(waveform view)다. VCD(Value Changed Dump)는 디지털 파형을 기록하는 베릴로그 표준 형식이다. SystemC는 하드웨어 객체의 변화를 VCD 로 기록하는 방법을 제공한다. 모듈 크래스 구성자에서 VCD를 기록하도록 지정할 수 있다. 베릴로그에서는 VCD 추적을 initial 구역에 지정 했었다.
- VCD 파일명은 "sc_dff_TB.vcd" 다.
- VCD 파일에 기록할 신호는 clk, d, q 다.
3-5. int sc_main(int argc, char** argv)
C++ 프로그램의 시작은 main() 호출이다. SystemC의 시작은 sc_main()이다. 헤더 파일로 기술한 테스트 벤치 모듈 클래스를 들여와서 사례화 한 후 시뮬레이터를 개시한다.
시뮬레이터는 아래 조건 중 하나를 충족할 때 중지된다.
- 지정된 시뮬레이션 시간에 도달했을 때
- 모든 채널(하드웨어 객체)에 사건이 발생하지 않을 때
- 사용자가 sc_stop() 을 호출 했을 때
4. 메이크(make) 유틸리티
Verilog 설계에 SystemC 테스트벤치를 씌운 시뮬레이터를 만들기까지 Verilator 변환도구가 동원되었고 최종적으로 GNU C++ 컴파일러를 사용하여 실행 파일을 만들어낸다. 일반적인 소프트웨어 개발용 표준 라이브러리에 더하여 특수한 라이브러리들을 동원하면 명령줄 옵션이 매우 복잡해진다. 개발 중 반복되는 컴파일 명령을 매번 명령줄에 입력하기도 어렵고 개발자의 피로도가 쌓여 실수를 낳게 되므로 스크립트를 활용 하는 것이 좋다.
그래픽 환경에서 통합된 개발도구의 활용이 늘고 있다. 통합 환경이 사용자 편의성을 높이긴 하지만 결국 스크립트의 실행이다. 필요할 때마다 외부 라이브러리를 추가하고 다양한 옵션을 자유롭게 활용 하려면 통합 환경의 사용법을 익혀야 하는데 결국 메이크 스크립트(Makefile)를 마주하게 된다. 메이크(make) 유틸리티는 명령줄에서 쓰이는 가장 강력한 스크립트 활용 방법이다. 긴 세월동안 수많은 개발자들에 의해 향상된 make 유틸리티의 내용은 매우 방대하지만 기본 사용법만 익혀도 활용도는 매우 높다.
[주] make의 사용법을 다룬 글들이 많지만 임대영(RAXIS)의 GNU Make강좌를 추천한다. https://doc.kldp.org/KoreanDoc/html/GNU-Make/GNU-Make.html
4-1. 목표와 의존 관계
명령줄에서 아무런 인수 없이 make 를 실행하면 현재 디렉토리에서 Makefile 을 기본으로 읽어 그 내용을 수행한다. Makefile 의 가장 단순한 사용법은 콜론(:) 을 사이에 둔 의존 관계(dependency)의 표현이다. 콜론의 왼편에 놓인 목표(target)를 얻기 위해 오른편에 놓인 파일들에 의존(dependent)한다는 의미다. 바로 이어 목표에 도달하는 방법을 기술 한다. 이때 들여쓰기(indent)는 반드시 공백없는 탭(tab) 문자가 선행되어야 한다. 의존성은 목표 파일의 존재 여부와 두 파일의 날짜와 시간을 살펴 정한다. 예를 들어 아래와 같은 간단한 의존 관계를 보자.
# Simple 'Makefile'
hello : hello.c
gcc -o hello hello.c
목표 파일 hello는 파일 hello.c 에 의존하는 관계에 있다. 목표인 hello 가 존재하지 않거나 hello.c 의 날짜가 목표보다 최신일 경우 바로 아래에 놓인 절차를 수행하라는 뜻이다. 위의 경우 hello.c 를 gcc 로 컴파일 하여 hello 를 만든다. 첫번째 칼럼에 # 은 주석이다. Makefile 내에 다수의 목표를 놓을 수 있다. 명령줄에서 make를 실행 할 때 명령줄 인수를 주어 여러 목표 중 하나를 선택 할 수 있다. 예를들어 명령줄에서 아래와 같이 인수를 주고 메이크 하였다.
$ make lint
Makefile 에 lint를 목표로 하는 절차가 있었다면 이를 찾아 수행한다. 다음은 간단한 Makefile의 예다.
의존 관계에서 콜론(:)의 왼편이 명령줄의 인수와 일치하는 목표를 찾아 이에 도달하는 절차를 수행한다. 위의 예에서 보듯이 목표 lint에 의존관계에 있는 파일들을 검사한다. 의존 관계에 있는 파일들의 목록은 변수 VERILOG_SRCS 에 지정 되었다. 목표에 도달하기 위한 절차는 VERILATOR 변수로 지정한 명령을 수행하는 것이다.
4-2. 내부 변수
Makefile 스크립트는 내부적으로 변수를 사용할 수 있다. 변수를 아래와 같이 선언해 주었다면 위의 수행 절차가 이해 될 것이다.
# Makefile variables:
VERILOG_SRCS = dff.v
SC_SRCS = sc_main.cpp
SC_TOP_H = sc_dff_TB.h
VERILATOR = verilator
TOP_MODULE = dff
TARGET = V$(TOP_MODULE)
TARGET_DIR = obj_dir
4-3. 다중 목표
위의 예에서 Makefile에 all, lint, run, wave, clean 등 5가지 명령줄 목표를 가지고 있다. 목표 all 은 make에 예약되었고 나머지는 사용자 지정 목표다.
a. all
목표 all 은 Makefile에서 예약되었다. 명령줄에서 인수없이 make 만 수행할 경우 자동으로 목표 all 을 찾아 수행한다. 위의 예에 따르면 목표 all 은 $(TARGET_DIR)/$(TARGET) 에 의존한다. 만들 방법이 제시되지 않고 있으므로 $(TARGET_DIR)/$(TARGET)가 목표인 의존성을 찾아 만들기(make)를 수행한다.
[주] Verilator 는 Verilog 를 SystemC/C++ 로 변환 한다. 옵션으로 --exe 와 --build 옵션을 주면 변환한 DUT와 테스트벤치 그리고 main() 이 포함된 C++ 원시 파일을 읽어 실행 파일(시뮬레이터)를 생성한다. 이때 GNU C++/clang++ 컴파일러를 사용한다.
b. lint
목표 lint 는 Verilator로 하여금 변환만 수행하도록 한다. Verilog의 무결성(문법적 오류는 물론 효과 없는 코드, 자료형 불일치 등)을 검사한다.
c. run
목표 run 은 Verilator와 C++ 컴파일러로 만들어진 실행 파일(시뮬레이터)를 실행한다. 리눅스 운영체제에서 실행되는 응용프로그램 이므로 실행 환경의 설정이 필요할 수 있다. DUT와 테스트벤치를 묶어 시뮬레이터를 빌드하는 과정에서 SystemC를 사용 하였으므로 크래스 헤더 파일을 들여오고 실행시 동적 라이브러리를 적재하기 위한 환경 변수를 설정해 주어야 한다.
export SYSTEMC = /opt/systemc
export SYSTEMC_HOME = $(SYSTEMC)
export SYSTEMC_INCLUDE = $(SYSTEMC_HOME)/include
export SYSTEMC_LIBDIR = $(SYSTEMC_HOME)/lib
export LD_LIBRARY_PATH :=$(LD_LIBRARY_PATH):$(SYSTEMC_LIBDIR)
SystemC를 설치하면 크래스 헤더와 라이브러리를 특정 위치에 놓는다. 따로 지정하지 않았다면 /opt/systemc 다. 시뮬레이터를 빌드하는 컴파일러에게 이 경로를 환경변수를 통해 알려준다. 아울러 SystemC의 시뮬레이션 엔진(simulation kernel)은 동적 라이브러리 libsystemc.so (shared object)로 설치된다. 컴파일된 시뮬레이터(바이너리 파일)를 실행 할 때 시뮬레이션 커널과 함께 실행 되어야 하므로 동적 라이브러리가 놓인 경로 /opt/systemc/lib 를 리눅스 배쉬 쉘(bash-shell)의 환경변수 LD_LIBRARY_PATH에 추가 시켜야 한다. export 는 Makefile의 외부변수 지정을 의미한다.
d. wave
목표 wave 는 시뮬레이션을 수행한 후 기록된 VCD 파형을 보기 위한 것이다. VCD 파형 보기 소프트웨어로 gtkwave를 사용하였다.
e. clean
목표 clean 은 현재 디렉토리를 정리한다. 빌드하는 과정에서 여러 중간 파일들이 생성된다. 개발 중 파일명이 변경 되기도 하고 임시 저장된 파일도 있다. 보관을 위해 디렉토리 청소가 필요할 때 보존되어야 할 파일들과 지워도 좋을 파일을 분명히 해줄 필요가 있다.
5. 하드웨어의 코딩 스타일
“내 칩 서비스” 표준 셀 디자인 킷(이하 디자인 킷)과 함께 제공된 예제의 시뮬레이션을 수행해 보면서 코딩 스타일이 설계 자동화 도구(HDL 시뮬레이터, 합성기 등)에 미치는 영향을 살펴 보기로 한다. ‘디자인 킷’은 깃-허브를 통해 내려 받을 수 있다.
https://github.com/GoodKook/ETRI-0.5um-CMOS-MPW-Std-Cell-DK
예제의 수행은 모두 오픈-소스 반도체 설계 도구를 활용한다. 오픈-소스 반도체 설계 도구와 디자인 킷의 설치 방법을 설명한 문서를 참조한다.
“ETRI 0.5um CMOS Std-Cell DK: 오픈-소스 반도체 설계 도구 설치 “[내려받기]
5-1. D-플립플롭 시뮬레이터의 빌드와 실행
레지스터 트랜스퍼 수준(RTL)에서 베릴로그로 기술한 D-플립플롭을 언어 변환 도구 Verilator를 사용하여 SystemC/C++ 모델로 변환하고 이를 SystemC 테스트벤치와 빌드(컴파일 및 링크)한다. 모두 C++ 이므로 GNU의 빌드 도구들(GNU GCC/G++)이 동원된다.
예제의 폴더로 이동 후 Verilog RTL로 기술한 D-플립플롭 과 SystemC 테스트벤치를 묶어 시뮬레이터를 빌드한다. 먼저 예제 디렉토리로 이동하여 베릴로그 DUT와 SystemC 테스트 벤치를 살펴본다.
$ cd ~/ETRI050_DesignKit/Tutorials/2-3_Lab1_dff/dff
시험할 대상(DUT, Desugn Under Test)은 RTL에서 베릴로그로 기술된 모듈 dff 다.
/**************************************
Associated Filename: dff.v
Purpose: D-FlipFlop
***************************************/
module dff(clk, d, q);
input clk, d;
output q;
reg q;
always @(posedge clk) // edge trigger
begin
q <= d;
end
endmodule
SystemC 테스트벤치는 다음과 같다. 시험할 DUT가 RTL 이므로 테스트를 수행에 필요한 시험입력 또한 이에 준하는 추상화 수준에서 작성 되어야 한다.
/**************************************************************
Associated Filename: sc_dff_TB.h
Purpose: Testbench
***************************************************************/
#ifndef _SC_DFF_TB_H_
#define _SC_DFF_TB_H_
#include <systemc.h>
#include "Vdff.h" // Verilated DUT
SC_MODULE(sc_dff_TB)
{
sc_clock clk;
sc_signal<bool> d, q;
Vdff* u_Vdff;
sc_trace_file* fp; // VCD file
SC_CTOR(sc_dff_TB): // constructor
clk("clk", 100, SC_NS, 0.5, 0.0, SC_NS, false)
{
// instantiate DUT
u_Vdff = new Vdff("u_Vdff");
// Binding
u_Vdff->clk(clk);
u_Vdff->d(d);
u_Vdff->q(q);
SC_THREAD(test_generator);
sensitive << clk;
// VCD Trace
fp = sc_create_vcd_trace_file("sc_dff_TB");
sc_trace(fp, clk, "clk");
sc_trace(fp, d, "d");
sc_trace(fp, q, "q");
}
void test_generator()
{
int test_count =0;
d.write(0);
while(true)
{
wait(clk.posedge_event());
d = 1;
wait(clk.posedge_event());
d = false;
wait(clk.negedge_event());
d = 1;
wait(clk.posedge_event());
d = 0;
wait(clk.posedge_event());
d = 1;
wait(clk.posedge_event());
wait(clk.posedge_event());
sc_close_vcd_trace_file(fp);
sc_stop();
}
}
};
#endif
SystemC는 높은 시스템 수준에서 낮은 RTL 까지 폭넓은 추상화 수준을 지원한다. SystemC 모듈은 먼저 두개의 헤더 파일을 들여오고(include) 있는데, “systemc.h”는 SystemC의 하드웨어 묘사용 크래스들을 사용하기 위한 것이며, “Vdff.h”는 베릴로그에서 C++ 로 변환된 모델을 기술한 크래스 정의다. 위의 SystemC 테스트벤치는 가장 기본적인 구성을 갖추고 있다. DUT를 사례화 하고 지역 신호에 연결 하며 테스트 입력을 생성한다. 이번 예제는 워낙 단순한 DUT라 출력 검사 부분은 생략 되었다.
예제 디렉토리에 준비되어 있는 Makefile을 가지고 시뮬레이터를 빌드한다.
$ make build
verilator --sc -Wall --trace --top-module dff --exe --build \
-CFLAGS -std=c++17 \
dff.v sc_main.cpp
............
- V e r i l a t i o n R e p o r t: Verilator 5.027 ..…
- Verilator: Built from 0.011 MB sources in 2 modules, ..…
- Verilator: Walltime 0.770 s (elab=0.004, cvt=0.008, bld=0.744)...
오류 없이 시뮬레이터(실행 파일)가 만들어 졌으면 실행시켜 보자.
$ make run
./obj_dir/Vdff
SystemC 3.0.0-Accellera --- Jun 18 2024 08:49:55
Copyright (c) 1996-2024 by all Contributors,
ALL RIGHTS RESERVED
Info: (I702) default timescale unit used for tracing: 1 ps (sc_dff_TB.vcd)
Info: /OSCI/SystemC: Simulation stopped by user.
VCD 로 기록된 시뮬레이션의 결과를 살펴보자.
$ make wave
DUT가 단순하고 시뮬레이션도 아주 짧다. D 플립플롭의 RTL 묘사는 클럭 엣지의 사건에 감응되어 입력 d 를 출력 q 로 전송한다. 입력의 이전 값을 취하고 있는 점에 주목하며 시뮬레이션 결과 파형을 살펴보자.
(1) 입력 d 와 clk 가 동시에 사건이 발생했다. 클럭의 상승 엣지 사건 (posedgge clk)에 구동된 순차 할당문은 입력 d 의 할당 이전 값을 취한다.
(2) clk 에 하강 엣지 사건이 발생 했지만 이에 감응된 프로세스는 없다. D 플립플롭의 행동을 묘사한 always @() 는 상승엣지에서만 반응한다.
(3) 이번에도 clk의 상승 엣지 사건와 입력 d의 할당이 동시에 일어났다. 출력 q 를 앞서 (1)의 경우와 비교해 보면 동일하게 d 의 이전의 값을 취하고 있다.
(4) 출력 q 는 clk의 하강 엣지 사건에 영향을 받지 않고 이전 값을 유지한다.
(5) 앞서 (3)의 경우와 같다.
(6) 앞서 (1)의 경우와 같다. clk의 상승 엣지 사건에 감응하여 실행된 할당문은 d 의 이전 값을 취한다.
(7) 앞서 (3), (5)의 경우와 같다. 입력 d 의 이전 값을 취하여 출력 q에 전송되었다.
5-2. 레지스터 전송 수준
흔히 RTL(레지스터 전송 수준)이라고 하는 추상화 수준은 디지털 하드웨어에 근접하여 "클럭 동기"에 맞춰 작동하는 디지털 회로를 묘사 했다는 의미가 포함되어 있다. 한 클럭으로 연동된 두 플립플롭 사이에 조합 회로들이 놓여 있다.
플립플롭 U0의 출력 Q0은 클럭의 상승 엣지에서 시작된다. 이 출력 값은 조합회로를 통과하며 지연으로 인한 불안정을 보이기 시작한다. 플립플롭 U1은 클럭의 다음번 상승 엣지에서 조합회로를 통과하며 지연된 값을 취한다. RTL이란 연속적인 클럭의 엣지를 사이에 두고 플립플롭 사이에 이뤄지는 신호의 전송을 표현한 것이다.
5-3. 컴퓨팅 언어에 의한 하드웨어의 묘사
하드웨어 D-플립플롭을 Verilog로 RTL에서 기술하고 C++ 로 작성한 테스트벤치와 함께 시뮬레이터를 만들고 실행해 동작을 파형으로 관찰하였다. 그 결과 컴퓨팅 언어로 전자회로의 동작을 모의 할 수 있음을 알게 되었다. 하지만 묘사하려는 대상이 하드웨어의 행동이라는 점을 염두에 두어야 한다.
하드웨어를 언어적으로 표현하기 위해 사용하는 객체는 소프트웨어 언어의 변수와는 사뭇 다르다. 하드웨어 언어에서도 객체를 심볼로 표현하지만 행동의 묘사방법(코딩 스타일)에 따라 단순한 전선(또는 조합회로)이 될 수도 있고 저장소(또는 순차회로)가 되기도 한다. 하드웨어를 표현한 문장은 전선 혹은 저장소 모두 병렬 실행이 원칙이다. 다음은 베릴로그의 순차구문 구역 내에 연속적인 할당을 보여주는 예다.
입력 d가 q1을 거쳐 연속적으로 q 까지 할당 되었다. 이때 d 와 q1의 연속적인 할당은 하나의 전선으로 합쳐진다. 만일 연속 할당 구문의 순서를 바꿀 경우 다음과 같은 결과를 낳는다.
할당 문의 순서가 역전되므로 인하여 저장소가 늘어났다. 이는 always 구역이 소프트웨어 처럼 순차 실행을 하고 있기 때문이다. 위의 예에서 두가지 할당 연산자가 사용되었는데, '='은 즉시 할당, <= 은 지연 할당이라 한다. 즉시 할당은 저장이 없는 조합회로(전선)를 표현할 때, 지연할당 연산자는 저장소(플립플롭)를 묘사할 때 적용된다. 하지만 순차구문 구역 내에서 할당 구문의 순서에 따라 즉시 할당문도 저장소를 만들 수 있다는 점에 주의해야한다. 할당 구문 순서에 상관없이 모두 연속적인 플립플롭을 묘사하고자 한다면 모두 지연 할당 연산자를 적용한다.
5-4. 하드웨어 행위 묘사의 코딩 스타일
컴퓨팅 언어로 알고리즘 또는 하드웨어를 묘사 할 때 대상에 따라 주의가 필요하다. 하드웨어를 대상으로 할 경우 이에 제시되는 언어의 문장구성과 문장의 실행방식에 주의해야 한다. 문법 오류없이 기술 되었다고 해서 의미를 제대로 담았다고 보장 할 수는 없다. 의미를 명확히 해주기 위한 지침이 필요한데 이를 코딩 스타일(coding style)이라 한다. 앞서 할당 연산자의 사용 만으로도 다른 의미가 될 수 있다는 점을 알게 되었다. 하드웨어 언어의 또 다른 주의점으로 문장의 실행이다. 하드웨어는 모든 문장이 병렬 실행을 원칙으로 하며 always 구역은 그 자체로 한개의 병렬 구문(여러개 순차 구문을 담은 절)과 같다. 언어로서 Verilog의 시뮬레이션 동작은 사건 감응과 구동이라는 원칙하에 일관성을 가지고 설명될 수 있다. 감응 목록에 넣고 빼기에 의해 할당문의 실행 결과가 다를 수 있음을 보여준다[4]. 코딩 스타일은 컴퓨팅 언어로서 문법적 규칙과 문장의 실행 방식(병렬실행과 순차실행) 외에 합성(synthesis)을 위한 작성 양식이다. 사건구동과 감응 목록은 하드웨어의 행동을 컴퓨팅 언어로 기술하기 위한 소프트웨어적 기법이며 실제 디지털 회로를 정확히 반영하지 않는다는 점을 염두에 두어야 한다. 심지어 합성기 마다 다른 회로를 생성해서 시뮬레이션과 일치하지 않을 수 있다. 이런 이유로 EDA 업계를 중심으로 합성용 RTL 코딩 스타일의 표준이 제정되었다[5].
순차구문 영역 always의 감응 리스트에 posedge clk 와 d 도 함께 주어 졌다면 문제가 발생한다. 할당문은 지정된 감응에 충실히 반응하여 아래와 같은 결과를 보여줄 것이다. 이는 우리가 원하던 플립플롭의 동작이 아니다.
감응목록에 따라 다른 결과를 보여주기도 하지만 시뮬레이션의 실행 속도에 영향을 주기도 한다. 아래의 경우 D-플립플롭의 행동을 적절하게 기술하고 있다. 하지만 always 구역이 clk의 상승과 하강 엣지 사건에 모두 반응한다. 하강 엣지에서 불필요하게 always 구역을 수행하므로써 시뮬레이터의 부담을 증가시킨다.
아래의 예는 마치 레벨 트리거 래치(level trigger latch)처럼 보인다. 감응신호 clk와 d 의 모든 사건에 always 가 반응 하지만 할당은 if 문의 조건을 따른다.
비동기 셋과 리셋을 가진 D 플립플롭을 기술하면 아래와 같다. clk의 상승 엣지 사건과 무관하게 q 가 셋 또는 리셋되고 있다. 플립플롭 동작은 clk의 상승 엣지 사건에 의해 작동한다.
감응 목록에서 r 과 s 를 빼면 모두 clk에 동기 시킬 수 있다. 클럭 동기 셋과 리셋을 가진 플립플롭은 권장하지 않는다.
동기 셋과 리셋을 가진 플립플롭을 표준셀로 가진 경우는 흔치 않다. 설계에 동기가 필요할 경우 대부분 입력 d 에 멀티 플렉서를 달아 구현한다.
<그림>
컴퓨팅 언어는 문법과 문장의 실행 방식을 정하고 있다. 문장이 실행된 후 그 결과의 일관성은 매우 중요하다. 특히 병렬 실행문과 순차실행문을 모두 수용하는 하드웨어 기술 언어의 경우 코딩 스타일에 따라 다른 결과를 가져올 수 있지만 실행방식의 규정으로 설명 될 수 있어야 한다.
감응 목록에 지정된 다수의 신호가 동시에 시건을 일으킨 경우를 상정해 보자. 각 사건에 대응하여 순차구역이 실행된다. 시뮬레이션 델타 시간 내에서 사건의 순서에 무관한 결과를 얻을 수 있다. 순차구문 if~else 의 논리에 따라 우선순위가 결정(priority encoder)되어 있을 뿐 시뮬레이션은 일관성을 유지한다.
clk의 상승 엣지와 r 과 s의 하강 엣지 사건이 동시에 일어 났다. always 구역은 각 사건에 대해 모두 반응할 것이다. 시뮬레이터가 어느 사건을 먼저 처리하든 그 결과는 동일해야 한다. 사건 구동 방식에서 할당과 사건의 수집 그리고 콜-백 그리고 갱신의 과정을 시뮬레이션 델타라한다.
감응 목록이 아래와 같은 경우 시뮬레이션 결과를 설명해 보자. 실제 디지털 회로가 되기 어렵지만 사건구동 시뮬레이션으로 일관성 있는 설명을 해보기 바란다.
5-5. 하드웨어 시뮬레이터의 작동방식
하드웨어 언어는 디지털 회로를 묘사하기 위해 특별한 저장소를 운영하며 사건 구동 기법으로 병렬 실행을 구현한다. 병렬 시뮬레이션 커널의 작동을 간략히 살펴보면 다음과 같다. SystemC가 가지고 있는 병렬 시뮬레이션 커널을 기반으로 설명 한다.
의 표현
의 연결
I. 시뮬레이션 델타 개시: 시뮬레이션 시간 멈추고 사건 인지 처리
(1) 하드웨어 작동요소들 사이의 연결은 채널로,
(1)-1. 채널은 2중의 저장소를 가짐(new_val, curr_val)
(1)-2. 채널로 접근은 읽기 .read()와 쓰기 .write() 방법을 통함
(1)-3. 최초 채널쓰기시 값은 new_val에 저장
(2) 임의 프로세스에서 채널에 쓰기 접근, new_val에 새값 저장
(3) new_val과 curr_val 값이 다를 경우 사건 인지 후 커널에 고지
(4) 해당 채널 사건에 구동되도록 지정된 프로세스가 있을 경우 new_val을 curr_val로 갱신
(5) 사건구동이 지정된 프로세스 호출
(6) 호출된 프로세스에서 채널 읽기 접근 및 새로운 쓰기
II. 시뮬레이션 델타 종료
III. 새로운 델타 진행 (I->II 반복)
IV. 사건에 구동될 프로세스들이 완료된 후 모든 채널들 갱신
V. 모든 사건이 처리된 후 시뮬레이션 시간 진행
* 재귀적 사건의 경우 횟수 제한
* VCD는 curr_val로 기록됨
예) clk의 상승 엣지 사건에 감응된 프로세스에 대하여 입력 d 와 clk가 동시에 사건이 발생한 경우,
1) clk 채널과 d 채널에 각각 new_val 저장
2) 두 채널 모두 커널에 사건 공지
3) clk의 사전에 만 구동될 프로세스 존재
4) clk 채널 만 갱신: clk.new_val -> clk.curr_val
5) clk 사건에 구동될 프로세스(콜-백)호출
6) 프로세스 진입 후 d 채널 읽기 (갱신되기 이전이므로 d.curr_val)
사건 처리완료 후 d 채널 갱신
아래 파형은 트랜지스터 회로로 설계된 D-플립플롭을 SPICE 회로 시뮬레이션을 하여 얻은 것이다.
---------------
사건을 처리하는 순서에 관한 일관성 문제
clk 채널에 앞서 d 채널을 먼저 처리했다면?
- elaboration 시 콜-백 프로세스 독립
- 사건 감응 지정된 채널을 프로세스의 지역화
always @(d, a)
begin
y <= d & a;
end
always @(posedge clk)
begin
q <= d;
end
2.
5-3. 논리 연산자를 사용하여 D-플립플롭 기술
https://devraphy.tistory.com/297
https://verilog-hdl-design.tistory.com/entry/D-latch-gate-level
6. 맺음말
Verilog로 설계하고 SystemC로 검증 하는 설계 방법론을 간단한 D-플립플롭의 예를 들어 소개했다. 트랜지스터의 회로에 비하여 추상화 수준을 비교해 볼 수 있을 것이다. 설계자의 안목 또한 중요한 요소가 된다. 반도체 설계에 컴퓨팅 언어를 사용하므로써 얻는 장점이 많다. 알고리즘 개발자도 쉽게 시작할 수 있다. 하지만 최종 목표가 전자회로라는 점을 인식하고 있어야 한다. 컴퓨팅 언어(Verilog 도 컴퓨팅 언어다.)로는 고도의(혹은 기이한 트릭) 행위의 표현이 가능 하지만 실제로 트랜지스터 회로는 제한이 있다.
시뮬레이션 소프트웨어에서 병렬성을 구현하는 방법을 간략히 살펴봤다. 코딩 스타일에 따라 시뮬레이션의 결과가 달라질 수 있는 이유를 설명 할 수 있을 것이다. 시뮬레이션 소프트웨어가 병렬성을 일관성있게 처리하는 방식을 알면 시스템 모델링에 큰 도움이 된다.
----------------------------------------------------------------
참고:
[1] SystemC Quick Reference card, http://www.eis.cs.tu-bs.de/klingauf/systemc/systemc_quickreference.pdf
[2] Verilog Quick Reference, https://web.stanford.edu/class/ee183/handouts_win2003/VerilogQuickRef.pdf
[3] C++ Quick Reference, https://www.hoomanb.com/cs/quickref/CppQuickRef.pdf
[4] RTL Coding Styles That Yield Simulation and Synthesis Mismatches, http://www.sunburst-design.com/papers/CummingsSNUG1999SJ_SynthMismatch.pdf
[5] IEEE 1364.1-2002 - IEEE Standard for Verilog Register Transfer Level Synthesis, https://ieeexplore.ieee.org/document/1146718
[6] IEEE Standard for Verilog Hardware Description Language, https://www.eg.bucknell.edu/~csci320/2016-fall/wp-content/uploads/2015/08/verilog-std-1364-2005.pdf
[7] "VLSI 레이아웃 설계 기초" [8] Std-Cell 제작 실습: DFF-SR, https://fun-teaching-goodkook.blogspot.com/2024/07/vlsi-8-std-cell-dff-sr.html
[8] GNU Make강좌, https://doc.kldp.org/KoreanDoc/html/GNU-Make/GNU-Make.html
[9] FORTE Design, Learn SystemC, https://www.youtube.com/playlist?list=PLcvQHr8v8MQLj9tCYyOw44X1PLisEsX-J