Driver (일꾼): 리셋이 눌려도 눈치채지 못하고, 쥐고 있던 핀(Pin)에 계속 쓰레기 값을 밀어 넣습니다.
Sequencer (조감독): 일꾼이 "택배 배달 완료(item_done)" 보고를 해야 다음 상자를 주는데, 일꾼이 리셋 때문에 멈춰버리면 조감독은 영원히 다음 상자를 주지 못하고 교착 상태(Deadlock/Stuck)에 빠집니다.
Monitor & Coverage: 리셋으로 버스가 초기화되었는데도, 계속 이상한 값을 훔쳐보며 엉뚱한 채점표를 작성합니다.
해결 목표: 리셋이 감지되는 즉시 모든 컴포넌트가 하던 일을 즉각 멈추고(Stop), 핀을 기본값으로 되돌린 후, 처음부터 다시 시작(Restart)해야 합니다.
강사는 이 문제를 해결하기 위해 코드를 어떻게 짤 것인가에 대해 3가지 방법을 비교합니다.
Driver, Sequencer, Monitor, Coverage 4개 파일의 run_phase 안에 각자 알아서 리셋을 기다리는 무한 루프(forever)를 복붙해서 넣습니다.
단점: 코드가 4배로 중복되고 엄청나게 지저분해집니다.
개별 직원들은 무한 루프를 지우고 순수하게 handle_reset()이라는 초기화 함수만 가집니다.
대신 관리자인 에이전트(Agent) 혼자 무한 루프를 돌면서 리셋을 감시하다가, 리셋이 터지면 driver.handle_reset(), monitor.handle_reset() 처럼 직원들의 이름을 일일이 호명하며 초기화시킵니다.
단점: 나중에 직원이 추가되거나 빠지면 에이전트 코드를 매번 수정(하드코딩)해야 합니다.
SystemVerilog의 고급 문법인 인터페이스 클래스(Interface Class)를 활용합니다. reset_handler라는 일종의 '리셋 자격증'을 하나 만듭니다.
리셋이 필요한 4명의 직원(Driver, Seq, Mon, Cov)에게 이 자격증(Interface)을 따게(Implement) 만듭니다.
에이전트는 직원들의 이름을 일일이 부를 필요 없이, "내 밑에 있는 직원들(get_children) 중에 reset_handler 자격증 있는 사람 전부 다 handle_reset() 실행해!" 라고 우아하게 일괄 처리($cast 캐스팅)합니다.
강사가 마지막에 선택한 3번째 방법이 바로 현업 UVM 프레임워크에서 가장 사랑받는 다형성(Polymorphism)의 극치입니다!
이렇게 짜두면 나중에 새로운 컴포넌트 100개를 추가하더라도, 에이전트 코드는 단 한 줄도 수정할 필요가 없습니다. 그냥 새 컴포넌트에 자격증(implements reset_handler)만 달아주면 에이전트가 알아서 리셋을 챙겨주게 됩니다.
`ifndef CFS_APB_AGENT_SV
`define CFS_APB_AGENT_SV
class cfs_apb_agent extends uvm_agent implements cfs_apb_reset_handler;
// handler를 만들기 위해 아래 다섯개 파일을 가지고 온다
//Agent configuration handler
cfs_apb_agent_config agent_config;
//Driver handler
cfs_apb_driver driver;
//Sequencer handler
cfs_apb_sequencer sequencer;
//Monitor handler
cfs_apb_monitor monitor;
//Coverage handler
cfs_apb_coverage coverage;
`uvm_component_utils(cfs_apb_agent) // cfs_apb_agent를 uvm에 등록
function new(string name = "", uvm_component parent);
super.new(name, parent);
endfunction
//------------------------------------------------------------------------------
// 만드는 단계
//------------------------------------------------------------------------------
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// agent_config 만들기
agent_config = cfs_apb_agent_config::type_id::create("agent_config", this);
// monitor 만들기
monitor = cfs_apb_monitor::type_id::create("monitor", this);
// 코드 해석: "리모컨 설정에서 커버리지 수집 기능이 켜져 있다면(get_has_coverage() == 1)
// ➔ 통계 장부를 작성할 서기(cfs_apb_coverage) 객체를 메모리에 생성(create)해서 에이전트 부서에 배치해라!"
if(agent_config.get_has_coverage()) begin
coverage = cfs_apb_coverage::type_id::create("coverage", this);
end
// 에이전트(cfs_apb_agent)는 상황에 따라 **'직접 경기에 뛰는 선수(Active)'**가 될 수도 있고,
// **'관중석에서 지켜보기만 하는 심판(Passive)'**이 될 수도 있습니다.
// 이것을 결정하는 리모컨 버튼이 바로 get_active_passive()입니다.
if(agent_config.get_active_passive() == UVM_ACTIVE) begin
driver = cfs_apb_driver::type_id::create("driver", this);
sequencer = cfs_apb_sequencer::type_id::create("sequencer", this);
//코드 해석: "리모컨 설정이 ACTIVE라면 ➔ 드라이버와 시퀀서 객체를 메모리에 생성(create)해서 채용해라!"
end
// UVM_PASSIVE 일때는 Monitor 와 Coverage만 남겨둔다.
// 코드 해석: if 문 조건이 거짓이 되므로, driver와 sequencer는 생성되지 않고 비어있는 상태(null)로 남게 됩니다.
// 에이전트는 조용히 데이터만 수집하는 스파이가 됩니다.
endfunction
//-------------------------------------------------------------------------------
// 연결하는 단계
//-------------------------------------------------------------------------------
virtual function void connect_phase(uvm_phase phase);
cfs_apb_vif vif; // vif를 담을 빈 바구니(포인터)만들기
string vif_name = "vif"; // testbench에서 vif를 config_db에 넣어둘때 vif라고 이름 붙였음,
// 그거 꺼내오는거임
super.connect_phase(phase);
// uvm_config_db안에 virtual cfs_apb_if을 찾아라 그리고 내 위치 agent(this)에서 vif_name을 찾고 vif에 넣어라
if(!uvm_config_db#(virtual cfs_apb_if)::get(this, "", vif_name, vif)) begin
// vif를 못찾으면 오류를 내고 시뮬레이션을 중단해라
`uvm_fatal("APB_NO_VIF", $sformatf("Could not get from the database the APB virtual interface using name \"%0s\"", vif_name))
end
else begin
// vif를 찾았다면 vif를 smart remote control(agent_config)에 넣어라
agent_config.set_vif(vif);
end
// monitor 안의 config에 agent_config값을 넣자
monitor.agent_config = agent_config;
// coverage 검사하면 coverage config값에 agent_config 값 넣어주자
if(agent_config.get_has_coverage()) begin
coverage.agent_config = agent_config;
// 모니터 결과 포트랑 coverage port랑 연결
monitor.output_port.connect(coverage.port_item);
end
// active 이면 driver 포트를 sequencer 이랑 연결하자
if(agent_config.get_active_passive() == UVM_ACTIVE) begin
driver.seq_item_port.connect(sequencer.seq_item_export);
// driver config에도 agent_config 값을 넣자
driver.agent_config = agent_config;
end
endfunction
//-------------------------------------------------------------------------------
// reset
//-------------------------------------------------------------------------------
//Task for waiting the reset to start
protected virtual task wait_reset_start();
agent_config.wait_reset_start();
endtask
//Task for waiting the reset to be finished
protected virtual task wait_reset_end();
agent_config.wait_reset_end();
endtask
//칩에 리셋이 떨어졌을 때, 소대장(Agent)이 대원들(Driver, Sequencer, Monitor, Coverage)에게 일일이 달려가서 "너 하던 일 멈춰!"라고 지시하면
// 코드가 굉장히 지저분해집니다. 대신, 전파 방송을 통해 훈련된 인원들이 각자 알아서 대응하게 만드는 방식을 사용합니다.
//Function to handle the reset
virtual function void handle_reset(uvm_phase phase);
uvm_component children[$]; // Queue to hold all child components, $=가변 Queue
get_children(children); //1. Gather all personnel in the tent (Driver, Monitor, etc.)
// 2. Iterate through each person
foreach(children[idx]) begin // foreach= 배열에 들어있는 데이터 개수만큼 알아서 처음부터 끝까지 돌아라
cfs_apb_reset_handler reset_handler; // Pointer for the "Reset License"
// 3. Dynamic Casting: "Do you have the Reset Handler license?"
if($cast(reset_handler, children[idx])) begin // $cast(A,B) = B가 A 타입으로 취급해도 안전하나?
reset_handler.handle_reset(phase); // = B가 자격증 A를 가지고 있나?
end
end
endfunction
//에이전트(cfs_apb_agent)는 평소에는 직원들(Driver, Monitor 등)을 연결해 주고 뒤로 빠져있지만,
// 칩에 **리셋(Reset)**이 떨어지는 순간 전체를 통제하는 지휘관으로 돌변합니다.
virtual task run_phase(uvm_phase phase);
forever begin
wait_reset_start(); // Wait until the physical reset pin drops to 0
handle_reset(phase); // Execute emergency protocol!
wait_reset_end(); //// Wait until the physical reset pin rises back to 1
end
endtask
//에이전트의 run_phase는 시뮬레이션이 끝날 때까지 영원히(forever) 도는 감시 레이더입니다.
//비상벨이 울리면(wait_reset_start), 즉시 아래에 있는 handle_reset() 지휘를 내리고, 상황이 종료될 때까지 대기(wait_reset_end)합니다.
endclass
`endif
// uvm_config_db의 정체: "UVM 전용 마법의 우체통"