UVM Phasing
UVM component로부터 extends된 컴포넌트는
phase 컨셉을 따른다.
function() 시뮬레이션 시간이 필요없는 바로 설정되는 부분들
task() 시뮬레이션 돌아가는 부분
//functions
build_phase()
connect_phase()
end_of elaboration_phase()
start_of_simulation_phase()
//function end
//준비 완료
//tasks
run_phase() // 실제 시뮬레이션 시간이 소모되는 부문
//tasks end
//결과 생성
//function
extract_phase
check_phase
report_phase
final_phase
//function end
순서로 동작한다고 정리되어있다.
내가 코드에서 본 부분은
build_phase()
컴포넌트와 객체 생성시 사용
탑다운 실행
connect_phase()
컴포넌트들 간에 TLM port로 연결하는데 사용
바텀업 실행
run_phase()
실제 simulation 코드가 실행되는 구간
병렬로 실행된다.
예시코드에서 볼 부분
Factory의 개념 Factory에 클래스를 등록한다?
phase.raise_objection(this); //run_phase들끼리의 동기를 맞추기 위한 메커니즘
phase.drop_objections(this);
`uvm_fatal("", "fatal")
`uvm_error( "", Error")
`uvm_warning("WARN", "Warning") //여기까지는 UVM_NONE으로 설정되어 뒤에 안써도 된다.
`uvm_info(" ", "message", UVM_LOW)
이중 선택 해서 출력 UVM_MEDIUM, UVM_HIGH, UVM_FULL, UVM_DEBUG
그러면 나중에 verbosity옵션으로 메세지 출력 제한을 걸수 있다. 특정 메시지 이상만 출력한다.
UVM_FULL이면 full 이상인 HIGH, MEDIUM, 등은 나오고 FULL과 DEBUG는 안나온다.
UVM testbench 내용
나중에 따라해보기
transaction
데이터 모델 uvm_sequence_item
각각의 클래스를 컴포넌트로 부른다.
여기서부터는 다양한 옵션들에 대한 설명이 많아서
일단 기본 코드를 다 이해하는 방향으로 간다.
Factory
driver, sequencer, monitor, env 등의 변경할때, 인스턴스 생성을 컨트롤 해야 된다.
이를 우리가 직접 처리하면 코드가 꼬인다.
이를 위해서 클래스에서 proxy_class에 등록하기 위해 다음과 같이 쓰고
`uvm_object_utils(Type)
`uvm_component_utils(Type)
으로 선언하고
build_phase에서 생성한다.
ClassName obj = ClassName::type_id::create();
이후
set_type_override_by_type (original_type, override_type);
set_inst_override_by_type (inst_path, original_type, override_type);
으로 오브젝트나 컴포넌트를 override한다고 한다.
클래스의 override라 내부 구조를 통째로 교체하는 의미로 보인다.
트랜젝션의 경우 `uvm_object_utils() 로 이용하고
컴포넌트는 `uvm_component_utils()로 이용한다.
트랜잭션은 인터페이스나, 시퀀스만 해당이 되고
나머지는 컴포넌트다.
set_type_override_by_type (driver::get_type(), newDriver::get_type());
->얘는 컴포넌트 설정이고
set_inst_override_by_type ("env.agt.sqr.seq", packet::get_type(), bad_packet::get_type());
->얘는 트랜잭션 설정이다.
path는 문자열로 점(.)찍어가면서 접근해서 교체하는 것 같다.
그리고 get_type()이라는 메서드가 있나보다.
아직 전체 구조에 대해 감이 안잡히기에 일단은 기존 코드를 완전히 이해하는 것부터 시작하기로 했다.
`include "uvm_macros.svh"
import uvm_pkg::*;
uvm 패키지와 매크로를 등록한다.
매크로는 Factory 매커니즘을 이용하려면 반드시 써야 된다.
interface adder_interface;
logic [31:0] a;
logic [31:0] b;
logic [31:0] result;
endinterface //adder_interface
인터페이스다. DUT의 입력이자 Driver와 Monitor에서 입력하고 읽어가는 연결 선쯤 된다.
class seq_item extends uvm_sequence_item; //UVM에 정해져 있는 걸 상속받아야 함
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");
super.new(name);
endfunction //new()
//객체 생성
`uvm_object_utils_begin(seq_item) //class의 이름을 넣음
`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 extends uvm_sequence_item
Sequence_item으로 인터페이스와 동일한 입출력이나, 여기서는 포트의 개념이 아닌 포트에 입력하는 데이터의 개념으로,
각각의 포트의 입력에 들어가는 변수를 선언한다.
이 형식으로 DUT와 연결된 부분을 제외한 나머지 부분에서 데이터를 주고 받는 형식이다.
rand는 신호를 랜덤으로 생성해달라는 구문이고
constraint는 랜덤 생성시 생성 범위를 지정하는 구문이다.
function new는 클래스라 객체가 만들어질때 실행되어 상위 클래스를 부르는 함수다.
이후 `uvm_object_utils_begin에 클래스를 이름을 적어 factory에 해당 클래스를 넣는다.
`uvm_object_utils_begin과 `uvm_object_utils_end 사이에는 변수를 넣는데 이는 이 데이터를 int데이터로 취급하고 이 데이터를 모든 방식으로 처리하는 것을 허용한다는 옵션이다.
데이터 메서드라는 것인데, 비교, 복사, 프린트, 팩 등의 기능을 모두 허용한다는 옵션이다.
class adder_sequence extends uvm_sequence; //Generator와 같은 역할
`uvm_object_utils(adder_sequence) //UVM Factory에 내 class 등록
seq_item adder_seq_item; //Handler 선언
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 //Generator 기능
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 //adder_sequence extends uvm_sequence
Sequence는 seq_item에서 선언한 트랜젝션에 맞춰서 랜덤 데이터를 생성합니다.
먼저 sequence_item을 불러올 핸들러를 선언하고, 이후 객채를 생성합니다.
이후 task에서 시뮬레이션을 위해 1000번 반복하면서 랜덤 값을 생성합니다.
adder_seq_item.randomize()로 랜덤 값을 생성하고
`uvm_info("ID", "message", verbosity)로 콘솔창에 메세지를 출력합니다.
ID는 임의의 태그로 보임 임의 설정 가능
verbosity는 중요도 순위로 중요도에 따라 메세지 출력 기준을 바꿀 수 있음
start_item / finish_item 은 `uvm_do()에 정의된 내용중 한 단계
`define uvm_do(UVM_SEQ_ITEM)
이 것은
`uvm_create(UVM_SEQ_ITEM) \
start_item(UVM_SEQ_ITEM); \
UVM_SEQ_ITEM.randomize(); \
finish_item(UVM_SEQ_ITEM);
위와 동일한 내용인데, 이중 2,3,4는 직접 하고 있고,
1은 seq_item::type_id::create()로 반복문 바깥에서 하고 있다.
start_item()은 시퀀스의 코드로 시퀀서로부터 트랜젝션 허가를 기다리다가
드라이버로부터 seq_item_port.get_next_item(req)은 시퀀서로 승인 프로세스의 시작을 알리고
이후 시퀀서는 시퀀스로 wait_for_grant()에서 멈춰있던 시퀀스 진행을 허가하고,
이때부터 트랜젝션은 time stamp가 찍힌다.
이후 시퀀스에서 randomize로 랜덤생성하고
finish_item()으로 드라이버가 처리할 트랜젝션이 준비되어서 시퀀서에게 send_request(req)를 통해 driver로 알린다.
그럼 드라이버는 트랜젝션을 가져가 send(req)처리를 하고
TLM port를 통해 seq_item_port.item_done을 call 하고 트랜젝션 처리가 끝났음을 알린다.
그럼 시퀀스의 finish_item()에서 wait_for_item_done()을 통해 트랜젝션의 처리가 끝났음을 인지하고 종료한다.
이로서 body() 내부의 다음 코드를 실행한다.
요약하면 시퀀스는 대기타다가 드라이버가 다음 데이터 달라고 하면 랜덤 생성하고
생성 끝났다고 finish_item하면 드라이버가 get_next_item 다음 코드인 send를 통해 인터페이스로 트랜젝션을 넘긴다.
이후 드라이버가 item_done으로 끝났다 하면 기다리고 있던 시퀀스가 종료한다.
시퀀스는 run_phase인 task phase에서 실행된다.
시퀀스는 시퀀서를 통해서만 실행되며, start()메소드를 호출하면서 실행된다.
시퀀스의 실행 방법은 2가지로
Explicit 시퀀스 실행 방식
시퀀스를 create() 하고
시퀀스 객체의 start()메소드를 호출하는 방법
Implicit 시퀀스 실행 방식
원하는 시퀀스를 uvm_config_db를 set하여 설정하고
시퀀서는 자동으로 uvm_config_db를 get하여 할당된 시퀀스를 create() 후 start()하는 방법
이렇게 2가지가 있다.
uvm_test에서 실행하는 예시 코드가 있다.
먼저 Explicit 방식이다.
class test_base extends uvm_test;
// 편의상 uvm_component_utils와 function new()는 생략함
virtual task main_phase(uvm_phase phase);
packet_sequence seq; // sequence를 instance 함
phase.raise_objection(this); // 다른 component와 sync를 위해 raise objection
seq = packet_sequence::type_id::create("seq", this); // sequence seq를 create 함.
seq.randomize(); // 추가적으로 randomize() 진행할 수도
seq.start(env.i_agent.sqr); // sequence seq를 env.i_agent.ser sequencer에서 start 함
phase.drop_objection(this); // sync를 위한 drop objection
endtask
endclass
보면 다른 컴포넌트와의 동기화를 위한 phase.raise_objection(this)와 phase.drop_objection(this)이 두가지를 빼면
핸들러 선언하고, create()하고 랜덤 생성하는 것 까지 같다.
마지막에 seq.start(env.i_agent.sqr);에서 시퀀스를 env.i_agent.sqr이란 시퀀서에서 start 하는 Explicit 방식이다.
Implicit 방식은 다음과 같다.
class test_base extends uvm_test;
// 편의상 uvm_component_utils와 function new() 부분 생략
virtual function void build_phase(uvm_phase phase);
...;
// 원하는 sequencer의 main_phase에서 사용되는 default_sequence 변수에
// 현재 packet_sequence를 설정함
uvm_config_db #(uvm_object_wrapper)::set(this, "*.sqr.main_phase",
"default_sequence", packet_sequence::get_type());
endfunction
endclass
uvm_config_db #(uvm_object_wrapper)::set(this, "*.sqr.main_phase", "default_sequence", packet_sequence::get_type());
이 코드로 설정한다. 시퀀서의 메인 페이즈의 default_sequence 변수에 패킷 시퀀스를 설정하는 방식이다.
///이부분은 이해하는데 중요해서 많은 설명이 필요하므로 일단은 넘어갑니다.
class adder_driver extends uvm_driver #(seq_item);
`uvm_component_utils(adder_driver) //UVM 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)
Driver는 Sequence Sequencer와 통신하며 랜덤 생성된 데이터를 받아다가 인터페이스로 넘겨준다.
그래서 DUT의 포트인 인터페이스와 포트 구조에 맞는 데이터 구조인 Sequence_item을 불러와서 시퀀스가 생성한 데이터를 넘겨 받아 저장한다.
그냥 이름 붙이면 헤더이고 create()해야 생성된다.
uvm_config_db는 시퀀서와 시퀀스를 연결하는 코드인데, 여기서는 설정이 잘 되었는지 get으로 데이터를 읽고 문제 없으면 실행, 아니면 `uvm_fatal 메세지를 출력하는 코드이다.
이후 나머지 함수들의 이름 build_phase, start_of_simulation_phase, run_phase 등은 미리 설정된 함수명이다.
그냥 순서대로 실행된다 보면된다.
빌드에서는 인터페이스를 점검하고, sequence_item 객체를 생성한다.
이후 그냥 화면에 문자열 출력 한번 하고
이후 run_phase에서 아까 시퀀스와 통신하던 코드가 실행된다.
seq_item_port.get_next_item(adder_seq_item)이건 시퀀서와 통신하는 함수이고
그러면 시퀀서가 시퀀스에게 랜덤 생성하라고 한다. 이후 완료되면
각 인터페이스에 랜덤 생성된 트랜젝션 값이 담긴 시퀀스의 변수 값을 할당하고
다시 seq_item_port.item_done();으로 시퀀서를 불러서 시퀀스의 task를 종료한다.
class adder_monitor extends uvm_monitor;
`uvm_component_utils(adder_monitor) //UVM Factory에 이 class 등록
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 //run_phase(uvm_phase phase)
endclass //adder_monitor extends uvm_monitor
Monitor는 DUT에서 나온 인터페이스에서 트랜젝션을 받아다가 Scoreboard로 전달하는 클래스입니다.
2.7, 2.8에 TLM과 커버리지나 스코어 보드에서 내용을 확인할 수 있다.
TLM(Transaction Level Modeling)이다. 포트를 통한 입출력을 통해 통신을 분리하는 것이다.
통신을 위한 전용 클래스로서 이를 거침으로서 해당 포트만 있으면 어떤 컴포넌트던 교체가 가능한 것으로 보인다.
TLM-1과 TLM-2가 있는데 여기서는 TLM-1만 사용한다.
uvm_analysis_port #(seq_item) send가 어떤 내용인지 찾아보기
-> uvm_analysis_port란 클래스에서 #(seq_item) 타입을 주고받는 send라는 핸들러를 생성한다.
이후 new()에서 객체를 생성한다.
이 데이터 통신방식은 Broadcast Mode (Analysis port) connection이라 불리며
여기는 Producer쪽이다. 데이터를 생산해서 전달하는 위치를 이렇게 부른다.
이후 Subscriber 쪽에서는 uvm_analysis_imp로 핸들러 recv를 구현한다. 이건 스코어보드에서 설명한다.
이후 Producer쪽에서는 send.write(adder_seq_item)를 통해 데이터를 전달하고
스코어보드의 write function에서 인자로 받는다.
이 둘의 연결은 agent와 scoreboard가 포함된 env에서 연결한다.
adderAgent.adderMonitor.send.connect(adderScoreboard.recv);
이 코드를 통해 agent의 monitor의 send를 connect 함수를 통해 Scoreboard의 recv에 연결한다는 의미이다.
class adder_scoreboard extends uvm_scoreboard; //UVM SB를 subclass에 상속 받겠다
`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 //new()
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 //adder_scoreboard extends uvm_scoreboard
2.8 스코어 보드 내용
uvm_analysis_imp #(seq_item, adder_scoreboard) recv;
-> TLM 통신중 analysis port connection을 위해 받아들이는 Subscriber로 구동하기 위해 implementation을 위한 코드이다.
이를 위해서 #(트랜젝션타입(seq_item), 현재클래스)로 파라미터 세팅을 한다.
이후 실제로 동작하는 것은 write 펑션을 정의하면서 사용된다.
모니터에서 포트의 write함수를 이용하기에 모니터에서 보낸 데이터가 이 함수의 인자로 들어가서 함수 내부에서
DUT 인터페이스에서 나온 데이터를 받을 수 있다. 이를 통해 여기서 예상한 값과 출력 값이 일치하는지 출력한다.
data.print(uvm_default_line_printer);는 write함수의 인자인 시퀀스의 함수로 보인다.
출력 형식을 보고 판단하자.
class adder_agent extends uvm_agent;
`uvm_component_utils(adder_agent) //UVM Factory에 등록
function new(input string name = "adder_agent", uvm_component c);
super.new(name, c);
endfunction //new()
adder_monitor adderMonitor; //Class, Handler
adder_driver adderDriver; //Class, Handler
uvm_sequencer #(seq_item) adderSequencer;
virtual function void build_phase(uvm_phase phase);
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
agent내용 모니터와 드라이버, 시퀀서 객체를 생성함
testbench에서 uvm_config_db에서 인터페이스 설정해둠, 그리고 uvm_test에서 시퀀스와 agent의 시퀀서 연결해둠
이후 TLM으로 드라이버의 seq_item 통신 포트를 시퀀서의 통신 포트에 연결함? TLM 찾아보기
-> 이후 Driver와 Sequencer 사이에 데이터를 주고 받는 포트를 만들어둠
이는 Driver와 Sequencer가 seq_item_port.get_next_item(adder_seq_item);으로 시퀀서와 통신하며 시퀀스가 생성한 랜덤 데이터를 보낼 준비가 되었는지 확인하고 seq_item_port.finish_item()으로 통신을 종료하는데, 이 또한 통신이라
연결을 할 필요가 있어서 Sequencer와 Driver 정의한 agent에서 이를 연결한 것으로 보임.
주로 보내는 쪽은 port, 받는쪽은 export란 postfix를 붙인다.
여기서는 시퀀서가 기본 시퀀서라 기본 명칭을 따른 것으로 보인다.
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; //Class, Handler
adder_agent adderAgent; //Class, Handler
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
adderScoreboard = adder_scoreboard::type_id::create("SCB", this); //Handler로 생성
adderAgent = adder_agent::type_id::create("AGENT", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
adderAgent.adderMonitor.send.connect(adderScoreboard.recv);
endfunction
endclass //adder_env extends uvm_env
environment 클래스임 에이전트 생성하고 스코어보드 생성함
이후 스코어 보드의 recv와 monitor의 send 포트를 연결함? 어떻게 연결한 것인지는 모름 TLM으로 연결한다는 것 같은데 찾아봐야 됨
-> TLM 으로 통신을 하는데,
스코어보드에서는 uvm_analysis_imp #(seq_item, adder_scoreboard) recv;로 데이터 입력 받을 포트를 구현한다.
다만, 실제 동작은 포트로 바로 데이터가 넘어오는 것이 아닌 함수의 인자를 넘겨줘서 실행하는 방식으로 통신하기에
write 함수를 만들어두었다.
모니터에서는 데이터 전달을 위한 출력 포트인 uvm_analysis_port #(seq_item) send; 을 통해서 출력 포트를 설정한다.
여기서는 write 함수를 쓰기 위해 send.write(seq_item)의 형식으로 send 핸들러와 연결된 클래스의 write함수에 seq_item을 인자로 보내면서 실행하는 방식으로 함수가 실행된다.
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 //run_phase
endclass //adder_test extends uvm_test
test클래스로 environment와 시퀀스를 설정함
동기화를 위해 phase.raise_objection(this);와 phase.drop_objection(this);를 사용하는 것을 볼 수 있음.
-> 동기화 관련은 공부가 필요함
여기서 시퀀스를 Explicit으로 실행하는 것을 확인할 수 있음 시퀀서는 agent에서 실행해서 실행 경로를 확인할 수 있음
나머지는 그냥 create()과정임
module tb_Adder ();
adder_interface adderIntf();
adder_test adderTest; //Class, Handler
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
시퀀서에 adderIntf를 uvm_config_db에 미리 설정해 둠 나중에 시퀀서를 생성할 때 자동으로 해당 인터페이스가 연결됨
2.6절 uvm_config_db 참조
uvm_config_db#(type)::set(context, inst_name, field, value);
type 설정하려는 필드의 타입
context 접근할 위치 보통은 현재 위치인 this사용
inst_name 설정하려는 필드가 있는 객체 이름
field 설정하려는 변수 이름
value 필드에 설정하려는 값
따라서 해당 코드를 보면 adder_interface 타입에 대해 설정하려는 것이고
adderIntf 필드에 대해 adderIntf를 설정하는 내용이다.
이 외에는 그냥 run_test();하면 adderTest의 각 페이즈가 순서대로 동작한다.
2일차 결론
결국 DUT에 맞춰서 인터페이스 설정하고,
시퀀스에서 랜덤 값 형성 범위만 결정하면
하나의 모듈을 테스트하는 데는 문제 없다.
다만 여러 모듈이나 테스트 환경을 변경하면
테스트 하는 UVM 구조를 내가 만들어야 하는데
아직 확신이 없다. 구조는 가져다 쓰면 되고,
그냥 Sequencer와 Sequence의 연결과 monitor와 Scoreboard의 연결만 해결하면 되는 것으로 보이는데
이 부분은 신입한테 요구할 것 같지는 않다.
나중에 내가 만들면서 해결해볼 수도 있겠지만, 지금 당장 신입의 요구사항은 아니다.
그래서 일단 여기까지 하고 UVM 으로 모듈을 검증하는 프로젝트를 시작할 계획이다.
생각보다 기본 개념과 구조를 이해하니 크게 어려운 것은 없다.
다만 UVM이란 라이브러리를 알아야 되서
https://wikidocs.net/book/8302
https://accellera.org/images/downloads/standards/uvm/UVM_Class_Reference_Manual_1.2.pdf
이 두 사이트를 참조하는 것이 제일 좋아보인다.
RTL 설계와 C++을 아는 사람이면
약 2일 정도만에 간단한 테스트 벤치는 작성할 수 있다.
내가 참고한 코드를 올린 사람도
실제로 검증 할때는 모듈 하나하나씩 따로 검증했고
입력 신호도 단순하다.
그걸 참고해서 내 코드를 UVM 검증을 돌려봐야겠다.
https://m.blog.naver.com/PostView.naver?blogId=nepenthes1&logNo=223501191297&navType=by
이 사람이 최종 프로젝트에서 모듈을 검증한 코드를 볼 수 있는데
구조는 그대로 가져다 쓴 것 같았고,
일부 객체가 추가되었지만, 기본적으로 시퀀스와 스코어보드의 코드로만 제어한 것으로 보인다.
'과정 종료 후 개별 스터디 내용 > SystemVerilog, UVM' 카테고리의 다른 글
UVM - 1일차 uvm 클래스의 이해 (0) | 2025.01.06 |
---|---|
SystemVerilog 검증 IDEC 강의 1일차 내용 요약 (2) | 2025.01.05 |
UVM 공부 시작 및 찾은 내용 정리 (1) | 2025.01.05 |
Vivado에서 UVM 사용하기 (0) | 2025.01.03 |