시리즈: 아이템 · 인터페이스 · 드라이버 · 모니터
┌─────────────────────────────────────────────────────────┐
│ interface axi4_if │
│ │
│ 파라미터: DATA_WIDTH, ADDR_WIDTH, STRB_WIDTH, ID_WIDTH │
│ 포트: clk, rst (외부에서 주입) │
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ raw 신호 5채널 (AW / W / B / AR / R) │ │
│ └────────────────────────────────────────────────┘ │
│ ↑ ↑ │
│ ┌──────┴───────┐ ┌──────┴───────┐ │
│ │ master_cb │ │ monitor_cb │ │
│ │(드라이브+샘플)│ │ (샘플 전용) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ modport master modport monitor │
└─────────┼──────────────────────────┼────────────────────┘
↓ ↓
axi4_driver axi4_monitor
핵심: 같은 신호를 두 개의 clocking block이 다른 방향/역할로 바라봄
interface axi4_if #(
parameter int DATA_WIDTH = 32,
parameter int ADDR_WIDTH = 16,
parameter int STRB_WIDTH = (DATA_WIDTH/8),
parameter int ID_WIDTH = 8
) (
input logic clk,
input logic rst
);
| 파라미터 | 값 | 의미 | 파생 결과 |
|---|---|---|---|
DATA_WIDTH | 32 | 데이터 버스 폭 | beat 하나 = 4바이트 |
ADDR_WIDTH | 16 | 주소 폭 | 64KB 주소 공간 |
STRB_WIDTH | 4 | DATA_WIDTH/8 | 바이트 레인 4개 |
ID_WIDTH | 8 | 트랜잭션 ID 폭 | 최대 256개 ID |
2^16 = 64KB, 4KB 페이지 = 16개
0x0000~0x0FFF, 0x1000~0x1FFF, ..., 0xF000~0xFFFF
→ seq_item의 c_4k constraint가 이 16개 경계를 지키게 함
→ 주소 공간이 작아서 경계 위반 시나리오가 자주 발생 (좋은 검증 환경)
DATA_WIDTH만 바꾸면 STRB_WIDTH가 자동으로 따라옴
DATA_WIDTH=32 → STRB=4, DATA=64 → STRB=8
→ 두 개를 따로 지정하다 실수로 안 맞추는 버그 방지
→ 다만 localparam으로 선언하면 오버라이드까지 막을 수 있어 더 안전
인터페이스는 클록을 생성하지 않음! TB Top에서 만들어 주입.
인터페이스 안에서 만들면 인스턴스마다 클록이 생겨 동기화 문제 발생.
rst 극성: monitor의 @(negedge vif.rst) → Active-HIGH 리셋
awlen [7:0] ← 8비트
AXI3: AWLEN[3:0] (최대 16 beats)
AXI4: AWLEN[7:0] (최대 256 beats) ✅
awlock [0:0] ← 1비트
AXI3: AWLOCK[1:0] (normal/exclusive/locked)
AXI4: AWLOCK (locked 삭제) ✅
→ 이 인터페이스는 정확히 AXI4 스펙
AXI3: WID 존재 → 쓰기 데이터 인터리빙 가능
AXI4: WID 제거! → 인터리빙 금지, W beat는 AW와 같은 순서로 연속
→ 그래서 monitor의 mon_w()가 aw_q[0](가장 오래된 것) 하나만
보고 데이터를 채워도 안전한 것!
→ 드라이버가 aw_q와 w_q에 같은 순서로 push하는 것도 이 때문
쓰기 읽기
┌──────────┐ ┌──────────┐
│ AW 채널 │ │ AR 채널 │
└──────────┘ └──────────┘
┌──────────┐ ┌──────────────────┐
│ W 채널 │ │ R 채널 │
│ (데이터) │ │ (데이터 + rresp) │
└──────────┘ └──────────────────┘
┌──────────┐ ↑
│ B 채널 │ 별도 응답 채널 없음
│ (응답) │
└──────────┘
쓰기: W(데이터) + B(응답) = 2채널
읽기: R(데이터+rresp) = 1채널
→ rresp는 beat마다 존재 → monitor mon_r()의 덮어쓰기 이슈 원인
없는 것: awqos/arqos, awregion/arregion, awuser/aruser, wuser/buser/ruser
→ 스펙상 optional, 작은 환경에서는 생략 합리적
→ QoS 기반 아비터를 검증하려면 추가 필요
clocking master_cb @(posedge clk);
default input #1step output #1;
// AW
output awid, awaddr, awlen, awsize, awburst,
awlock, awcache, awprot, awvalid;
input awready;
// W
output wdata, wstrb, wlast, wvalid;
input wready;
// B
input bid, bresp, bvalid;
output bready;
// AR
output arid, araddr, arlen, arsize, arburst,
arlock, arcache, arprot, arvalid;
input arready;
// R
input rid, rdata, rresp, rlast, rvalid;
output rready;
endclocking
input #1step → 엣지 직전 안정된 값 읽기 (Preponed 영역)
output #1 → 엣지 후 1ns에 드라이브
T=10ns (posedge)
│
─────────┼──────┬──────
↑ │ │
#1step │ output #1
(읽기) │ (쓰기 T=11ns)
⚠️
output #1은 절대 시간(1ns). 클록 주기 10ns면 10%로 안전하지만,
2ns 클록이면 50%, 1ns 클록이면 충돌. 고속 클록에서는 재조정 필요.
| 채널 | master_cb output (드라이브) | master_cb input (샘플) |
|---|---|---|
| AW | 페이로드 + awvalid | awready |
| W | 페이로드 + wvalid | wready |
| B | bready | 페이로드 + bvalid |
| AR | 페이로드 + arvalid | arready |
| R | rready | 페이로드 + rvalid |
패턴:
데이터 나가는 채널(AW,W,AR) → 마스터가 VALID 드라이브
데이터 들어오는 채널(B,R) → 마스터가 READY 드라이브
→ VALID는 보내는 쪽, READY는 받는 쪽이 올림 (골든 룰 구조)
vif.master_cb.awready <= 1'b1; // ❌ 컴파일 에러!
// awready는 input으로 선언됨 → 드라이브 불가
clocking monitor_cb @(posedge clk);
default input #1step;
input awid, awaddr, ..., awvalid, awready;
input wdata, wstrb, wlast, wvalid, wready;
input bid, bresp, bvalid, bready;
input arid, araddr, ..., arvalid, arready;
input rid, rdata, rresp, rlast, rvalid, rready;
endclocking
차이 1 — output이 하나도 없음
default input #1step; ← output skew조차 없음
→ 물리적으로 어떤 신호도 드라이브 불가 → 진짜 passive 보장 ✅
vif.monitor_cb.awvalid <= 1; // ❌ 컴파일 에러
차이 2 — VALID와 READY를 둘 다 봄
input awvalid, awready; ← 둘 다!
→ 그래서 if(awvalid && awready)로 핸드셰이크 감지 가능
→ 모니터가 트랜잭션을 감지하는 원리의 근거
차이 3 — 마스터/슬레이브 구분 없음
모든 신호를 평등하게 input → 어디에 붙여도 동작 → 재사용성 ✅
awvalid: master_cb=output(드라이브), monitor_cb=input(샘플)
→ 드라이버 1개, 샘플러 1개 → 충돌 없음 ✅
(monitor_cb에 output이 없어서 multiple driver 문제 없음)
modport master (clocking master_cb, input clk, rst);
modport monitor (clocking monitor_cb, input clk, rst);
각 modport가 노출하는 것:
✅ 해당 clocking block
✅ clk, rst (읽기 전용)
❌ raw 신호 직접 접근 불가!
목적: raw 신호로 clocking block을 우회하는 실수를 컴파일 타임에 차단
vif.awvalid <= 1; // ❌ (스큐 우회 = race 위험)
vif.master_cb.awvalid <= 1; // ✅ 이것만 허용
⚠️ 현재 UVM 클래스는
virtual axi4_if vif;로 modport 없는 순수 타입을 씀.
virtual axi4_if.monitor vif;처럼 modport를 명시하면 안전성이 올라감.
slave modport가 없는 이유: DUT가 plain Verilog라 개별 신호로 직접 연결.
UVM Slave Agent를 만들려면modport slave추가 필요.