네이버 이웃 추가 / GitHub Profile / 카카오톡 채널 추가 / 방명록 / 이용 안내

Intel Quartus에서 Verilog testbench 사용하기

수성컴 | 2023. 08. 17.
Intel Quartus에서 Verilog testbench 사용하기

수성컴전자방입니다. 오늘은 Intel Quartus에서 Verilog testbench를 사용하여 simulation을 해보겠습니다. testbench는 다른 HDL module을 시험하기 위한 HDL 코드이며, 합성 불가능합니다. 오늘은 예제 코드 위주로 설명 드리겠습니다.

  • IDE: Intel Quartus 18.1
  • Device: Cyclone V 5CSXFC6D6F31C6(밑에서 6번째)
  • Simulation: ModelSim-Altera, Verilog HDL
  • Project Name & the name of Top-level: sillyfunction

목차

1. 예제 코드(sillyfunction.v)
2. Simple Testbench(testbench1.v)와 Intel Quartus 연결
3. Self-checking Testbench(testbench2.v)

4. Testbench with Testvectors
4.1. testbench3.v
4.2. simulation\modelsim\testvector.tv
4.3. RTL Simulation

5. 오늘은 여기까지!
6. 참고 문헌

목차를 간략하게 설명해 드리겠습니다. 1번 문단의 sillyfunction.v 코드는 이 글에서 전체적으로 사용합니다. Intel Quartus에서 testbench를 추가하는 방법과 Simulation을 실행하는 방법은 2번 문단에서 다루며, 3~4번 문단에서 반복 설명하지 않으니 참고하시기 바랍니다.
Project 생성과 새 파일 추가 방법이 궁금하신 분은 이 글을 읽기에 앞서 Verilog 기초적인 내용과 Gate Design을 먼저 읽으시기 바랍니다.

1. 예제 코드(sillyfunction.v)

module sillyfunction(y, a, b);
	input a, b;
	output y;
	
	assign y=a&~b|~a&b;
endmodule

module 이름은 대충 sillyfunction으로 지었습니다. 이 코드는 \(y=a \bar b+ \bar a b\)를 구현하는 코드입니다. 즉, \(y=a \oplus b\), XOR Gate입니다. 그냥 built-in module을 쓰는 것이 더 빠르겠지만 오늘만큼은 풀어서 5행에 assign y=a&~b|~a&b;이라고 작성했습니다.

rtl-view
참고로 합성 후 RTL View는 위와 같이 나옵니다.

2. Simple Testbench(testbench1.v)와 Intel Quartus 연결

2.1. 시뮬레이션 할 회로의 코드(1번 문단)를 작성한 후 새 파일(Ctrl+NVerilog HDL File) testbench1.v를 만들어 아래의 예제 코드를 작성합니다.

`timescale 1ns/100ps

module testbench1();
	reg a, b;
	wire y;
	
	sillyfunction dut(y, a, b);
	
	initial begin
		a=0; b=0; #10;
		b=1; #10;
		a=1; b=0; #10;
		b=1; #10;
	end
endmodule

[Line 1]
`timescale unit/precision
timescale 왼쪽에 쓰는 기호가 작은 따옴표(‘)가 아니라 backtick(`)이라는 점에 주의하시기 바랍니다.
unit은 단위입니다.
precision은 얼마나 정밀하게 simulation 할 것인지를 정하는 부분입니다.

예제에서는 `timescale 1ns/100ps이라고 썼으므로 unit이 1ns이고, precision이 100ps입니다.
unit이 1ns라는 것은 #1==1ns라는 뜻입니다.
precision은 그림 자료를 이용해서 설명하겠습니다.

precision 설명 그래프
오늘은 combinational circuit으로 simulation 하기 때문에 체감이 잘 안 되시겠지만, sequential circuit을 만들 경우 delay가 발생하게 됩니다. 위의 그림에서 sb는 precision 설명을 위해 임의로 도입한 신호입니다(예제 코드와 관련 없음). sb의 delay가 0.123ns라서 0.1ns와 0.2ns 사이에 신호가 바뀌었습니다. 그러나 precision이 100ps=0.1ns이기 때문에 simulation에서는 0.2ns에서 신호가 바뀐 것으로 나옵니다.

[Line 3]
module testbench1();
여기서 testbench1은 testbench module의 이름입니다.
참고로 ()는 생략할 수 있습니다.

[Line 4]
reg a, b;
sillyfunction의 input들을 testbench 코드에서는 reg로 선언합니다.

[Line 5]
wire y;
sillyfunction의 output들을 testbench 코드에서는 wire로 선언합니다.

[Line 7]
sillyfunction dut(y, a, b);
sillyfunction module의 instance를 하나 만듭니다. instance의 이름은 “dut”로 하겠습니다.

[Line 9~14]
initial문을 사용합니다. initial block 안에 있는 내용은 각각 한 번씩만 순서대로 실행됩니다. 중간 중간 있는 #10은 delay time이며, 10의 시간동안 delay된다는 뜻입니다. 1행에서 unit을 1ns로 정했기 때문에 10ns동안 delay됩니다.

Files에 testbench1.v와 sillyfunction.v
2.2. Intel Quartus에서 왼쪽 위 Project Navigator를 Files로 설정하면 sillyfunction.v와 testbench1.v가 Project에 잘 들어있는 것을 보실 수 있습니다.

Assignments-Settings
2.3. AssignmentsSettings로 들어갑니다.

Settings-Simulation
2.4. Simulation으로 들어갑니다.
2.5. NativeLink settings의 라디오 버튼 중에서 Compile test bench를 선택합니다.
2.6. Test Benches..를 클릭합니다.

TestBenches
2.7. New…를 클릭합니다.

NewTestBenchSettings
2.8. Test bench name과 Top level module in test bench를 설정합니다. 예제 코드대로라면 Top level module in test bench는 testbench1입니다. Test bench name은 Top level module in test bench와 다르게 해도 되긴 합니다.
2.9. 을 클릭합니다.

SelectFile
2.10. testbench1.v를 선택합니다.
주의) testbench1.v.bak을 선택하면 안 됨!

돌아온NewTestBenchSettings
2.11. Add를 클릭합니다.

다한NewTestBenchSettings
2.12. OK를 클릭합니다.

돌아온TestBenches
2.13. OK를 클릭합니다.

testbench-pull-down-menu
2.14. OK를 클릭합니다.
위의 스크린샷에서 파란색으로 표시한 부분은 testbench를 선택할 수 있는 목록입니다. 여러분은 아직 testbench1밖에 없을 것입니다. 그러나 3~4번 문단을 진행하다 보면 새로운 testbench를 계속해서 만들게 될 것입니다. 이때 새로운 프로젝트를 생성하지 않고 하나의 프로젝트에 모든 testbench 파일들을 넣은 뒤 모두 다 testbench로 추가하신 다음, testbench를 선택하시면 됩니다.

compile
2.15. 컴파일합니다.(Ctrl+L)

Tools-Run
2.16. ToolsRun Simulation ToolRTL Simulation을 클릭합니다.
RTL(Register Transfer Level) Simulation은 register의 개수와 위치는 고정되지만 register 사이의 combinational logic은 tool에 의해 수정될 수 있습니다. RTL Simulation이 Gate Level Simulation보다 빠릅니다.

RTLsimulation
2.17. 그러면 이렇게 ModelSim 창이 뜹니다.

RTLsimulation축소
2.18. Ctrl+휠아래로 해서 축소하면 waveform을 한 눈에 보실 수 있습니다. 휠을 사용하는 것이 불편하다면 위의 스크린샷에 파란색 네모로 표시한 돋보기 단추를 이용하셔도 됩니다.

RTLsimulation닫기
2.19. Simulation을 끝내려면 창 닫기 단추(X)를 누릅니다.
2.20. 를 클릭하면 창이 닫힙니다.

3. Self-checking Testbench(testbench2.v)

Self-checking Testbench는 testbench 코드의 if문을 이용하여 원하는 결괏값이 안 나오면 오류 메시지를 띄우는 방식입니다.

`timescale 1ns/100ps

module testbench2();
	reg a, b;
	wire y;
	
	sillyfunction dut(y, a, b);
	
	initial begin
		a=0; b=0; #10;
		if(y!==0) $display("00 failed.");
		
		b=1; #10;
		if(y!==1) $display("01 failed.");
		
		a=1; b=0; #10;
		if(y!==1) $display("10 failed.");
		
		b=1; #10;
		if(y!==0) $display("11 failed.");
	end
endmodule

!==라는 연산자가 어색하신 분들이 있을 것 같은데, 이는 4번 문단에서 설명하겠습니다.

$display(“출력할 내용”);
$display문을 사용하여 터미널에 문자열을 출력할 수 있습니다.

하단transcript강조
만약 원하는 결괏값이 안 나오면 ModelSim 하단 Transcript에 “?? failed.”와 같이 메시지가 뜨는 것입니다.
다만, 예제에는 오류가 없어서 아무 문구도 뜨지 않을 것입니다.

4. Testbench with Testvectors

Testbench with Testvectors는 별도의 testvector 파일에 원하는 결괏값(기댓값)들을 넣어 두고 simulation할 때 testvector 파일에 있는 값과 동일한 결과가 나오는지 확인하는 방식입니다.

4.1. testbench3.v

`timescale 1ns/100ps

module testbench3();
	reg clk, reset;
	reg a, b, y_expected;
	wire y;
	reg [31:0] vectornum, errors;	//bookkeeping variables
	reg [31:0] testvectors[10000:0];	//array of testvectors
	
	//instantiate device under test
	sillyfunction dut(y, a, b);
	
	//generate clock
	always
		begin
			clk=1; #5; clk=0; #5;
		end
	
	//at start of test, load vectors and pulse reset
	initial
		begin
			$readmemb("./testvector.tv", testvectors);	//Put testvector file at simulation\modelsim
			vectornum=0; errors=0;
			reset=1; #27; reset=0;
		end
		
	//apply test vectors on rising edge of clk
	always @(posedge clk)
		begin
			#1; {a, b, y_expected}=testvectors[vectornum];
		end
		
	//check results on falling edge of clk
	always @(negedge clk)
		if(~reset) begin //skip during reset==1
			if(y!==y_expected) begin
				$display("Error: inputs=%b", {a, b});
				$display("	outputs=%b (%b expected)", y, y_expected);
				errors=errors+1;
			end
			
	//increment array index and read next testvector
			vectornum=vectornum+1;
			if(testvectors[vectornum]===32'bx) begin
				$display("%d tests completed with %d errors.", vectornum, errors);
				$finish;
			end
		end
endmodule

[Line 4]
reg clk, reset;
clk는 clock(클럭)입니다.
reset==0일 때 simulation이 진행됩니다.

[Line 5]
reg a, b, y_expected;
sillyfunction에서 input인 a, b와 output y의 기댓값인 y_expected를 reg로 선언합니다.

[Line 6]
wire y;
sillyfunction에서 output인 y를 wire로 선언합니다.

[Line 7]

reg [31:0] vectornum, errors;	//bookkeeping variables

32-bit reg vectornum과 errors를 선언합니다. 8 bits=1 Byte이므로 32 bits=4 Bytes네요. vectornum과 errors는 4바이트짜리 reg가 됩니다.

[Line 8]

reg [31:0] testvectors[10000:0];	//array of testvectors

이번에는 길이가 10001인 32-bit reg 배열 testvectors를 선언합니다.
왼쪽의 [31:0]은 32-bit reg라는 뜻입니다.
오른쪽의 [10000:0]은 길이가 10001인 배열이라는 뜻입니다.

[Line 13~17]
always문은 특정 조건동안 항상 반복하는 구문입니다. 그러나 이 testbench 코드에서는 조건을 지정하지 않았으므로 항상 반복합니다. 반복 내용을 보면 clk=1; #5; clk=0; #5;라고 되어 있습니다.

clk-graph
따라서 위와 같이 작동합니다.

[Line 19~25]
test가 시작되면 testvector를 불러오고 reset=0;으로 합니다. 이때 initial문을 사용합니다.

[Line 22]

$readmemb("./testvector.tv", testvectors);	//Put testvector file at simulation\modelsim

$readmemb는 파일의 내용을 binary(2진수)로 불러오는 함수입니다. 만약 hexadecimal(16진수)로 불러오고 싶다면 readmemh를 사용합니다.
함수의 1번째 인수에 ”./testvector.tv”라고 적혀 있지만, Intel Quartus에서 이렇게 작성하면 simulation\modelsim\testvector.tv를 불러옵니다. 만약 testvector.tv 파일을 testbench3.v와 동일한 경로에 두고 싶다면 “../../testvector.tv”라고 쓰시면 됩니다.
함수의 2번째 인수 testvectors는 8행에서 선언한 배열입니다. testvector.tv의 i번째 줄이 testvector[i]에 들어간다고 생각하시면 됩니다.

[Line 23]
vectornum=0; errors=0;
vectornum과 errors를 초기화합니다.

[Line 24]
reset=1; #27; reset=0;
처음에는 reset을 1로 했다가 27ns 후 reset을 0으로 바꿉니다.
따라서 simulation 실행 27ns 후에 본격적인 simulation이 시작됩니다.

[Line 27~31]
clk의 posedge(rising edge)마다 실행할 내용입니다. testvector를 reg로 가져옵니다.

[Line 30]
#1; {a, b, y_expected}=testvectors[vectornum];
concatenation을 이용한 구문입니다.
예를 들어 testvectors[vectornum]이 101이면 a=1, b=0, y_expected=1이 되는 식입니다.

[Line 33~48]
clk의 negedge(falling edge)마다 실행할 내용입니다. 결과가 맞게 나왔는지 확인합니다.

[Line 35]

if(~reset) begin //skip during reset==1

reset==0일 때에만 작동하도록 합니다.
reset==0 ⇔ ~reset==1

[Line 35~40]
결과가 기대한 값과 다르게 나오면(y!==y_expected이면) 오류 메시지를 띄우고 errors reg를 1 증가시킵니다.
예를 들어 a=1, b=0, y_expected=1인데 y=0이면 아래와 같은 메시지를 띄웁니다.
Error: inputs=10
outputs=0 (1 expected)

[Line 43]
vectornum=vectornum+1;
testvector 한 줄 한 줄 테스트할 때마다 vectornum을 1씩 증가시킵니다.

[Line 44]
if(testvectors[vectornum]===32’bx) begin
testvectors[vectornum]===32’bx이면 모든 testvector로 테스트했다는 뜻입니다.
==와 ===의 차이, !=와 !===의 차이는 말로 설명해도 이해하기 어려운 것 같아서 표로 보여드리겠습니다.

표로 설명
x와 z를 비교하는 부분에서의 차이입니다. x!=x이지만, x===x입니다.

[Line 46]

$finish;

Simulation을 종료합니다.(이게 없으면 오류 발생함.)

4.2. simulation\modelsim\testvector.tv

아래와 같은 형식으로 testvector.tv를 만들어 simulation\modelsim에 저장합니다.

00_0
10_1
01_1
11_0

[Line 2]
10_1
4.1번 문단 testbench3.v의 30행에서 {a, b, y_expected}=testvectors[vectornum];라고 했으므로 a=1, b=0, y_expected=1이 됩니다. underscore(_)는 편의상 쓰는 구분자이며, 없어도 무방합니다.

4.3. RTL Simulation

4.3.1. RTL Simulation을 실행합니다.

ModelSim 종료 창
4.3.2. 아니요를 클릭합니다.

ModelSim 소스코드 창
4.3.3. 소스코드 오른쪽 위의 X를 클릭합니다.

ModelSim waveform
4.3.4. 하단의 Transcript를 보시면 에러 목록과 에러 개수가 뜨는 것을 볼 수 있습니다. 위의 스크린샷을 보니 에러가 없네요!
waveform은 알아서 축소해서 보시면 됩니다.

ModelSim 변수 우클릭
4.3.5. waveform에서 vectornum과 errors를 보면 2진수로 나와 있는 것을 보실 수 있습니다. 이것들을 10진수로 띄워 보겠습니다. Ctrl을 누른 채로 vectornum과 errors를 클릭하면 한 번에 클릭할 수 있습니다.
4.3.6. RadixDecimal을 선택합니다.

ModelSim 10진수
vectornum과 errors가 10진수로 나옵니다.

5. 오늘은 여기까지!

제 글을 읽어 주셔서 감사합니다. 글에 오류가 있다면 댓글 남겨 주시면 감사하겠습니다. 다음에 만나요!

6. 참고 문헌

1) David Money Harris, Sarah L. Harris. 2013. Digial Design and Computer Architecture. 2nd Edition. Elsevier Korea L.L.C.