AXI4 UVM (4) — axi4_monitor

Seungyun Lee·2026년 7월 30일

AXI4_UVM_FULL

목록 보기
7/16

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


axi4_monitor — 채널 병렬 옵저버

전체 구조

        AXI 버스 (5채널)
             │
   ┌─────┬───┼───┬─────┬─────┐
   ↓     ↓   ↓   ↓     ↓     ↓
mon_aw mon_w mon_b mon_ar mon_r   ← 5개 스레드 병렬
   │     │     │     │     │
   └──aw_q──w_done_q──┘     │
         (write 파이프)      │
                    ar_q ────┘
                          │
                          ↓
                    ap.write(tr) → Scoreboard / Coverage

핵심: 채널마다 독립 스레드 + 큐(FIFO)로 phase를 이어붙임
(드라이버가 "구동"을 채널 병렬로 했듯, 모니터는 "관찰"을 채널 병렬로)


왜 이 구조인가

순차 "AW → W → B" 루프의 함정:

T=0: AW #1 잡음
T=1: AW #2 발생 → W beat 수집 중이라 못 봄! ❌
...

→ Outstanding 발생 순간 조용히 데이터 유실 (에러도 안 남)

채널 병렬:
  mon_aw는 오직 AW만 감시 → 절대 안 놓침
  큐로 phase 연결 → Outstanding depth 몇이든 캡처 ✅

큐 3개 — 모니터의 심장

protected axi4_seq_item aw_q[$];      // AW 봤음, W 아직 안 끝남
protected axi4_seq_item w_done_q[$];  // W 끝남, B 기다리는 중
protected axi4_seq_item ar_q[$];      // AR 봤음, R 아직 안 끝남
Write 생애:  AW핸드셰이크 → [aw_q] → (WLAST) → [w_done_q] → (B) → ap.write()
Read 생애:   AR핸드셰이크 → [ar_q] → (RLAST) → ap.write()
             (읽기는 R에 RRESP 같이 오므로 B 단계 불필요)

run_phase() — 5개 스레드

task run_phase(uvm_phase phase);
    @(negedge vif.rst);      // 리셋 해제 대기 (X 관찰 방지)
    fork
        mon_aw();  mon_w();  mon_b();  mon_ar();  mon_r();
    join                     // 전부 forever → 시뮬 끝까지 유지
endtask
join 선택 이유: 5개 태스크 전부 forever → 절대 안 끝남
              → join이 영원히 블록 → run_phase가 시뮬 끝까지 유지
              (join_none이면 phase 종료로 판단해 스레드가 죽을 수 있음)

모니터는 objection을 걸지 않음 (Driver/Sequence가 관리)

mon_aw() — 쓰기 주소 관찰

task mon_aw();
    axi4_seq_item tr;
    forever begin
        @(vif.monitor_cb);
        if (vif.monitor_cb.awvalid && vif.monitor_cb.awready) begin  // 핸드셰이크
            tr       = axi4_seq_item::type_id::create("mon_wr");
            tr.dir   = AXI_WRITE;
            tr.id    = vif.monitor_cb.awid;
            tr.addr  = vif.monitor_cb.awaddr;
            tr.len   = vif.monitor_cb.awlen;
            tr.size  = vif.monitor_cb.awsize;
            tr.burst = axi_burst_e'(vif.monitor_cb.awburst);   // enum cast
            tr.data  = new[tr.len + 1];      // 배열 미리 할당
            tr.strb  = new[tr.len + 1];
            aw_q.push_back(tr);
        end
    end
endtask
@(vif.monitor_cb): Clocking Block의 #1step 덕에 race-free 샘플링
awvalid && awready: 골든 룰 4 → 핸드셰이크 성립 = 주소 전송 완료
enum cast: awburst(2'b01) → AXI_INCR → 로그 가독성 & 비교 편의
new[len+1]: AWLEN=3 → 4 beat → 배열 크기 4

mon_w() — 쓰기 데이터 관찰 (핵심)

task mon_w();
    axi4_seq_item tr;
    int unsigned  beat = 0;      // forever 반복 사이에 값 유지
    forever begin
        @(vif.monitor_cb);
        if (vif.monitor_cb.wvalid && vif.monitor_cb.wready) begin
            if (aw_q.size() == 0)
                `uvm_warning("MON", "W beat with no outstanding AW - ignored")
            else begin
                tr = aw_q[0];            // ⚠️ pop 안 함! 참조만!
                if (beat <= tr.len) begin
                    tr.data[beat] = vif.monitor_cb.wdata;
                    tr.strb[beat] = vif.monitor_cb.wstrb;
                end
                if (vif.monitor_cb.wlast) begin
                    void'(aw_q.pop_front());     // 이제 제거
                    w_done_q.push_back(tr);      // B 대기로 이동
                    beat = 0;
                end
                else beat++;
            end
        end
    end
endtask
핵심 트릭: tr = aw_q[0] (pop 아님!)
  tr은 Handle 복사 → tr 수정 = 큐 안 객체 수정 (의도된 동작!)
     aw_q[0] ──┐
                ├──→ [같은 Object]
     tr ───────┘
  tr.data[beat]=... → 큐 안 객체가 직접 채워짐 ✅

범위 가드 (beat <= tr.len): WLAST 안 오고 beat 계속 오면
  배열 범위 밖 접근 방지

void'(): pop_front() 반환값 안 쓴다는 표시 (경고 억제)

mon_b() — 쓰기 응답 관찰

task mon_b();
    forever begin
        @(vif.monitor_cb);
        if (vif.monitor_cb.bvalid && vif.monitor_cb.bready) begin
            if (w_done_q.size() == 0)
                `uvm_warning("MON", "B response with no completed write burst")
            else begin
                tr      = w_done_q.pop_front();
                tr.resp = axi_resp_e'(vif.monitor_cb.bresp);
                ap.write(tr);        // Scoreboard/Coverage로 발행! 🎉
            end
        end
    end
endtask
B 핸드셰이크 = 쓰기 완료 → w_done_q에서 pop → resp 저장 → ap.write()

ap.write(tr)는 Handle 전달
→ 모니터는 매번 create()로 새 객체 → 재사용 안 함 → 안전
→ 단, Scoreboard가 저장할 땐 copy()로 독립 복사본 필수

mon_ar() / mon_r() — 읽기 채널

// mon_ar: mon_aw와 거의 동일, 유일한 차이:
foreach (tr.strb[j]) tr.strb[j] = '1;   // 읽기는 STRB 없음 → 전부 1

// mon_r: mon_w와 구조 같지만 2가지 차이
tr.resp = axi_resp_e'(vif.monitor_cb.rresp);   // 차이1: 응답이 R에 함께
if (vif.monitor_cb.rlast) begin                // 차이2: RLAST에서 바로 발행
    void'(ar_q.pop_front());
    ap.write(tr);        // B 대기 없이 즉시!
    beat = 0;
end
차이 1 — 응답이 데이터 채널에 함께
  읽기는 별도 응답 채널 없음 → RLAST 시점에 바로 ap.write()

차이 2 — RRESP는 beat마다 옴 (⚠️ 이슈)
  tr.resp = ... 를 매 beat 덮어씀
  → 중간 beat의 SLVERR가 마지막 OKAY에 덮여 사라질 수 있음

타임라인 — Outstanding 2개

사이클  버스 이벤트     큐 상태
─────  ─────────────  ─────────────────────
 1     AW #1          aw_q=[tr1]
 2     AW #2          aw_q=[tr1, tr2]   ← 순차 구현이면 유실!
 3     W beat (tr1)   aw_q=[tr1, tr2]
 4     W WLAST (tr1)  aw_q=[tr2], w_done_q=[tr1]
 5     W beat (tr2)   aw_q=[tr2], w_done_q=[tr1]
 6     W WLAST (tr2)  aw_q=[], w_done_q=[tr1, tr2]
 7     B #1           w_done_q=[tr2]  → tr1 발행
 8     B #2           w_done_q=[]     → tr2 발행

모니터 구조 다이어그램

스레드 구조 & 데이터 흐름

채널마다 독립 스레드로 관찰하고, 큐로 phase를 이어붙여 Outstanding 트랜잭션을 유실 없이 캡처.

Write 트랜잭션 생애 (큐 이동)

읽기는 더 단순 (B 단계 없음):
AR 핸드셰이크 → ar_q → R beats 채움 → RLAST에서 바로 ap.write()
(R 채널에 RRESP가 같이 오므로 별도 응답 채널 불필요)

mon_w 내부 로직 (가장 복잡한 스레드)

핵심 트릭: tr = aw_q[0] (pop 아님!)
  → Handle 복사라 tr 수정 = 큐 안 객체 직접 채움
  → WLAST에서야 pop_front()로 제거

드라이버 vs 모니터 큐 방향 대비

드라이버: 큐 → 버스  (pop 후 구동)
모니터:   버스 → 큐  ([0] 참조하며 채우고 발행)
방향이 정확히 반대

전체 마인드맵

mindmap
  root((axi4_monitor))
    설계철학
      채널 병렬 관찰
      순차 루프는 Outstanding 유실
      passive - monitor_cb만 사용
    5개 스레드
      mon_aw - 쓰기 주소
      mon_w - 쓰기 데이터
      mon_b - 쓰기 응답
      mon_ar - 읽기 주소
      mon_r - 읽기 데이터
    3개 상관 큐
      aw_q - AW봤음 W채우는중
      w_done_q - W끝남 B대기
      ar_q - AR봤음 R채우는중
    핵심 트릭
      "[0] 참조 - pop 안 함"
      Handle 복사로 직접 채움
      WLAST/RLAST에서 발행
    이슈
      같은 사이클 AW+W 레이스
      RRESP 덮어쓰기
      ID 재정렬 미지원

profile
Design Verification engineer

0개의 댓글