FPGA中的原语(Language Templates)

一、什么是原语

  • 原语是官方将芯片中一部分电路封成类似于IP核一样的东西,用户可以直接调用原语来实现一些常见功能比如IBUFGDS(差分转普通)

  • 原语因芯片而异,所以可移植性比较差,正因如此,IP核的创建尽量不要使用原语

  • 原语比IP核速度还要快,因为是底层电路

下图是xilinx的vivado中关于原语的分类,可以看到在verilog中(目前先关注这个)根据芯片分了很多类,xcku060分在Kintex UltraScale中,zynq xczu4ev严格意义上不属于其中的任何一类,但是可以使用Kintex UltraScale+和Versal UltraScale+,因为其pl端是相同的。

language template for fifo
类别 子类 主要内容/用途
Device Macro Instantiation Artix-7, Kintex-7, Virtex-7 针对 7 系 FPGA 的宏单元例化(例如 BUFG, PLL, BRAM, MMCM 等)
Device Primitive Instantiation Artix-7 提供 7 系 Artix 设备的底层原语例化模板
Kintex UltraScale UltraScale 架构的 Kintex 设备原语(如高速收发器 GTY、BRAM、PLL)
Kintex UltraScale+ UltraScale+ 架构的 Kintex 设备原语(支持更高性能的 DSP、GTY、HBM 等)
Kintex-7 7 系 Kintex 设备原语
Versal AI Core series Versal AI Core 系列设备原语(支持 AI Engine、NoC 等新特性)
Versal Premium series Versal Premium 系列设备原语(更适合高带宽通信和网络)
Versal Prime series Versal Prime 系列设备原语(均衡型,适合通用 SoC 应用)
Virtex UltraScale UltraScale 架构的 Virtex 设备原语(面向高端通信/计算)
Virtex UltraScale+ UltraScale+ 架构的 Virtex 设备原语(更高带宽、更大逻辑资源)
Virtex-7 7 系 Virtex 设备原语
IP Integrator HDL Advanced Interfaces 高级接口例化模板(如 AXI 总线接口信号)
AXI Interfaces 专门的 AXI4/AXI4-Lite/AXI4-Stream 接口模板
Signal Interfaces 常用信号接口模板(如握手、时钟复位等)
Simulation Constructs 提供仿真专用的语法模板
Synthesis Constructs 提供综合相关的语法模板
Xilinx Parameterized Macros (XPM) XPM, Usage Instructions Xilinx 提供的参数化宏(例如 XPM_FIFO, XPM_MEMORY),可移植性和灵活性比直接用原语更好

二、原语

2.1 IBUFDS

IBUFDS 是 Vivado 提供的 差分信号输入缓冲器原语
它的作用是:
将一对外部差分信号(如时钟信号 CLK_P / CLK_N)转换为单端信号,
供 FPGA 内部逻辑或全局时钟网络使用。

端口名 方向 说明
I 输入 差分信号正端(如 CLK_P
IB 输入 差分信号负端(如 CLK_N
O 输出 转换后的单端时钟输出
1
2
3
4
5
6
7
8
IBUFDS #(
.DIFF_TERM("TRUE"), // 启用差分终端匹配电阻(可选)
.IBUF_LOW_PWR("FALSE") // 设置为 FALSE 提高性能(默认 TRUE)
) IBUFDS_inst (
.I (CLK_P), // 差分正端输入
.IB(CLK_N), // 差分负端输入
.O (clk_buf) // 输出单端时钟信号
);

差分终端电阻:若外部没有匹配电阻,可以开启参数 .DIFF_TERM("TRUE")

2.2 BUFG

2.2.1 WHAT & WHY

BUFG 是 Vivado 提供的 全局时钟缓冲器(Global Clock Buffer),它的作用是:

将一个普通时钟信号(如外部晶振输入)连接到 FPGA 内部的全局时钟网络(Global Clock Tree),保证整个 FPGA 芯片内部所有时序单元可以同步工作。

那为什么需要 BUFG?

FPGA 是一个时序电路,每个触发器、状态机、计数器等都依赖统一的全局时钟

而从外部引脚进来的时钟(例如 PL_CLK_P/PL_CLK_N)只是普通信号:

  • 没有经过专用时钟树缓冲;
  • 无法直接驱动大量触发器;
  • 会导致严重的时钟偏斜(clock skew)
  • Vivado 会报时钟相关的 Place & Route 错误或严重 timing violation

使用BUFG的好处:

  1. 全局驱动:将时钟驱动到整个 FPGA 内部
  2. 消除时钟偏斜:所有逻辑单元同时接受一个“均衡”的时钟
  3. 必须走时钟树:Vivado 综合器只允许 BUFG 的输出进入 posedge clk 等时序逻辑
  4. 布局优化:Vivado 能做出专门优化策略,满足时序需求

如果不在时钟输入里面加BUFG,可能会导致仿真OK,结果综合后跑不了,并且在Timing Report中看到非常大的clock skew。

正确设计结构需要:

1
2
3
4
5
6
7
8
9
10
IBUFDS → BUFG → clk → always @(posedge clk)

即:
外部差分时钟(100MHz)

IBUFDS(差分转单端)

BUFG(接入全局时钟网络)

clk → 所有模块时钟

2.2.2 HOW

端口名 方向 说明
I 输入 输入时钟信号
O 输出 输出全局时钟
1
2
3
4
BUFG BUFG_inst (
.I (clk_in), // 输入时钟信号(来自 IBUFDS 或其他模块)
.O (clk_out) // 输出全局时钟(分发到 FPGA 内部逻辑)
);

2.3 IDELAYE3

2.3.1 WHAT & WHY

IDELAYE3 是 Xilinx UltraScale / UltraScale+ 系列 FPGA 中提供的 输入延迟原语(Input Delay Element),用于对高速输入信号进行时序补偿

它的核心作用是:

在 FPGA 内部,对输入信号引入一个可控、可调的亚纳秒级延迟,用于补偿外部走线、器件、时钟相位不一致带来的时序偏差。

在高速接口中(如 LVDS、DDR、ADC/AFE 输出数据):

  • 数据(Data)
  • 时钟(Dclk / Fclk)

几乎不可能在物理上做到完全对齐

常见问题有:

  • 数据与时钟相位偏移
    • PCB 走线不等长
    • AFE 内部通道不一致
  • 建立时间 / 保持时间不满足
    • ISERDES 采样点落在数据边沿附近
  • 温度、电压变化导致相位漂移
  • 多通道一致性要求(lane-to-lane alignment)

IDELAYE3 就是用来解决这个问题的


2.3.2 WHERE

在正确的设计中,IDELAYE3 一定处在:

1
外部引脚 → IBUF / IBUFDS → IDELAYE3 → ISERDES / 逻辑

典型结构如下:

1
2
3
4
5
6
7
8
9
外部 LVDS 数据

IBUFDS

IDELAYE3 ← 关键对齐点

ISERDESE3

并行数据

2.3.3 HOW

IDELAYE3 本质是一个 可编程输入延时线:把输入信号(来自 IO 的 IDATAIN 或逻辑的 DATAIN)延迟若干个 tap,再从 DATAOUT 输出。它的端口大体分 4 组:

A. 数据通路端口

  • IDATAIN (Input):由“关联的 IOB 引脚”驱动的输入数据(最常用的输入路径)。
  • DATAIN (Input):由 FPGA 内部互连逻辑驱动的输入数据(逻辑可达的延时线输入,不能直接回 IO)。
  • DATAOUT (Output):延迟后的数据输出,可去 ILOGIC/ISERDESE3 或 FPGA 逻辑。

B. 延时控制端口(VARIABLE/VAR_LOAD 模式才真正用起来)

  • CLK (Input):采样控制信号(LOAD/CE/INC 等)的时钟;当 IDELAYE3 配成 VARIABLE/VAR_LOAD 时必须接。
  • CE (Input):控制接口使能;CE=1 时,INC 才能对延时 tap 做加/减操作。
  • !!INC (Input):与 CE 配合使用;INC=1 表示“加 tap”(延迟变大),INC=0 表示“减 tap”(延迟变小)。
  • RST (Input):异步复位输入;复位会把延时线回到设定的初始值(通常是 DELAY_VALUE 对应的 tap)。
  • LOAD (Input):把 CNTVALUEIN 指定的 tap 值加载到延时线(VAR_LOAD 或需要动态装载时用)。

C. 延时数值端口

  • CNTVALUEIN[8:0] (Input):要加载的 tap 数(动态装载用;通常建议在 LOAD 前提前 1 个 CLK 周期把 CNTVALUEIN 准备好)。这里也就是说IDELAYE3其实是支持给定的tap数,其实对于一块成型的板子,调试一次之后,其tap数就已经稳定了,那此时就可以使用固化版本的延迟,不用每次都动态计算每一路的tap。
  • CNTVALUEOUT[8:0] (Output):报告当前延时 tap 数(读出当前处在多少 tap)。

D. 电压温漂补偿端口

  • EN_VTC (Input):Voltage/Temperature Compensation 使能(决定是否由 IDELAYCTRL 做温漂电压补偿,让“tap 对应的真实时间延迟”更稳定)。

E. 级联相关(不做IDELAYE3的级联的话用不到)

  • CASC_IN / CASC_OUT / CASC_RETURN:用于和 ODELAYE3/IDELAYE3 做级联扩展延迟范围的链路。

使用例程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module idelay
(
input wire CLK,
input wire Idata,
input wire RESET,
input wire INC,
input wire CE,
input wire EN_VTC,

output wire [8:0] CNTVALUEOUT,
output wire 0data
);


IDELAYE3 #(
.CASCADE("NONE"),
.DELAY_FORMAT("COUNT"),
.DELAY_SRC("IDATAIN"),
.DELAY_TYPE("VARIABLE"),
.DELAY_VALUE(0),
.REFCLK_FREQUENCY(300.0),
.SIM_DEVICE("ULTRASCALE"),
.UPDATE_MODE("ASYNC")
) u_IDELAYE3(
.CNTVALUEOUT(CNTVALUEOUT),
.DATAOUT(Odata),
.CE(CE),
.CLK(CLK),
.CNTVALUEIN(9'd0),
.EN_VTC(1'd0),
.IDATAIN(Idata),
.INC(INC),
.LOAD(1'd0),
.RST(RESET)
);
  1. 延迟对象选 IDATAIN(来自 IO)
    .DELAY_SRC("IDATAIN") + .IDATAIN(Idata) 表示: 外部进来的高速引脚信号 做精细延时,这正是 IDELAYE3 最典型用途(给 ISERDES 找采样窗口)。
  2. 模式选 VARIABLE + COUNT
  • VARIABLE:在运行时扫描/微调 tap(自动校准就必须是可变)
  • COUNT:用 tap 计数值表达延迟(对应 CNTVALUEOUT 是“第几 tap”)
  1. 控制接口用 CE/INC + CLK
    因为 IDELAYE3 的控制输入(CE/INC/LOAD)是跟着CLK跑的同步信号,选择比 Dclk 慢的时钟,便于状态机稳定操作、等待数据收敛。

  2. REFCLK_FREQUENCY(300.0)
    IDELAYE3 背后一定有一个 IDELAYCTRL

    在 UltraScale 架构中:

    • IDELAYE3 本身不能独立工作
    • 每一组 IDELAYE3 必须依赖一个 IDELAYCTRL
    • IDELAYCTRL 需要一个 稳定的参考时钟(REFCLK)

    REFCLK_FREQUENCY 用来告诉 IDELAYCTRL:
    “参考时钟是多少 MHz,用它来标定 IDELAYE3 每一个 tap 的真实时间长度。”

    不等于 每个 tap 的时间,
    而是 tap 校准和温漂补偿的基准条件

    REFCLK 频率越高,校准精度越高,每个 tap 的延迟值越精确

2.4 ISERDESE3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ISERDESE3 #(
// .DATA_WIDTH(8), // 并行数据宽度(可选 4 或 8)
// .FIFO_ENABLE("FALSE"), // 是否启用内部 FIFO
// .FIFO_SYNC_MODE("FALSE"), // FIFO 同步模式,始终为 FALSE(TRUE 预留,当前不可用)
// .IS_CLK_B_INVERTED(1'b0), // 是否对 CLK_B 进行反相
// .IS_CLK_INVERTED(1'b0), // 是否对 CLK 进行反相
// .IS_RST_INVERTED(1'b0), // 是否对 RST 进行反相
// .SIM_DEVICE("ULTRASCALE") // 仿真所使用的器件架构(ULTRASCALE)
// )
// ISERDESE3_inst (
// .FIFO_EMPTY(FIFO_EMPTY), // 1 位输出:FIFO 空标志
// .INTERNAL_DIVCLK(INTERNAL_DIVCLK), // 内部分频时钟输出(FIFO 关闭时使用)
//
// .Q(Q), // 并行数据输出(宽度由 DATA_WIDTH 决定)
// .CLK(CLK), // 高速串行采样时钟
// .CLKDIV(CLKDIV), // 低速并行域时钟
// .CLK_B(CLK_B), // CLK 的反相信号(用于 DDR 采样)
// .D(D), // 串行数据输入
// .FIFO_RD_CLK(FIFO_RD_CLK), // FIFO 读时钟
// .FIFO_RD_EN(FIFO_RD_EN), // FIFO 读使能
// .RST(RST) // 异步复位
// );

2.4.1 WHAT

ISERDESE3(Input SERializer / DESerializer)是 Xilinx UltraScale / UltraScale+ FPGA 中的输入端串并转换原语

它的核心功能是:

  • 在高速时钟 CLK 驱动下,对单比特串行输入数据 D 进行采样;
  • 通过 DDR 结构在一个时钟周期内完成多次采样;
  • 将采样得到的数据重新整理,在低速时钟 CLKDIV 域中以并行形式输出。

与普通逻辑级串并转换不同,ISERDESE3 工作在 IO 逻辑与高速时钟网络附近,具备明确的时序模型和物理实现路径,是高速接口设计中不可替代的基础单元。

2.4.2 WHY

在高速数据接口中,串行传输是不可避免的选择:

  • IO 引脚数量有限;
  • 高速并行总线的时序、串扰和功耗难以控制;
  • AFE、ADC、SerDes 前端通常以串行或准串行形式输出数据。

而 FPGA 内部逻辑并不适合直接处理 GHz 级别的串行信号。

ISERDESE3 的作用正是在 IO 边界完成“高速 → 低速”的物理级转换,将高速、严格时序约束的采样问题,封装在一个确定行为的原语中,使后级逻辑只需要面对稳定、同步的并行数据。

2.4.3 WHERE

在一个典型的高速接收链路中,ISERDESE3 所处的位置通常如下:

1
2
3
4
5
6
7
8
9
外部高速数据

IBUF / IBUFDS

IDELAYE3

ISERDESE3

并行逻辑 / 帧对齐 / FIFO / AXI

其中:

  • IBUF / IBUFDS 负责完成 IO 电平与差分转换;
  • IDELAYE3 用于补偿数据与时钟之间的相位偏差;
  • ISERDESE3 是真正完成串并转换的核心节点
  • !!后级逻辑运行在稳定的 CLKDIV 时钟域中。

可以认为,ISERDESE3 是 IO 物理世界与 FPGA 同步逻辑世界的分界点

2.4.4 HOW

下面给出一个工程里常用的封装方式:输入为串行数据与高速采样时钟,输出为并行数据与有效标志。例程默认启用 FIFO,并在 CLKDIV 域内只要 FIFO 非空就持续读出,适合 ADC/AFE 这种连续数据流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
module iserdes3_rx #(
parameter integer DATA_WIDTH = 8 // 仅支持 4 或 8
)(
input wire rst, // 异步复位
input wire dclk, // 高速采样时钟(bit clock)
input wire clkdiv, // 分频时钟(dclk/DATA_WIDTH)
input wire din, // 串行数据输入

output wire [DATA_WIDTH-1:0] q, // 并行输出
output wire q_valid, // 本例定义:有数据读出即认为有效
output wire fifo_empty // FIFO 空标志(可用于调试/统计)
);

// FIFO 空标志
wire FIFO_EMPTY;

// ISERDESE3 实例
ISERDESE3 #(
.DATA_WIDTH (DATA_WIDTH), // 4 或 8
.FIFO_ENABLE ("TRUE"), // 启用内部 FIFO
.FIFO_SYNC_MODE ("FALSE"), // 固定为 FALSE
.IS_CLK_B_INVERTED (1'b1), // 用参数生成反相 CLK_B
.IS_CLK_INVERTED (1'b0),
.IS_RST_INVERTED (1'b0),
.SIM_DEVICE ("ULTRASCALE")
) u_iserdes3 (
.FIFO_EMPTY (FIFO_EMPTY), //O,fifo空标志位
.INTERNAL_DIVCLK(), //FIFO 模式不使用

.Q (q), //O,并行输出
.CLK (dclk), //I,高速串行时钟输入.480MHz
.CLKDIV (clkdiv), //I,120MHz分频时钟输入
.CLK_B (dclk), //I,物理只接一根 dclk,由参数反相
.D (din), //I,数据输入

.FIFO_RD_CLK (clkdiv), //I,FIFO读时钟
.FIFO_RD_EN (~FIFO_EMPTY), //I,FIFO读使能,非空就读
.RST (rst) //1位输入:异步复位
);

assign fifo_empty = FIFO_EMPTY;

// q_valid 的定义需要与系统语义一致:
// 若希望每个 clkdiv 周期都有“是否读到数据”的标志,可用读使能作为有效标志。
assign q_valid = ~FIFO_EMPTY;

endmodule

2.4.5 TIPS

  1. DATA_WIDTH 的选择

DATA_WIDTH 决定每个 CLKDIV 周期输出多少位数据:

  • 4:适合中等速率接口;
  • 8:适合更高速率、降低后级逻辑频率压力。

必须保证:

① SDR(单沿采样)下(

1
2
3
.IS_CLK_B_INVERTED(1'b0),
.CLK(Dclk),
.CLK_B(~Dclk), // 或者直接接差分时钟的反相端

)

fCLK_DIV=fCLKDATA_WIDTH f_{CLK\_DIV}=\frac{f_{CLK}}{DATA\_WIDTH}

② DDR 采样下(

1
2
3
.IS_CLK_B_INVERTED(1'b1),
.CLK(Dclk),
.CLK_B(Dclk),

)

fCLKDIV=fCLK/(DATA_WIDTH/2) f_{CLKDIV} = f_{CLK} / (DATA\_WIDTH / 2)
  1. CLK 与 CLK_B 的使用技巧

ISERDESE3 内部基于 DDR 采样结构,理论上需要正反两相高速时钟。

工程中常见做法是:

  • 物理上只接一根高速时钟;
  • 通过 IS_CLK_B_INVERTED 参数在原语内部完成反相。这样就不用通过PLL再生成新的反相时钟信号了。

这种方式更利于时钟树规划,也减少了额外布线与约束复杂度。


  1. FIFO_ENABLE 的工程意义

启用 FIFO 后,ISERDESE3 不再是“采样即输出”的结构,而是:

  • 高速采样写入 FIFO;
  • 低速逻辑按自身节奏读取。

这在以下场景中尤为重要:

  • 高速采样域与系统逻辑域存在不可避免的相位漂移;
  • 后级逻辑需要连续、稳定的数据流;
  • 希望降低 CDC 风险。

一旦启用 FIFO,FIFO_EMPTY 就成为数据有效性的唯一依据


  1. FIFO_RD_EN 的推荐策略

连续流式数据(如 ADC)中,最稳妥的方式是:

1
FIFO_RD_EN = ~FIFO_EMPTY

避免在 FIFO 为空时误读,同时保证最大吞吐。

若是突发或分帧数据,则需要额外的读控制逻辑,而不能直接使用该简化策略。

2.5 xpm_cdc_single

1
2
3
4
5
6
7
8
9
10
11
12
13
xpm_cdc_single #(
.DEST_SYNC_FF(4), // DECIMAL; range: 2-10
.INIT_SYNC_FF(0), // DECIMAL; 0=disable simulation init values, 1=enable simulation init values
.SIM_ASSERT_CHK(0), // DECIMAL; 0=disable simulation messages, 1=enable simulation messages
.SRC_INPUT_REG(1) // DECIMAL; 0=do not register input, 1=register input
)
xpm_cdc_single_inst (
.dest_out(dest_out), // 1-bit output: src_in synchronized to the destination clock domain. This output is registered.

.dest_clk(dest_clk), // 1-bit input: Clock signal for the destination clock domain.
.src_clk(src_clk), // 1-bit input: optional; required when SRC_INPUT_REG = 1
.src_in(src_in) // 1-bit input: Input signal to be synchronized to dest_clk domain.
);

2.5.1 WHAT

xpm_cdc_single 是 Xilinx 提供的跨时钟域(CDC)宏原语,专门用于将单 bit 信号从一个时钟域安全地同步到另一个时钟域。它的内部本质上是一条由多级触发器串联组成的同步链(synchronizer chain),通过在目标时钟域连续打多拍来抑制亚稳态传播。Vivado 会自动对这些寄存器施加 ASYNC_REG 约束,确保布局布线工具将它们放置在相邻位置,最大限度地减小级间延迟。

2.5.2 WHY

当一个信号从时钟域 A 传递到时钟域 B 时,如果两个时钟之间没有固定的相位关系(异步时钟),信号在 B 域的采样时刻可能恰好落在建立时间或保持时间的违例窗口内,导致触发器进入亚稳态。

亚稳态的输出值不确定,如果直接被后续逻辑使用,可能引发不可预测的行为甚至系统崩溃。

xpm_cdc_single 通过多级同步寄存器,给亚稳态留出足够的恢复时间,使其在到达后续逻辑前收敛到确定的 0 或 1,从而保证跨时钟域传输的可靠性。

2.5.3 WHERE

适用于任何需要将单 bit 控制信号或状态信号从一个时钟域传递到另一个时钟域的场景。

典型用法包括:将慢速时钟域的使能信号同步到快速时钟域将外部异步输入(如按键去抖后的信号)同步到系统时钟域将状态标志位(如 FIFO 的空/满标志)在不同时钟域之间传递

注意,该原语仅适用于单 bit 电平信号的同步,不适用于多 bit 总线(应使用 xpm_cdc_gray 或 xpm_cdc_handshake)或脉冲信号(应使用 xpm_cdc_pulse)。

2.5.4 HOW

端口名称 类型 说明
.dest_out output 同步后的输出信号,已在 dest_clk 域下寄存
.dest_clk input 目标时钟域的时钟信号
.src_clk input 源时钟域的时钟信号,SRC_INPUT_REG=1 时必须连接
.src_in input 需要跨时钟域同步的 1-bit 输入信号
参数名称 默认值 说明
DEST_SYNC_FF 4 目标时钟域同步寄存器级数,范围 2~10,级数越多亚稳态概率越低
INIT_SYNC_FF 0 是否在仿真中对同步寄存器赋初值
SIM_ASSERT_CHK 0 是否在仿真中启用断言检查消息
SRC_INPUT_REG 1 是否在源时钟域先打一拍再送入同步链,设为1时需要连接 src_clk

2.6 xpm_cdc_async_rst

1
2
3
4
5
6
7
8
9
10
xpm_cdc_async_rst #(
.DEST_SYNC_FF (4), // DECIMAL; range: 2-10
.INIT_SYNC_FF (0), // DECIMAL; 0=disable simulation init values, 1=enable simulation init values
.RST_ACTIVE_HIGH (1) // DECIMAL; 0=active low reset, 1=active high reset
)
xpm_cdc_async_rst_inst (
.dest_arst (dest_arst), // 1-bit output: src_arst synchronized to dest_clk domain
.dest_clk (dest_clk), // 1-bit input: Destination clock
.src_arst (src_arst) // 1-bit input: Source asynchronous reset signal
);

2.6.1 WHAT & WHY

xpm_cdc_async_rst 是 Xilinx 提供的异步复位信号跨时钟域同步原语

它解决的是一个非常具体的问题:异步复位信号的释放毛刺。异步复位的置位(assert)本身不需要同步——无论什么时候来,都能立刻让目标时钟域进入复位状态。但复位的释放(deassert)如果恰好落在目标时钟的建立/保持时间窗口内,目标域的触发器就会进入亚稳态,导致部分逻辑退出复位而部分没有,系统行为不可预测。

xpm_cdc_async_rst 的策略是:异步置位、同步释放。复位信号的到来立即生效(不等时钟沿),但复位的撤除必须在 dest_clk 的上升沿经过多级同步寄存器后才传递给后续逻辑,确保所有触发器在同一个时钟沿统一退出复位。

和 xpm_cdc_single 的区别:xpm_cdc_single 同步的是普通数据/控制信号(置位和释放都需要同步),xpm_cdc_async_rst 专门针对复位信号优化(置位不同步,释放才同步),响应更快。

2.6.2 WHERE

适用于任何需要将异步复位信号引入到一个有明确时钟域的模块中的场景。

典型用法包括:外部复位按键信号同步到系统时钟域全局异步复位 rst_0 同步到各个独立时钟域PLL/MMCM 未锁定信号作为复位源送入下游模块

具体应用示例可以看uiData_Fifo模块中的fifo.c

2.6.3 HOW

参数名称 默认值 说明
DEST_SYNC_FF 4 同步寄存器级数,范围 2~10,级数越多亚稳态概率越低
INIT_SYNC_FF 0 是否在仿真中对同步寄存器赋初值
RST_ACTIVE_HIGH 1 复位极性,1=高电平复位,0=低电平复位
端口名称 类型 说明
.dest_arst output 同步后的复位输出,异步置位、同步释放,已对齐到 dest_clk
.dest_clk input 目标时钟域的时钟信号
.src_arst input 源异步复位信号,不需要与任何时钟对齐

2.6.5 TIPS

  1. 不需要提供 src_clk

和 xpm_cdc_single 不同,这个原语没有 src_clk 端口。因为源信号本身就是异步的(可能来自按键、外部芯片、PLL 状态等),根本没有所属的时钟域,不需要在源侧打拍。

  1. RST_ACTIVE_HIGH 必须和实际极性匹配

如果源复位是高有效(rst=1 为复位),设 RST_ACTIVE_HIGH=1;如果是低有效(rst=0 为复位),设 RST_ACTIVE_HIGH=0。设错了会导致复位逻辑完全反转。

  1. 每个时钟域需要独立的实例

如果系统中有多个时钟域都需要用到同一个异步复位,必须为每个时钟域各实例化一个 xpm_cdc_async_rst,不能共用一个输出。因为同步释放必须分别对齐到各自的时钟,共用会破坏同步语义。

2.7 xpm_fifo_async

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
xpm_fifo_async #(
.FIFO_MEMORY_TYPE ("auto"), // "auto", "block", "distributed"
.ECC_MODE ("no_ecc"), // "no_ecc" or "en_ecc"
.RELATED_CLOCKS (0), // 0=异步时钟, 1=同源时钟
.FIFO_WRITE_DEPTH (16), // 写深度, 必须是2的幂
.WRITE_DATA_WIDTH (384), // 写数据位宽
.WR_DATA_COUNT_WIDTH(4), // 写侧计数器位宽
.PROG_FULL_THRESH (11), // 可编程将满阈值
.FULL_RESET_VALUE (0), // 复位期间full信号的值
.USE_ADV_FEATURES ("1707"), // 高级特性开关(16进制)
.READ_MODE ("std"), // "std"=标准读, "fwft"=首字直通
.FIFO_READ_LATENCY (1), // 读延迟(std模式下>=1, fwft模式下=0)
.READ_DATA_WIDTH (384), // 读数据位宽
.RD_DATA_COUNT_WIDTH(4), // 读侧计数器位宽
.PROG_EMPTY_THRESH (3), // 可编程将空阈值
.DOUT_RESET_VALUE ("0"), // 复位期间dout的值
.CDC_SYNC_STAGES (2), // 内部CDC同步级数, 范围2~8
.WAKEUP_TIME (0) // 0=不休眠, 2=休眠模式
)
xpm_fifo_async_inst (
.rst (rst),
.wr_clk (wr_clk),
.wr_en (wr_en),
.din (din),
.full (full),
.overflow (overflow),
.prog_full (prog_full),
.wr_data_count (wr_data_count),
.almost_full (almost_full),
.wr_ack (wr_ack),
.wr_rst_busy (wr_rst_busy),
.rd_clk (rd_clk),
.rd_en (rd_en),
.dout (dout),
.empty (empty),
.underflow (underflow),
.rd_rst_busy (rd_rst_busy),
.prog_empty (prog_empty),
.rd_data_count (rd_data_count),
.almost_empty (almost_empty),
.data_valid (data_valid),
.sleep (1'b0),
.injectsbiterr (1'b0),
.injectdbiterr (1'b0),
.sbiterr (),
.dbiterr ()
);

2.7.1 WHAT & WHY

xpm_fifo_async 是 Xilinx 提供的异步 FIFO原语,用于在两个没有固定相位关系的时钟域之间安全地传输多 bit 数据。

跨时钟域传多 bit 数据是 FPGA 设计中最容易出错的地方之一。

xpm_cdc_single 只能传 1bit,xpm_cdc_array_single 虽然能传多 bit 但不保证各 bit 同时到达(可能读到从未存在过的中间值),xpm_cdc_handshaking 能保证一致性但需要握手协议、吞吐率低。对于连续数据流(如 ADC 采样数据),异步 FIFO 是唯一既能保证数据一致性、又能维持高吞吐的方案。

xpm_fifo_async 内部通过格雷码指针跨时钟域传递读写地址,保证指针每次只变化 1bit,从根本上消除多 bit 跨时钟域的亚稳态风险。写侧和读侧各自在自己的时钟域内独立工作,FIFO 作为弹性缓冲吸收两个时钟之间的频率差和相位差。

2.7.2 WHERE

适用于任何需要在两个异步时钟域之间传输多 bit 数据流的场景。

在 uiData_Fifo 工程中出现了两处:

1. fifo_c 内部(x4): 4 片 A_Chip_Decode 的输出数据从各自的 Fclk_w[i](40MHz 写时钟)搬到统一的 Fclk_r(40MHz 读时钟)。配置为 384bit 宽、深度 16。

2. 顶层输出级(x1): interp 模块的输出从 interp_clk(80MHz)搬到用户提供的 rd_clk。配置为 1536bit 宽、深度 32、distributed RAM 实现。

2.7.3 HOW

端口分为写侧、读侧、复位、休眠/ECC 四组。

A. 写侧端口

端口名 方向 说明
wr_clk input 写时钟
wr_en input 写使能,wr_clk 上升沿采样,full=1 时写入无效
din input 写数据,位宽 = WRITE_DATA_WIDTH 384
full output 满标志,为 1 时不能写
almost_full output 将满标志(还剩 1 个空位)
prog_full output 可编程将满标志(剩余空位 <= PROG_FULL_THRESH)
overflow output 溢出标志,full=1 时仍写入则置 1(调试用)
wr_data_count output 写侧数据计数(当前 FIFO 中有多少数据,写时钟域视角)
wr_ack output 写确认,成功写入一笔数据后置 1
wr_rst_busy output 写侧复位忙,为 1 时不能写入

B. 读侧端口

端口名 方向 说明
rd_clk input 读时钟
rd_en input 读使能,rd_clk 上升沿采样,empty=1 时读取无效
dout output 读数据,位宽 = READ_DATA_WIDTH
empty output 空标志,为 1 时不能读
almost_empty output 将空标志(只剩 1 笔数据)
prog_empty output 可编程将空标志(剩余数据 <= PROG_EMPTY_THRESH)
underflow output 下溢标志,empty=1 时仍读取则置 1(调试用)
rd_data_count output 读侧数据计数(读时钟域视角)
data_valid output 数据有效标志,成功读出一笔数据后置 1
rd_rst_busy output 读侧复位忙,为 1 时不能读取

C. 复位

端口名 方向 说明
rst input 异步复位,高电平有效,原语内部自动做释放同步

D. 休眠与 ECC(通常不用)

端口名 方向 说明
sleep input 休眠模式,不用时接 1’b0
injectsbiterr input 注入单 bit 错误(ECC 测试),不用时接 1’b0
injectdbiterr input 注入双 bit 错误(ECC 测试),不用时接 1’b0
sbiterr output 单 bit 纠错标志
dbiterr output 双 bit 不可纠错标志

2.7.4 TIPS

  1. READ_MODE 的选择

“std”(标准模式):rd_en 拉高后,下一个 rd_clk 周期 dout 才更新,读延迟 = FIFO_READ_LATENCY 个周期。适合对延迟不敏感的场景。

“fwft”(First-Word-Fall-Through):FIFO 非空时,第一笔数据自动出现在 dout 上,rd_en 的作用变成”确认消费当前数据并请求下一笔”。读延迟为 0,适合需要立即看到数据的场景。使用 fwft 时 FIFO_READ_LATENCY 必须设为 0。

  1. FIFO_MEMORY_TYPE 的选择

“distributed”:用 LUT RAM 实现,适合浅 FIFO(深度 <= 32),延迟低但消耗逻辑资源。工程中顶层输出级的 FIFO(深度 32、宽 1536bit)用的就是 distributed。

“block”:用 BRAM 实现,适合深 FIFO 或宽数据,容量大但有固定的读延迟。

“auto”:让 Vivado 根据深度和宽度自动选择。fifo_c 中的 4 个 FIFO 用的是 auto。

  1. PROG_EMPTY_THRESH 和 PROG_FULL_THRESH

这两个是可编程的水位线。prog_empty 在 FIFO 中剩余数据少于等于 PROG_EMPTY_THRESH 时置 1,prog_full 在剩余空位少于等于 PROG_FULL_THRESH 时置 1。

fifo_c 中用 prog_empty(而不是 empty)作为 4 个 FIFO 的同步读控制信号,阈值设为 3。这意味着 FIFO 中数据少于 3 笔就认为”接近空”,停止读取。比用 empty 更保守,留了一点余量防止因为格雷码指针跨时钟域的延迟导致误判空。

  1. rst 的注意事项

复位是异步置位、内部同步释放的,和 xpm_cdc_async_rst 的行为一致。复位释放后,wr_rst_busy 和 rd_rst_busy 会持续一段时间(若干个时钟周期),这期间不能进行读写操作。安全做法是等 wr_rst_busy=0 且 rd_rst_busy=0 之后再开始使用 FIFO。

  1. USE_ADV_FEATURES

这是一个 16 进制字符串,每个 bit 控制一个高级特性输出端口是否使能。工程中用的 “1707” 对应的二进制是 0001_0111_0000_0111,具体哪些端口被使能可以查 Xilinx 的 XPM 文档。不需要的输出端口关掉可以节省一点逻辑资源。如果不确定,设成 “1F1F” 全部打开也不影响功能

bit 位 对应功能 默认值 说明
[0] overflow 1 (开) 写溢出标志,full=1 时仍写入则置 1
[1] prog_full 1 (开) 可编程将满标志
[2] wr_data_count 1 (开) 写侧数据计数
[3] almost_full 0 (关) 将满标志(还剩 1 个空位)
[4] wr_ack 0 (关) 写确认标志
[5:7] 未使用
[8] underflow 1 (开) 读下溢标志,empty=1 时仍读取则置 1
[9] prog_empty 1 (开) 可编程将空标志
[10] rd_data_count 1 (开) 读侧数据计数
[11] almost_empty 0 (关) 将空标志(只剩 1 笔数据)
[12] data_valid 0 (关) 数据有效标志,成功读出一笔后置 1
[13:15] 未使用
  1. 读写位宽可以不同

WRITE_DATA_WIDTH 和 READ_DATA_WIDTH 可以设成不同的值,实现位宽转换。比如写侧 32bit、读侧 128bit,每写 4 次读 1 次。但两者的比值必须是 2 的幂。当前工程中写读位宽相同,没有用到这个特性。