在Verilog中用稍微倾斜的时钟同步两个状态机的正确方法

在Verilog中用稍微倾斜的时钟同步两个状态机的正确方法,verilog,Verilog,我正在Verilog中为ADC实现一个接收器。每21个时钟周期后获得一个样本 接收器为ADC生成控制信号和占空比采样时钟。ADC按顺序发回数据,但为了考虑延迟,它还发回占空比采样时钟的倾斜匹配副本。该时钟用于对数据进行时钟输入 该代码应适用于两个时钟之间的零延迟以及更大的延迟。(但延迟不会超过几个时钟周期) 我不知道最好的方法是什么,因为: 综合禁止将变量写入具有(可能)不同时钟的不同始终@(posedge…块中 数据中的时钟部分没有真正的时钟(它是占空比的!),因此它无法自行维持状态。它需要以

我正在Verilog中为ADC实现一个接收器。每21个时钟周期后获得一个样本

接收器为ADC生成控制信号和占空比采样时钟。ADC按顺序发回数据,但为了考虑延迟,它还发回占空比采样时钟的倾斜匹配副本。该时钟用于对数据进行时钟输入

该代码应适用于两个时钟之间的零延迟以及更大的延迟。(但延迟不会超过几个时钟周期)

我不知道最好的方法是什么,因为:

  • 综合禁止将变量写入具有(可能)不同时钟的不同
    始终@(posedge…
    块中
  • 数据中的时钟部分没有真正的时钟(它是占空比的!),因此它无法自行维持状态。它需要以某种方式从控制FSM获取其处于哪个周期的信息
  • 一旦读取了采样值,就需要将其传输回原始的、无偏差的时钟域,以便进一步处理
  • 这显示了我的方法的一个最小示例:

    // Used to synchronize state between domains
    reg sync_cnv = 0; // toggled by TX side when new sampling cycle starts
    reg sync_sdo = 0; // synchronized by the RX side
    reg reset_rx = 0; // Notify RX side of a global reset
    reg reset_rx_ack = 0; // acknowledgement thereof
    
    reg [4:0] state = 0;
    reg [4:0] nextState = 0;
    always @(posedge clk) begin
        if (reset == 1) begin // global reset
            state <= 0;
            sync_cnv <= 0;
            reset_rx <= 1;
        end else begin
            state <= nextState;
    
            // new sampling cycle starts. Inform RX logic
            if (state == 0) begin
                sync_cnv <= ~sync_cnv;
            end
            // If RX acknowledges the reset, we can turn if off again
            if (reset_rx_ack == 1) begin
                reset_rx <= 0;
            end
        end
    end
    
    // Normally, would generate all kinds of status/control signal for the ADC here
    always @(*) begin
        if (state == 20) begin
            nextState = 0;
        end else begin
            nextState = state + 1;
        end
    end
    

    这段代码在模拟中完美地工作(有无扭曲)。它大部分时间也在FPGA上工作。但是,重置后的数据值是不可预测的:大多数数据以0开始(如预期),但有时以1和或任意数字开始(可能是重置前的最后一个周期)。

    在时钟域之间使用NRZ信号是已知的方法。但是你没有一个真正的同步器。为了安全地在时钟之间切换,需要两个寄存器和第三个用于边缘检测的寄存器:

    // Clock domain 1:
       nrz <= ~nrz;
    
    // Clock domain 2:
    reg nrz_meta,nrz_sync,nrz_old;
    ....
       nrz_meta <= nrz;
       nrz_sync <= nrz_meta; 
       // nrz_sync is the signal you can safely use!
       // Do NOT use nrz_sync ^ nrz_meta, it is not reliable!
    
       nrz_old <= nrz_sync; // required to 'find' an edge
       if (nrz_old ^ nrz_sync)
       begin
          // Process data 
       ....
    
    //时钟域1:
    
    nrz你为什么说没有“真正的同步机”?查看最后一个
    始终
    。这是一个两级同步器,它对异步
    采样完成
    ,在第二级的输出上使用逻辑来产生
    数据有效
    。当然,
    data\u valid
    未使用,而且2个阶段实际上还不够。抱歉,也许我无法100%理解您的代码,但sync\u cnv似乎没有遵守规则。谢谢您的回答。尽管对我的问题没有帮助,但它对我的总体理解仍然有用。正如我在上面的评论中所写的(由@EML正确假设),我不需要第二个“时钟域”中的状态。我只是将位时钟输入FIFO,没有状态。当主逻辑采样周期结束时,我读取FIFO。然后我读出最后16位,答案不完整。正确的时钟域交叉还需要约束以及特定于供应商的属性,以确保合成工具不会通过优化破坏CDC。为什么需要在两个FSM之间共享状态?采样时钟就是一个采样时钟。你说它“已经循环工作了”,但你用它来计时计数器和其他各种东西。这太复杂了-重新开始,如果您有问题,请给我们ADC零件号。您是对的,它太复杂了。我重新开始,现在开始工作了。密钥不是共享状态,但我只是将这些位输入FIFO寄存器并读取最后16位。以前我认为我需要一个状态,因为如果重置发生在中间,比如比特5,会发生什么?我想我需要知道这个词什么时候结束。事实证明这并不重要,因为FIFO会处理它:5位(不完整的)被再次移出。这很好。作为练习,你应该试着摆脱FIFO,这是(大规模的)过度使用。将传入数据加载到
    rxclk
    上的寄存器中。现在,假设
    rxclk
    实际上是1/16频率,使用同步电路(可能有3级)将
    rxclk
    采样到
    clk
    域,并为
    clk
    域生成1周负载启用脉冲。现在,将采样的输入数据重新采样到
    clk
    域中,就完成了,无需FIFO。
    // Clock domain 1:
       nrz <= ~nrz;
    
    // Clock domain 2:
    reg nrz_meta,nrz_sync,nrz_old;
    ....
       nrz_meta <= nrz;
       nrz_sync <= nrz_meta; 
       // nrz_sync is the signal you can safely use!
       // Do NOT use nrz_sync ^ nrz_meta, it is not reliable!
    
       nrz_old <= nrz_sync; // required to 'find' an edge
       if (nrz_old ^ nrz_sync)
       begin
          // Process data 
       ....