본문 바로가기
Digital Design/SoC

[SoC] FIFO (Design with Verilog)

by 스테고사우르스 2023. 2. 11.

오늘은 FIFO (First In First Out)에 대해 알아보고 Verilog로 설계해보려고 합니다.

 

FIFO는 직역하면 '선입선출'로 일상생활에서도 많이 들어보셨을 겁니다.

 

회로에서는 데이터를 buffer하기 위해 사용하곤 합니다.

 


Circular Buffer FIFO

그림처럼 33, 15, 137, 11, 60, 94를 넣으면

똑같이 33, 15, 137, 11, 60, 94 순으로 데이터를 빼는 구조입니다.

 

FIFO는 address가 따로 없기 때문에 순서대로 넣고 순서대로 데이터를 뺍니다.

 

자주 사용하는  FIFO는 Circular Buffer FIFO인데 순환하는 구조로 생각하시면 됩니다.

Shift register를 이용해서 번지 수를 한칸 씩 이동시켜서 마지막 번지에서 빼는 것도 좋겠지만

그렇게 되면 F/F이 계속 작동하니까 dynamic power를 너무 많이 소모할 겁니다.

이런 이유 때문에 Circular Buffer FIFO를 선호하는 것 같습니다.

 

 

Circular Buffer FIFO는 그림처럼 두 개의 포인터를 가지고 있습니다.

READ_POINTER / WRITE_POINTER

 

FIFO에 write를 하면 WRITE_POINTER가 하나 커집니다.

반대로 read를 하면 READ_POINTER가 하나 커집니다.

 

그럼 이제 두 포인터를 비교하여서 empty와 full을 비교할 수 있겠죠?

WRITE_POINTER와 READ_POINTER가 같은 위치에 있으면 empty이거나 full일 겁니다.

 

이때 이 두개를 비교하기 위해서 포인터의 크기는 FIFO_depth보다 하나 더 크게 만들어줍니다.

그렇게 되면 포인터가 완전 같으면 empty,

MSB만 다르고 나머지가 같으면 full로 비교할 수 있습니다.

이 부분은 verilog code에서 다시 설명하도록 하겠습니다.

 

 


Block Diagram

 

FIFO의 IO port입니다.

여기에 write_pointer와 read_pointer 레지스터를 추가할 예정입니다.

포인터는 카운터와 비슷한 방식으로 설계하는 것이 좋아 보입니다.

 


Verilog Code

DUT

`timescale 1ns / 1ps

module fifo #(
    parameter DATA_WIDTH = 16,
    parameter FIFO_DEPTH = 8,
    parameter PTR_SIZE = 3
    )
    (
    input                          i_clk,
    input                          i_resetn,
    input                          i_ren,
    input                          i_wen,
    input       [DATA_WIDTH-1:0]   i_wdata,

    output reg  [DATA_WIDTH-1:0]   o_rdata,
    output                          o_empty,
    output                          o_full
    );
    
    reg         [DATA_WIDTH-1:0]   mem_fifo [0:FIFO_DEPTH-1];
    reg             [PTR_SIZE:0]   r_rptr;
    reg             [PTR_SIZE:0]   r_wptr;
    
    assign o_empty = (r_wptr == r_rptr);
    assign o_full  = (r_wptr[PTR_SIZE-1:0] == r_rptr[PTR_SIZE-1:0]) & (r_wptr[PTR_SIZE] != r_rptr[PTR_SIZE]);
    
    always @ (posedge i_clk or negedge i_resetn) begin         // WRITE POINTER
        if (!i_resetn) begin
            r_wptr <= 'd0;
        end
        else if (!o_full && i_wen) begin
            r_wptr <= r_wptr + 1;
        end
        else begin
            r_wptr <= r_wptr;
        end
    end
    
    always @ (posedge i_clk or negedge i_resetn) begin          // READ POINTER
        if (!i_resetn) begin
            r_rptr <= 'd0;
        end
        else if (!o_empty && i_ren) begin
            r_rptr <= r_rptr + 1;
        end
        else begin
            r_rptr <= r_rptr;
        end
    end

    always @ (posedge i_clk) begin         					     // WRITE
        if (!o_full && i_wen) begin
            mem_fifo[r_wptr[PTR_SIZE-1:0]] <= i_wdata;
        end
    end
    
    always @ (posedge i_clk) begin                               //READ
        if (!o_empty && i_ren) begin
            o_rdata <= mem_fifo[r_rptr[PTR_SIZE-1:0]];
        end
    end

endmodule

FIFO에서 데이터를 저장할 곳이 필요한데 register로 선언해주었습니다.

포인터의 크기는 한 사이즈 크게 선언하였습니다.

 

o_empty와 o_full은 assign으로 선언하였고

포인터의 크기를 비교하여 값을 정해주었습니다.

 

write는 full이 아니면서 wen이 켜졌을 때 진행하였습니다.

read는 empty가 아니면서 ren이 켜졌을 때 진행하였습니다.

 

full이어도 write를 할 수 있게 설계하려면 always block내에 조건문에서 조건만 빼주면 됩니다.

 

 

Testbench

`timescale 1ns / 1ps

module tb_fifo();

    parameter DATA_WIDTH = 16;
    parameter FIFO_DEPTH = 8;
    parameter PTR_SIZE = 3;

    reg                       i_clk;
    reg                       i_resetn;
    reg                       i_ren;
    reg                       i_wen;
    reg    [DATA_WIDTH-1:0]   i_wdata;

    wire   [DATA_WIDTH-1:0]   o_rdata;
    wire                      o_empty;
    wire                      o_full;

    fifo fifo (               .i_clk       (i_clk),
                              .i_resetn    (i_resetn),
                              .i_ren       (i_ren),
                              .i_wen       (i_wen),
                              .i_wdata     (i_wdata),
                              
                              .o_rdata     (o_rdata),
                              .o_empty     (o_empty),
                              .o_full       (o_full)      );

    always #5 i_clk = ~i_clk;
    initial begin
        i_clk = 1'd0; i_resetn = 1'd1;
        i_ren = 1'd0; i_wen = 1'd0;
        #1
        i_resetn = 1'd0;
        #1
        i_resetn = 1'd1;
        #13
        i_wen = 1'd1;
        i_wdata = 'd100;
        #10
        i_wdata = 'd101;
        #10
        i_wdata = 'd102;
        #10
        i_wdata = 'd103;
        #10
        i_wdata = 'd104;
        #10
        i_wdata = 'd105;
        #10
        i_wdata = 'd106;
        #10
        i_wdata = 'd107;
        #10
        i_wdata = 'd108;
        #10
        i_wen = 1'd0;
        #10
        i_ren = 1'd1;
        #100
        i_ren = 1'd0;
        i_wen = 1'd1;
        #10
        i_wdata = 'd44;
        #10
        i_wdata = 'd55;
        #10
        i_wdata = 'd66;
        #20
        $finish;
    end

endmodule

 

 

Simulation

노란 점선을 보면 wen이 켜지고 wdata가 들어왔을 때 full이 아니기 때문에 write를 실행하였습니다.

 

이후 첫번째 빨간 네모에서 rptr == wptr이라 full이 떴습니다.

따라서 더이상 write를 수행하지 않습니다.

 

회색 점선을 보면 ren이 켜져서 들어온 순서대로 read를 하고 있습니다.

 

다음 빨간 네모에서는 모두 읽었기 때문에 empty가 켜지고 더 읽지 않습니다.

 

마지막 노란 점선에서 다시 full이 아니고 wen이 켜져있기 때문에 write를 수행하고 있습니다.

 


 

댓글