如何处理从一个时钟域到另一个时钟域的数据,该时钟域的时钟除以第一个时钟的 2 个版本?
How to handle data going from a clock domain to another clock domain whose clock is divide by 2 version of the first clock?
我有以下代码。
module tb;
reg clk;
reg clk_2;
reg [15:0] from_flop;
reg [15:0] to_flop;
initial begin
clk = 0;
clk_2 = 0;
from_flop = 1;
end
always #10 clk = ~clk;
always @(posedge clk) begin
clk_2 <= ~clk_2;
end
always @(posedge clk) begin
from_flop <= from_flop + 1;
end
always @(posedge clk_2) begin
to_flop <= from_flop;
end
endmodule
然而,在 10ns 时刻,from_flop 和 to_flop 都得到值 = 2。这与我的预期相反。我期待 from_flop 在 10ns 时从 1 变为 2,而 to_flop 在 10ns 时从 x 变为 1。
为什么会发生这种情况以及如何编写代码以防止这种情况发生?
问题出在这一行:
clk_2 <= ~clk_2;
您正在使用非阻塞赋值,而您可能需要阻塞赋值:
clk_2 = ~clk_2;
非阻塞分配在阻塞分配之后安排,因此 always @(posedge clk) begin
将始终在 always @(posedge clk_2) begin
之前计时。
显然,这不是可综合的代码。所以,这是一个模拟(调度)问题。如果您打算使用此功能综合一些东西,请非常仔细地考虑如何生成分频时钟。
通常顺序块中的赋值是使用非阻塞(<=
)赋值。一个值得注意的例外是生成派生时钟,它应该使用阻塞 (=
) 分配。顺序块中的阻塞赋值是合法的;但是您需要知道自己在做什么,否则 RTL 仿真和电路之间会出现功能不匹配。也就是说,在使用这种方法时,您仍然需要谨慎行事。
触发器有时钟到 Q 延迟,因此任何派生时钟都会与其父时钟有相位偏移。一些合成工具可能会为您补偿偏移量,但是您需要指定合成器应该发生这种情况的所有情况;它不会为你解决。这意味着您不应在整个设计中零星地创建派生时钟。所有派生时钟信号都需要从一个专用模块生成;这使它易于管理。
在大多数情况下,您不应创建任何派生时钟。相反,通过添加一个额外的触发器对每隔一个时钟进行采样。
always @(posedge clk) begin
from_flop <= from_flop + 1;
if (transfer_n==1'b0) begin
to_flop <= from_flop;
end
transfer_n <= ~transfer_n;
end
此策略将设计保持在一个时钟域中,这通常可以通过更好的时序使综合更容易。最小的额外触发器对面积的影响可以很容易地小于保持派生时钟对齐所需的添加缓冲区的面积损失。
我有以下代码。
module tb;
reg clk;
reg clk_2;
reg [15:0] from_flop;
reg [15:0] to_flop;
initial begin
clk = 0;
clk_2 = 0;
from_flop = 1;
end
always #10 clk = ~clk;
always @(posedge clk) begin
clk_2 <= ~clk_2;
end
always @(posedge clk) begin
from_flop <= from_flop + 1;
end
always @(posedge clk_2) begin
to_flop <= from_flop;
end
endmodule
然而,在 10ns 时刻,from_flop 和 to_flop 都得到值 = 2。这与我的预期相反。我期待 from_flop 在 10ns 时从 1 变为 2,而 to_flop 在 10ns 时从 x 变为 1。
为什么会发生这种情况以及如何编写代码以防止这种情况发生?
问题出在这一行:
clk_2 <= ~clk_2;
您正在使用非阻塞赋值,而您可能需要阻塞赋值:
clk_2 = ~clk_2;
非阻塞分配在阻塞分配之后安排,因此 always @(posedge clk) begin
将始终在 always @(posedge clk_2) begin
之前计时。
显然,这不是可综合的代码。所以,这是一个模拟(调度)问题。如果您打算使用此功能综合一些东西,请非常仔细地考虑如何生成分频时钟。
通常顺序块中的赋值是使用非阻塞(<=
)赋值。一个值得注意的例外是生成派生时钟,它应该使用阻塞 (=
) 分配。顺序块中的阻塞赋值是合法的;但是您需要知道自己在做什么,否则 RTL 仿真和电路之间会出现功能不匹配。也就是说,在使用这种方法时,您仍然需要谨慎行事。
触发器有时钟到 Q 延迟,因此任何派生时钟都会与其父时钟有相位偏移。一些合成工具可能会为您补偿偏移量,但是您需要指定合成器应该发生这种情况的所有情况;它不会为你解决。这意味着您不应在整个设计中零星地创建派生时钟。所有派生时钟信号都需要从一个专用模块生成;这使它易于管理。
在大多数情况下,您不应创建任何派生时钟。相反,通过添加一个额外的触发器对每隔一个时钟进行采样。
always @(posedge clk) begin
from_flop <= from_flop + 1;
if (transfer_n==1'b0) begin
to_flop <= from_flop;
end
transfer_n <= ~transfer_n;
end
此策略将设计保持在一个时钟域中,这通常可以通过更好的时序使综合更容易。最小的额外触发器对面积的影响可以很容易地小于保持派生时钟对齐所需的添加缓冲区的面积损失。