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

时间:2018-06-25 21:11:43

标签: verilog

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

接收器为ADC生成控制信号以及占空比采样时钟。 ADC按顺序发送回数据,但为了解决延迟,它还发送回占空比采样时钟的偏移匹配副本。该时钟将用于输入数据。

该代码应在两个时钟之间的零延迟以及更大的延迟下工作。 (但是延迟不会超过几个时钟周期。)

我不知道执行此操作的最佳方法,因为:

  1. 综合禁止使用(可能)不同的时钟在不同的always @(posedge...)块中写入变量。
  2. 时钟中的数据部分没有真实的时钟(已占空比!),因此它无法自行维持状态。它需要以某种方式从控制FSM中获取它处于哪个周期的信息
  3. 一旦读取了采样值,就需要将其传输回原始的,未偏移的时钟域以进行进一步处理。

这显示了我的方法的一个最小示例:

// 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 更改
  • 在全局重置时,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 个答案:

答案 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 
   ....

在复位时,将所有寄存器设置为零。这样一来,您一开始就不会有“假”样本。在所有时钟域中进行相同的异步复位是最简单的。处理时钟域中的重置是一个相当(大)的主题,需要A4页面来简要说明。就您而言,在21个时钟周期内什么都没有发生,因此您很安全。

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