IP-FIFO的介绍
FIFO(First In First Out,即先入先出),是一种数据缓冲器,用来实现数据先入先出的读写方式。与 ROM 或 RAM 的按地址读写方式不同,FIFO 的读写遵循“先进先出”的 原则,即数据按顺序写入 FIFO,先被写入的数据同样在读取的时候先被读出,所以 FIFO 存储器没有地址线。FIFO 有一个写端口和一个读端口外部无需使用者控制地址,使用方 便。 FIFO 存储器主要是作为缓存,应用在同步时钟系统和异步时钟系统中,在很多的设计中都会使用,后面实例中如:多比特数据做跨时钟域的转换、前后带宽不同步(跨位宽)等都用到了 FIFO。FIFO 根据读写时钟是否相同,分为 SCFIFO(同步 FIFO)和 DCFIFO(异步 FIFO),SCFIFO 的读写为同一时钟,应用在同步时钟系统中,起到了缓存的作用;DCFIFO 的读写时钟不同,应用在异步时钟系统中,可以解决跨时域和跨位宽的问题。
SCFIFO IP核配置



full:写满标志位,有效表示 FIFO 已经存储满了,此时应该通过该信号控制写请求信 号(也称为写使能信号),禁止再往 FIFO 中写入数据,防止数据溢出丢失。当写入数据 量达到FIFO设置的最大空间时,时钟上升沿写入最后一个数据同时full拉高;读取数据时 随时钟上升沿触发同时拉低。
empty:读空标志位,有效表示 FIFO 中已经没有数据了,此时应该通过该信号控制读 请求信号(也称为读使能信号),禁止 FIFO 继续再读出数据,否则读出的将是无效数 据。与full相反,写入数据同时拉低;读到最后一个数据同时拉高。
usedw:显示当前 FIFO 中已存数据个数,与写入数据的个数是同步的,即写第一个数 据时就置1,空或满时值为0(满是因为寄存器溢出)。 剩下的信号可以选择添加也可以选择不添加,这里我们只做简单的介绍不进行添加。
almost full:几乎满标志信号,我们可以控制 FIFO 快要被写满的时候和 full 信号的作 用一样。 almost empty:几乎空标志信号,我们可以控制 FIFO快要被读空的时候和 empty信号 的作用一样。
Asynchronous clear:异步复位信号,用于清空FIFO。
Synchronous clear:同步复位信号,用于清空FIFO。

普通同步 FIFO 模式,当前读请求有效的下一拍数据才出来,像时序逻辑一样,当然我们可以将rdreq打一拍,就可以解决数据和读请求信号不对齐的问题;而下面的则是首字先出 FIFO 模式,读请求来到之前第一个数据就已经先出来了, 使得当前的读请求有效时立刻输出数据,像组合逻辑一样,而不会像普通模式那样当前读请求有效后下一拍 才会输出数据。
指定实现 FIFO 使用的存储块类型和 FIFO 的存储深度,具体可选值与使用的 FPGA芯片有关,默认为Auto,我们一般使用默认值就可以了。
选择框1:选择“Yes(best speed)”将获得最大的速度(性能),但是需要牺牲更多的资源(面积);选择“No(smallest area)”将使用更少的资源(面积)但是速度(性能)可能不是最快的。没有特殊要求,默认即可。
选择框2:选择是否禁止上溢检测和下溢检测的保护电路(上溢检测保护电路主要是用于 在FIFO存储满时禁止继续写数据;下溢检测保护电路主要是用于FIFO被读空时,禁止继续读数据)。如果需要则保持默认,如果不需要可以选择禁用下面的两个选项来提高FIFO 速度(性能),没有特殊需求,保持默认即可。
hui'zhi 选择框3:使用逻辑单元实现 FIFO 存储器。这里使用默认设置,即用存储块实现 FIFO。

绘制模块框图及波形图

编写模块代码
module fifo(
input wire clk ,
input wire rst_n ,
output wire [7:0] q
);
reg [9:0] cnt ;
reg wrreq;
reg rdreq;
reg [7:0] data ;
wire full ;
wire empty;
wire[7:0] usedw;
//cnt
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt <= 10'd0;
else
cnt <= cnt + 10'd1;
end
//wrreq、data
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
wrreq <= 1'b0;
data <= 8'd0;
end
else if(10'd2 <= cnt && cnt <= 10'd257)
begin
wrreq <= 1'b1;
data <= cnt – 10'd2;
end
else
begin
wrreq <= 1'b0;
data <= 8'd0;
end
end
//rdreq
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
rdreq <= 1'b0;
else if(10'd258 <= cnt && cnt <= 10'd513)
rdreq <= 1'b1;
else
rdreq <= 1'b0;
end
scfifo_8x256 scfifo_8x256_inst (
.clock ( clk ),
.data ( data ),
.rdreq ( rdreq ),
.wrreq ( wrreq ),
.empty ( empty ),
.full ( full ),
.q ( q ),
.usedw ( usedw )
);
endmodule
编写仿真代码
`timescale 1ns/1ps
module fifo_tb();
reg clk ;
reg rst_n;
initial
begin
clk <= 1'b0;
rst_n <= 1'b0;
#123
rst_n <= 1'b1;
end
always #10 clk = ~clk;
fifo fifo_inst(
.clk (clk ) ,
.rst_n (rst_n) ,
.q ()
);
endmodule
仿真验证



综合练习
使用fifo和ram-ip核完成

绘制模块框图及波形图

然后建立工程,按照需求配置fifo和ram-ip核。
编写模块代码
这里是写在一起的没有划分模块写
module fifo_ram(
input wire clk ,
input wire rst_n ,
input wire wrreq ,
input wire [15:0] data ,
output wire [15:0] q1 ,
output reg [15:0] q2 ,
output reg [10:0] addr ,
output wire [15:0] q3
);
reg rdreq;
wire [15:0] fifo_q;
wire full;
wire empty;
wire [10:0] usedw;
//rdreq
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rdreq <= 1'b0;
else if(full && !usedw)//写满时拉高
rdreq <= 1'b1;
else if(usedw == 1'b1)//读空时拉低
rdreq <= 1'b0;
else
rdreq <= rdreq;
end
scfifo_16x2048 scfifo_16x2048_inst (
.clock ( clk ),
.data ( data ),
.rdreq ( rdreq ),
.wrreq ( wrreq ),
.empty ( empty ),
.full ( full ),
.q ( fifo_q),
.usedw ( usedw )
);
//fifo的读请求信号和fifo的输出数据信号延迟了一拍
reg ram1_wren;//打一拍,与输出数据对齐,ram1的写使能
reg ram1_wren_reg;
wire ram1_wrflag;//采集写使能信号的上升沿
reg ram1_rdflag;//采集写使能信号的下降沿
reg [10:0] ram1_addr;
wire[15:0] ram1_q;
//ram1_wren
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
ram1_wren <= 1'b0;
ram1_wren_reg <= 1'b0;
end
else
begin
ram1_wren <= rdreq;
ram1_wren_reg <= ram1_wren;
end
end
//ram1_wrflag
assign ram1_wrflag = !ram1_wren && rdreq;
//ram1_rdflag
always@(*)
begin
if(!rst_n)
ram1_rdflag = 1'b0;
else
ram1_rdflag = ram1_wren_reg && !ram1_wren;
end
//ram1_addr
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
ram1_addr <= 11'b0;
else if(ram1_rdflag || ram1_wrflag)
ram1_addr <= 11'b0;
else
ram1_addr <= ram1_addr + 1'b1;
end
ram1_16x2048 ram1_16x2048_inst (
.address ( ram1_addr ),
.clock ( clk ),
.data ( fifo_q ),
.wren ( ram1_wren ),
.q ( ram1_q )
);
///////////////////////////////////////////////////////////////倒着读q1
reg ram1_rdflag_r1;//ram1_rdflag打两拍
reg ram1_rdflag_r2;
reg wren_ram2_1;//ram2倒着读的写使能
reg [10:0] wraddr_ram2_1;
reg [10:0] rdaddr_ram2_1;
reg rden_ram2_1;//ram2倒着读的读使能
/////////////////////////////////////写操作
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
ram1_rdflag_r1 <= 1'b0;
ram1_rdflag_r2 <= 1'b0;
end
else
begin
ram1_rdflag_r1 <= ram1_rdflag;
ram1_rdflag_r2 <= ram1_rdflag_r1;
end
end
//写使能wren_ram2_1与输入数据对齐
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
wren_ram2_1 <= 1'b0;
else if(ram1_rdflag_r2)
wren_ram2_1 <= 1'b1;
else if(wraddr_ram2_1 == 11'd2047)
wren_ram2_1 <= 1'b0;
else
wren_ram2_1 <= wren_ram2_1;
end
//写地址wraddr_ram2_1对齐
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
wraddr_ram2_1 <= 11'd0;
else if(wren_ram2_1)
wraddr_ram2_1 <= wraddr_ram2_1 + 11'd1;
else
wraddr_ram2_1 <= 11'd0;
end
//读操作(倒着读)
reg wren_ram2_1_r;//打拍
wire wren_nege_ram2_1;//读使能下降沿
//wren_ram2_1_r
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
wren_ram2_1_r <= 1'b0;
else
wren_ram2_1_r <= wren_ram2_1;
end
//wren_nege_ram2_1
assign wren_nege_ram2_1 = !wren_ram2_1 && wren_ram2_1_r;
//rden_ram2_1
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rden_ram2_1 <= 1'b0;
else if(wren_nege_ram2_1)
rden_ram2_1 <= 1'b1;
else if(rdaddr_ram2_1 == 11'd0)
rden_ram2_1 <= 1'b0;
else
rden_ram2_1 <= rden_ram2_1;
end
//rdaddr_ram2_1
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rdaddr_ram2_1 <= 11'd2047;
else if(rden_ram2_1)
rdaddr_ram2_1 <= rdaddr_ram2_1 – 1'b1;
else
rdaddr_ram2_1 <= 11'd2047;
end
ram2_16x2048 ram2_16x2048_inst1 (
.clock ( clk ),
.data ( ram1_q ),
.rdaddress ( rdaddr_ram2_1 ),
.rden ( rden_ram2_1 ),
.wraddress ( wraddr_ram2_1 ),
.wren ( wren_ram2_1 ),
.q ( q1 )
);
////////////////////////////////////////////////////////////////找最大值及地址
wire [15:0] q_reg;
reg [10:0] rdaddr_ram2_2;
//顺序读操作
//rdaddr_ram2_2
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rdaddr_ram2_2 <= 11'd0;
else if(rden_ram2_1)
rdaddr_ram2_2 <= rdaddr_ram2_2 + 1'b1;
else
rdaddr_ram2_2 <= 11'd0;
end
//找最大值及地址
//地址打2拍,与数据对齐
reg [10:0] rdaddr_ram2_2_r1;
reg [10:0] rdaddr_ram2_2_r2;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rdaddr_ram2_2_r1 <= 11'd0;
rdaddr_ram2_2_r2 <= 11'd0;
end
else
begin
rdaddr_ram2_2_r1 <= rdaddr_ram2_2;
rdaddr_ram2_2_r2 <= rdaddr_ram2_2_r1;
end
end
//q2、addr
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
q2 <= 16'd0;
addr <= 11'd0;
end
else if(q_reg > q2)
begin
q2 <= q_reg;
addr <= rdaddr_ram2_2_r2;
end
end
ram2_16x2048 ram2_16x2048_inst2 (
.clock ( clk ),
.data ( ram1_q ),
.rdaddress ( rdaddr_ram2_2 ),
.rden ( rden_ram2_1 ),
.wraddress ( wraddr_ram2_1 ),
.wren ( wren_ram2_1 ),
.q ( q_reg )
);
////////////////////////////////////////////////////////从中间读q3
reg [10:0] rdaddr_ram2_3;
//rdaddr_ram2_3
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rdaddr_ram2_3 <= 11'd1024;
else if(rden_ram2_1)
rdaddr_ram2_3 <= rdaddr_ram2_3 + 1'b1;
else
rdaddr_ram2_3 <= 11'd1024;
end
ram2_16x2048 ram2_16x2048_inst3 (
.clock ( clk ),
.data ( ram1_q ),
.rdaddress (rdaddr_ram2_3 ),
.rden ( rden_ram2_1 ),
.wraddress ( wraddr_ram2_1 ),
.wren ( wren_ram2_1 ),
.q ( q3 )
);
endmodule
编写仿真代码
`timescale 1ns/1ps
module fifo_ram_tb();
reg clk ;
reg rst_n ;
reg wrreq ;
reg [15:0] data ;
initial
begin
clk <= 1'b0;
rst_n <= 1'b0;
wrreq <= 1'b0;
data <= 16'd0;
#123
rst_n <= 1'b1;
end
always #10 clk = ~clk;
//wrreq
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
wrreq <= 1'b0;
else if(fifo_ram_inst.empty)
wrreq <= 1'b1;
else if(fifo_ram_inst.usedw == 11'd2047)
wrreq <= 1'b0;
else
wrreq <= wrreq;
end
//data
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
data <= 16'd0;
else if(data == 11'd2047)
data <= 16'd0;
else if(wrreq)
data <= data + 16'd1;
else
data <= 16'd0;
end
fifo_ram fifo_ram_inst(
.clk (clk ) ,
.rst_n (rst_n) ,
.wrreq (wrreq) ,
.data (data ) ,
.q1 () ,
.q2 () ,
.addr () ,
.q3 ()
);
endmodule
仿真验证

可以看出fifo写数据没有问题。

fifo读数据没有问题,输出的数据延迟了一拍。

将fifo的读请求信号延迟一拍,就可以当作单端口ram的写使能信号,可以看出单端口ram写操作没有问题。

采集单端口ram的写使能的下降沿,此时单端口ram写使能为低,只需要修改地址就可以按顺序读出数据,读出的数据与地址延迟了两拍。

将单端口ram的写使能的下降沿标志信号,进行打两拍,当检测其有效时,拉高双端口ram的写使能信号(当写地址到最大值2047时,写使能拉低,其余时刻保持),这样就可以与单端口ram的输出数据进行对齐,且写地址信号递增,也与之对齐。可以看出双端口ram写操作没有问题。

采集双端口ram的写使能信号的下降沿,作为双端口ram的读使能信号拉高的标志。当地址到达一定值时,拉低读使能信号即可。后面三个功能的读使能信号均保持了2048个时钟周期,所以后面三个功能的读使能信号,可以使用统一的读使能信号,只需要修改读地址信号即可。

可以看出倒着读的功能没有问题,找最大值及其地址的逻辑功能没有问题,从中间读也没有问题。

从中间读也没有问题。

三个功能的仿真验证通过。


