Home Articles

在Verilog中使用略微偏斜的时钟同步两台状态机的正确方法

Asked
Viewed 1229 times
0

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

接收器为ADC生成控制信号以及占空比的采样时钟 . ADC按顺序发送数据,但为了说明延迟,它还会发回一个占空比的采样时钟 skew matched copy . 该时钟用于计时数据 .

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

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

  • 综合禁止将变量写入具有(可能)不同时钟的不同 always @(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
  • 状态刚刚实现为21状态计数器变量 statenextState

  • 当状态为零时,开始新的采样间隔 . 接收器逻辑(见下文)将通过 sync_cnv changes 来识别这一点 .

  • 在全局复位时,FSM进入已知状态 . 此外, reset_rx 设置为1以通知接收器逻辑(见下文)有关复位的信息 . 它保持为1直到被确认( reset_rx_ack ) .

接收逻辑:

reg [14:0] counter = 0; // just for dummy data. Increments every sample interval
reg sampling_done = 0; // raised when sampling is done
reg [15:0] cbuf; // holds data during data reception

always @(posedge rxclk) begin
    if ( reset_rx == 1) begin
        reset_rx_ack <= 1;
        sync_sdo <= sync_cnv;
        counter <= 0;
    end else begin
        reset_rx_ack <= 0;

        if (sync_cnv != sync_sdo) begin
            // A new sampling interval begins

            sync_sdo <= sync_cnv;

            counter <= counter + 1;
            sampling_done <= 1;
            data <= cbuf;
        end else begin
            // normal operation
            cbuf <= counter;
            sampling_done <= 0;
        end
    end
end

// synchronize "sampling_done" back to the unskewed clock.
// if data_valid, then data can be read the next cycle of clk
always @(posedge clk) begin
    r1 <= sampling_done;    // first stage of 2-stage synchronizer
    r2 <= r1;               // second stage of 2-stage synchronizer
    r3 <= r2;               // edge detector memory
end

assign data_valid = (r2 && !r3);   // pulse on rising edge

此代码在模拟中有完美的工作(有和没有偏斜) . 它大多数时候也适用于FPGA . 但是,复位后的数据值是不可预测的:大多数数据从0开始(如预期的那样),但有时是1和/或任意数字(可能是从复位前的最后一个循环开始) .

1 Answer

  • 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 
       ....
    

    在复位时,将所有寄存器设置为零 . 这样你一开始就没有'假'样本 . 在所有时钟域中进行相同的异步复位是最简单的 . 处理时钟域中的重置是一个相当(大)的主题,需要A4页面来简洁地解释 . 在你的情况下,21个时钟周期没有任何反应,所以你是安全的 .

    另一种方法是使用标准异步FIFO在时钟域之间传输数据 . 如果您的时钟完全独立(这可能比另一个更慢或更快),这是最好的解决方案 . 我相信你可以在WWW上找到它的代码 . 另一个方向的异步FIFO可用于向ADC发送控制信息 .

Related