SystemVerilog: Safe Use of disable fork - IKSciting
1655
post-template-default,single,single-post,postid-1655,single-format-standard,bridge-core-2.8.7,qodef-qi--no-touch,qi-addons-for-elementor-1.7.1,qode-page-transition-enabled,ajax_fade,page_not_loaded,,qode-title-hidden,qode_grid_1300,footer_responsive_adv,qode-content-sidebar-responsive,qode-theme-ver-27.1,qode-theme-bridge,qode_header_in_grid,wpb-js-composer js-comp-ver-6.6.0,vc_responsive,elementor-default,elementor-kit-838

Safe Use of disable fork

forkjoin과 disable fork에 대한 기본적인 설명은 fork-join and disable fork 페이지에서 확인할 수 있다. 현재 실행 중인 process를 강제 종료하는 disable fork는 사용 시 주의를 기울이지 않으면 의도하지 않은 동작을 할 수 있다.

먼저 다음과 같은 예제를 살펴보자. 하나의 forkjoin_none과 하나의 forkjoin_any가 있으며 join_any 뒤에는 disable fork가 뒤따른다. disable fork는 해당 구문이 실행되는 process 내에 실행 중인 모든 process를 종료하기 때문에 의도하지 않은 process까지 종료될 위험이 있다. 아래는 매우 단순한 예제이므로 두 개의 forkjoin 구문이 들어간 것이 한 눈에 보이지만, code의 길이가 길어지거나 여러 명이 공동 작업을 할 경우에는 disable fork로 인해 종료되는 모든 process를 명확히 파악하기 힘들 수도 있다. 아래 예제에서는 두 번째 forkjoin 구문 다음에 disable fork가 위치하였지만 첫 번째 forkjoin 구문 내의 process도 강제 종료된 것을 simulation 결과로 확인할 수 있다.

fork
  #100ns $display("@%3t: A", $realtime);
  #200ns $display("@%3t: B", $realtime);
join_none
    
fork
  #10ns $display("@%3t: C", $realtime);
  #20ns $display("@%3t: D", $realtime);
join_any
disable fork;
@ 10: C

아래에서는 이와 같이 의도하지 않은 동작이 발생하는 것을 방지하기 위한 safe coding 기법을 설명한다.

disable LABEL instead of disable fork

먼저 각 forkjoin 구문에 label을 지정하고 종료하고자 하는 process가 속한 forkjoin 구문의 label을 이용하여 disable 하는 방식이다. 아래 예제를 보면 ‘LABEL_A’의 process는 종료되지 않고, ‘LABEL_B’에 남아있는 process만 종료되었음을 확인할 수 있다. 하지만 결론부터 이야기하자면 이 방법도 확실한 safe coding 기법은 아니다.

fork: LABEL_A
  #100ns $display("@%3t: A", $realtime);
  #200ns $display("@%3t: B", $realtime);
join_none
    
fork: LABEL_B
  #10ns $display("@%3t: C", $realtime);
  #20ns $display("@%3t: D", $realtime);
join_any
disable LABEL_B;
@ 10: C
@100: A
@200: B

조금 더 복잡하지만 다음 예제를 보면 label을 이용한 disable 역시 경우에 따라 문제가 됨을 확인할 수 있다. 즉, ‘LABEL’로 정의된 forkjoin 구문이 여러 object에 의해 call 되는 경우 disable LABEL의 결과로 다른 object의 ‘LABEL’ 내 process까지 종료되는 위험이 존재하는 것이다.

class my_class;

  int id;

  function new(int id);
    this.id = id;
  endfunction: new

  task kill();
    fork: LABEL
      #(this.id * 10) $display("@%2t: process done for object #%1d", $realtime, this.id);
    join
    disable LABEL;
    $display("@%2t: disable done for object #%1d", $realtime, this.id);
  endtask: kill

endclass: my_class

module top;

  my_class c1 = new(1);
  my_class c2 = new(2);

  initial begin
    fork
      c1.kill();
      c2.kill();
    join
  end 

endmodule: top
@ 0: process done for object #1
@ 0: disable done for object #1
@ 0: disable done for object #2

이러한 경우에는 오히려 일반적인 disable fork를 사용하는 것이 안전할 수 있다.

task kill();
  fork
    #(this.id * 10) $display("@%2t: process done for object #%1d", $realtime, this.id);
  join
  disable fork;
  $display("@%2t: disable done for object #%1d", $realtime, this.id);
endtask: kill
@ 0: process done for object #1
@ 0: disable done for object #1
@10: process done for object #2
@10: disable done for object #2

그렇다면 어떤 경우에도 의도된 동작을 보장할 수 있는 coding 기법은 무엇일까?

Enclosing with fork … join

원하지 않는 process가 강제 종료되는 위험을 방지하기 위한 가장 안전한 coding 방법은 disable fork와 그로 인해 종료하고자 하는 process가 존재하는 forkjoin 구문을 다시 한 번 forkjoin 구문으로 감싸는 방법이다. disable fork는 disable fork가 실행되는 process 내의 실행 중인 process를 종료하는 구문이며, forkjoin 구문으로 다시 한 번 감싸게 되면 해당 영역은 별도의 process로 구분되기 때문에, 이를 통해 강제 종료 대상을 한정시키는 효과가 있는 것이다.

fork
  #100ns $display("@%3t: A", $realtime);
  #200ns $display("@%3t: B", $realtime);
join_none

fork
  begin
    fork
      #10ns $display("@%3t: C", $realtime);
      #20ns $display("@%3t: D", $realtime);
    join_any
    disable fork;
  end
join
@ 10: C
@100: A
@200: B

Summary

위에서 알아보았듯 disable fork 사용 시, code의 복잡도가 높거나 여러 명이 동시에 code를 작성하는 경우, 안정적인 동작을 보장하기 위해 forkjoin으로 감싸는 것을 권장한다.

class my_class;

  int id;

  function new(int id);
    this.id = id;
  endfunction: new

  task kill();
    fork
      #(this.id * 1000) $display("@%4t: process A done for object #%1d", $realtime, this.id);
    join_none
    
    fork
      begin
        fork
          #(this.id * 10) $display("@%4t: process B done for object #%1d", $realtime, this.id);
          #(this.id * 20) $display("@%4t: process C done for object #%1d", $realtime, this.id);
        join
        disable fork;
      end
    join
        
    $display("@%4t: disable done for object #%1d", $realtime, this.id);
  endtask: kill

endclass: my_class

module top;
  
  my_class c1 = new(1);
  my_class c2 = new(2);

  initial begin
    fork
      c1.kill();
      c2.kill();
    join
  end 
  
endmodule: top
@  10: process B done for object #1
@  20: process C done for object #1
@  20: disable done for object #1
@  20: process B done for object #2
@  40: process C done for object #2
@  40: disable done for object #2
@1000: process A done for object #1
@2000: process A done for object #2

References

6 Comments

Post A Comment