异步FIFO设计(Verilog)

FIFO(First In First Out)是异步数据传输时经常使用的存储器。该存储器的特点是数据先进先出(后进后出)。其实,多位宽数据的异步传输问题,无论是从快时钟到慢时钟域,还是从慢时钟到快时钟域,都可以使用 FIFO 处理。


FIFO 原理

工作流程

复位之后,在写时钟和状态信号的控制下,数据写入 FIFO 中。RAM 的写地址从 0 开始,每写一次数据写地址指针加一,指向下一个存储单元。当 FIFO 写满后,数据将不能再写入,否则数据会因覆盖而丢失。

FIFO 数据为非空、或满状态时,在读时钟和状态信号的控制下,可以将数据从 FIFO 中读出。RAM 的读地址从 0 开始,每读一次数据读地址指针加一,指向下一个存储单元。当 FIFO 读空后,就不能再读数据,否则读出的数据将是错误的。

FIFO 的存储结构为双口 RAM,所以允许读写同时进行。典型异步 FIFO 结构图如下所示。端口及内部信号将在代码编写时进行说明。

读写时刻

关于写时刻,只要 FIFO 中数据为非满状态,就可以进行写操作;如果 FIFO 为满状态,则禁止再写数据。关于读时刻,只要 FIFO 中数据为非空状态,就可以进行读操作;如果 FIFO 为空状态,则禁止再读数据。不管怎样,一段正常读写 FIFO 的时间段,如果读写同时进行,则要求写 FIFO 速率不能大于读速率。

读空状态

开始复位时,FIFO 没有数据,空状态信号是有效的。当 FIFO 中被写入数据后,空状态信号拉低无效。当读数据地址追赶上写地址,即读写地址都相等时,FIFO 为空状态。

因为是异步 FIFO,所以读写地址进行比较时,需要同步打拍逻辑,就需要耗费一定的时间。所以空状态的指示信号不是实时的,会有一定的延时。如果在这段延迟时间内又有新的数据写入 FIFO,就会出现空状态指示信号有效,但是 FIFO 中其实存在数据的现象。

严格来讲该空状态指示是错误的。但是产生空状态的意义在于防止读操作对空状态的 FIFO 进行数据读取。产生空状态信号时,实际 FIFO 中有数据,相当于提前判断了空状态信号,此时不再进行读 FIFO 数据操作也是安全的。所以,该设计从应用上来说是没有问题的。

写满状态

开始复位时,FIFO 没有数据,满信号是无效的。当 FIFO 中被写入数据后,此时读操作不进行或读速率相对较慢,只要写数据地址超过读数据地址一个 FIFO 深度时,便会产生满状态信号。此时写地址和读地址也是相等的,但是意义是不一样的。

此时经常使用多余的 1bit 分别当做读写地址的拓展位,来区分读写地址相同的时候,FIFO 的状态是空还是满状态。当读写地址与拓展位均相同的时候,表明读写数据的数量是一致的,则此时 FIFO 是空状态。如果读写地址相同,拓展位为相反数,表明写数据的数量已经超过读数据数量的一个 FIFO 深度了,此时 FIFO 是满状态。当然,此条件成立的前提是空状态禁止读操作、满状态禁止写操作。

同理,由于异步延迟逻辑的存在,满状态信号也不是实时的。但是也相当于提前判断了满状态信号,此时不再进行写 FIFO 操作也不会影响应用的正确性。


FIFO 设计

设计要求

为设计应用于各种场景的 FIFO,这里对设计提出如下要求:

  • (1) FIFO 深度、宽度参数化,输出空、满状态信号,并输出一个可配置的满状态信号。当 FIFO 内部数据达到设置的参数数量时,拉高该信号。
  • (2) 输入数据和输出数据位宽可以不一致,但要保证写数据、写地址位宽与读数据、读地址位宽的一致性。例如写数据位宽 8bit,写地址位宽为 6bit(64 个数据)。如果输出数据位宽要求 32bit,则输出地址位宽应该为 4bit(16 个数据)。
  • (3) FIFO 是异步的,即读写控制信号来自不同的时钟域。输出空、满状态信号之前,读写地址信号要用格雷码做同步处理,通过减少多位宽信号的翻转来减少打拍法同步时数据的传输错误。 格雷码与二进制之间的转换如下图所示。

双口 RAM 设计

RAM 端口参数可配置,读写位宽可以不一致。建议 memory 数组定义时,以长位宽地址、短位宽数据的参数为参考,方便数组变量进行选择访问。

Verilog 描述如下。

module  ramdp#(  parameter       AWI     = 5 ,parameter       AWO     = 7 ,parameter       DWI     = 64 ,parameter       DWO     = 16)(input                   CLK_WR , //写时钟input                   WR_EN ,  //写使能input [AWI-1:0]         ADDR_WR ,//写地址input [DWI-1:0]         D ,      //写数据input                   CLK_RD , //读时钟input                   RD_EN ,  //读使能input [AWO-1:0]         ADDR_RD ,//读地址output reg [DWO-1:0]    Q        //读数据);//输出位宽大于输入位宽,求取扩大的倍数及对应的位数parameter       EXTENT       = DWO/DWI ;parameter       EXTENT_BIT   = AWI-AWO > 0 ? AWI-AWO : 'b1 ;//输入位宽大于输出位宽,求取缩小的倍数及对应的位数parameter       SHRINK       = DWI/DWO ;parameter       SHRINK_BIT   = AWO-AWI > 0 ? AWO-AWI : 'b1;genvar i ;generate//数据位宽展宽(地址位宽缩小)if (DWO >= DWI) begin//写逻辑,每时钟写一次reg [DWI-1:0]         mem [(1<<AWI)-1 : 0] ;always @(posedge CLK_WR) beginif (WR_EN) beginmem[ADDR_WR]  <= D ;endend//读逻辑,每时钟读 4 次for (i=0; i<EXTENT; i=i+1) beginalways @(posedge CLK_RD) beginif (RD_EN) beginQ[(i+1)*DWI-1: i*DWI]  <= mem[(ADDR_RD*EXTENT) + i ] ;endendendend//=================================================//数据位宽缩小(地址位宽展宽)else begin//写逻辑,每时钟写 4 次reg [DWO-1:0]         mem [(1<<AWO)-1 : 0] ;for (i=0; i<SHRINK; i=i+1) beginalways @(posedge CLK_WR) beginif (WR_EN) beginmem[(ADDR_WR*SHRINK)+i]  <= D[(i+1)*DWO -1: i*DWO] ;endendend//读逻辑,每时钟读 1 次always @(posedge CLK_RD) beginif (RD_EN) beginQ <= mem[ADDR_RD] ;endendendendgenerateendmodule

计数器设计

计数器用于产生读写地址信息,位宽可配置,不需要设置结束值,让其溢出后自动重新计数即可。Verilg 描述如下。

module  ccnt#(parameter W )(input              rstn ,input              clk ,input              en ,output [W-1:0]     count);reg [W-1:0]          count_r ;always @(posedge clk or negedge rstn) beginif (!rstn) begincount_r        <= 'b0 ;endelse if (en) begincount_r        <= count_r + 1'b1 ;endendassign count = count_r ;endmodule

FIFO 设计

该模块为 FIFO 的主体部分,产生读写控制逻辑,并产生空、满、可编程满状态信号。

鉴于篇幅原因,这里只给出读数据位宽大于写数据位宽的逻辑代码。

module  fifo#(  parameter       AWI        = 5 ,parameter       AWO        = 3 ,parameter       DWI        = 4 ,parameter       DWO        = 16 ,parameter       PROG_DEPTH = 16) //可设置深度(input                   rstn,  //读写使用一个复位input                   wclk,  //写时钟input                   winc,  //写使能input [DWI-1: 0]        wdata, //写数据input                   rclk,  //读时钟input                   rinc,  //读使能output [DWO-1 : 0]      rdata, //读数据output                  wfull,    //写满标志output                  rempty,   //读空标志output                  prog_full //可编程满标志);//输出位宽大于输入位宽,求取扩大的倍数及对应的位数parameter       EXTENT       = DWO/DWI ;parameter       EXTENT_BIT   = AWI-AWO ;//输出位宽小于输入位宽,求取缩小的倍数及对应的位数parameter       SHRINK       = DWI/DWO ;parameter       SHRINK_BIT   = AWO-AWI ;//==================== push/wr counter ===============wire [AWI-1:0]      waddr ;wire                wover_flag ; //多使用一位做写地址拓展ccnt         #(.W(AWI+1))            u_push_cnt(.rstn           (rstn),.clk            (wclk),.en             (winc && !wfull), //full 时禁止写.count          ({wover_flag, waddr}));//============== pop/rd counter ===================wire [AWO-1:0]            raddr ;wire                      rover_flag ;  //多使用一位做读地址拓展ccnt         #(.W(AWO+1))    u_pop_cnt(.rstn           (rstn),.clk            (rclk),.en             (rinc & !rempty), //empyt 时禁止读.count          ({rover_flag, raddr}));//==============================================//窄数据进,宽数据出
generateif (DWO >= DWI) begin : EXTENT_WIDTH//格雷码转换wire [AWI:0] wptr    = ({wover_flag, waddr}>>1) ^ ({wover_flag, waddr}) ;//将写数据指针同步到读时钟域reg [AWI:0]  rq2_wptr_r0 ;reg [AWI:0]  rq2_wptr_r1 ;always @(posedge rclk or negedge rstn) beginif (!rstn) beginrq2_wptr_r0     <= 'b0 ;rq2_wptr_r1     <= 'b0 ;endelse beginrq2_wptr_r0     <= wptr ;rq2_wptr_r1     <= rq2_wptr_r0 ;endend//格雷码转换wire [AWI-1:0] raddr_ex = raddr << EXTENT_BIT ;wire [AWI:0]   rptr     = ({rover_flag, raddr_ex}>>1) ^ ({rover_flag, raddr_ex}) ;//将读数据指针同步到写时钟域reg [AWI:0]    wq2_rptr_r0 ;reg [AWI:0]    wq2_rptr_r1 ;always @(posedge wclk or negedge rstn) beginif (!rstn) beginwq2_rptr_r0     <= 'b0 ;wq2_rptr_r1     <= 'b0 ;endelse beginwq2_rptr_r0     <= rptr ;wq2_rptr_r1     <= wq2_rptr_r0 ;endend//格雷码反解码//如果只需要空、满状态信号,则不需要反解码//因为可编程满状态信号的存在,地址反解码后便于比较reg [AWI:0]       wq2_rptr_decode ;reg [AWI:0]       rq2_wptr_decode ;integer           i ;always @(*) beginwq2_rptr_decode[AWI] = wq2_rptr_r1[AWI];for (i=AWI-1; i>=0; i=i-1) beginwq2_rptr_decode[i] = wq2_rptr_decode[i+1] ^ wq2_rptr_r1[i] ;endendalways @(*) beginrq2_wptr_decode[AWI] = rq2_wptr_r1[AWI];for (i=AWI-1; i>=0; i=i-1) beginrq2_wptr_decode[i] = rq2_wptr_decode[i+1] ^ rq2_wptr_r1[i] ;endend//读写地址、拓展位完全相同是,为空状态assign rempty    = (rover_flag == rq2_wptr_decode[AWI]) &&(raddr_ex >= rq2_wptr_decode[AWI-1:0]);//读写地址相同、拓展位不同,为满状态assign wfull     = (wover_flag != wq2_rptr_decode[AWI]) &&(waddr >= wq2_rptr_decode[AWI-1:0]) ;//拓展位一样时,写地址必然不小于读地址//拓展位不同时,写地址部分比如小于读地址,实际写地址要增加一个FIFO深度assign prog_full  = (wover_flag == wq2_rptr_decode[AWI]) ?waddr - wq2_rptr_decode[AWI-1:0] >= PROG_DEPTH-1 :waddr + (1<<AWI) - wq2_rptr_decode[AWI-1:0] >= PROG_DEPTH-1;//双口 ram 例化ramdp#( .AWI     (AWI),.AWO     (AWO),.DWI     (DWI),.DWO     (DWO))u_ramdp(.CLK_WR          (wclk),.WR_EN           (winc & !wfull), //写满时禁止写.ADDR_WR         (waddr),.D               (wdata[DWI-1:0]),.CLK_RD          (rclk),.RD_EN           (rinc & !rempty), //读空时禁止读.ADDR_RD         (raddr),.Q               (rdata[DWO-1:0]));end//==============================================//big in and small out/*else begin: SHRINK_WIDTH……end*/endgenerate
endmodule

FIFO 调用

下面可以调用设计的 FIFO,完成多位宽数据传输的异步处理。

写数据位宽为 4bit,写深度为 32。

读数据位宽为 16bit,读深度为 8,可配置 full 深度为 16。

module  fifo_s2b(input                   rstn,input [4-1: 0]          din,     //异步写数据input                   din_clk, //异步写时钟input                   din_en,  //异步写使能output [16-1 : 0]       dout,      //同步后数据input                   dout_clk,  //同步使用时钟input                   dout_en ); //同步数据使能wire         fifo_empty, fifo_full, prog_full ;wire         rd_en_wir ;wire [15:0]  dout_wir ;//读空状态时禁止读,否则一直读assign rd_en_wir     = fifo_empty ? 1'b0 : 1'b1 ;fifo  #(.AWI(5), .AWO(3), .DWI(4), .DWO(16), .PROG_DEPTH(16))u_buf_s2b(.rstn           (rstn),.wclk           (din_clk),.winc           (din_en),.wdata          (din),.rclk           (dout_clk),.rinc           (rd_en_wir),.rdata          (dout_wir),.wfull          (fifo_full),.rempty         (fifo_empty),.prog_full      (prog_full));//缓存同步后的数据和使能reg          dout_en_r ;always @(posedge dout_clk or negedge rstn) beginif (!rstn) begindout_en_r       <= 1'b0 ;endelse begindout_en_r       <= rd_en_wir ;endendassign       dout    = dout_wir ;assign       dout_en = dout_en_r ;endmodule

testbench

`timescale 1ns/1ns
`define         SMALL2BIG
module test ;`ifdef SMALL2BIGreg          rstn ;reg          clk_slow, clk_fast ;reg [3:0]    din ;reg          din_en ;wire [15:0]  dout ;wire         dout_en ;//resetinitial beginclk_slow  = 0 ;clk_fast  = 0 ;rstn      = 0 ;#50 rstn  = 1 ;end//读时钟 clock_slow 较快于写时钟 clk_fast 的 1/4//保证读数据稍快于写数据parameter CYCLE_WR = 40 ;always #(CYCLE_WR/2/4) clk_fast = ~clk_fast ;always #(CYCLE_WR/2-1) clk_slow = ~clk_slow ;//data generateinitial begindin       = 16'h4321 ;din_en    = 0 ;wait (rstn) ;//(1) 测试 full、prog_full、empyt 信号force test.u_data_buf2.u_buf_s2b.rinc = 1'b0 ;repeat(32) begin@(negedge clk_fast) ;din_en = 1'b1 ;din    = {$random()} % 16;end@(negedge clk_fast) din_en = 1'b0 ;//(2) 测试数据读写#500 ;rstn = 0 ;#10 rstn = 1 ;release test.u_data_buf2.u_buf_s2b.rinc;repeat(100) begin@(negedge clk_fast) ;din_en = 1'b1 ;din    = {$random()} % 16;end//(3) 停止读取再一次测试 empyt、full、prog_full 信号force test.u_data_buf2.u_buf_s2b.rinc = 1'b0 ;repeat(18) begin@(negedge clk_fast) ;din_en = 1'b1 ;din    = {$random()} % 16;endendfifo_s2b u_data_buf2(.rstn           (rstn),.din            (din),.din_clk        (clk_fast),.din_en         (din_en),.dout           (dout),.dout_clk       (clk_slow),.dout_en        (dout_en));`else
`endif//stop siminitial beginforever begin#100;if ($time >= 5000)  $finish ;endendendmodule

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

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

相关文章

python中RabbitMQ的使用(路由键模糊匹配)

路由键模糊匹配 使用正则表达式进行匹配。其中“#”表示所有、全部的意思&#xff1b;“*”只匹配到一个词。 匹配规则&#xff1a; 路由键&#xff1a;routings [ happy.work, happy.life , happy.work.teacher, sad.work, sad.life, sad.work.teacher ] "#"&am…

时钟切换处理(Verilog)

随着各种应用场景的限制&#xff0c;芯片在运行时往往需要在不同的应用下切换不同的时钟源&#xff0c;例如低功耗和高性能模式就分别需要低频率和高频率的时钟。两个时钟源有可能是同源且同步的&#xff0c;也有可能是不相关的。直接使用选择逻辑进行时钟切换大概率会导致分频…

Angular Elements 组件在非angular 页面中使用的DEMO

2019独角兽企业重金招聘Python工程师标准>>> 一、Angular Elements 介绍 Angular Elements 是伴随Angular6.0一起推出的新技术。它借助Chrome浏览器的ShadowDom API&#xff0c;实现一种自定义组件。 这种组件可以用Angular普通组件的开发技术进行编写&#xff0c;…

卢卡斯定理

卢卡斯定理:解决一类组合数取模问题 A、B是非负整数&#xff0c;p是质数。AB写成p进制&#xff1a;Aa[n]a[n-1]...a[0]&#xff0c;Bb[n]b[n-1]...b[0]。 则组合数C(A,B)与C(a[n],b[n])*C(a[n-1],b[n-1])*...*C(a[0],b[0]) modp同余 即&#xff1a;Lucas(n,m,p)c(n%p,m%p)*Luc…

loadrunner中对https证书的配置

1、准备好网站的证书&#xff0c;一般证书是cer格式&#xff1b; 2、因为loadrunner只支持pem格式的证书&#xff0c;所以要将证书转换格式&#xff0c;利用openssl工具&#xff1b;&#xff08;或者直接让开发提供pem格式的证书&#xff09;3、得到pem格式的证书之后&#xff…

Datapath综合代码规范(Verilog)

一、一般准则 1、有符号数运算 利用类型“signed”完成有符号数运算&#xff0c;而不是用无符号数模拟有符号数运算。这样可以得到更好的QoR。在资源报告中检查操作数的类型和大小。 2、符号/零扩展 尽量不要手动扩展。verilog利用signed/unsigned会自动完成扩展。这样代码可…

CMOS 图像传感器——Skipping 和 Binning 模式

在通常的CMOS读取方式中&#xff0c;由于像素读取规模的差异&#xff0c;不同的分辨率对应不同的帧率。在通道带宽固定的前提下&#xff0c;想要提高帧率就要考虑是否需要缩小视野&#xff08;外圈裁切&#xff09;。若不希望视野缩小&#xff0c;需要减少采样的分辨率。 常用的…

APB协议学习

APB(Advanced Peripheral Bus) 1、APB的概述与特点 APB主要用于低带宽的周边外设之间的连接&#xff0c;例如UART、1284等&#xff0c;它的总线架构不像AHB支持多个主模块&#xff0c;在APB里面唯一的主模块就是APB 桥。其特性包括&#xff1a;两个时钟周期传输&#xff1b;无…

私有协议栈开发

通信协议从广义上区分&#xff0c;可以分为公有协议和私有协议。由于私有协议的灵活性&#xff0c;它往往会在某个公司或者组织内部使用&#xff0c;按需定制&#xff0c;也因为如此&#xff0c;升级起来会非常方便&#xff0c;灵活性好。绝大多数的私有协议传输层都基于TCP/IP…

数字图像处理——2D降噪

图像降噪处理主要分为2D&#xff08;空域&#xff09;与3D降噪&#xff08;时域/多帧&#xff09;&#xff0c;而2D降噪由于相关的实现算法丰富&#xff0c;效果各异&#xff0c;有着丰富的研究价值。理解2D降噪算法的流程&#xff0c;也对其他的增强算法有很大的帮助&#xff…

项目开发(Require + E.js)

最近在做的几个项目&#xff0c;分别用了不同的框架跟方式&#xff0c;有个H5的项目&#xff0c;用了vue框架&#xff0c; 这个项目我还没有正式加入进去&#xff0c; 等手头的这个项目完成就可以去搞vue了&#xff0c; 现在手头的这个项目是一个招聘的项目&#xff0c; 用到了…

AHB协议学习

1. 简介 AHB(Advanced High Performance Bus)总线规范是AMBA(Advanced Microcontroller Bus Architecture) V2.0总线规范的一部分&#xff0c;AMBA总线规范是ARM公司提出的总线规范&#xff0c;被大多数SoC设计采用&#xff0c;它规定了AHB (Advanced High-performance Bus)、A…

数字图像处理——引导滤波

一、概述 引导滤波是由何恺明等人于2010年发表在ECCV的文章《Guided Image Filtering》中提出的&#xff0c;后续于2013年发表。引导过滤器根据局部线性模型原理&#xff0c;通过考虑引导图像的内容来计算过滤输出&#xff0c;引导图像可以是输入图像本身或另一个不同的图像。具…

Ubuntu 18.04换国内源

2019独角兽企业重金招聘Python工程师标准>>> 参考文档&#xff1a; https://blog.csdn.net/zhangjiahao14/article/details/80554616 https://blog.csdn.net/xiangxianghehe/article/details/80112149 1.复制源文件备份&#xff0c;以防万一 我们要修改的文件是sour…

数字后端——布图规划

布图规划&#xff08;floorplan&#xff09;与布局&#xff08;place&#xff09;在芯片设计中占据着重要的地位&#xff0c;它的合理与否直接关系到芯片的时序收敛、布线通畅、电源稳定以及良品率。所以在整个芯片设计中&#xff0c;从布图规划到完成布局一般需要占据整个物理…

利用SSH传输文件

在linux下一般用scp这个命令来通过ssh传输文件。 1、从服务器上下载文件scp usernameservername:/path/filename /var/www/local_dir&#xff08;本地目录&#xff09; 2、上传本地文件到服务器scp /path/filename usernameservername:/path 例如scp /var/www/test.php root19…

App WebView实例化

a&#xff0c;高级设置里的环境变量 jdk的配置 b&#xff0c;下载Google的sdk&#xff0c;里面直接包含eclipse 1&#xff0c;新建一个项目 2&#xff0c;起个名字 3&#xff0c;设么走不做&#xff0c;next 4&#xff0c;只操作选择显示的三种方式 5&#xff0c;next什么都不做…

[动态代理三部曲:下] - 从动态代理,看Retrofit的源码实现

前言 关于动态代理的系列文章&#xff0c;到此便进入了最后的“一出好戏”。前俩篇内容分别展开了&#xff1a;从源码上&#xff0c;了解JDK实现动态代理的原理&#xff1b;以及从动态代理切入&#xff0c;学会看class文件结构的含义。 如果还没有看过这俩篇文章的小伙伴&#…

数字后端——电源规划

电源规划是给整个芯片的供电设计出一个均勻的网络&#xff0c;它是芯片物理设计中非常关键的一部分。电源规划在芯片布图规划后或在布图规划过程中交叉完成,它贯穿于整个设计中&#xff0c;需要在芯片设计的不同阶段对电源的供电网络进行分析并根据要求进行修改。&#xff0c;主…

逆向project实战--Acid burn

0x00 序言 这是第二次破解 crackme 小程序&#xff0c;感觉明显比第一次熟练。破解过程非常顺利&#xff0c;差点儿是分分钟就能够找到正确的 serial&#xff0c;可是我们的目标是破解计算过程。以下将具体介绍。 0x01 初次执行 刚開始拿到 crackme 先执行程序。看看有哪些明显…