AXI4-Lite Verification

Seungyun Lee·2026년 5월 12일

AXI4-Lite Verification

목록 보기
3/3

Block Diagram

File Descprition

1. rtl/ 폴더 (검증 대상 하드웨어)

axi4_lite_slave.sv

  • 우리가 검증해야 할 타겟 하드웨어, 즉 DUT(Design Under Test)입니다. 다이어그램 상의 AXI4-LITE SLAVE 블록에 해당합니다.

2. tb/ 폴더 (테스트벤치 최상단 및 환경 설정)

axi4_lite_if.sv

  • UVM 테스트벤치와 DUT 사이를 연결해주는 핀(Pin)들의 묶음인 인터페이스(Interface)입니다. 다이어그램 중앙의 AWADDR, WDATA 등의 신호들이 정의되어 있습니다.

tb_top.sv

  • 하드웨어(DUT)와 인터페이스를 인스턴스화하고 클럭/리셋을 생성하며, run_test()를 호출하여 UVM 환경을 구동시키는 최상단 모듈입니다.

run.do

  • 시뮬레이터(ModelSim/Questa 등)에서 컴파일 및 시뮬레이션 실행을 자동화하는 매크로/스크립트 파일입니다.

3. src/ 폴더 (UVM 핵심 컴포넌트)

[기본 데이터 및 시퀀스]

sy_transaction.sv
AXI4-Lite 버스를 타고 다니는 데이터 패킷(Address, Data, Control 등)을 정의한 클래스입니다. rand 변수들이 이곳에 선언되어 무작위 검증(CRV)의 기반이 됩니다.

sy_sequence.sv
sy_transaction 객체들을 생성하여 시퀀서(Sequencer)로 보내는 기본적인 트래픽 생성기입니다.

UVM 내장 함수
- start_item(req); (버스 사용 허가 요청)
시퀀서(Sequencer)에게 "나 드라이버한테 보낼 데이터가 생겼는데, 지금 버스(Driver) 써도 돼?
- finish_item(req); (데이터 전송 및 대기)
허락이 떨어지면 드라이버에게 실제로 데이터를 쏴줍니다.
그리고 드라이버가 핀 레벨 제어를 완전히 끝내고 "전송 완료(item_done)" 신호를 보내줄 때까지 또 기다려줍니다.
if (!req.randomize() with { trans_kind == WRITE; })

req.randomize() with { trans_kind == WRITE; }
트랜잭션 내부의 변수들(addr, data 등)에 무작위 값을 채워 넣으라는 명령입니다. 단, with {} 블록을 써서 "종류(trans_kind)는 무조건 WRITE로 고정해라"라는 강제 조건(Inline Constraint)을 달았습니다.

함수의 반환값 (성공=1, 실패=0)
if (! ... ) (안전장치): 앞에 느낌표(!)가 붙었으므로 조건이 뒤집힙니다. 즉, "만약 값 무작위 생성에 실패(0)했다면" 이 if 문 안으로 들어가게 됩니다.

[Virtual Sequencer & Sequences]

여러 시나리오를 지휘하는 역할을 합니다.

sy_virtual_sequencer.sv

  • 하위 시퀀서(sy_sequencer)들을 제어하기 위한 가상 시퀀서입니다.
uvm_sequencer #(sy_transaction) axi_sqr;

오직 AXI 트랜잭션(sy_transaction) 규격의 패킷만 취급하는 전용 우체국(Sequencer)의 연락처를 axi_sqr라는 이름으로 만들겠다

sy_vseq_base.sv

  • 가상 시퀀스들의 공통 속성을 정의한 부모(Base) 클래스입니다.

sy_vseq_sweep.sv

  • 전체 주소 대역을 순차적 혹은 무작위로 훑는(Sweep) 정상적인 통신 시나리오입니다.

sy_vseq_error.sv

  • 다이어그램에 명시된 Error Injection Sequence입니다. 의도적으로 허용되지 않은 주소나 잘못된 제어 신호를 보내 DUT가 에러를 잘 뱉어내는지 테스트합니다.
start_item(tx, -1, axi_sqr)

start_item(보낼 물건, 우선순위, 목적지 우체국);

sy_vseq_rmw.sv

  • Read-Modify-Write 시퀀스입니다. 동일한 주소에 읽고, 수정하고, 다시 쓰는 동작을 스트레스 테스트합니다.
// .read(status, rdata, path, extension, parent)

regmodel.ctrl_reg.read(status, rdata, UVM_FRONTDOOR, null, this);

regmodel.ctrl_reg: regmodel 안에 있는 컨트롤 레지스터ctrl_reg 를 지칭합니다.

[Agent 컴포넌트 (버스와의 직접적인 상호작용)]

sy_agent.sv

  • Sequencer, Driver, Monitor를 하나로 묶어주는 컨테이너 역할을 합니다.

sy_driver.sv

  • 시퀀스로부터 받은 트랜잭션을 실제 AXI4-Lite 프로토콜 타이밍(VALID/READY 핸드셰이크)에 맞춰 인터페이스(핀)에 구동합니다.

sy_monitor.sv

  • 인터페이스(핀)의 신호를 수동적으로 지켜보다가, 유효한 통신이 발생하면 이를 묶어 트랜잭션 객체로 변환한 뒤 Analysis Port를 통해 Scoreboard와 Subscriber로 쏴줍니다.
// Analysis Port
item_collected_port = new("item_collected_port", this);

// run phase
/ 1. 핀 데이터를 트랜잭션 객체에 옮겨 담기
tx.addr = vif.araddr; 
// 2. 안테나를 통해 스코어보드로 던짐
item_collected_port.write(tx);

item_collected_port는 버스에서 수집한 신호들을 sy_transaction 객체로 포장한 뒤, Scoreboard 나 Coverage Collector 등 다른 컴포넌트들에게 Broadcast하기 위해 설치하는 전용 송신 안테나

[검증 및 커버리지 (결과 확인)]

다이어그램 우측의 SCOREBOARD와 COVERAGE COLLECTOR 블록입니다.

sy_scoreboard.sv

  • 모니터가 보내온 데이터를 바탕으로 Reference Model(가짜 메모리, 연관 배열)을 업데이트하고, DUT가 뱉은 실제 값과 예상 값을 비교(Compare)하여 에러를 잡아냅니다.
uvm_analysis_imp #(sy_transaction, sy_scoreboard) item_collected_export;

이전 대화에서 모니터(Monitor)가 데이터를 방송하기 위해 uvm_analysis_port라는 송신 안테나를 세운 것을 확인하셨을 것입니다.

  • 이 uvm_analysis_imp는 그 방송을 수신하는 전용 수신기입니다.
  • 환경(Env) 단에서 모니터의 port와 스코어보드의 export를 선으로 연결(connect)해 주면, 모니터가 데이터를 쏠 때마다 스코어보드의 write() 함수가 자동으로 호출되며 트랜잭션이 넘어오게 됩니다.

Associative Array (연관배열)

logic [31:0] ref_memory [logic [31:0]];
앞부분 logic [31:0] : 가로 길이 (데이터의 크기)
의미: 이 메모리의 한 칸(방)에 들어가는 데이터의 크기가 32비트(4바이트)라는 뜻입니다.
질문자님이 말씀하신 "32비트고"에 해당하는 부분이며, 이는 메모리의 가로폭을 의미합니다.

뒷부분 [logic [31:0]] : 세로 길이 (주소의 개수)
의미: 이 메모리의 방 번호(Index/Address)를 매길 때 32비트짜리 숫자를 쓴다는 뜻입니다.
오해의 원인: 세로로 32개가 있는 것이 아닙니다. 32비트로 표현할 수 있는 숫자의 경우의 수는 $2^{32}$입니다. 즉, 세로로 최대 4,294,967,296개(약 43억 개)의 방을 가질 수 있다는 뜻입니다
만약 코드를 일반적인 하드웨어 배열 문법으로 짰다면 이렇게 됩니다.
logic [31:0] ref_memory [0:4294967295];

sy_subscriber.sv

  • 100% 검증이 완료되었는지 확인하기 위해 Functional Coverage를 수집(covergroup, coverpoint)하는 컴포넌트입니다.

[RAL (Register Abstraction Layer) - 레지스터 자동화 검증]

다이어그램에는 생략되어 있지만 매우 중요한 고급 UVM 기능입니다.

sy_reg_block.sv

  • DUT 내부의 레지스터 맵(주소, 권한 등)을 소프트웨어적으로 모델링한 파일입니다.
status_reg.configure(this, null);

.configure(...): UVM이 제공하는 함수로, 방금 갓 태어난 레지스터에게 "너의 부모(Parent)가 누구인지" 지정해 주는 역할을 합니다.

sy_reg_adapter.sv

  • UVM 레지스터 트랜잭션(RAL)과 AXI4-Lite 트랜잭션(sy_transaction) 사이의 형태를 변환해주는 어댑터입니다.

sy_ral_sequence.sv

  • RAL 모델을 사용하여 DUT의 레지스터를 편하게 읽고 쓰는(Front-door/Back-door access) 시퀀스입니다.

[최상위 컴포넌트 및 패키지]

sy_env.sv
Agent, Scoreboard, Subscriber, Virtual Sequencer 등을 인스턴스화하고, 모니터와 스코어보드/서브스크라이버 간의 Analysis Port를 연결(Connect)하는 컨테이너입니다.

sy_test.sv
UVM 최상단 컴포넌트(UVM TEST)입니다. sy_env를 생성하고, 어떤 테스트 시나리오(Virtual Sequence)를 돌릴지 결정하여 실행합니다.

sy_uvm_pkg.sv
위의 모든 src/ 폴더 내의 SV 파일들을 올바른 순서로 include 하여 하나의 패키지로 묶어주는 파일입니다. 컴파일 에러를 방지합니다.

Where is the Seqeuncer?

UVM에서 Sequencer는 특별한 추가 기능(예: 복잡한 중재(Arbitration) 로직이나 시퀀서 전용 변수)이 필요하지 않다면, 굳이 별도의 .sv 파일을 만들지 않는 것이 실무에서도 일반적인 관례입니다.

sy_agent.sv 파일 상단이나 sy_uvm_pkg.sv 내부에 다음과 같이 한 줄로 정의되어 있습니다

uvm_sequencer #(sy_transaction) sqr;

왜 별도의 파일을 만들지 않았나요?
기본 기능만 사용: 단순히 시퀀스(Sequence)에서 받은 아이템을 드라이버(Driver)로 전달하는 '큐(Queue)' 역할만 수행한다면, UVM이 이미 제공하는 uvm_sequencer를 그대로 가져다 쓰기만 하면 됩니다.


Intentional Error Injection & Verification

100% Functional Coverage

profile
Design Verification engineer

0개의 댓글