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 저장소(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)들이 존재하게 됩니다.

위의 그림을 보고 설명하겠습니다.
(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이 됩니다.
인프런 사이트의 삼코치님의 코드를 참고 및 변형했습니다.
Verilog로 작성하시려면 for문의 문법정도만 수정하면 될 것 같습니다.
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
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하도록 테스트 벤치를 작성했습니다.

FIFO의 기본적인 작동 원리정도만 구현해보았습니다.
코드는 사용하시는 IP의 조건대로 수정하시면 될 것 같습니다.
봐주셔서 감사합니다😁.
댓글과 하트 부탁드립니당ㅎㅎ😘