for + fork-join_none을 통한 병렬화
SystemVerilog에서는 fork-join_none과 for 루프를 결합해 여러 작업을 동시에 실행할 수 있다. 이 방식은 각 반복마다 독립적인 스레드를 생성하며, 이후 명령어 실행을 차단하지 않기 때문에 효율적인 병렬 처리가 가능하다. 핵심은 join_none이어야 하며, join 또는 join_any을 사용하면 기대하는 병렬 동작을 얻지 못한다.
다음 예제는 흔히 발생하는 캡처 오류(capture bug)를 보여준다:
program example_capture_bug;
initial begin
for (int idx = 0; idx < 3; idx++) begin
fork
$write(idx, " ");
join_none
end
#0 $display("— done");
end
endprogram
위 코드는 0 1 2 — done을 출력하려는 의도지만, 실제 출력은 2 2 2 — done이 된다. 이유는 모든 fork 블록이 동일한 변수 idx를 참조하기 때문이며, 루프 종료 시점에서 idx 값은 이미 3에 도달했고, #0 시점에 모든 스레드가 idx의 최종 값을 읽게 된다.
해결책은 각 스레드가 고유한 로컬 복사본을 가지도록 하는 것이다. 이를 위해 automatic 변수를 fork 내부에 선언하거나, 외부 자동 저장 영역을 사용한다:
program fixed_with_local_copy;
initial begin
for (int idx = 0; idx < 3; idx++) begin
fork
automatic int local_idx = idx; // 각 스레드별 별도 스택 프레임
$write(local_idx, " ");
join_none
end
#0 $display("— done");
end
endprogram
또는, 전체 프로그램이나 모듈을 automatic으로 선언하면 루프 내 변수 선언도 자동으로 지역화된다:
program automatic demo_auto_scope;
initial begin
for (int idx = 0; idx < 3; idx++) begin
int local_val = idx; // automatic scope 내에서 자동으로 지역 변수 처리
fork
$write(local_val, " ");
join_none
end
#0 $display("— done");
end
endprogram
foreach 기반 병렬 시퀀스 실행
foreach는 배열 또는 연관 배열의 인덱스를 자동으로 순회하며, 특히 UVM 환경에서 멀티에이전트 시나리오에 유용하다. 다음은 여러 에이전트에 동시에 시퀀스를 전송하는 패턴이다:
task launch_parallel_sequences();
seq_class seqs[$];
// 시퀀스 인스턴스 할당
foreach (env.agents[i]) begin
automatic int agent_id = i; // 인덱스 캡처 방지
seqs.push_back(seq_class::type_id::create(
$sformatf("seq_%0d", agent_id),
this
));
fork
begin
seqs[agent_id].start(env.agents[agent_id].sequencer);
end
join_none
end
wait fork; // 모든 파생 스레드 완료 대기
endtask
실행 흐름은 다음과 같다:
foreach가env.agents배열의 각 요소에 대해 반복한다.- 각 반복에서
automatic int agent_id = i를 통해 현재 인덱스를 고유하게 저장하여 캡처 문제를 회피한다. fork블록 내부에서 해당 에이전트의 시퀀서에 시퀀스를 시작한다.wait fork는 모든join_none스레드가 종료될 때까지 메인 스레드를 대기시킨다.
UVM 클래스 내부 메서드는 기본적으로 automatic 스코프이므로, automatic 키워드 생략이 가능하지만, 가독성과 명시성을 위해 포함하는 것이 권장된다.