我正在用Verilog实现ADC的接收器。在每个第21个时钟周期后获得一个样本。
接收器为ADC生成控制信号以及占空比采样时钟。 ADC按顺序发送回数据,但为了解决延迟,它还发送回占空比采样时钟的偏移匹配副本。该时钟将用于输入数据。
该代码应在两个时钟之间的零延迟以及更大的延迟下工作。 (但是延迟不会超过几个时钟周期。)
我不知道执行此操作的最佳方法,因为:
always @(posedge...)
块中写入变量。这显示了我的方法的一个最小示例:
// 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
state
和nextState
sync_cnv
更改。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和/或任意数字开头(可能从复位之前的最后一个周期开始)。
答案 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。