cdc跨时钟域处理-结绳握手法

参考文档

https://blog.csdn.net/u011412586/article/details/10009761

前言

对于信号需要跨时钟域处理而言,最重要的就是确保数据能稳定的传送到采样时钟域。

普通的cdc处理方法需要关注时钟域速度的异同,即分慢时钟域到快时钟域、快时钟域到慢时钟域、相位关系等问题,会让人瞬间爆炸。

那么,是否有一种相对稳定,又无需关注传送时钟域和接收时钟域两者时钟速度、相位关系的数据传递方式呢?

这里仿真验证握手结绳法,自己想的,不知道跟结绳法是不是一致的。。。。。。

思考方式为:要绝对的保证数据被接收,自然是握手应答机制。

 

流程

首先考虑亚稳态说明:对于异步信号的采集,不可避免的会有亚稳态,但亚稳态的恢复时间一般为1个时钟或者两个时钟,同样其不会是0、1的中间值,而是一个开发者无法确定的定值,同时你的时钟频率越高,亚稳态发生的概率就越大。

网上感觉都说的不明就里,对于简单的使用多级触发器,然后使用最高的2bit进行边沿检测,目测是不行的。

可以理解为仿真中的x值,但数字电路的本质上是有值的,要么0,要么1。

所以如果是检测一个信号的上升沿,即使采到亚稳态,那就分两种情况:

(1)边沿比较陡峭,时钟只采到1次亚稳态,那么边沿寄存器的值就可能为:00、01。检测到00,下一次就会采到01。如果是检测到01,就是想要的沿。

(2)边沿一点都不陡峭,导致时钟采到两次甚至更多次的亚问题,那这就没得玩了。因为你不知道当前是00\01\10\11,这就可能导致你的后续逻辑完蛋了。

当然这里第二种情况不予考虑,FPGA内部不太可能出现这么糟糕的上升沿,除非你的时钟过快,在时钟沿到来前,亚稳态还没有恢复过来。

极端的处理方式是(本质上是滤波):用更多位的寄存器,比如8bit,当检测到为8'b0000_1111,那就是上升沿啦,虽然这阔以绝对可靠的检测到边沿,但检测出沿的所用的时间更长,同时需要保证前级信号的高低电平长度,否则就被滤波了。。。。。。

所以,信号的边沿陡峭性是很重要的,专业术语叫做:上升时间、下降时间。

言归正传,那么握手结绳法是咋样的操作呢?

如下图所示:有点复杂

如图所示,则操作流程为:上述方法应该是支持多bit数据跨时钟域的,这里的多bit说的是FPGA内部数据,pulse_clk1表示发送数据使能,clk1检测到使能则拉高pulse_delay_clk1开始结绳。在clk2中,检测到clk1中的结绳信号的上升沿则产生pulse_clk2,clk2检测到pulse_clk2则启动clk2中的结绳pulse_delay_clk2,在clk1中检测到clk2中的结绳信号为高则解掉clk1的结绳。在clk2中检测到clk1的结绳拉低,则解绳clk2中的结绳信号pulse_delay_clk2,完成传输。

待传送数据在clk2的结绳上升沿被采样。

busy信号为clk1中的信号,在clk1的发送数据使能到来则拉高,在pulse_delay_clk2的下降沿则拉低。

可以看到,这种方式只适合少量的慢速的数据传输。

 

源代码

 

`timescale 1ns/1ps
module cdc_test (
    input               i_clk1               ,
    input               i_clk2               ,
    // input               i_rst_n              ,
    input               i_en_clk1            ,
    input     [15:0]    i_data_clk1          ,
    output              o_busy_clk1          ,
    output              o_valid_clk2         ,
    output    [15:0]    o_data_clk2             
);
////clk1
////clk1的发送使能边沿检测
reg [1:0] r_en_clk1_edge = 2'b00;
always @(posedge i_clk1)
begin
    r_en_clk1_edge <= {r_en_clk1_edge[0],i_en_clk1};
end
reg r_en_clk2 = 1'b0;clk2的使能边沿检测
reg [1:0] r_en_clk2_edge = 2'b00;
always @(posedge i_clk1)
begin
    r_en_clk2_edge <= {r_en_clk2_edge[0],r_en_clk2};
end
reg r_en_expand = 1'b0; clk1的使能延长
always @(posedge i_clk1)
begin
    if (r_en_clk1_edge == 2'b01) 
        r_en_expand <= 1'b1;
    else if (r_en_clk2_edge == 2'b01)
        r_en_expand <= 1'b0;
end
reg r_busy_clk1 = 1'b0; 传输忙碌指示,忙碌的时候clk1的数据不可发生改变
always @(posedge i_clk1)
begin
    if (r_en_clk1_edge == 2'b01)
        r_busy_clk1 <= 1'b1;
    else if (r_en_clk2_edge == 2'b10)
        r_busy_clk1 <= 1'b0;
end

////clk2
reg [1:0] r_en_expand_edge = 2'b00; clk1的扩展后的使能边沿检测
always @(posedge i_clk2)
begin
    r_en_expand_edge <= {r_en_expand_edge[0],r_en_expand};
end

always @(posedge i_clk2) ////clk2中的使能
begin
    if (r_en_expand_edge == 2'b01)
        r_en_clk2 <= 1'b1;
    else if (r_en_expand_edge == 2'b10)
        r_en_clk2 <= 1'b0;
end

reg r_valid_clk2 = 1'b0;
always @(posedge i_clk2)
begin
    if (r_en_expand_edge == 2'b01) 上升沿表示数据有效
        r_valid_clk2 <= 1'b1;
    else 
        r_valid_clk2 <= 1'b0;
end
reg [15:0] r_data_clk2 = 16'd0;
always @(posedge i_clk2)
begin
    if (r_en_expand_edge == 2'b01) 上升沿刷新数据
        r_data_clk2 <= i_data_clk1;
end

////信号输出

assign o_busy_clk1  = r_busy_clk1;
assign o_valid_clk2 = r_valid_clk2;
assign o_data_clk2  = r_data_clk2;


endmodule // end the cdc_test model

 

仿真看看。

可以看到数据正确传输,不管是快到慢还是慢到快。

以后再板级测试啦。

以上。

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嗨,很高兴能够回答你的问题。以下是一个使用 RecyclerView 实现循环滚动的示例代码: ```java public class CircularScrollActivity extends AppCompatActivity { private RecyclerView mRecyclerView; private LinearLayoutManager mLayoutManager; private CircularRecyclerViewAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_circular_scroll); // 初始化 RecyclerView mRecyclerView = findViewById(R.id.circular_recycler_view); mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); mRecyclerView.setLayoutManager(mLayoutManager); mAdapter = new CircularRecyclerViewAdapter(this); mRecyclerView.setAdapter(mAdapter); // 为 RecyclerView 添加滚动监听器 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); // 如果 RecyclerView 停止滚动,则计算当前可见的第一个和最后一个条目的位置 if (newState == RecyclerView.SCROLL_STATE_IDLE) { int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); int lastVisibleItem = mLayoutManager.findLastVisibleItemPosition(); // 如果当前可见的最后一个条目是列表中的最后一个条目,则滚动到列表中的第一个条目 if (lastVisibleItem == mAdapter.getItemCount() - 1) { mRecyclerView.scrollToPosition(0); } // 如果当前可见的第一个条目是列表中的第一个条目,则滚动到列表中的最后一个条目 if (firstVisibleItem == 0) { mRecyclerView.scrollToPosition(mAdapter.getItemCount() - 1); } } } }); } } ``` 这个示例代码中,我们创建了一个 LinearLayoutManager ,并将其水平滚动方向设置为 HORIZONTAL 。我们还为 RecyclerView 添加了一个滚动监听器,当 RecyclerView 停止滚动时,我们计算当前可见的第一个和最后一个条目的位置。如果最后一个可见条目是列表中的最后一个条目,则将 RecyclerView 滚动到列表中的第一个条目。如果第一个可见条目是列表中的第一个条目,则将 RecyclerView 滚动到列表中的最后一个条目。这样,我们就实现了循环滚动的效果。希望这个示例代码能够对你有帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值