参考csdn:
(2)FPGA学习笔记——跨时钟域(CDC)设计之多bit信号同步_多bit同步 skew约束-CSDN博客
参考其他:
(1)【数字IC】异步FIFO设计详解(含源码) - 知乎 (zhihu.com)
1.亚稳态问题
首先是关于在学习跨时钟域上一篇单bit数据跨时钟域的最后提出的问题,对我当时的代码来说,数据打一拍就足够了,多打拍反而会导致数据传输延后一个周期,不能及时接收数据;但是网上查到的代码都选择了打两拍,因此这个问题存疑。
这个问题的正解确实是为了避免亚稳态的出现。
首先来解释一下亚稳态这个概念:
亚稳态是指在设计正常运行期间的某个时间点,信号在一段时间内不会呈现稳定的0 或1 状态。在多时钟设计中,由于建立时间和保持时间可能不满足要求,所以无法避免亚稳态,但可以减小亚稳态的不利影响。
而解决亚稳态影响,目前设计最主流、也是最常见的方法就是双触发器同步器(打两拍),同步器图示和经过之后的波形如下图所示。对于大多数同步应用,两个触发器同步器足以消除所有可能的亚稳态,它可以避免将第一级的错误继续传递到第二级,但是在保证数据正确传递的同时,必要的牺牲了一点传输的实时性。而我的设计中因为要讲究数据快速传递,所以只打一拍也是足够了的。
2.多bit跨时钟域和单bit的不同
在时钟域之间传递多bit信号时,简单的同步器并不能保证数据的安全传输。
工程师在进行多时钟设计时经常犯的一个错误是将同一事务中所需的多个 CDC 位从一个时钟域传递到另一个时钟域,而忽视了 CDC 位同步采样的重要性。
问题是同步到一个时钟的多bit信号会经历小的数据变化偏斜(skews),偶尔会在第二个时钟域的不同时钟上升沿上采样。即使我们可以完美地控制和匹配多个信号的走线长度,上升和下降时间的差异以及芯片上的工艺变化可能会引入足够的偏斜,从而导致对原本精心匹配的走线的采样失败。
总结就是:多bit信号的CDC问题主要来自于到达目的时钟域的时间无法控制一致。如果简单将多bit信号拆分成好几个单bit信号,分别采用寄存器来进行传输,那么由于每个寄存器的位置不同,布局布线会导致每个数据到达下一级寄存器的延时不同,可能会采样的中间变化的任何值。
3.多bit跨时钟域的几种解决方法
(1)多bit信号合并——字面意思,将多bit信号合并成单bit信号,一般只适用于多个控制信号且控制信号之间有一定逻辑关系。合并之后再采用单bit跨时钟域方式进行传递。这种方法最简单,但是不常见,也没什么要多说明的。
(2)MUX同步器。
(3)异步FIFO。
(4)格雷码编码处理——有局限性,只有在数据在相数值间连续变化的情况下才有用,不适用于大多数信号传输或者数据传输的情况,且这个方法在异步fifo中有被包含到,就不单独说明。
除此之外,我查到的还有握手协议、多周期路径(Multi-Cycle path,MCP)同步法,不过感觉多bit来说,掌握其中两种比较有代表性的就足够了,因此这两种方法就暂时不详细看了。
下面就2、3方法进行详细讲述。
4. MUX同步器解决多bit跨时钟域
使用MUX同步器要求被同步的数据跟随一个使能信号,当使能信号有效时,数据才会被同步。MUX同步器应用在慢到快的CDC时,数据长度应该至少为m+1个目标时钟周期,m指的是同步器数量,此处为m=2。
具体实现代码如下所示
module mux_sync(clk_a, clk_b, arst_n, brst_n, data_in, data_out, data_en);
/********************参数定义********************/
/*********************IO 说明********************/
input wire clk_a ;//A时钟域时钟(发送域)
input wire clk_b ;//B时钟域时钟(接收域)
input wire arst_n ;//A时钟异步复位
input wire brst_n ;//B时钟异步复位
input wire data_en ;//A时钟域有效信号,高电平有效
input wire [3:0] data_in ;//A时钟域数据输入
output reg [3:0] data_out;//B时钟域数据输出
/********************** 内部信号声明 **********************/
reg [3:0] data_in_reg; //输入数据暂存寄存器
reg a_data_en;
reg b_data_en;
reg b_date_en_reg;
/*************************功能定义*************************/
/*数据暂存*/
always@(posedge clk_a or negedge arst_n)
begin
if(!arst_n)
data_in_reg <= 1'b0;
else
data_in_reg <= data_in;
end
//使能信号在A时钟域用一个D触发器暂存
always@(posedge clk_a or negedge arst_n)
begin
if(!arst_n)
a_data_en <= 1'b0;
else
a_data_en <= data_en;
end
//使能信号在B时钟域打两拍(两级电平同步器)
always@(posedge clk_b or negedge brst_n)
begin
if(!brst_n)
begin
b_date_en_reg<= 1'b0;
b_data_en <= 1'b0;
end
else
begin
b_date_en_reg <= a_data_en;
b_data_en <= b_date_en_reg;
end
end
/*根据同步到B时钟域的使能信号b_data_en,更新输出。*/
always@(posedge clk_b or negedge brst_n)
begin
if(!brst_n)
data_out <= 4'b0;
else
begin
data_out <= b_data_en ? data_in_reg : data_out;
end
end
endmodule
上述代码是针对正常慢时钟域到快时钟域的,但如果B时钟域(接收域)的时钟频率时A时钟域(发送域)的时钟频率的几十倍,甚至上百倍。且data_en为脉冲信号时,data_en在在快时钟域打完几拍的时间相对于慢时钟域是非常短暂的,此时慢时钟域中的多bit数据信号可能还处于冒险中间态,则此时选通进入快时钟域的数据就是“毛刺”。
针对这种情况,可以在接收域使用边沿同步器,检测data_en的下降沿,以保证此时的多比特数据一定是稳定的。
module mux_sync(clk_a, clk_b, arst_n, brst_n, data_in, data_out, data_en);
/********************参数定义********************/
/*********************IO 说明********************/
input wire clk_a ;//A时钟域时钟(发送域)
input wire clk_b ;//B时钟域时钟(接收域)
input wire arst_n ;//A时钟异步复位
input wire brst_n ;//B时钟异步复位
input wire data_en ;//A时钟域有效信号,高电平有效
input wire [3:0] data_in ;//A时钟域数据输入
output reg [3:0] data_out;//B时钟域数据输出
/********************** 内部信号声明 **********************/
reg [3:0] data_in_reg; //输入数据暂存寄存器
reg a_data_en;
reg b_data_en;
reg b_date_en_reg;
reg edge_reg;
wire edge_flag;
/*************************功能定义*************************/
/*数据暂存*/
always@(posedge clk_a or negedge arst_n)
begin
if(!arst_n)
data_in_reg <= 1'b0;
else
data_in_reg <= data_in;
end
//使能信号在A时钟域用一个D触发器暂存
always@(posedge clk_a or negedge arst_n)
begin
if(!arst_n)
a_data_en <= 1'b0;
else
a_data_en <= data_en;
end
//使能信号在B时钟域打两拍(两级电平同步器)
always@(posedge clk_b or negedge brst_n)
begin
if(!brst_n)
begin
b_date_en_reg<= 1'b0;
b_data_en <= 1'b0;
end
else
begin
b_date_en_reg <= a_data_en;
b_data_en <= b_date_en_reg;
end
end
//下降沿检测
always@(posedge clk_b or negedge brst_n)
begin
if(!brst_n)
edge_reg <= 1'b0;
else
edge_reg <= b_data_en;
end
assign edge_flag = ~b_data_en & edge_reg;
/*根据同步到B时钟域的使能信号b_data_en,更新输出。*/
always@(posedge clk_b or negedge brst_n)
begin
if(!brst_n)
data_out <= 4'b0;
else
data_out <= edge_flag ? data_in_reg : data_out;
end
endmodule
这个方法感觉很熟悉,和单bit数据跨时钟域处理慢时钟域到快时钟域避免重复采样的方法是基本一致的。只不过不同的是,单bit采用的是读取上升沿,而多bit采用的是读取下降沿,要解决的是输入的多bit数据还没有全部传完,输出就完成打两拍开始采集data,因此选择采样下降沿,保证采集到数据的时候已经完全传输完成,这点没有问题。
上述这两种代码都是处理慢到快时钟域的,对于快到慢时钟域来说,目前我查到的资料说是保证输入的使能信号和数据周期足够长就可以了。目前先存疑一下,感觉也可以通过单bit方法来实现。
总结,对于MUX同步器来说,其实本质上还是和单bit有相似之处的,就是将valid信号按照单bit的信号进行处理。
5.异步fifo
异步fifo可以说是解决多bit跨时钟域最常见的方法了,它相当于将源时钟数据缓存起来,然后目标时钟从缓存中取数据,传输速度较快。
异步FIFO其主要由六部分组成,整体结构如下所示。
(1)写数据控制模块:生成写地址指针、生成写数据使能;
(2)读数据控制模块:生成写地址指针、生成写数据使能;
(3)格雷码转换模块:将二进制码转换为格雷码;
(4)格雷码同步模块:将格雷码同步到目标时钟域;
(5)空满信号生成模块:通过读写指针经格雷码转换、同步后进行比较,生成full & empty信号;
(6)数据存储模块:利用双口ram实现数据存储。
关于异步fifo的概念并不难理解,就是一个数据的储存和提取的过程。它的难点主要还是在手搓代码上,看评论我找的链接当中代码还是存在一些小问题,具体代码看那篇知乎链接,我就不再复制粘贴一遍了。
标签:en,CDC,信号,bit,data,reg,时钟 From: https://blog.csdn.net/2401_83129283/article/details/136732392