Quick Start
- 准备开发环境:安装 Vivado 或 Quartus(推荐 Vivado 2019.1 及以上版本)。
- 新建工程:选择目标器件,例如 Xilinx Artix-7 XC7A35T。
- 创建顶层模块:编写
top.v,包含两个时钟域(clk_a和clk_b)及复位信号。 - 编写双寄存器同步器模块:实现
sync_2ff.v(代码详见“实施步骤”)。 - 编写握手控制模块:实现
handshake.v,完成请求-应答逻辑。 - 编写 testbench:模拟跨时钟域数据传输,运行仿真。
- 观察仿真波形:确认
data_out稳定输出,无毛刺或亚稳态传播。 - 综合实现:检查时序报告,确保无跨时钟域路径违例。
- 上板验证:通过 LED 或 UART 观察数据正确性。
前置条件
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Artix-7 (XC7A35T) | 主流 FPGA,资源充足 | Altera Cyclone IV / Lattice ECP5 |
| EDA 版本 | Vivado 2020.1 | 支持异步约束语法 | Quartus Prime 18.0+ |
| 仿真器 | Vivado Simulator / ModelSim | 用于功能仿真和时序仿真 | Verilator(仅功能仿真) |
| 时钟/复位 | clk_a = 50MHz, clk_b = 75MHz | 异步时钟,频率不同 | 任意频率差,但需满足 setup/hold |
| 接口依赖 | 无特殊外设 | 仅需 GPIO 或 UART 观察 | 逻辑分析仪(上板调试) |
| 约束文件 | XDC(Vivado) | 必须添加 set_false_path 或 set_clock_groups | SDC(Quartus) |
目标与验收标准
- 功能点:实现从
clk_a域到clk_b域的单比特或少量多比特数据可靠传输,无亚稳态传播。 - 性能指标:握手延迟不超过 3 个
clk_b周期(从请求到应答)。 - 资源消耗:双寄存器同步器占用约 2 个触发器,握手控制约 4-6 个触发器。
- 验收方式:仿真波形中
data_out在握手完成后稳定,且与data_in一致;时序分析报告无跨时钟域违例。
实施步骤
工程结构
project/
├── rtl/
│ ├── top.v
│ ├── sync_2ff.v
│ └── handshake.v
├── sim/
│ └── tb_top.v
├── constraints/
│ └── top.xdc
└── scripts/
└── run.tcl顶层模块 top.v
顶层模块实例化 handshake 和 sync_2ff,其中 handshake 内部调用 sync_2ff 同步请求和应答信号。
关键模块:双寄存器同步器
module sync_2ff (
input wire clk_dst,
input wire rst_n,
input wire async_in,
output wire sync_out
);
reg sync_reg1, sync_reg2;
always @(posedge clk_dst or negedge rst_n) begin
if (!rst_n) begin
sync_reg1 <= 1'b0;
sync_reg2 <= 1'b0;
end else begin
sync_reg1 <= async_in;
sync_reg2 <= sync_reg1;
end
end
assign sync_out = sync_reg2;
endmodule机制说明:两级触发器串联,第一级可能产生亚稳态,但经过一个时钟周期后,第二级捕获到稳定值。该结构将亚稳态概率降低至可忽略水平,是跨时钟域同步的基础单元。
关键模块:握手控制
握手协议通过请求-应答机制确保数据可靠传输。发送域(clk_a)拉高请求信号,接收域(clk_b)同步后采样数据,并拉高应答信号。发送域检测到应答后,拉低请求,完成一次传输。
module handshake (
input wire clk_a,
input wire clk_b,
input wire rst_n,
input wire [7:0] data_in,
input wire req_in,
output wire ack_out,
output reg [7:0] data_out
);
wire req_sync, ack_sync;
reg req_a, ack_b;
// 同步请求到 clk_b 域
sync_2ff u_sync_req (
.clk_dst(clk_b),
.rst_n(rst_n),
.async_in(req_a),
.sync_out(req_sync)
);
// 同步应答到 clk_a 域
sync_2ff u_sync_ack (
.clk_dst(clk_a),
.rst_n(rst_n),
.async_in(ack_b),
.sync_out(ack_sync)
);
// clk_a 域:请求生成
always @(posedge clk_a or negedge rst_n) begin
if (!rst_n)
req_a <= 1'b0;
else if (req_in && !ack_sync)
req_a <= 1'b1;
else if (ack_sync)
req_a <= 1'b0;
end
// clk_b 域:应答与数据采样
always @(posedge clk_b or negedge rst_n) begin
if (!rst_n) begin
ack_b <= 1'b0;
data_out <= 8'b0;
end else if (req_sync && !ack_b) begin
data_out <= data_in;
ack_b <= 1'b1;
end else if (!req_sync) begin
ack_b <= 1'b0;
end
end
assign ack_out = ack_sync;
endmodule风险边界:握手协议适用于数据变化频率低于握手周期的情况。若数据在握手期间多次变化,可能导致数据丢失或错误。多比特数据建议使用 FIFO 或 DMUX 结构。
约束文件(top.xdc)
# 对跨时钟域路径设置 false path
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
set_false_path -from [get_clocks clk_b] -to [get_clocks clk_a]原因:跨时钟域路径无需时序收敛,因为同步器本身容忍亚稳态。若不设置 false path,工具可能报时序违例,影响综合结果。
验证结果
仿真波形应显示:
req_a拉高后,经过 2-3 个clk_b周期,req_sync变为高电平。ack_b拉高后,再经 2-3 个clk_a周期,ack_out变为高电平。data_out在ack_b拉高时更新,并保持稳定直到下一次握手。- 无毛刺或不定态(X)传播到
data_out。
时序分析报告应无跨时钟域违例(若已正确设置 false path)。
排障指南
- 仿真中出现 X 态:检查复位信号是否在仿真开始时有效,且所有触发器正确初始化。
- 握手不完成:确认请求和应答同步路径正确,且握手状态机无死锁。可增加超时计数器辅助调试。
- 时序违例:确保约束文件已添加 false path,且时钟定义正确。若仍有违例,检查是否误将同步器内部路径包含在时序分析中。
- 数据错误:对于多比特数据,确认握手期间数据保持稳定。若数据变化快,考虑使用格雷码或 FIFO。
扩展阅读
- 多比特同步:可使用握手协议传输多比特数据,但需保证数据在握手期间不变。更高效方案是异步 FIFO。
- 格雷码:适用于连续变化的多比特数据(如计数器),每次只变化一位,降低亚稳态风险。
- DMUX 结构:通过使能信号同步后,用多路选择器选择数据,适合少量多比特传输。
- 异步 FIFO:最通用的跨时钟域数据传输方案,适用于任意位宽和速率。
参考文档
- Xilinx UG949: Vivado Design Suite User Guide - Using Constraints
- Clifford E. Cummings: “Synthesis and Scripting Techniques for Designing Multi-Asynchronous Clock Designs”
- Altera AN 433: Constraining and Analyzing Source-Synchronous Interfaces
附录:完整 testbench 示例
module tb_top;
reg clk_a, clk_b, rst_n;
reg [7:0] data_in;
reg req_in;
wire ack_out;
wire [7:0] data_out;
// 时钟生成
always #10 clk_a = ~clk_a; // 50MHz
always #6.667 clk_b = ~clk_b; // 75MHz
initial begin
clk_a = 0; clk_b = 0; rst_n = 0;
data_in = 8'hA5; req_in = 0;
#100 rst_n = 1;
#50 req_in = 1;
#100 req_in = 0;
#500 $finish;
end
top u_top (
.clk_a(clk_a),
.clk_b(clk_b),
.rst_n(rst_n),
.data_in(data_in),
.req_in(req_in),
.ack_out(ack_out),
.data_out(data_out)
);
endmodule


