오늘은 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를 수행하고 있습니다.
'Digital Design > SoC' 카테고리의 다른 글
[AMBA] AXI Protocol 설계(Verilog) - ② Write Transaction (0) | 2023.02.24 |
---|---|
[AMBA] AXI Protocol 설계(Verilog) - ① Signal Descriptions (0) | 2023.02.24 |
[AMBA] APB Protocol 설계 (Design with Verilog) (1) | 2023.02.09 |
[AMBA] AXI (Advanced eXtensible Interface) (1) | 2023.02.08 |
[AMBA] AHB (Advanced High-Performance Bus) (1) | 2023.02.06 |
댓글