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 差分时钟相关设计

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 BUFG使用

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

2.3 IDELAYE3

2.3.1 IDELAYE3 是什么与为什么

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 IDELAYE3 在系统中的位置

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

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

典型结构如下:

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

IBUFDS

IDELAYE3 ← 关键对齐点

ISERDESE3

并行数据

2.3.3 使用

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 校准和温漂补偿的基准条件

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),
.INTERNAL_DIVCLK(), // FIFO 模式不使用

.Q (q),
.CLK (dclk),
.CLKDIV (clkdiv),
.CLK_B (dclk), // 物理只接一根 dclk,由参数反相
.D (din),

.FIFO_RD_CLK (clkdiv),
.FIFO_RD_EN (~FIFO_EMPTY), // FIFO 非空就读
.RST (rst)
);

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:适合更高速率、降低后级逻辑频率压力。

必须保证:

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

这是硬性约束,而非推荐条件。


  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 为空时误读,同时保证最大吞吐。

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