手撕代码——同步FIFO

news2025/5/22 12:08:47

手撕代码——同步FIFO

  • 一、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无效,重新仿真,可以看到结果正确。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/549234.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[元带你学: eMMC完全解读 7] eMMC 设备与系统概述

依JEDEC eMMC 5.1及经验辛苦整理,付费内容,禁止转载。 所在专栏 《元带你学: eMMC完全解读》 前言 全文3600 字, 全文介绍eMMC 内部系统框架, Bus 总线宽度, 总线Speed Mode, 了解即可。对应Bus 总线重点看 8 Bit 即可, Speed Mode 重点看 HS400。几乎所有的系统都是跑在…

Java并发编程-synchronized

目录 1. synchronized在jdk 1.6中的优化 1.1 锁消除 1.2 锁粗化 1.2 锁升级/锁膨胀 1.2.1 锁升级原理 1.2.2 自适应自旋锁 2. synchronized实现原理 3. synchronized和Lock的对比 1. synchronized在jdk 1.6中的优化 在JDK1.5的时候,Doug Lee推出了ReentrantLock,lock的…

【密码学复习】第九讲 密钥管理(一)

密钥管理简介 • 柯克霍夫斯原则(Kerckhoffs Principle) 即使密码系统的任何细节已为人悉知&#xff0c;只要密钥未泄漏&#xff0c;它也应是安全的(19世纪). 密钥安全&#xff1a;三分技术&#xff0c;七分管理 密钥管理就是在授权各方之间实现密钥关系的建立和维护…

dom4j 读取xml配置文件,根据配置文件利用反射创建对象

pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 …

maven打包命令打出的可执行的jar包和可依赖的jar包的区别

目录 引出问题: 介绍打包插件 下面开始打包: 解压比较这两个jar包的区别: 引出问题: 当我建了一个maven的springboot项目A写了一个工具类,我把A项目打包成jar包去给B项目用,结果 B项目报错找不到这个jar包. 百度后发现原来jar包分为可执行jar包和可被依赖的jar包 介绍打包…

二、医院设置管理

文章目录 一、医院设置管理1、项目开发流程1.1 定义路由模块1.2 定义api模块1.3 定义页面组件脚本1.4 定义页面组件模板1.5 测试数据通信1.6 跨域处理 2、分页查询2.1 定义页面组件脚本2.2 定义页面组件模板2.3 表单查询 3、删除3.1 定义api模块3.2 定义页面组件模板3.3 定义页…

Midjourney|文心一格prompt教程[进阶篇]:Midjourney Prompt 高级参数、各版本差异、官方提供常见问题

Midjourney|文心一格prompt教程[进阶篇]&#xff1a;Midjourney Prompt 高级参数、各版本差异、官方提供常见问题 1.Midjourney Prompt 高级参数 Quality 图片质量是另一个我比较常用的属性&#xff0c;首先需要注意这个参数并不影响分辨率&#xff0c;并不改变分辨率&#x…

【C++】实现一个日期计算器

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;命运给你一个低的起点&#xff0c;是想看你精彩的翻盘&#xff0c;而不是让你自甘堕落&#xff0c;脚下的路虽然难走&#xff0c;但我还能走&#xff0c;比起向阳而生&#xff0c;我更想尝试逆风…

线程池学习

一、线程池的7个核心参数说明&#xff1a; corePoolSize&#xff1a;核心线程数 maximumPoolSize&#xff1a;最大线程数 keepAliveTime&#xff1a;最大空闲时间 unit&#xff1a;最大空闲时间单位 workQueue&#xff1a;任务队列 threadFactory&#xff1a;表示生成线程…

1.Hyperledger Fabric架构介绍

&#xff08;1&#xff09;Hyperledger定义&#xff1a; Hyperledger是一个开放源代码的区块链项目合作组织&#xff0c;旨在推动跨行业的企业级区块链解决方案的发展。该项目由Linux基金会于2015年发起&#xff0c;致力于建立一个可靠、安全和可扩展的区块链框架和工具集。Hy…

堆结构 - 大根堆、小根堆

在开发语言中&#xff0c;heap在使用层次的名字叫PriorityQueue&#xff08;优先级队列&#xff09;&#xff0c;PriorityQueue数据结构的名字就叫做堆&#xff0c;底层就是用堆结构实现的。 完全二叉树 空树也算是完全二叉树每一层都是满的也算是完全二叉树如果层不满&#…

魔改车钥匙实现远程控车:(番外)在macOS上安装使用MicroPython

前言 哈哈&#xff0c;各位可能会奇怪为啥上一篇文章还在说怎么在 ESP32C3 上安装 Arduino&#xff0c;现在怎么又变成了安装 MIcroPython。 其实是因为上次写 Arduino 还是我高中时候的事了&#xff0c;已经不太会了。 虽然 MIcroPython 我从来没有接触过&#xff0c;但是 …

Microsoft Office 2003的安装

哈喽&#xff0c;大家好。今天一起学习的是office2003的安装&#xff0c;这个老版本的office可是XP操作系统的老搭档了&#xff0c;有兴趣的小伙伴也可以来一起试试手。 一、测试演示参数 演示操作系统&#xff1a;Windows XP 不建议win7及以上操作系统使用 系统类型&#xff…

Springboot 搭建WebService客户端+服务端

WebService简介 Web Service技术&#xff0c; 能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件&#xff0c; 就可相互交换数据或集成。依据Web Service规范实施的应用之间&#xff0c; 无论它们所使用的语言、 平台或内部协议是什么&#xff0c; 都可…

软件设计和架构设计

软件设计和架构设计 1.软件设计 1.1设计 设计是从架构 构件 接口以及系统其他特征定义的过程。 软件设计的结果必须描述系统的架构&#xff0c;系统如何分解和组织构件。 描述构件间的接口。 描述构件必须详细到可进一步构造的程度。 设计是把分析模型转换成设计模型的过…

三个帮助你整理信息的桌面 WiKi

如果你想在桌面上感受 wiki&#xff0c;而不用做那些复杂的工作&#xff0c;这很容易做到。这有一些轻量级 wiki&#xff0c;可以帮助你组织你的信息、跟踪你的任务、管理你的笔记等等。 这个词时&#xff0c;可能会想到 MediaWiki 或 DokuWiki 这样的例子。它们开源、好用、强…

Go 并发之channel(通道)

一、前言 作为 Go 语言最有特色的数据类型&#xff0c;通道&#xff08;channel&#xff09;完全可以与 goroutine&#xff08;也可称为 go 程&#xff09;并驾齐驱&#xff0c;共同代表 Go 语言独有的并发编程模式和编程哲学。 通道&#xff08;channel&#xff09;可以利用…

TOGAF架构开发方法—G阶段:实施治理

本章提供了对实现的体系结构监督。 一、目标 G阶段的目标是&#xff1a; 通过实施项目确保符合目标架构为解决方案和任何实施驱动的架构更改请求执行适当的架构管理功能 二、 输入 本节定义阶段 G 的输入。 1 、企业外部参考物质 架构参考资料 2、 非架构输入 架构工作请…

K8s之污点、容忍度与Pod重启策略详解

文章目录 一、污点-Taint二、容忍度-Tolerations二、Pod重启策略1、Pod常见状态2、Pod重启策略 一、污点-Taint 在 Kubernetes 中&#xff0c;污点&#xff08;Taint&#xff09;是一种标记&#xff0c;用于标识一个Node节点上的某些资源或条件不可用或不可接受。当一个节点被…

基于springboot的社区疫情防控平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SpringBoot 前端&#xff1a;HTML、Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 前言 基于springboot…