Veloris.
返回索引
深入原理 2026-02-19

SRL 与触发器:SliceM 的存储魔法与 FF 的设计陷阱

10 分钟
3.2k words

SRL 与触发器:SliceM 的存储魔法与 FF 的设计陷阱

💡 在上一篇文章中,我们提到 SliceM 有两个”超能力”:分布式 RAM 和移位寄存器(SRL)。其中 SRL 是一个被很多初学者忽视、但在实际工程中极其实用的资源——一个 LUT 就能替代 32 个触发器

但另一方面,触发器(FF)本身也藏着不少设计陷阱:同步复位和异步复位该怎么选?为什么一不小心就会综合出锁存器?控制集(Control Set)又是什么?

这篇文章会带你深入 SRL 的工作原理和正确使用姿势,然后聊聊 FF 的四种类型和常见的设计陷阱。


目录


1. SRL:一个 LUT 顶 32 个触发器

1.1 什么是 SRL?

SRL(Shift Register LUT,移位寄存器 LUT) 是 SliceM 中 LUT 的一种特殊配置模式。它把 LUT 内部的 64 个 SRAM 存储单元重新利用——不再存放真值表,而是当作一个串行输入、可寻址输出的移位寄存器

传统的移位寄存器需要 N 个触发器级联:

D → FF₁ → FF₂ → FF₃ → ... → FFₙ → Q

而 SRL 只需要 1 个 LUT 就能实现最多 32 位的移位:

D → [LUT 内部 32 个 SRAM 单元] → Q(由地址选择输出哪一位)

资源节省是惊人的:一个 32 位延迟线,用 FF 需要 32 个触发器,用 SRL 只需要 1 个 LUT。

1.2 SRL 的应用场景

场景说明
数据延迟线在流水线设计中对齐不同路径的数据延迟
小型 FIFO配合计数器实现简单的先入先出缓冲
去抖动对输入信号进行多拍采样
串并转换串行数据的暂存和并行输出

2. SRL 的工作原理

2.1 SRLC32E:32 位移位寄存器原语

最常用的 SRL 原语是 SRLC32E,它的端口如下:

端口方向作用
D输入串行数据输入,每个时钟周期写入第一位
CLK输入时钟信号
CE输入时钟使能,高电平有效
A[4:0]输入5 位地址,选择输出哪一位(移位长度 = A + 1)
Q输出由地址选中的数据位
Q31输出第 31 位(末位)的数据,用于级联

2.2 移位操作

每当 CLK 上升沿到来且 CE 为高时:

  1. 输入 D 的值被写入第 0 位
  2. 原来第 0 位的数据移到第 1 位,第 1 位移到第 2 位……以此类推
  3. 原来第 31 位的数据从 Q31 输出(可用于级联到下一个 SRL)

2.3 读取操作

读取是异步的——改变地址 A 会立即(经过 LUT 延迟后)改变输出 Q,不需要等待时钟。

  • 静态读取:地址固定不变,Q 输出随每次移位更新(等效于固定长度的延迟线)
  • 动态读取:地址动态变化,可以随时”窥探”移位寄存器内部任意位置的数据

2.4 级联扩展

单个 SRLC32E 最多 32 位。通过 Q31 级联,可以构建更长的移位寄存器:

级联方式最大位数所需资源
2 个 SRL32 + 1 个 F7MUX64 位2 个 LUT
3 个 SRL32 + 3 个 MUX96 位3 个 LUT
4 个 SRL32 + 3 个 MUX128 位4 个 LUT(= 1 个 SliceM)

一个 SliceM 有 4 个 LUT,所以一个 SliceM 最多实现 128 位的移位寄存器。由于 4 个 LUT 在同一个 Slice 内,布线短、延迟小,非常有利于时序收敛。


3. SRL 的正确使用姿势

3.1 方式一:原语例化

直接例化 SRLC32E 原语:

SRLC32E #(
    .INIT(32'h00000000)
) srl_inst (
    .Q(Q),
    .Q31(shift_out),
    .A(5'b01001),    // 移位长度 = 9 + 1 = 10
    .CE(ce),
    .CLK(clk),
    .D(shift_in)
);

3.2 方式二:综合工具推断(推荐)

更常见的做法是写常规的 RTL 代码,让综合工具自动推断为 SRL:

reg [31:0] dff;

always @(posedge clk) begin
    if (ce) begin
        dff[31:0] <= {dff[30:0], shift_in};
    end
end

assign shift_out = dff[31];
assign Q = dff[9];

3.3 致命错误:加了复位信号

下面的代码不会被推断为 SRL,而是退化为 32 个 LUT + 32 个 FF:

// ❌ 错误示范:SRL 不能带复位!
always @(posedge clk) begin
    if (rst)           // ← 这行导致无法推断 SRL
        dff <= 0;
    else if (ce)
        dff <= {dff[30:0], shift_in};
end

原因:SRLC32E 原语没有复位端口。流水线延迟线本质上不需要复位——数据会自然地被新数据”冲刷”掉。加复位是画蛇添足,不仅浪费资源,还可能影响时序。

💡 工程师手记:我曾经在一个信号处理项目中,用移位寄存器做 FIR 滤波器的延迟线。最初代码里习惯性地加了复位信号,结果综合报告显示用了 256 个 FF 而不是 8 个 LUT。删掉复位后,资源占用直接降了 97%。从此我养成了一个习惯:写移位寄存器时,先问自己”这里真的需要复位吗?“


4. FF:FPGA 时序逻辑的基石

4.1 FF 的基本角色

FF(Flip-Flop,触发器) 是 FPGA 中实现时序逻辑的核心组件。它在时钟边沿锁存数据,是计数器、状态机、流水线等一切时序电路的基础。

在 Xilinx 7 系列中,每个 Slice 有 8 个 FF,分为两组:

组别数量能力
FF 组4 个只能配置为边沿触发的 D 触发器
FF/Latch 组4 个可配置为 D 触发器电平敏感的锁存器

重要限制:当 FF/Latch 组被配置为锁存器时,同一 Slice 中的 FF 组不能使用

4.2 FF 的输入来源

每个 FF 的数据输入 D 有两种来源:

  1. LUT 输出:通过 FFMUX 从对应 LUT 的输出获取数据(最常见)
  2. 旁路输入(BYPASS):通过 AX/BX/CX/DX 端口直接输入,绕过 LUT

4.3 共享控制信号

同一个 Slice 内的 8 个 FF 共享以下控制信号:

  • CLK:时钟
  • CE:时钟使能
  • S/R:置位/复位

这意味着:如果你的设计中有些 FF 需要同步复位,有些需要异步复位,它们不能放在同一个 Slice 内——这就引出了”控制集”的概念。


5. FF 的四种类型:FDRE、FDSE、FDCE、FDPE

根据 S/R 端口的配置方式,Xilinx 7 系列的 FF 有四种原语类型:

原语复位/置位方式Verilog 描述特征
FDRE同步复位(Reset)always @(posedge clk) if(rst) q <= 0;
FDSE同步置位(Set)always @(posedge clk) if(set) q <= 1;
FDCE异步复位(Clear)always @(posedge clk or posedge rst)
FDPE异步置位(Preset)always @(posedge clk or posedge set)

同步 vs 异步:该怎么选?

方面同步复位(FDRE/FDSE)异步复位(FDCE/FDPE)
时序分析简单,复位信号是数据路径的一部分复杂,需要额外的恢复/移除时间约束
资源消耗复位逻辑可能占用 LUT 输入使用 FF 的专用异步端口,不占 LUT
推荐程度✅ 现代设计推荐⚠️ 仅在必要时使用

⚠️ 设计建议:现代 FPGA 设计推荐使用同步复位(FDRE),并且只对确实需要复位的寄存器添加复位逻辑。不要给所有寄存器都加复位——这会增加布线负担,降低时钟频率。


6. 设计陷阱:锁存器的意外产生

6.1 锁存器 vs 触发器

特性锁存器(Latch)触发器(Flip-Flop)
敏感方式电平敏感边沿敏感
透明性使能有效时,输出跟随输入仅在时钟边沿采样
毛刺敏感高——使能期间毛刺直接传到输出低——只在边沿瞬间采样
时序分析困难简单

在绝大多数 FPGA 设计中,锁存器是要避免的。它们通常是代码不规范导致的意外产物。

6.2 三种常见的锁存器陷阱

陷阱 1:不完整的 if 语句

// ❌ 缺少 else 分支,q 在 enable=0 时需要保持值 → 推断锁存器
always @(*) begin
    if (enable)
        q = d;
end

修正

// ✅ 方法 1:补全 else
always @(*) begin
    if (enable) q = d;
    else q = 1'b0;
end

// ✅ 方法 2:赋默认值
always @(*) begin
    q = 1'b0;       // 默认值
    if (enable) q = d;
end

陷阱 2:不完整的 case 语句

// ❌ 缺少 default,sel=00 或 11 时 q 未赋值 → 推断锁存器
always @(*) begin
    case (sel)
        2'b01: q = a;
        2'b10: q = b;
    endcase
end

修正:添加 default 分支或在块开头赋默认值。

陷阱 3:部分输出未在所有分支赋值

// ❌ q2 只在 IDLE 状态赋值,PROCESS 状态未赋值 → q2 推断锁存器
always @(*) begin
    case (state)
        IDLE:    begin q1 = 1; q2 = x; end
        PROCESS: begin q1 = y; end        // q2 呢?
        default: q1 = 0;
    endcase
end

6.3 黄金法则

在组合逻辑 always @(*) 块中,每一个被赋值的信号,必须在所有可能的执行路径中都有明确的赋值。

最简单的实践:在 always 块的最开头给所有输出信号赋默认值。

💡 工程师手记:我在一个状态机设计中曾经被锁存器坑过一次。综合报告里有一条 Warning:“Inferred latch for signal ‘data_out’”,但我当时没在意。结果上板后发现输出信号偶尔会“卡”在某个值上不动,排查了两天才定位到是 case 语句缺少 default 分支导致的锁存器。从那以后,我养成了两个习惯:一是综合后必看 Warning,二是组合逻辑块开头必赋默认值。


7. 控制集:影响资源利用率的隐形杀手

7.1 什么是控制集?

控制集(Control Set) 是指一组 FF 共享的控制信号组合:CLK + CE + S/R

同一个 Slice 内的 FF 必须属于同一个控制集。如果你的设计中有太多不同的控制集,FF 会被分散到更多的 Slice 中,导致:

  • Slice 利用率下降(每个 Slice 只用了部分 FF)
  • 布线变得更复杂
  • 可能影响时序

7.2 如何减少控制集?

策略说明
统一复位信号尽量让所有 FF 使用同一个复位信号
统一时钟使能避免为每个模块定义独立的 CE 信号
减少不必要的复位不是所有 FF 都需要复位,流水线寄存器通常不需要
同步/异步统一全设计统一使用同步复位或异步复位,不要混用

💬 你可能会问:怎么查看设计中的控制集数量?

在 Vivado 的综合报告中搜索 “Control Sets”,可以看到设计使用了多少个不同的控制集。如果数量过多,报告通常会给出警告。你也可以在 Implementation 报告的 “Control Set Information” 部分看到每个控制集的具体组成(CLK + CE + SR)和影响的 FF 数量。一般来说,控制集数量不应超过可用 Slice 数量的 10%。


8. 总结

主题核心要点你需要记住的一件事
SRL用 LUT 实现移位寄存器,1 个 LUT = 32 位不能带复位信号,否则退化为 FF 链
SRL 级联单个 SliceM 最多 128 位级联在 Slice 内完成,时序友好
FF 四种类型FDRE/FDSE(同步)、FDCE/FDPE(异步)推荐同步复位(FDRE)
锁存器陷阱组合逻辑中未完全赋值会产生锁存器always @(*) 开头赋默认值
控制集同 Slice 的 FF 共享 CLK/CE/SR减少独特控制信号,提高 Slice 利用率

SRL 是 FPGA 工程师的”省资源神器”,但需要正确的代码风格才能触发。FF 是时序逻辑的基石,但控制信号的选择和锁存器的规避需要养成良好的编码习惯。


常见问题

Q1:SRL 的输出是同步的还是异步的?

SRL 的 Q 输出是异步的——改变地址 A 会立即改变输出,不需要等待时钟。如果需要同步输出,需要在 Q 后面接一个 FF。Q31(级联输出)也是异步的,但它始终输出末位数据。

Q2:分布式 RAM 和 SRL 可以同时使用吗?

不能。SliceM 中的每个 LUT 在同一时刻只能配置为三种模式之一:查找表、分布式 RAM 或 SRL。但同一个 SliceM 中的不同 LUT 可以配置为不同模式。

Q3:为什么 Vivado 综合报告中的 FF 数量和我预期的不一样?

三个常见原因:① 部分 FF 被吸收进 DSP 或 Block RAM 的内部寄存器;② 综合工具进行了寄存器复制(Register Duplication)以改善时序;③ 无用的或等效的寄存器被优化移除了。

Q4:异步复位真的不好吗?什么时候该用?

异步复位不是“不好”,而是需要更谨慎的时序约束。在以下场景中可以使用异步复位:① 全局上电复位(通常由外部复位电路提供);② 需要在时钟丢失时仍能复位的安全关键逻辑。但对于普通的数据通路逻辑,同步复位是更安全的选择。

Q5:SRL 和 Block RAM 都能做存储,怎么选?

它们的定位完全不同。SRL 是串行输入、可寻址输出的移位寄存器,适合做延迟线和流水线对齐;Block RAM 是随机读写的大容量存储器,适合做 FIFO、缓存、查找表。简单的判断标准:如果你的数据是“一个接一个流过去”,用 SRL;如果需要“随时读写任意地址”,用 Block RAM。


参考资料


系列导航:本文是「FPGA 内部资源深度解析」系列第 3 篇。

如果这篇文章对你有帮助,欢迎点赞、收藏,也欢迎在评论区分享你踩过的 SRL 或锁存器的坑。

End of file.