在IC基础(二)中我们已经介绍了异步FIFO,本章介绍的同步FIFO会比上一章简单许多。
一、什么是同步FIFO?
这部是很简单的问题吗?同步FIFO就是读写时钟域都是同一个时钟的FIFO。也就是说同步FIFO不用考虑跨时钟域的问题。同步FIFO和异步FIFO的根本区别就是读写时钟的不一样。
二、同步FIFO的常见参数
clk:读写时钟
rst_n:同步复位信号
w_en:写使能信号
r_en:读使能信号
wdata:输入数据信号
rdata:读出数据信号
wfull:满信号
rempty:空信号
三、同步FIFO建模
同步FIFO的模型可比异步FIFO的模型简单多了。同步FIFO的模型一般如下所示。(懒得画图了,直接用Xilinx的模型)
可以看到我们设计的端口信号和XIlinx的同步FIFO的模型的端口信号是一致的。FIFO的读写行为如下:
当需要写入数据时:需要判断两个信号,一个是wfull信号,另一个是写使能信号w_en。只有FIFO不满并且写使能有效的时候才可以写入数据。用逻辑来表达就是:
(!wfull&&w_en==1’b1),并且此时的数据要和写使能对齐。用代码来表示就是:
always@(*) begin
if(rst_n==1'b0)
wdata = 0;
else if(!wfull&&w_en==1)
wdata = i;
else
wdata <= 0;
end
如果不满足这个条件,那么写入数据就会有错误。
当需要读出数据的时候:需要判断两个信号,一个是rempty信号,另一个是r_en信号。只有当FIFO不空,并且读使能有效的时候才可以读取数据。用逻辑来表示就是:
(!rempty&&r_en==1'b1)
读时能没有那么讲究了,只要不空随时可以读。
always@(posedge clk or negedge rst_n) begin
if(rst_n==1'b0)
r_en =1'b0;
else if(!rempty)
r_en = 1'b1;
else
r_en = 1'b0;
end
四、地址跳变
FIFO设计最难的地方就在于如何设计读写指针,反正我在调试的时候遇到了很多问题。
总之记住:
FIFO每读一个数据,读指针就加一。FIFO每写一个数,写指针就加一。
FIFO的读和写指针永远都是指向下一个即将要读或者写的单元。
五、判空和判满
同步FIFO的判空和判满也是和异步FIFO一样的,区别在于异步FIFO在各自的时钟域使用格雷码进行判空和判满;而同步FIFO只有一个时钟域,判空和判满是使用的自然二进制地址。
FIFO的读指针和写指针相同时不是空就是满,同步FIFO同样需要一个额外的位来区别是空还是满。
判空:assign rempty_val = (rbinnext==wbinnext);
判满:assign wfull_val = (rbinnext=={~wbinnext[ADDR_SIZE],wbinnext[ADDR_SIZE-1:0]});
注意:这里使用的是rbinnext和wbinnext进行判空和判满。因为我们说过了,FIFO的指针总是指向下一个即将要写入或者读取的数据,因此使用的是对应的next信号。而把真正需要送到RAM的地址用:
assign raddr = rbinnext[ADDR_SIZE-1:0];
assign waddr = wbinnext[ADDR_SIZE-1:0];
六、代码实现
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2019/03/25 10:00:51
// Design Name:
// Module Name: async_fifo
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module async_fifo#(
parameter DATA_SIZE = 8,
parameter ADDR_SIZE = 4
)(
input clk,
input rst_n,
input w_en,
input r_en,
input [DATA_SIZE-1:0] wdata,
output reg [DATA_SIZE-1:0] rdata,
output reg wfull,
output reg rempty
);
//开辟一个RAM存储空间
localparam DEPTH = 1<<(ADDR_SIZE);
reg [DATA_SIZE-1:0] mem [0:DEPTH-1];
//定义读指针和写指针
reg [ADDR_SIZE:0] rbin,wbin;
wire [ADDR_SIZE:0] rbinnext,wbinnext;
wire [ADDR_SIZE-1:0] raddr,waddr;
wire wfull_val;
wire rempty_val;
//写地址变化
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rbin <= 0;
end
else
rbin <= rbinnext;
end
//读地址变化
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
wbin <= 0;
end
else
wbin <= wbinnext;
end
//满标志
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;
end
//空标志
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
rempty <= 1'b1;
else
rempty <= rempty_val;
end
//读数据
always@(*)begin
if(!rst_n)
rdata <= 0;
else if(r_en==1'b1)
rdata <= mem[raddr];
else
rdata <= rdata;
end
// assign rdata = mem[raddr];
//写数据
always@(posedge clk)begin
if(w_en==1'b1)
mem[waddr] <= wdata;
else
mem[waddr] <= mem[waddr];
end
//逻辑设计
assign wbinnext = !wfull?(wbin + w_en):wbin;
assign rbinnext = !rempty?(rbin + r_en):rbin;
assign raddr = rbinnext[ADDR_SIZE-1:0];
assign waddr = wbinnext[ADDR_SIZE-1:0];
assign wfull_val = (rbinnext=={~wbinnext[ADDR_SIZE],wbinnext[ADDR_SIZE-1:0]});
assign rempty_val = (rbinnext==wbinnext);
endmodule
七、测试文件
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2019/03/25 10:30:43
// Design Name:
// Module Name: TB_async_fifo
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module TB_async_fifo;
parameter DATA_SIZE = 8;
parameter ADDR_SIZE = 4;
parameter CLK_PERIOD = 10;
integer i = 3;
reg clk;
reg rst_n;
reg w_en;
reg r_en;
reg [DATA_SIZE-1:0] wdata;
wire [DATA_SIZE-1:0] rdata;
wire wfull;
wire rempty;
initial begin
clk = 0;
end
always#(CLK_PERIOD/2) clk = ~clk;
initial begin;
rst_n = 1'b0;
#15;
rst_n = 1'b1;
end
// always@(rst_n or wfull) begin
// if(rst_n==1'b0)
// w_en = 1'b0;
// else if(!wfull)
// w_en = 1'b1;
// else
// w_en = 1'b0;
// end
always@(posedge clk or negedge rst_n) begin
if(rst_n==1'b0)
w_en = 1'b0;
else if(!wfull)
w_en = 1'b1;
else
w_en = 1'b0;
end
// always@(rst_n or rempty) begin
// if(rst_n==1'b0)
// r_en =1'b0;
// else if(!rempty)
// r_en = 1'b1;
// else
// r_en = 1'b0;
// end
always@(posedge clk or negedge rst_n) begin
if(rst_n==1'b0)
r_en =1'b0;
else if(!rempty)
r_en = 1'b1;
else
r_en = 1'b0;
end
always@(posedge clk or negedge rst_n) begin
if(rst_n==1'b0)
i <= 3;
else if(!wfull)
i <= i+1;
else
i <= i;
end
always@(*) begin
if(rst_n==1'b0)
wdata = 0;
else if(!wfull&&w_en==1)
wdata = i;
else
wdata <= 0;
end
async_fifo#(
.DATA_SIZE(DATA_SIZE),
.ADDR_SIZE(ADDR_SIZE)
)
uut(
.clk(clk),
.rst_n(rst_n),
.w_en(w_en),
.r_en(r_en),
.wdata(wdata),
.rdata(rdata),
.wfull(wfull),
.rempty(rempty)
);
endmodule
八、测试波形