이제 배운 내용을 바탕으로 UVM 형태의 Testbench를 작성해보자. 우선 DUT인 FIFO를 설계한다.
`timescale 1ns / 1ps
module FIFO #(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4
) (
input logic clk,
input logic reset,
input logic we,
input logic re,
input logic [DATA_WIDTH-1:0] d_in,
output logic [DATA_WIDTH-1:0] q_out,
output logic empty,
output logic full
);
logic [ADDR_WIDTH-1:0] w_ptr, r_ptr;
logic [ADDR_WIDTH:0] cnt;
logic [DATA_WIDTH-1:0] mem [0:2**ADDR_WIDTH-1];
assign full = (cnt == 2 ** ADDR_WIDTH);
assign empty = (cnt == 0);
always_ff @(posedge clk or posedge reset) begin
if (reset) begin
w_ptr <= 0;
r_ptr <= 0;
cnt <= 0;
q_out <= '0;
end else begin
if (we && !full) begin
mem[w_ptr] <= d_in;
w_ptr <= w_ptr + 1;
cnt <= cnt + 1;
end
if (re && !empty) begin
q_out <= mem[r_ptr];
r_ptr <= r_ptr + 1;
cnt <= cnt - 1;
end
end
end
endmodule
그 다음에는 FIFO 테스트를 위한 기본 Transaction 클래스를 선언한다. oper 값을 기반으로 제어 신호(we, re)가 생성되며, constraint 구문은 oper가 write(1) 또는 read(0)를 50% 확률로 선택하도록 제약을 설정한다.
class transaction;
rand bit oper; // Randomized bit for operation control (1 or 0)
bit rd, wr; // Read and write control bits
bit [7:0] data_in; // 8-bit data input
bit full, empty; // Flags for full and empty status
bit [7:0] data_out; // 8-bit data output
constraint oper_ctrl {
oper dist {1 :/ 50 , 0 :/ 50}; // Constraint to randomize 'oper' with 50% probability of 1 and 50% probability of 0
}
endclass
Generator 클래스 선언부이다. 트랜잭션을 랜덤하게 생성하고 mailbox를 통해 driver에 전달하는 역할을 한다. event next를 통해 한 사이클씩 동기화하며 트랜잭션 수를 조절한다.
class generator;
transaction tr; // Transaction object to generate and send
mailbox #(transaction) mbx; // Mailbox for communication
int count = 0; // Number of transactions to generate
int i = 0; // Iteration counter
event next; // Event to signal when to send the next transaction
event done; // Event to convey completion of requested number of transactions
function new(mailbox #(transaction) mbx);
this.mbx = mbx;
tr = new();
endfunction;
task run();
repeat (count) begin
assert (tr.randomize) else $error("Randomization failed");
i++;
mbx.put(tr);
$display("[GEN] : Oper : %0d iteration : %0d", tr.oper, i);
@(next);
end -> done;
endtask
endclass
Driver 클래스이다. generator로부터 받은 트랜잭션을 기반으로 FIFO DUT에 신호를 적용한다. we, re, data_in을 조절하여 write/read를 실행한다.
class driver;
virtual fifo_if fif; // Virtual interface to the FIFO
mailbox #(transaction) mbx; // Mailbox for communication
transaction datac; // Transaction object for communication
function new(mailbox #(transaction) mbx);
this.mbx = mbx;
endfunction;
// Reset the DUT
task reset();
fif.rst <= 1'b1;
fif.rd <= 1'b0;
fif.wr <= 1'b0;
fif.data_in <= 0;
repeat (5) @(posedge fif.clock);
fif.rst <= 1'b0;
$display("[DRV] : DUT Reset Done");
$display("------------------------------------------");
endtask
// Write data to the FIFO
task write();
@(posedge fif.clock);
fif.rst <= 1'b0;
fif.rd <= 1'b0;
fif.wr <= 1'b1;
fif.data_in <= $urandom_range(1, 10);
@(posedge fif.clock);
fif.wr <= 1'b0;
$display("[DRV] : DATA WRITE data : %0d", fif.data_in);
@(posedge fif.clock);
endtask
// Read data from the FIFO
task read();
@(posedge fif.clock);
fif.rst <= 1'b0;
fif.rd <= 1'b1;
fif.wr <= 1'b0;
@(posedge fif.clock);
fif.rd <= 1'b0;
$display("[DRV] : DATA READ");
@(posedge fif.clock);
endtask
// Apply random stimulus to the DUT
task run();
forever begin
mbx.get(datac);
if (datac.oper == 1'b1)
write();
else
read();
end
endtask
endclass
Monitor 클래스이다. DUT 인터페이스의 동작을 관찰하고 트랜잭션으로 복원하여 scoreboard에 전달한다.
class monitor;
virtual fifo_if fif; // Virtual interface to the FIFO
mailbox #(transaction) mbx; // Mailbox for communication
transaction tr; // Transaction object for monitoring
function new(mailbox #(transaction) mbx);
this.mbx = mbx;
endfunction;
task run();
tr = new();
forever begin
repeat (2) @(posedge fif.clock);
tr.wr = fif.wr;
tr.rd = fif.rd;
tr.data_in = fif.data_in;
tr.full = fif.full;
tr.empty = fif.empty;
@(posedge fif.clock);
tr.data_out = fif.data_out;
mbx.put(tr);
$display("[MON] : Wr:%0d rd:%0d din:%0d dout:%0d full:%0d empty:%0d", tr.wr, tr.rd, tr.data_in, tr.data_out, tr.full, tr.empty);
end
endtask
endclass
Scoreboard 클래스이다. monitor에서 받은 트랜잭션을 기준으로 기대값과 비교하여 FIFO 동작이 정확한지 검사하고 mismatch 발생 시 오류 출력한다.
class scoreboard;
mailbox #(transaction) mbx; // Mailbox for communication
transaction tr; // Transaction object for monitoring
event next;
bit [7:0] din[$]; // Array to store written data
bit [7:0] temp; // Temporary data storage
int err = 0; // Error count
function new(mailbox #(transaction) mbx);
this.mbx = mbx;
endfunction;
task run();
forever begin
mbx.get(tr);
$display("[SCO] : Wr:%0d rd:%0d din:%0d dout:%0d full:%0d empty:%0d", tr.wr, tr.rd, tr.data_in, tr.data_out, tr.full, tr.empty);
if (tr.wr == 1'b1) begin
if (tr.full == 1'b0) begin
din.push_front(tr.data_in);
$display("[SCO] : DATA STORED IN QUEUE :%0d", tr.data_in);
end
else begin
$display("[SCO] : FIFO is full");
end
$display("--------------------------------------");
end
if (tr.rd == 1'b1) begin
if (tr.empty == 1'b0) begin
temp = din.pop_back();
if (tr.data_out == temp)
$display("[SCO] : DATA MATCH");
else begin
$error("[SCO] : DATA MISMATCH");
err++;
end
end
else begin
$display("[SCO] : FIFO IS EMPTY");
end
$display("--------------------------------------");
end
-> next;
end
endtask
endclass
Environment 클래스이다. generator, driver, monitor, scoreboard를 생성하고 연결하여 전체 시뮬레이션 흐름을 구성한다. 테스트 실행 순서를 pre_test → test → post_test로 관리한다.
class environment;
generator gen;
driver drv;
monitor mon;
scoreboard sco;
mailbox #(transaction) gdmbx; // Generator + Driver mailbox
mailbox #(transaction) msmbx; // Monitor + Scoreboard mailbox
event nextgs;
virtual fifo_if fif;
function new(virtual fifo_if fif);
gdmbx = new();
gen = new(gdmbx);
drv = new(gdmbx);
msmbx = new();
mon = new(msmbx);
sco = new(msmbx);
this.fif = fif;
drv.fif = this.fif;
mon.fif = this.fif;
gen.next = nextgs;
sco.next = nextgs;
endfunction
task pre_test();
drv.reset();
endtask
task test();
fork
gen.run();
drv.run();
mon.run();
sco.run();
join_any
endtask
task post_test();
wait(gen.done.triggered);
$display("---------------------------------------------");
$display("Error Count :%0d", sco.err);
$display("---------------------------------------------");
$finish();
endtask
task run();
pre_test();
test();
post_test();
endtask
endclass
FIFO DUT와 testbench 환경(environment)을 연결하고 시뮬레이션을 시작하는 최상위 모듈이다. fifo_if 인터페이스를 생성하고, 이를 통해 DUT와 testbench 사이의 모든 신호를 주고받는다.
$dumpfile과 $dumpvars는 시뮬레이션 중에 Waveform을 저장하기 위한 SystemVerilog의 내장 system task이다.
- $dumpfile("파일명.vcd") : 파형을 저장할 VCD(Value Change Dump) 파일의 이름을 지정한다.
- $dumpvars : 실제 파형 저장을 시작하는 명령이다.
module tb;
fifo_if fif();
FIFO dut (fif.clock, fif.rst, fif.wr, fif.rd, fif.data_in, fif.data_out, fif.empty, fif.full);
initial begin
fif.clock <= 0;
end
always #5 fif.clock <= ~fif.clock;
environment env;
initial begin
env = new(fif);
env.gen.count = 10;
env.run();
end
initial begin
$dumpfile("dump.vcd");
$dumpvars;
end
endmodule
테스트 벤치의 실행은 EDA Playground에서 Synopsys VCS를 이용한다.
Chronologic VCS simulator copyright 1991-2023
Contains Synopsys proprietary information.
Compiler version U-2023.03-SP2_Full64; Runtime version U-2023.03-SP2_Full64; Apr 19 05:29 2025
[DRV] : DUT Reset Done
------------------------------------------
[GEN] : Oper : 0 iteration : 1
[DRV] : DATA READ
[MON] : Wr:0 rd:1 din:0 dout:0 full:0 empty:1
[SCO] : Wr:0 rd:1 din:0 dout:0 full:0 empty:1
[SCO] : FIFO IS EMPTY
--------------------------------------
[GEN] : Oper : 0 iteration : 2
[DRV] : DATA READ
[MON] : Wr:0 rd:1 din:0 dout:0 full:0 empty:1
[SCO] : Wr:0 rd:1 din:0 dout:0 full:0 empty:1
[SCO] : FIFO IS EMPTY
--------------------------------------
[GEN] : Oper : 0 iteration : 3
[DRV] : DATA READ
[MON] : Wr:0 rd:1 din:0 dout:0 full:0 empty:1
[SCO] : Wr:0 rd:1 din:0 dout:0 full:0 empty:1
[SCO] : FIFO IS EMPTY
--------------------------------------
[GEN] : Oper : 0 iteration : 4
[DRV] : DATA READ
[MON] : Wr:0 rd:1 din:0 dout:0 full:0 empty:1
[SCO] : Wr:0 rd:1 din:0 dout:0 full:0 empty:1
[SCO] : FIFO IS EMPTY
--------------------------------------
[GEN] : Oper : 1 iteration : 5
[DRV] : DATA WRITE data : 8
[MON] : Wr:1 rd:0 din:8 dout:0 full:0 empty:1
[SCO] : Wr:1 rd:0 din:8 dout:0 full:0 empty:1
[SCO] : DATA STORED IN QUEUE :8
--------------------------------------
[GEN] : Oper : 0 iteration : 6
[DRV] : DATA READ
[MON] : Wr:0 rd:1 din:8 dout:8 full:0 empty:0
[SCO] : Wr:0 rd:1 din:8 dout:8 full:0 empty:0
[SCO] : DATA MATCH
--------------------------------------
[GEN] : Oper : 1 iteration : 7
[DRV] : DATA WRITE data : 1
[MON] : Wr:1 rd:0 din:1 dout:8 full:0 empty:1
[SCO] : Wr:1 rd:0 din:1 dout:8 full:0 empty:1
[SCO] : DATA STORED IN QUEUE :1
--------------------------------------
[GEN] : Oper : 1 iteration : 8
[DRV] : DATA WRITE data : 10
[MON] : Wr:1 rd:0 din:10 dout:8 full:0 empty:0
[SCO] : Wr:1 rd:0 din:10 dout:8 full:0 empty:0
[SCO] : DATA STORED IN QUEUE :10
--------------------------------------
[GEN] : Oper : 0 iteration : 9
[DRV] : DATA READ
[MON] : Wr:0 rd:1 din:10 dout:1 full:0 empty:0
[SCO] : Wr:0 rd:1 din:10 dout:1 full:0 empty:0
[SCO] : DATA MATCH
--------------------------------------
[GEN] : Oper : 1 iteration : 10
[DRV] : DATA WRITE data : 9
[MON] : Wr:1 rd:0 din:9 dout:1 full:0 empty:0
[SCO] : Wr:1 rd:0 din:9 dout:1 full:0 empty:0
[SCO] : DATA STORED IN QUEUE :9
--------------------------------------
---------------------------------------------
Error Count :0
---------------------------------------------
$finish called from file "testbench.sv", line 250.
$finish at simulation time 345000
V C S S i m u l a t i o n R e p o r t
Time: 345000 ps
CPU Time: 0.450 seconds; Data structure size: 0.0Mb
'PROJECTS > UVM (Universal Verification Methodology)' 카테고리의 다른 글
UVM Project - 4BIT Multiplier (0) | 2025.04.19 |
---|