2026. 4. 10. 20:02ㆍUVM
UVM RAL이란?
RAL(Register Abstraction Layer)은 DUT 내부의 레지스터와 메모리를 객체로 모델링하여, 테스트벤치에서 일관된 방식으로 읽고 쓸 수 있게 해주는 UVM 표준 계층입니다. 직접 트랜잭션을 만들지 않아도 reg.write(), reg.read() 한 줄로 레지스터 접근이 가능합니다.
핵심 클래스 구조
RAL 모델은 세 가지 핵심 클래스로 구성됩니다.
uvm_reg_block // 레지스터 블록 (최상위 컨테이너)
uvm_reg // 개별 레지스터 (예: CTRL_REG, STATUS_REG)
uvm_reg_field // 레지스터 내 비트 필드 (예: [7:4] MODE, [0] EN)
uvm_mem // 메모리 모델
uvm_reg_map // 주소 맵 (베이스 주소 + 오프셋 매핑)
uvm_reg_block 작성 예시
class my_reg_block extends uvm_reg_block;
`uvm_object_utils(my_reg_block)
rand ctrl_reg CTRL; // 컨트롤 레지스터
rand status_reg STATUS; // 상태 레지스터
uvm_reg_map default_map;
function void build();
// 레지스터 생성 및 구성
CTRL = ctrl_reg::type_id::create("CTRL");
CTRL.configure(this, null, "");
CTRL.build();
STATUS = status_reg::type_id::create("STATUS");
STATUS.configure(this, null, "");
STATUS.build();
// 주소 맵 생성: 베이스 주소 0x0, 버스 폭 32bit
default_map = create_map("default_map", 0, 4, UVM_LITTLE_ENDIAN);
default_map.add_reg(CTRL, 32'h00, "RW");
default_map.add_reg(STATUS, 32'h04, "RO");
endfunction
endclass
uvm_reg + uvm_reg_field 작성 예시
class ctrl_reg extends uvm_reg;
`uvm_object_utils(ctrl_reg)
rand uvm_reg_field MODE; // [3:2] 동작 모드
rand uvm_reg_field EN; // [0] 활성화
function void build();
MODE = uvm_reg_field::type_id::create("MODE");
EN = uvm_reg_field::type_id::create("EN");
// configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible)
MODE.configure(this, 2, 2, "RW", 0, 2'b00, 1, 1, 0);
EN.configure(this, 1, 0, "RW", 0, 1'b0, 1, 1, 0);
endfunction
function new(string name = "ctrl_reg");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
endclass
Adapter — RAL과 버스 프로토콜 연결
RAL의 write()/read()는 내부적으로 uvm_reg_bus_op를 만들고, Adapter가 이를 실제 버스 트랜잭션(APB, AXI 등)으로 변환합니다.
class my_reg_adapter extends uvm_reg_adapter;
`uvm_object_utils(my_reg_adapter)
// RAL op → 버스 트랜잭션 변환
function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
my_bus_seq_item tr = my_bus_seq_item::type_id::create("tr");
tr.addr = rw.addr;
tr.data = rw.data;
tr.write = (rw.kind == UVM_WRITE);
return tr;
endfunction
// 버스 트랜잭션 → RAL op 변환
function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
my_bus_seq_item tr;
$cast(tr, bus_item);
rw.kind = tr.write ? UVM_WRITE : UVM_READ;
rw.addr = tr.addr;
rw.data = tr.data;
rw.status = UVM_IS_OK;
endfunction
endclass
Frontdoor vs Backdoor 접근
Frontdoor는 실제 버스 트랜잭션을 통해 레지스터에 접근합니다 — 버스 프로토콜 검증에 사용합니다. Backdoor는 시뮬레이터 계층에서 직접 DUT 내부 신호를 읽고 씁니다 — 속도가 매우 빠르고 시간이 소요되지 않습니다.
task run_phase(uvm_phase phase);
uvm_status_e status;
uvm_reg_data_t val;
// Frontdoor write: 버스를 통해 CTRL 레지스터에 0x3 기록
regmodel.CTRL.write(status, 32'h3, UVM_FRONTDOOR);
// Backdoor read: 시뮬레이터 직접 접근 (시간 소요 없음)
regmodel.STATUS.read(status, val, UVM_BACKDOOR);
`uvm_info("RAL", $sformatf("STATUS = 0x%0h", val), UVM_LOW)
// 필드 단위 접근
regmodel.CTRL.EN.set(1'b1);
regmodel.CTRL.update(status); // set 값을 DUT에 반영
endtask
Predictor — 미러 값 자동 갱신
uvm_reg_predictor는 버스 모니터에서 트랜잭션을 받아 RAL 미러(mirror) 값을 자동으로 업데이트합니다. Scoreboard에서 reg.get_mirrored_value()로 예상값을 가져올 수 있습니다.
class my_env extends uvm_env;
my_agent agent;
my_reg_block regmodel;
uvm_reg_predictor #(my_bus_seq_item) predictor;
function void build_phase(uvm_phase phase);
agent = my_agent::type_id::create("agent", this);
predictor = uvm_reg_predictor#(my_bus_seq_item)::type_id::create("predictor", this);
regmodel = my_reg_block::type_id::create("regmodel");
regmodel.build(); regmodel.lock_model();
endfunction
function void connect_phase(uvm_phase phase);
// adapter 연결
my_reg_adapter adapter = my_reg_adapter::type_id::create("adapter");
regmodel.default_map.set_sequencer(agent.sequencer, adapter);
// predictor 연결
predictor.map = regmodel.default_map;
predictor.adapter = adapter;
agent.monitor.ap.connect(predictor.bus_in);
endfunction
endclass