Verilog에서도 $random으로 난수 생성을 지원하지만, SystemVerilog의 randomization은 더 많은 기능을 제공한다.
1. 테스트벤치 구조
테스트벤치의 주요 구성 요소는 다음과 같다.
1) 제너레이터(Generator): 트랜잭션을 생성하는 역할 2) 드라이버(Driver): 생성된 트랜잭션을 인터페이스를 통해 DUT로 전달하는 역할 3) 모니터(Monitor): DUT의 신호를 감시하고 트랜잭션을 생성하는 역할 4) 스코어보드(Scoreboard): 기대값과 실제 결과를 비교하여 검증하는 역할
5) 인터페이스(Interface): SW(test)와 HW(DUT) 신호 연결 역할 스티뮬러스(Stimulus): 트랜잭션을 생성하여 DUT(Device Under Test)에 전달하는 역할 트랜잭션(Transaction): 데이터 전송을 추상화한 객체
2. 테스트 시나리오
테스트벤치를 활용하여 여러 개의 랜덤 트랜잭션을 생성하고, 이를 DUT로 전달한 후 검증을 수행하게 된다.
본 게시글에서는 8Bit Adder의 Testbench를 SystemVerilog로 구현하는 실습을 진행한다.
Interface, Transaction, Generator, Driver, Environment를 선언하는 부분이다. 이 부분은 C언어로 생각해봤을 때, Interface, Transaction은 구조체 선언, Generator, Driver, Environment는 내부에 함수를 갖고 있는 구조체를 선언한다 생각하면 된다.
SystemVerilog에서 클래스 객체를 다룰 때 두 단계로 나뉘게 된다.
transaction tr; 이 단계에서는 tr이라는 핸들(handle)만 선언된다. 이 시점에서는 실제 객체를 가리키지 않으며, null 상태이다.
tr = new(); 이 단계에서 실제 transaction 클래스의 객체가 메모리에 생성된다.. new() 함수를 호출하여 객체를 인스턴스화하고, 그 참조를 tr 핸들에 할당한다.
인터페이스 선언 시 virtual 키워드의 차이:
virtual 없는 경우: 일반적인 인터페이스 인스턴스를 선언하며, 모듈이나 인터페이스 내부에서 직접 사용된다. virtual 있는 경우: 인터페이스에 대한 핸들(또는 포인터)을 선언하며, 클래스 내부에서 인터페이스를 참조할 때 사용된다.
fork, join
fork와 join은 SystemVerilog에서 병렬 실행을 구현하기 위해 사용된다. 기본적으로 SystemVerilog는 순차적 실행 방식이지만, 때때로 여러 작업을 동시에 처리해야 하는 경우가 발생하는데, 이를 해결하기 위해 병렬 처리를 구현할 수 있는 방법으로 fork와 join이 제공된다.
mailbox #(transaction) gen2drv_mbox
mailbox를 사용한 신호 전달 방식은 IPC(Inter-Process Communication, 프로세스 간 통신)라고한다. generator에서 driver로 직접 신호를 보내지 않고 mailbox를 사용하는 이유는 다음과 같다.
generator는 보통 비동기적으로 신호를 생성하는 반면, driver는 시뮬레이션 시간에 맞춰 신호를 전달하게 된다. mailbox는 이러한 비동기적 신호 전송을 동기화할 수 있는 방법을 위해 SystemVerilog에서 제공하는 자료구조이다. driver는 mailbox에서 신호를 읽을 때까지 기다리거나, 시뮬레이션 시간에 맞춰 적절하게 동작할 수 있다.
1) mailbox: SystemVerilog에서 mailbox는 프로세스 간에 데이터를 안전하게 전달할 수 있는 큐(queue)이다. 2) #(transaction): mailbox에 전달될 데이터 타입을 지정하는 부분이다. 여기선 사용자 정의 타입인 transaction이 사용됨. 3) gen2drv_mbox: mailbox의 인스턴스 이름. gen2drv_mbox는 generator에서 driver로 신호를 전달하는데 사용될 mailbox 객체이다.
6. Testing and Debugging
* Testing Tools: VIVADO, Modelsim
module tb_adder(
);
adder_intf adder_interface();
generator gen;
adder DUT(
.a(adder_interface.a),
.b(adder_interface.b),
.sum(adder_interface.sum),
.carry(adder_interface.carry)
);
initial begin
gen = new(adder_interface);
gen.run(100) ;
end
endmodule