1. System Verilog
1. Transaction : test를 위한 Data 묶음
a. DUT와 테스트벤치 간의 데이터 전송을 추상화한 것
2. Generator : 입력 Data(Transaction) 생성
3. Driver : Data → H/W 신호 변경
a. Generator에서 생성된 Transaction을 실제 신호로 변환하여 DUT에 전달
b. 인터페이스를 통해 DUT와 통신
c. S/W 개념인 Class를 H/W와 통신하게 해주는 역할
4. Monitor : DUT 출력 신호를 Data(Transaction)으로 변경
a. DUT의 출력을 관찰하여 수집하고, 이를 분석하거나 기록
5. Scoreboard : 생성 Data와 Monitor Data 비교 ⇒ Pass, Fail 판단
- driver는 HW에 값을 직접 할당하기에 non-blocking을 사용할 수 있으나 나머지 generator 등은 sw개념이기에 blocking만 사용 가능(sw는 기본적으로 blocking)
왜 검증용으로 System Verilog를 사용하는가?
- Randmized stimulus ⇒ 임의 값으로 DUT 신호 입력
- Class(객체 지향 언어) 지원 ⇒ 캡슐화, 상속, 다형성
- 캡슐화 : 특정 기능만을 수행하는 함수 → 추상화
- 상속 : 재사용성, 확장성(extend)
- 다형성 : 같은 객체를 다르게 정의 할 수 있다.
Interface
- 신호들의 묶음
- System Verilog 문법
→ 신호선들을 interface 하나의 묶음으로 만듦
Types
→ 4 state는 기본 값 X
(나머지는 기본 값 0
)
Event Data Type
: 신호 전달, c언어의 flag와 비슷한 개념
- Event 정의 :
event <event_name>;
- Event Triggering :
-> evnet_name;
- Waiting event :
@ event_name;
또는wait(event_name.triggered);
- trigger 될 때까지 대기
2. System Verilog : Adder test bech
→ valid 신호 들어가면 다음 클럭에서 결과 값 출력
Adder 구현
`timescale 1ns / 1ps
module adder (
input clk,
input reset,
input valid,
input [3:0] a,
input [3:0] b,
output [3:0] sum,
output carry
);
reg [3:0] sum_reg, sum_next;
reg carry_reg, carry_next;
// output combinational logic
assign sum = sum_reg;
assign carry = carry_reg;
// state register
always @(posedge clk, posedge reset) begin
if (reset) begin
sum_reg <= 0;
carry_reg <= 1'b0;
end else begin
sum_reg <= sum_next;
carry_reg <= carry_next;
end
end
// next state combinational logic
always @(*) begin
carry_next = carry_reg;
sum_next = sum_reg;
if (valid) begin
{carry_next, sum_next} = a + b;
end
end
endmodule
2-2. Verification
랜덤 number Test
`timescale 1ns / 1ps
interface adder_intf; //HW 신호 묶음
logic clk;
logic reset;
logic valid;
logic [3:0] a;
logic [3:0] b;
logic [3:0] sum;
logic carry;
endinterface //adder_intf
class transaction; // test를 위한 data 묶음
rand logic [3:0] a;
rand logic [3:0] b;
// rand logic valid;
task display(string name);
$display("[%s] a:%d, b:%d", name, a, b);
endtask
endclass //transaction
class generator; // 입력 data 생성
transaction tr; // 핸들러 선언 개념
function new(); // 실체화 해주는 함수
tr = new();
endfunction //new()
task run(); // 랜덤 값 만들고 그 값을 display
repeat (10) begin
assert (tr.randomize())
else $error("tr.randomize() error!");
// $error : display보다 강력한 출력
tr.display("GEN");
end
endtask //run
endclass //generator
module tb_adder ();
generator gen;
initial begin
gen = new();
gen.run(); // generator의 run task 실행
end
endmodule
2-3. Generator, Driver
`timescale 1ns / 1ps
interface adder_intf; //HW 신호 묶음(물리적 신호)
logic clk;
logic reset;
logic valid;
logic [3:0] a;
logic [3:0] b;
logic [3:0] sum;
logic carry;
endinterface
class transaction; // test를 위한 data 묶음
rand logic [3:0] a;
rand logic [3:0] b;
// rand logic valid;
task display(string name);
$display("[%s] a:%d, b:%d", name, a, b);
endtask
endclass
class generator; // 입력 data 생성
transaction tr; // transaction class 핸들러 선언
// generator에서 만든 값(transaction)을 mailbox 공유 메모리에 저장하겠다.
mailbox #(transaction) gen2drv_mbox;
function new(); // 실체화 해주는 함수
tr = new(); // transaction tr 실체화(메모리 공간 할당)
endfunction
task run(); // 랜덤 값 만들고 그 값을 display
repeat (10) begin
assert (tr.randomize()) // transaction generate(데이터 랜덤 변수 대입)
else $error("tr.randomize() error!");
// $error : display보다 강력한 출력
// mailbox 공유 메모리에 tr 핸들러의 값을 넣겠다.
gen2drv_mbox.put(tr);
tr.display("GEN");
end
endtask
endclass
class driver; // data -> HW 신호 변경
virtual adder_intf adder_if;
/*
* 가상 인터페이스 (HW적으로 인터페이스 생성 안됨)
* 실제 인터페이스는 test_bench module에서 생성
*/
mailbox #(transaction) gen2drv_mbox;
transaction tr;
// new()를 안해줬기 때문에 메모리에 공간 할당은 하지 않음
// mailbox 공유 메모리에 접근하기 위한 transaction 핸들러
function new(virtual adder_intf adder_if);
this.adder_if = adder_if;
endfunction
task reset();
adder_if.a = 0;
adder_if.b = 0;
adder_if.valid = 1'b0;
adder_if.reset = 1'b1; // 5 클럭동안 reset High
repeat (5) @(adder_if.clk);
adder_if.reset = 1'b0;
endtask
task run();
forever begin
// generator에서 생성된 transaction 값을 interface로 넘겨야함
gen2drv_mbox.get(tr);
// mailbox 공유메모리의 값을 가져옴
// blocking code -> 값이 들어올때까지 대기
adder_if.a = tr.a;
adder_if.b = tr.b;
adder_if.valid = 1'b1;
tr.display("DRV");
@(posedge adder_if.clk); // clk rising edge 대기
adder_if.valid = 1'b0;
@(posedge adder_if.clk);
end
endtask
endclass
module tb_adder ();
adder_intf adder_if (); // interface 실체화
generator gen;
driver drv;
mailbox #(transaction) gen2drv_mbox;
adder dut (
.clk(adder_if.clk), // adder 인터페이스의 멤벼변수 접근
.reset(adder_if.reset),
.valid(adder_if.valid),
.a(adder_if.a),
.b(adder_if.b),
.sum (adder_if.sum),
.carry(adder_if.carry)
);
always #5 adder_if.clk = ~adder_if.clk;
initial begin
adder_if.clk = 1'b0;
adder_if.reset = 1'b1;
end
initial begin
gen2drv_mbox = new(); // mailbox 실체화(생성자)
gen = new();
drv = new(adder_if); // 실체화된 adder_if를 driver 가상 인터페이스에 넣음
gen.gen2drv_mbox = gen2drv_mbox;
drv.gen2drv_mbox = gen2drv_mbox; // driver의 mailbox에 실체화된 mailbox 레퍼런스 값 넣음
drv.reset();
fork
gen.run(); // generator의 run task 실행
drv.run();
join
#10 $finish;
end
endmodule
test 결과
→ 10번 generator display 나오고 그 뒤로 a:5, b:7
driver display 나옴
⇒ GEN → DRV → GEN → DRV 순서로 나와야 정상적으로 결과를 확인할 수 있음
오류 수정 : fork 추가 + event 추가
`timescale 1ns / 1ps
interface adder_intf; //HW 신호 묶음(물리적 신호)
logic clk;
logic reset;
logic valid;
logic [3:0] a;
logic [3:0] b;
logic [3:0] sum;
logic carry;
endinterface
class transaction; // test를 위한 data 묶음
rand logic [3:0] a;
rand logic [3:0] b;
// rand logic valid;
task display(string name);
$display("[%s] a:%d, b:%d", name, a, b);
endtask
endclass
class generator; // 입력 data 생성
transaction tr; // transaction class 핸들러 선언
// generator에서 만든 값(transaction)을 mailbox 공유 메모리에 저장하겠다.
mailbox #(transaction) gen2drv_mbox;
event genNextEvent1;
function new(); // 실체화 해주는 함수
tr = new(); // transaction tr 실체화(메모리 공간 할당)
endfunction
task run(); // 랜덤 값 만들고 그 값을 display
repeat (10) begin
assert (tr.randomize()) // transaction generate(데이터 랜덤 변수 대입)
else $error("tr.randomize() error!");
// $error : display보다 강력한 출력
// mailbox 공유 메모리에 tr 핸들러의 값을 넣겠다.
gen2drv_mbox.put(tr);
tr.display("GEN");
//wait(genNextEvent1.triggered); // 이벤트 트리거 될 때까지 대기
@(genNextEvent1);
end
endtask
endclass
class driver; // data -> HW 신호 변경
virtual adder_intf adder_if;
/*
* 가상 인터페이스 (HW적으로 인터페이스 생성 안됨)
* 실제 인터페이스는 test_bench module에서 생성
*/
mailbox #(transaction) gen2drv_mbox;
transaction trans;
// new()를 안해줬기 때문에 메모리에 공간 할당은 하지 않음
// mailbox 공유 메모리에 접근하기 위한 transaction 핸들러
event genNextEvent2;
function new(virtual adder_intf adder_if);
this.adder_if = adder_if;
endfunction
task reset();
adder_if.a = 0;
adder_if.b = 0;
adder_if.valid = 1'b0;
adder_if.reset = 1'b1; // 5 클럭동안 reset High
repeat (5) @(adder_if.clk);
adder_if.reset = 1'b0;
endtask
task run();
forever begin
// generator에서 생성된 transaction 값을 interface로 넘겨야함
gen2drv_mbox.get(trans);
// mailbox 공유메모리의 값을 가져옴
// blocking code -> 값이 들어올때까지 대기
adder_if.a = trans.a;
adder_if.b = trans.b;
adder_if.valid = 1'b1;
trans.display("DRV");
@(posedge adder_if.clk); // clk rising edge 대기
adder_if.valid = 1'b0;
@(posedge adder_if.clk);
-> genNextEvent2; // 이벤트 트리거링
end
endtask
endclass
module tb_adder ();
adder_intf adder_if (); // interface 실체화
generator gen;
driver drv;
event genNextEvent;
mailbox #(transaction) gen2drv_mbox;
adder dut (
.clk(adder_if.clk), // adder 인터페이스의 멤벼변수 접근
.reset(adder_if.reset),
.valid(adder_if.valid),
.a(adder_if.a),
.b(adder_if.b),
.sum (adder_if.sum),
.carry(adder_if.carry)
);
always #5 adder_if.clk = ~adder_if.clk;
initial begin
adder_if.clk = 1'b0;
adder_if.reset = 1'b1;
end
initial begin
gen2drv_mbox = new(); // mailbox 실체화(생성자)
gen = new();
drv = new(adder_if); // 실체화된 adder_if를 driver 가상 인터페이스에 넣음
gen.genNextEvent1 = genNextEvent;
drv.genNextEvent2 = genNextEvent; // gen event와 drv event 연결
gen.gen2drv_mbox = gen2drv_mbox;
drv.gen2drv_mbox = gen2drv_mbox; // driver의 mailbox에 실체화된 mailbox 레퍼런스 값 넣음
drv.reset();
fork // fork 안의 내용 동시 실행
gen.run(); // generator의 run task 실행
drv.run();
join
#10 $finish;
end
endmodule
test 결과
- 정상 작동
fork-join
(1) : fork-join 사용 결과 / (2) : fork-join_any 사용 결과 / (3) : fork-join_none 사용 결과
fork-join
: 모든 process가 끝나야 join 다음 line 실행fork-join_any
: process 중 하나라도 끝나면 join_any 다음 line 실행fork-join_none
: process 실행 후 바로 join_none 다음 line 실행
2-4. Monitor
`timescale 1ns / 1ps
interface adder_intf; //HW 신호 묶음(물리적 신호)
logic clk;
logic reset;
logic valid;
logic [3:0] a;
logic [3:0] b;
logic [3:0] sum;
logic carry;
endinterface
class transaction; // test를 위한 data 묶음
rand logic [3:0] a;
rand logic [3:0] b;
logic [3:0] sum;
logic carry;
// rand logic valid;
task display(string name);
$display("[%s] a:%d, b:%d, carry:%d, sum:%d", name, a, b, carry, sum);
endtask
endclass
class generator; // 입력 data 생성
transaction tr; // transaction class 핸들러 선언
// generator에서 만든 값(transaction)을 mailbox 공유 메모리에 저장하겠다.
mailbox #(transaction) gen2drv_mbox_gen;
event genNextEvent_gen;
function new(); // 실체화 해주는 함수
tr = new(); // transaction tr 실체화(메모리 공간 할당)
endfunction
task run(); // 랜덤 값 만들고 그 값을 display
repeat (10) begin
assert (tr.randomize()) // transaction generate(데이터 랜덤 변수 대입)
else $error("tr.randomize() error!");
// $error : display보다 강력한 출력
// mailbox 공유 메모리에 tr 핸들러의 값을 넣겠다.
gen2drv_mbox_gen.put(tr);
tr.display("GEN");
//wait(genNextEvent1.triggered); // 이벤트 트리거 될 때까지 대기
@(genNextEvent_gen);
end
endtask
endclass
class driver; // data -> HW 신호 변경
virtual adder_intf drv_adder_intf;
/*
* 가상 인터페이스 (HW적으로 인터페이스 생성 안됨)
* 실제 인터페이스는 test_bench module에서 생성
*/
mailbox #(transaction) gen2drv_mbox_drv;
transaction trans;
// new()를 안해줬기 때문에 메모리에 공간 할당은 하지 않음
// mailbox 공유 메모리에 접근하기 위한 transaction 핸들러
event genNextEvent_drv;
event monNextEvent_drv;
function new(virtual adder_intf adder_if2);
this.drv_adder_intf = adder_if2;
endfunction
task reset();
drv_adder_intf.a <= 0;
drv_adder_intf.b <= 0;
drv_adder_intf.valid <= 1'b0;
drv_adder_intf.reset <= 1'b1; // 5 클럭동안 reset High
repeat (5) @(drv_adder_intf.clk);
drv_adder_intf.reset <= 1'b0;
endtask
task run();
forever begin
// generator에서 생성된 transaction 값을 interface로 넘겨야함
gen2drv_mbox_drv.get(trans);
// mailbox 공유메모리의 값을 가져옴
// blocking code -> 값이 들어올때까지 대기
drv_adder_intf.a <= trans.a;
drv_adder_intf.b <= trans.b;
drv_adder_intf.valid <= 1'b1;
trans.display("DRV");
@(posedge drv_adder_intf.clk); // clk rising edge 대기
drv_adder_intf.valid <= 1'b0;
@(posedge drv_adder_intf.clk);
->monNextEvent_drv;
->genNextEvent_drv; // 이벤트 트리거링
end
endtask
endclass
class monitor; // DUT(HW) 출력 신호를 Transaction으로 변경
virtual adder_intf adder_intf_mon;
mailbox #(transaction) mon2scb_mbox_mon; // scoreboard와 사용하기 위한 mailbox
transaction trans;
event monNextEvent_mon;
function new(virtual adder_intf adder_if2);
this.adder_intf_mon = adder_if2;
trans = new(); // gen, drv에서 사용하는 transaction과 달라서 monitor에서 실체화
endfunction
task run();
forever begin
@(monNextEvent_mon); // driver 이벤트 트리거 되면 실행
trans.a = adder_intf_mon.a;
trans.b = adder_intf_mon.b;
trans.sum = adder_intf_mon.sum;
trans.carry = adder_intf_mon.carry;
mon2scb_mbox_mon.put(trans);
trans.display("MON");
end
endtask
endclass
module tb_adder ();
adder_intf adder_intface (); // interface 실체화
generator gen;
driver drv;
monitor mon;
event genNextEvent;
event monNextEvent;
mailbox #(transaction) gen2drv_mbox;
mailbox #(transaction) mon2scb_mbox;
adder dut (
.clk(adder_intface.clk), // adder 인터페이스의 멤벼변수 접근
.reset(adder_intface.reset),
.valid(adder_intface.valid),
.a(adder_intface.a),
.b(adder_intface.b),
.sum (adder_intface.sum),
.carry(adder_intface.carry)
);
always #5 adder_intface.clk = ~adder_intface.clk;
initial begin
adder_intface.clk = 1'b0;
adder_intface.reset = 1'b1;
end
initial begin
gen2drv_mbox = new(); // mailbox 실체화(생성자)
mon2scb_mbox = new();
gen = new();
drv = new(adder_intface); // 실체화된 adder_if를 driver 가상 인터페이스에 넣음
mon = new(adder_intface);
gen.genNextEvent_gen = genNextEvent;
drv.genNextEvent_drv = genNextEvent; // gen event와 drv event 연결
mon.monNextEvent_mon = monNextEvent;
drv.monNextEvent_drv = monNextEvent;
gen.gen2drv_mbox_gen = gen2drv_mbox;
drv.gen2drv_mbox_drv = gen2drv_mbox; // driver의 mailbox에 실체화된 mailbox 레퍼런스 값 넣음
mon.mon2scb_mbox_mon = mon2scb_mbox;
drv.reset();
fork // fork 안의 내용 동시 실행
gen.run(); // generator의 run task 실행
drv.run();
mon.run();
join_any
$display("test bench is finished!");
#10 $finish;
end
endmodule
2-5. Scoreboard teset
`timescale 1ns / 1ps
interface adder_intf; //HW 신호 묶음(물리적 신호)
logic clk;
logic reset;
logic valid;
logic [3:0] a;
logic [3:0] b;
logic [3:0] sum;
logic carry;
endinterface
class transaction; // test를 위한 data 묶음
rand logic [3:0] a;
rand logic [3:0] b;
logic [3:0] sum;
logic carry;
// rand logic valid;
task display(string name);
$display("[%s] a:%d, b:%d, carry:%d, sum:%d", name, a, b, carry, sum);
endtask
endclass
class generator; // 입력 data 생성
transaction tr; // transaction class 핸들러 선언
// generator에서 만든 값(transaction)을 mailbox 공유 메모리에 저장하겠다.
mailbox #(transaction) gen2drv_mbox_gen;
event genNextEvent_gen;
function new(); // 실체화 해주는 함수
tr = new(); // transaction tr 실체화(메모리 공간 할당)
endfunction
task run(); // 랜덤 값 만들고 그 값을 display
repeat (10) begin
assert (tr.randomize()) // transaction generate(데이터 랜덤 변수 대입)
else $error("tr.randomize() error!");
// $error : display보다 강력한 출력
// mailbox 공유 메모리에 tr 핸들러의 값을 넣겠다.
gen2drv_mbox_gen.put(tr);
tr.display("GEN");
//wait(genNextEvent1.triggered); // 이벤트 트리거 될 때까지 대기
@(genNextEvent_gen);
end
endtask
endclass
class driver; // data -> HW 신호 변경
virtual adder_intf drv_adder_intf;
/*
* 가상 인터페이스 (HW적으로 인터페이스 생성 안됨)
* 실제 인터페이스는 test_bench module에서 생성
*/
mailbox #(transaction) gen2drv_mbox_drv;
transaction trans;
// new()를 안해줬기 때문에 메모리에 공간 할당은 하지 않음
// mailbox 공유 메모리에 접근하기 위한 transaction 핸들러
event genNextEvent_drv;
event monNextEvent_drv;
function new(virtual adder_intf adder_if2);
this.drv_adder_intf = adder_if2;
endfunction
task reset();
drv_adder_intf.a <= 0;
drv_adder_intf.b <= 0;
drv_adder_intf.valid <= 1'b0;
drv_adder_intf.reset <= 1'b1; // 5 클럭동안 reset High
repeat (5) @(drv_adder_intf.clk);
drv_adder_intf.reset <= 1'b0;
endtask
task run();
forever begin
// generator에서 생성된 transaction 값을 interface로 넘겨야함
gen2drv_mbox_drv.get(trans);
// mailbox 공유메모리의 값을 가져옴
// blocking code -> 값이 들어올때까지 대기
drv_adder_intf.a <= trans.a;
drv_adder_intf.b <= trans.b;
drv_adder_intf.valid <= 1'b1;
trans.display("DRV");
@(posedge drv_adder_intf.clk); // clk rising edge 대기
drv_adder_intf.valid <= 1'b0;
@(posedge drv_adder_intf.clk);
->monNextEvent_drv; ->genNextEvent_drv; // 이벤트 트리거링
end
endtask
endclass
class monitor; // DUT(HW) 출력 신호를 Transaction으로 변경
virtual adder_intf adder_intf_mon;
mailbox #(transaction) mon2scb_mbox_mon; // scoreboard와 사용하기 위한 mailbox
transaction trans;
event monNextEvent_mon;
function new(virtual adder_intf adder_if2);
this.adder_intf_mon = adder_if2;
trans = new(); // gen, drv에서 사용하는 transaction과 달라서 monitor에서 실체화
endfunction
task run();
forever begin
@(monNextEvent_mon); // driver 이벤트 트리거 되면 실행
trans.a = adder_intf_mon.a;
trans.b = adder_intf_mon.b;
trans.sum = adder_intf_mon.sum;
trans.carry = adder_intf_mon.carry;
mon2scb_mbox_mon.put(trans);
trans.display("MON");
end
endtask
endclass
class scoreboard; // SW 값과 HW 값 비교
mailbox #(transaction) mon2scb_mbox_sb;
transaction trans;
function new();
endfunction
task run();
forever begin
mon2scb_mbox_sb.get(trans);
trans.display("SCB");
// (trans.a + trans.b) <- reference model, golden reference
if ((trans.a + trans.b) == {trans.carry, trans.sum}) begin
$display(" ---> PASS ! %d + %d = %d", trans.a, trans.b, {
trans.carry, trans.sum});
end else begin
$display(" ---> FAIL ! %d + %d = %d", trans.a, trans.b, {
trans.carry, trans.sum});
end
end
endtask
endclass
module tb_adder ();
adder_intf adder_intface (); // interface 실체화
generator gen;
driver drv;
monitor mon;
scoreboard scb;
event genNextEvent;
event monNextEvent;
mailbox #(transaction) gen2drv_mbox;
mailbox #(transaction) mon2scb_mbox;
adder dut (
.clk(adder_intface.clk), // adder 인터페이스의 멤벼변수 접근
.reset(adder_intface.reset),
.valid(adder_intface.valid),
.a(adder_intface.a),
.b(adder_intface.b),
.sum (adder_intface.sum),
.carry(adder_intface.carry)
);
always #5 adder_intface.clk = ~adder_intface.clk;
initial begin
adder_intface.clk = 1'b0;
adder_intface.reset = 1'b1;
end
initial begin
gen2drv_mbox = new(); // mailbox 실체화(생성자)
mon2scb_mbox = new();
gen = new();
drv = new(adder_intface); // 실체화된 adder_if를 driver 가상 인터페이스에 넣음
mon = new(adder_intface);
scb = new();
gen.genNextEvent_gen = genNextEvent;
drv.genNextEvent_drv = genNextEvent; // gen event와 drv event 연결
mon.monNextEvent_mon = monNextEvent;
drv.monNextEvent_drv = monNextEvent;
gen.gen2drv_mbox_gen = gen2drv_mbox;
drv.gen2drv_mbox_drv = gen2drv_mbox; // driver의 mailbox에 실체화된 mailbox 레퍼런스 값 넣음
mon.mon2scb_mbox_mon = mon2scb_mbox;
scb.mon2scb_mbox_sb = mon2scb_mbox;
drv.reset();
fork // fork 안의 내용 동시 실행
gen.run(); // generator의 run task 실행
drv.run();
mon.run();
scb.run();
join_any
$display("test bench is finished!");
#10 $finish;
end
endmodule
수정 : 이벤트 트리거 순서 변경(scoreboard 완료 후 generator 시작)
`timescale 1ns / 1ps
interface adder_intf; //HW 신호 묶음(물리적 신호)
logic clk;
logic reset;
logic valid;
logic [3:0] a;
logic [3:0] b;
logic [3:0] sum;
logic carry;
endinterface
class transaction; // test를 위한 data 묶음
rand logic [3:0] a;
rand logic [3:0] b;
logic [3:0] sum;
logic carry;
// rand logic valid;
task display(string name);
$display("[%s] a:%d, b:%d, carry:%d, sum:%d", name, a, b, carry, sum);
endtask
endclass
class generator; // 입력 data 생성
transaction tr; // transaction class 핸들러 선언
// generator에서 만든 값(transaction)을 mailbox 공유 메모리에 저장하겠다.
mailbox #(transaction) gen2drv_mbox_gen;
event genNextEvent_gen;
function new(); // 실체화 해주는 함수
tr = new(); // transaction tr 실체화(메모리 공간 할당)
endfunction
task run(); // 랜덤 값 만들고 그 값을 display
repeat (10) begin
assert (tr.randomize()) // transaction generate(데이터 랜덤 변수 대입)
else $error("tr.randomize() error!");
// $error : display보다 강력한 출력
// mailbox 공유 메모리에 tr 핸들러의 값을 넣겠다.
gen2drv_mbox_gen.put(tr);
tr.display("GEN");
//wait(genNextEvent1.triggered); // 이벤트 트리거 될 때까지 대기
@(genNextEvent_gen);
end
endtask
endclass
class driver; // data -> HW 신호 변경
virtual adder_intf drv_adder_intf;
/*
* 가상 인터페이스 (HW적으로 인터페이스 생성 안됨)
* 실제 인터페이스는 test_bench module에서 생성
*/
mailbox #(transaction) gen2drv_mbox_drv;
transaction trans;
// new()를 안해줬기 때문에 메모리에 공간 할당은 하지 않음
// mailbox 공유 메모리에 접근하기 위한 transaction 핸들러
// event genNextEvent_drv;
event monNextEvent_drv;
function new(virtual adder_intf adder_if2);
this.drv_adder_intf = adder_if2;
endfunction
task reset();
drv_adder_intf.a <= 0;
drv_adder_intf.b <= 0;
drv_adder_intf.valid <= 1'b0;
drv_adder_intf.reset <= 1'b1; // 5 클럭동안 reset High
repeat (5) @(drv_adder_intf.clk);
drv_adder_intf.reset <= 1'b0;
endtask
task run();
forever begin
// generator에서 생성된 transaction 값을 interface로 넘겨야함
gen2drv_mbox_drv.get(trans);
// mailbox 공유메모리의 값을 가져옴
// blocking code -> 값이 들어올때까지 대기
drv_adder_intf.a <= trans.a;
drv_adder_intf.b <= trans.b;
drv_adder_intf.valid <= 1'b1;
trans.display("DRV");
@(posedge drv_adder_intf.clk); // clk rising edge 대기
drv_adder_intf.valid <= 1'b0;
@(posedge drv_adder_intf.clk);
->monNextEvent_drv;
//->genNextEvent_drv; // 이벤트 트리거링
end
endtask
endclass
class monitor; // DUT(HW) 출력 신호를 Transaction으로 변경
virtual adder_intf adder_intf_mon;
mailbox #(transaction) mon2scb_mbox_mon; // scoreboard와 사용하기 위한 mailbox
transaction trans;
event monNextEvent_mon;
function new(virtual adder_intf adder_if2);
this.adder_intf_mon = adder_if2;
trans = new(); // gen, drv에서 사용하는 transaction과 달라서 monitor에서 실체화
endfunction
task run();
forever begin
@(monNextEvent_mon); // driver 이벤트 트리거 되면 실행
trans.a = adder_intf_mon.a;
trans.b = adder_intf_mon.b;
trans.sum = adder_intf_mon.sum;
trans.carry = adder_intf_mon.carry;
mon2scb_mbox_mon.put(trans);
trans.display("MON");
end
endtask
endclass
class scoreboard; // SW 값과 HW 값 비교
mailbox #(transaction) mon2scb_mbox_sb;
transaction trans;
event genNextEvent_sb;
function new();
endfunction
task run();
forever begin
mon2scb_mbox_sb.get(trans);
trans.display("SCB");
// (trans.a + trans.b) <- reference model, golden reference
if ((trans.a + trans.b) == {trans.carry, trans.sum}) begin
$display(" ---> PASS ! %d + %d = %d", trans.a, trans.b, {
trans.carry, trans.sum});
end else begin
$display(" ---> FAIL ! %d + %d = %d", trans.a, trans.b, {
trans.carry, trans.sum});
end
-> genNextEvent_sb;
end
endtask
endclass
module tb_adder ();
adder_intf adder_intface (); // interface 실체화
generator gen;
driver drv;
monitor mon;
scoreboard scb;
event genNextEvent;
event monNextEvent;
mailbox #(transaction) gen2drv_mbox;
mailbox #(transaction) mon2scb_mbox;
adder dut (
.clk(adder_intface.clk), // adder 인터페이스의 멤벼변수 접근
.reset(adder_intface.reset),
.valid(adder_intface.valid),
.a(adder_intface.a),
.b(adder_intface.b),
.sum (adder_intface.sum),
.carry(adder_intface.carry)
);
always #5 adder_intface.clk = ~adder_intface.clk;
initial begin
adder_intface.clk = 1'b0;
adder_intface.reset = 1'b1;
end
initial begin
gen2drv_mbox = new(); // mailbox 실체화(생성자)
mon2scb_mbox = new();
gen = new();
drv = new(adder_intface); // 실체화된 adder_if를 driver 가상 인터페이스에 넣음
mon = new(adder_intface);
scb = new();
gen.genNextEvent_gen = genNextEvent;
scb.genNextEvent_sb = genNextEvent; // gen event와 drv event 연결
mon.monNextEvent_mon = monNextEvent;
drv.monNextEvent_drv = monNextEvent;
gen.gen2drv_mbox_gen = gen2drv_mbox;
drv.gen2drv_mbox_drv = gen2drv_mbox; // driver의 mailbox에 실체화된 mailbox 레퍼런스 값 넣음
mon.mon2scb_mbox_mon = mon2scb_mbox;
scb.mon2scb_mbox_sb = mon2scb_mbox;
drv.reset();
fork // fork 안의 내용 동시 실행
gen.run(); // generator의 run task 실행
drv.run();
mon.run();
scb.run();
join_any
$display("test bench is finished!");
#10 $finish;
end
endmodule
최종 : 카운트 추가
`timescale 1ns / 1ps
interface adder_intf; //HW 신호 묶음(물리적 신호)
logic clk;
logic reset;
logic valid;
logic [3:0] a;
logic [3:0] b;
logic [3:0] sum;
logic carry;
endinterface
class transaction; // test를 위한 data 묶음
rand logic [3:0] a;
rand logic [3:0] b;
logic [3:0] sum;
logic carry;
// rand logic valid;
task display(string name);
$display("[%s] a:%d, b:%d, carry:%d, sum:%d", name, a, b, carry, sum);
endtask
endclass
class generator; // 입력 data 생성
transaction tr; // transaction class 핸들러 선언
// generator에서 만든 값(transaction)을 mailbox 공유 메모리에 저장하겠다.
mailbox #(transaction) gen2drv_mbox_gen;
event genNextEvent_gen;
function new(); // 실체화 해주는 함수
tr = new(); // transaction tr 실체화(메모리 공간 할당)
endfunction
task run(); // 랜덤 값 만들고 그 값을 display
repeat (1000) begin
assert (tr.randomize()) // transaction generate(데이터 랜덤 변수 대입)
else $error("tr.randomize() error!");
// $error : display보다 강력한 출력
// mailbox 공유 메모리에 tr 핸들러의 값을 넣겠다.
gen2drv_mbox_gen.put(tr);
tr.display("GEN");
//wait(genNextEvent1.triggered); // 이벤트 트리거 될 때까지 대기
@(genNextEvent_gen);
end
endtask
endclass
class driver; // data -> HW 신호 변경
virtual adder_intf drv_adder_intf;
/*
* 가상 인터페이스 (HW적으로 인터페이스 생성 안됨)
* 실제 인터페이스는 test_bench module에서 생성
*/
mailbox #(transaction) gen2drv_mbox_drv;
transaction trans;
// new()를 안해줬기 때문에 메모리에 공간 할당은 하지 않음
// mailbox 공유 메모리에 접근하기 위한 transaction 핸들러
// event genNextEvent_drv;
event monNextEvent_drv;
function new(virtual adder_intf adder_if2);
this.drv_adder_intf = adder_if2;
endfunction
task reset();
drv_adder_intf.a <= 0;
drv_adder_intf.b <= 0;
drv_adder_intf.valid <= 1'b0;
drv_adder_intf.reset <= 1'b1; // 5 클럭동안 reset High
repeat (5) @(drv_adder_intf.clk);
drv_adder_intf.reset <= 1'b0;
endtask
task run();
forever begin
// generator에서 생성된 transaction 값을 interface로 넘겨야함
gen2drv_mbox_drv.get(trans);
// mailbox 공유메모리의 값을 가져옴
// blocking code -> 값이 들어올때까지 대기
drv_adder_intf.a <= trans.a;
drv_adder_intf.b <= trans.b;
drv_adder_intf.valid <= 1'b1;
trans.display("DRV");
@(posedge drv_adder_intf.clk); // clk rising edge 대기
drv_adder_intf.valid <= 1'b0;
@(posedge drv_adder_intf.clk);
->monNextEvent_drv;
//->genNextEvent_drv; // 이벤트 트리거링
end
endtask
endclass
class monitor; // DUT(HW) 출력 신호를 Transaction으로 변경
virtual adder_intf adder_intf_mon;
mailbox #(transaction) mon2scb_mbox_mon; // scoreboard와 사용하기 위한 mailbox
transaction trans;
event monNextEvent_mon;
function new(virtual adder_intf adder_if2);
this.adder_intf_mon = adder_if2;
trans = new(); // gen, drv에서 사용하는 transaction과 달라서 monitor에서 실체화
endfunction
task run();
forever begin
@(monNextEvent_mon); // driver 이벤트 트리거 되면 실행
trans.a = adder_intf_mon.a;
trans.b = adder_intf_mon.b;
trans.sum = adder_intf_mon.sum;
trans.carry = adder_intf_mon.carry;
mon2scb_mbox_mon.put(trans);
trans.display("MON");
end
endtask
endclass
class scoreboard; // SW 값과 HW 값 비교
mailbox #(transaction) mon2scb_mbox_sb;
transaction trans;
event genNextEvent_sb;
int total_cnt, pass_cnt, fail_cnt;
function new();
total_cnt = 0;
pass_cnt = 0;
fail_cnt = 0;
endfunction
task run();
forever begin
mon2scb_mbox_sb.get(trans);
trans.display("SCB");
// (trans.a + trans.b) <- reference model, golden reference
if ((trans.a + trans.b) == {trans.carry, trans.sum}) begin
$display(" ---> PASS ! %d + %d = %d", trans.a, trans.b, {
trans.carry, trans.sum});
pass_cnt++;
end else begin
$display(" ---> FAIL ! %d + %d = %d", trans.a, trans.b, {
trans.carry, trans.sum});
fail_cnt++;
end
total_cnt++;
->genNextEvent_sb;
end
endtask
endclass
module tb_adder ();
adder_intf adder_intface (); // interface 실체화
generator gen;
driver drv;
monitor mon;
scoreboard scb;
event genNextEvent;
event monNextEvent;
mailbox #(transaction) gen2drv_mbox;
mailbox #(transaction) mon2scb_mbox;
adder dut (
.clk(adder_intface.clk), // adder 인터페이스의 멤벼변수 접근
.reset(adder_intface.reset),
.valid(adder_intface.valid),
.a(adder_intface.a),
.b(adder_intface.b),
.sum (adder_intface.sum),
.carry(adder_intface.carry)
);
always #5 adder_intface.clk = ~adder_intface.clk;
initial begin
adder_intface.clk = 1'b0;
adder_intface.reset = 1'b1;
end
initial begin
gen2drv_mbox = new(); // mailbox 실체화(생성자)
mon2scb_mbox = new(); // mailbox의 생성자는 systme verilog에 내장되어 있음
// () 값이 비어있으면 mailbox 크기 무한대
gen = new(); // gen 객체가 속한 class(generator)의 생성자 호출
drv = new(adder_intface); // 실체화된 adder_if를 driver 가상 인터페이스에 넣음
mon = new(adder_intface);
scb = new();
gen.genNextEvent_gen = genNextEvent;
scb.genNextEvent_sb = genNextEvent; // gen event와 drv event 연결
mon.monNextEvent_mon = monNextEvent;
drv.monNextEvent_drv = monNextEvent;
gen.gen2drv_mbox_gen = gen2drv_mbox;
drv.gen2drv_mbox_drv = gen2drv_mbox; // driver의 mailbox에 실체화된 mailbox 레퍼런스 값 넣음
mon.mon2scb_mbox_mon = mon2scb_mbox;
scb.mon2scb_mbox_sb = mon2scb_mbox;
drv.reset();
fork // fork 안의 내용 동시 실행
gen.run(); // generator의 run task 실행
drv.run();
mon.run();
scb.run();
join_any
$display("==========================");
$display("== Final Report ==");
$display("==========================");
$display("Total Test : %d", scb.total_cnt);
$display("Pass Count : %d", scb.pass_cnt);
$display("Fail Count : %d", scb.fail_cnt);
$display("==========================");
$display("== test bench is finished! ==");
$display("==========================");
#10 $finish;
end
endmodule
3. 정리
Interface
- HW 물리 신호
- DUT의 모든 입,출력을
logic
형태로 선언 - test bench module*에서 *driver, DUT, monitor를 연결해준다. (인스턴스화는 test bench module에서!!!)
interface adder_intf; //HW 신호 묶음(물리적 신호)
logic clk;
logic reset;
logic valid;
logic [3:0] a;
logic [3:0] b;
logic [3:0] sum;
logic carry;
endinterface
Transaction
- 데이터 묶음 (구조체와 비슷하지만 종속 task가 있을 수 있음)
- 출력에 사용할 값만 선언하면 될듯?
- 같은 transaction을 공유하는 class(generator↔driver, …)는
mailbox
를 통해 read/write - transaction 인스턴스화 : generator, monitor ← transaction을 저장하는 class들
- mailbox 인스턴스화 : test bench module
class transaction; // test를 위한 data 묶음
rand logic [3:0] a;
rand logic [3:0] b;
logic [3:0] sum;
logic carry;
// rand logic valid;
task display(string name);
$display("[%s] a:%d, b:%d, carry:%d, sum:%d", name, a, b, carry, sum);
endtask
endclass
Generator
- 테스트에 사용할 입력 데이터(랜덤 값) 생성
run task
: 몇번 생성(repeat)할 것인지 결정 → mailbox에 저장
- 데이터를 저장해야 하기 때문에 trasaction 인스턴스화 (
new()
메소드에서) - task는 scoreboard 끝나면(이벤트 트리거되면) 다시 시작
class generator; // 입력 data 생성
transaction tr; // transaction class 핸들러 선언
// generator에서 만든 값(transaction)을 mailbox 공유 메모리에 저장하겠다.
mailbox #(transaction) gen2drv_mbox_gen;
event genNextEvent_gen;
function new(); // 실체화 해주는 함수
tr = new(); // transaction tr 실체화(메모리 공간 할당)
endfunction
task run(); // 랜덤 값 만들고 그 값을 display
repeat (1000) begin
assert (tr.randomize()) // transaction generate(데이터 랜덤 변수 대입)
else $error("tr.randomize() error!");
// $error : display보다 강력한 출력
// mailbox 공유 메모리에 tr 핸들러의 값을 넣겠다.
gen2drv_mbox_gen.put(tr);
tr.display("GEN");
//wait(genNextEvent1.triggered); // 이벤트 트리거 될 때까지 대기
@(genNextEvent_gen);
end
endtask
endclass
Driver
- transaction을 HW 신호(interface)로 변경
reset task
: 초기 값 설정, reset 신호run task
: mailbox에서 transaction 데이터 꺼내옴 → interface에 저장 + valid 신호gen2drv_mbox_drv.get(trans);
코드가 blocking 코드이기 때문에 값이 들어와야 task 실행 ← 따로 이벤트 트리거 후 실행하는 알고리즘을 구현하지 않아도 됨(generator에서 값 저장해야 실행)
- 가상 interface 인스턴스 선언 →
new()
메소드에서 가상 interface와 실제 interface 연결 - task 끝나면 monitor에 이벤트 트리거 신호 발생
class driver; // data -> HW 신호 변경
virtual adder_intf drv_adder_intf;
/*
* 가상 인터페이스 (HW적으로 인터페이스 생성 안됨)
* 실제 인터페이스는 test_bench module에서 생성
*/
mailbox #(transaction) gen2drv_mbox_drv;
transaction trans;
// new()를 안해줬기 때문에 메모리에 공간 할당은 하지 않음
// mailbox 공유 메모리에 접근하기 위한 transaction 핸들러
// event genNextEvent_drv;
event monNextEvent_drv;
function new(virtual adder_intf adder_if2);
this.drv_adder_intf = adder_if2;
endfunction
task reset();
drv_adder_intf.a <= 0;
drv_adder_intf.b <= 0;
drv_adder_intf.valid <= 1'b0;
drv_adder_intf.reset <= 1'b1; // 5 클럭동안 reset High
repeat (5) @(drv_adder_intf.clk);
drv_adder_intf.reset <= 1'b0;
endtask
task run();
forever begin
// generator에서 생성된 transaction 값을 interface로 넘겨야함
gen2drv_mbox_drv.get(trans);
// mailbox 공유메모리의 값을 가져옴
// blocking code -> 값이 들어올때까지 대기
drv_adder_intf.a <= trans.a;
drv_adder_intf.b <= trans.b;
drv_adder_intf.valid <= 1'b1;
trans.display("DRV");
@(posedge drv_adder_intf.clk); // clk rising edge 대기
drv_adder_intf.valid <= 1'b0;
@(posedge drv_adder_intf.clk);
->monNextEvent_drv;
//->genNextEvent_drv; // 이벤트 트리거링
end
endtask
endclass
Monitor
- HW 출력 신호(interface)를 transaction으로 변경
run task
: driver와 반대로 interface를 transaction으로 변경 → mailbox에 저장
- 가상 interface 인스턴스 선언 →
new()
메소드에서 가상 interface와 실제 interface 연결 - 데이터를 저장해야 하기 때문에 trasaction 인스턴스화 (
new()
메소드에서) - task는 driver 끝나면(이벤트 트리거되면) 다시 시작
class monitor; // DUT(HW) 출력 신호를 Transaction으로 변경
virtual adder_intf adder_intf_mon;
mailbox #(transaction) mon2scb_mbox_mon; // scoreboard와 사용하기 위한 mailbox
transaction trans;
event monNextEvent_mon;
function new(virtual adder_intf adder_if2);
this.adder_intf_mon = adder_if2;
trans = new(); // gen, drv에서 사용하는 transaction과 달라서 monitor에서 실체화
endfunction
task run();
forever begin
@(monNextEvent_mon); // driver 이벤트 트리거 되면 실행
trans.a = adder_intf_mon.a;
trans.b = adder_intf_mon.b;
trans.sum = adder_intf_mon.sum;
trans.carry = adder_intf_mon.carry;
mon2scb_mbox_mon.put(trans);
trans.display("MON");
end
endtask
endclass
Scoreboard
- SW 연산 결과와 HW(DUT)값 비교(Pass, Fail 판단)
run task
: mailbox*에서 *transaction 데이터 꺼내옴 → sw 연산(+,-,/,*…)과 DUT 연산 값 비교mon2scb_mbox_sb.get(trans);
코드가 blocking 코드이기 때문에 값이 들어와야 task 실행 ← 따로 이벤트 트리거 후 실행하는 알고리즘을 구현하지 않아도 됨(monitor에서 값 저장해야 실행)
new()
메소드에서 카운트 값 초기화 → task에서 pass, fail count- task 끝나면 monitor에 이벤트 트리거 신호 발생
class scoreboard; // SW 값과 HW 값 비교
mailbox #(transaction) mon2scb_mbox_sb;
transaction trans;
event genNextEvent_sb;
int total_cnt, pass_cnt, fail_cnt;
function new();
total_cnt = 0;
pass_cnt = 0;
fail_cnt = 0;
endfunction
task run();
forever begin
mon2scb_mbox_sb.get(trans);
trans.display("SCB");
// (trans.a + trans.b) <- reference model, golden reference
if ((trans.a + trans.b) == {trans.carry, trans.sum}) begin
$display(" ---> PASS ! %d + %d = %d", trans.a, trans.b, {
trans.carry, trans.sum});
pass_cnt++;
end else begin
$display(" ---> FAIL ! %d + %d = %d", trans.a, trans.b, {
trans.carry, trans.sum});
fail_cnt++;
end
total_cnt++;
->genNextEvent_sb;
end
endtask
endclass
test bench module
- interface 인스턴스화, 각 class 인스턴스 선언 + 인스턴스화
- generator ↔ scoreboard
이벤트
연결, monitor ↔ driver이벤트
연결 - generator ↔ driver
mailbox
연결, monitor ↔ scoreboardmailbox
연결 - driver ↔ DUT ↔ monitor
interface
연걸 for-join_any
사용하여 각 class의 task 동시 실행
module tb_adder ();
adder_intf adder_intface (); // interface 실체화
generator gen; // 인스턴스 선언
driver drv;
monitor mon;
scoreboard scb;
event genNextEvent;
event monNextEvent;
mailbox #(transaction) gen2drv_mbox;
mailbox #(transaction) mon2scb_mbox;
adder dut (
.clk(adder_intface.clk), // adder 인터페이스의 멤벼변수 접근
.reset(adder_intface.reset),
.valid(adder_intface.valid),
.a(adder_intface.a),
.b(adder_intface.b),
.sum (adder_intface.sum),
.carry(adder_intface.carry)
);
always #5 adder_intface.clk = ~adder_intface.clk;
initial begin
adder_intface.clk = 1'b0;
adder_intface.reset = 1'b1;
end
initial begin
gen2drv_mbox = new(); // mailbox 실체화(생성자)
mon2scb_mbox = new(); // mailbox의 생성자는 systme verilog에 내장되어 있음
// () 값이 비어있으면 mailbox 크기 무한대
gen = new(); // gen 객체가 속한 class(generator)의 생성자 호출
drv = new(adder_intface); // 실체화된 adder_if를 driver 가상 인터페이스에 넣음
mon = new(adder_intface);
scb = new();
// 클래스 내에 new() 생성자가 없을 경우 기본 생성자 -> 메모리에 공간 할당
gen.genNextEvent_gen = genNextEvent;
scb.genNextEvent_sb = genNextEvent; // gen event와 drv event 연결
mon.monNextEvent_mon = monNextEvent;
drv.monNextEvent_drv = monNextEvent;
gen.gen2drv_mbox_gen = gen2drv_mbox;
drv.gen2drv_mbox_drv = gen2drv_mbox; // driver의 mailbox에 실체화된 mailbox 레퍼런스 값 넣음
mon.mon2scb_mbox_mon = mon2scb_mbox;
scb.mon2scb_mbox_sb = mon2scb_mbox;
drv.reset();
fork // fork 안의 내용 동시 실행
gen.run(); // generator의 run task 실행
drv.run();
mon.run();
scb.run();
join_any
$display("==========================");
$display("== Final Report ==");
$display("==========================");
$display("Total Test : %d", scb.total_cnt);
$display("Pass Count : %d", scb.pass_cnt);
$display("Fail Count : %d", scb.fail_cnt);
$display("==========================");
$display("== test bench is finished! ==");
$display("==========================");
#10 $finish;
end
endmodule
Made By Minseok KIM
'VerilogHDL > Study' 카테고리의 다른 글
[VerilogHDL] FIFO, UART&FIFO (0) | 2024.05.30 |
---|---|
[VerilogHDL] Verification(32bit register, BRAM) (0) | 2024.05.23 |
[VerilogHDL] UART Rx, Tx 최종(Oversampling) (0) | 2024.05.22 |
[VerilogHDL] UART Tx(2) (0) | 2024.05.22 |
[VerilogHDL] UpCounter, 디버깅, UART Tx (0) | 2024.05.20 |
Let's Be Happy!
도움이 되었으면 좋겠어요 :)