👉异步FIFO的分析请看:异步FIFO
注:代码附于文末
注:因为CSDN不支持Verilog高亮,先用截图的代码便于阅读。
1.代码截图
2.仿真结果
仿真结果如下:
- 空满标志生成正常
- 读写地址指针计数器正常
- 数据存取正常
- 格雷码逻辑正常
- 指针跨时钟域同步正常
3.代码
代码如下:
module asy_fifo#(
parameter WIDTH=8,//FIFO的位宽
parameter DEEPTH=32,//FIFO的深度
parameter POINTER_WIDTH=5,//读写地址指针,指针向高位扩展一位
parameter ADDR_WIDTH=4//读写地址的位宽
)
(empty,full,r_data,w_clk,w_rstn,w_en,w_data,r_clk,r_rstn,r_en);
output empty,full; //FIFO生成的空满信号
output reg [WIDTH-1:0] r_data;//从FIFO中读出的数据
input w_clk,w_rstn;//读时钟域时钟、读时钟域复位信号
input w_en;//读操作使能信号
input [WIDTH-1:0] w_data;//向FIFO中写入的数据
input r_clk,r_rstn;//写时钟域时钟、写时钟域复位信号
input r_en;//写操作使能信号
//定义:FIFO的buffer
reg [WIDTH-1:0] fifo_mem [0:DEEPTH-1];
//定义:FIFO的读写指针
reg [POINTER_WIDTH-1:0] w_pointer,r_pointer;
//定义:FIFO内数据对应的存取地址
wire [ADDR_WIDTH-1:0]w_addr,r_addr;
//定义:转换为格雷码的指针
wire [POINTER_WIDTH-1:0] w_gray_pointer;
wire [POINTER_WIDTH-1:0] r_gray_pointer;
//定义:指针经过两级触发器同步时的指针
reg [POINTER_WIDTH-1:0] w_pointer_d1,w_pointer_d2;
reg [POINTER_WIDTH-1:0] r_pointer_d1,r_pointer_d2;
//===========写时钟域========
//写时钟域内,写数据进FIFO,并将写指针加1
assign w_addr = w_pointer[POINTER_WIDTH-2:0];
always@(posedge w_clk or negedge w_rstn)begin
if( w_en & (!full) ) begin
fifo_mem[w_addr] <= w_data;
end
end
//写指针计数器
always@(posedge w_clk or negedge w_rstn) begin
if(!w_rstn) begin
w_pointer <= 0;
end
else if(w_en & (!full)) begin
w_pointer <= w_pointer + 1;
end
else begin
w_pointer <= w_pointer;
end
end
//==========================
//===========读时钟域========
//读时钟域内,把FIFO内的数据取出,并将读指针加1
assign r_addr = r_pointer[POINTER_WIDTH-2:0];
always@(posedge r_clk or negedge r_rstn)begin
if(!r_rstn) begin
r_data <= 'h0;
end
else if( r_en & (!empty) ) begin
r_data <= fifo_mem[r_addr] ;
end
end
//读指针计数器
always@(posedge r_clk or negedge r_rstn)begin
if(!r_rstn) begin
r_pointer <= 0;
end
else if( r_en & (!empty) ) begin
r_pointer <= r_pointer + 1 ;
end
else begin
r_pointer <= r_pointer;
end
end
//==========================
//===========指针同步======
//将格雷码写指针同步到读时钟域
//1.将写指针转换为格雷码的形式
assign w_gray_pointer = w_pointer^(w_pointer >> 1);
//2.再将转换后的指针经过两级触发器同步
always@(posedge r_clk or negedge r_rstn) begin
if(!r_rstn)begin
{w_pointer_d1,w_pointer_d2} <= 0;
end
else begin
{w_pointer_d2,w_pointer_d1} <= {w_pointer_d1,w_gray_pointer};
end
end
//将格雷码读指针经过两级触发器同步到写时钟域
//1.将读指针转换为格雷码的形式
assign r_gray_pointer = r_pointer^(r_pointer >> 1);
//2.再将转换后的指针经过两级触发器同步
always@(posedge w_clk or negedge w_rstn) begin
if(!w_rstn) begin
{r_pointer_d1,r_pointer_d2} <= 0;
end
else begin
{r_pointer_d2,r_pointer_d1} <= {r_pointer_d1,r_gray_pointer};
end
end
//==========================
//判断空满
assign full = (
(r_pointer_d2[POINTER_WIDTH-1] != w_gray_pointer[POINTER_WIDTH-1]) && //最高位不同
(r_pointer_d2[POINTER_WIDTH-2] != w_gray_pointer[POINTER_WIDTH-2]) && //次高位不同
(r_pointer_d2[POINTER_WIDTH-3:0] == w_gray_pointer[POINTER_WIDTH-3:0])//其他低位相同
);
assign empty = (w_pointer_d2 == r_gray_pointer);
endmodule
//==========TESTBENCH=========
`timescale 1ns/10ps
module TB;
parameter WIDTH=8;
wire empty,full;
wire [WIDTH-1:0] r_data;
reg w_clk,w_rstn;
reg w_en;
reg [WIDTH-1:0] w_data;
reg r_clk,r_rstn;
reg r_en;
asy_fifo dut(.empty(empty),
.full(full),
.r_data(r_data),
.w_clk(w_clk),
.w_rstn(w_rstn),
.w_en(w_en),
.w_data(w_data),
.r_clk(r_clk),
.r_rstn(r_rstn),
.r_en(r_en)
);
//产生写时钟,100MHz
initial begin
w_clk <= 0;
forever begin
#5 w_clk <= !w_clk;
end
end
initial begin
#10 w_rstn <= 0;
repeat(10) @(posedge w_clk);
#1ns;
w_rstn <= 1;
end
//产生读时钟,50MHz
initial begin
r_clk <= 0;
forever begin
#5 r_clk <= !r_clk;
end
end
initial begin
#10 r_rstn <= 0;
repeat(10) @(posedge w_clk);
r_rstn <= 1;
end
initial begin
#10 w_en = 1'b1;
w_data = #(0.01) $random;
repeat(5) begin
@(posedge w_clk);
w_data = #(0.01) $random;
end
@(posedge w_clk);
w_en = 1'b0;
w_data = #(0.01) $random;
#20 r_en = #(0.01) 1'b1;
repeat(5) begin
@(posedge r_clk);
end
@(posedge r_clk);
r_en = 1'b0;
#20 w_en = 1'b1;
repeat(20) begin
@(posedge w_clk);
w_data = #(0.01) $random;
end
@(posedge w_clk);
w_en = 1'b0;
w_data = #(0.01) $random;
r_en = #(0.01) 1'b1;
repeat(20) begin
@(posedge r_clk);
end
#50 $finish();
end
endmodule