1.参考
1.SPEC
同步FIFO实现了对write/read的控制,其接口解决了接口两端数据速率不匹配的问题
2.RTL代码
1.01 【Verilog实战】同步FIFO的设计(附源码RTL/TB)
//同步FIFO
module sync_fifo
#(
parameter DATA_WIDTH = 32, //FIFO储存32位数据
parameter DATA_DEPTH = 8 , //深度为8
parameter PTR_WIDTH = 3 //8的深度对应3位地址
//parameter PTR_WIDTH = $clog2(DATA_DEPTH)
)
(
input wire clk_i ,
input wire rst_n_i ,
//write interface
input wire wr_en_i , //写使能
input wire [DATA_WIDTH-1:0] wr_data_i,
//read interface
input wire rd_en_i , //读使能
output reg [DATA_WIDTH-1:0] rd_data_o, //wdata是input;rdata是output
//Flags_o
output reg full_o ,
output reg empty_o
);
reg [DATA_WIDTH-1:0] regs_array [DATA_DEPTH-1:0]; //32位寄存器,深度8
reg [PTR_WIDTH-1 :0] wr_ptr ; //写指针地址
reg [PTR_WIDTH-1 :0] rd_ptr ;
reg [PTR_WIDTH :0] elem_cnt ;
reg [PTR_WIDTH :0] elem_cnt_nxt ;
//Flags
wire full_comb ;
wire empty_comb ;
/*---------------------------------------------------\
--------------- write poiter addr ----------------
复位写指针归0,写使能同时fifo未写满 写指针+1
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
wr_ptr <= 3'b0;
end
else if (wr_en_i && !full_o) begin
wr_ptr <= wr_ptr + 3'b1;
end
end
/*---------------------------------------------------\
-------------- read poiter addr ------------------
复位写指针归0,读使能同时fifo未读空 读指针+1
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
rd_ptr <= 3'b0;
end
else if (rd_en_i && !empty_o) begin
rd_ptr <= rd_ptr + 3'b1;
end
end
/*---------------------------------------------------\
--------------- element counter ------------------
使用element counter(elem_cnt)记录FIFO RAM 中的数据个数:
▷ 等于0时,给出empty信号;
等于BUF_LENGTH时,给出full信号
elem_cnt:
▷ 写而未满时增加1
▷ 读而未空时减1
▷ 同时发生读写操作时,elem_cnt不变
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
elem_cnt <= 4'b0;
end
else if (wr_en_i && rd_en_i && !full_o && !empty_o) begin
elem_cnt <= elem_cnt;
end
else if(wr_en_i && !full_o) begin
elem_cnt <= elem_cnt + 1'b1;
end
else if(rd_en_i && !empty_o) begin
elem_cnt <= elem_cnt - 1'b1;
end
end
/*---------------------------------------------------\
------------- generate the flags -----------------
\---------------------------------------------------*/
always @(*) begin
if(!rst_n_i) begin
elem_cnt_nxt = 1'b0;
end
else if(elem_cnt != 4'd0 && rd_en_i && !empty_o) begin
elem_cnt_nxt = elem_cnt - 1'b1;
end
else if(elem_cnt != 4'd8 && wr_en_i && !full_o) begin
elem_cnt_nxt = elem_cnt + 1'b1;
end
else begin
elem_cnt_nxt = elem_cnt;
end
end
assign full_comb = (elem_cnt_nxt == 4'd8);
assign empty_comb = (elem_cnt_nxt == 4'd0);
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
full_o <= 1'b0;
end
else begin
full_o <= full_comb;
end
end
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
empty_o <= 1'b1;
end
else begin
empty_o <= empty_comb;
end
end
/*---------------------------------------------------\
-------------------- read data -------------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
rd_data_o <= 32'b0;
end
else if(rd_en_i && !empty_o) begin
rd_data_o <= regs_array[rd_ptr];
end
end
/*---------------------------------------------------\
------------------- write data -------------------
\---------------------------------------------------*/
reg [PTR_WIDTH:0] i;
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
for(i=0;i<DATA_DEPTH;i=i+1) begin
regs_array[i] <= 32'b0;
end
end
else if(wr_en_i && !full_o) begin
regs_array[wr_ptr] <= wr_data_i;
end
end
endmodule
2.同步FIFO的两种Verilog设计方法(计数器法、高位扩展法)
计数器法
//计数器法实现同步FIFO
module sync_fifo_cnt
#(
parameter DATA_WIDTH = 'd8 , //FIFO位宽
parameter DATA_DEPTH = 'd16 //FIFO深度
)
(
input clk , //系统时钟
input rst_n , //低电平有效的复位信号
input [DATA_WIDTH-1:0] data_in , //写入的数据
input rd_en , //读使能信号,高电平有效
input wr_en , //写使能信号,高电平有效
output reg [DATA_WIDTH-1:0] data_out, //输出的数据
output empty , //空标志,高电平表示当前FIFO已被写满
output full , //满标志,高电平表示当前FIFO已被读空
output reg [$clog2(DATA_DEPTH) : 0] fifo_cnt //$clog2是以2为底取对数
);
//reg define
reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0]; //用二维数组实现RAM
reg [$clog2(DATA_DEPTH) - 1 : 0] wr_addr; //写地址
reg [$clog2(DATA_DEPTH) - 1 : 0] rd_addr; //读地址
//读操作,更新读地址
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
rd_addr <= 0;
else if (!empty && rd_en)begin //读使能有效且非空
rd_addr <= rd_addr + 1'd1; //读指针+1
data_out <= fifo_buffer[rd_addr]; //读出数据
end
end
//写操作,更新写地址
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
wr_addr <= 0;
else if (!full && wr_en)begin //写使能有效且非满
wr_addr <= wr_addr + 1'd1; //写指针+1
fifo_buffer[wr_addr]<=data_in; //写数据进fifo
end
end
//更新计数器
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
fifo_cnt <= 0;
else begin
case({wr_en,rd_en}) //拼接读写使能信号进行判断
2'b00:fifo_cnt <= fifo_cnt; //不读不写
2'b01: //仅仅读
if(fifo_cnt != 0) //fifo没有被读空
fifo_cnt <= fifo_cnt - 1'b1; //fifo个数-1
2'b10: //仅仅写
if(fifo_cnt != DATA_DEPTH) //fifo没有被写满
fifo_cnt <= fifo_cnt + 1'b1; //fifo个数+1
2'b11:fifo_cnt <= fifo_cnt; //读写同时
default:;
endcase
end
end
//依据计数器状态更新指示信号
//依据不同阈值还可以设计半空、半满 、几乎空、几乎满
assign full = (fifo_cnt == DATA_DEPTH) ? 1'b1 : 1'b0; //满信号
assign empty = (fifo_cnt == 0)? 1'b1 : 1'b0; //空信号
endmodule
高位扩展法
module sync_fifo_ptr
#(
parameter DATA_WIDTH = 'd8 , //FIFO位宽
parameter DATA_DEPTH = 'd16 //FIFO深度
)
(
input clk , //系统时钟
input rst_n , //低电平有效的复位信号
input [DATA_WIDTH-1:0] data_in , //写入的数据
input rd_en , //读使能信号,高电平有效
input wr_en , //写使能信号,高电平有效
output reg [DATA_WIDTH-1:0] data_out, //输出的数据
output empty , //空标志,高电平表示当前FIFO已被写满
output full //满标志,高电平表示当前FIFO已被读空
);
//reg define
//用二维数组实现RAM
reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0];
reg [$clog2(DATA_DEPTH) : 0] wr_ptr; //写地址指针,位宽多一位
reg [$clog2(DATA_DEPTH) : 0] rd_ptr; //读地址指针,位宽多一位
//wire define
wire [$clog2(DATA_DEPTH) - 1 : 0] wr_ptr_true; //真实写地址指针
wire [$clog2(DATA_DEPTH) - 1 : 0] rd_ptr_true; //真实读地址指针
wire wr_ptr_msb; //写地址指针地址最高位
wire rd_ptr_msb; //读地址指针地址最高位
//!!!
assign {wr_ptr_msb,wr_ptr_true} = wr_ptr; //将最高位与其他位拼接
assign {rd_ptr_msb,rd_ptr_true} = rd_ptr; //将最高位与其他位拼接
//读操作,更新读地址
always @ (posedge clk or negedge rst_n) begin
if (rst_n == 1'b0)
rd_ptr <= 'd0;
else if (rd_en && !empty)begin //读使能有效且非空
data_out <= fifo_buffer[rd_ptr_true];
rd_ptr <= rd_ptr + 1'd1;
end
end
//写操作,更新写地址
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
wr_ptr <= 0;
else if (!full && wr_en)begin //写使能有效且非满
wr_ptr <= wr_ptr + 1'd1;
fifo_buffer[wr_ptr_true] <= data_in;
end
end
//!!!更新指示信号
//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign empty = ( wr_ptr == rd_ptr ) ? 1'b1 : 1'b10;
//当最高位不同但是其他位相等时,写指针超过读指针一圈,FIFO被写满
assign full = ( (wr_ptr_msb != rd_ptr_msb ) && ( wr_ptr_true == rd_ptr_true ) )? 1'b1 : 1'b0;
endmodule
验证
1.思路
例化1个深度为8,位宽为8的同步FIFO
先对FIFO进行写操作,直到其写满,写入的数据为随机数据
然后对FIFO进行读操作,直到其读空
然后对FIFO写入4个随机数据后,同时对其进行读写操作
2.TB代码
对应2.2.1的RTL
`timescale 1ns/1ns //时间单位/精度
//------------<模块及端口声明>----------------------------------------
module tb_sync_fifo_cnt();
parameter DATA_WIDTH = 8 ; //FIFO位宽
parameter DATA_DEPTH = 8 ; //FIFO深度
reg clk ;
reg rst_n ;
reg [DATA_WIDTH-1:0] data_in ;
reg rd_en ;
reg wr_en ;
wire [DATA_WIDTH-1:0] data_out;
wire empty ;
wire full ;
wire [$clog2(DATA_DEPTH) : 0] fifo_cnt;
//------------<例化被测试模块>----------------------------------------
sync_fifo_cnt
#(
.DATA_WIDTH (DATA_WIDTH), //FIFO位宽
.DATA_DEPTH (DATA_DEPTH) //FIFO深度
)
sync_fifo_cnt_inst(
.clk (clk ),
.rst_n (rst_n ),
.data_in (data_in ),
.rd_en (rd_en ),
.wr_en (wr_en ),
.data_out (data_out ),
.empty (empty ),
.full (full ),
.fifo_cnt (fifo_cnt )
);
//------------<设置初始测试条件>----------------------------------------
initial begin
clk = 1'b0; //初始时钟为0
rst_n <= 1'b0; //初始复位
data_in <= 'd0;
wr_en <= 1'b0;
rd_en <= 1'b0;
//重复8次写操作,让FIFO写满
repeat(8) begin
@(negedge clk)begin
rst_n <= 1'b1;
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
end
end
//重复8次读操作,让FIFO读空
repeat(8) begin
@(negedge clk)begin
wr_en <= 1'b0;
rd_en <= 1'd1;
end
end
//重复4次写操作,写入4个随机数据
repeat(4) begin
@(negedge clk)begin
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
rd_en <= 1'b0;
end
end
//持续同时对FIFO读写,写入数据为随机数据
forever begin
@(negedge clk)begin
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
rd_en <= 1'b1;
end
end
end
//------------<设置时钟>----------------------------------------------
always #10 clk = ~clk; //系统时钟周期20ns
endmodule
1.实践
1.RTL
module sync_fifo
#(
parameter fifo_width=16,
parameter fifo_depth=8,
parameter addr_width=3
)
(
input clk,
input rst_n,
input [fifo_width-1:0] data_in,
output reg [fifo_width-1:0] data_out,
input wr_en,
input rd_en,
output empty,
output full,
output reg [addr_width:0] fifo_cnt
);
reg [fifo_width-1:0]fifo_buffer [fifo_depth-1:0];
reg [addr_width-1:0]wr_addr;
reg [addr_width-1:0]rd_addr;
//read
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rd_addr <= 'd0;
else if (rd_en && !empty)
begin
rd_addr<= rd_addr +'d1;
data_out <= fifo_buffer[rd_addr];
end
end
//write
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
wr_addr <= 'd0;
else if (wr_en && !full)
begin
wr_addr<= wr_addr +'d1;
fifo_buffer [wr_addr ]<= data_in;
end
end
//cnt
always@(posedge clk or negedge rst_n)
begin
if(! rst_n )
fifo_cnt<='d0;
else
begin
case({wr_en,rd_en})
2'b00:fifo_cnt <=fifo_cnt;
2'b01:
if(fifo_cnt !='b0)
fifo_cnt <=fifo_cnt -'b1;
2'b10:
if(fifo_cnt!=fifo_depth)
fifo_cnt <=fifo_cnt +'b1;
2'b11:fifo_cnt <=fifo_cnt ;
endcase
end
end
//
assign full=(fifo_cnt==fifo_depth)?1'b1:'b0;
assign empty=(fifo_depth ==0)?1'b1:'b0;
endmodule
2.TB
`timescale 1ns/1ns
module sync_fifo_tb();
parameter fifo_width=16;
parameter fifo_depth=8;
parameter addr_width=3;
reg clk;
reg rst_n;
reg [fifo_width-1:0] data_in ;
wire [fifo_width-1:0] data_out;
reg wr_en ;
reg rd_en ;
wire empty;
wire full;
wire [addr_width:0] fifo_cnt;
//
sync_fifo
#(
.fifo_width(fifo_width),
.fifo_depth(fifo_depth ),
.addr_width(addr_width)
)
sync_fifo_u1
(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.data_out(data_out ),
.wr_en (wr_en ),
.rd_en(rd_en ),
.empty(empty),
.full(full ),
.fifo_cnt(fifo_cnt )
);
//time
always #10 clk= ~clk ;
//
initial begin
clk<=1'b0;
rst_n<=1'b0;
wr_en<=1'b0;
rd_en <=1'b0;
data_in <='d0;
//write
repeat(8)begin
@(negedge clk)begin
rst_n<=1'b1;
wr_en<=1'b1;
data_in <=$random;
end
end
//read
repeat(8)begin
@(negedge clk)begin
rst_n<=1'b1;
rd_en <=1'b1;
wr_en<='b0;
end
end
//write&read
repeat(4)begin
@(negedge clk)begin
rst_n<=1'b1;
wr_en<=1'b1;
rd_en<='b0;
data_in<=$random;
end
end
repeat(4)begin
@(negedge clk)begin
rst_n<=1'b1;
wr_en<=1'b1;
data_in<=$random;
rd_en<=1'b1;
end
end
end
endmodule
3.波形debug
1.问题1
发现读空后empty信号不为0
解决思路
增加读操作循环repeat(10) 信号仍不变
考虑fifo dut有bug
assign empty=(fifo_depth ==0)?1'b1:'b0;
更正为
assign empty=(fifo_cnt ==0)?1'b1:'b0;
正确代码
module sync_fifo
#(
parameter fifo_width=16,
parameter fifo_depth=8,
parameter addr_width=3
)
(
input clk,
input rst_n,
input [fifo_width-1:0] data_in,
output reg [fifo_width-1:0] data_out,
input wr_en,
input rd_en,
output empty,
output full,
output reg [addr_width:0] fifo_cnt
);
reg [fifo_width-1:0]fifo_buffer [fifo_depth-1:0];
reg [addr_width-1:0]wr_addr;
reg [addr_width-1:0]rd_addr;
//read
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rd_addr <= 'd0;
else if (rd_en && !empty)
begin
rd_addr<= rd_addr +'d1;
data_out <= fifo_buffer[rd_addr];
end
end
//write
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
wr_addr <= 'd0;
else if (wr_en && !full)
begin
wr_addr<= wr_addr +'d1;
fifo_buffer [wr_addr ]<= data_in;
end
end
//cnt
always@(posedge clk or negedge rst_n)
begin
if(! rst_n )
fifo_cnt<='d0;
else
begin
case({wr_en,rd_en})
2'b00:fifo_cnt <=fifo_cnt;
2'b01:
if(fifo_cnt !='b0)
fifo_cnt <=fifo_cnt -'b1;
2'b10:
if(fifo_cnt!=fifo_depth)
fifo_cnt <=fifo_cnt +'b1;
2'b11:fifo_cnt <=fifo_cnt ;
endcase
end
end
//
assign full=(fifo_cnt==fifo_depth)?1'b1:'b0;
assign empty=(fifo_cnt ==0)?1'b1:'b0;
endmodule
正确波形
2. 问题2
RTL
parameter fifo_width=16,
TB
parameter fifo_width=8,
parameter fifo_width=32,
仿真波形的fifo buffer的位宽为8/32