手撕代码——同步FIFO

一、FIFO原理与设计

  查看Xilinx官方FIFO IP核,其主要的信号有时钟信号、写端口信号、读端口信号,其中,写端口信号包括写满信号full、写使能信号wr_en、写数据输入din、几乎满信号almost_full;读端口信号包括读空信号empty、读使能信号rd_en、读数据输出dout、几乎空信号almost_empty。几乎满信号almost_full与几乎空信号almost_empty是可选的。
在这里插入图片描述
  根据Xilinx官方的FIFO IP核,可以仿照写一个简单的同步FIFO(读写在同一时钟域)。在这里我们设计一个宽度为DATA_WIDTH,深度为DATA_DEPTH的同步FIFO,其输入输出端口如下:

  • 输入端口
    clk:时钟信号
    rst_n:异步复位信号
    wr_en:写使能信号,在wr_en为高电平,且满信号full为低电平期间,数据写入FIFO;
    rd_en:读使能信号,在rd_en高电平,且空信号empty为低电平期间,从FIFO中读出数据;
    din:写数据输入,位宽为DATA_WIDTH;
  • 输出端口
    dout:读数据输出,位宽为DATA_WIDTH;
    full:写满信号,当FIFO中存储的数据个数达到DATA_DEPTH时,full为高电平,表示FIFO写满了,不再向FIFO写入数据(即使写使能wr_en为高电平);当FIFO中存储的数据个数小于DATA_DEPTH时,full为低电平,表示可以继续往FIFO中写入数据;
    empty:读空信号,当FIFO中存储的数据个数为0时,empty为高电平,表示FIFO中已无数据,不再从FIFO中读出数据(即使读使能rd_en为高电平);当FIFO中存储的数据个数大于0时,empty为低电平,表示FIFO中还有数据,可以继续从FIFO中读出数据;

  那么,FIFO本质还是一个存储器,在这里使用一个宽度为DATA_WIDTH,深度为DATA_DEPTH的SRAM实现存储,使用写指针地址wr_ptr控制写地址,每进行一次写操作,写指针地址自加1,使用读指针地址rd_ptr控制读地址,每进行一次读操作,读指针地址自加1。在写使能信号为高电平,FIFO未写满(full为低电平)时,执行写操作;在读使能信号为高电平,FIFO未读空(empty为低电平)时,执行读操作。

//===========write pointer address===========//
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        wr_ptr <= 'd0;
    else if(wr_en && !full)
        wr_ptr <= wr_ptr + 1'b1;
    else
        wr_ptr <= wr_ptr;
end

//===========read pointer address===========//
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        rd_ptr <= 'd0;
    else if(rd_en && !empty)
        rd_ptr <= rd_ptr + 1'b1;
    else
        rd_ptr <= rd_ptr;
end

  那么,用一个计数器cnt来计算当前FIFO中存储的数据。计数器的控制分为四种情况:
(1)单时钟只执行写操作:当FIFO只进行写操作,而不执行读操作,即写使能wr_en为高电平,写满信号full为低电平,读使能rd_en为低电平时,计数器cnt自加1;
(2)单时钟只执行读操作:当FIFO只进行读操作,而不进行写操作,即读使能rd_en为高电平,读空信号empty为低电平,写使能wr_en为低电平时,计数器自加1;
(3)单时钟同时执行写操作与地操作:当FIFO同时执行写操作与读操作,即写使能wr_en为高电平,写满信号full为低电平,读使能rd_en为高电平,读空信号empty为低电平时,计数器cnt不变;
(4)单时钟既不执行写操作也不执行读操作:当FIFO既不执行写操作也不执行读操作时,计数器cnt不变;

//===========counter===========//
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt <= 'd0;
    else if(wr_en && !full && !rd_en)
        cnt <= cnt + 1'b1;
    else if(rd_en && !empty && !wr_en)
        cnt <= cnt - 1'b1;
    else
        cnt <= cnt;
end

  另外,用FIFO中存储的数据计数器cnt来判断写满信号full、读空信号empty、几乎满信号almost_full、几乎空信号almost_empty。当计数器cnt为0时,表示FIFO中数据为空,读空信号empty为高电平;当计数器cnt为1时,表示FIFO中数据几乎空,几乎空信号almost_empty为高电平;当计数器为DATA_DEPTH-1时,表示FIFO中的存储的数据个数几乎为满,则几乎满信号almost_full信号为高电平;当计数器为DATA_DEPTH时,表示FIFO中存储的数据个数满了,则写满信号full为高电平。

//===========full===========//
assign full = (cnt == DATA_DEPTH) ? 1'b1 : 1'b0;

//===========empty===========//
assign empty = (cnt == 'd0) ? 1'b1 : 1'b0;

//===========almost_full===========//
assign almost_full = (cnt == DATA_DEPTH-1) ? 1'b1 : 1'b0;

//===========almost_empty===========//
assign almost_empty = (cnt == 'd1) ? 1'b1 : 1'b0;

  最后,根据读操作以及读地址,从FIFO中读出数据;根据写操作及写地址,将数据写入FIFO。

//===========read data===========//
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        dout_r <= 'd0;
    else if(rd_en && !empty)
        dout_r <= mem[rd_ptr];
end
assign dout = dout_r;

//===========write data===========//
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        for(i=0;i<DATA_DEPTH;i=i+1) begin
            mem[i] <= 'd0;
        end
    else if(wr_en && !full)
        mem[wr_ptr] <= din;
end

二、完整代码与仿真结果

  同步FIFO完整代码如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/05/16 09:23:07
// Design Name: 
// Module Name: sync_fifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 同步FIFO
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module sync_fifo
#(
    parameter   DATA_WIDTH = 8,
    parameter   DATA_DEPTH = 8,
    parameter   CNT_WIDTH  = $clog2(DATA_DEPTH)
)
(
    input                      clk         ,
    input                      rst_n       ,
    input                      wr_en       ,
    input   [DATA_WIDTH-1:0]   din         ,
    input                      rd_en       ,
    output  [DATA_WIDTH-1:0]   dout        ,
    output                     almost_full ,
    output                     full        ,
    output                     almost_empty,    
    output                     empty        
);

integer i;
reg     [DATA_WIDTH-1:0]    mem     [DATA_DEPTH-1:0];
reg     [CNT_WIDTH:0]       cnt;
reg     [CNT_WIDTH-1:0]     wr_ptr,rd_ptr;
reg     [DATA_WIDTH-1:0]    dout_r;

//===========write pointer address===========//
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        wr_ptr <= 'd0;
    else if(wr_en && !full)
        wr_ptr <= wr_ptr + 1'b1;
    else
        wr_ptr <= wr_ptr;
end

//===========read pointer address===========//
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        rd_ptr <= 'd0;
    else if(rd_en && !empty)
        rd_ptr <= rd_ptr + 1'b1;
    else
        rd_ptr <= rd_ptr;
end

//===========counter===========//
//原版
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt <= 'd0;
    else if(wr_en && !full && !rd_en)
        cnt <= cnt + 1'b1;
    else if(rd_en && !empty && !wr_en)
        cnt <= cnt - 1'b1;
    else
        cnt <= cnt;
end

/*
//修改版
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt <= 'd0;
    else if(wr_en && !full && !rd_en) //写使能wr_en有效,读使能rd_en无效
        cnt <= cnt + 1'b1;
    else if(wr_en && !full && rd_en && empty) //rd_en为高电平,但是empty为高电平,所以实际上读使能rd_en无效
        cnt <= cnt + 1'b1;
    else if(rd_en && !empty && !wr_en) //读使能rd_en有效,写使能wr_en无效
        cnt <= cnt - 1'b1;
    else if(rd_en && !empty && wr_en && full) //wr_en为高电平,但是full为高电平,所以实际上写使能wr_en无效
        cnt <= cnt - 1'b1;
    else
        cnt <= cnt;
end
*/

//===========full===========//
assign full = (cnt == DATA_DEPTH) ? 1'b1 : 1'b0;

//===========empty===========//
assign empty = (cnt == 'd0) ? 1'b1 : 1'b0;

//===========almost_full===========//
assign almost_full = (cnt >= DATA_DEPTH-1) ? 1'b1 : 1'b0;

//===========almost_empty===========//
assign almost_empty = (cnt <= 'd1) ? 1'b1 : 1'b0;

//===========read data===========//
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        dout_r <= 'd0;
    else if(rd_en && !empty)
        dout_r <= mem[rd_ptr];
end
assign dout = dout_r;

//===========write data===========//
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        for(i=0;i<DATA_DEPTH;i=i+1) begin
            mem[i] <= 'd0;
        end
    else if(wr_en && !full)
        mem[wr_ptr] <= din;
end

endmodule

  仿真文件如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/05/16 11:22:57
// Design Name: 
// Module Name: tb_sync_fifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module tb_sync_fifo;

parameter   DATA_WIDTH = 8;
parameter   DATA_DEPTH = 16;
parameter   CNT_WIDTH  = $clog2(DATA_DEPTH);

reg                      clk,rst_n;
reg                      wr_en    ;
reg   [DATA_WIDTH-1:0]   din      ;
reg                      rd_en    ;
wire  [DATA_WIDTH-1:0]   dout     ;
wire                     full     ;
wire                     empty    ;
wire                     almost_full ;
wire                     almost_empty;

parameter  clk_period = 10; 

initial 
begin
    clk = 1'b1;
    rst_n <= 1'b1;
    rd_en <= 1'b0;
    wr_en <= 1'b0;
    din <= 'd0;
  
    #(clk_period*10)
    rst_n <= 1'b0;
    #(clk_period*1)
    rst_n <= 1'b1;    

    repeat(50) begin
        @(posedge clk) begin
          wr_en <= {$random}%2;
          rd_en <= {$random}%2;
          
          
          din <= din + 1'b1;
        end
    end
    #(clk_period)
    #(clk_period*10)
    $stop;
end

always #(clk_period/2)  clk = ~clk;

sync_fifo
#(
    .DATA_WIDTH(DATA_WIDTH),
    .DATA_DEPTH(DATA_DEPTH)
)sync_fifo
(
    .clk         (clk         ),
    .rst_n       (rst_n       ),
    .wr_en       (wr_en       ),
    .din         (din         ),
    .rd_en       (rd_en       ),
    .dout        (dout        ),
    .almost_full (almost_full ),
    .full        (full        ),
    .almost_empty(almost_empty),    
    .empty       (empty       ) 
);

endmodule

三、仿真结果

  对同步FIFO进行仿真,可以看到,初始化复位下,空信号empty与几乎空信号almost_empty均为高电平,在成功写入数据,FIFO中数据个数大于0时,empty信号被拉低;当FIFO中存储数据大于1时,几乎空信号almost_empty被拉低。

在这里插入图片描述
  在这里仍然存在问题:修改仿真文件,使得读使能早于写使能,可以看到数据成功写入FIFO,因为写使能wr_en有效取决于满信号full为低电平,而数据并没有成功从FIFO中读出,因为读使能rd_en有效取决于空信号empty为低电平,而在FIFO成功写入数据后,空信号并没有拉低,从而导致读数据失败,但空信号empty取决于FIFO中的数据计数器,计数器由读使能与写使能同时控制,由于写使能与读使能同为高,所以计数器并没有加1,从而导致空信号没有拉低。

在这里插入图片描述
  于是,找到问题的关键所在,把同步FIFO设计中的计数器cnt部分原版修改为修改版,增加了两种情况:写使能wr_en有效读使能rd_en无效、读使能rd_en有效写使能wr_en无效,重新仿真,可以看到结果正确。

在这里插入图片描述

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个同步复位的FIFOVerilog 设计代码和对应的 testbench: ```verilog module fifo_sync_rst ( input clk, // 时钟信号 input rst, // 复位信号 input we, // 写使能信号 input [7:0] din, // 写数据信号 input re, // 读使能信号 output [7:0] dout, // 读数据信号 input full, // FIFO满信号 input empty // FIFO空信号 ); parameter DEPTH = 16; // FIFO深度 reg [7:0] mem [0:DEPTH-1]; // FIFO存储单元 reg [3:0] wr_ptr = 0; // 写指针 reg [3:0] rd_ptr = 0; // 读指针 reg [3:0] count = 0; // FIFO计数器 // 写操作 always @(posedge clk) begin if (rst) begin wr_ptr <= 0; count <= 0; end else if (we && !full) begin mem[wr_ptr] <= din; wr_ptr <= wr_ptr + 1; count <= count + 1; end end // 读操作 always @(posedge clk) begin if (rst) begin rd_ptr <= 0; count <= 0; end else if (re && !empty) begin dout <= mem[rd_ptr]; rd_ptr <= rd_ptr + 1; count <= count - 1; end end // FIFO满和空信号 assign full = (count == DEPTH); assign empty = (count == 0); endmodule module fifo_sync_rst_tb; reg clk, rst, we, re; reg [7:0] din; wire [7:0] dout; wire full, empty; fifo_sync_rst dut (.clk(clk), .rst(rst), .we(we), .din(din), .re(re), .dout(dout), .full(full), .empty(empty)); // 时钟信号 always #5 clk = ~clk; // 重置信号 initial begin rst = 1; #50 rst = 0; end // 写操作 initial begin we = 1; din = 8'h01; #10; din = 8'h02; #10; din = 8'h03; #10; din = 8'h04; #10; din = 8'h05; #10; din = 8'h06; #10; din = 8'h07; #10; din = 8'h08; #10; din = 8'h09; #10; din = 8'h0A; #10; din = 8'h0B; #10; din = 8'h0C; #10; din = 8'h0D; #10; din = 8'h0E; #10; din = 8'h0F; #10; din = 8'h10; #10; din = 8'h11; #10; din = 8'h12; #10; din = 8'h13; #10; din = 8'h14; #10; din = 8'h15; #10; din = 8'h16; #10; din = 8'h17; #10; din = 8'h18; #10; din = 8'h19; #10; din = 8'h1A; #10; din = 8'h1B; #10; din = 8'h1C; #10; din = 8'h1D; #10; din = 8'h1E; #10; din = 8'h1F; end // 读操作 initial begin re = 1; #60; re = 0; #10; re = 1; #10; re = 0; #20; re = 1; #10; re = 0; #30; re = 1; #10; re = 0; #40; re = 1; #10; re = 0; #50; re = 1; #10; re = 0; #60; re = 1; #10; re = 0; #70; re = 1; #10; re = 0; end // 输出状态 always @(posedge clk) begin $display("count=%d full=%d empty=%d", dut.count, full, empty); end endmodule ``` 在设计中,FIFO 采用双时钟同步存储器的方式进行存储,读写指针和计数器用于维护 FIFO 的状态,full 和 empty 信号用于表示 FIFO 的满和空状态。在 testbench 中,使用了一个时钟信号和一个复位信号,以及写使能、写数据、读使能等信号来模拟 FIFO 的读写操作。利用 $display 函数输出 FIFO 的状态信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值