Tác giả: ThienPham , TrongTran.
Ngày: 08/10/2019
Có một anh bạn của mình đã hướng dẫn mình từ những ngày đầu
mình bắt tay học system Verilog và uvm.
Đây là một môi trường có đầy đủ các thành phần trong một môi
trường random test căn bản.
Tuy nhiên, trong bài này mình không sử dụng thư viện uvm mà
sử dụng chỉ ngôn ngữ System Verilog.
Hi vọng bài viết này sẽ giúp cho những bạn vừa bắt tay vào
nghiên cứu System Verilog và uvm hiểu rõ hơn.
Các bạn có thể tham khảo 1 môi trường uvm đơn giản hoàn chỉnh
trong bài viết này:
Trong bài này mình xét 1 DUT như bên dưới:
module bit_adder ( clk, rst_n, in_a, in_b, carry_in,
carry_out, sum );
input clk;
input rst_n;
input in_a;
input in_b;
input carry_in;
output carry_out;
output sum;
wire clk;
wire rst_n;
wire in_a;
wire in_b;
wire carry_in;
reg carry_out;
reg sum;
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0)
begin //reset is triggered
carry_out = 0;
sum = 0;
end else begin
carry_out <= (
in_a & in_b ) | ( carry_in & ( in_a ^ in_b ));
sum <= in_a ^ in_b ^ carry_in;
end
end
endmodule
Bên trên là bộ bit_adder. Nghĩa là cộng 2 phần tử 1 bit và biến nhớ 1 bit đầu vào lại với nhau.
Dưới đây là bộ multi_bit_adder dùng để cộng 2 phần tử nhiều 3 bit:
module multi_bit_adder (adder_if.ADDER multi_bit_adder_if);
wire carry_out_0;
wire carry_out_1;
bit_adder adder_0 (
.clk (multi_bit_adder_if.clk)
,.rst_n
(multi_bit_adder_if.adder.rst_n)
,.in_a
(multi_bit_adder_if.adder.in_a[0])
,.in_b
(multi_bit_adder_if.adder.in_b[0])
,.carry_in (multi_bit_adder_if.adder.carry_in)
,.carry_out (carry_out_0)
,.sum
(multi_bit_adder_if.sum[0]) );
bit_adder adder_1 (
.clk
(multi_bit_adder_if.clk)
,.rst_n
(multi_bit_adder_if.adder.rst_n)
,.in_a
(multi_bit_adder_if.adder.in_a[1])
,.in_b
(multi_bit_adder_if.adder.in_b[1])
,.carry_in (carry_out_0)
,.carry_out (carry_out_1)
,.sum
(multi_bit_adder_if.sum[1]) );
bit_adder adder_2 (
.clk
(multi_bit_adder_if.clk)
,.rst_n
(multi_bit_adder_if.adder.rst_n)
,.in_a (multi_bit_adder_if.adder.in_a[2])
,.in_b
(multi_bit_adder_if.adder.in_b[2])
,.carry_in (carry_out_1)
,.carry_out (multi_bit_adder_if.carry_out)
,.sum (multi_bit_adder_if.sum[2])
);
endmodule
Với đoạn code bên trên chúng ta đang tiến hành cộng 2 số 3
bit. Kết quả là 1 số 3 bit và 1 biến nhớ.
Các bạn có thắc mắc cấu trúc này không:
adder_if.ADDER multi_bit_adder_if
Không có gì khó hiểu cả. Chúng ta đang tiến hành connect bộ
“multi_bit_adder” và interface. Điều này giúp chúng ta giảm 1 thao tác để connect trong khối top module.
Khối interface:
interface adder_if (input wire clk);
wire [2:0]
in_a;
wire [2:0]
in_b;
wire carry_in;
wire rst_n;
logic [2:0] sum;
logic carry_out;
clocking adder @
(posedge clk);
input in_a;
input in_b;
input carry_in;
input rst_n;
endclocking
clocking testbench @
(posedge clk);
input sum;
input carry_out;
output in_a;
output in_b;
output carry_in;
output rst_n;
endclocking
clocking assertion @
(posedge clk);
input sum;
input carry_out;
endclocking
modport ADDER (clocking adder, input clk, output sum,
output carry_out);
modport TESTBENCH
(clocking testbench, input clk);
modport ASSERTION
(clocking assertion
,input clk
,input rst_n
,input in_a
,input in_b
,input carry_in);
endinterface
Khối transaction:
`ifndef ADDER_BASE_OBJECT_SV
`define ADDER_BASE_OBJECT_SV
class adder_base_object;
rand bit [2:0] in_a;
rand bit [2:0] in_b;
rand bit carry_in;
bit carry_out;
bit [2:0] sum;
bit rst_n = 1;
constraint legal {
in_a dist {
[0 :
7 ] := 100 };
in_b dist {
[0 :
7 ] := 100 };
}
endclass
`endif
Class adder_base_object tương tự như class transaction trong
môi trường UVM.
Chức năng khối này là khai báo các input/output giao tiếp giữa khối sequence và DUT. Hay nói đúng hơn đó là các cổng input/output của DUT.
Với những biến input ta sẽ khai báo bởi các key word: “rand”
kèm theo các constraint cho việc random đó. Còn với output bạn định nghĩa kiểu "bit".
Khối configuration:
class Config;
integer a;
integer b;
constraint
reasonable {
a inside {[0:7]};
b inside {[0:7]};
}
endclass
Thật ra khối này không quan trọng và cũng không thật sự cần
trong môi trường UVM. Khối config nhằm giúp cho người verifier dễ dàng chỉnh sữa các constraint hay
thông số đầu vào của khối transaction mà không làm sữa đổi khối transaction. Với một môi trường UVM phức tạp thì bộ input rất lớn và có những constraint vô cùng phức tạp. Khi chúng ta cần check một case cụ thể ta thường sữa trong khối config.
Khối generator:
`ifndef ADDER_TXGEN_SV
`define ADDER_TXGEN_SV
class Generator;
adder_base_object
adder_object;
mailbox gen2agt;
function new(mailbox
gen2agt);
// this.gen2agt =
new(); //if use this line. this mail is discarded.
this.gen2agt =
gen2agt;
endfunction
task run(int count);
repeat (count)
begin
adder_object =
new();
assert(adder_object.randomize);
gen2agt.put(adder_object);
end
endtask
endclass
`endif
Khối này nhằm mục đích tạo ra các tín hiệu random gửi vào DUT. Chúng ta có rất nhiều cách để tạo ra 1 tín hiệu random.
Mình lấy ví dụ trong môi trường UVM ta có:
- uvm_do
- uvm_do_with
- uvm_do_on_with
- ...
Ngoài ra còn có một số cách khác như là:
Dùng cập:
start_item(a);
- void'(std::randomize(a));
Dùng cập:
start_item(a);
assert (a.randomize ());
finish_item(a);
Trong trường hợp trên mình dùng câu lệnh:
- assert(adder_object.randomize);
Khối driver:
`ifndef ADDER_DRIVER_SV
`define ADDER_DRIVER_SV
class Driver;
adder_base_object adder_object;
mailbox agt2drv;
virtual adder_if.TESTBENCH test_port;
function new(mailbox agt2drv, virtual adder_if.TESTBENCH test_port);
this.agt2drv = agt2drv;
this.test_port = test_port;
endfunction
task run(int count);
repeat (count) begin
agt2drv.get(adder_object);
test_port.testbench.in_a <= adder_object.in_a;
test_port.testbench.in_b <= adder_object.in_b;
test_port.testbench.carry_in <= adder_object.carry_in;
test_port.testbench.rst_n <= adder_object.rst_n;
@(posedge test_port.clk);
@(posedge test_port.clk);
@(posedge test_port.clk);
@(negedge test_port.clk);
$write("--------------------------- New Test Data----- --------------------\n");
$write("@%4d clk: %0d rst: %0d a: %0d b: %0d carry_in: %x sum: %0d carry_out: %x\n"
, $time
, test_port.clk
, test_port.testbench.rst_n
, test_port.testbench.in_a
, test_port.testbench.in_b
, test_port.testbench.carry_in
, test_port.testbench.sum
, test_port.testbench.carry_out);
end
@(posedge test_port.clk);
@(posedge test_port.clk);
@(posedge test_port.clk);
@(negedge test_port.clk);
$finish;
endtask
endclass
`endif
Trong đoạn code trên, mình đơn giản là control các tín hiệu in_a, in_b, rst_n, carry_in từ khối generator bên trên để gửi vào DUT.
Số lần lập bằng số "count" khai báo vào. Đồng thời, Sau 3 chu kì sau khi gửi input vào DUT ta sẽ tiến hành dump giá trị ra log file để quan sát. tiếp đó ta đợi 3 chu kì tiếp theo để gửi tiếp tín hiệu vào DUT.
Khối monitor:
`ifndef ADDER_MONITOR_SV
`define ADDER_MONITOR_SV
class Monitor;
adder_base_object adder_object;
mailbox mon2chk;
virtual adder_if.TESTBENCH test_port;
integer ck = 0;
function new(mailbox mon2chk, virtual adder_if.TESTBENCH test_port);
this.mon2chk = mon2chk;
this.test_port = test_port;
endfunction
task run();
forever begin
@ (posedge test_port.clk);
ck++;
if ( ck == 3 ) begin
@ (negedge test_port.clk);
ck = 0;
adder_object = new();
$display("@%4d sum: %0d carry_out: %x", $time, test_port.testbench.sum, test_port.testbench.carry_out);
adder_object.sum = test_port.testbench.sum;
adder_object.carry_out = test_port.testbench.carry_out;
mon2chk.put(adder_object);
end
end
endtask
endclass
`endif
Đây là một khối thụ động tức là không điều khiển tín hiệu mà chỉ thực hiện chức năng quan sát các output được gửi từ DUT ra ngoài.
Khối Scoreboard:
`ifndef ADDER_SCOREBOARD_SV
`define ADDER_SCOREBOARD_SV
class Scoreboard;
adder_base_object adder_object_in, adder_object_out;
mailbox mon2chk;
mailbox agt2scb;
integer real_sum;
bit [2:0] expect_sum;
bit expect_carry_out;
bit flag = 0;
function new(mailbox mon2chk, agt2scb);
this.mon2chk = mon2chk;
this.agt2scb = agt2scb;
endfunction
task run();
forever begin
adder_object_in = new();
adder_object_out = new();
mon2chk.get(adder_object_out);
agt2scb.get(adder_object_in);
if (flag == 0) begin
flag = 1;
if (adder_object_in.rst_n == 0) begin
expect_sum = 0;
expect_carry_out = 0;
end
else begin
expect_sum = adder_object_in.in_a + adder_object_in.in_b + adder_object_in.carry_in;
real_sum = adder_object_in.in_a + adder_object_in.in_b + adder_object_in.carry_in;
if (real_sum > 7) begin
expect_carry_out = 1;
end
else begin
expect_carry_out = 0;
end
end
end
else begin
if ( (expect_sum != adder_object_out.sum) || (expect_carry_out != adder_object_out.carry_out) ) begin
$display("An error occurs at @%4d: expect sum: %0d sum: %0d expect carry out: %0d carry out: %0d", $time
, expect_sum
, adder_object_out.sum
, expect_carry_out
, adder_object_out.carry_out);
end
else begin
$display("PASSED a data test case");
end
if (adder_object_in.rst_n == 0) begin
expect_sum = 0;
expect_carry_out = 0;
end
else begin
expect_sum = adder_object_in.in_a + adder_object_in.in_b + adder_object_in.carry_in;
real_sum = adder_object_in.in_a + adder_object_in.in_b + adder_object_in.carry_in;
if (real_sum > 7) begin
expect_carry_out = 1;
end
else begin
expect_carry_out = 0;
end
end
end
end
endtask
endclass
`endif
Khối này có vẻ hơi dài nhỉ. Đơn giản là lấy tín hiệu output được gửi từ DUT -> Monitor và đồng thời lấy tín hiệu input (Gửi từ khối driver đến interface) từ inteface để tính toán kết quả mong muốn. Đem kết quả đó so sánh với kết quả nhận được từ khối monitor và in ra màn hình kết quả so sánh.
Khối testbench top:
`include "Adder_Interface.sv";
program test(adder_if.TESTBENCH test_port);
`include "Base_Object.sv"
`include "Generator.sv"
`include "Driver.sv"
`include "Agent.sv"
`include "Monitor.sv"
`include "Scoreboard.sv"
`include "Testbench_Environment.sv"
TB_environment env;
initial begin
env = new();
env.gen_cfg();
env.build();
env.run();
env.wrap_up();
end
endprogram
Khối này là gọi các function thôi. hông có gì nhiều.
Khối env:
`ifndef ADDER_TESTBENCH_ENVIRONMENT_SV
`define ADDER_TESTBENCH_ENVIRONMENT_SV
class TB_environment;
Generator gen;
Agent agt;
Driver drv;
Monitor mon;
Scoreboard scb;
mailbox gen2agt, agt2drv, agt2scb, mon2chk;
extern function new();
extern function void gen_cfg();
extern function void build();
extern task run();
extern task wrap_up();
endclass
function TB_environment::new();
//cfg = new();
endfunction
function void TB_environment::gen_cfg;
// assert(cfg.randomize);
endfunction
function void TB_environment::build();
// Initialize mailboxes
gen2agt = new();
agt2drv = new();
mon2chk = new();
agt2scb = new();
// Initialize transactors
gen = new(gen2agt);
agt = new(gen2agt, agt2drv, agt2scb);
drv = new(agt2drv, test_port);
mon = new(mon2chk, test_port);
scb = new(mon2chk, agt2scb);
endfunction
task TB_environment::run();
fork
gen.run(200);
agt.run();
drv.run(200);
mon.run();
scb.run();
join
endtask
task TB_environment::wrap_up();
fork
// gen.wrap_up();
// agt.wrap_up();
// drv.wrap_up();
// mon.wrap_up();
// chk.wrap_up();
// scb.wrap_up();
join
endtask
`endif
Mình đã hướng dẫn cách dùng Ncsim và VCS online miễn phí trong các bài trước. Các bạn tham khảo và chạy kiểm tra kết quả nhé.
No comments:
Post a Comment