手撕代码——同步FIFO

这篇具有很好参考价值的文章主要介绍了手撕代码——同步FIFO。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、FIFO原理与设计

  查看Xilinx官方FIFO IP核,其主要的信号有时钟信号、写端口信号、读端口信号,其中,写端口信号包括写满信号full、写使能信号wr_en、写数据输入din、几乎满信号almost_full;读端口信号包括读空信号empty、读使能信号rd_en、读数据输出dout、几乎空信号almost_empty。几乎满信号almost_full与几乎空信号almost_empty是可选的。
手撕代码——同步FIFO
  根据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
  在这里仍然存在问题:修改仿真文件,使得读使能早于写使能,可以看到数据成功写入FIFO,因为写使能wr_en有效取决于满信号full为低电平,而数据并没有成功从FIFO中读出,因为读使能rd_en有效取决于空信号empty为低电平,而在FIFO成功写入数据后,空信号并没有拉低,从而导致读数据失败,但空信号empty取决于FIFO中的数据计数器,计数器由读使能与写使能同时控制,由于写使能与读使能同为高,所以计数器并没有加1,从而导致空信号没有拉低。

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

手撕代码——同步FIFO文章来源地址https://www.toymoban.com/news/detail-460004.html

到了这里,关于手撕代码——同步FIFO的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数字IC手撕代码】Verilog模三检测器(判断输入序列能否被三整除)|题目|原理|设计|仿真

    芯片设计验证社区·芯片爱好者聚集地·硬件相关讨论社区·数字verifier星球 四社区 联合力荐 !近500篇 数字IC精品文章收录 ! 【数字IC精品文章收录】学习路线·基础知识·总线·脚本语言·芯片求职·EDA工具·低功耗设计Verilog·STA·设计·验证·FPGA·架构·AMBA·书籍 本系列旨在提

    2024年02月16日
    浏览(41)
  • FIFO的工作原理及其设计

            FIFO( First Input First Output)简单说就是指先进先出。FIFO存储器是一个先入先出的双口缓冲器,即第一个进入其内的数据第一个被移出,其中一个口是存储器的输入口,另一个口是存储器的输出口。         对于单片FIFO来说,主要有两种结构:触发导向结构和零导

    2024年01月25日
    浏览(46)
  • 基于xilinx的fifo IP核使用

            FIFO(First In First Out,即先入先出),是一种数据缓冲器,用来实现数据先入先出的读写方式。与 ROM 或 RAM 的按地址读写方式不同,FIFO 的读写遵循“先进先出”的原则,即数据按顺序写入 FIFO,先被写入的数据同样在读取的时候先被读出,所以FIFO存储器没有地址

    2024年02月08日
    浏览(43)
  • 基于xilinx的axi-fifo的应用

    作为一个有一定工作经验(划水好多年)的FPGA工程师,很多模块都已经学习过或者使用过,但是如果让我重新实现,感觉又是一脸懵。因此,这是我发文档的原因。对于自己来说,这是一个总结归纳的过程,对读者,可能是一次解惑。 后期,将会逐渐分享DDR/ETH/SERDES/PCIe/SP

    2024年02月09日
    浏览(59)
  • Xilinx IP解析之FIFO Generator v13.2

    可参考Xilinx官网fifo_generator概述, 以下翻译自官网此IP的概述。 产品描述: LogiCORE™IP FIFO生成器内核生成经过充分验证的先进先出(FIFO)内存队列,非常适合需要按顺序存储和检索数据的应用。 该内核为所有FIFO配置提供了优化的解决方案,并在利用最少资源的同时提供了最

    2024年02月06日
    浏览(43)
  • xilinx FPGA FIFO IP核的使用(VHDL&ISE)

    1.新建工程和ip核文件 下图显示了一个典型的写操作。拉高WR_EN,导致在WR_CLK的下一个上升边缘发生写入操作。因为FIFO未满,所以WR_ACK输出1,确认成功的写入操作。当只有一个附加的单词可以写入FIFO时,FIFO会拉高ALMOST_FULL标志。当ALMOST_FULL拉高之后,一个附加的写入将导致

    2024年02月03日
    浏览(54)
  • 跨时钟域方法(同步器、异步FIFO、边沿检测器、脉冲同步器、同步FIFO)

    目录 1、跨时钟域方法的原因 2、跨时钟处理的两种思路 3、跨时钟域分类——单比特信号跨时钟 3.1.1慢时钟———快时钟。(满足三边沿准则,有效事件可以被安全采样) 3.1.2慢时钟———快时钟。(不满足三边沿准则,有效事件可以被安全采样) 3.2.1有效事件传输背景下确

    2024年02月12日
    浏览(48)
  • 【GAOPS051】(xilinx First-Word Fall-Through模式)预读FIFO

    预读FIFO:rdata在ren当拍有效。(xilinx First-Word Fall-Through模式) 普通FIFO:rdata在ren下拍有效。(xilinx Standard模式) 注:实现的思路各有千秋,下面是我的实现思路。 首先用简单双口RAM包装出一个Lfifo,这个Lfifo有以下关键特征: raddr可以由外部输入的switch_addr信号来切换 Lfifo_raddr_o

    2024年02月09日
    浏览(46)
  • FIFO专题之单口RAM实现FIFO(同步)

    使用单口RAM实现FIFO,其实很简单,其中的重点就是区分出读写, 读写如果同时启动,你肯定会思考单口RAM肯定会出问题,毕竟单口RAM只有一个口,肯定不能实现同时读写,那么怎么解决这个问题呢。 有两种办法: 第一种办法就是采用两个单口RAM,这样就可以了,两个单口

    2024年02月02日
    浏览(42)
  • xilinx 7系列FPGA 官方文档整理

    1. 官方文档查找链接 搜索结果 • AMD 自适应计算文档门户 (xilinx.com) 2. ug470 - 配置user guide 7 Series FPGAs Configuration User Guide • 7 Series FPGAs Configuration User Guide (UG470) • 阅读器 • AMD 自适应计算文档门户 (xilinx.com) 3. ug471 -IO资源 ug471_7Series_SelectIO.pdf • 查看器 • AMD 自适应计算文档

    2024年04月13日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包