[하만]세미콘 아카데미/verilog

0618 UVM

내머리속은이런걸로 2024. 7. 10. 12:04

UVM (Universal Verification Methodology)

: 범용 검증 방법, 반도체 산업에서 복잡한 ASIC 및 FPGA 설계 검증에 사용된다.

  • 클래스 기반 구조 : 검증 형식을 미리 만들어놓고 그 형식에 맞춰 동작시킨다.
  • 테스트벤치 계층화  : 여러 계층으로 구성되어 있으며 특정 작업을 수행한다. 
    • agent, driver, sequencer, monitor 등이 있다.
  • 디버깅 : 에러를 조기에 발견하고 수정하여 제품의 신회성을 높이며, 시간을 단축시킬 수 있다

→ Class 객체지향(OOP) 기능을 사용하여 SystemVerilog를 사용한다.

( SystemVerilog는 verlog, c++, Java 언어 영향을 받은 언어이다. )

 

UVM Interface & class

  • Interface : Testbench와 DUT 사이의 신호를 연결한다.
  • Sequence Item : driver와 monitor가 데이터를 주고 받을 수 있는 데이터 구조를 제공한다. 제약 조건과 랜덤화를 거쳐 데이터를 생성한다.
  • Sequence : 반복적으로 Sequence Item을 생성하고 driver에 전달함으로써 많은 변수를 DUT에서 테스트할 수 있다.
  • Monitor : DUT의 출력을 모니터링하여 Sequence Item 형태로 scoreboard에 전달하여 검증한다.
  • Agent : Testbench의 구성 요소(driver, monitor, sequence)를 하나로 묶는다.
  • Environment : Testbench의 전체 환경을 구성하여 각 요소를 연결한다.
  • Test : Testbench의 최상위 수준에서 테스트를 시작하고 관리한다.
  • Testbench Module : UVM testbench를 초기화하고 시뮬레이션을 실행한다.

OOP

: 객체 지향 프로그래밍

  • 캡슐화(class) : 데이터와 데이터를 조작하는 코드를 하나로 묶는 것이다.
  • 다형성 : 같은 이름의 메소드가 다양한 방식으로 작동할 수 있도록 한다.
  • 상속 : 한 클래스의 특성을 다른 클래스가 물려받을 수 있다. 코드 재사용이 가능하다.
    • 확장성, 기존 기능을 포함한 다른 기능을 추가할 수 있다. (extend)
  • 추상화 : 복잡한 실제 상황을 단순화하여 모델링한다. 필수적인 정보만 이용하여 인터페이스를 제공한다.

 

→ Factory Method Pattern

: 객체를 생성하는 코드와 응용 프로그램의 나머지 부분을 분리한다.

객체를 생성하는 인터페이스를 정의하고, 어떤 클래스의 인스턴스를 생성할지에 대한 결정은 서브클래스가 내린다. 

기반 클래스의 코드 변경 없이 새로운 클래스의 인스턴스를 생성할 수 있다.

 

ex)

class uvm_driver // 부모 class, super class

{

   기본 기능 코드

}

 

class my_driver extend uvm_driver // sub class

{

  실행 코드 구현, my_driver class를 uvm framework에 등록해야한다.

}

 


기본 simulation 코드

adder.sv

`timescale 1ns / 1ps

module tb_Adder ();

    logic [31:0] a;
    logic [31:0] b;
    logic [31:0] result;

    adder dut(
        .a(a),
        .b(b),
        .result(result)
    );

    initial begin
        a = 0; b= 0;
        #10 a = 10; b= 20;
        #10 a = 100; b= 120;
        #10 a = 200; b=220;
        #10 $finish;
    end
endmodule

 

simulation

 


UVM Simulation 설정 (1, 2 모두 해야함)

  1. Setting - Simulation - Compilation - xsim.compile.xvlog.more_options : -L uvm
  2. Setting - Simulation - Elaboration - xsim.elaboration.xelab.more_options : -L uvm

 

xsim.elaboration.xelab.more_options : -L uvm

 


adder.sv

`timescale 1ns / 1ps

module adder (
    input  logic [31:0] a,
    input  logic [31:0] b,
    output logic [31:0] result
);

    assign result = a + b;
endmodule


tb_Adder.sv

`timescale 1ns / 1ps
`include "uvm_macros.svh"
import uvm_pkg::*;

module tb_Adder ();

    logic [31:0] a;
    logic [31:0] b;
    logic [31:0] result;

    adder dut (
        .a(a),
        .b(b),
        .result(result)
    );

    initial begin
        `uvm_info("Test 1", "Hello World", UVM_NONE);
    end
endmodule

 

simulation

 


adder.sv

`timescale 1ns / 1ps

module adder (
    input  logic [31:0] a,
    input  logic [31:0] b,
    output logic [31:0] result
);

    assign result = a + b;
endmodule

 

tb_Adder.sv

`timescale 1ns / 1ps
`include "uvm_macros.svh"
import uvm_pkg::*;

interface adder_interface;
    logic [31:0] a;
    logic [31:0] b;
    logic [31:0] result;
endinterface //adder_interface

class seq_item extends uvm_sequence_item;  // super class 상속
    rand bit [31:0] a;
    rand bit [31:0] b;
    bit [31:0] result;
    
    constraint adder_c {a<100;b<100;}

    function new(input string name = "seq_item");   // seq_item : string name
        super.new(name); // 부모 class
    endfunction //new()

    // uvm 등록 (field 값으로 uvm 등록)
    `uvm_object_utils_begin(seq_item)   // seq_item : class name
        `uvm_field_int(a, UVM_DEFAULT)
        `uvm_field_int(b, UVM_DEFAULT)
        `uvm_field_int(result, UVM_DEFAULT)
    `uvm_object_utils_end

endclass //seq_item extend uvm_sequence_item

class adder_sequence extends uvm_sequence; // generator
    `uvm_object_utils(adder_sequence)   // add uvm factory, adder_sequence : class name

    seq_item adder_seq_item;

    function new(input string name = "adder_sequence");
        super.new(name);        
    endfunction //new()

    virtual task body(); // virtual : 다형성 기능(subclass)
        adder_seq_item = seq_item::type_id::create("SEQ_ITEM"); // create instance
        repeat(1000) begin
            start_item(adder_seq_item);
            adder_seq_item.randomize();
            `uvm_info("SEQ", "Data send to Driver", UVM_NONE);
            finish_item(adder_seq_item);
        end
    endtask
endclass // extends superClass

class adder_driver extends uvm_driver #(seq_item);
    `uvm_component_utils(adder_driver) // add factory
    
    virtual adder_interface adderIntf;
    seq_item adder_seq_item;
    
    function new(input string name = "adder_driver", uvm_component c);
        super.new(name, c);
    endfunction //new()

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        adder_seq_item = seq_item::type_id::create("SEQ_ITEM", this);
        if (!uvm_config_db#(virtual adder_interface)::get(this, "", "adderIntf", adderIntf)) begin
            `uvm_fatal(get_name(), "Unable to access adder interface");
        end
    endfunction

    virtual function void start_of_simulation_phase(uvm_phase phase);
        $display("display start of simulation phase!");
    endfunction

    virtual task run_phase(uvm_phase phase);
        $display("display run phase!");
        forever begin
            #10;
            seq_item_port.get_next_item(adder_seq_item);
                adderIntf.a = adder_seq_item.a;
                adderIntf.b = adder_seq_item.b;
                `uvm_info("DRV", "Send data to DUT", UVM_NONE);
            seq_item_port.item_done();
        end
    endtask

endclass //adder_driver extends uvm_driver #(seq_item)

class adder_monitor extends uvm_monitor;
    `uvm_component_utils(adder_monitor) // add factory

    uvm_analysis_port #(seq_item) send;
    virtual adder_interface adderIntf;
    seq_item adder_seq_item;

    function new(input string name = "adder_monitor", uvm_component c);
        super.new(name, c);
        send = new("WRITE", this);
    endfunction //new()

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        adder_seq_item = seq_item::type_id::create("SEQ_ITEM", this);
        if (!uvm_config_db#(virtual adder_interface)::get(this, "", "adderIntf", adderIntf)) begin
            `uvm_fatal(get_name(), "Unable to access adder interface");
        end
    endfunction

    virtual task run_phase(uvm_phase phase);
        forever begin
            #10;
            adder_seq_item.a = adderIntf.a;
            adder_seq_item.b = adderIntf.b;
            adder_seq_item.result = adderIntf.result;
            `uvm_info("MON", "send data to Scoreboard", UVM_NONE);
            send.write(adder_seq_item);
        end
    endtask
endclass //adder_monitor extends uvm_monitor

class adder_scoreboard extends uvm_scoreboard;
    `uvm_component_utils(adder_scoreboard)

    uvm_analysis_imp #(seq_item, adder_scoreboard) recv;

    function new(input string name = "adder_scoreboard", uvm_component c);
        super.new(name, c);
        recv = new("READ", this);
    endfunction

    virtual function void write(seq_item data);
        `uvm_info("SCB", "Data received from Monitor", UVM_NONE);

        if((data.a + data.b) == data.result) begin
            `uvm_info("SCB", $sformatf("Pass!, %d + %d = %d", data.a, data.b, data.result), UVM_NONE);
        end
        else begin
            `uvm_error("SCB", $sformatf("Fail!, %d + %d = %d", data.a, data.b, data.result));
        end
        data.print(uvm_default_line_printer);
    endfunction
endclass

class adder_agent extends uvm_agent;
    `uvm_component_utils(adder_agent)

    function new(input string name = "adder_agent", uvm_component c);
        super.new(name, c);
    endfunction //new()

    adder_monitor adderMonitor;
    adder_driver adderDriver;
    uvm_sequencer #(seq_item) adderSequencer;

    virtual function void build_phase(uvm_phase phase); // make instance
        super.build_phase(phase);
        adderMonitor = adder_monitor::type_id::create("MON", this);
        adderDriver = adder_driver::type_id::create("DRV", this);
        adderSequencer = uvm_sequencer#(seq_item)::type_id::create("SQR", this);
    endfunction

    virtual function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        adderDriver.seq_item_port.connect(adderSequencer.seq_item_export);
    endfunction

endclass //adder_agent extends uvm_agent

class adder_env extends uvm_env;
    `uvm_component_utils(adder_env)

    function new(input string name = "adder_env", uvm_component c);
        super.new(name, c);
    endfunction //new()

    adder_scoreboard adderScoreboard;
    adder_agent adderAgent;

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        adderScoreboard = adder_scoreboard::type_id::create("SCB", this);
        adderAgent = adder_agent:: type_id::create("AGENT", this);  // this : use in this class
    endfunction

    virtual function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        adderAgent.adderMonitor.send.connect(adderScoreboard.recv);
    endfunction
endclass //adder extends uvm_env

class adder_test extends uvm_test;
    `uvm_component_utils(adder_test);

    function new(input string name = "adder_test", uvm_component c);
        super.new(name, c);
    endfunction //new()

    adder_sequence adderSequence;
    adder_env adderEnv;

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        adderSequence = adder_sequence::type_id::create("SEQ", this);
        adderEnv = adder_env::type_id::create("ENV", this);
    endfunction

    virtual task run_phase(uvm_phase phase);
        phase.raise_objection(this);
        adderSequence.start(adderEnv.adderAgent.adderSequencer);
        phase.drop_objection(this);
    endtask
endclass //adder_test extends uvm_test

module tb_Adder ();

    adder_interface adderIntf();
    adder_test adderTest;

    adder dut (
        .a(adderIntf.a),
        .b(adderIntf.b),
        .result(adderIntf.result)
    );

    initial begin
        adderTest = new("Adder UVM Verification", null); 
        uvm_config_db#(virtual adder_interface)::set(null, "*", "adderIntf", adderIntf);
        run_test();
    end
endmodule

 

simulation


코드 분석

sequence & driver 분석

sequence

driver

  1. sequence에서 start_item을 하면서 sequencer에게 adder_seq_item을 만든다고 해당 동작 신호를 보내고 대기한다.
  2. Driver에서 seq_item를 얻기 위해 get_next_item을 통해 해당 동작 신호를 보내고 대기한다.
  3. sequencer에서 대기하고 있는 sequence가 해당 신호를 받고 돌아온다.
  4. sequence에서 adder_seq_item을 랜덤화하여 생성한다.
  5. sequence에서 adder_seq_item을 랜덤화한 것을 마친다.
  6. sequence가 adder_seq_item을 sequencer에게 전달하고 대기한다.
  7. sequencer에서 대기하고 있던 driver가 랜덤화된 adder_seq_item 값을 얻어 돌아온다.
  8. driver가 adderIntf에게 adder_seq_item의 input a, b 값을 전달한다.
  9. driver가 input 값을 모두 전달했다고 sequencer에게 신호를 보낸다.
  10. sequencer에서 대기하고 있던 sequence가 돌아온다.
  11. 이 과정을 repeat 안의 매개변수만큼 반복한다.

 

[Harman] 노진호 교수님 필서

 

class adder_sequence_item

: 테스트에 사용될 데이터를 정의한다.

  1. 'seq_item' 클래스는 'uvm_seq_item' 클래스를 상속받는다.
  2. 'seq_item' 이름인 인스턴스를 만든다. 이 때 uvm_sequence_item을 호출하여 초기화를 실행한다.
  3. `uvm_object_utils_begin과 ` uvm_object_utils_end를 하여 클래스와 필드를 등록한다.
    • 필드는 자동화 기능(복사, 비교 등)을 사용할 때에만 사용된다. → `uvm_object_utils가 아닌 이유
  4. 인스턴스를 uvm 객체에 등록을 하여, uvm의 자동화 기능을 사용할 수 있게 한다.
    • UVM_DEFAULT : 필드에 대해 수행할 기본 동작을 정의한다.

 

class adder_sequence

: sequence를 정의하며, 'seq_item'을 생성하고 driver에 전달한다.

  1. `uvm_object_utils : uvm_sequence 생성자를 호출하여 uvm 팩토리에 저장, 초기화를 한다.
  2. virtual task body() : virtual를 통하여 다형성을 실행한다. 동적 바인딩(Dynamic Binding)을 하여 프로그램 실행 중에 호출할 메소드가 결정된다.
  3. task body() : sequence가 실행될 때 수행해야할 transaction의 생성, 전송, 제어 등을 구현한다. 
    • randomize()를 함으로써 시뮬레이션을 하며 복잡한 동작을 수행하기 때문에 task를 사용한다.
  4. seq_item 객체를 생성하여 'SEQ_ITEM'이라는 인스턴스 이름을 붙여준다.
  5. 드라이버가 adder_seq_item을 받아 위에서 설명한 sequence & driver의 동작 처리 순서대로 정보를 처리하고 종료한다.
  • virtual function : 주로 짧고 빠르게 실행되는 작업을 수행한다. uvm 환경에서 상태를 읽거나 단순한 계산과 같은 설정이나 상태 확인, 간단한 데이터를 처리할 때 사용된다.
  • virtual task : 시뮬레이션 시간 동안 실행되는 복잡한 동작을 수행하는데 사용된다. 시뮬레이션이 시간 경과에 영향을 미치는 작업을 한다.

class adder_driver

: sequence로 item을 받아 DUT에 전달한다.

  1. class ~ #(seq_item) : seq_item을 generic 클래스로 설계하여, 어떤 타입의 sequence item이든 처리할 수 있도록 한다.
    • generic 클래스 : 여러 데이터 타입을 처리할 수 있도록 설계된 클래스이다.
  2. `uvm_component_utils : 팩토리에 클래스를 등록하여 인스턴스화를 한다.
  3. super.new(name, c)를 하여 부모 클래스의 생성자를 호출하여 초기화를 수행한다. (c: 부모 컴포넌트)
  4. build_phase로 빌드 페이즈를 호출하여 기본 설정을 수행한다. seq_item의 type_id 중 create 기능을 수행하여, 이 클래스에서 "SEQ_ITEM"의 이름으로 adder_seq_item을 만든다.
  5. uvm_config_db#(virtual adder_interface)::get(this, "", "adderIntf", adderIntf):
    • 데이터베이스에서 adderIntf라는 이름을 설정, 검색하여 adderIntf에 저장한다.
  6. start_of_simulation_pahse로 시뮬레이션 시작 메세지를 나타낸다.
  7. run_phase로 시뮬레이션 시작 메세지를 나타낸다.
  8. sequence로부터 adder_seq_item을 가져오고, 가상 인터페이스에 할당한다. 데이터를 DUT에 전달했다는 메세지를 출력하고, 처리가 완료되었음을 알린다. 

- get(this, "", "adderIntf", adderIntf) : (현재 객체, 경로("" : 최상위 수준), 변수 이름, 값을 할당할 변수)

type key vlaue
virtual adder_interface "adderIntf" adderIntf

 

class adder_monitor

: DUT의 출력을 모니터링하고, 수집된 데이터를 sequence_item으로 변환하여 scoreboard에 전달한다.

  1. adder_monitor을 팩토리에 저장한다.
  2. handler를 생성한다. 
    1. `uvm_analysis_port : 데이터 스트림을 다른 컴포넌트로 전달하는데 사용된다.
    2. #(seq_item) : 이 포트가 'seq_item'타입의 데이터를 전송할 것이다.
    3. send : 포트의 인스턴스 이름이다.
  3. send 포트의 인스턴스를 생성하고, WRITE 이름을 지정한다. 현재 객체를 참조하여, adder_monitor 인스턴스에 속한다.
  4. seq_item의 객체를 생성하여 adder_seq_item을 만든다.
  5. dut에서 나온 adder_interface의 정보를 얻을 수 있는지 확인한다.
  6. adderIntf에 저장되어 있는 a, b, result 값을 adder_seq_item에 저장한다.
  7. adder_seq_item 데이터를 send 포트를 통해 전송한다.
type key vlaue
virtual adder_interface "adderIntf" adderIntf

 

class adder_scoreboard

: monitor로부터 수신된 데이터를 검증하고, 결과를 scoreboard에 기록한다.

  1. adder_scoreboard 클래스는 uvm_scoreboard를 상속받는다.
  2. uvm_component_utils는 이 클래스를 uvm 컴포넌트로 등록, uvm factory를 통해 객체를 생성할 수 있도록 한다.
  3. uvm_analysis_imp #(seq_item, adder_scoreboard) recv : uvm에서 데이터 전달을 위한 포트이다.
    • 제너릭 파라미터로 'seq_item'(전달 될 데이터 타입)과 'adder_scoreboard'(this class)를 지정한다.
    • uvm_analysis_imp : 데이터를 수신한다. → uvm_analysis_port로 데이터를 받는다.
  4. uvm_component c를 통해 부모 클래스인 'uvm_scoreboard_의 생성자를 호출하여 초기화를 수행한다.
  5. recv = new("READ", this)로 recv를 초기화한다. 모니터로부터 데이터를 수신하는 역할을 한다.
  6. virtual function void write(seq_item data); : 다형성 지원 write 함수며, 반환값을 가지지 않는다.

 

class adder_agent

: driver, monitor, sequence를 포함한다.

  1. uvm_agent 클래스를 상속한다.
  2. `uvm_component_utils를 통해 uvm factory에 이 클래스를 등록한다. 테스트벤치에서 이 클래스를 인스턴스할 수 있다.
  3. uvm_agnet의 생성자를 호출하여 객체 이름과 부모 컴포넌트를 설정한다.
  4. build_phase로 uvm에서 컴포넌트를 생성하고 초기화한다.
    1. super.build_phase(phase): 부모 클래스의 build_phase 함수를 호출한다.
    2. adder_monitor::type_id::create("MON", this): 모니터를 인스턴스화하고 MON이라는 이름을 지정한다.
    3. adder_driver::type_id::create("DRV", this): 드라이버를 인스턴스화하고 DRV라는 이름을 지정한다.
    4. uvm_sequencer#(seq_item)::type_id::create("SQR", this) : sequencer를 인스턴스화하고 SQR이라는 이름을 지정한다. 
  5. connect_phase : uvm에서 컴포넌트 간의 연결을 설정한다.
    1. super.connect_phase(phase) : 부모 클래스의 connect_phase를 호출한다.
    2. adderDriver.seq_item_port.connect(adderSequencer.seq_item_export); : driver의 sequence item port를 sequencer의 seq_item export에 연결한다. sequencer에서 생성된 seq_item이 드라이버로 전달될 수 있도록 한다.
  6. 모니터 & 스코어 보드의 uvm_analysis_port & uvm_analysis_imp : 데이터를 전달하고 검증한다.
  7. 드라이버 & 시퀀서의 uvm_seq_item_port & uvm_seq_item_export : 시퀀스 아이템을 주고 받는다.

class adder_env

: agent와 scoreboard를 포함하는  환경을 구축한다.

  1. uvm_env는 UVM 환경 클래스의 기본 클래스이다.
  2. ...
  3. build_phase에서 모든 컴포넌트를 생성한다.
  4. super.build_phase(phase) L 부모 클래스의 build_phase를 실행한다.
  5. adder_scoreboard와 adder_agent의 인스턴스를 생성한다.
  6. virtual functinon void connect_phase(umv_phase phase) :
    1. connect_phase 함수가 호출된다. 모든 포트와 임펙트가 연결된다. 
    2. super.connect_pahse(phase)를 호출하여 부모 클래스의 connect_pahse 함수를 실행한다.
    3. adderMonitor의 send 포트를 adderScoreboardㅇ의 recv 포트에 연결한다. 모니터에서 스코어보드로 데이터가 전달된다.

class adder_test

: 전체 테스트를 실행한다.

  1. ...
  2. vuild_phase가 uvm 빌드 단계에서 호출된다. 모든 컴포넌트가 생성된다. 
  3. super.build_phase(phase)를 호출하여 build_phase 함수를 실행한다.
  4. adder_sequence와 adder_env 인스턴스를 생성한다.
  5. virtual task run_phase(uvm_phase phase): uvm의 런 단계에서 호출된다. 테스트가 실제로 실행된다.
  6. phase.raise_objection(this) : 테스트가 끝나기 전까지 시뮬레이션이 종료되지 않는다.
  7. adderSequence.start(adderEnv.adderAgent.adderSequencer)를 호출하여시퀀스를 시작한다.
    • adderEnv의 adderAgent 내의 adderSequencer에서 adderSeuqence가 실행된다.
  8. phase.drop_objection(this): 테스트가 끝났음을 알린다.

 

tb_Adder

  1. adder_interface adderIntf : 물리적인 interface 생성, adder 모듈과 test bench 간의 신호 연결을 제공한다.
  2. adder_test adderTest : adderTest 실체화하여 uvm 테스트 클래스로, 실제 테스트를 수행한다.
  3. adderTest = new(~) : adder_test 클래스의 인스턴스를 생성한다.
  4. `uvm_config_db#(virtual adder_interface)::set(null, "*", "adderIntf", adderIntf);
    • uvm_config_db를 사용하여 uvm 환경 내에서 adderIntf 인터페이스 인스턴스를 설정한다.
    • null  : 모든 컴포넌트에 대해 설정한다.
    • adderIntf : 실제 인터페이스 인스턴스이다.
    • virtual adder_interface : 자료형, set(null, "*", key, value);
type key vlaue
virtual adder_interface "adderIntf" adderIntf

'[하만]세미콘 아카데미 > verilog' 카테고리의 다른 글

0619 uvm_ram  (0) 2024.07.10
0618 UVM_Adder_HW  (0) 2024.07.10
0617 AXI_memory  (0) 2024.07.10
0614 AMBA AXI BUS  (0) 2024.07.10
0604 RISC-v GPO GPI GPIO  (0) 2024.07.10