跨越时钟域
FPGA设计可以使用多个时钟。每个时钟在FPGA内部形成一个“时钟域”,如果在另一个时钟域中需要在一个时钟域中生成的信号,则需要格外小心。
本文引用地址:http://www.amcfsurvey.com/article/202312/454268.htm跨时钟域1-信号
假设 clkB 域中需要来自 clkA 域的信号。 它需要“同步”到 clkB 域,因此我们要构建一个同步器设计,它从 clkA 域获取一个信号,并在 clkB 域中创建一个新信号。
在第一种设计中,我们假设与 clkA 和 clkB 时钟速度相比,“信号输入”变化缓慢。
您需要做的就是使用两个触发器将信号从 clkA 移动到 clkB。
module Signal_CrossDomain(
input clkA,
// we actually don't need clkA in that example, but it is here for completeness as we'll need it in further examples input SignalIn_clkA,
input clkB,
output SignalOut_clkB
);
// We use a two-stages shift-register to synchronize SignalIn_clkA to the clkB clock domain
reg [1:0] SyncA_clkB;
always @(posedge clkB) SyncA_clkB[0] <= SignalIn_clkA; // notice that we use clkB
always @(posedge clkB) SyncA_clkB[1] <= SyncA_clkB[0]; // notice that we use clkB
assign SignalOut_clkB = SyncA_clkB[1]; // new signal synchronized to (=ready to be used in) clkB domain
endmodule
例如,在以下波形中,您可以看到慢速移动的信号被两个触发器同步(和延迟)到 clkB 域:
跨时钟域 2 - 标志
另一个时钟域的标志
如果需要跨越时钟域的信号只是一个脉冲(即它只持续一个时钟周期),我们称之为“标志”。 以前的设计通常不起作用(标志可能会丢失,或者看到时间过长,具体取决于所使用的时钟的比例)。
我们仍然希望使用同步器,但该同步器适用于标志。
诀窍是将标志转换为电平变化,这样可以更容易地跨越时钟域。
module Flag_CrossDomain(
input clkA,
input FlagIn_clkA, // this is a one-clock pulse from the clkA domain
input clkB,
output FlagOut_clkB // from which we generate a one-clock pulse in clkB domain
);
reg FlagToggle_clkA;
always @(posedge clkA) FlagToggle_clkA <= FlagToggle_clkA ^ FlagIn_clkA; // when flag is asserted, this signal toggles (clkA domain)
reg [2:0] SyncA_clkB;
always @(posedge clkB) SyncA_clkB <= {SyncA_clkB[1:0], FlagToggle_clkA}; // now we cross the clock domains
assign FlagOut_clkB = (SyncA_clkB[2] ^ SyncA_clkB[1]); // and create the clkB flag
endmodule
现在,如果您希望 clkA 域收到确认(clkB 收到标志),只需添加繁忙信号即可。
module FlagAck_CrossDomain(
input clkA,
input FlagIn_clkA,
output Busy_clkA,
input clkB,
output FlagOut_clkB
);
reg FlagToggle_clkA;
always @(posedge clkA) FlagToggle_clkA <= FlagToggle_clkA ^ (FlagIn_clkA & ~Busy_clkA);
reg [2:0] SyncA_clkB;
always @(posedge clkB) SyncA_clkB <= {SyncA_clkB[1:0], FlagToggle_clkA};
reg [1:0] SyncB_clkA;
always @(posedge clkA) SyncB_clkA <= {SyncB_clkA[0], SyncA_clkB[2]};
assign FlagOut_clkB = (SyncA_clkB[2] ^ SyncA_clkB[1]);
assign Busy_clkA = FlagToggle_clkA ^ SyncB_clkA[1];
endmodule
跨越时钟域 3 - 任务
在另一个时钟域中完成任务
如果 clkA 域中有任务需要在 clkB 域中完成,则可以使用以下设计。
这是一种方法
module TaskAck_CrossDomain(
input clkA,
input TaskStart_clkA,
output TaskBusy_clkA, TaskDone_clkA,
input clkB,
output TaskStart_clkB, TaskBusy_clkB,
input TaskDone_clkB
);
reg FlagToggle_clkA, FlagToggle_clkB, Busyhold_clkB;
reg [2:0] SyncA_clkB, SyncB_clkA;
always @(posedge clkA) FlagToggle_clkA <= FlagToggle_clkA ^ (TaskStart_clkA & ~TaskBusy_clkA);
always @(posedge clkB) SyncA_clkB <= {SyncA_clkB[1:0], FlagToggle_clkA};
assign TaskStart_clkB = (SyncA_clkB[2] ^ SyncA_clkB[1]);
assign TaskBusy_clkB = TaskStart_clkB | Busyhold_clkB;
always @(posedge clkB) Busyhold_clkB <= ~TaskDone_clkB & TaskBusy_clkB;
always @(posedge clkB) if(TaskBusy_clkB & TaskDone_clkB) FlagToggle_clkB <= FlagToggle_clkA;
always @(posedge clkA) SyncB_clkA <= {SyncB_clkA[1:0], FlagToggle_clkB};
assign TaskBusy_clkA = FlagToggle_clkA ^ SyncB_clkA[2];
assign TaskDone_clkA = SyncB_clkA[2] ^ SyncB_clkA[1];
endmodule
下面是一个匹配的仿真波形
跨越时钟域 4 - 数据总线
数据总线到另一个时钟域
为了将数据总线(2 位宽或更高)从一个时钟域移动到另一个时钟域,我们可以使用多种技术。
这里有一些想法。
格雷码:如果数据总线是单调计数器(即仅递增或递减),我们可以将其转换为格雷码,该格雷码具有跨时钟域的能力(在某些时序条件下)。
数据冻结:如果数据总线是非单调的,则使用标志向另一个域发出信号以捕获该值(当它在源时钟域中冻结时)。
数据突发:如果数据总线有许多需要跨越时钟域的连续值,请使用异步 FIFO,从源时钟域推送值,然后从另一个域读回值。
评论