SystemVerilog에서 for 및 foreach를 활용한 비동기 병렬 실행 기법

for + fork-join_none을 통한 병렬화

SystemVerilog에서는 fork-join_nonefor 루프를 결합해 여러 작업을 동시에 실행할 수 있다. 이 방식은 각 반복마다 독립적인 스레드를 생성하며, 이후 명령어 실행을 차단하지 않기 때문에 효율적인 병렬 처리가 가능하다. 핵심은 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

실행 흐름은 다음과 같다:

  1. foreachenv.agents 배열의 각 요소에 대해 반복한다.
  2. 각 반복에서 automatic int agent_id = i를 통해 현재 인덱스를 고유하게 저장하여 캡처 문제를 회피한다.
  3. fork 블록 내부에서 해당 에이전트의 시퀀서에 시퀀스를 시작한다.
  4. wait fork는 모든 join_none 스레드가 종료될 때까지 메인 스레드를 대기시킨다.

UVM 클래스 내부 메서드는 기본적으로 automatic 스코프이므로, automatic 키워드 생략이 가능하지만, 가독성과 명시성을 위해 포함하는 것이 권장된다.

태그: systemverilog parallel-execution fork-join foreach uvmsystemverilog

9월 20일 01:34에 게시됨