跨异步时钟域处理方法大全

描述

方法1 双触发器(打2拍)

该方法只用于慢到快时钟域的1bit信号传递。在Xilinx器件中,可以使用(* ASYNC_REG = "TRUE" *)标记,将两个寄存器尽量靠近综合,降低 亚稳态因导线延迟太大而传播到第二个寄存器的可能性。

 

module ff2(    input  clk0,//10Minput  din,    input  clk1,//100Moutput dout );    reg din_r=1'd0;     (* ASYNC_REG = "TRUE" *)    reg r0=1'd0;     (* ASYNC_REG = "TRUE" *)    reg r1=1'd0;    assign dout = r1;    always@(posedge clk0)         din_r <= din;//由于不确定前级是否有触发器,这里默认加一级寄存,防止出现毛刺always@(posedge clk1)begin     r0    <= din_r;         r1    <= r0;    endendmodule

 

方法2 双向握手 传递信号

双向握手方法使用了5个触发器,右边3个与方法1一致。左边两个用于将确认信号同步到写时钟域。用户必须将信号保持到确认信号置高。
该方法适用于慢到快快到慢1bit信号传递,一般而言,信号有效值为高电平。

寄存器
可以用一个非门、一个异或门、一个与门,和一个触发器(下图中的reg-2)实现信号保持,输入低速单脉冲,但是不保证输出单脉冲,可以在输出端加一个寄存器,用于上升沿判断.
寄存器
在verilog可以用一些简单的判断来保持信号。下面是一个单向信号异步桥,支持单脉冲输入,单脉冲输出,时钟速率无限制
使用时,确保在active置低时,将s_vld给异步桥。

 

/*  *   Name  : 单向信号异步桥 *   Origin: 230406 *   EE    : hel */module signalbridge_async(    inputwire                s_clk   ,    inputwire                s_rstn  ,    inputwire                s_vld   ,// pulse input inputwire                d_clk   ,    inputwire                d_rstn  ,    outputreg                 d_vld   ,// pulse output outputwire                active    );reg [1   :0] s_syncer ;reg [1   :0] d_syncer ;wire         ack_d2 = s_syncer[1];wire         req_d2 = d_syncer[1];     reg          req_d3;reg          async_req;wire         async_ack = req_d2;assign       active = async_req | ack_d2 ;// Source clock domainalways @(posedge s_clk ornegedge s_rstn) beginif (!s_rstn) begin         s_syncer   <= 2'd0;    endelsebegin         s_syncer   <=  {s_syncer[0],async_ack};    endendalways @(posedge s_clk ornegedge s_rstn) beginif (!s_rstn) begin         async_req   <= 1'd0;    endelseif(ack_d2)begin         async_req   <= 1'd0;    endelseif(s_vld)begin         async_req   <= 1'd1;    endend// Destination clock domainalways @(posedge d_clk ornegedge d_rstn) beginif (!d_rstn) begin         d_syncer   <= 2'd0;    endelsebegin         d_syncer   <=  {d_syncer[0],async_req};    endendalways @(posedge d_clk ornegedge d_rstn) beginif (!d_rstn) begin         req_d3      <= 1'd0;         d_vld       <= 1'd0;    endelsebegin         req_d3      <= req_d2;         d_vld       <= req_d2 & (~req_d3);    endendendmodule

 

方法3 双向握手 传递数据

可以使用请求信号采样跨时钟域过来的多比特数据,就可以做到数据的跨时钟域了(数据跨时钟域,频率低到高,高到低)如下图:

寄存器
图里没有画反馈信号,数据需要在目的时钟采样时保持稳定。这种单个数据跨时钟域传输的编写流程如下:

原时钟域置高req,置好data

目的时钟域将req打两拍变成req_d2

目的时钟域检测到req_d2置高,将data打到寄存器内,并将ack置高

原时钟域检测到ack_d2被置高,拉低req

目的时钟域检测到req_d2被拉低,将ack拉低

通过上述流程编写的数据异步桥如下:
支持单个数据输入输出,时钟速率无限制。使用时,确保在active置低时,将s_din和s_vld给异步桥,s_vld必须为单脉冲。

 

/*  *   Name  : 单向数据异步桥 *   Origin: 230404 *   EE    : hel */module databridge_async #(    parameter DW = 8)(    inputwire                s_clk   ,    inputwire                s_rstn  ,    inputwire    [DW-1:0]    s_din   ,    inputwire                s_vld   , // pulse input inputwire                d_clk   ,    inputwire                d_rstn  ,    outputreg     [DW-1:0]    d_dout  ,    outputreg                 d_vld   ,// pulse output outputwire                active    );reg [1   :0] s_syncer ;reg [1   :0] d_syncer ;wire         ack_d2 = s_syncer[1];wire         req_d2 = d_syncer[1];     reg          req_d3;reg [DW-1:0] async_dat;reg          async_req;wire         async_ack = req_d2;assign       active = async_req | ack_d2 ;// Source clock domainalways @(posedge s_clk ornegedge s_rstn) beginif (!s_rstn) begin         s_syncer   <= 2'd0;    endelsebegin         s_syncer   <=  {s_syncer[0],async_ack};    endendalways @(posedge s_clk ornegedge s_rstn) beginif (!s_rstn) begin         async_dat   <= {DW{1'd0}};    endelseif(s_vld && (!active))begin         async_dat   <=  s_din;    endendalways @(posedge s_clk ornegedge s_rstn) beginif (!s_rstn) begin         async_req   <= 1'd0;    endelseif(ack_d2)begin         async_req   <= 1'd0;    endelseif(s_vld)begin         async_req   <= 1'd1;    endend// Destination clock domainalways @(posedge d_clk ornegedge d_rstn) beginif (!d_rstn) begin         d_syncer   <= 2'd0;    endelsebegin         d_syncer   <=  {d_syncer[0],async_req};    endendalways @(posedge d_clk ornegedge d_rstn) beginif (!d_rstn) begin         d_dout      <= {DW{1'd0}};    endelseif(req_d2)begin         d_dout      <= async_dat;    endendalways @(posedge d_clk ornegedge d_rstn) beginif (!d_rstn) begin         req_d3      <= 1'd0;         d_vld       <= 1'd0;    endelsebegin         req_d3      <= req_d2;         d_vld       <= req_d2 & (~req_d3);    endendendmodule

 

testbench:

 

`timescale 1ns/1psmodule databridge_async_tb ();parameter DW = 8;reg s_clk  = 0;reg s_rstn = 0;reg [DW-1:0] s_din = 0;reg s_vld = 0;reg d_clk  = 0;reg d_rstn = 0;wire [DW-1:0] d_dout;wire d_vld;wire active;reg [DW-1:0]buffer ;real ts;real td;real a;real b;real clkstp;always#(ts)  s_clk <= ~s_clk;always#(td)  d_clk <= ~d_clk; databridge_async #(DW) databridge_async (    .s_clk   ( s_clk  ),    .s_rstn  ( s_rstn ),    .s_din   ( s_din  ),    .s_vld   ( s_vld  ),    .d_clk   ( d_clk  ),    .d_rstn  ( d_rstn ),    .d_dout  ( d_dout ),    .d_vld   ( d_vld  ),    .active  ( active ) );taskautomatic gen_trans;input [DW-1:0]data;begin     #0     s_vld  =  1;     s_din  = data;     @(posedge s_clk);     #0     s_vld  =  0;     s_din  =  0;endendtask//automatictaskautomatic wait_busy;begin     @(posedge s_clk);#0;    while (active) begin         @(posedge s_clk);#0;    endendendtask//automatictaskautomatic loop_test;inputinteger n;integer i;reg [63:0]random_dat;beginfor (i = 0;i

 

方法4 转为独热码或格雷码

如果多bit信号是简单连续变化(连续加一或减一),可以将其转为格雷码,再打2拍到目标时钟域,最终再解码为二进制编码。

如果多bit信号没有变化规律,可以将其转为独热码,再打2拍到目标时钟域,最终再解码为二进制编码。该方法适用于慢到快多bit信号传递。

为什么data不能直接通过同步器过域:
寄存器
多比特数据由于存在传输路径延迟,到达同步器有先后顺序,导致同步器采样到不同电平,输出错误数据。
根本原因是源端数据有多个bit发生跳变。如果每次只有一个bit跳变(连续变化格雷码、独热码),就一定能采样到正确的数据。
假如目的时钟非常慢,比原时钟慢很多,在慢时钟上升沿时还是能采到正确数据,可能采到跳变之前的也可能是之后的,但总之都是source的数据。如果采到跳变之前的数据,就会产生异步FIFO的虚空虚满。从这个角度看,异步FIFO是绝对安全的(忽略同步器失效),异步FIFO两边的时钟频率没有任何要求。

方法5 深度为2的ASYNC-FIFO

在传输信号,不需要较高带宽时,可以将普通格雷码异步FIFO特化为2-deep ASYNC-FIFO,该方法适用于慢到快快到慢多bit信号传递。一般很少使用2deep-FIFO,而是使用双向握手来传递单个多比特数据。
寄存器

方法6 ASYNC-FIFO

该方法适用于慢到快快到慢多bit数据传递。

 

module async_fifo //(First_Word_Fall_Through FIFO,数据提前输出,可能不利于时序收敛)#(    parameterinteger DATA_WIDTH = 16 ,    parameterinteger ADDR_WIDTH = 8//深度只能是2**ADDR_WIDTH ) (    inputwire                 wr_rst  ,         inputwire                 wr_clk  ,         inputwire                 wr_ena  ,         inputwire[DATA_WIDTH-1:0] wr_dat  ,      outputwire                 wr_full ,    inputwire                 rd_rst  ,       inputwire                 rd_clk  ,         inputwire                 rd_ena  ,         outputwire[DATA_WIDTH-1:0] rd_dat  ,      outputwire                 rd_empty  );reg  [DATA_WIDTH-1:0]mem[0:2**ADDR_WIDTH-1];reg  [ADDR_WIDTH  :0]wrptr;reg  [ADDR_WIDTH  :0]rdptr;wire [ADDR_WIDTH-1:0]wraddr = wrptr[ADDR_WIDTH-1:0];wire [ADDR_WIDTH-1:0]rdaddr = rdptr[ADDR_WIDTH-1:0];wire [ADDR_WIDTH  :0]wrptr_gray = (wrptr>>1)^wrptr;wire [ADDR_WIDTH  :0]rdptr_gray = (rdptr>>1)^rdptr;reg  [ADDR_WIDTH  :0]wrptr_gray_r0;reg  [ADDR_WIDTH  :0]wrptr_gray_r1;reg  [ADDR_WIDTH  :0]rdptr_gray_r0;reg  [ADDR_WIDTH  :0]rdptr_gray_r1;assign wr_full  = (wrptr_gray == {~rdptr_gray_r1[ADDR_WIDTH:ADDR_WIDTH-1],rdptr_gray_r1[ADDR_WIDTH-2:0]});assign rd_empty = (rdptr_gray == wrptr_gray_r1);//在有些设计中,full和empty提早一个时钟出现,并加了一级寄存,这有助于收敛always @(posedge wr_clk orposedge wr_rst) beginif (wr_rst) begin         rdptr_gray_r0 <= 'd0;         rdptr_gray_r1 <= 'd0;    endelsebegin         rdptr_gray_r0 <= rdptr_gray;         rdptr_gray_r1 <= rdptr_gray_r0;    endendalways @(posedge rd_clk orposedge rd_rst) beginif (rd_rst) begin         wrptr_gray_r0 <= 'd0;         wrptr_gray_r1 <= 'd0;    endelsebegin         wrptr_gray_r0 <= wrptr_gray;         wrptr_gray_r1 <= wrptr_gray_r0;    endendalways @(posedge wr_clk orposedge wr_rst) beginif (wr_rst) begin         mem[0]      <= 'd0;         wrptr       <= 'd0;    endelseif (wr_ena&&(!wr_full)) begin         mem[wraddr] <= wr_dat;         wrptr       <= wrptr + 1'd1;    endelsebegin         mem[wraddr] <= mem[wraddr];         wrptr       <= wrptr;    endendalways @(posedge rd_clk orposedge rd_rst) beginif (rd_rst) begin         rdptr       <= 'd0;    endelseif (rd_ena&&(!rd_empty)) begin         rdptr       <= rdptr + 1'd1;    endelsebegin         rdptr       <= rdptr;    endendassign rd_dat = mem[rdaddr];endmodule

 

 

   

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分