简介
本页讨论源同步(source-synchronous)数据输出。这种技术是指:所有数据输出与 FPGA 随这些输出一同生成的一个时钟保持同步。
如果与外部组件的接口仅由来自 FPGA 的输出端口组成,那么这种方法显然是首选。如果 I/O 是双向的,也同样适用,这将在本页末尾讨论。此外,还有一个专门页面讨论时钟与数据之间的总体关系。
基本应用
请看以下 Verilog 示例:
module top (
input clk,
output reg [7:0] data_out,
output reg sync_clk_out
);
always @(posedge clk)
begin
sync_clk_out <= !sync_clk_out;
if (sync_clk_out)
data_out <= data_out + 1;
end
endmodule
请注意,@data_out 与 @sync_clk_out 的下降沿同时发生改变:当 @sync_clk_out 为低电平时,@data_out 保持不变;当 @sync_clk_out 为高电平时,@data_out 会响应 @clk 的上升沿而更新为一个新值。
如果 @data_out 和 @sync_clk_out 都使用 IOB 寄存器,那么所有这些输出信号实际上都会在同一时刻改变。接收到这些信号的外部组件因而可以把 @sync_clk_out 用作自己的时钟。这样做通常很容易满足外部组件的时序要求(前提是 @data_out 的采样(sampling)与 @sync_clk_out 的上升沿同步)。
使用 DDR 寄存器
上面的示例有一个明显缺点:@sync_clk_out 的频率只有 @clk 的一半,因此 @data_out 的数据速率同样最多只能达到 @clk 频率的一半。唯一的例外是,外部组件对 @sync_clk_out 的两个时钟边沿都敏感(即它带有 DDR 输入),但通常并非如此。
为了生成一个与数据输出对齐的输出时钟,可以使用输出 DDR 寄存器。其思路是使用一个在时钟两个边沿都会改变取值的触发器。因此,这种触发器的行为类似这样:
always @(posedge clk or negedge clk)
if (clk)
sync_clk_out <= 0;
else
sync_clk_out <= 1;
@sync_clk_out 响应 @clk 的上升沿而变为低电平;响应下降沿则发生相反的变化。最终结果是一个与 @clk 频率相同的输出时钟。
不过,这段 Verilog 代码在大多数综合器(synthesizer)上都无法正常工作,因为逻辑结构中的触发器通常不具备对时钟两个边沿都敏感的能力。只有 IOB 寄存器具备这种能力,而综合器很少聪明到能理解这种可能性。
因此,需要对一个逻辑原语(primitive)进行实例化(instantiation)。这个原语与所用 FPGA 密切相关。下面的示例展示了在 Kintex Ultrascale 上如何完成这一操作:
ODDRE1 ODDR_ins
(.C(clk), .Q(sync_clk_out),
.D1(1'b0), .D2(1'b1),
.SR(1'b0));
但这样一来,时钟是用输出 DDR 寄存器生成的,而数据信号是用普通 IOB 寄存器生成的。数据与时钟之间的对齐关系就不像之前那么显而易见了。因此,务必阅读所有输出的时序报告,并确认各延迟之间的差值足够小。
也可以为数据信号使用输出 DDR 寄存器:如果 D1 和 D2 都输入相同的值,那么输出在每个时钟周期内只会改变一次:
module top (
input clk,
output [7:0] data_out,
output sync_clk_out
);
reg [7:0] data;
ODDRE1 ODDR_ins
(.C(clk), .Q(sync_clk_out),
.D1(1'b0), .D2(1'b1),
.SR(1'b0));
ODDRE1 ODDR_data_ins [7:0]
(.C(clk), .Q(data_out),
.D1(data), .D2(data),
.SR(1'b0));
always @(posedge clk)
data <= data + 1;
endmodule
这种方法可以确保数据与时钟对齐。显式地对输出 DDR 寄存器进行实例化,也保证了会使用 IOB 寄存器。这种方法的唯一缺点是它依赖于具体 FPGA。
请注意,输出 DDR 寄存器的行为可能出乎意料,甚至令人困惑。例如,注意 @data 与 @clk 的上升沿同步,但 ODDRE1 在两个时钟边沿都会工作。请务必阅读你的 FPGA 文档,了解这两个数据输入是如何以及何时被采样的。
还需注意,本例中没有使用复位。这可能不安全,尤其是在复杂的 FPGA 上:输出 DDR 寄存器可能是 IOB 内部某个更复杂逻辑单元的一部分。如果没有复位,这样的逻辑单元可能无法正常工作。
时序约束
那么,这种方法是否还需要时序约束(timing constraints)呢?如果时钟输出和数据输出由于使用 IOB 寄存器而几乎完美对齐,这难道还不够吗?
答案是,即使没有时序约束,这种方法的运行效果也可能完全正常。不过,仍然最好利用时序约束。其原因与 关于 IOB 寄存器的页面中针对同一主题所述的原因相同。
其他注意事项
不过,使用 IOB 寄存器并不是唯一需要确保的事情:还应注意各输出端口之间的物理临近性。如果输出端口彼此相距很远,这种物理距离会对 FPGA 上的时钟偏移(clock skew)产生不良影响。PCB 上走线(即 PCB 上的导线)长度的差异也会带来负面影响。此外,所有输出的电气参数(电压、I/O 标准、压摆率、驱动电流等)也应保持一致。
如果 FPGA 没有 IOB 寄存器,时序约束就变得至关重要:使用源同步输出的基础,是能够确保所有相关输出端口之间具有很小的偏移。通常,控制触发器与 I/O 引脚之间的延迟就够了:如果该延迟接近相关 FPGA 所可能达到的最小值,那么各输出之间的差异对于大多数用途来说已经足够小。
双向 I/O
源同步输出的主要特点是,相关时钟由 FPGA 产生。当所有数据信号都指向外部组件时,这显然很合适。但即使数据信号是双向的,依赖 FPGA 产生的时钟也可能是有意义的。
但请注意,数据输入是与 PCB 上的时钟同步的,而不是与 FPGA 芯片内部的时钟同步。当然,这两个时钟的频率完全相同。但它们之间存在一个取决于输出端口时钟到输出(clock-to-output)延迟的时序差,这个差可能达到数纳秒。
因此,建议在 PCB 上做一个回环(loopback):将 FPGA 的时钟输出连接到一个输入端口。可以将这个输入端口当作系统同步时钟(system synchronous clock)来对待:外部组件的行为就像它的时钟来自专用振荡器而不是 FPGA 一样。这类似于系统同步时钟,因为 FPGA 和外部组件都依赖 PCB 上的同一个物理信号。
也可以把数据输入当作源同步输入(source-synchronous inputs)来处理。尤其是如果使用了移相(phase shifting)技术,就不需要时钟回环。事实上,这也是与 DDR SDRAM 存储器对接的常用方式。
重要的结论是:应在 PCB 制造之前仔细规划输入信号的时序,并在必要时考虑加入时钟回环。

