跨时钟域处理
为什么跨时钟域可能出现亚稳态?
采样时可能采到上升沿和下降沿
单 bit 信号
电平信号检测
电平检测
通过寄存器打两拍进行同步,也就是电平同步器。等同亚稳态处理
问题
- 输入信号必须保持两个接收时钟周期,每次同步完,输入信号要恢复到无效状态。如果是从快到慢,信号很有可能被滤除。
适用于慢时钟域向快时钟域,或者差异不大的时钟
脉冲信号检测
问题
窄脉冲跨到慢速时钟里,可能会被当毛刺过滤
慢速时钟到快速时钟,脉冲变长
解决方式
握手方式:
- A 在 clk1 下构建 a_latch
- B_latch 在 clk2 下识别 a_latch
- B_latch_r 再在 clk2 下识别 b_latch(消除亚稳态)
- 利用 b_latch_r 再打一拍,形成异或逻辑,组成 b 脉冲信号
- B_feedback_latch 在 clk2 下识别 b 脉冲锁住
- C_latch 在 clk1 下识别 b_feedback_latch
- C_latch_r 在 clk1 下识别 C_latch (消除亚稳态)
- A 在 clk1 下识别 C_latch_r 反馈
- 重复流程 a - b - c 逐步稳定
为什么使用 c 作为反馈,而不是 b 直接反馈?(b 的脉冲在 clk1 相当于电平)
- 这种设计可以不需要管 clk1、clk2 的快慢问题
Busy 信号用来提示 clk1 上的 a 不要频繁发送
代码
module cross_domain_clock(
input clk1,
input rst1_n,
input clk2,
input rst2_n,
input a,
output b,
output reg busy
);
reg a_latch;
reg b_latch;
reg b_latch_r;
reg b_latch_r_2;
reg b_feedback_latch;
reg c_latch;
reg c_latch_r;
always @(posedge clk1 or negedge rst1_n) begin
if (!rst1_n) begin
a_latch <= 0;
end else if(a)
a_latch <= 1;
else if(c_latch_r)
a_latch <= 0;
end
always @(posedge clk2 or negedge rst2_n) begin
if (!rst2_n) begin
b_latch <= 0;
b_latch_r <= 0;
b_latch_r_2 <= 0;
end else
b_latch <= a_latch;
b_latch_r <= b_latch;
b_latch_r_2 <= b_latch_r;
end
assign b = b_latch_r & !b_latch_r_2;
always @(posedge clk2 or negedge rst2_n) begin
if (!rst2_n) begin
b_feedback_latch <= 0;
end else if(b)
b_feedback_latch <= 1;
else if(!b_latch_r)
b_feedback_latch <= 0;
end
always @(posedge clk1 or negedge rst1_n) begin
if (!rst1_n) begin
c_latch <= 0;
c_latch_r <= 0;
end
c_latch <= b_feedback_latch;
c_latch_r <= c_latch;
end
assign busy = a_latch | c_latch_r;
endmodule
Testbech
module cross_domain_clock_tb;
// Declare testbench signals
reg clk1;
reg rst1_n;
reg clk2;
reg rst2_n;
reg a;
wire b;
wire busy;
// Instantiate the DUT
cross_domain_clock dut (
.clk1(clk1),
.rst1_n(rst1_n),
.clk2(clk2),
.rst2_n(rst2_n),
.a(a),
.b(b),
.busy(busy)
);
// Clock generation for clk1 (100MHz)
initial begin
clk1 = 0;
forever #5 clk1 = ~clk1; // 10ns period
end
// Clock generation for clk2 (75MHz)
initial begin
clk2 = 0;
forever #12.5 clk2 = ~clk2; // 13.33ns period
end
// Test stimulus
initial begin
// Initialize signals
rst1_n = 0;
rst2_n = 0;
a = 0;
// Wait for 100ns and release reset
#100;
rst1_n = 1;
rst2_n = 1;
// Test case 1: Toggle input a
#50;
a = 1;
#10;
a = 0;
// Test case 2: Rapid toggle of input a
#200;
// End simulation after some time
$finish;
end
// Monitor changes
initial begin
$monitor("Time=%0t rst1_n=%b rst2_n=%b a=%b busy=%b",
$time, rst1_n, rst2_n, a, busy);
end
initial begin
$fsdbDumpfile("tb.fsdb"); // 波形文件名
$fsdbDumpvars(0); // 0表示dump所有层级
$fsdbDumpMDA(); // 专门用于dump内存数组
$fsdbDumpMemNow; // 立即dump当前内存内容
end
endmodule
仿真图
多 bit 信号
握手
Clk1 下数据变化后稳定时,发出 vld1 信号,clk2 接到 vld2 时对数据采样
前提要求,data_in 在 vld2 采样之前不发生变化,如果可能发生变化,则进行锁存
需要锁存的代码
module cross_domain_bits(
input clk1,
input rst1_n,
input clk2,
input rst2_n,
input valid,
input [3:0] data_in,
output busy,
output reg [3:0] data_out
);
reg a_latch;
reg b_latch;
reg b_latch_r;
reg b_latch_r_2;
reg b_feedback_latch;
reg c_latch;
reg c_latch_r;
wire valid_2;
always @(posedge clk1 or negedge rst1_n) begin
if (!rst1_n) begin
a_latch <= 0;
end else if(valid)
a_latch <= 1;
else if(c_latch_r)
a_latch <= 0;
end
always @(posedge clk2 or negedge rst2_n) begin
if (!rst2_n) begin
b_latch <= 0;
b_latch_r <= 0;
b_latch_r_2 <= 0;
end else
b_latch <= a_latch;
b_latch_r <= b_latch;
b_latch_r_2 <= b_latch_r;
end
assign valid_2 = b_latch_r & !b_latch_r_2;
always @(posedge clk2 or negedge rst2_n) begin
if (!rst2_n) begin
b_feedback_latch <= 0;
end else if(valid)
b_feedback_latch <= 1;
else if(!b_latch_r)
b_feedback_latch <= 0;
end
always @(posedge clk1 or negedge rst1_n) begin
if (!rst1_n) begin
c_latch <= 0;
c_latch_r <= 0;
end
c_latch <= b_feedback_latch;
c_latch_r <= c_latch;
end
assign busy = a_latch | c_latch_r;
always @(posedge clk2 or negedge rst2_n) begin
if (!rst2_n) begin
data_out <= 0;
end if(valid_2)
data_out <= data_in;
end
endmodule
Testbench
module cross_domain_bits_tb;
// Declare testbench signals
reg clk1;
reg rst1_n;
reg clk2;
reg rst2_n;
reg valid;
reg [3:0] data_in;
wire busy;
reg [3:0] data_out;
// Instantiate the DUT
cross_domain_bits dut (
.clk1(clk1),
.rst1_n(rst1_n),
.clk2(clk2),
.rst2_n(rst2_n),
.valid(valid),
.data_in(data_in),
.busy(busy),
.data_out(data_out)
);
// Clock generation for clk1 (100MHz)
initial begin
clk1 = 0;
forever #5 clk1 = ~clk1; // 10ns period
end
// Clock generation for clk2 (75MHz)
initial begin
clk2 = 0;
forever #12.5 clk2 = ~clk2; // 13.33ns period
end
// Test stimulus
initial begin
// Initialize signals
rst1_n = 0;
rst2_n = 0;
valid = 0;
data_in = 4'h0;
// Wait for 100ns and release reset
#100;
rst1_n = 1;
rst2_n = 1;
// Test case 1: Toggle input a
#50;
valid = 1;
data_in = 4'hf;
#10;
valid = 0;
// Test case 2: Rapid toggle of input a
#200;
// End simulation after some time
$finish;
end
// Monitor changes
initial begin
$fsdbDumpfile("tb.fsdb"); // 波形文件名
$fsdbDumpvars(0); // 0表示dump所有层级
$fsdbDumpMDA(); // 专门用于dump内存数组
$fsdbDumpMemNow; // 立即dump当前内存内容
end
endmodule