Tuesday, October 8, 2019

[UVM] Mô phỏng môi trường UVM đầy đủ thành phần chỉ sử dụng ngôn ngữ System Verilog (Không dùng thư viện uvm)



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 này mình đã nói đến trong những bái trước đây các bạn có thể tham khảo lại nhé.

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à:

  • 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

Cách tính BW và latency trong 1 hệ thống SoC sử dụng chuẩn giao tiếp AXI protocol

Tác giả:  TrongTran Ngày:  31/12/2019 Nếu bạn nào đang làm về verification cho system performance (ST) thì bài này sẽ bổ ích cho bạn. Ngày ...