跨越时钟域

时间:2023-12-26来源:电子森林

FPGA设计可以使用多个时钟。每个时钟在FPGA内部形成一个“时钟域”,如果在另一个时钟域中需要在一个时钟域中生成的信号,则需要格外小心。

跨时钟域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,从源时钟域推送值,然后从另一个域读回值。

关键词: FPGA 时钟 时钟域

加入微信
获取电子行业最新资讯
搜索微信公众号:EEPW

或用微信扫描左侧二维码

相关文章

查看电脑版