시리즈: ... · agent_cfg/agent/env · base_test · sequences
"어떤 트랜잭션을 어떤 순서로 만들지"를 정의하는 자극 프로그램
test가 get_seq()로 고르는 그 시퀀스들
→ 실제로 아이템(axi4_seq_item)을 randomize해서 시퀀서에 밀어넣음
지금까지: test가 "어떤 시퀀스 쓸지" 골랐다면, 이 파일은 그 시퀀스가 실제로 뭘 하는지다.
axi4_base_seq (부모)
├── num 필드 (몇 개 만들지)
└── wr_then_rd() 헬퍼 (쓰고 같은 영역 읽기)
↓ 상속
axi4_write_seq : N개 랜덤 쓰기
axi4_read_seq : N개 랜덤 읽기
axi4_rand_seq : N개 랜덤 (읽기/쓰기 섞임)
axi4_wr_rd_seq : 쓰고 → 같은 영역 읽기 (self-checking, 기본 시퀀스)
axi4_os_*_seq : outstanding용 (고정 주소 8개)
axi4_cov_seq : 커버리지 채우기 (모든 조합 sweep)
axi4_directed_seq : 손으로 고른 코너 케이스
axi4_wrap_seq : WRAP 버그 재현 (FAIL 예상)
// the general-purpose sequences deliberately exclude AXI_WRAP so
// the normal regression is clean.
일반 시퀀스들은 일부러 AXI_WRAP을 제외함!
이유:
DUT가 WRAP을 선형 증가로 처리하는 버그가 있음
→ 일반 시퀀스에 WRAP을 넣으면 정상 회귀도 FAIL
→ WRAP은 별도 시퀀스/테스트로 격리해서 "일부러" 버그 시연
→ 그래서 대부분 시퀀스에 burst inside {AXI_INCR, AXI_FIXED}
(WRAP 빠짐)
이건 실무에서 중요한 판단이에요. "알려진 버그"를 정상 회귀에서 분리해서, 회귀는 clean하게 유지하고 버그는 별도로 문서화/추적하는 방식.
class axi4_base_seq extends uvm_sequence #(axi4_seq_item);
`uvm_object_utils(axi4_base_seq)
rand int unsigned num = 5; // how many transactions / pairs
function new(string name = "axi4_base_seq");
super.new(name);
endfunction
uvm_sequence #(axi4_seq_item) 상속
→ axi4_seq_item 타입 아이템을 생성/전달하는 시퀀스
num = 생성할 트랜잭션 개수 (기본 5)
→ test에서 s.num = 40 처럼 바꿀 수 있음 (cov_test에서 봤던 것)
task wr_then_rd(bit [AXI_ADDR_WIDTH-1:0] a,
bit [7:0] l,
bit [2:0] s,
axi_burst_e b);
axi4_seq_item wr, rd;
wr = axi4_seq_item::type_id::create("wr");
start_item(wr);
if (!wr.randomize() with { dir == AXI_WRITE; addr == a;
len == l; size == s; burst == b; })
`uvm_error("RAND", "directed write randomize() failed")
finish_item(wr);
rd = axi4_seq_item::type_id::create("rd");
start_item(rd);
if (!rd.randomize() with { dir == AXI_READ; addr == a;
len == l; size == s; burst == b;
id == wr.id; })
`uvm_error("RAND", "directed read randomize() failed")
finish_item(rd);
endtask
"쓰고 → 정확히 같은 영역 읽기" 한 세트
같은 addr/len/size/burst로:
1. 쓰기 아이템 생성 → 랜덤 데이터로 씀
2. 읽기 아이템 생성 → 같은 영역 읽음
→ 스코어보드가 "쓴 값 == 읽은 값" 비교 → self-checking
정상 DUT면 데이터 일치해야 함
start_item(wr) → "이 아이템 보낼게, 시퀀서 준비되면 알려줘"
(드라이버가 get_next_item 할 때까지 대기)
wr.randomize() → 제약 안에서 랜덤화
finish_item(wr) → "완성됐어, 드라이버로 보내"
(드라이버가 item_done 할 때까지 대기)
→ driver의 get_next_item() / item_done()과 짝을 이루는 반대편!
randomize() with { ... } — 인라인 제약randomize() with { dir == AXI_WRITE; addr == a; ... }
= "이번 랜덤화는 이 조건들을 지켜라"
dir는 WRITE로 고정
addr는 인자 a로 고정
나머지(data, strb)는 seq_item 제약 안에서 랜덤
→ seq_item의 constraint(c_4k, c_align 등)는 그대로 적용되면서
추가로 이 조건들을 얹음
id == wr.id 하는 이유읽기 아이템의 id를 쓰기와 같게 맞춤
→ 같은 트랜잭션 쌍임을 ID로 표시
→ (outstanding 환경에서 매칭에 도움)
class axi4_write_seq extends axi4_base_seq;
task body();
repeat (num) begin
req = axi4_seq_item::type_id::create("req");
start_item(req);
if (!req.randomize() with { dir == AXI_WRITE;
burst inside {AXI_INCR, AXI_FIXED}; })
`uvm_error("RAND", "write item randomize() failed")
finish_item(req);
end
endtask
endclass
body() = 시퀀스의 메인 (test가 seq.start() 하면 실행됨)
repeat (num) → num개 생성
req = built-in 핸들 (uvm_sequence가 제공)
세 시퀀스 차이는 dir 제약만:
write_seq: dir == AXI_WRITE
read_seq: dir == AXI_READ
rand_seq: dir 제약 없음 (읽기/쓰기 랜덤 혼합)
공통: burst inside {AXI_INCR, AXI_FIXED} ← WRAP 제외!
class axi4_wr_rd_seq extends axi4_base_seq;
task body();
repeat (num) begin
axi4_seq_item wr, rd;
wr = axi4_seq_item::type_id::create("wr");
start_item(wr);
if (!wr.randomize() with { dir == AXI_WRITE; burst == AXI_INCR; })
`uvm_error("RAND", "write randomize() failed")
finish_item(wr);
rd = axi4_seq_item::type_id::create("rd");
start_item(rd);
if (!rd.randomize() with { dir == AXI_READ; burst == AXI_INCR;
addr == wr.addr; len == wr.len;
size == wr.size; id == wr.id; })
`uvm_error("RAND", "read randomize() failed")
finish_item(rd);
end
endtask
endclass
랜덤 쓰기 → 같은 영역 읽기 (wr_then_rd와 비슷하지만 주소도 랜덤)
wr는 완전 랜덤 (addr/len/size 랜덤)
rd는 wr를 따라감 (addr == wr.addr, len == wr.len, ...)
→ 랜덤한 위치에 쓰고 그 위치를 읽어서 검증
이게 base_test의 기본 자극 → "환경이 제대로 도나" 확인

class axi4_os_base_seq extends axi4_base_seq;
localparam int OS_COUNT = 8;
// 0x5000, 0x5100, ... 0x5700
function bit [AXI_ADDR_WIDTH-1:0] os_addr(int i);
return 16'h5000 + (i * 16'h0100);
endfunction
endclass
8개 버스트를 고정된 겹치지 않는 주소에 배치
0x5000, 0x5100, 0x5200, ... 0x5700
각 4 beat × 4B, 전부 4KB 안전
왜 고정 주소? outstanding은 "여러 개 동시 진행"을 봐야 하는데
주소가 겹치면 검증이 복잡 → 겹치지 않게 미리 계획
class axi4_os_write_seq extends axi4_os_base_seq;
task body();
for (int i = 0; i < OS_COUNT; i++) begin
req = axi4_seq_item::type_id::create("req");
start_item(req);
if (!req.randomize() with { dir == AXI_WRITE;
burst == AXI_INCR;
addr == os_addr(i);
len == 3;
size == 3'd2;
foreach (strb[j])
strb[j] == {AXI_STRB_WIDTH{1'b1}}; })
`uvm_error("RAND", "outstanding write randomize() failed")
finish_item(req);
end
endtask
endclass
write와 read가 별도 시퀀스인 이유:
test에서 사이에 drain을 넣어야 함
→ 읽기가 아직 안 끝난 쓰기를 추월하면 안 됨
→ (AXI 읽기/쓰기 채널 독립)
foreach (strb[j]) strb[j] == {AXI_STRB_WIDTH{1'b1}}
= 모든 strobe 비트를 1로 (전체 바이트 쓰기)
{N{1'b1}} = 1을 N번 반복 = 전부 1
이게 outstanding_test에서 봤던 "phase 1 쓰기 → drain → phase 2 읽기"의 시퀀스 쪽 구현이에요.
// this sequence allows AXI_WRAP, so it can legitimately surface
// the DUT's WRAP addressing bug as scoreboard mismatches.
가장 복잡한 시퀀스. 세 단계로 커버리지를 채운다.
task body();
foreach_burst_size(); // 모든 (burst × size) 조합
burst_len_sweep(); // 모든 (burst × length class) 조합
repeat (num) begin // 넓은 랜덤 (length/region/strobe bin 채우기)
req = axi4_seq_item::type_id::create("req");
start_item(req);
if (!req.randomize())
`uvm_error("RAND", "coverage item randomize() failed")
finish_item(req);
end
endtask
랜덤만으로는 특정 조합이 안 채워짐 (plateau)
FIXED/WRAP은 가중치가 낮아서 (dist 8:1:1)
→ burst × length 교차 bin에 구멍이 남
해결: deterministic sweep으로 모든 조합을 명시적으로 방문
→ 랜덤이 못 가는 코너를 확정적으로 채움
이게 이력서의 "constrained-random (plateau 98.3%) + directed sweeps"의 실제 코드예요.
task foreach_burst_size();
axi_burst_e blist[3] = '{AXI_FIXED, AXI_INCR, AXI_WRAP};
bit [AXI_ADDR_WIDTH-1:0] a = 16'h2000;
foreach (blist[b]) begin
for (int s = 0; s <= 2; s++) begin
automatic bit [7:0] l = (blist[b] == AXI_WRAP) ? 8'd3 : 8'd2;
wr_then_rd(a, l, s[2:0], blist[b]);
a += 16'h0100;
end
end
endtask
3가지 burst × 3가지 size = 9개 조합 전부 방문
각각 wr_then_rd로 쓰고 읽음
WRAP일 때만 len=3 (4 beats, WRAP 합법)
나머지는 len=2
automatic = 각 반복마다 새 변수 (루프 변수 캡처 문제 방지)
task burst_len_sweep();
bit [7:0] lens[4] = '{8'd0, 8'd2, 8'd6, 8'd12}; // 각 길이 클래스 대표
foreach (lens[i]) begin
wr_then_rd(a, lens[i], 3'd2, AXI_FIXED); a += 16'h0100;
wr_then_rd(a, lens[i], 3'd2, AXI_INCR); a += 16'h0100;
end
// WRAP은 2/4/8/16 beats만 합법
wr_then_rd(a, 8'd1, 3'd2, AXI_WRAP); ...
wr_then_rd(a, 8'd3, 3'd2, AXI_WRAP); ...
wr_then_rd(a, 8'd7, 3'd2, AXI_WRAP); ...
wr_then_rd(a, 8'd15, 3'd2, AXI_WRAP);
endtask
길이 클래스: single(0) / 2-4(2) / 5-8(6) / 9-16(12)
각 클래스 대표값으로 FIXED, INCR 방문
WRAP은 합법 길이(1/3/7/15 = 2/4/8/16 beats)만 별도로
task body();
// addr len size burst what it exercises
wr_then_rd(16'h0000, 8'd0, 3'd2, AXI_INCR); // single beat, full width
wr_then_rd(16'h0100, 8'd15, 3'd2, AXI_INCR); // max 16-beat burst
wr_then_rd(16'h0FC0, 8'd15, 3'd2, AXI_INCR); // ends exactly at 4KB edge
wr_then_rd(16'h0200, 8'd7, 3'd0, AXI_INCR); // narrow: 1 byte/beat
wr_then_rd(16'h0300, 8'd3, 3'd1, AXI_INCR); // narrow: 2 bytes/beat
wr_then_rd(16'h0400, 8'd3, 3'd2, AXI_FIXED); // FIXED: same address
endtask
손으로 고른 위험한 코너들:
single beat : len=0 → 1 beat (경계값)
max burst : len=15 → 16 beats (최대)
4KB edge : 0x0FC0 + 16beat×4B = 0x1000 딱 경계
narrow 1B/beat : size=0 → lane_mask가 토글하는 케이스
narrow 2B/beat : size=1
FIXED : 같은 주소 반복
→ 랜덤이 잘 안 만드는 경계값을 확정적으로 테스트
이게 directed_test가 쓰는 시퀀스. 주석에 각 케이스가 뭘 테스트하는지 다 적혀있어요.
// The reference model wraps correctly, the DUT does not,
// so this sequence is EXPECTED to raise scoreboard mismatches.
task wrap_write_incr_read(bit [AXI_ADDR_WIDTH-1:0] a,
bit [7:0] l,
bit [2:0] s);
axi4_seq_item wr, rd;
int unsigned nbytes = 1 << s;
int unsigned total = (l + 1) * nbytes; // wrap window size
bit [AXI_ADDR_WIDTH-1:0] base = a - (a % total); // window base
// WRAP으로 쓰기 (window 중간에서 시작 → 실제로 wrap 발생)
wr = axi4_seq_item::type_id::create("wr_wrap");
start_item(wr);
if (!wr.randomize() with { dir == AXI_WRITE; addr == a;
len == l; size == s; burst == AXI_WRAP;
foreach (strb[j])
strb[j] == {AXI_STRB_WIDTH{1'b1}}; })
`uvm_error("RAND", "wrap write randomize() failed")
finish_item(wr);
// INCR로 window 전체 읽기
rd = axi4_seq_item::type_id::create("rd_incr");
start_item(rd);
if (!rd.randomize() with { dir == AXI_READ; addr == base;
len == l; size == s; burst == AXI_INCR; })
`uvm_error("RAND", "incr read-back randomize() failed")
finish_item(rd);
endtask
주석이 핵심을 설명한다:
// Why not a plain WRAP write + WRAP read? Because that hides the
// bug: the DUT is consistently wrong on both sides, so it reads
// from the same (wrong) places it wrote to and the data matches.
// Only an INCR read exposes where the bytes actually landed.
왜 WRAP 쓰기 + WRAP 읽기가 아니라 WRAP 쓰기 + INCR 읽기?
WRAP write + WRAP read (대칭):
DUT가 쓸 때도 틀리고 읽을 때도 똑같이 틀림
→ 틀린 위치에 쓰고, 그 틀린 위치에서 읽음
→ 데이터는 일치! → 버그 숨겨짐 ❌
WRAP write + INCR read (비대칭):
DUT: WRAP 쓰기를 틀린(선형) 위치에 씀
INCR 읽기: 올바른 순서로 window 전체를 읽음
→ "바이트가 실제로 어디 떨어졌나" 드러남
→ 예상과 불일치 → 버그 노출! ✅
이게 이력서의 "asymmetric WRAP-write / INCR-read cross-check that surfaced 24 mismatches"의 정확한 코드예요.

task body();
// len 1/3/7/15 -> 2/4/8/16 beats
wrap_write_incr_read(16'h0A08, 8'd3, 3'd2); // window 0x0A00..0x0A0F
wrap_write_incr_read(16'h0B10, 8'd7, 3'd2); // window 0x0B00..0x0B1F
endtask
0x0A08에서 WRAP 쓰기 시작 (window 0x0A00~0x0A0F 중간)
→ 0x0A08부터 쓰다가 0x0A0F 넘으면 0x0A00으로 되돌아와야 함 (정상)
→ DUT는 안 되돌아오고 계속 직진 (버그)
base(0x0A00)부터 INCR로 window 전체 읽기
→ 실제로 어디 썼는지 드러남 → 미스매치!

| 시퀀스 | WRAP 포함? | 특징 |
|---|---|---|
| write/read/rand | ❌ | N개 랜덤 (INCR/FIXED만) |
| wr_rd | ❌ | 쓰고 읽기, 기본 자극 |
| os_* | ❌ | 고정 주소, 파이프라인용 |
| cov | ✅ | 커버리지 sweep (WRAP 버그 노출 가능) |
| directed | ❌ | 손으로 고른 코너 |
| wrap | ✅ | 비대칭 cross-check (버그 재현) |
Sequence = 어떤 트랜잭션을 어떤 순서로 만들지 정의하는 자극 프로그램
axi4_base_seq: 부모, wr_then_rd() 헬퍼 (쓰고 같은 영역 읽기)
핵심 설계:
1. 일반 시퀀스는 WRAP 제외 → 정상 회귀 clean 유지
2. WRAP은 wrap_seq로 격리 → 비대칭(WRAP write + INCR read)으로
버그를 "일부러" 노출
3. cov_seq는 deterministic sweep으로 랜덤 plateau의 구멍을 채움
start_item/finish_item = driver의 get_next_item/item_done과 짝
randomize() with {...} = seq_item 제약 위에 추가 조건
이제 AXI4 UVM 환경의 모든 파일을 봤습니다.
tb_top → 조립 & 시작
cfg/agent/env → 계층 빌드
base_test → 지휘 (get_seq로 시퀀스 선택)
sequence → 자극 생성 (이 글!) ← randomize()
↓
seq_item → 트랜잭션 단위
↓
driver → 핀 구동
↓ (인터페이스 = 신호 + clocking block)
DUT
↓
monitor → 관찰 & 재조립
↓
scoreboard → ref_model과 비교 → pass/fail
자극이 생성(sequence)되어 아이템(seq_item)으로 만들어지고, 드라이버가 핀을 흔들어 DUT에 전달하고, 모니터가 관찰해서 스코어보드가 정답지와 비교하는 — 검증의 전체 순환이 완성됩니다.