시리즈: ... · base_test · sequences · coverage
"우리가 실제로 무엇을 검증했나?"를 추적하는 컴포넌트
스코어보드와는 다른 질문을 던짐:
스코어보드: "맞았나?" (correctness)
커버리지: "이 시나리오를 실행했나?" (completeness)
env에서 monitor의 analysis port에 스코어보드와 함께 연결됐던 그 커버리지다. 같은 트랜잭션을 받아서 "어떤 조합을 봤는지" 체크리스트를 채운다.
스코어보드만 있으면:
"실행한 것"은 다 맞는지 확인함
근데 "무엇을 안 실행했는지"는 모름!
예:
WRAP 버스트를 한 번도 안 보냈으면?
→ 스코어보드는 "통과"만 보고함 (WRAP 관련 검증 0)
→ "우리가 WRAP을 검증했나?" 답을 못 함
커버리지:
"WRAP 봤음/안 봤음"을 체크리스트로 추적
→ 안 본 시나리오(구멍)를 드러냄
→ 검증의 완결성(completeness) 측정
이게 이력서의 "100% functional coverage"가 측정하는 것이다.
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 #(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() 하나만 구현하면 끝
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 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
cp_dir: coverpoint tr.dir {
bins rd = {AXI_READ};
bins wr = {AXI_WRITE};
}
읽기와 쓰기를 각각 봤나?
bins rd → 읽기 트랜잭션이 오면 체크
bins wr → 쓰기 트랜잭션이 오면 체크
둘 다 체크돼야 100%
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: 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: 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: 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: 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: 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 = 조건부 커버리지 (이 조건일 때만 카운트)
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의 "조합"을 추적
→ 개별로는 다 봤어도 "조합"으로는 안 봤을 수 있음
예: 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.
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"예요.

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 체크)
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 계산)
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" 증거 출력이에요.
function void report_phase(uvm_phase phase);
super.report_phase(phase);
`uvm_info("COV", coverage_report(), UVM_LOW)
endfunction
시뮬레이션 끝에 커버리지 리포트 출력
→ 스코어보드의 report_phase(미스매치 개수)와 나란히 로그에 찍힘
두 개를 같이 보면:
스코어보드: "다 맞았나?" (0 mismatch)
커버리지: "다 봤나?" (100%)
→ 둘 다 만족해야 검증 완료

| Scoreboard | Coverage | |
|---|---|---|
| 질문 | 맞았나? (correctness) | 봤나? (completeness) |
| 베이스 | uvm_scoreboard | uvm_subscriber |
| 핵심 | ref_model과 비교 | covergroup 샘플 |
| 실패 시 | uvm_error (버그) | 낮은 % (검증 부족) |
| 목표 | 0 mismatch | 100% |
커버리지 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: 도달 불가능한 것 제외 (구멍 아니라 의도)
스코어보드와 다른 질문:
스코어보드 = "맞았나?"
커버리지 = "봤나?"
→ 둘 다 만족해야 검증 완료
이제 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%
검증의 두 축이 완성됩니다:
이 둘이 만나서 "많이 실행하고, 정확히 검증했다"는 신뢰를 만듭니다.