FPGA之手撕fifo(含设计代码和仿真)
本文回答以下几个问题:
1:fifo的读空和写满信号如何给出
2:fifo的写控制模块设计
3:fifo的读控制模块设计
4:双口RAM使用
5:顶层文件
6:仿真文件编写
7:modelsim的RTL仿真
1:上一篇文章(FPGA之FIFO IP核详细教程)已经简单说了一下读写指针变换准则:概括一句话就是读地址和写地址不能相同,读地址不能追上写地址,写地址不能追上读地址(多一个周期),你可以把他们想成在一个圆圈上转动。那读写地址相同的情况如何区分呢?到底是读地址追上写地址(读空),还是写地址经过一个存储深度周期后追上读地址(写满)呢?
这一段回答上一段的两个问题。对于读写地址来说地址的每一位对应其在地址数的权值,如4位地址MSB(高位)到LSB(低位)都有自己的数值分别是8,4,2,1,换句话说就是我有专门的用处了,别让我干其他事情了,心有余而力不足。所以无法用真实对应于RAM上地址的其中一位或者多位来区分读空和写满信号。这时只有在高位增加一位来区分。如8位地址在首位增加一位,看起来是16个数,其实真实地址(下面说的读地址写地址都是4位,真实地址是3位)还是只有8。先说读空这种情况最好理解,就是读地址追上了些地址,也就是读写地址的每一位都一样。图1右侧是写满时候的指针情况,可以看到读地址是0001,写地址是1001,首位不同而后三位相同。
读写地址要对比,要涉及到跨时钟域了,但是地址仅仅是8421BCD编码在跨时钟域时候有缺陷:相邻地址间存在多bit变化情况如0111——>1000就发生4bit变化,多比特跨时钟域传输很容易产生亚稳态。这时你可能会想把信号在另一个时钟域打两拍就可以大大降低亚稳态发生概率了。没错,确实是可以将信号打两排,但是地址位数较多时候会消耗很多芯片内部的资源。
图1 4位8421BCD码
而格雷码相邻地址只有1bit变化,所以讲地址在跨时钟域前前转换成格雷码,然后再打两拍即可。格雷码的产生,8421编码的二进制右移一位再异或初始的二进制数如0010转换成格雷码的过程是0010右移一位变成0001,0001异或0010就是0011。格雷码编码中读空和上面一样只要读写地址一样就是读空。而写满稍有变化,还以地址2为例,这时候看到读地址指向0001,写地址指向1101,对比时候将读或者写的地址高两位取反如果相同则说明已经写满。
图2 4位二进制格雷码编码
在说第二个问题前,先看一下整个fifo的“框架”,(示意的可能不规范)但心里至少有一个框架。
图3 fifo框架
2:写控制模块
module wr_ctrl(
input wire wrclk,
input wire rst_n,
input wire [8:0] gray_rd_addr,
input wire wr_req,//写请求
output reg wr_full,
output wire [8:0] gray_wr_addr,
output wire [8:0] wr_addr
);
wire wr_en;
reg [8:0] gray_rd_addr1;
reg [8:0] gray_rd_addr2;
reg [8:0] wraddr;
assign wr_addr = wraddr;
assign wr_en = (~wr_full) & wr_req;//写使能信号产生
assign gray_wr_addr = (wr_addr >> 1) ^ wr_addr;
always @(posedge wrclk or negedge rst_n)
if(rst_n == 1'b0)
wraddr <= 9'd0;
else if((~wr_full) & wr_en)
wraddr <= wraddr +1'b1;
// else wraddr <= 9'd0;
//把来自读时钟的格雷码地址打两排
always @(posedge wrclk or negedge rst_n)
if(rst_n == 1'b0)
{gray_rd_addr2[8:0],gray_rd_addr1[8:0]} = 18'b0;
else
{gray_rd_addr2[8:0],gray_rd_addr1[8:0]} = {gray_rd_addr1[8:0],gray_rd_addr} ;
//写满信号产生
always @(posedge wrclk or negedge rst_n)
if(rst_n == 1'b0)
wr_full <= 1'b0;
else if({~gray_rd_addr2[8:7],gray_rd_addr2[6:0]} == (gray_wr_addr[8:0]))
wr_full <= 1'b1;
else
wr_full <= 1'b0;
endmodule
3:读控制模块
module rd_ctrl(
input wire rdclk,
input wire rst_n,
input wire [8:0] wr_gray_addr,
input wire rd_en,
output reg rd_empty,
output wire [8:0] rd_gray_addr,
output wire [8:0] rd_addr
);
reg [8:0] w_gaddr1;
reg [8:0] w_gaddr2;
reg [8:0] rdaddr;
assign rd_gray_addr = (rd_addr >> 1) ^ rd_addr;//读地址格雷码
assign rd_addr = rdaddr;
always @(posedge rdclk or negedge rst_n)
if(rst_n == 1'b0)
rdaddr <= 9'd0;
else if((~rd_empty) & rd_en)
rdaddr <= rdaddr + 1'b1;
//else rdaddr <= 9'd0;
//输入的写地址打两排
always @(posedge rdclk or negedge rst_n)
if(rst_n == 1'b0)
{w_gaddr2,w_gaddr1} <= 18'b0;
else
{w_gaddr2,w_gaddr1} <= {w_gaddr1,wr_gray_addr};
//读空信号产生
always @(posedge rdclk or negedge rst_n)
if(rst_n == 1'b0)
rd_empty <= 1'b0;
else if(rd_gray_addr == w_gaddr2)
rd_empty <= 1'b1;
else rd_empty <= 1'b0;
endmodule
4:双口RAM(这里用到RAM IP核)
module dp_ram(
input wire rdclk,
input wire wrclk,
input wire rst_n,
input wire [7:0] w_data,
output wire rden,
output wire wren,
input wire [8:0] wr_addr,
input wire [8:0] rd_addr,
output wire wr_full,
output wire rd_empty,
output wire [7:0] rd_data
);
wire ram_wren;
wire ram_rden;
assign ram_wren = (~wr_full) & wren;
assign ram_rden = (~rd_empty) & rden;
dpram_8x256 dpram_8x256_inst (
.data ( w_data ),
.rdaddress ( rd_addr[7:0] ),
.rdclock ( rdclk ),
.rden ( ram_rden ),
.wraddress ( wr_addr[7:0] ),
.wrclock ( wrclk ),
.wren ( ram_wren ),
.q ( rd_data )
);
endmodule
5:顶层模块
module top_fifo(
input wire wrclk,
input wire rdclk,
input wire rst_n,
input wire wrfull,
input wire rden,
input wire wren,
input wire [7:0] data_in,
output wire [7:0] data_o
);
//wire wrfull;
wire rdempty;
wire [8:0] rd_addr;
wire [8:0] wr_addr;
wire [8:0] wr_gaddr;
wire [8:0] rd_gaddr;
dp_ram dp_ram_inst(
.rdclk (rdclk),
.wrclk (wrclk),
.rst_n (rst_n),
.w_data (data_in),
.rden (rden),
.wren (wren),
.wr_addr (wr_addr),
.rd_addr (rd_addr),
.wr_full (wrfull),
.rd_empty (rdempty),
.rd_data (data_o)
);
wr_ctrl wr_ctrl_inst(
.wrclk (wrclk),
.rst_n (rst_n),
.gray_rd_addr (rd_gaddr),
.wr_req (wren),
.wr_full (wrfull),
.gray_wr_addr (wr_gaddr),
.wr_addr (wr_addr)
);
rd_ctrl rd_ctrl_inst(
.rdclk (rdclk),
.rst_n (rst_n),
.wr_gray_addr (wr_gaddr),
.rd_en (rden),
.rd_empty (rdempty),
.rd_gray_addr (rd_gaddr),
.rd_addr (rd_addr)
);
endmodule
6:仿真文件
`timescale 1ns/1ns
module tb_fifo;
reg r_clk;
reg w_clk;
reg rst_n;
reg wren;
reg [7:0] w_data;
reg rden;
wire w_full;
wire [7:0] r_data;
initial begin
r_clk <= 0;
w_clk <= 0;
rst_n <= 0;
#100
rst_n <= 1;
end
initial begin
w_data <= 0;
wren <=0;
#200
wr_data_fun();
end
initial begin
rden <= 0;
//@(posedge w_full)
#500
rd_data_fun();
end
always #7 r_clk =~r_clk;
always #10 w_clk =~w_clk;
top_fifo top_fifo_inst(
.wrclk (w_clk),
.rdclk (r_clk),
.rst_n (rst_n),
.wrfull (w_full),
.rden (rden),
.wren (wren),
.data_in (w_data),
.data_o (r_data)
);
task wr_data_fun();
integer i;
begin
for (i=0;i<256;i=i+1)
begin
@(posedge w_clk);
wren=1'b1;
w_data=i;
end
@(posedge w_clk);
wren = 1'b0;
w_data =0;
end
endtask
task rd_data_fun();
integer i;
begin
for (i=0;i<256;i=i+1)
begin
@(posedge r_clk);
rden=1'b1;
end
@(posedge r_clk);
rden = 1'b0;
end
endtask
endmodule
7:仿真
图4,同步fifo起始信号变化,读空信号在地址
图4
图5异步fifo信号变化
图5
图6边读边写,读时钟频率大于写时钟的
图6
写完一个周期
既然看到这里就动手试一试吧。如果发现瑕疵和错误之处还请指出,作者虚心学习,感激不尽。需要整个设计评论里留下邮箱。