FPGA|跨时钟域

一、定义

模块之间有数据交互但两个模块不是同一个时钟驱动。根据clk1与clk2是否为同步时钟,分为跨同步时钟域和跨异步时钟域。。根据信号是控制信号还是数据信号可以分为控制信号传输和数据信号的传输。


解释

同步时钟与异步时钟

同步时钟:(1)同频同相位(2)同频不同相位,但相位固定(3)不同频,但存在整数倍的关系

异步时钟:两时钟信号完全没有关系。


二、单比特数据

1、跨同步时钟域:

(1) 同频同相:

该情况只要满足普通的同步电路设计的要求(建立和保持时间,信号的传输延时要在一定范围内)即可。一般不需要同步器。

(2) 同频不同相:

相位为固定值,允许的传输时间小于一个时钟周期。但是只要满足控制信号的输出是在clk1的控制下进行翻转的,因此只要满足同步设计的一般要求(满足建立时间和保持时间,控制信号的传输延时要在一定范围内),就可以满足时序,不会出生亚稳态,也不会出现数据丢失的情况,因此一般不需要同步器。

(3) 不同频:

慢->快,打拍即可。快->慢,展宽后,打拍。(参考下文)

2、跨异步时钟域

当从慢时钟域到快时钟域时,只需要打两拍防止亚稳态即可。

注:单比特信号从慢速时钟域同步到快速时钟域需要使用打两拍的方式消除亚稳态,第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为70%~80%左右,第二级寄存器可以稳定输出的概率为99%左右,再后面改善就不明显了,所以数据进来后一般选择打两拍即可。

当从快时钟域到慢时钟域时,如图所示,慢时钟域可能根本就采集不到信号。

1、因此需要想办法将信号进行展宽,可以将脉冲信号转化为沿信号。当检验到信号电平为高时,就对沿信号进行取反,使得的第一个脉冲变为沿信号的上升沿,第二个脉变为沿信号的下降沿,后面如果信号还有脉冲依然是变为沿信号的上升沿和下降沿。

2、对沿信号进行打拍两次,获得稳定的信号。

3、最后,再将沿信号转化为脉冲信号。使用的方法为边沿检测。为了还原出脉冲,还需要再打一拍。


    
    
  1. 上升沿检测:对数据进行寄存,若前值为低,后值为高则为上升沿。
  2. 方法一:与逻辑实现
  3. //-----------------------------------------------
  4. always@(posedge sys_clk ornegedge sys_rst_n)
  5. if(sys_rst_n ==1'b0)
  6. podge <=1'b0;
  7. else if((data)&&(~data_reg)) //核心逻辑
  8. podge <=1'b1;
  9. else
  10. podge <=1'b0;
  11. //-----------------------------------------------
  12. 方法二:或逻辑实现
  13. //-----------------------------------------------
  14. always@(posedge sys_clk ornegedge sys_rst_n)
  15. if(sys_rst_n ==1'b0)
  16. podge <=1'b0;
  17. else if((~data)||(data_reg)) //核心逻辑
  18. podge <=1'b0;
  19. else
  20. podge <=1'b1;
  21. //-----------------------------------------------
  22. 下降沿检测
  23. //-----------------------------------------------
  24. always@(posedge sys_clk ornegedge sys_rst_n)
  25. if(sys_rst_n ==1'b0)
  26. nedge <=1'b0;
  27. else if((~data)&&(data_reg)) //核心逻辑
  28. nedge <=1'b1;
  29. else
  30. nedge <=1'b0;
  31. //-----------------------------------------------

注:对于一个信号的展宽,也可以在快时钟域打拍,然后三信号相与。

三、多比特数据

1、使用握手信号进行跨时钟域数据传输

使用握手信号xack和yreq,系统X发给系统Y,下面是使用握手信号传输数据的例子:

1)发送器系统X将数据放到数据总线上并发出xreq请求信号,表示有效数据已经发送到接收器系统Y的数据总线上

2)把xreq信号同步到接收器的时钟域yclk上。

3)接收器在识别xreq同步信号yreq2后,锁存数据总线上的信号

4)接收器发出确认信号yack,表示其已经接受了数据

5)接收器发出的yack信号同步到发送时钟xclk上

6)发送器在识别同步的ack信号后,将下一个数据放到数据总线上

握手信号的时序图如下所示:

注:数据应该在发送时钟域内稳定至少两个时钟上升沿,请求信号xreq的宽度应该超过两个上升沿时钟,否则从高速时钟的低速时钟域传递可能无法捕捉到该信号。缺点是是延迟太大(相比于FIFO)。


    
    
  1. `timescale 1ns/1ns
  2. module data_driver(
  3. input clk_a,
  4. input rst_n,
  5. input data_ack,
  6. output reg [3:0]data,
  7. output reg data_req
  8. );
  9. reg data_ack_reg_1;
  10. reg data_ack_reg_2;
  11. reg [9:0] cnt;
  12. always @ (posedge clk_a or negedge rst_n)
  13. if (!rst_n)
  14. begin
  15. data_ack_reg_1 <= 0;
  16. data_ack_reg_2 <= 0;
  17. end
  18. else
  19. begin
  20. data_ack_reg_1 <= data_ack;
  21. data_ack_reg_2 <= data_ack_reg_1;
  22. end
  23. always @ (posedge clk_a or negedge rst_n)
  24. if (!rst_n)
  25. begin
  26. data <= 0;
  27. end
  28. else if(data_ack_reg_1 && !data_ack_reg_2)
  29. begin
  30. data <= data+1;
  31. end
  32. else begin
  33. data <= data;
  34. end
  35. //同时在data_ack有效之后,开始计数五个时钟,之后发送新的数据,也就是再一次拉高data_req.
  36. always @ (posedge clk_a or negedge rst_n)
  37. if (!rst_n)
  38. cnt <= 0;
  39. else if (data_ack_reg_1 && !data_ack_reg_2)
  40. cnt <= 0;
  41. else if (data_req)
  42. cnt <= cnt;
  43. else
  44. cnt <= cnt+1;
  45. always @ (posedge clk_a or negedge rst_n)
  46. if (!rst_n)
  47. data_req <= 0;
  48. else if (cnt == 3'd4)
  49. data_req <= 1'b1;
  50. else if (data_ack_reg_1 && !data_ack_reg_2)
  51. data_req <= 1'b0;
  52. else
  53. data_req <= data_req;
  54. endmodule
  55. module data_receiver(
  56. input clk_b,
  57. input rst_n,
  58. output reg data_ack,
  59. input [3:0]data,
  60. input data_req
  61. );
  62. reg [3:0]data_in_reg;
  63. reg data_req_reg_1;
  64. reg data_req_reg_2;
  65. always @ (posedge clk_b or negedge rst_n)
  66. if (!rst_n)
  67. begin
  68. data_req_reg_1 <= 0;
  69. data_req_reg_2 <= 0;
  70. end
  71. else
  72. begin
  73. data_req_reg_1 <= data_req;
  74. data_req_reg_2 <= data_req_reg_1;
  75. end
  76. always @ (posedge clk_b or negedge rst_n)
  77. if (!rst_n)
  78. data_ack <= 0;
  79. else if (data_req_reg_1)
  80. data_ack <= 1;
  81. else data_ack <=0 ;
  82. always @ (posedge clk_b or negedge rst_n)
  83. if (!rst_n)
  84. data_in_reg <= 0;
  85. else if (data_req_reg_1 && !data_req_reg_2)
  86. data_in_reg <= data;
  87. else data_in_reg <= data_in_reg ;
  88. endmodule

2、FIFO

同步FIFO常用于同步时钟的数据缓存,异步FIFO常用于跨时钟域的数据信号的传递。

a、双端口RAM


    
    
  1. module dual_port_RAM #(parameter DEPTH = 16,
  2. parameter WIDTH = 8)(
  3. input wclk
  4. ,input wenc
  5. ,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
  6. ,input [WIDTH-1:0] wdata //数据写入
  7. ,input rclk
  8. ,input renc
  9. ,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
  10. ,output reg [WIDTH-1:0] rdata //数据输出
  11. );
  12. reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
  13. always @(posedge wclk) begin
  14. if(wenc)
  15. RAM_MEM[waddr] <= wdata;
  16. end
  17. always @(posedge rclk) begin
  18. if(renc)
  19. rdata <= RAM_MEM[raddr];
  20. end
  21. endmodule

b、FIFO地址设计

我们知道FIFO中是没有地址线的,地址靠自身计数器自加1来控制,那么我们很容易想到把外部输入信号wr_addr和rd_addr换成内部信号并用计数器来控制其自加,计数器加满之后直接清零,从0重新开始写/读,循环往复。由于写端和读端的时钟速率不同,就会有快慢的问题,那么就出现了一个问题,以地址3为例,写入的数据还没有被读出,又被新的数据覆盖了,造成数据丢失;或者写入的数据已经被读出,新的数据还没有写进来,地址3的老数据又被读了一遍,造成数据重复。

上图是本异步FIFO的结构示意图。蓝色区域是读时钟域,黄色部分是写时钟域。异步FIFO主要包含四部分:读写地址发生器、格雷码的产生与打拍、空满信号发生器以及RAM。

输入:winc, rcin, wdata

输出:rdata, wfull, rempty

读写使能:rcin\wcin,读写数据:rdata\wdata,空满信号:wfull\rempty。

当读使能rinc==1且FIFO非空rempty==0时,读地址在读时钟rclk下自增;当写使能winc==1且FIFO非满wfull==0时,写地址在写时钟wclk下自增。这两种地址可以直接传入RAM模块。


    
    
  1. wire wenc, renc;
  2. wire [$clog2(DEPTH)-1:0] waddr, raddr;
  3. assign wenc = winc&!wfull;
  4. assign renc = rinc&!rempty;
  5. always@(posedge wclk or negedge wrstn) begin
  6. if(~wrstn)
  7. waddr_bin <= 0;
  8. else
  9. waddr_bin <= wenc? waddr_bin+1: waddr_bin;
  10. end
  11. always@(posedge rclk or negedge rrstn) begin
  12. if(~rrstn)
  13. raddr_bin <= 0;
  14. else
  15. raddr_bin <= renc? raddr_bin+1: raddr_bin;
  16. end

c、格雷码打拍

为了产生空满信号,需要比较读写地址的大小。但两个地址是由不同的时钟控制的,需要先做跨时钟域处理才能比较。所以使用了打两拍。又因为FIFO的读写地址是连续变化的,采用格雷码可以有效减少相邻地址的bit变化,进一步降低打拍过程产生亚稳态的可能性。


    
    
  1. reg [$clog2(DEPTH):0] waddr_bin, raddr_bin;
  2. wire [$clog2(DEPTH):0] waddr_gray, raddr_gray;
  3. reg [$clog2(DEPTH):0] waddr_gray1, raddr_gray1;
  4. reg [$clog2(DEPTH):0] waddr_gray2, raddr_gray2;
  5. reg [$clog2(DEPTH):0] waddr_gray3, raddr_gray3;
  6. assign waddr_gray = waddr_bin^(waddr_bin>>1);
  7. assign raddr_gray = raddr_bin^(raddr_bin>>1);
  8. assign waddr = waddr_bin[$clog2(DEPTH)-1:0];
  9. assign raddr = raddr_bin[$clog2(DEPTH)-1:0];
  10. always@(posedge rclk or negedge rrstn) begin
  11. if(~rrstn)
  12. raddr_gray1 <= 0;
  13. else
  14. raddr_gray1 <= raddr_gray;
  15. end
  16. always@(posedge rclk or negedge rrstn) begin
  17. if(~rrstn) begin
  18. waddr_gray2 <= 0;
  19. waddr_gray3 <= 0;
  20. end
  21. else begin
  22. waddr_gray2 <= waddr_gray1;
  23. waddr_gray3 <= waddr_gray2;
  24. end
  25. end
  26. always@(posedge wclk or negedge wrstn) begin
  27. if(~wrstn) begin
  28. raddr_gray2 <= 0;
  29. raddr_gray3 <= 0;
  30. end
  31. else begin
  32. raddr_gray2 <= raddr_gray1;
  33. raddr_gray3 <= raddr_gray2;
  34. end
  35. end

d、空满信号发生器

当读写地址的格雷码仅有最高的2bit不同时,FIFO满;当读写地址完全相同时,FIFO空。


    
    
  1. assign wfull = (waddr_gray1=={~raddr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_gray3[$clog2(DEPTH)-2:0]});
  2. assign rempty = (raddr_gray1==waddr_gray3);

格雷码地址比`waddr`和`raddr`多出1bit用于判断是否满。当读地址和写地址在不同圈时,格雷码的最高位也会不同。再根据格雷码的对称性,此时如果次高位不同而其他位相同,说明读写地址的自然二进制码是相通的。这体现出了格雷码的另一个优势,就是方便判断是否套圈和满信号。

参考:题解 | #异步FIFO#_牛客博客 (nowcoder.net)

加油站| 单比特信号跨时钟域问题详解(大疆FPGA逻辑岗A卷) (qq.com)

跨时钟域信号传输(一)——控制信号篇 - IC_learner - 博客园 (cnblogs.com)

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值