文章来源:OpenFPGA
用 FPGA 做设计时,我们总在寻找用器件内可用资源实现需求的最高效方式。
组合逻辑用 LUT 实现,移位寄存器可以用 SRL,算术运算可以用 DSP Slice,存储可以用寄存器或 BRAM。
这些 FPGA 资源有意思的地方在于,它们往往还内置了一些设计师容易忽略、或者根本不知道的额外能力。理解这些特性,能带来不同的实现思路,帮我们获得更好的资源利用率和时序性能。
其中较少被讨论的一项,就是 DSP48 和 DSP58 资源内置的模式检测(pattern detection)能力。
那我们就来看看,怎么用 DSP 资源做模式识别。这个能力在 DSP48E2 的 UG579 里有描述,但我们需要深入拆解一下并走一个实际例子。
本文聚焦 DSP48E2,它提供一个 48 位模式检测器。
基本机制
在 DSP48E2 里,模式检测器把算术逻辑单元(ALU)的输出与一个指定的 48 位模式(pattern)作比较。如果 ALU 输出与配置的模式匹配,PATTERNDETECT 输出就被置位。
这个比较是针对 DSP 输出 P 进行的,而不是一个直接连到任意信号的独立比较器。不过,DSP 数据通路的灵活性,让我们能完成一系列不同的比较。

除了模式寄存器,还有一个 48 位掩码(mask),可以把某些位排除在比较之外。当我们只关心输出值的一部分时,这特别有用 —— 后面会看到。
掩码的工作方式是:1 表示该位被忽略,0 表示对应位必须与配置的模式匹配。
举个例子,假设我们想检测 P 的低 8 位等于 0xA5,而不管高 40 位是什么。可以这样配置:
PATTERN = 48'h0000000000A5MASK = 48'hFFFFFFFFFF00
这样,只要低 8 位等于 0xA5,无论其余位是什么,模式检测器都会置位。
当然,如果要比较完整的 48 位值,就把掩码设为零,让每一位都参与比较。
有一点很重要:DSP48E2 的默认掩码是按溢出 / 下溢检测配置的,而不是完整的 48 位相等比较,所以我们需要根据具体应用选择合适的掩码。
DSP48E2 还提供第二个输出 PATTERNBDETECT,它拿配置模式的反码来比较。
这并不等于 PATTERNDETECT 的简单取反。视掩码而定,输出可能既不匹配原模式、也不匹配其反码。
模式不必在实现时固定
另一个有用的能力是:比较用的模式不必在综合 / 实现阶段就定死。
模式可以通过 PATTERN 属性提供,也可以通过 C 输入提供,由 SEL_PATTERN 选择。这样我们既可以做固定比较,也可以让比较值在运行中变化。
当用 C 输入作为动态模式时,必须考虑数据通路配置,以及 C 输入上使能的各级寄存器。UG579 还规定:当 C 被用作比较模式时,Z 多路选择器应选零。
这在比较阈值或比较值需要在运行中改变时,提供了相当大的灵活性。
最常见的用途:溢出与下溢
模式检测最常见的应用之一,是在算术运算中识别溢出(overflow)和下溢(underflow)。
这在处理累加器时特别有用 —— 我们需要知道累加结果何时超出了允许的数值范围。
DSP48E2 提供专用的 OVERFLOW 和 UNDERFLOW 输出,它们利用模式检测逻辑和先前的模式检测状态,判断结果何时越出允许范围。
这里有个重要区别:模式检测器本身不会相对 P 输出额外增加一个时钟周期的延迟。模式检测结果和对应的 P 值在同一个周期可用。
但溢出 / 下溢功能要利用上一次的比较状态,来检测 "越界" 这一跳变。这意味着上一次的值对检测过程很重要,而不是说模式检测器简单地把比较延迟了一个周期。
别只把它当成溢出检测
很容易把模式检测器只当成溢出 / 下溢特性。但它其实远不止于此。
能在 DSP 内部直接完成带掩码的 48 位相等比较,打开了不少其他应用可能:
终端计数检测(terminal-count detection)
计数器回绕(counter wrap-around)
选定范围和位域检查
饱和(saturation)支持
舍入操作
与运行时变量的比较
和往常一样,选哪种方案取决于设计需求,以及可用的 FPGA 资源。
放在 DSP 里做,有什么好处
在 DSP48 里实现模式检测、而不是用可编程逻辑,有几个潜在好处,其中最有意思的是时序。
如果我们在 FPGA fabric 里实现一个宽计数器或累加器,可能要考虑算术进位链、用 LUT 实现的比较,以及这些元件之间的布线。
对一个带终端计数检测的计数器,比较逻辑和相关控制逻辑可能变成关键路径,具体取决于实现方式。
而在 DSP48E2 里,模式检测器是用专用硬件直接接到 ALU 输出上的。模式检测结果与 P 一起寄存,所以比较结果与它所描述的值是对齐的。
当比较能在 DSP 内完成时,就不再需要单独的 fabric 比较器及其布线。
不过,这并不意味着模式检测没有时序代价。UG579 指出,启用该特性后模式检测路径会有中等程度的速度下降。
和任何 FPGA 实现一样,应该实际验证达到的时序,而不是想当然地认为某种方案总是更快。
另一个考量是资源利用率:把算术和比较都放进 DSP,能减少可编程逻辑里所需的 LUT 和寄存器数量。当 DSP 本来就在做这个算术运算时,这特别划算。
当然,为了一个计数器或比较器就占用一整块 DSP48,未必总是最高效的做法。如果 DSP 资源已经很紧张,用 CLB 实现同样功能可能更可取。
最有效的方案,取决于器件内可用资源的平衡,以及应用的时序需求。
实例:带自动复位的大计数器
DSP48 模式检测器一个有意思的应用,是实现 "到达终端计数自动复位" 的大计数器。
计数器本质上是个简单累加器,每个时钟周期把上一个值加一。如果希望计数器在某个特定值处回绕,还需要检测该值并复位计数器。
DSP48E2 提供了必要功能,可以在一个 DSP slice 内同时实现算术运算和终端计数检测。
实现计数器时,我们配置 ALU 做加法:把寄存后的 P 输出经 X 多路选择器反馈回来;把其他算术操作数选为零、进位输入设为 1,ALU 就会在每个使能的时钟周期把上一个 P 值加一。
这个反馈结构要求 P 输出寄存器被使能。
然后配置模式检测器识别所需的终端计数值。
例如,想做一个 0 到 999 的计数器,就把模式检测器配置为识别 999,并启用自动复位功能。
关键模式检测设置:
PATTERN = 48'h0000000003E7(即 999)MASK = 48'h000000000000(全比较)SEL_PATTERN = "PATTERN"USE_PATTERN_DETECT = "PATDET"AUTORESET_PATDET = "RESET_MATCH"
这些设置下,当 P 达到 999 时模式检测器置位,自动复位功能便在下一个时钟沿复位 P 寄存器。
于是计数器先走到终端值,再回到零,得到我们需要的计数序列。

重要提示:自动复位是在下一个时钟沿生效,而不是检测到模式时立即生效。
AUTORESET_PRIORITY 属性还能决定自动复位与 P 寄存器时钟使能之间的交互方式。如果计数器可能被暂停或禁用,这一点需要纳入考虑。
仿真初始复位序列时也要留意:UG579 描述了复位之后 PATTERNDETECT 会出现一个瞬态置位,实现计数器控制时需要注意。
仿真结果展示了计数器递增、检测到终端值、并借助 DSP 的自动复位能力回到零的过程。

适用边界
当然,这种做法未必总是 DSP48 最高效的用法。但当你需要一个宽计数器、恰好 DSP 资源有余、而 fabric 资源或时序吃紧时,这是一招值得留着的技巧。
单个 DSP48E2 提供 48 位结果,所以需要超过 48 位的计数器,就得用更多资源做更宽的实现。
通过改变配置的模式、调整掩码、或用 C 输入引入动态模式,这个例子还能改造成其他应用。
GitHub 仓库里的示例代码演示了如何把 DSP48E2 的 ALU 和模式检测器结合起来,实现一个简单计数器。
https://github.com/ATaylorCEngFIET/mZ683
这个例子的目的不是宣称 "基于 DSP 的计数器一定优于 fabric 实现",而是展示 FPGA 架构里另一个可用的能力。
一旦理解了模式检测器怎么工作,就更容易看出同一技术还能用在哪些功能上 —— 终端计数检测、累加器监控、可动态配置的比较等。
结语
DSP48 模式检测器是又一个 "当我们只盯着某个 FPGA 资源的主要用途时很容易忽略" 的特性。
虽然我们未必每个设计都会用到它,但理解算术、模式比较和自动复位如何在一个硬化资源内结合,就多了一种实现选择。
这归根到底是 FPGA 设计有趣的地方之一:我们对器件内可用资源理解得越多,实现高效方案的选择就越多。