方法1 双触发器(打2拍)
该方法只用于慢到快时钟域的1bit信号传递。在Xilinx器件中,可以使用(* ASYNC_REG = "TRUE" *)
标记,将两个寄存器尽量靠近综合,降低 亚稳态因导线延迟太大而传播到第二个寄存器的可能性。
module ff2(
input clk0,//10M
input din,
input clk1,//100M
output 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;
end
endmodule
方法2 双向握手 传递信号
双向握手方法使用了5个触发器,右边3个与方法1一致。左边两个用于将确认信号同步到写时钟域。用户必须将信号保持到确认信号置高。
该方法适用于慢到快、快到慢、1bit信号传递,一般而言,信号有效值为高电平。
可以用一个非门、一个异或门、一个与门,和一个触发器(下图中的reg-2)实现信号保持,输入低速单脉冲,但是不保证输出单脉冲,可以在输出端加一个寄存器,用于上升沿判断.
在verilog可以用一些简单的判断来保持信号。下面是一个单向信号异步桥,支持单脉冲输入,单脉冲输出,时钟速率无限制
使用时,确保在active置低时,将s_vld给异步桥。
/*
* Name : 单向信号异步桥
* Origin: 230406
* EE : hel
*/
module signalbridge_async(
input wire s_clk ,
input wire s_rstn ,
input wire s_vld ,// pulse input
input wire d_clk ,
input wire d_rstn ,
output reg d_vld ,// pulse output
output wire 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 domain
always @(posedge s_clk or negedge s_rstn) begin
if (!s_rstn) begin
s_syncer <= 2'd0;
end else begin
s_syncer <= {s_syncer[0],async_ack};
end
end
always @(posedge s_clk or negedge s_rstn) begin
if (!s_rstn) begin
async_req <= 1'd0;
end else if(ack_d2)begin
async_req <= 1'd0;
end else if(s_vld)begin
async_req <= 1'd1;
end
end
// Destination clock domain
always @(posedge d_clk or negedge d_rstn) begin
if (!d_rstn) begin
d_syncer <= 2'd0;
end else begin
d_syncer <= {d_syncer[0],async_req};
end
end
always @(posedge d_clk or negedge d_rstn) begin
if (!d_rstn) begin
req_d3 <= 1'd0;
d_vld <= 1'd0;
end else begin
req_d3 <= req_d2;
d_vld <= req_d2 & (~req_d3);
end
end
endmodule
方法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
)(
input wire s_clk ,
input wire s_rstn ,
input wire [DW-1:0] s_din ,
input wire s_vld , // pulse input
input wire d_clk ,
input wire d_rstn ,
output reg [DW-1:0] d_dout ,
output reg d_vld ,// pulse output
output wire 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 domain
always @(posedge s_clk or negedge s_rstn) begin
if (!s_rstn) begin
s_syncer <= 2'd0;
end else begin
s_syncer <= {s_syncer[0],async_ack};
end
end
always @(posedge s_clk or negedge s_rstn) begin
if (!s_rstn) begin
async_dat <= {DW{1'd0}};
end else if(s_vld && (!active))begin
async_dat <= s_din;
end
end
always @(posedge s_clk or negedge s_rstn) begin
if (!s_rstn) begin
async_req <= 1'd0;
end else if(ack_d2)begin
async_req <= 1'd0;
end else if(s_vld)begin
async_req <= 1'd1;
end
end
// Destination clock domain
always @(posedge d_clk or negedge d_rstn) begin
if (!d_rstn) begin
d_syncer <= 2'd0;
end else begin
d_syncer <= {d_syncer[0],async_req};
end
end
always @(posedge d_clk or negedge d_rstn) begin
if (!d_rstn) begin
d_dout <= {DW{1'd0}};
end else if(req_d2)begin
d_dout <= async_dat;
end
end
always @(posedge d_clk or negedge d_rstn) begin
if (!d_rstn) begin
req_d3 <= 1'd0;
d_vld <= 1'd0;
end else begin
req_d3 <= req_d2;
d_vld <= req_d2 & (~req_d3);
end
end
endmodule
testbench:
`timescale 1ns/1ps
module 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 )
);
task automatic 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;
end
endtask //automatic
task automatic wait_busy;
begin
@(posedge s_clk);#0;
while (active) begin
@(posedge s_clk);#0;
end
end
endtask //automatic
task automatic loop_test;
input integer n;
integer i;
reg [63:0]random_dat;
begin
for (i = 0;i<n ;i=i+1 ) begin
random_dat = {$random} % (2**DW);
gen_trans(random_dat[DW-1:0]);
wait_busy();
if (buffer != random_dat[DW-1:0]) begin
$display("error missmatch, send:0x%x, recv:0x%x",random_dat[DW-1:0],buffer);
#100
$finish;
end
$display("%0d/%0d 0x%0x==0x%0x",i+1,n,random_dat[DW-1:0],buffer);
end
$display("FULL PASS %0d",n);
end
endtask //automatic
always @(posedge d_clk) begin
buffer <= (d_vld)?d_dout:buffer;
end
initial begin
ts = 3.333;
td = 10.567;
#3000;
ts = 10.567;
td = 3.333;
while ( clkstp!=1 ) begin
#3000;
a = {$random}%300;
b = {$random}%300;
ts = a<10 ?10:a;
td = b<10 ?10:b;
end
end
initial begin
// $dumpfile("wave.vcd"); //生成的vcd文件名称
// $dumpvars(0, databridge_async_tb); //tb模块名称
clkstp = 0;
#10 s_rstn = 1;
#10 d_rstn = 1;
#10;
@(posedge s_clk);
loop_test(10000);
#1000;
clkstp = 1;
$finish;
end
endmodule
方法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,数据提前输出,可能不利于时序收敛)
#(
parameter integer DATA_WIDTH = 16 ,
parameter integer ADDR_WIDTH = 8 //深度只能是2**ADDR_WIDTH
)
(
input wire wr_rst ,
input wire wr_clk ,
input wire wr_ena ,
input wire[DATA_WIDTH-1:0] wr_dat ,
output wire wr_full ,
input wire rd_rst ,
input wire rd_clk ,
input wire rd_ena ,
output wire[DATA_WIDTH-1:0] rd_dat ,
output wire 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 or posedge wr_rst) begin
if (wr_rst) begin
rdptr_gray_r0 <= 'd0;
rdptr_gray_r1 <= 'd0;
end else begin
rdptr_gray_r0 <= rdptr_gray;
rdptr_gray_r1 <= rdptr_gray_r0;
end
end
always @(posedge rd_clk or posedge rd_rst) begin
if (rd_rst) begin
wrptr_gray_r0 <= 'd0;
wrptr_gray_r1 <= 'd0;
end else begin
wrptr_gray_r0 <= wrptr_gray;
wrptr_gray_r1 <= wrptr_gray_r0;
end
end
always @(posedge wr_clk or posedge wr_rst) begin
if (wr_rst) begin
mem[0] <= 'd0;
wrptr <= 'd0;
end else if (wr_ena&&(!wr_full)) begin
mem[wraddr] <= wr_dat;
wrptr <= wrptr + 1'd1;
end else begin
mem[wraddr] <= mem[wraddr];
wrptr <= wrptr;
end
end
always @(posedge rd_clk or posedge rd_rst) begin
if (rd_rst) begin
rdptr <= 'd0;
end else if (rd_ena&&(!rd_empty)) begin
rdptr <= rdptr + 1'd1;
end else begin
rdptr <= rdptr;
end
end
assign rd_dat = mem[rdaddr];
endmodule