이 강의는 UVM 검증 환경의 가장 핵심적인 "데이터 생성 및 전달 과정"인 Sequence 메커니즘(Sequencer Driver)을 설명하는 매우 중요한 파트입니다.RTL 설계자 입장에서 이 파트가 왜 중요하냐면, 하드웨어 타이밍(클럭, 핀 신호)을 소프트웨어 알고리즘(클래스, 태스크)으로 변환하는 다리 역할을 하기 때문입니다.

현업의 시각에서 이 복잡한 개념들을 '영화 촬영장'에 비유해서 아주 명쾌하게 쪼개 드릴게요!
① Sequence Item (cfs_apb_item_drv) = [소품/대사 1줄]
② Sequence (my_sequence) = [시나리오 / 대본]
③ Sequencer (apb_sequencer) = [조감독]
사진 오른쪽의 파란색 화살표들은 "우리가 짤 대본(my_sequence)이 어떤 부모님들의 능력을 물려받았는가?"를 보여줍니다.
uvm_sequence_base (할아버지):
uvm_sequence (아버지):
my_sequence (나):
앞서 엔지니어님이 10번 for 문을 돌려서 아이템을 찍어냈던 그 코드가 원래는 테스트벤치가 아니라 이 대본(my_sequence의 body 태스크) 안으로 들어와야 합니다.
흐름을 순서대로 따라가 볼까요?
[Test 지휘관의 명령]
my_sequence.start(apb_sequencer);
해석: "야! my_sequence 대본아! 너 지금부터 저기 현장에 있는 apb_sequencer(조감독)한테 붙어서 네 대본대로 연기 지시해!"
이때, 대본의 무전기(m_sequencer)가 실제 조감독(apb_sequencer)과 자동으로 연결됩니다.
[Sequence 대본의 실행 - task body() 내부]
대본이 시작되면 자동으로 body() 태스크가 실행됩니다.
start_item(apb_item_drv); * "조감독! 나 지금 새 아이템(택배 상자) 하나 만들었어. 배우(Driver)가 이전 연기 다 끝내고 새 거 받을 준비 됐는지 확인해 줘!" (권한 요청)
(이 사이에서 item에 랜덤 값(randomize)을 채웁니다.)
finish_item(apb_item_drv);

이 장표는 UVM의 가장 위대한 마법 중 하나인 'TLM (Transaction Level Modeling) 연결'을 보여주고 있습니다. 이전 장표가 대본(Sequence)의 족보였다면, 이번에는 직원들(Component)의 족보와 그들의 통신망을 보여줍니다.
마법의 무전기 연결 (connect_phase)
장표 아래쪽에 노란색 박스로 강조된 코드가 보이시죠? 이게 바로 에이전트(Agent) 파일 안에서 일어나는 가장 중요한 작업입니다.
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Connect the driver's port to the sequencer's export
// Now they can communicate with each other!
driver.seq_item_port.connect(sequencer.seq_item_export);
endfunction
의미: "조감독(Sequencer)의 송신기(export)와 배우(Driver)의 수신기(port)를 주파수 맞춰서 서로 연결해라!"
에이전트(Agent)라는 포장 상자는 시뮬레이션 시작 전(connect_phase)에 미리 이 둘 사이에 전용 핫라인(파이프)을 뚫어줍니다. 장표 왼쪽 그림에 있는 작은 초록색 동그라미(포트 연결)가 바로 이 작업을 뜻합니다.

드디어 퍼즐의 마지막 조각, "액션 배우(Driver)의 시점"에서 바라본 대본 전달 메커니즘입니다!
앞선 장표들이 대본(Sequence)이 어떻게 만들어지고 조감독(Sequencer)과 연결되는지를 보여줬다면, 이번 장표는 "드라이버가 무전기를 통해 어떻게 대본을 받아와서 연기(물리적 핀 제어)를 하는가?"를 정확하게 보여줍니다.
노란색 박스 안에 있는 코드가 바로 Driver의 run_phase 안에 들어갈 가장 핵심적인 형태입니다. 실무의 관점에서 3단계로 명쾌하게 해부해 드릴게요!
forever begin
seq_item_port.get_next_item(item);
// Drive the information from the item on the bus
seq_item_port.item_done();
end
드라이버(일꾼)는 시뮬레이션이 시작되자마자 이 forever (무한 반복) 굴레에 빠집니다. 여기서 일어나는 3단계의 완벽한 핸드셰이크(Handshake) 과정을 볼까요?
드라이버는 자기 주머니에 있는 무전기(seq_item_port)를 통해 조감독에게 연락합니다.
만약 지휘관(Test)이 아직 대본을 안 줬다면? 드라이버는 여기서 시뮬레이션 시간이 멈춘 채로 새 택배 상자가 배달 올 때까지 무한정 대기(Block)합니다. * 대본 쪽에서 finish_item을 부르는 순간, 아이템이 쏙! 하고 드라이버의 item 변수 안으로 넘어옵니다.
넘겨받은 상자(item) 안에는 우리가 아까 만들었던 랜덤 주소(addr), 쓸 데이터(data), 방향(dir)이 들어있습니다.
이제 드라이버는 이 값들을 꺼내서, 물리적인 cfs_apb_if의 핀들(PADDR, PWDATA 등)에 클럭(PCLK) 타이밍에 맞춰 1과 0을 집어넣는 '진짜 하드웨어 제어 노동'을 시작합니다.
이게 가장 중요한 통신 규약입니다.
드라이버가 핀을 다 흔들고 나서 이 item_done()을 무전기에 대고 외치지 않으면, 위에서 기다리던 대본(Sequence) 쪽은 "아직 이전 상자 배달이 안 끝났구나?" 하고 멈춰버립니다.
즉, item_done()은 "배달 완료 서명"이자, 대본에게 "다음 랜덤 상자 만들어서 또 보내줘!"라고 허락하는 완벽한 동기화(Sync) 신호입니다.
장표 오른쪽을 보시면 uvm_seq_item_pull_port라는 박스 안에 get_next_item()과 item_done()이라는 함수가 들어있는 게 보이실 겁니다.
앞서 우리가 "드라이버는 상속만 받으면 무전기를 공짜로 쓴다"라고 했던 것 기억하시죠? 그 무전기가 바로 저기 있는 pull_port (당겨오는 포트) 객체이고, UVM을 만든 사람들이 저 두 함수를 미리 다 짜서 무전기 안에 내장해 둔 것입니다.
전체 스토리 총정리
1. [Sequence]: "빈 상자에 랜덤 값 채웠다! 조감독, 이거 보낼게!" (finish_item)
2. [Driver]: "어? 무전기에 상자 들어왔네? 가져올게!" (get_next_item)
3. [Driver]: "상자 까서 하드웨어 핀 존나게 흔든다~ 으쌰!" (vif.paddr = item.addr)
4. [Driver]: "휴, 다 흔들었다! 조감독! 나 다했어! 다음 거 줘!" (item_done)
5. [Sequence]: "오케이, 그럼 다음 2번째 랜덤 상자 또 빚는다!"

개발자가 매번 start_item, randomize, finish_item을 일일이 치려면 코드가 너무 길어집니다. 그래서 UVM은 편리한 매크로를 제공합니다.`
:단 한 줄의 코드(`uvm_do(req))만 쓰면 객체 생성 무작위 데이터 채우기(Randomization) Sequencer로 전송(Start/Finish) 과정을 UVM이 알아서 전부 처리해 줍니다.
앞서 우리가 대본(Sequence) 안에서 택배 상자(Item)를 찍어내고 배달할 때, 원래는 이렇게 4단계를 거쳐야 했습니다. (수동 변속기)
// --- [Manual Approach] ---
// 1. Create an empty box
req = cfs_apb_item_drv::type_id::create("req");
// 2. Request permission from the sequencer
start_item(req);
// 3. Roll the dice (Randomize)
if (!req.randomize()) begin
`uvm_error("SEQ", "Randomization failed!")
end
// 4. Hand it over to the driver
finish_item(req);
이걸 매번 치려니 손가락이 너무 아프겠죠? 그래서 UVM 창시자들이 "야, 어차피 매번 똑같이 치는 거, 그냥 단축키 하나 만들자!" 하고 만든 것이 바로 `uvm_do 입니다. (자동 변속기)
// --- [Auto Approach using Macro] ---
// Create, request, randomize, and send all at once!
`uvm_do(req)
단 한 줄이면 끝납니다! 시뮬레이터가 컴파일할 때 저 한 줄을 보고 알아서 위의 4줄짜리 수동 코드로 쫙 풀어서 실행해 줍니다. 앞으로 대본(Sequence)을 짜실 때는 무조건 이 마법의 매크로를 쓰시게 될 겁니다.
이 개념은 올려주신 세 번째 사진(계층도)을 보면 아주 정확하게 이해할 수 있습니다.
[문제점]
대본(Sequence)은 원래 자기가 어느 조감독(Sequencer) 위에서 실행되는지 정확히 모릅니다.
사진 맨 위를 보시면, 부모 클래스인 uvm_sequence_base에는 m_sequencer라는 기본 무전기가 달려있습니다.
이 무전기는 "그냥 아무 조감독하고나 통신할 수 있는 범용 무전기"입니다. 그래서 APB 전용 기능이나 APB 설정(Config) 값 같은 디테일한 정보는 끌어올 수가 없습니다.
[해결책: 매크로의 마법]
사진 중간에 있는 노란색 박스 코드를 유심히 봐주세요!
// Declare a specific sequencer pointer for APB
`uvm_declare_p_sequencer(cfs_apb_sequencer)
우리가 만든 cfs_apb_sequence_base 파일 안에 이 매크로를 딱 한 줄 선언해 줍니다.
그러면 UVM 시스템이 알아서 범용 무전기(m_sequencer)를 APB 조감독 전용 최신 스마트폰인 p_sequencer로 업그레이드(Type Casting)해 줍니다! (사진에 초록색 동그라미 쳐진 부분입니다.)
[이게 왜 실무에서 중요할까요?]
만약 테스트벤치에서 "이번에는 PADDR 주소 폭을 16비트로 제약해서 랜덤 돌려!"라고 config 금고에 설정을 넣어놨다고 칩시다.
대본(Sequence)이 그 설정값을 읽어와서 랜덤을 돌리려면 조감독(Sequencer)의 주머니를 뒤져야 합니다.
이때 범용 m_sequencer는 조감독 주머니를 뒤질 권한이 없지만, 전용 p_sequencer는 p_sequencer.agent_config.어쩌구저쩌구... 하면서 APB 조감독의 모든 내부 정보를 마음대로 꺼내 쓸 수 있는 막강한 권한을 가지게 됩니다.
(참고: 그래서 사진 맨 아래를 보면, 자식 대본들(simple, rw, random)은 이 base 대본을 상속받았기 때문에 자동으로 막강한 p_sequencer를 쓸 수 있게 되는 구조입니다!)