
이전 강의 까지는 틀을 만들었다면 이번 강의는 sequncer과 driver를 만들거임
========================================================================
[ 칩과 보드 (하드웨어 영역) ]
========================================================================
[ testbench.sv ] (최상위 보드)
│
├──> [ cfs_apb_if.sv ] (APB 핀 묶음 리모컨)
│ │
│ ▼ (물리적 선 연결)
└──> [ Aligner (DUT) ] (두 번째 그림 맨 아래에 있던 칩)
========================================================================
[ UVM 패키지 (소프트웨어 영역) ]
========================================================================
📦 1. cfs_algn_test_pkg.sv (최고 지휘관)
│
├── [ cfs_algn_test_base.sv ] (기본 뼈대, Env 생성)
│ ▲ (extends)
└── [ cfs_algn_test_reg_access.sv ] (레지스터 검증 특수 대본)
│
▼ (create)
📦 2. cfs_algn_pkg.sv (거대한 몸통)
│
└── [ cfs_algn_env.sv ] (Agent들을 담는 큰 상자)
│
▼ (create)
📦 3. cfs_apb_pkg.sv (⭐ 업데이트된 APB 통신 전담반!)
│
├── [ cfs_apb_agent_config.sv ] (설정 객체)
├── [ cfs_apb_types.sv ] (enum, 구조체 모음)
│
│ ====================================================
├── [ cfs_apb_item_base.sv ] (택배 상자: 주소, 데이터가 담김)
│ ====================================================
│
└── [ cfs_apb_agent.sv ] (APB 전담반 텐트)
│
├── [ uvm_sequencer ] (택배 배송 담당자)
│ │
│ ▼ (item_base를 드라이버에게 전달!)
│
└── [ cfs_apb_item_drv.sv ] (Driver 일꾼: 핀 제어)
│
▼ (vif를 흔듦)
[ cfs_apb_if.sv ]
cfs_apb_item_base.sv (트랜잭션 / 시퀀스 아이템)
APB 프로토콜은 결국 "어느 주소(PADDR)"에 "무슨 데이터(PWDATA)"를 "쓸 건지/읽을 건지(PWRITE)"를 결정하는 통신입니다.
이 파일 안에 rand logic [31:0] paddr;, rand logic [31:0] pwdata; 같은 변수들을 담아 택배 상자를 규격화하게 됩니다.
cfs_apb_item_drv.sv (드라이버)
Sequencer는 파일이 없나요?
APB 통신에는 여러 가지 핀 신호가 있지만, 이 택배 상자 안에는 "우리가 조작해야 할 핵심 알맹이"만 담아야 합니다.
버려야 할 신호들 (상자에 안 담음):
prdata, pready, pslverr: 이 신호들은 우리가 보내는 게 아니라 슬레이브(Aligner 칩)가 대답하는 신호입니다. (우리는 Master Agent를 만들고 있으므로 뺍니다.)
psel, penable: 이 신호들은 APB 프로토콜의 규칙에 따라 무조건 2박자로 똑같이 움직입니다. 굳이 매번 상자에 담아줄 필요 없이, 나중에 Driver가 알아서 1과 0으로 껐다 켰다 해줄 것입니다.
담아야 할 5가지 핵심 정보 (상자에 담음):

Direction (방향): Read를 할지 Write를 할지
Address (주소): 어느 레지스터 주소에 접근할지
Data (데이터): Write할 때 어떤 값을 쓸지
Pre-drive delay (사전 대기 시간): 핀에 신호를 쏘기 전, 몇 클럭 동안 버스를 쉬게 할지
Post-drive delay (사후 대기 시간): 핀에 신호를 쏘고 나서, 몇 클럭 동안 버스를 점유하며 대기할지
강사는 여기서 cfs_apb_item_driver라는 실제 택배 상자를 만들기 전에, cfs_apb_item_base라는 중간 부모 클래스를 하나 끼워 넣습니다.
이유: 지금은 입력용(Driver) 택배 상자를 만들지만, 나중에는 출력 결과를 관찰하는 모니터용(Monitor) 상자도 만들어야 합니다. 주소나 데이터 같은 공통 정보들을 매번 따로 코딩하지 않고, 이 Base 클래스에 한 번만 적어둔 뒤 상속받아서 재사용하기 위함입니다. (객체 지향의 아주 훌륭한 설계 방식입니다!)

UVM 규칙상, 드라이버가 하드웨어로 쏘아 보낼 모든 데이터 패킷 클래스는 무조건 uvm_sequence_item이라는 UVM 기본 클래스의 핏줄을 이어받아야(상속) 합니다.
코드에서 앞에 백틱(`)이 붙어있는 단어들(예: uvm_object_utils , uvm_info)을 매크로(Macro)라고 부릅니다.
매크로는 쉽게 말해 "수십~수백 줄짜리 복잡한 코드를 단 한 줄로 압축해 놓은 마법의 단축키"입니다.
왜 쓸까요? UVM을 만든 똑똑한 엔지니어들이 생각해 보니, 새로운 클래스(상자나 일꾼)를 만들 때마다 UVM 시스템에 등록하기 위해 50줄이 넘는 코드를 매번 똑같이 타이핑해야 하는 게 너무 비효율적이었습니다.
그래서 그 50줄짜리 코드를 `uvm_object_utils(이름) 이라는 단축키 하나로 묶어버린 겁니다. 우리가 컴파일을 돌리면, 시뮬레이터가 저 백틱을 보고 알아서 원래의 긴 코드 50줄로 쫙 풀어서 해석해 줍니다.
(참고: 맨 처음 질문하셨던 uvm_macros.svh 파일이 바로 이 수많은 단축키들의 정의를 모아둔 사전이었습니다!)
`ifndef CFS_APB_ITEM_BASE_SV
`define CFS_APB_ITEM_BASE_SV
class cfs_apb_item_base extends uvm_sequence_item;
// [1] 주민등록 (Factory Registration)
`uvm_object_utils(cfs_apb_item_base)
// [2] 출생신고 및 이름표 달기 (Constructor)
function new(string name = "");
super.new(name);
endfunction
endclass
역할: UVM 공장(Factory)에 설계도 등록하기
앞서 우리가 부품을 찍어낼 때 type_id::create("이름") 이라는 마법의 주문을 외웠던 것 기억나시나요?
우리가 만든 이 택배 상자(cfs_apb_item_base)를 나중에 공장에서 찍어내려면, "공장장님! 제가 이런 설계도를 새로 만들었으니 컴퓨터에 등록해 주세요!"라고 알려야 합니다. 그 등록 절차가 바로 이 한 줄입니다.
역할: 생성될 때 이름표 달아주기
new 함수는 객체 지향 프로그래밍(OOP)에서 이 클래스가 처음 세상에 태어날 때(생성될 때) 무조건 한 번 실행되는 초기화 함수입니다.
UVM의 모든 객체는 반드시 자기만의 문자열 이름(string name)을 가져야 합니다. 그래야 나중에 에러가 났을 때 로그 창에 "아! cfs_apb_item_base라는 이름의 상자에서 에러가 났구나!" 하고 추적할 수 있거든요.
super.new(name); : 여기서 super는 나의 부모 클래스(uvm_sequence_item)를 뜻합니다. 즉, "부모님! 저 태어났고요, 제 이름은 이거니까 부모님 장부에도 제 이름 좀 올려주세요!"라고 부모의 초기화 함수를 호출하는 필수 과정입니다.