目录
参考资料:
《FPGA自学笔记——设计与验证》;《硬件架构的艺术》;《Verilog HDL数字集成电路高级程序设计》等
链接:https://pan.baidu.com/s/1dKOXQMg3M3vHmVIx9KdMxg
提取码:kqy9
--来自百度网盘超级会员V6的分享
一、FIFO的定义和应用场景
FIFO(First in First Out)是一种先进先出的数据缓冲器,通常用于接口电路的数据缓存。与普通存储器的区别是没有外部读写地址线,可以使用两个时钟分别进行读和写操作。
FIFO只能顺序写入数据和顺序读出数据,其数据地址由内部读、写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
二、FIFO的结构
FIFO从读/写端口和同步时钟关系来分,有两类结构:单时钟FIFO(Single Clock FIFO,SCFIFO)和双时钟(Dual Clock FIFO,DCFIFO),其中DCFIFO又可以分为普通双时钟(DCFIFO)和混合宽度双时钟FIFO(DCFIFO_MIXED_WIDTHS)。分别如下图所示:
从图中可以看到,单时钟FIFO具有一个独立的时钟端口 Clock,因此所有输入信号的读取都是在Clock的上升沿进行的,所有输出信号的变化也是在Clock信号的上升沿的控制下进行的,即单时钟 FIFO 的所有输入输出信号都是同步于 Clock 信号的。
而在双时钟 FIFO 结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wrclk 的,所有与读相关的信号都是同步于读时钟 rdclk 的。在双时钟 FIFO 的符号图中,位于上部分的为与写相关的所有信号,位于中间部分的为与读相关的所有信号,位于下部的为异步清零信号。
三、FIFO的应用场景
3.1 单时钟(同步)FIFO
3.2 双时钟(异步)FIFO
四、FIFO的结构
一个FIFO的组成一般包括两个部分:地址控制部分和存储数据的DPRAM(双端口RAM)部分,如下图所示:
地址控制部分可以根据读写指令生成RAM地址。RAM用于存储堆栈数据,并根据控制部分生成的地址信号进行数据的存储和读取操作。
五、FIFO常见参数
六、实现 FIFO 的方法
6.1 IP核的使用——FIFO
6.1.1 单时钟 FIFO 实现与测试
1、创建IP核
2、将生成的IP核文件设为顶层文件
3、自动生成了my_fifo.v文件,打开查看一下端口,方便写激励文件
// megafunction wizard: %FIFO%
// GENERATION: STANDARD
// VERSION: WM1.0
// MODULE: scfifo
// ============================================================
// File Name: myfifo.v
// Megafunction Name(s):
// scfifo
//
// Simulation Library Files(s):
// altera_mf
// ============================================================
// ************************************************************
// THIS IS A WIZARD-GENERATED FILE. DO NOT EDIT THIS FILE!
//
// 13.1.0 Build 162 10/23/2013 SJ Full Version
// ************************************************************
// synopsys translate_off
`timescale 1 ps / 1 ps
// synopsys translate_on
module myfifo (
clock,
data,
rdreq,
sclr,
wrreq,
almost_empty,
almost_full,
empty,
full,
q,
usedw);
input clock;
input [7:0] data;
input rdreq;
input sclr;
input wrreq;
output almost_empty;
output almost_full;
output empty;
output full;
output [7:0] q;
output [3:0] usedw;
wire [3:0] sub_wire0;
wire sub_wire1;
wire sub_wire2;
wire [7:0] sub_wire3;
wire sub_wire4;
wire sub_wire5;
wire [3:0] usedw = sub_wire0[3:0];
wire empty = sub_wire1;
wire full = sub_wire2;
wire [7:0] q = sub_wire3[7:0];
wire almost_empty = sub_wire4;
wire almost_full = sub_wire5;
scfifo scfifo_component (
.clock (clock),
.sclr (sclr),
.wrreq (wrreq),
.data (data),
.rdreq (rdreq),
.usedw (sub_wire0),
.empty (sub_wire1),
.full (sub_wire2),
.q (sub_wire3),
.almost_empty (sub_wire4),
.almost_full (sub_wire5),
.aclr ());
defparam
scfifo_component.add_ram_output_register = "OFF",
scfifo_component.almost_empty_value = 2,
scfifo_component.almost_full_value = 14,
scfifo_component.intended_device_family = "Cyclone IV GX",
scfifo_component.lpm_numwords = 16,
scfifo_component.lpm_showahead = "OFF",
scfifo_component.lpm_type = "scfifo",
scfifo_component.lpm_width = 8,
scfifo_component.lpm_widthu = 4,
scfifo_component.overflow_checking = "ON",
scfifo_component.underflow_checking = "ON",
scfifo_component.use_eab = "ON";
endmodule
4、编写和添加仿真激励文件fifo_tb.v
`timescale 1ns/1ps
`define clk_period 20
module fifo_tb;
//source define
reg Clk;
reg [7:0] data;
reg rdreq;
reg sclr;
reg wrreq;
//probe define
wire almost_empty;
wire almost_full;
wire empty;
wire full;
wire [7:0] q;
wire [3:0] usedw;
//instant user module
myfifo my_fifo(
.clock(Clk),
.data(data),
.rdreq(rdreq),
.sclr(sclr),
.wrreq(wrreq),
.almost_empty(almost_empty),
.almost_full(almost_full),
.empty(empty),
.full(full),
.q(q),
.usedw(usedw)
);
//generater clock
initial Clk = 1;
always #(`clk_period/2)Clk = ~Clk;
integer i;
initial begin
wrreq = 0;
data = 0;
rdreq = 0;
sclr = 0;
#(`clk_period*20 + 1);
for (i=0;i <= 15 ;i = i + 1)begin
wrreq = 1;
data = i;
#`clk_period;
end
wrreq = 0;
#(`clk_period*20);
for (i=0;i <= 15 ;i = i + 1)begin
rdreq = 1;
#`clk_period;
end
rdreq = 0;
$stop;
end
endmodule
5、RTL simulation打开Modelsim,查看波形
最后打开RTL Viewer,查看模块组成:主要是调用了FIFO的IP核,使用比较简单,引脚可以根据需要进行增删。
6.1.2 混合宽度异步(双时钟) FIFO 实现与测试
要求:输入的数据是16位,输出数据为8位,且输入输出由两个时钟控制
1、新建一个fifo,注意读写时钟以及 输入输出位宽的差别
2、查看生成的IP核文件,并设为顶层文件
// synopsys translate_off
`timescale 1 ps / 1 ps
// synopsys translate_on
module myfifo (
data,
rdclk,
rdreq,
wrclk,
wrreq,
q,
rdempty,
rdusedw,
wrfull,
wrusedw);
input [15:0] data;
input rdclk;
input rdreq;
input wrclk;
input wrreq;
output [7:0] q;
output rdempty;
output [4:0] rdusedw;
output wrfull;
output [3:0] wrusedw;
wire sub_wire0;
wire [7:0] sub_wire1;
wire sub_wire2;
wire [3:0] sub_wire3;
wire [4:0] sub_wire4;
wire wrfull = sub_wire0;
wire [7:0] q = sub_wire1[7:0];
wire rdempty = sub_wire2;
wire [3:0] wrusedw = sub_wire3[3:0];
wire [4:0] rdusedw = sub_wire4[4:0];
dcfifo_mixed_widths dcfifo_mixed_widths_component (
.rdclk (rdclk),
.wrclk (wrclk),
.wrreq (wrreq),
.data (data),
.rdreq (rdreq),
.wrfull (sub_wire0),
.q (sub_wire1),
.rdempty (sub_wire2),
.wrusedw (sub_wire3),
.rdusedw (sub_wire4),
.aclr (1'b0),
.rdfull (),
.wrempty ());
defparam
dcfifo_mixed_widths_component.intended_device_family = "Cyclone IV GX",
dcfifo_mixed_widths_component.lpm_numwords = 16,
dcfifo_mixed_widths_component.lpm_showahead = "OFF",
dcfifo_mixed_widths_component.lpm_type = "dcfifo_mixed_widths",
dcfifo_mixed_widths_component.lpm_width = 16,
dcfifo_mixed_widths_component.lpm_widthu = 4,
dcfifo_mixed_widths_component.lpm_widthu_r = 5,
dcfifo_mixed_widths_component.lpm_width_r = 8,
dcfifo_mixed_widths_component.overflow_checking = "ON",
dcfifo_mixed_widths_component.rdsync_delaypipe = 4,
dcfifo_mixed_widths_component.underflow_checking = "ON",
dcfifo_mixed_widths_component.use_eab = "ON",
dcfifo_mixed_widths_component.wrsync_delaypipe = 4;
endmodule
3、编写testbench
`timescale 1ns/1ps
`define wrclk_period 20
`define rdclk_period 10
module mydcfifo_tb;
//source define
reg [15:0] data;
reg rdclk;
reg rdreq;
reg wrclk;
reg wrreq;
//probe define
wire [7:0] q;
wire rdempty;
wire [8:0] rdusedw;
wire wrfull;
wire [7:0] wrusedw;
//instant user module
myfifo my_dcfifo(
.data(data),
.rdclk(rdclk),
.rdreq(rdreq),
.wrclk(wrclk),
.wrreq(wrreq),
.q(q),
.rdempty(rdempty),
.rdusedw(rdusedw),
.wrfull(wrfull),
.wrusedw(wrusedw)
);
//generater clock
initial wrclk = 1;
always #(`wrclk_period/2)wrclk = ~wrclk;
initial rdclk = 1;
always #(`rdclk_period/2)rdclk = ~rdclk;
integer i;
initial begin
data = 0;
rdreq = 0;
wrreq = 0;
#(`wrclk_period*20 + 1);
for (i=0;i <= 255 ;i = i + 1)begin
wrreq = 1;
data = i + 1024;
#`wrclk_period;
end
wrreq = 0;
#(`rdclk_period*20);
for (i=0;i <= 511 ;i = i + 1)begin
rdreq = 1;
#`rdclk_period;
end
rdreq = 0;
#(`rdclk_period*20);
$stop;
end
endmodule
4、查看结果(用16进制表示)
5、查看RTL视图
调用了一个IP核,就能实现混合宽度的异步FIFO
6.2 纯编程实现同步FIFO
6.2.1 顶层模块
//一、顶层模块
module my_scfifo(
//输入信号源
input CLK,Rst,//时钟和异步复位
input Write_En,Read_En,//RAM/.的读写控制
input [7:0] Data_In,//输入数据
output wire [7:0] Data_Out,//数据输出
output wire [3:0] FCount,
output Full,Empty
);
//wire Full,Empty;//RAM的空、满状态
wire [3:0] Addr_In,Addr_Out;//中间变量
FIFO_control U1(//FIFO控制模块的例化
//U1与my_scfifo的连接
.clk(CLK),
.rst(Rst),
.write_en(Write_En),
.read_en(Read_En),
.RAM_Full(Full),
.RAM_Empty(Empty),
.Fcount(FCount),
.write_ptr(Addr_In),
.read_ptr(Addr_Out)
);
RAM_dual U2(//RAM存储模块的例化
//U2与my_scfifo的连接
.d(Data_In),
.q(Data_Out),
.write_en(Write_En),
.read_en(Read_En),
.write_clk(CLK),
.read_clk(CLK),
.addr_in(Addr_In),
.addr_out(Addr_Out)
);
endmodule
6.2.2 FIFO控制模块
//二、FIFO控制模块
module FIFO_control(clk,rst,write_en,read_en,
RAM_Full,RAM_Empty,write_ptr,read_ptr,Fcount
);
parameter RAM_WIDTH = 8;//数据位宽为8位
parameter RAM_DEPTH = 16;//数据深度为16,表示最多可存储16个8位数据
parameter RAM_PTR_WIDTH = 4;//16个数据对应4位地址线
input clk,rst;//输入信号源
input write_en,read_en;//数据写入、读出RAM
output reg [RAM_PTR_WIDTH - 1 : 0] write_ptr,read_ptr;//读写指针
output RAM_Full,RAM_Empty;//空满标志
output reg [RAM_PTR_WIDTH - 1 : 0] Fcount;//通过Fcount(已存数据深度)的值来判断空、满状态
//reg [RAM_WIDTH - 1:0] data_out;//8位数据输出,让其与Data_Out连接
//reg [RAM_WIDTH - 1:0] RAM [RAM_DEPTH - 1 : 0];//定义了16*8bit的存储器
//RAM状态信号:空、满状态
assign RAM_Full = (Fcount == RAM_DEPTH);//当Fcount已存数据深度的值等于RAM的深度,则满
assign RAM_Empty = (Fcount == 0);//当Fcount已存数据深度等于0,则无数据,表示空
always@(posedge clk, posedge rst)
begin
if(rst)//异步复位
begin
//data_out <= 0;
write_ptr <= 0;
read_ptr <= 0;
Fcount <= 0;
end
//①只写不读时:在写入控制有效,非满且不能读出
else if(write_en && (!RAM_Full) && (!read_en))
begin
write_ptr <= write_ptr + 1;//写指针+1,指向下一个空单元
Fcount <= Fcount + 1;//已存数据深度+1
end
//②只读不写时:读操作执行:在读出控制有效,非空且这时不能写入数据
else if(!write_en && (!RAM_Empty) && (read_en))
begin
read_ptr <= read_ptr + 1;//读指针+1,
Fcount <= Fcount - 1;//数据被读出,已存数据深度减少1个
end
//③空时又读又写:只有写操作有效
else if(write_en && RAM_Empty && read_en)
begin
write_ptr <= write_ptr + 1;
Fcount <= Fcount + 1;
end
//④满时又读又写:只有读操作有效
else if(write_en && RAM_Full && read_en)
begin
read_ptr <= read_ptr + 1;
Fcount <= Fcount - 1;
end
//⑤非空非满时又读又写:读写操作都有效,Fcount不变
else if(write_en && read_en && (!RAM_Empty) && (!RAM_Full))
begin
write_ptr <= write_ptr + 1;
read_ptr <= read_ptr + 1;
end
end
endmodule
6.2.3 双端口RAM模块
//三、双端口RAM
module RAM_dual(
input [7:0] d,//输入数据
input [3:0] addr_in,//写入数据的地址
input [3:0] addr_out,//读出数据的地址
input write_en,read_en,//读、写使能控制信号
input write_clk,read_clk,//读、写时钟信号
output reg [7:0] q//输出数据
);
reg[7:0] memory [15:0];//16*8bits寄存器
always@(posedge write_clk)//写入数据
begin
if(write_en) memory[addr_in] <= d;
end
always@(posedge read_clk)//读出数据
begin
if(read_en) q <= memory[addr_out];
else q <= 0;
end
endmodule
6.2.4 RTL视图
6.2.5 TestBench代码
`timescale 1ns/1ns
module my_scfifo_tb;
reg clk,rst;
reg write,read;
reg [7:0] data_in;
wire [7:0] data_out;
wire [3:0] fcount;
wire full,empty;
my_scfifo U1(
.CLK(clk),
.Rst(rst),
.Write_En(write),
.Read_En(read),
.Data_In(data_in),
.Data_Out(data_out),
.Empty(empty),
.Full(full),
.FCount(fcount)
);
initial clk = 1;
always #(5) clk = ~clk;
integer i;
initial
begin
rst = 1; #10;//复位
rst = 0; #10;//复位撤销
write = 1; read = 0; data_in=0; #10;//写入数据0
write = 0; read = 1; #10;//读出数据
read = 0; #10;//停止读出数据
for(i = 0; i<=14; i = i+1)//连续写入16个数据
begin
write = 1;
data_in = i;
#10;
end
write = 0;
#10;
for(i = 0; i<=14; i = i+1)//连续读出16个数据
begin
read = 1;
#10;
end
read = 0;
$stop;
end
endmodule
6.2.6 仿真结果
6.3 纯编程实现异步混合宽度FIFO
思路:在6.2的基础上,增加my_scfifo顶层模块中的读、写双时钟;输入数据8bit和输出数据4bit位宽进行差异化;
混合宽度如何实现,继续探索中...