AXI4 UVM (2) — axi4_if

Seungyun Lee·2026년 7월 30일

AXI4_UVM_FULL

목록 보기
5/16

시리즈: 아이템 · 인터페이스 · 드라이버 · 모니터

axi4_if — 인터페이스

전체 구조

┌─────────────────────────────────────────────────────────┐
│                    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_WIDTH32데이터 버스 폭beat 하나 = 4바이트
ADDR_WIDTH16주소 폭64KB 주소 공간
STRB_WIDTH4DATA_WIDTH/8바이트 레인 4개
ID_WIDTH8트랜잭션 ID 폭최대 256개 ID

ADDR_WIDTH=16과 4KB 규칙

2^16 = 64KB, 4KB 페이지 = 16개
0x0000~0x0FFF, 0x1000~0x1FFF, ..., 0xF000~0xFFFF

→ seq_item의 c_4k constraint가 이 16개 경계를 지키게 함
→ 주소 공간이 작아서 경계 위반 시나리오가 자주 발생 (좋은 검증 환경)

STRB_WIDTH가 파생 파라미터인 이유

DATA_WIDTH만 바꾸면 STRB_WIDTH가 자동으로 따라옴
DATA_WIDTH=32 → STRB=4,  DATA=64 → STRB=8

→ 두 개를 따로 지정하다 실수로 안 맞추는 버그 방지
→ 다만 localparam으로 선언하면 오버라이드까지 막을 수 있어 더 안전

clk, rst가 포트인 이유

인터페이스는 클록을 생성하지 않음! TB Top에서 만들어 주입.
인터페이스 안에서 만들면 인스턴스마다 클록이 생겨 동기화 문제 발생.

rst 극성: monitor의 @(negedge vif.rst) → Active-HIGH 리셋

5채널 신호 — AXI 버전 정보가 드러나는 지점

AW 채널 폭에서 읽는 AXI4 시그니처

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 스펙

W 채널 — WID가 없는 게 포인트!

AXI3: WID 존재 → 쓰기 데이터 인터리빙 가능
AXI4: WID 제거! → 인터리빙 금지, W beat는 AW와 같은 순서로 연속

→ 그래서 monitor의 mon_w()가 aw_q[0](가장 오래된 것) 하나만
  보고 데이터를 채워도 안전한 것!
→ 드라이버가 aw_q와 w_q에 같은 순서로 push하는 것도 이 때문

쓰기 vs 읽기 비대칭

        쓰기                       읽기
   ┌──────────┐              ┌──────────┐
   │ AW 채널  │              │ AR 채널  │
   └──────────┘              └──────────┘
   ┌──────────┐              ┌──────────────────┐
   │ W  채널  │              │ R 채널           │
   │ (데이터) │              │ (데이터 + rresp) │
   └──────────┘              └──────────────────┘
   ┌──────────┐                    ↑
   │ B  채널  │              별도 응답 채널 없음
   │ (응답)   │
   └──────────┘

쓰기: W(데이터) + B(응답) = 2채널
읽기: R(데이터+rresp)     = 1채널

→ rresp는 beat마다 존재 → monitor mon_r()의 덮어쓰기 이슈 원인

빠진 optional 신호

없는 것: awqos/arqos, awregion/arregion, awuser/aruser, wuser/buser/ruser
→ 스펙상 optional, 작은 환경에서는 생략 합리적
→ QoS 기반 아비터를 검증하려면 추가 필요

master_cb — 드라이버용 Clocking Block

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

skew 해석

input #1step  → 엣지 직전 안정된 값 읽기 (Preponed 영역)
output #1     → 엣지 후 1ns에 드라이브

        T=10ns (posedge)
             │
    ─────────┼──────┬──────
       ↑     │      │
    #1step   │    output #1
    (읽기)   │    (쓰기 T=11ns)

⚠️ output #1은 절대 시간(1ns). 클록 주기 10ns면 10%로 안전하지만,
2ns 클록이면 50%, 1ns 클록이면 충돌. 고속 클록에서는 재조정 필요.

방향 설계 — AXI 마스터 역할

채널master_cb output (드라이브)master_cb input (샘플)
AW페이로드 + awvalidawready
W페이로드 + wvalidwready
Bbready페이로드 + bvalid
AR페이로드 + arvalidarready
Rrready페이로드 + rvalid
패턴:
  데이터 나가는 채널(AW,W,AR) → 마스터가 VALID 드라이브
  데이터 들어오는 채널(B,R)   → 마스터가 READY 드라이브

→ VALID는 보내는 쪽, READY는 받는 쪽이 올림 (골든 룰 구조)

컴파일 타임 안전장치

vif.master_cb.awready <= 1'b1;   // ❌ 컴파일 에러!
// awready는 input으로 선언됨 → 드라이브 불가

monitor_cb — 모니터용 Clocking Block

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

master_cb와의 차이 3가지

차이 1 — output이 하나도 없음
  default input #1step;  ← output skew조차 없음
  → 물리적으로 어떤 신호도 드라이브 불가 → 진짜 passive 보장 ✅
  vif.monitor_cb.awvalid <= 1;  // ❌ 컴파일 에러

차이 2 — VALID와 READY를 둘 다 봄
  input awvalid, awready;  ← 둘 다!
  → 그래서 if(awvalid && awready)로 핸드셰이크 감지 가능
  → 모니터가 트랜잭션을 감지하는 원리의 근거

차이 3 — 마스터/슬레이브 구분 없음
  모든 신호를 평등하게 input → 어디에 붙여도 동작 → 재사용성 ✅

두 CB가 같은 신호를 써도 괜찮은 이유

awvalid: master_cb=output(드라이브), monitor_cb=input(샘플)
→ 드라이버 1개, 샘플러 1개 → 충돌 없음 ✅
(monitor_cb에 output이 없어서 multiple driver 문제 없음)

Modport

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 추가 필요.


profile
Design Verification engineer

0개의 댓글