本文调用vivado中ip核的两个standard fifo,通过调整使能信号(wr_en,rd_en)的时序和data_in(与in_1,in_2),data_out(与out_1,out_2)的接通,实现乒乓fifo传输数据流的无缝输入输出,不丢失任何一位数据。
本项目的难点:
1、写入:必须在fifo1停止写入数据之后,下一个时钟上升沿马上把数据写入fifo2,但是由于standard fifo的运行模式,若我们把使能信号简单地分配给统一的状态机,会出现读/写不全、重复读/写等问题。
在almost_full_1信号拉高之后,下一个时钟上升沿fifo1将写入最后一位数据,但state仍旧未切换,等state切换再拉高wr_en_2会使得至少一位数据发生丢失。因此我们需要在almost_full_1信号拉高之后,将wr_en_2提前拉高,并将wr_en_1拉低,保证对data_in数据的完整采集,且不会出现一个数据写入到两个fifo的现象。如下图所示:fifo_1最后一位写入的是“0”,此后将data_in新输入的数据“1”直接写入fifo_2。
特别声明:wr_ack信号拉高前一个周期的数据能够写入,而拉低前的最后一位数据是无法写入fifo(因为该信号判断流程为:尝试写入——写入成功/不成功——拉高/拉低,属于“滞后”标记位)
既然读使能信号wr_en需要提前打开,那么data_in所进入的通道(in_1\in_2)也应该随着使能信号wr_en同时切换(注意:是同时切换,wr_en一变化,通道同时就切换,是同时操作,并不是滞后一个时期),如下图所示:
2、读出:
读出操作相对而言就比较简单,在对写入操作进行上述处理后,rd_en只需在n_state切换的时刻将rd_en进行切换,即可正常读出数据。
data_out根据rd_en的切换来切换即可,时序不需要多做要求,自然顺着读就行,不会丢数据,仅会滞后几个周期。
特别声明:只要数据在valid信号拉高的“框内"即可成功读出(因为该信号判断流程为:valid拉高/拉低——能够/不能成功读出——成功写入/没写成功,属于“提前”标记位)
3、ip核的配置
看图:
源代码:
`timescale 1ns / 1ps
module fifo_pp_myself(
input clk,
input _rst,
input wr_en,
input [9:0]data_in,
output reg [9:0]data_out
);
reg [3:0]c_state;
reg [3:0]n_state;
reg wr_en_1;
reg rd_en_1;
reg wr_en_2;
reg rd_en_2;
reg [9:0]in_1;
reg [9:0]in_2;
wire [9:0]out_1;
wire [9:0]out_2;
wire full_1;
wire full_2;
wire empty_1;
wire empty_2;
wire [9:0]data_out_r;
wire wr_ack_1,wr_ack_2;
wire valid_1,valid_2;
wire almost_full_1,almost_full_2;
wire almost_empty_1,almost_empty_2;
parameter
idle = 4'b0000,
start = 4'b0010,
w1_r2 = 4'b1000,
r1_w2 = 4'b0100;
/********例话两个标准FIFO*********/
fifo_generator_0 fifo_1(
.clk(clk),
.srst(~_rst),
.din(in_1),
.full(full_1),
.almost_full(almost_full_1),
.wr_en(wr_en_1),
.empty(empty_1),
.almost_empty(almost_empty_1),
.dout(out_1),
.rd_en(rd_en_1),
.wr_ack(wr_ack_1),
.valid(valid_1)
);
fifo_generator_0 fifo_2(
.clk(clk),
.srst(~_rst),
.din(in_2),
.full(full_2),
.almost_full(almost_full_2),
.wr_en(wr_en_2),
.empty(empty_2),
.almost_empty(almost_empty_2),
.dout(out_2),
.rd_en(rd_en_2),
.wr_ack(wr_ack_2),
.valid(valid_2)
);
/*******状态连续*********/
always@(posedge clk or negedge _rst)
if(!_rst)
c_state <= idle;
else
c_state <= n_state;
/********状态机**********/
always@(posedge clk or negedge _rst)
case(c_state)
idle:
if(wr_en)
n_state <= start;
else
n_state <= idle;
start:
if(full_1)
n_state <= r1_w2;
else
n_state <= start;
r1_w2:
if(empty_1 | full_2)
n_state <= w1_r2;
else
n_state <= r1_w2;
w1_r2:
if(empty_2 | full_1)
n_state <= r1_w2;
else
n_state <= w1_r2;
default:n_state <= idle;
endcase
/******使能信号*******/
always@(posedge clk or negedge _rst)
begin
case(n_state)
idle:
begin
wr_en_1 <= 1'b0;
rd_en_1 <= 1'b0;
wr_en_2 <= 1'b0;
rd_en_2 <= 1'b0;
end
start:
begin
if(almost_full_1)//提前打开下一个写入通道的开关,防止丢交接时候的两个数据
begin
wr_en_1 <= 1'b0;
wr_en_2 <= 1'b1;
end
else
begin
wr_en_1 <= 1'b1;
rd_en_1 <= 1'b0;
wr_en_2 <= 1'b0;
rd_en_2 <= 1'b0;
end
end
r1_w2:
begin
if(almost_full_2)
begin
wr_en_1 <= 1'b1;
wr_en_2 <= 1'b0;
end
else
begin
wr_en_1 <= 1'b0;
rd_en_1 <= 1'b1;
wr_en_2 <= 1'b1;
rd_en_2 <= 1'b0;
end
end
w1_r2:
begin
if(almost_full_1)
begin
wr_en_1 <= 1'b0;
wr_en_2 <= 1'b1;
end
else
begin
wr_en_1 <= 1'b1;
rd_en_1 <= 1'b0;
wr_en_2 <= 1'b0;
rd_en_2 <= 1'b1;
end
end
endcase
end
/***写数据的交互****/
always@(posedge clk or negedge _rst)
case(n_state)
start: in_1 <= data_in;
endcase
always@(posedge clk or negedge _rst)
case(c_state)
start:begin
if(almost_full_1)begin
in_2 <= data_in;
in_1 <= in_1; end
else
in_1 <= data_in;
end
r1_w2:begin//4
if(almost_full_2)begin
in_1 <= data_in;
in_2 <= in_2;end
else
begin
in_2 <= data_in;
//data_out <= out_1;
end
end
w1_r2://8
begin
if(almost_full_1)begin
in_2 <= data_in;
in_1 <= in_1;end
else
begin
in_1 <= data_in;
// data_out <= out_2;
end
end
endcase
/*******data读出只需要跟读使能匹配就行*************/
always@(posedge clk or negedge _rst)
if(!_rst)
data_out <= 0;
else if(rd_en_1)
data_out <= out_1;
else
data_out <= out_2;
endmodule
tb文件:
`timescale 1ns / 1ps
module fifo_pp_myself_tb();
reg clk;
reg _rst;
reg wr_en;
reg [9:0]data_in;
wire [9:0]data_out;
fifo_pp_myself fifo_pp_myself(
.clk (clk ),
._rst (_rst ),
.wr_en (wr_en ),
.data_in (data_in ),
.data_out (data_out)
);
initial clk = 1;
always#10 clk = ~clk;
always#20 data_in = data_in + 1'b1;
initial begin
_rst = 1'b1;
#20;
_rst = 1'b0;
// wr_en = 1'b1;
#200;
wr_en = 1'b1;//开启读使能之后再拉高复位,这样第一个输入的数据也能读出来
#20;
_rst = 1'b1;
//#500;
data_in = 10'b0;
#200000;
$stop;
end
endmodule
仿真图:
根据细节图,在状态交接处,数据仍是连续不间断,且不会出现重复读写同一数据的情况。
根据全局图,可以看出每次写入fifo时的最后一位都是0,说明每个周期读取次数也并没有出现问题(多读或者少读,长长的那一位都不会是0,可能是3,5,7)
结论:乒乓fifo操作基本实现,目前并未发现有bug。