AXI4 UVM (11) — coverage

Seungyun Lee·2026년 7월 30일

AXI4_UVM_FULL

목록 보기
14/16

시리즈: ... · base_test · sequences · coverage


Coverage란

"우리가 실제로 무엇을 검증했나?"를 추적하는 컴포넌트

스코어보드와는 다른 질문을 던짐:
  스코어보드: "맞았나?" (correctness)
  커버리지:   "이 시나리오를 실행했나?" (completeness)

env에서 monitor의 analysis port에 스코어보드와 함께 연결됐던 그 커버리지다. 같은 트랜잭션을 받아서 "어떤 조합을 봤는지" 체크리스트를 채운다.

  • Code Coverage: which lines/branches/states executed (what the RTL DID)
  • Functional Coverage: which spec requirements were exercised (What the RTL was supposed to do)
  • Assertion Coverage: which assertions actually fired

왜 커버리지가 필요한가

스코어보드만 있으면:
  "실행한 것"은 다 맞는지 확인함
  근데 "무엇을 안 실행했는지"는 모름!

예:
  WRAP 버스트를 한 번도 안 보냈으면?
  → 스코어보드는 "통과"만 보고함 (WRAP 관련 검증 0)
  → "우리가 WRAP을 검증했나?" 답을 못 함

커버리지:
  "WRAP 봤음/안 봤음"을 체크리스트로 추적
  → 안 본 시나리오(구멍)를 드러냄
  → 검증의 완결성(completeness) 측정

이게 이력서의 "100% functional coverage"가 측정하는 것이다.


클래스 선언 — uvm_subscriber

class axi4_coverage extends uvm_subscriber #(axi4_seq_item);
    `uvm_component_utils(axi4_coverage)

    axi4_seq_item   tr;
    axi_strb_kind_e strb_kind;
    int unsigned    n_sampled;

uvm_subscriber — analysis port 받는 전용 베이스

uvm_subscriber #(T):
  → 내장 analysis_export를 가짐
  → write() 함수만 구현하면 트랜잭션을 받음

스코어보드는 uvm_analysis_imp를 수동 선언했는데
커버리지는 uvm_subscriber라 analysis_export가 이미 내장

env의 연결:
  agent.mon.ap.connect(cov.analysis_export);  ← 이 export가 내장된 것
uvm_subscriber = "analysis port 하나만 받으면 되는 컴포넌트"용 축약형
  → write() 하나만 구현하면 끝

strb_kind enum — 파생 값

typedef enum { STRB_NONE, STRB_PARTIAL, STRB_FULL } axi_strb_kind_e;
write strobe의 "전체 모양"을 3가지로 분류:
  STRB_NONE    : 아무 lane도 안 씀
  STRB_PARTIAL : 일부만 씀
  STRB_FULL    : 모든 유효 lane을 다 씀

→ covergroup이 이 분류를 커버 (strobe 다양성 측정)

covergroup — 커버리지 정의 (핵심)

covergroup axi_cg;
    option.per_instance = 1;
    option.name         = "axi4_functional_coverage";
    ...
endgroup
covergroup = "무엇을 추적할지" 정의하는 묶음
  coverpoint = 개별 추적 항목
  cross      = 항목들의 조합 추적
  bins       = 각 항목의 세부 카테고리

option.per_instance = 1 → 인스턴스별로 커버리지 따로 집계

Where should covergroups be sampled? => Monitor

coverpoint 하나씩

cp_dir — 방향

cp_dir: coverpoint tr.dir {
    bins rd = {AXI_READ};
    bins wr = {AXI_WRITE};
}
읽기와 쓰기를 각각 봤나?
  bins rd → 읽기 트랜잭션이 오면 체크
  bins wr → 쓰기 트랜잭션이 오면 체크
둘 다 체크돼야 100%

cp_burst — 버스트 타입

cp_burst: coverpoint tr.burst {
    bins fixed = {AXI_FIXED};
    bins incr  = {AXI_INCR};
    bins wrap  = {AXI_WRAP};
}
FIXED/INCR/WRAP 각각 봤나?
→ WRAP bin은 wrap_seq/cov_seq에서만 채워짐 (일반 시퀀스는 WRAP 제외)

cp_size — 전송 크기 (ignore_bins 등장)

cp_size: coverpoint tr.size {
    bins bytes1 = {0};
    bins bytes2 = {1};
    bins bytes4 = {2};
    ignore_bins too_wide = {[3:7]};   // impossible on a 32-bit bus
}
size 0(1B)/1(2B)/2(4B) 각각 봤나?

ignore_bins too_wide = {[3:7]}
  → size 3~7은 32비트 버스에서 불가능 (8B 이상)
  → "일부러 무시" → 커버리지 계산에서 제외
  → 안 그러면 영원히 100% 못 채움 (도달 불가능한데 카운트되니까)

이게 ignore_bins의 핵심 용도예요.

ignore_bins = "구조적으로 도달 불가능한 것을 명시적으로 제외"
→ 커버리지 구멍이 아니라 "일부러 뺀 것"임을 문서화

cp_len — 버스트 길이

cp_len: coverpoint tr.len {
    bins single   = {0};            // 1 beat
    bins len_2_4  = {[1:3]};
    bins len_5_8  = {[4:7]};
    bins len_9_16 = {[8:15]};
    ignore_bins beyond = {[16:255]}; // stimulus is constrained to <=16 beats
}
길이를 클래스로 묶어서 추적:
  single / 2-4 / 5-8 / 9-16 beats

ignore_bins beyond = {[16:255]}
  → 자극이 16 beats 이하로 제약됨 → 그 위는 안 나옴 → 무시

범위 bins:
  {[1:3]} = 1,2,3 중 아무거나 오면 이 bin 체크

cp_resp — 응답 (문서화된 DUT 한계)

cp_resp: coverpoint tr.resp {
    bins okay = {AXI_OKAY};
    ignore_bins unreachable = {AXI_EXOKAY, AXI_SLVERR, AXI_DECERR};
}
주석: "The DUT hardwires bresp/rresp to OKAY, so error responses are
       unreachable by construction."

DUT가 항상 OKAY만 응답 → 에러 응답은 절대 안 나옴
→ ignore_bins으로 제외 (DUT 한계를 문서화)
→ "커버리지 구멍이 아니라 의도된 것"

cp_region — 주소 영역

cp_region: coverpoint tr.addr[15:12] {
    bins region[16] = {[0:15]};
}
addr[15:12] = 상위 4비트 = 4KB 영역 구분 (64KB / 4KB = 16개)

bins region[16] = {[0:15]}
  → 16개 bin 자동 생성 (region[0]~region[15])
  → 각 4KB 영역에 접근했나 추적

[15:12] 뽑는 이유:
  64KB 공간을 4KB로 나누면 상위 4비트가 영역 번호
  0x0xxx→0, 0x1xxx→1, ..., 0xFxxx→15

cp_strb — strobe 모양 (조건부 커버)

cp_strb: coverpoint strb_kind iff (tr.dir == AXI_WRITE) {
    bins none    = {STRB_NONE};
    bins partial = {STRB_PARTIAL};
    bins full    = {STRB_FULL};
}
iff (tr.dir == AXI_WRITE)
  = "쓰기일 때만 이 coverpoint를 샘플"
  → 읽기는 strobe가 의미 없으니 (읽기는 전부 1로 채움) 제외

none/partial/full 각각 봤나 → strobe 다양성 측정
iff = 조건부 커버리지 (이 조건일 때만 카운트)

Cross Coverage

x_burst_size : cross cp_burst, cp_size;

x_burst_len  : cross cp_burst, cp_len {
    ignore_bins wrap_single = binsof(cp_burst.wrap) && binsof(cp_len.single);
}

x_dir_burst  : cross cp_dir,   cp_burst;

cross = 두 coverpoint의 "조합"을 추적

  • x_burst_size: burst 3종 × size 3종 = 9개 조합 다 봤나?
  • x_burst_len: burst × length 조합
  • x_dir_burst: 방향 × burst 조합

→ 개별로는 다 봤어도 "조합"으로는 안 봤을 수 있음
예: INCR도 봤고 size1도 봤지만 "INCR+size1"은 안 봤을 수 있음
→ cross가 이걸 잡음

Cross coverage tracks combinations of coverpoints. If coverpoint A has 4bins and coverpoint B has 3bins, the cross has 4x3=12bins

State space explosion: Crossing a 32 bit address(4B bins) with a 32 bit data(4B bins) creates 1.8x10^19 bins -> simulator crashes.
-> alwasy reduce coverpoints to rchitecturally siginificant bins before crossing.

cross의 ignore_bins

ignore_bins wrap_single = binsof(cp_burst.wrap) && binsof(cp_len.single);
주석: "A WRAP burst must be 2,4,8,16 beats, so a single-beat WRAP
       cannot exist."

WRAP + single(1 beat) 조합은 불가능 (WRAP은 최소 2 beats)
  binsof(cp_burst.wrap) && binsof(cp_len.single)
  = "burst가 wrap이면서 len이 single인" 교차 bin
  → ignore로 제외 (도달 불가능)

binsof(...) = 특정 bin을 지칭하는 문법

이게 이력서의 "formally justifying structurally unreachable bins via ignore_bins"예요.


커버리지 구조 시각화


new() & write()

function new(string name, uvm_component parent);
    super.new(name, parent);
    axi_cg = new();          // covergroup 인스턴스화
endfunction

function void write(axi4_seq_item t);
    tr        = t;
    strb_kind = classify_strb(t);
    axi_cg.sample();         // 커버리지 샘플!
    n_sampled++;
endfunction
write() = monitor가 ap.write(tr) 하면 자동 호출

1. tr에 받은 트랜잭션 저장 (covergroup이 tr을 읽음)
2. strb_kind 계산 (파생 값)
3. axi_cg.sample() → 현재 tr 값으로 모든 coverpoint 체크!
4. 카운트 증가

axi_cg = new() 필수:
  covergroup은 선언만으로 인스턴스 안 됨 → new()로 생성해야 샘플 가능
sample()이 하는 일:
  현재 tr.dir, tr.burst, tr.size... 값을 보고
  해당하는 bin을 체크 (예: tr.burst=INCR이면 cp_burst.incr 체크)

classify_strb() — strobe 분류

protected function axi_strb_kind_e classify_strb(axi4_seq_item t);
    bit all_full = 1;
    bit all_zero = 1;
    foreach (t.strb[i]) begin
        if (t.strb[i] !== t.lane_mask(i)) all_full = 0;
        if (t.strb[i] !== '0)             all_zero = 0;
    end
    if (all_zero) return STRB_NONE;
    if (all_full) return STRB_FULL;
    return STRB_PARTIAL;
endfunction
각 beat의 strobe를 검사:

all_full: 모든 beat가 자기 유효 lane을 다 켰나?
  strb[i] == lane_mask(i) → 그 beat는 full
  하나라도 아니면 all_full = 0

all_zero: 모든 beat가 아무것도 안 켰나?
  strb[i] == 0 → 그 beat는 empty
  하나라도 아니면 all_zero = 0

판정:
  전부 0 → STRB_NONE
  전부 full → STRB_FULL
  그 외 → STRB_PARTIAL

lane_mask(i) = seq_item의 그 함수! (유효 lane 계산)

coverage_report() — 자체 리포트

function string coverage_report();
    coverage_report = {
        "=========== AXI4 functional coverage ===========\n",
        $sformatf("  bursts sampled : %0d\n", n_sampled),
        $sformatf("  %-14s %7.2f %%\n", "cp_dir", axi_cg.cp_dir.get_inst_coverage()),
        ...
        $sformatf("  %-14s %7.2f %%\n", "TOTAL", axi_cg.get_inst_coverage())
    };
endfunction

주석에 중요한 실무 이유가 있다:

// Vivado's external report generator (xcrg) needs a PRO license tier,
// which the free BASIC tier does not have — so we build the report
// from the SystemVerilog coverage API instead.
Vivado 무료 버전은 커버리지 리포트 생성기(xcrg)가 없음
→ SystemVerilog 커버리지 API로 직접 리포트 작성
→ 어떤 시뮬레이터에서도 동작, 무료

get_inst_coverage() = 그 coverpoint/cross의 현재 커버리지 %
  cp_dir.get_inst_coverage() → cp_dir이 몇 % 채워졌나
  axi_cg.get_inst_coverage() → 전체 covergroup 몇 %

출력 예:

=========== AXI4 functional coverage ===========
  bursts sampled : 351
  ---------------------------------------------
  cp_dir          100.00 %
  cp_burst        100.00 %
  cp_size         100.00 %
  cp_len          100.00 %
  cp_resp         100.00 %
  cp_region       100.00 %
  cp_strb         100.00 %
  ---------------------------------------------
  x_burst_size    100.00 %
  x_burst_len     100.00 %
  x_dir_burst     100.00 %
  =============================================
  TOTAL           100.00 %
===============================================

이게 이력서의 "100% functional + cross coverage" 증거 출력이에요.


report_phase()

function void report_phase(uvm_phase phase);
    super.report_phase(phase);
    `uvm_info("COV", coverage_report(), UVM_LOW)
endfunction
시뮬레이션 끝에 커버리지 리포트 출력
→ 스코어보드의 report_phase(미스매치 개수)와 나란히 로그에 찍힘

두 개를 같이 보면:
  스코어보드: "다 맞았나?" (0 mismatch)
  커버리지:   "다 봤나?" (100%)
  → 둘 다 만족해야 검증 완료

스코어보드 vs 커버리지 — 두 질문

ScoreboardCoverage
질문맞았나? (correctness)봤나? (completeness)
베이스uvm_scoreboarduvm_subscriber
핵심ref_model과 비교covergroup 샘플
실패 시uvm_error (버그)낮은 % (검증 부족)
목표0 mismatch100%

왜 둘 다 필요한가 (매우 중요)

커버리지 100%인데 스코어보드 약함:
  → 다 실행했지만 틀린 걸 못 잡음 (체커 부실)

스코어보드 강한데 커버리지 낮음:
  → 실행한 건 정확히 검증하지만 안 본 게 많음 (자극 부족)

→ 둘 다 있어야 "많이 실행하고(coverage) 정확히 검증한다(scoreboard)"

이력서의 blind pass criterion 버그가 이 교훈:
  커버리지는 높은데 체커가 눈멀어있었음
  → coverage ≠ correctness

한 줄 요약

Coverage = "우리가 무엇을 검증했나"를 추적하는 수집기

uvm_subscriber로 monitor의 ap를 받아
  covergroup을 sample() → 각 조합을 봤는지 체크리스트 채움

coverpoint: dir/burst/size/len/region/strb 개별 추적
cross:      조합 추적 (burst×size, burst×len, dir×burst)
ignore_bins: 도달 불가능한 것 제외 (구멍 아니라 의도)

스코어보드와 다른 질문:
  스코어보드 = "맞았나?"
  커버리지   = "봤나?"
  → 둘 다 만족해야 검증 완료

시리즈 완결 — 전체 UVM 환경

이제 AXI4 UVM 환경의 모든 파일을 다뤘습니다.

                        tb_top (조립 & 시작)
                            │
                    test (지휘, get_seq)
                            │
                    sequence (자극 생성)
                            │
                    seq_item (트랜잭션 단위)
                            │
        cfg/agent/env (계층) ─ driver (구동)
                            │
                    interface (신호+clocking)
                            │
                          DUT
                            │
                    monitor (관찰)
                            │
              ┌─────────────┴─────────────┐
        scoreboard (맞나?)          coverage (봤나?)
              │                           │
         ref_model                   covergroup
              └─────────────┬─────────────┘
                       pass/fail + 100%

검증의 두 축이 완성됩니다:

  • 정확성(correctness): scoreboard가 ref_model과 비교
  • 완결성(completeness): coverage가 모든 조합을 추적

이 둘이 만나서 "많이 실행하고, 정확히 검증했다"는 신뢰를 만듭니다.

profile
Design Verification engineer

0개의 댓글