参考网上资料,整理了同步fifo的两种设计方法。
一、FIFO是什么?
fifo是一种先入先出的存储机制,同步fifo指读写数据共用一个时钟。
本文是基于双端口RAM设计的同步fifo,深度为16,宽度为8。
二、同步FIFO
(一)双端口RAM设计
1. 思路
设计一个深度16,宽度8的双端口RAM。
Verilog代码如下:
//设计一个伪双端口的RAM,宽度8bit,深度16
//具有独立的读写时钟
module dp_RAM(
input wr_clk,
input rd_clk,
input wr_allow,
input rd_allow,
input [3:0] wr_addr,
input [3:0] rd_addr,
input [7:0] wr_data,
output reg [7:0] rd_data
);
reg [7:0] memory[15:0]; //16个8位的存储器
always@(posedge wr_clk) begin
if(wr_allow)
memory[wr_addr] <= wr_data;
end
always@(posedge rd_clk) begin
if(rd_allow)
rd_data <= memory[rd_addr];
end
endmodule
(二)同步FIFO设计
- 方法一:利用计数器对fifo中数据计数,确定空满标志。
Verilog代码如下:
`timescale 1ns / 1ps
Company:
Engineer:
Create Date: 2022/04/24 15:56:38
Design Name:
Module Name: sync_fifo
Project Name:
Target Devices:
Tool Versions:
Description: fifo深度为16,宽度为8
Dependencies:
Revision:
Revision 0.01 - File Created
Additional Comments:
module sync_fifo(
input clk, // 同步时钟
input rst_n,
input rd_en,
input wr_en,
input [7:0] wr_data,
output [7:0] rd_data,
output reg full, // fifo满信号
output reg empty, // fifo空信号
output reg [3:0] fifo_cnt // 对fifo中的数据计数,深度为16---地址总线4
); // 每向fifo写入一个数据,fifo_cnt就加1;每读出数据就减1
reg [3:0] rd_addr;
reg [3:0] wr_addr;
wire rd_allow; // 双端口RAM的读控制信号
wire wr_allow;
assign rd_allow = (rd_en && !empty); // 空不能读(当外部fifo有读使能信号且非空标志,双端口RAM读允许)
assign wr_allow = (wr_en && !full); // 满不能写
dp_RAM dp_RAM_inst( // 例化
.wr_clk (clk), // 设计的fifo是同步时钟
.rd_clk (clk),
.wr_allow(wr_allow),
.rd_allow(rd_allow),
.wr_addr (wr_addr),
.rd_addr (rd_addr),
.wr_data (wr_data),
.rd_data (rd_data)
);
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
empty <= 0;
else
empty <= (!wr_en && fifo_cnt==0) || (!wr_en && fifo_cnt==4'd1 && rd_en);
// (没有写&&计数值为0) || (没有写&&计数值为1&&读信号)
// 已空 或 将空,两种情况fifo为空
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
full <= 0;
else
full <= (!rd_en && fifo_cnt==15) || (!rd_en && fifo_cnt==14 && wr_en);
// 已满 或 将满
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rd_addr <= 0;
end
else if(rd_en & !empty)
rd_addr <= rd_addr + 1;
else rd_addr <= rd_addr;
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
wr_addr <= 0;
end
else if(wr_en & !full)
wr_addr <= wr_addr + 1;
else wr_addr <= wr_addr;
end
// always@(posedge clk or negedge rst_n) begin // 没有考虑同时读写
// if(!rst_n)
// fifo_cnt <= 0;
// else if((!rd_allow && wr_allow) || (rd_allow && !wr_allow)) begin
// if(wr_allow) fifo_cnt <= fifo_cnt + 1;
// else fifo_cnt <= fifo_cnt - 1;
// end
// end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
fifo_cnt <= 0;
else begin
case({rd_en,wr_en})
2'b00: fifo_cnt <= fifo_cnt;
2'b01: if(!full)
fifo_cnt <= fifo_cnt + 1;
else
fifo_cnt <= fifo_cnt;
2'b10: if(!empty)
fifo_cnt <= fifo_cnt - 1;
else
fifo_cnt <= fifo_cnt;
2'b11: fifo_cnt <= fifo_cnt;
endcase
end
end
endmodule
- 方法二: 将原fifo地址位扩展一位,用最高位判断空满标志。
Verilog代码如下:
module sync_fifo #(
parameter data_addr = 4, // 参数赋值,模块内参数视为本地参数
parameter data_width = 8,
parameter fifo_depth = 16
)
(
input clk,
input rst_n,
input wr_en,
input [data_width-1:0]wr_data,
// output [fifo_addr-1 :0]wr_addr,
input rd_en,
output reg [data_width-1:0]rd_data,
// output [fifo_addr -1:0]rd_addr,
output full,
output empty
);
//================定义读写指针,多一位来控制空满==================================
reg [data_addr:0] wr_addr_ptr;
reg [data_addr:0] rd_addr_ptr;
wire [data_addr-1 : 0] wr_addr; // fifo的读写地址,遵循先入先出原则
wire [data_addr-1 : 0] rd_addr;
assign wr_addr = wr_addr_ptr[data_addr-1 : 0];
assign rd_addr = rd_addr_ptr[data_addr-1 : 0];
//=============读写指针模块====================================================
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_addr_ptr <= 0;
else if(wr_en && (~full))
wr_addr_ptr <= wr_addr_ptr + 1;
else // 防止产生锁存器
wr_addr_ptr <= wr_addr_ptr;
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
rd_addr_ptr <= 0;
else if(rd_en && (~empty))
rd_addr_ptr <= rd_addr_ptr + 1;
else
rd_addr_ptr <= rd_addr_ptr;
end
//==============双端口RAM=======================
integer i;
reg [data_width-1:0] memory[fifo_depth-1:0]; //定义fifo_depth个大小为data_width宽度的内存
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
for(i=0;i<fifo_depth;i=i+1) begin
memory[i] <= 0;
end
else if(wr_en && (!full))
memory[wr_addr] <= wr_data;
else
memory[wr_addr] <= memory[wr_addr];
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
rd_data <= 0; // 复位,读出数据为0
else if(rd_en && (!empty))
rd_data <= memory[rd_addr];
else
rd_data <= rd_data;
end
//================空满标志信号输出==================
//=======读写指针相同(读指针第一次追上写指针),输出空标志=======
//===============读写指针最高位不同,其他位相同,输出满标志=======
assign empty = (wr_addr_ptr == rd_addr_ptr) ? 1'b1 : 1'b0;
assign full = ({!(wr_addr_ptr[data_addr]),wr_addr} == rd_addr_ptr) ? 1'b1 : 1'b0;
endmodule
仿真testbench文件:
`timescale 1ns / 1ps
`define clk_period 20
module sync_fifo_tb();
reg clk;
reg rst_n;
reg rd_en;
reg wr_en;
reg [7:0] wr_data;
wire full;
wire empty;
wire [7:0] rd_data;
always #10 clk = ~clk;
initial begin
clk = 0;
rst_n = 0;
wr_en = 0;
rd_en = 0;
wr_data = 0;
#20;
rst_n = 1;
write; // 写任务
read; // 读任务
end
task write;
integer i;
begin
for(i=0;i<16;i=i+1) begin
wr_en = 1;
@(posedge clk);
wr_data = {$random}%256; // 产生0-255范围的随机数
end
wr_en = 0;
end
endtask
task read;
integer i;
begin
for(i=0;i<16;i=i+1) begin
rd_en = 1;
@(posedge clk);
end
rd_en = 0;
end
endtask
sync_fifo inst(
.clk (clk), // 同步时钟
.rst_n (rst_n),
.rd_en (rd_en),
.wr_en (wr_en),
.wr_data (wr_data),
.rd_data (rd_data),
.full (full),
.empty (empty)
);
endmodule