CDC处理——异步FIFO

2023-10-30

1. 异步FIFO原理

        请看《硬件架构的艺术》笔记(三)3.8节 异步FIFO 

2. 格雷码传递FIFO读写指针(回环特性)

        通常情况下,设计的异步FIFO的深度是2的N次方,但事实上,选择这个2^N的原因也是因为格雷码这么取的时候,最大值+1回到最小值时,跳变还是只有1bit。

        事实上格雷码可以取2N个进行环回,例如取格雷码个数为12,在2^4=16的基础上,掐头去尾,取中间的12个格雷码,这样从0011到最大值1011中间任意两个相邻数之间只有1bit位的跳变。

        任意偶数个数格雷码,都可以实现相邻数字跳变仅有1bit发生变化。 

 3. 任意深度异步FIFO

        异步FIFO的关键就在于读写指针跨时钟域传输的问题,保证每次跳变仅 有1bit变化,这样无论是变化前还是变化后的读写指针被同步到另一个时钟域,都不会发生“FIFO空时继续读”、“FIFO满时继续写”这样的逻辑错误。

3.1 偶数深度的异步FIFO

        对于偶数深度的FIFO,如FIFO深度为4。那么无论FIFO深度是多少,我们都会对地址进行扩展1bit,来作为标志位,用于产生空满信号。因此FIFO深度为4时,我们的读写指针取值范围为:

        {0_110,0_111,0_101,0_100,1_100,1_101,1_111,1_110}

        可以看到,任意两个相邻的数字仅有1bit发生跳变,取格雷码回环就按照:在2^4=16的基础上,掐头去尾,取中间的8个格雷码。

3.2 奇数深度的异步FIFO

        对于奇数深度的FIFO,如FIFO深度为3。那么无论FIFO深度是多少,我们都会对地址进行扩展1bit,来作为标志位,用于产生空满信号。因此FIFO深度为3时,我们的读写指针取值范围为:

        {0_111,0_101,0_100,1_100,1_101,1_111}

        可以看到,任意两个相邻的数字仅有1bit发生跳变,取格雷码回环就按照:在2^4=16的基础上,掐头去尾,取中间的6个格雷码。

        也就是说,无论FIFO深度是奇数还是偶数,我们都会对读写指针扩1bit标志位,使得扩充后他们的深度一定是偶数。这样我们就可以用第二节提到的偶数格雷码环回特性:任意偶数个数格雷码,都可以实现相邻数字跳变仅有1bit发生变化。让读写指针跨时钟域传输时,相邻数字仅有1bit发生变化,就不会出现FIFO读写错误,所以FIFO深度可以是任意值。

4. Verilog 实现

4.1 设计代码

module async_fifo
#(
    parameter   DATA_WIDTH  =   16, //FIFO 位宽(数据位宽)
                FIFO_DEPTH  =   8,  //FIFO 深度
                ADDR_DEPTH  =   $clog2(FIFO_DEPTH) //根据FIFO深度计算地址位宽,此处为3。
)
(
    //reset signals
    input   wire                        wr_rst_n_i  ,
    input   wire                        rd_rst_n_i  ,
    
    //write interface
    input   wire                        wr_clk_i    ,
    input   wire                        wr_en_i     ,
    input   wire    [DATA_WIDTH-1:0]    wr_data_i   ,
    
    //read interface
    input   wire                        rd_clk_i    ,
    input   wire                        rd_en_i     ,
    output  reg     [DATA_WIDTH-1:0]    rd_data_o   ,
    
    //flag signals
    output  wire                        fifo_full_o ,
    output  wire                        fifo_empty_o
);

//memary
reg     [DATA_WIDTH-1:0]    fifo_buffer [FIFO_DEPTH-1:0];

//memary addr
wire    [ADDR_DEPTH-1:0]    wr_addr;    //真实写地址,作为写ram地址
wire    [ADDR_DEPTH-1:0]    rd_addr;    //真实读地址,作为读ram地址

//write poiter and write poiter of gray and sync
reg     [ADDR_DEPTH:0]  wr_ptr          ;   //写指针,二进制
wire    [ADDR_DEPTH:0]  gray_wr_ptr     ;   //写指针,格雷码
reg     [ADDR_DEPTH:0]  gray_wr_ptr_d0  ;   //写指针在读时钟域下同步一拍
reg     [ADDR_DEPTH:0]  gray_wr_ptr_d1  ;   //写指针在读时钟域下同步二拍

//read poiter and read poiter of gray and sync
reg     [ADDR_DEPTH:0]  rd_ptr          ;   //读指针,二进制
wire    [ADDR_DEPTH:0]  gray_rd_ptr     ;   //读指针,格雷码
reg     [ADDR_DEPTH:0]  gray_rd_ptr_d0  ;   //读指针在写时钟域下同步一拍
reg     [ADDR_DEPTH:0]  gray_rd_ptr_d1  ;   //读指针在写时钟域下同步二拍

//---write poiter and bin2gray---//
always@(posedge wr_clk_i or negedge wr_rst_n_i)begin 
    if(~wr_rst_n_i)begin 
        wr_ptr <= 'b0;
    end else if(wr_en_i && !fifo_full_o)begin   //写使能有效,且非满
        wr_ptr <= wr_ptr + 1'b1;
    end 
end 

assign gray_wr_ptr = wr_ptr ^ (wr_ptr>>1);

//---gray_wr_ptr sync to read clk domain---//
always@(posedge rd_clk_i or negedge rd_rst_n_i)begin 
    if(~rd_rst_n_i)begin 
        gray_wr_ptr_d0 <= 'b0;              //寄存1拍
        gray_wr_ptr_d1 <= 'b0;              //寄存2拍
    end else begin 
        gray_wr_ptr_d0 <= gray_wr_ptr   ;   //寄存1拍
        gray_wr_ptr_d1 <= gray_wr_ptr_d0;   //寄存2拍
    end 
end 

//---read poiter and bin2gray---//
always@(posedge rd_clk_i or negedge rd_rst_n_i)begin 
    if(~rd_rst_n_i)begin 
        rd_ptr <= 'b0;
    end else if(rd_en_i && !fifo_empty_o)begin  //读使能有效,且非空
        rd_ptr <= rd_ptr + 1'b1;
    end 
end 

assign gray_rd_ptr = rd_ptr ^ (rd_ptr>>1);

//---gray_rd_ptr sync to write clk domain---//
always@(posedge wr_clk_i or negedge wr_rst_n_i)begin 
    if(~wr_rst_n_i)begin 
        gray_rd_ptr_d0 <= 'b0;              //寄存1拍
        gray_rd_ptr_d1 <= 'b0;              //寄存2拍
    end else begin                          
        gray_rd_ptr_d0 <= gray_rd_ptr   ;   //寄存1拍
        gray_rd_ptr_d1 <= gray_rd_ptr_d0;   //寄存2拍
    end 
end 

//---full flag and empty flag---//
//当高位相反且其他位相等时,写指针超过读指针一圈,FIFO被写满
//同步后的读指针格雷码高两位取反,再拼接上余下位
assign fifo_full_o = (gray_wr_ptr == {~gray_rd_ptr_d1[ADDR_DEPTH], ~gray_rd_ptr_d1[ADDR_DEPTH-1], gray_rd_ptr_d1[ADDR_DEPTH-2:0]})
                       ? 1'b1 : 1'b0;
                      
//当所有位相等时,读指针追到到了写指针,FIFO被读空                     
assign fifo_empty_o= (gray_rd_ptr == gray_wr_ptr_d1) ? 1'b1 : 1'b0;

//---write addr and read addr---//
assign wr_addr = wr_ptr[ADDR_DEPTH-1:0];    //写RAM地址等于写指针的低 ADDR_DEPTH 位(去除最高位)
assign rd_addr = rd_ptr[ADDR_DEPTH-1:0];    //读RAM地址等于读指针的低 ADDR_DEPTH 位(去除最高位)

//---write operation---//
integer  i;

always@(posedge wr_clk_i or negedge wr_rst_n_i)begin 
    if(~wr_rst_n_i)begin 
        for(i=0; i<FIFO_DEPTH; i=i+1)begin 
            fifo_buffer[i] <= 'd0;
        end 
    end else if(wr_en_i && !fifo_full_o)begin   //写使能有效,且非满
        fifo_buffer[wr_addr] <= wr_data_i;
    end 
end 

//---read operation---//
always@(posedge rd_clk_i or negedge rd_rst_n_i)begin 
    if(~rd_rst_n_i)begin 
        rd_data_o <= {(DATA_WIDTH){1'b0}};
    end else if(rd_en_i && !fifo_empty_o)begin  //读使能有效,且非空
        rd_data_o <= fifo_buffer[rd_addr];
    end 
end      

endmodule


 

4.2 测试代码(testbench)

`timescale 1ns/1ns	//时间单位/精度
 
//----------<模块及端口声明>-------//
module tb_async_fifo();
 
parameter   DATA_WIDTH = 8  ;		//FIFO位宽
parameter   FIFO_DEPTH = 8 ;		//FIFO深度

parameter   TEST_TIME = 300;        //随机测试最大次数
 
 
reg                     wr_rst_n_i  ;
reg                     rd_rst_n_i  ;
                                    
reg                     wr_clk_i    ;
reg                     wr_en_i     ;
reg  [DATA_WIDTH-1:0]   wr_data_i   ;
                                    
reg                     rd_clk_i    ;
reg                     rd_en_i     ;
wire  [DATA_WIDTH-1:0]  rd_data_o   ;
                                                                       
wire                    fifo_full_o ;
wire                    fifo_empty_o;
 
integer i;

 
//------------<设置时钟>-------------------//
always #10 rd_clk_i = ~rd_clk_i;			//读时钟周期20ns
always #20 wr_clk_i = ~wr_clk_i;			//写时钟周期40ns
 
//------------<设置初始测试条件>----------//
initial begin
	rd_clk_i = 1'b0;			    //初始时钟为0
	wr_clk_i = 1'b0;				//初始时钟为0
	wr_rst_n_i <= 1'b0;				//初始复位
	rd_rst_n_i <= 1'b0;				//初始复位
	wr_en_i <= 1'b0;
	rd_en_i <= 1'b0;	
	wr_data_i <= 'd0;
	#5
	wr_rst_n_i <= 1'b1;				
	rd_rst_n_i <= 1'b1;	
    
//-----------固定测试-------------//    
//重复8次写操作,让FIFO写满 	
	repeat(8) begin
		@(negedge wr_clk_i)begin		
			wr_en_i <= 1'b1;
			wr_data_i <= $random;	//生成8位随机数
		end
	end
//拉低写使能	
	@(negedge wr_clk_i)	wr_en_i <= 1'b0;
	
//重复8次读操作,让FIFO读空 	
	repeat(8) begin
		@(negedge rd_clk_i)
        rd_en_i <= 1'd1;		
	end
//拉低读使能
	@(negedge rd_clk_i)rd_en_i <= 1'd0;	   
	
//重复4次写操作,写入4个随机数据	
	repeat(4) begin
		@(negedge wr_clk_i)begin		
			wr_en_i <= 1'b1;
			wr_data_i <= $random;	//生成8位随机数
		end
	end
//持续同时对FIFO读
	@(negedge rd_clk_i)rd_en_i <= 1'b1;
//持续同时对FIFO写,写入数据为随机数据	
	repeat(100) begin
		@(negedge wr_clk_i)begin		
			wr_en_i <= 1'b1;
			wr_data_i <= $random;	//生成8位随机数
		end
	end	
// 拉低读写使能   
    @(negedge wr_clk_i)wr_en_i <= 1'b0;
    @(negedge rd_clk_i)rd_en_i <= 1'b0;
    
//等待5个写时钟周期
    repeat(5) @(posedge wr_clk_i);
    
//--------------随机测试---------------//   
    for(i=0; i<TEST_TIME; i=i+1)begin
        if(i<100) begin 
            @(negedge wr_clk_i) ;
                wr_en_i <= $random() % 2;
                wr_data_i <= $random();
            @(negedge rd_clk_i);
                rd_en_i <= 1;         
        end 
        if(i<200) begin 
            @(negedge wr_clk_i) ;
                wr_en_i <= 1;
                wr_data_i <= $random();
            @(negedge rd_clk_i);
                rd_en_i <= $random() % 2;
            
        end 
        else if(i<TEST_TIME)begin
            @(negedge wr_clk_i) ;
                wr_en_i <= $random() % 2;
                wr_data_i <= $random();
            @(negedge rd_clk_i);
                rd_en_i <= $random() % 2;         
        end      
    end 

    repeat(5) @(posedge wr_clk_i);
    $finish();
end

//------------<例化被测试模块>-----------//
async_fifo
#(
	.DATA_WIDTH	(DATA_WIDTH),			//FIFO位宽
    .FIFO_DEPTH	(FIFO_DEPTH)			//FIFO深度
)
async_fifo_inst(
	.wr_rst_n_i     (wr_rst_n_i  ),  
	.rd_rst_n_i     (rd_rst_n_i  ),
                     
	.wr_clk_i       (wr_clk_i    ),          
	.wr_en_i        (wr_en_i     ),
	.wr_data_i      (wr_data_i   ),
	                 
	.rd_clk_i    	(rd_clk_i    ),
	.rd_en_i        (rd_en_i     ),
    .rd_data_o      (rd_data_o   ),
                     
    .fifo_full_o    (fifo_full_o ),
    .fifo_empty_o   (fifo_empty_o)
);
 
endmodule

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CDC处理——异步FIFO 的相关文章

  • dotNet基于office实现word转pdf

    20171228 WordToPdf byDotNet 基于Office的实现步骤 主要是利用了 office com 组件中的 Microsoft Office Interop word dll 动态链接库文件可以通过 c 代码实现对 w

随机推荐

  • 科普文章|通过Polkassembly平台使用MetaMask参与Moonbeam链上民主治理

    Moonriver上的Runtime 1200升级引入了一个民主预编译的功能 允许网络参与者使用MetaMask钱包参与链上治理投票 此外 近期Polkassembly与Mooriver集成 这是一个供任何人可以讨论并参与基于Substra
  • scala机器学习实战(一) 保险数据预测分析

    scala机器学习之保险数据预测分析 数据资料来源 此文章数据内容来源于Scala Machine Learning Projects 2018版 一书 本书分为是一个章节 本文章内容来自于第一章节 书本链接 Scala Machine L
  • unity图片相似度2

    using System Collections using System Collections Generic using System IO using UnityEngine using UnityEngine UI public
  • OneOS 定位功能测评+开发详解

    本文分享自中移OneOS微信公众号 定位功能测评 开发详解 本期严同学为了给大家展现 OnePos定位服务能力 复现地铁轨迹线路 坐了大半天的地铁 老辛苦了 这期视频大家一定要看看喔 点此跳转演示视频 想要一比一复原开发流程的小伙伴 可以看
  • Windows10 屏幕底部有一条蓝线挡住任务栏底部解决办法

    Windows10 屏幕底部有一条蓝线挡住任务栏底部解决办法 网上无效解决办法 跳出睡眠模式 网上无效解决办法 1 任务栏设置 选择总是隐藏标签 这个在我这一版的Windows10早就没有这个选项了 其他的选项也都没有用 2 屏幕物理损坏或
  • Java HTTP 代理服务器

    Java HTTP 代理服务器 在当今的企业开发环境中 我们不得不与代理打交道 通常是作为系统管理员 在大多数情况下 应用程序将配置为系统的默认设置 但如果您想对应用程序进行非常严格的控制 例如代理设置 对于这种情况 Java允许使用 AP
  • AWVS 15.6 使用教程

    目录 介绍 版本 AWVS具有以下特点和功能 功能介绍 Dashboard功能 Targets功能 Scans功能 Vulnerabilities功能 Reports功能 Users功能 Scan Profiles功能 WAFs功能 Pro
  • 英语音标表、48个国际音标发音表、falsh音标学习视频

    随着时代的进步 世界变得越来越小了 掌握一门外语是必不可少的 其中英语是目前使用最广泛的语言 英语音标 则是掌握英语发音的基础 通过本站英语音标表 falsh音标学习 希望对您有帮助 音标发音表 鼠标移到音标上 稍停留 即发音 48个国际音
  • C语言中getchar()函数的详解

    文章目录 关于getchar 函数的基本作用 getchar 函数基本案例 在循环当中的getchar getchar 函数的作用 清理缓冲区中多个字符 关于getchar 函数的基本作用 getchar 函数顾名思义就是获取一个字符 那么
  • 避坑之路 —— 前后端 json 的注意问题

    当我们在进行开发项目的时候 在前后端需要进行数据之间的传输 那么就会需要到json 而json算是字符串中的一种 1 先说一下前端的 其实这两种都是表示前端希望能收到后端json这样的数据格式 那么我们在后端就需要注意将数据进行转换为jso
  • 透视Matplotlib核心功能和工具包 - Cartopy工具包

    Cartopy是用于在Matplotlib上绘制地理地图的第三方工具包 Cartopy具有各种各样的功能 可以满足许多不同的用户群体 在这里 我们将尝试介绍企业中通常使用的大多数功能 地理地图以经度和纬度绘制 均以度为单位 经度绘制在x轴上
  • Qt编程基础

    一 信号与槽 1 什么是信号与槽 信号和槽是用于对象之间的通信 它是Qt的核心机制 在Qt编程中有着广泛的应用 如果想学好Qt 一定要充分掌握信号的槽的概念与使用 2 信号和槽的代码实例 在Qt中 发送对象 发送的信号 接收对象 槽可以通过
  • ISP(三) 硬阈值函数(Hard Thresholding)与软阈值函数(Soft Thresholding)的区别

    一旦明白 其实简单至极 也就那么回事 常用的软阈值函数 是为了解决硬阈值函数 一刀切 导致的影响 模小于3sigma的小波系数全部切除 大于3sigma全部保留 势必会在小波域产生突变 导致去噪后结果产生局部的抖动 类似于傅立叶变换中频域的
  • 日历插件美化版

    https ext dcloud net cn plugin id 3324
  • 服务器显卡:驱动高性能计算和人工智能应用

    一 引言 随着高性能计算和人工智能应用的不断发展 服务器显卡的性能显得越来越重要 服务器显卡是服务器硬件配置中的一个关键组件 它不仅提供基本的图形渲染能力 还在高性能计算和人工智能应用中发挥着重要作用 本文将探讨服务器显卡的重要性和发展趋势
  • 使用ESP8266 12-E板载的CH340对ESP01-s进行烧录

    先借两张图 因为ESP01 S的烧录器找不到了 临时用ESP8266 12 E板载的CH340对ESP01 s进行烧录 1 12 E的EN引脚接地G 2 ESP01 s的3v3连接12 E的3v3 3 ESP01 s的GND连接12 E的G
  • 【左神算法课学习笔记】动态规划

    左神算法课学习笔记 动态规划 动态规划是对暴力递归算法的优化 主要是通过数组记录的方法 优化掉一些重复计算的过程 总结下动态规划的过程 1 抽象出一种 试法 递归解决问题的方法 很重要 2 找到 试法 中的可变参数 规划成数组表 可变参数一
  • 蓝桥杯官网练习题(李白打酒)

    题目描述 本题为填空题 只需要算出结果后 在代码中使用输出语句将所填结果输出即可 话说大诗人李白 一生好饮 幸好他从不开车 一天 他提着酒壶 从家里出来 酒壶中有酒2斗 他边走边唱 无事街上走 提壶去打酒 逢店加一倍 遇花喝一斗 这一路上
  • 查看python环境路径_查看python环境的一些知识点

    1 查看python中的查找模块的路径import sys sys path usr bin usr lib64 python26 zip usr lib64 python2 6 usr lib64 python2 6 plat linux
  • CDC处理——异步FIFO

    1 异步FIFO原理 请看 硬件架构的艺术 笔记 三 3 8节 异步FIFO 2 格雷码传递FIFO读写指针 回环特性 通常情况下 设计的异步FIFO的深度是2的N次方 但事实上 选择这个2 N的原因也是因为格雷码这么取的时候 最大值 1回