[SoC]Synchronous FIFO 개념 및 설계

새옹지마·2025년 1월 8일

SoC

목록 보기
1/5

SoC에서 사용하는 통신 방식을 크게 3가지로 나눌 수 있습니다.
1. FIFO: 모든 IP에 자주 사용
2. AMBA: 칩내부에서 블럭단위 통신
3. Serial Interface(I2C, SPI, UART..): 칩과 칩간 통신, 주로 펌웨어에서 사용

FIFO에는 Synchronous FIFO와 Asynchronous FIFO가 있는데 전자는 두 Block간의 같은 clk을 사용할 때, 후자는 clk이 다를 때 사용합니다.
오늘은 그 중 Synchronous FIFO(First In First Out)에 대해서 알아보겠습니다.

FIFO란 무엇인가?

FIFO
FIFO는 한국말로 하면 선입선출입니다. 먼저 들어온 것이 먼저 나가게 되는 구조입니다.
이 때, FIFO 저장소(buffer라고 하겠습니다.)에 넣는 행위를 PUSH, 추출하는 행위를 POP이라고 합니다.

왜 사용하는 가?>>시간낭비를 줄이려고

두 block간의 통신을 하는 아래 예시를 보면서 설명드리겠습니다.
우선 FIFO를 사용하지 않을 때 입니다.

B block이 busy면 A block은 데이터를 보낼 수 없으니 기다려야겠죠. 이런 과정에서 많은 delay가 발생합니다. 따라서 FIFO를 buffer로서 사용하게 됩니다.

B가 Busy면 일단 FIFO에 데이터를 집어 넣고 나중에 B가 데이터를 받을 수 있는 상태인 IDLE이 되면 B는 FIFO에서 값을 빼오면 됩니다. A는 B가 busy던 말던 그냥 자기 할 일을 하면 되죠.

이 때, FIFO의 용량보다 A가 보내고싶은 데이터가 많다면 초과하는 만큼의 데이터는 소실됩니다.
따라서 현재 FIFO가 어느정도 찼는지, 얼마나 비었는지에 대한 signal(empty, full)들이 존재하게 됩니다.


동작 원리 & Signal 설명


위의 그림을 보고 설명하겠습니다.

  • 위에서는 depth가 8인 fifo buffer를 사용하였습니다. 그림의 ptr은 pointer이며 rd_ptr은 현재 어디까지 push가 되었는지, wr_ptr은 어디까지 pop했는지를 나타냅니다. 처음엔 둘 다 0번지를 가리키겠지만 (b)에선 한 번 push했으니 wr_ptr이 1이 되죠.
  • (f)처럼 빠진 곳이 없이 모든 depth에 데이터가 작성되어 있는 상태를 full이라고 합니다.
  • (i)처럼 모든 데이터를 pop해서 모든 depth가 비어있는 상태를 empty라고 합니다.
  • full과 empty 모두 rd_ptr==wr_ptr이기 때문에 full과 empty상태를 비교하기 위한 logic을 코드로 구현해야만 합니다. 아래에 코드 예시를 작성해두었으니 참고하시면 됩니다.
  • 미리 말씀드리자면 full상태는 wr_ptr가 한 바퀴 돌아서 rd_ptr를 따라잡은 것이니 (wr_ptr-rd_ptr)는 0이 아니라 8인 것으로 full과 empty를 구분할 수 있습니다. empty일 땐 0이겠죠.

아래 코드를 보시면 a_full, a_empty라는 Signal이 있는데, 이는 almost full, almost empty라는 뜻입니다. Level은 사용자가 언제를 거의 다 찼는지, 비었는지 설정하게 됩니다.
AF_LEVEL이 1이면 depth가 1칸 남았을 때 almost full이 1이 되고, AF_LEVEL이 2면 depth가 2칸 남았을 때 almost full이 1이 됩니다.


코드로 구현(SystemVerilog)

인프런 사이트의 삼코치님의 코드를 참고 및 변형했습니다.
Verilog로 작성하시려면 for문의 문법정도만 수정하면 될 것 같습니다.

module

module sync_fifo #(
   parameter   DEPTH=4,
   parameter   WIDTH=8,   
   parameter   AF_LEVEL = 1,
   parameter   AE_LEVEL = 1,
     parameter   DEPTH_LOG=$clog2(DEPTH)	//depth를 log취했으니 2임.
)(
   input               clk, rstn,
   input               push, pop,   
   input       [WIDTH-1:0]   din,
   output  reg     [WIDTH-1:0]   dout,
   output               full,empty,a_full,a_empty
);
   reg [WIDTH-1:0]      mem[DEPTH-1:0];	   //8bit 데이터가 4개 있다.
   reg [DEPTH_LOG-1:0]   wr_ptr, rd_ptr;   //2bit
   reg [DEPTH_LOG  :0]   diff_ptr;         //3bit
   
   ////push////
   always @(posedge clk, negedge rstn)begin
      if   (!rstn)   begin
         for (int i=0;i<DEPTH;i++)   mem[i] = 0;
      end else if (!full&&push) begin
         mem[wr_ptr]   <= din;
      end
   end

   always @(posedge clk, negedge rstn)begin
      if      (!rstn)   wr_ptr   <= 0;
      else if (!full&&push)   wr_ptr   <= wr_ptr + 1;
   end
   
	////pop////
   always @(posedge clk, negedge rstn)begin
      if      (!rstn)   rd_ptr   <= 0;
      else if (!empty&&pop)   rd_ptr   <= rd_ptr + 1;
   end

   always @(posedge clk, negedge rstn)begin
      if      (!rstn)   dout   <= 0;
      else if (!empty&&pop)   dout   <= mem[rd_ptr];
   end
      
  ////full, empty 조건 작성////
   always @(posedge clk, negedge rstn)begin
      if      (!rstn)   diff_ptr <= 0;
      else         diff_ptr <= diff_ptr + push - pop;   
   end
   
   assign   full    = diff_ptr >= DEPTH;				
   assign   a_full   = diff_ptr >= DEPTH - AF_LEVEL;
   assign   empty   = diff_ptr == 0;
   assign   a_empty = diff_ptr <= AE_LEVEL;   
   
endmodule
  • 마지막에 작성한 것들이 full인지 empty인지 결정하게 되는 코드입니다.
  • full과 empty 둘 다 read_ptr==write_ptr이므로 둘을 분리하는 로직이 필요하게 됩니다.
  • 따라서 둘의 차이가 depth만큼인지, 0인지를 구분하기 위한 diff_ptr 시그널을 추가하였습니다.

testbench

module tb_sync_fifo;
  reg clk, rstn;
  reg push, pop;
  reg [7:0] din;
  wire [7:0] dout;
  wire full,empty,a_full,a_empty;
  
  initial begin
      clk   = 0;
      forever #5 clk = ~clk;
   end
   
  initial begin
    rstn = 1;
    #20 rstn = 0;
    #30 rstn = 1;
  end
  
  initial begin
   push <= 0;            @(posedge rstn);
    push <= 0;              @(posedge clk);
    push <= 1; din <= 'h10; @(posedge clk);
    push <= 1; din <= 'h11; @(posedge clk);
    push <= 1; din <= 'h12; @(posedge clk);
    push <= 1; din <= 'h13; @(posedge clk);
    push <= 0;            @(posedge clk);
  end
  
  initial begin
    pop <= 0;            @(posedge rstn);
    pop   <= 0;   repeat (8)    @(posedge clk);
    pop   <= 1; @(posedge clk); pop <= 0;   @(posedge clk);
    pop   <= 1; @(posedge clk); pop <= 0;   @(posedge clk);
    pop   <= 1; @(posedge clk); pop <= 0;   @(posedge clk);
    pop   <= 1; @(posedge clk); pop <= 0;   @(posedge clk);
    pop <= 0;   repeat (2)    @(posedge clk);
    $finish;
  end

    sync_fifo #(4,8,1,1) u_sync_fifo (
    clk, rstn, push, pop, din, dout, full, empty, a_full, a_empty);
  
endmodule

4번 데이터를 push하고 4번 데이터를 pop하도록 테스트 벤치를 작성했습니다.

waveform

FIFO의 기본적인 작동 원리정도만 구현해보았습니다.
코드는 사용하시는 IP의 조건대로 수정하시면 될 것 같습니다.

봐주셔서 감사합니다😁.
댓글과 하트 부탁드립니당ㅎㅎ😘


Reference

  1. https://ko.wikipedia.org/wiki/%EC%84%A0%EC%9E%85_%EC%84%A0%EC%B6%9C
  2. https://simplefpga.blogspot.com/2012/12/fifofirst-in-first-out-buffer-in-verilog.html
  3. https://www.inflearn.com/course/%EB%B0%98%EB%8F%84%EC%B2%B4-%EC%95%84%EB%82%A0%EB%A1%9C%EA%B7%B8-%ED%9A%8C%EB%A1%9C%EC%84%A4%EA%B3%84-%EC%8B%A4%EB%AC%B4-digital-ip/dashboard
profile
반도체, HW ,SW 탐구생활

0개의 댓글