1.当采集数据的时候由慢时钟域到快时钟域,肯定需要一个buffer做缓冲,这样才才能完成时钟域的转换,一般这种情况下都选择FIFO来做缓冲。
2.当读写FIFO的时钟不同那么称为异步FIFO。
3.FIFO就是一个“环形存储器”读操作会把读指针指向下一个读数据,写操作会把写指针指向下一个写数据地址。当读指针追上写指针时称为读空,当写指针追上读地址时称作写满。
假设存储器的深度为8,那么在表示数据地址的时候三位2进制数就可以了,但是为了区分写满和读空则再增加1位二进制的标志位,放在最前面。
写数据的地址应该在写的时钟域里面产生的,读数据的地址在读的时钟域里面产生,如果切换时钟域将这两个信号简单的拿过来进行比较的话很容易出现亚稳态,这个时候为了避免亚稳态就通过编码地址来进行比较
格雷码:将二进制码从左位第二位起,依次将每一位与左边一位异或,作为对应格雷码在该位的值,最左边一位不变。另一种方法为先将整个数据向右移动一位,然后与原数据按位异或,得到的数据就是我们所要的格雷码。
将格雷码转换为二进制码方法一样。
格雷码的相邻两个数据之间只有一位数据发生了改变,在传输过程中就相当与只有一个数据发生了改变,就等同于打两拍消除亚稳态的作用。
8个深度的存储地址二进制编码 格雷码
0= 4'b0_000 //0_000
1= 4'b0_001 //0_001
2= 4'b0_010 //0_011
3= 4'b0_011 //0_010
4= 4'b0_100 //0_110
5= 4'b0_101 //0_111
6= 4'b0_110 //0_101
7= 4'b0_111 //0_100
0= 4'b1_000 //1_100
1= 4'b1_001 //1_101
2= 4'b1_010 //1_111
3= 4'b1_011 //1_110
4= 4'b1_100 //1_010
5= 4'b1_101 //1_011
6= 4'b1_110 //1_001
7= 4'b1_111 //1_000
当写满的时候其实就是写比读快了一圈,其实在写第二圈的时候地址的标志位就变为1,所以区分写满的时候可以这样设置,以十进制这么写是非常合理的;**但是转换成格雷码之后则需要前两位都取反**
assign w_full=(r_addr[2:0] == w_addr[2:0]&&r_addr[3] == (~w_addr[3]))
而读空的话其实就是所有的地址都一样
assign r_empty=(r_addr[3:0] == w_addr[3:0]);
这个图里面的waddr指的是二进制码的地址,而wptr是经过格雷码转换之后的地址
rptr就是经过格雷码转换之后的读时钟域的地址,将这个地址再输出到写控制端口,经过延时两拍之后进行比较。
关于延时两拍之后再进行比较会不会出现已经读空或写满但是漏报的情况?
这个是不会的,对于读指针来说的话最差的情况不就是写满,写满的话那么我的写指针在两个时钟信号后是不会变化的
如果是读空的话同样的道理,我的读指针是不会发生变换的,那么你传过来的写指针其实还是和我两个时钟之前的读指针进行比较
格雷码计数器:产生格雷码地址的
转换格雷码的时候可以看到使用的是加法器后面的地址而不是输出的地址,这是因为在转换格雷码的后面也有一个寄存器,这样的话就可以是得输出到外面的格雷码的地址核二进制的地址保持一致
先调用一下RAM的IP核
这里的RAM是一个双口的RAM,因为我们设置的FIFO是异步FIFO,所以需要双口的RAM,写与读的时钟不一致。
M9K就是我们FPGA内部的memeory的资源,这个资源已经存在于我们的FPGA内部了,你用不用他都在那了。
Byte enable端口:比如一个16位的数据,如果这个端口的的信号是2’b01,就意味着低八位数据写进去了,高八位数据没有写进去。
在读数据端口不加寄存器,这样就只是延时一拍,给地址就出数据
这是我们的仿真库
左下角会提示我们使用了多少资源
创建完成之后就要开始例化了,重新创建一个文件把这个ip核给例化一下
module fifomem(
input wire w_clk,
input wire r_clk,
input wire w_en, //来自于FIFO的写控制模块
//RAM不需要读使能就可以,给一个地址他就出数据
input wire w_full, //来自FIFO的写控制模块
input wire [7:0] w_data, //来自于外部数据源
input wire [8:0] w_addr, //来自于我们的FIFO写控制模块
input wire r_empty,
input wire [8:0] r_addr, //来自于FIFO的读控制模块
output wire [7:0] r_data //读数据从内部ram中读取
);
wire ram_w_en;
assign ram_w_en = w_en&(~w_full);//这个控制信号都是根据FIFO的逻辑框图来看着写的
dp_ram_512_8 dp_ram_512_8_inst (
.wraddress ( w_addr[7:0] ),
.wren ( ram_w_en ),
.wrclock ( w_clk ),
.data ( w_data ),
.rdclock ( r_clk ),
.rdaddress ( r_addr[7:0] ),
.q ( r_data )
);
endmodule
//这个是用来描述写的控制信号
module w_ctrl(
input wire w_clk,//写时钟
input wire rst_n, //复位
input wire w_en, //写使能
input wire [8:0] r_gaddr,//读时钟域过来的格雷码读地址指针
output reg w_full, //写满标志
output wire [8:0] w_addr, //假设这次是256深度的FIFO
output wire [8:0] w_gaddr //写FIFO地址格雷码编码
);
reg [8:0] addr; //从格雷码计算器的图可以看出来最终输出的都是经过寄存器的数据
reg [8:0] gaddr; //从格雷码计算器的图可以看出来最终输出的都是经过寄存器的数据
wire [8:0] addr_wire;
wire [8:0] gaddr_wire;
reg [8:0] r_gaddr_d1,r_gaddr_d2;
//这是将输入的格雷码打两拍,避免亚稳态
always @(posedge w_clk or negedge rst_n)
if(rst_n == 1'b0)
{r_gaddr_d2,r_gaddr_d1} <= 18'd0;
else
{r_gaddr_d2,r_gaddr_d1} <= {r_gaddr_d1,r_gaddr};
//产生写RAM的地址指针 二进制
assign addr_wire = addr + ((~w_full)&w_en);
always @(posedge w_clk or negedge rst_n)
if(rst_n == 1'b0)
addr <= 9'd0;
else
addr <= addr_wire;
assign w_addr = addr;
//转换格雷码地址
assign gaddr_wire = (addr_wire>>1)^addr_wire;//^这个符号的意思就是异或
always @(posedge w_clk or negedge rst_n)
if(rst_n == 1'b0)
gaddr <= 9'd0;
else
gaddr <= gaddr_wire;
assign w_gaddr = gaddr;
//产生写满标志
always @(posedge w_clk or negedge rst_n)
if(rst_n == 1'b0)
w_full <= 1'b0;
//else if({~gaddr_wire[8],gaddr_wire[7:0]} == r_gaddr_d2) //输入比较的话应该拿输入与wire类型的数据进行比较,二进制比较这样就可以
else if({~gaddr_wire[8:7],gaddr_wire[6:0]} == r_gaddr_d2)
//格雷码比较是需要把高位和次高位都取反
w_full<=1'b1;
else
w_full<=1'b0;
endmodule
module r_ctrl(
input wire r_clk, //读时钟
input wire rst_n,
input wire r_en, //读使能
input wire [8:0] w_gaddr,//写时钟域的写地址指针
output reg r_empty,//读空标志
output wire [8:0] r_addr,//读时钟域的地址
output wire [8:0] r_gaddr //读格雷码的地址
);
reg [8:0] addr;
reg [8:0] gaddr;
wire [8:0] addr_wire;
wire [8:0] gaddr_wire;
reg [8:0] w_gaddr_d1,w_gaddr_d2;
always @(posedge r_clk or negedge rst_n)
if(rst_n == 1'b0)
{w_gaddr_d2,w_gaddr_d1} <= 18'd0;
else
{w_gaddr_d2,w_gaddr_d1} <= {w_gaddr_d1,w_gaddr};
//读二进制的地址
assign r_addr = addr;
assign addr_wire = addr + ((~r_empty)&(r_en));
always @(posedge r_clk or negedge rst_n)
if(rst_n == 1'b0)
addr <= 9'd0;
else
addr <= addr_wire;
//读格雷码的地址
assign r_gaddr = gaddr;
assign gaddr_wire = (addr_wire>>1)^addr_wire;
always @(posedge r_clk or negedge rst_n)
if(rst_n == 1'b0)
gaddr <= 9'd0;
else
gaddr <= gaddr_wire;
//读空标志的产生
always @(posedge r_clk or negedge rst_n)
if(rst_n == 1'b0)
r_empty <= 1'b0;
else if(gaddr_wire == w_gaddr_d2)
r_empty <= 1'b1;
else
r_empty <=1'b0;
endmodule
module ex_fifo(
input wire w_clk,
input wire r_clk,
input wire rst_n,
input wire w_en,
input wire [7:0] w_data,
output wire w_full,
input wire r_en,
output wire [7:0] r_data,
output wire r_empty
);
wire [8:0] r_gaddr;
wire [8:0] w_addr;
wire [8:0] w_gaddr;
wire [8:0] r_addr;
w_ctrl w_ctrl_inst(
.w_clk(w_clk),//写时钟
.rst_n(rst_n), //复位
.w_en(w_en), //写使能
.r_gaddr(r_gaddr),//读时钟域过来的格雷码读地址指针
.w_full(w_full), //写满标志
.w_addr(w_addr), //假设这次是256深度的FIFO
.w_gaddr(w_gaddr) //写FIFO地址格雷码编码
);
fifomem fifomem_inst(
.w_clk(w_clk),
.r_clk(r_clk),
.w_en(w_en), //来自于FIFO的写控制模块
.w_full(w_full), //来自FIFO的写控制模块
.w_data(w_data), //来自于外部数据源
.w_addr(w_addr), //来自于我们的FIFO写控制模块
.r_empty(r_empty),
.r_addr(r_addr), //来自于FIFO的读控制模块
.r_data(r_data) //读数据从内部ram中读取
);
r_ctrl r_ctrl_inst(
.r_clk(r_clk), //读时钟
.rst_n(rst_n),
.r_en(r_en), //读使能
.w_gaddr(w_gaddr),//写时钟域的写地址指针
.r_empty(r_empty),//读空标志
.r_addr(r_addr),//读时钟域的地址
.r_gaddr(r_gaddr) //读格雷码的地址
);
endmodule
可以看到顶层文件其实就是例化前面已经写好的三个模块
`timescale 1ns/1ns
module tb_ex_fifo;
reg r_clk,w_clk,rst_n;
reg w_en;
reg [7:0] w_data;
reg r_en;
wire [7:0] r_data;
wire w_full;
wire r_empty;
parameter CLK_P = 20;
//写的初始化模块
initial begin
r_clk=0;
w_clk=0;
rst_n=0;
#200
rst_n=1;
end
//读的初始化模块
initial begin
w_en = 0;
w_data = 0;
#300
write_data();
end
initial begin
r_en = 0;
@(posedge w_full)
#40;
read_data();
end
always # (CLK_P/2) r_clk = ~r_clk;
always # (CLK_P/2) w_clk = ~w_clk;
ex_fifo ex_fifo_inst(
.w_clk(w_clk),
.r_clk(r_clk),
.rst_n(rst_n),
.w_en(w_en),
.w_data(w_data),
.w_full(w_full),
.r_en(r_en),
.r_data(r_data),
.r_empty(r_empty)
);
task write_data();
integer i;
begin
for(i=0;i<258;i=i+1)
begin
@(posedge w_clk);
w_en = 1'b1;
w_data = i;
end
@(posedge w_clk);
w_en=1'b0;
w_data = 0;
end
endtask
task read_data();
integer i;
begin
for(i=0;i<256;i=i+1)
begin
@(posedge r_clk);
r_en = 1'b1;
end
@(posedge r_clk);
r_en = 1'b0;
w_data = 0;
end
endtask
endmodule
在这个例子里面的读写时钟是一样的,这是便于测试,其实现在就完全可以将其设置为不同频率的时钟