Wednesday, January 21, 2015

[verilog] blocking과 non-blocking

이번에는 verilog에서 이해하기 어려웠던 blocking과 non-blocking에 대해서 설명해보기로 한다.

우선 사용법부터 정리하면
initial 또는 always 구문 안에 작성해야하는데

blocking 은
y = x 와 같은 형식이고

non-blocking은
y <= x 와 같은 형식으로 표현한다.

먼가 같은것 처럼 보이지만 굉장히 다르다.

= 를 사용하는 blocking 같은 경우 말 그대로 이다. 신호의 흐름을 'blocking' 한다 라고 생각하면 된다. 따라서 코드 내에서 blocking 구문으로 사용할 경우 이는 C언어 처럼 순차 실행을 의미하게 된다.

initial begin
  #10 a = b;
  #5 c = a;
end

위와 같은 경우 처음 10 단위 시간 뒤에 b를 a에 넣고 다시 5 단위 시간 뒤에 a를 c에 넣으므로 두번째 라인은 15 단위 시간 뒤에 동작하게 되는 것이다.

(단위시간은 코드처음에 `timescale 1ns/1ps 라고 했을 때 1ns 를 말한다. 즉, 다음과 같이 timescale를 설정했을 경우 #10 단위시간은 10ns를 말하는 것임)

<= 를 사용하는 non-blocking 같은 경우 말 그대로 신호의 흐름을 'non-blocking' 막지 않는 것이다.

initial begin
   a <= #10 b;
   c <= #5 a;
end

위와 같은 경우 처음 5단위 시간에서 c에 a값이 들어가고 10단위 시간에서 a에 b 값이 들어가므로 순서와 상관없이 동신에 실행하게 된다.

그림으로 마무리 하자면


위와 같이 blocking과 non-blocking을 설명할 수 있는 것이다.

아마 여기까지는 기존에 있던 블로그나 설명들을 통해 어느정도 이해를 했을 것이다. 아마 대부분의 엔지니어들은 '아 그렇구나.. ' 할 것이다.

그러나 대부분의 선배들은 다음과 같이 얘기 한다.

" always 구문 안에서는 가능하면 non-blocking 구문으로 작성할 것"

그럼 여러분들은 질문할 것이다.

"왜요?"

단편적으로 여기서 대답을 해주냐 못해주냐에 따라 실력있는 선배, 실력 없는 선배로 나누는 것은 옳지 않지만

"그냥 원래 그런거니까 그렇게해~ "

라고 할지도 모른다. 왜냐하면 나도 이글을 작성하기 전까지는 몰랐으니까!(그냥 연구원임 ㅠ)

그럼 여러분들은 고민할 것이다. 'C언어 짜던대로 순차로 실행하는게 더 좋지 않나?' 라고 하고 아마 대부분의 학생들은 실제로 그렇게 짜는 것을 보기도 했다.

이것이 나쁘기만 한 것은 아니지만 왜 always 안에서 non-blocking 구문을 주로 써야 하냐면 바로 경쟁조건(race condition) 때문이다.

이게 먼소리인고 하니 엔지니어들이니 말보다 코드로 설명한다.

보기1.
always@(posedge clock)
  a = b;

always@(posedge clock)
  b = a;

보기2.
always@(posedge clock)
  a <= b;

always@(posedge clock)
  b <= a;

위의 보기1과 보기2를 보면 보기1 blocking 구문부터 보면 a = b 와 b = a의 구문이 clock 상승 에지에서 동시에 실행되게 되는데 이것을 경쟁 상태 라고 한다. 이것이 어느것이 먼저 실행되느냐는 실제 게이트 레벨에서 어떻게 합성되느냐에 따라 선의 길이가 누가 더 긴가(?)와 같은 애매한 관계에 따라 달라지게 된다. 한마디로 예측이 어려운 상태가 되는 것이다.  이것을 경쟁 조건(race condition) 이라 한다.

하지만 보기2 에서 보면 둘은 동시에 실행되기 때문에 클럭이 발생했을 때 a는 현재 b값을 가지고 b는 이전의 a값을 가지고 있게 된다. 한마디로 서로 부딧칠 일이 없다.

그래서!! 왜 always 구문 안에서는 가능하면 non-blocking으로 설계해야 하는가? 바로 경쟁 조건이 발생하지 않기 위해서 이다. 저런 동시에 실행되는 많은 always 문이 생기게 되면 자신도 모르게 어디선가 경쟁조건이 발생하게 되고 시뮬레이션 상에서는 알 수 없지만 나중에 칩으로 갔을 때 제대로 동작 되지 않는 상태가 되는 것이다!!!!

(참조 : 책에는 non-blocking으로 파이프라인과 상호 배타적인 데이터 전송을 하지만 단점으로 시뮬레이터의 성능 하락과 메모리 사용량을 늘리는 것의 원인이라고 나와 있음)

이제 어느정도 blocking과 non-blocking에 대해서 알겠는가?

끝!
P.S. 필자가 이해한대로 작성하여 오류가 있을 수도 있습니다. 잘못된 점은 리플 부탁드립니다!

11 comments :

  1. 정말 유용한 정보 잘 읽고 갑니다. 감사합니다!

    ReplyDelete
  2. ㅇㅎ 대단하시군요 이제 배우기 시작했는데 이해가 잘됩니다!

    ReplyDelete
  3. 좋은 글 읽고 갑니다~ 학교에서 디지털 설계 구현시 베릴로그를 사용하는데 문법에대해 정리한 한글 포스트가 너무 없어서 영어로 보고있는데 한글로 잘 설명된 글있어서 기분이 좋네요.

    ReplyDelete
  4. 그냥 always 안에 = 있는게 본능적으로 싫었었는데 정확한 이유를 알고 갑니다.
    개인적으로는 always 안에 reset,clock 외에 event 가 있는 것도 싫고,
    state 만들때 많이들 쓰는 방식이 cstate 는 걍 기술, 나중에 clock 으로 한번채서
    nstate 기술 하는데 이것도 영 맘에 안듭니다. 꼭 그래야 하는 이유가 있나요?

    ReplyDelete
  5. 이해하기 쉽게 설명되있어서 너무 좋았어요~ 감사합니다!

    ReplyDelete
  6. 베린이도 이해할 수 있는 쉬운 설명 감사합니다

    ReplyDelete
  7. 와우 교수님보다 잘가르쳐 주시네요^^;

    ReplyDelete
  8. 맞는 설명이지만 하나 빠뜨리신게 있네요 ㅎㅎ
    sequential logic의 경우 non-blocking
    always문으로 combinational logic을 짤때에는 blocking으로 표현해야 합니다.

    예를 들면 아래와 같은 comb logic 예제에서
    always @(a or b or c or d) begin
    tmp1 <= a & b;
    tmp2 <= c & d;
    y <= tmp1 | tmp2;
    end

    시뮬레이션의 타임 스텝이 종료될때 tmp1, tmp2, y가 동시에 업데이트 되게 됩니다.
    하지만 y는 tmp1 및 tmp2의 결과에 의해 계산된 결과의 값이 아니라(현재 값이 아니라)
    이전 값에 의해 계산된 값으로 업데이트 됩니다.(과거의 값으로 업데이트됨)

    물론 아래처럼 tmp1 및 tmp2를 sensitivity list에 추가해주면 문제가 해결되지만
    always @(a or b or c or d or tmp1 or tmp2) begin
    tmp1 <= a & b;
    tmp2 <= c & d;
    y <= tmp1 | tmp2;
    end
    더 많은 sensitivity list는 시뮬레이션 속도를 낮춥니다.

    따라서 comb logic을 짤때에는 blocking을 사용하여 바로 할당되도록 해야합니다.
    always @(a or b or c or d) begin
    tmp1 = a & b;
    tmp2 = c & d;
    y = tmp1 | tmp2;
    end
    이렇게 말이죠

    자세한 내용은 쿠밍아저씨의 논문
    Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!
    을 참고하세요

    ReplyDelete