
UVM 클래스(소프트웨어)들을 실행하려면, 바탕이 되는 물리적 하드웨어 모듈(module top)이 필요합니다. 이 Testbench는 다음 세 가지 역할을 합니다.

run_test()가 호출되면 UVM은 1) 테스트 클래스를 생성하고, 2) Phase들을 실행합니다. 이때 어떤 테스트(예: Register Test, Random Test 등)를 실행할지 결정하는 방식이 중요합니다.
하드코딩 (비권장): run_test("my_test")처럼 코드 안에 테스트 이름을 직접 적는 방식입니다.
시뮬레이터 인자 사용 (현업 표준): SystemVerilog 코드는 run_test()로 비워두고, 시뮬레이터(Cadence, Synopsys 등)를 실행할 때 터미널 명령어 인자로 +UVM_TESTNAME=테스트클래스이름을 넘겨주는 방식입니다. 코드를 수정하고 다시 컴파일할 필요 없이 원하는 테스트만 골라서 돌릴 수 있어 훨씬 유연합니다.


UVM의 모든 구조적 블록들(Agent, Env 등)은 uvm_component라는 기본 클래스를 상속받습니다. 이 컴포넌트들은 시뮬레이션 시작부터 끝까지 정해진 순서의 9가지 Phase(단계)를 거칩니다.
핵심 특징: run_phase만 유일하게 시뮬레이션 시간(Time)을 소모하는 task이고, 나머지 모든 phase는 0초에 실행되는 function입니다.
가장 많이 쓰는 3대 Phase:

수많은 종류의 테스트를 만들어야 하는데, 매번 테스트 코드 안에 'Environment 생성 로직'을 중복해서 적는 것은 비효율적입니다.
모든 테스트는 기본적으로 UVM 패키지의 uvm_test를 상속받아야 합니다.
base_test 클래스 도입: uvm_test를 상속받는 중간 단계인 base_test를 하나 만듭니다. 오직 이 안에서만 Environment 인스턴스를 생성합니다.
이후 만드는 모든 세부 테스트들(Register Access Test, Random Test 등)은 이 base_test를 상속받기만 하면 자동으로 Environment 구조를 물려받게 됩니다.
=========================================================================
[하드웨어 영역 - 물리적 보드]
1️⃣ testbench.sv (최상위 실행 파일)
│
├── ⚡ 전원 및 클럭 (Reset & Clock) 인가
├── 🖲️ DUT (검증 대상 칩 - Aligner) 올려놓기
│
└── 🚀 run_test() 실행!! ──────────┐ (소프트웨어 세계를 깨움)
===================================== │===============================
▼
[소프트웨어/UVM 영역 - 객체 지향 클래스]
[대본 보관 상자] (테스트 시나리오) [장비 보관 상자] 검증 장비 세트
2️⃣ cfs_algn_test_pkg.sv 5️⃣ cfs_algn_pkg.sv
│ │
│ (공통 뼈대 대본) │ (종합 검증 장비)
├──3️⃣ cfs_algn_test_base.sv ────────▶ 6️⃣ cfs_algn_env.sv (생성/설치)
│ │ └─ "무조건 env를 설치해라!" (Agent, Scoreboard 등이 들어갈 곳)
│ │
│ ▼ (상속: extends)
│ (실제 액션 대본)
└──4️⃣ cfs_algn_test_reg_access.sv
└─ "base가 장비 설치했지?
나는 레지스터를 공격할게!"
======================================================================
전체 흐름
1. 우리가 시뮬레이터를 켜면 1️⃣ testbench.sv가 돌아갑니다.
2. testbench.sv가 run_test()를 부르면서, "오늘은 4️⃣번 대본(reg_access)으로 실행해!" 라고 명령합니다.
3. 시스템은 2️⃣ 대본 상자(test_pkg)를 열어서 4️⃣번 대본을 찾습니다.
4. 그런데 4️⃣번 대본은 3️⃣ base 대본의 자식(상속)입니다. 그래서 부모인 3️⃣번 대본의 규칙을 먼저 따릅니다.
5. 3️⃣번 대본의 규칙에 따라, 5️⃣ 장비 상자(pkg)를 열어서 6️⃣ env 장비를 꺼내 테스트장에 쫙 설치합니다. (이 과정을 build_phase라고 합니다.)
6. 장비 설치가 끝나면, 다시 4️⃣번 대본으로 돌아와서 본격적인 레지스터 공격(시뮬레이션)을 시작합니다. (이 과정을 run_phase라고 합니다.)
가장 밑바탕이 되는 물리적인 작업장입니다. 6개 파일 중 유일하게 class(소프트웨어)가 아닌 module(하드웨어)로 작성됩니다.
`include "cfs_algn_test_pkg.sv" // 대본상자 가져오기
module testbench();
import uvm_pkg::*; // UVM library(기본제공함수)불러오기
import cfs_algn_test_pkg::*; // 대본상자 안의 내용물 사용가능하게 불러오기
//CLock signal
reg clk;
//Reset signal - active low (0일때 reset)
reg reset_n;
//Clock generator
initial begin
clk = 0;
forever begin
//Generate an 100MHz clock
clk = #5ns ~clk;
end
end
//Initial reset generator
initial begin
reset_n = 1;
#6ns;
reset_n = 0;
#30ns;
reset_n = 1;
end
initial begin
$dumpfile("dump.vcd"); // waveform
$dumpvars;
//Start UVM test and phases
run_test(""); // UVM system 실행, 괄호가 빈 이유는 시뮬레이터 실행
end // 할때 터미널 창에서 수동으로 입력 받기 위함
//Instantiate the DUT // DUT 장착
cfs_aligner dut(
.clk( clk),
.reset_n(reset_n)
);
endmodule
테스트장에 설치할 첨단 장비들(센서, 모니터, 채점기 등)을 정의하는 곳입니다.
'종합 검증 장비 세트(빈 상자)'를 만드는 6️⃣번 파일
`ifndef CFS_ALGN_ENV_SV // ifndef(If Not Defined): "만약 CFS_ALGN_ENV_SV라는 이름표가
`define CFS_ALGN_ENV_SV // 아직 안 붙어있다면, 지금 붙이고(define) 코드를 읽어라
class cfs_algn_env extends uvm_env; // 우리가 설계할 검증 환경의 이름을 cfs_algn_env로 지음
// uvm_env(환경용 표준상자) 상속
`uvm_component_utils(cfs_algn_env) // UVM Factory system에 등록
function new(string name = "", uvm_component parent);
super.new(name, parent); //부모 클래스(uvm_env)의 생성자에게 그 이름과 족보 정보를 그대로 전달
endfunction
endclass
`endif
아직 Agent(데이터 입력기)나 Scoreboard(자동 채점기) 같은 진짜 장비들을 하나도 넣지 않은 '텅 빈 껍데기 상자'를 방금 막 종이접기로 완성한 상태
생성자(constructor)
`ifndef CFS_ALGN_PKG_SV //중복 방지 보호막 (Include Guards)
`define CFS_ALGN_PKG_SV
`include "uvm_macros.svh" //UVM에서 제공하는 유용한 단축키(매크로)들을
// 이 파일 안에서 쓸 수 있게 가져옵니다.
package cfs_algn_pkg; //패키지 선언 (Package Declaration)
// 패키지 내부
import uvm_pkg::*; // 이 상자(cfs_algn_pkg) 안에서 UVM의 기본 클래스(uvm_env 등)를
// 인식할 수 있도록 UVM 라이브러리를 통째로 수입(Import)합니다.
`include "cfs_algn_env.sv" // 방금 전 우리가 살펴봤던 6️⃣번 파일(빈 환경 클래스)의 내용물을
// 이 패키지 상자 안으로 물리적으로 복사해 넣습니다.
endpackage
`endif
장비 세팅이 끝났으니, 이제 "어떤 방식"으로 칩을 괴롭힐지 시나리오 대본을 작성하는 곳입니다.
앞서 만든 공통 뼈대 대본(base)과 실제 액션 대본(reg_access)을 이 상자 하나에 깔끔하게 포장해 두는 역할을 합니다. 이렇게 해두면 최상위 하드웨어인 testbench.sv에서는 이 상자 하나만 import 하면 모든 테스트 대본을 사용할 수 있게 됩니다.
ifndef CFS_ALGN_TEST_PKG_SV
`define CFS_ALGN_TEST_PKG_SV
`include "uvm_macros.svh" //외부 장비 상자 연결하기 (Dependency)
`include "cfs_algn_pkg.sv" // 장비 상자 파일 불러오기
package cfs_algn_test_pkg; // 이 아래에 있는 클래스들은 모두 test_pkg라는 이름의 상자에 소속됨
import uvm_pkg::*;
import cfs_algn_pkg::*;
`include "cfs_algn_test_base.sv" // 대본들을 순서대로 상자에 넣기 (Compilation Order)
`include "cfs_algn_test_reg_access.sv"
endpackage
`endif
package cfs_algn_test_pkg;: 이제부터 이 아래에 있는 클래스들은 모두 test_pkg라는 이름의 상자에 소속됨을 선언합니다.
import cfs_algn_pkg::*;: 위에서 불러온 장비 상자 안의 내용물(클래스들)을 이 대본 상자 안에서도 자유롭게 쓸 수 있도록 수입(Import)해 줍니다.
`ifndef CFS_ALGN_TEST_BASE_SV
`define CFS_ALGN_TEST_BASE_SV
class cfs_algn_test_base extends uvm_test; //이 클래스가 UVM의 최고 권위자인 '테스트 대본'임을 선언
//Environment instance
cfs_algn_env env; // 포인터 선언, env라는 이름표를 가진 장비 세트를 조종할거야
`uvm_component_utils(cfs_algn_test_base) //UVM factory 등록
function new(string name = "", uvm_component parent);
super.new(name, parent); // 이름과, 족보를 물려받아 초기화
// 내가 태어나기(new) 전에, 내 부모인 uvm_test 당신 먼저 기본 세팅을 완료해 주세요!"
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase); // 시뮬레이션 시간이 0초일때 가장 먼저 실행되는 단계
// 테스트벤치(testbench.sv)에서 run_test()를 외치면,
// UVM 시스템은 가장 먼저 이 build_phase 함수를 찾아내서 실행시킵니다.
env = cfs_algn_env::type_id::create("env", this);
endfunction
endclass
`endif
`ifndef CFS_ALGN_TEST_REG_ACCESS_SV
`define CFS_ALGN_TEST_REG_ACCESS_SV
class cfs_algn_test_reg_access extends cfs_algn_test_base; // base 상속
`uvm_component_utils(cfs_algn_test_reg_access)
function new(string name = "", uvm_component parent);
super.new(name, parent);
endfunction
virtual task run_phase(uvm_phase phase);
phase.raise_objection(this, "TEST_DONE"); // 1. 발언권 획득 (끝내지 마!)
#(100ns); // 2. 실제 테스트 시간 소모
`uvm_info("DEBUG", "this is the end of the test", UVM_LOW) //3.메시지 출력
phase.drop_objection(this, "TEST_DONE"); // 4. 발언권 반납 (이제 끝내도 돼!)
endtask
endclass
`endif
virtual task run_phase(uvm_phase phase);
Objection