01signal.com

오버플로 보호를 위한 EOF 기능의 FIFO

이 페이지는 FIFO에 관한 다섯 편의 시리즈 중 네 번째 글입니다. 이 페이지에서는 오버플로(overflow)가 발생할 가능성에 대처하는 방법을 제안합니다.

소개

FIFO를 사용하는 많은 응용 분야에서는 도착하는 데이터의 흐름을 제어할 방법이 없습니다. 예를 들어 데이터 수집(data acquisition) 응용 프로그램에서는 캡처된 데이터를 로직이 직접 FIFO에 씁니다. FIFO가 full(가득 찬 상태)이면 이 데이터는 유실됩니다. 이런 유형의 응용 프로그램에서는 FIFO를 읽는 쪽이 FIFO 안의 데이터를 충분히 빨리 소비해서 FIFO가 절대 full(가득 찬 상태)이 되지 않도록 책임져야 합니다.

그러나 FIFO가 full(가득 찬 상태)이 되면, 데이터가 유실되므로 데이터의 연속성이 깨집니다. FIFO가 쓰기 시도를 무시하기 때문입니다. 그 결과 FIFO의 내용은 올바르지 않게 됩니다. 이러한 상황을 오버플로(overflow)라고 합니다.

오버플로(overflow)는 일종의 오작동으로 인해 생기는 것이므로 피해야 합니다. 하지만 오버플로(overflow)가 발생하면, 그 사건을 감지하고 피해를 줄이는 것이 여전히 중요합니다. 아래에서는 그 전략을 제안합니다.

변형된 FIFO

항상 오버플로(overflow)가 발생하지 않도록 막는 것이 최우선이어야 합니다. 하지만 그렇게 하지 못하면, FIFO에서 잘못된 데이터가 소비되지 않도록 하는 것만 남습니다. 다시 말해, FIFO에서 읽히는 모든 데이터가 연속적이고 올바르도록 만드는 것입니다.

제안하는 해결책은 두 가지 점에서 다른 변형 FIFO입니다.

첫 번째 기능은 변형 FIFO에서 읽히는 데이터가 항상 연속적이고 올바르다는 것을 보장합니다. 연속성이 깨진 뒤에는 FIFO가 데이터 쓰기를 거부하기 때문입니다. 두 번째 기능(EOF)은 FIFO를 사용하는 로직에 문제가 발생했음을 알리는 메시지를 보냅니다. 그러면 로직은 데이터 흐름을 다시 시작할 기회를 얻게 됩니다.

EOF라는 이름은 End of File에서 비롯되었습니다. 이 변형 FIFO는 Xillybus IP 코어(IP core)를 기반으로 하는 데이터 수집 응용 프로그램에 유용합니다. 이런 종류의 응용 프로그램에서는 Xillybus IP 코어(IP core)가 데이터를 컴퓨터로 전송합니다. 간단한 컴퓨터 프로그램은 표준 파일 입출력 API를 사용해 FIFO에서 데이터를 읽습니다. 다시 말해, 컴퓨터 프로그램은 평소와 같은 방식으로 파일을 열고 데이터를 읽습니다. FIFO의 EOF는 이 파일이 일반 파일의 끝에 도달했을 때와 비슷하게 동작하게 만듭니다. 더 이상 읽을 데이터가 없다는 사실에 대한 자연스러운 응답인 셈입니다.

Verilog 구현 예

다음은 위에서 제안한 변형 FIFO를 구현한 Verilog 모듈 예입니다.

module eof_fifo
  (
   input 	   rst,
   input 	   wr_clk,
   input 	   rd_clk,
   input [31:0]    din,
   input 	   wr_en,
   input 	   rd_en,
   output [31:0]   dout,
   output 	   full,
   output 	   empty,
   output 	   eof
   );

   reg 		   rst_sync;
   reg 		   rst_cross;
   reg 		   fifo_has_been_full;
   reg 		   fifo_has_been_nonfull;
   reg 		   has_been_full_cross;
   reg 		   has_been_full;

   assign ok_to_write = !rst_sync && !full && !fifo_has_been_full;
   assign eof = empty && has_been_full;

   always @(posedge wr_clk)
     begin
	if (!full)
	  fifo_has_been_nonfull <= 1;
	else if (rst_sync)
	  fifo_has_been_nonfull <= 0;

	if (full && fifo_has_been_nonfull)
	  fifo_has_been_full <= 1;
	else if (rst_sync)
	  fifo_has_been_full <= 0;
     end

   // Clock domain crossing logic: asynchronous -> wr_clk
   always @(posedge wr_clk)
     begin
	rst_cross <= rst;
	rst_sync <= rst_cross;
     end

   // Clock domain crossing logic: wr_clk -> rd_clk
   always @(posedge rd_clk)
     begin
	has_been_full_cross <= fifo_has_been_full;
	has_been_full <= has_been_full_cross;
     end

   fifo fifo_ins
     (
      .rst(rst),
      .wr_clk(wr_clk),
      .rd_clk(rd_clk),
      .din(din),
      .wr_en(wr_en && ok_to_write),
      .rd_en(rd_en),
      .dout(dout),
      .full(full),
      .empty(empty)
      );
endmodule

이 모듈이 표준 FIFO의 인스턴스화(instantiation)에 약간의 추가 로직을 더한 것임을 쉽게 알 수 있습니다. 이 모듈의 포트가 표준 FIFO의 포트와 거의 정확히 같다는 점에도 유의하세요. 차이는 딱 하나뿐입니다. 변형 FIFO에는 @eof라는 포트가 있습니다.

또한 @din과 @dout의 폭이 32비트임에 유의하세요. 다른 폭의 FIFO가 필요하다면, 모듈 맨 위에 있는 이 포트들의 선언만 바꾸면 됩니다.

변형 FIFO에는 @full 포트가 있지만, 이 포트가 반드시 유용한 것은 아닙니다. FIFO에 쓰는 로직은 이 포트를 무시해도 됩니다. FIFO가 full(가득 찬 상태)이 되면 어차피 할 수 있는 일이 없기 때문입니다. 어느 쪽이든 FIFO는 그 이후의 쓰기 시도를 무시할 것입니다. 대부분의 응용 프로그램에서 FIFO가 full(가득 찬 상태)이 되었다는 사실을 알아도 별 도움이 되지 않습니다. @eof가 high가 될 때까지 기다린 다음 전체 메커니즘을 다시 시작하는 것이 최선입니다.

오버플로 이후 쓰기 동작 막기

앞서 언급했듯이 이 모듈은 표준 FIFO를 기반으로 합니다. 이 FIFO의 모든 포트는 @wr_en 포트 하나를 제외하고는 eof_fifo의 포트에 직접 연결됩니다. @wr_en 자리에는 "wr_en && ok_to_write"가 연결됩니다. 따라서 @ok_to_write가 FIFO가 full(가득 찬 상태)이 된 이후 쓰기 동작을 막는 데 사용된다는 것은 아주 분명합니다. 이 wire의 정의는 다음과 같습니다.

assign ok_to_write = !rst_sync && !full && !fifo_has_been_full;

이 식을 통해 쓰기 동작을 막는 세 가지 상황이 있음을 알 수 있습니다.

첫 번째와 두 번째 조건은 자명합니다. @fifo_has_been_full로 표현되는 세 번째 조건에 집중해 보겠습니다.

   always @(posedge wr_clk)
     begin
	if (!full)
	  fifo_has_been_nonfull <= 1;
	else if (rst_sync)
	  fifo_has_been_nonfull <= 0;

	if (full && fifo_has_been_nonfull)
	  fifo_has_been_full <= 1;
	else if (rst_sync)
	  fifo_has_been_full <= 0;
     end

@fifo_has_been_full은 FIFO가 full(가득 찬 상태)이었던 적이 있을 때 high입니다. 이 레지스터는 @full과 @fifo_has_been_nonfull이 모두 high일 때 high로 바뀝니다.

첫 번째 조건은 놀랍지 않습니다. @full은 FIFO의 full(가득 찬 상태) 포트에 연결되어 있습니다. 그런데 @fifo_has_been_nonfull은 왜 필요할까요? 그 이유는 FIFO가 리셋 상태에 있는 동안에는 종종 full(가득 찬 상태) 출력을 high로 유지하기 때문입니다. 이는 애플리케이션 로직에게 FIFO가 아직 데이터를 받을 준비가 되지 않았음을 알리기 위한 것입니다. @fifo_has_been_nonfull은 바로 이런 상황에서 @fifo_has_been_full이 실수로 high가 되는 것을 막는 역할을 합니다.

@fifo_has_been_full은 FIFO가 리셋될 때 low로 바뀝니다. 즉, FIFO를 리셋하는 것이 오버플로(overflow) 발생 후 변형 FIFO로 정상 동작을 재개하는 유일한 방법입니다. @rst는 비동기 리셋(asynchronous reset)입니다. 따라서 올바른 클록 도메인(clock domain)에 속한 리셋 신호를 만들기 위해 별도의 로직을 추가해야 합니다. 그렇게 만든 리셋 신호가 @rst_sync이며, 이는 @rst의 사본입니다.

EOF 생성

@eof 포트는 다음 두 조건이 모두 충족될 때 high입니다.

이 조건들을 Verilog 코드로 표현하면 다음과 같습니다.

   assign eof = empty && has_been_full;

이 식이 @fifo_has_been_nonfull이 아니라 @has_been_full에 기반한다는 점에 유의하세요. @eof와 @empty는 모두 @rd_clk의 클록 도메인(clock domain)에 속합니다. 그러나 @fifo_has_been_nonfull은 @wr_clk의 클록 도메인(clock domain)에 속합니다. 따라서 @fifo_has_been_nonfull은 클록 도메인 간 이동(clock domain crossing)을 통해 @rd_clk의 클록 도메인(clock domain)으로 복사되며, 그 사본이 @has_been_full입니다.

즉, @eof는 "FIFO가 단지 empty(비어 있음)일 뿐만 아니라, FIFO를 리셋하기 전에는 채워지지 않을 것"이라는 뜻입니다.

결론

많은 응용 프로그램에서는 오버플로(overflow)가 발생하지 않으리라고 보장하는 것이 불가능합니다. 이 사건을 용납할 수 없다면, 사건이 발생했을 때 피해를 줄이는 방법을 쓸 수 있습니다. 그 전략은 오버플로(overflow)가 발생할 때까지 쓰인 데이터는 통과시키고, 그 이후에는 더 이상 데이터가 쓰이지 않도록 막는 것입니다. 결국 FIFO는 empty(비어 있음) 상태가 됩니다. 그 시점이 되면 FIFO는 @eof 포트를 통해 더 이상 데이터가 도착하지 않을 것임을 알립니다.

이 방법은 FIFO에서 읽히는 데이터의 연속성을 보장합니다. FIFO가 full(가득 찬 상태)이 되어 데이터가 중간에 유실되는 일이 없기 때문입니다. 그래서 이 방법은 데이터 수집(data acquisition) 응용 프로그램에 유용합니다.

이 페이지에서는 이 전략을 기반으로 하는 변형 FIFO를 구현하는 방법을 보여주었습니다. 이 변형 FIFO는 표준 FIFO가 들어갈 자리에 그대로 사용할 수 있습니다. 유일하게 중요한 차이는, FIFO를 사용하는 로직이 @eof 포트에 주의를 기울여야 한다는 것입니다. 이 포트가 high가 되면 로직은 FIFO를 리셋하고 데이터 흐름을 다시 시작해야 합니다.

이상으로 FIFO에 관한 이 시리즈의 네 번째 페이지를 마칩니다. 다음 페이지에서는 '표준 FIFO'를 FWFT FIFO로 변환하고 그 반대로 변환하는 방법과 함께 타이밍(timing)을 개선하는 방법을 보여드립니다.

이 페이지는 영어 원문을 기계 번역한 것입니다. 의문이 드는 부분이 있으면 원문을 참조하시기 바랍니다.
Copyright © 2021-2026. All rights reserved. (dcc38493)