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 모두 해야함)
- Setting - Simulation - Compilation - xsim.compile.xvlog.more_options : -L uvm
- Setting - Simulation - Elaboration - 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
- sequence에서 start_item을 하면서 sequencer에게 adder_seq_item을 만든다고 해당 동작 신호를 보내고 대기한다.
- Driver에서 seq_item를 얻기 위해 get_next_item을 통해 해당 동작 신호를 보내고 대기한다.
- sequencer에서 대기하고 있는 sequence가 해당 신호를 받고 돌아온다.
- sequence에서 adder_seq_item을 랜덤화하여 생성한다.
- sequence에서 adder_seq_item을 랜덤화한 것을 마친다.
- sequence가 adder_seq_item을 sequencer에게 전달하고 대기한다.
- sequencer에서 대기하고 있던 driver가 랜덤화된 adder_seq_item 값을 얻어 돌아온다.
- driver가 adderIntf에게 adder_seq_item의 input a, b 값을 전달한다.
- driver가 input 값을 모두 전달했다고 sequencer에게 신호를 보낸다.
- sequencer에서 대기하고 있던 sequence가 돌아온다.
- 이 과정을 repeat 안의 매개변수만큼 반복한다.
class adder_sequence_item
: 테스트에 사용될 데이터를 정의한다.
- 'seq_item' 클래스는 'uvm_seq_item' 클래스를 상속받는다.
- 'seq_item' 이름인 인스턴스를 만든다. 이 때 uvm_sequence_item을 호출하여 초기화를 실행한다.
- `uvm_object_utils_begin과 ` uvm_object_utils_end를 하여 클래스와 필드를 등록한다.
- 필드는 자동화 기능(복사, 비교 등)을 사용할 때에만 사용된다. → `uvm_object_utils가 아닌 이유
- 인스턴스를 uvm 객체에 등록을 하여, uvm의 자동화 기능을 사용할 수 있게 한다.
- UVM_DEFAULT : 필드에 대해 수행할 기본 동작을 정의한다.
class adder_sequence
: sequence를 정의하며, 'seq_item'을 생성하고 driver에 전달한다.
- `uvm_object_utils : uvm_sequence 생성자를 호출하여 uvm 팩토리에 저장, 초기화를 한다.
- virtual task body() : virtual를 통하여 다형성을 실행한다. 동적 바인딩(Dynamic Binding)을 하여 프로그램 실행 중에 호출할 메소드가 결정된다.
- task body() : sequence가 실행될 때 수행해야할 transaction의 생성, 전송, 제어 등을 구현한다.
- randomize()를 함으로써 시뮬레이션을 하며 복잡한 동작을 수행하기 때문에 task를 사용한다.
- seq_item 객체를 생성하여 'SEQ_ITEM'이라는 인스턴스 이름을 붙여준다.
- 드라이버가 adder_seq_item을 받아 위에서 설명한 sequence & driver의 동작 처리 순서대로 정보를 처리하고 종료한다.
- virtual function : 주로 짧고 빠르게 실행되는 작업을 수행한다. uvm 환경에서 상태를 읽거나 단순한 계산과 같은 설정이나 상태 확인, 간단한 데이터를 처리할 때 사용된다.
- virtual task : 시뮬레이션 시간 동안 실행되는 복잡한 동작을 수행하는데 사용된다. 시뮬레이션이 시간 경과에 영향을 미치는 작업을 한다.
class adder_driver
: sequence로 item을 받아 DUT에 전달한다.
- class ~ #(seq_item) : seq_item을 generic 클래스로 설계하여, 어떤 타입의 sequence item이든 처리할 수 있도록 한다.
- generic 클래스 : 여러 데이터 타입을 처리할 수 있도록 설계된 클래스이다.
- `uvm_component_utils : 팩토리에 클래스를 등록하여 인스턴스화를 한다.
- super.new(name, c)를 하여 부모 클래스의 생성자를 호출하여 초기화를 수행한다. (c: 부모 컴포넌트)
- build_phase로 빌드 페이즈를 호출하여 기본 설정을 수행한다. seq_item의 type_id 중 create 기능을 수행하여, 이 클래스에서 "SEQ_ITEM"의 이름으로 adder_seq_item을 만든다.
- uvm_config_db#(virtual adder_interface)::get(this, "", "adderIntf", adderIntf):
- 데이터베이스에서 adderIntf라는 이름을 설정, 검색하여 adderIntf에 저장한다.
- start_of_simulation_pahse로 시뮬레이션 시작 메세지를 나타낸다.
- run_phase로 시뮬레이션 시작 메세지를 나타낸다.
- sequence로부터 adder_seq_item을 가져오고, 가상 인터페이스에 할당한다. 데이터를 DUT에 전달했다는 메세지를 출력하고, 처리가 완료되었음을 알린다.
- get(this, "", "adderIntf", adderIntf) : (현재 객체, 경로("" : 최상위 수준), 변수 이름, 값을 할당할 변수)
type | key | vlaue |
virtual adder_interface | "adderIntf" | adderIntf |
class adder_monitor
: DUT의 출력을 모니터링하고, 수집된 데이터를 sequence_item으로 변환하여 scoreboard에 전달한다.
- adder_monitor을 팩토리에 저장한다.
- handler를 생성한다.
- `uvm_analysis_port : 데이터 스트림을 다른 컴포넌트로 전달하는데 사용된다.
- #(seq_item) : 이 포트가 'seq_item'타입의 데이터를 전송할 것이다.
- send : 포트의 인스턴스 이름이다.
- send 포트의 인스턴스를 생성하고, WRITE 이름을 지정한다. 현재 객체를 참조하여, adder_monitor 인스턴스에 속한다.
- seq_item의 객체를 생성하여 adder_seq_item을 만든다.
- dut에서 나온 adder_interface의 정보를 얻을 수 있는지 확인한다.
- adderIntf에 저장되어 있는 a, b, result 값을 adder_seq_item에 저장한다.
- adder_seq_item 데이터를 send 포트를 통해 전송한다.
type | key | vlaue |
virtual adder_interface | "adderIntf" | adderIntf |
class adder_scoreboard
: monitor로부터 수신된 데이터를 검증하고, 결과를 scoreboard에 기록한다.
- adder_scoreboard 클래스는 uvm_scoreboard를 상속받는다.
- uvm_component_utils는 이 클래스를 uvm 컴포넌트로 등록, uvm factory를 통해 객체를 생성할 수 있도록 한다.
- uvm_analysis_imp #(seq_item, adder_scoreboard) recv : uvm에서 데이터 전달을 위한 포트이다.
- 제너릭 파라미터로 'seq_item'(전달 될 데이터 타입)과 'adder_scoreboard'(this class)를 지정한다.
- uvm_analysis_imp : 데이터를 수신한다. → uvm_analysis_port로 데이터를 받는다.
- uvm_component c를 통해 부모 클래스인 'uvm_scoreboard_의 생성자를 호출하여 초기화를 수행한다.
- recv = new("READ", this)로 recv를 초기화한다. 모니터로부터 데이터를 수신하는 역할을 한다.
- virtual function void write(seq_item data); : 다형성 지원 write 함수며, 반환값을 가지지 않는다.
class adder_agent
: driver, monitor, sequence를 포함한다.
- uvm_agent 클래스를 상속한다.
- `uvm_component_utils를 통해 uvm factory에 이 클래스를 등록한다. 테스트벤치에서 이 클래스를 인스턴스할 수 있다.
- uvm_agnet의 생성자를 호출하여 객체 이름과 부모 컴포넌트를 설정한다.
- build_phase로 uvm에서 컴포넌트를 생성하고 초기화한다.
- super.build_phase(phase): 부모 클래스의 build_phase 함수를 호출한다.
- adder_monitor::type_id::create("MON", this): 모니터를 인스턴스화하고 MON이라는 이름을 지정한다.
- adder_driver::type_id::create("DRV", this): 드라이버를 인스턴스화하고 DRV라는 이름을 지정한다.
- uvm_sequencer#(seq_item)::type_id::create("SQR", this) : sequencer를 인스턴스화하고 SQR이라는 이름을 지정한다.
- connect_phase : uvm에서 컴포넌트 간의 연결을 설정한다.
- super.connect_phase(phase) : 부모 클래스의 connect_phase를 호출한다.
- adderDriver.seq_item_port.connect(adderSequencer.seq_item_export); : driver의 sequence item port를 sequencer의 seq_item export에 연결한다. sequencer에서 생성된 seq_item이 드라이버로 전달될 수 있도록 한다.
- 모니터 & 스코어 보드의 uvm_analysis_port & uvm_analysis_imp : 데이터를 전달하고 검증한다.
- 드라이버 & 시퀀서의 uvm_seq_item_port & uvm_seq_item_export : 시퀀스 아이템을 주고 받는다.
class adder_env
: agent와 scoreboard를 포함하는 환경을 구축한다.
- uvm_env는 UVM 환경 클래스의 기본 클래스이다.
- ...
- build_phase에서 모든 컴포넌트를 생성한다.
- super.build_phase(phase) L 부모 클래스의 build_phase를 실행한다.
- adder_scoreboard와 adder_agent의 인스턴스를 생성한다.
- virtual functinon void connect_phase(umv_phase phase) :
- connect_phase 함수가 호출된다. 모든 포트와 임펙트가 연결된다.
- super.connect_pahse(phase)를 호출하여 부모 클래스의 connect_pahse 함수를 실행한다.
- adderMonitor의 send 포트를 adderScoreboardㅇ의 recv 포트에 연결한다. 모니터에서 스코어보드로 데이터가 전달된다.
class adder_test
: 전체 테스트를 실행한다.
- ...
- vuild_phase가 uvm 빌드 단계에서 호출된다. 모든 컴포넌트가 생성된다.
- super.build_phase(phase)를 호출하여 build_phase 함수를 실행한다.
- adder_sequence와 adder_env 인스턴스를 생성한다.
- virtual task run_phase(uvm_phase phase): uvm의 런 단계에서 호출된다. 테스트가 실제로 실행된다.
- phase.raise_objection(this) : 테스트가 끝나기 전까지 시뮬레이션이 종료되지 않는다.
- adderSequence.start(adderEnv.adderAgent.adderSequencer)를 호출하여시퀀스를 시작한다.
- adderEnv의 adderAgent 내의 adderSequencer에서 adderSeuqence가 실행된다.
- phase.drop_objection(this): 테스트가 끝났음을 알린다.
tb_Adder
- adder_interface adderIntf : 물리적인 interface 생성, adder 모듈과 test bench 간의 신호 연결을 제공한다.
- adder_test adderTest : adderTest 실체화하여 uvm 테스트 클래스로, 실제 테스트를 수행한다.
- adderTest = new(~) : adder_test 클래스의 인스턴스를 생성한다.
- `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 |