2. HLS基础概念与术语

高层次综合(High-Level Synthesis,HLS)把设计者从“逐拍写状态机”的负担中解放出来:输入是带有行为语义的 C/C++/SystemC 代码,输出是可综合的 RTL(Verilog/VHDL),中间经历调度、绑定、资源分配与接口生成。理解 HLS,关键是理解它“在什么约束下,把什么行为,映射成什么硬件”。

一、HLS 的定义与输入输出

HLS 的本质是一次“行为到结构”的编译:前端解析 C/C++ 语义并建立数据流/控制流图(CFG/DFG),中端在时序与资源约束下完成调度(Scheduling)——决定每个操作在哪个时钟周期执行;随后进行绑定(Binding)——把操作映射到功能单元、把变量映射到寄存器或存储器;最后生成 RTL 与接口逻辑。

输入语言通常有三类:C/C++(最常用,需可综合子集)、SystemC(适合事务级建模与周期精确建模)、以及带 HLS pragma/directive 的扩展 C。输出 RTL 则包括:数据通路、控制 FSM、存储器(BRAM/URAM/寄存器堆)、以及总线接口(AXI4、AXI4-Lite、AXI4-Stream 等)。

关键认知:HLS 不是“把 C 直接翻译成 Verilog”,而是“在约束空间里搜索一种硬件实现”。同一段 C 代码,换一组约束,可能得到面积差 5 倍、吞吐率差 10 倍的两种 RTL。

二、时序与资源约束:HLS 的两条缰绳

约束决定综合结果的形态。最核心的两类约束是:

  • 时序约束:目标时钟周期(ns 或 MHz)。它决定一个时钟周期内能完成多少组合逻辑,直接影响调度结果——例如一个 32 位乘法若无法在 5ns 内完成,就会被拆成多周期。
  • 资源约束:可用 DSP、BRAM、LUT、FF 的数量,以及是否允许共享运算单元。资源紧张时,工具会倾向于复用加法器/乘法器,牺牲并行度换面积。

此外还有接口约束(协议、位宽、握手方式)与循环约束(unroll、pipeline、flatten)。它们共同构成 HLS 的“设计空间”。

HLS 输入 → 约束 → 输出 RTL 映射关系 输入语言 C / C++ / SystemC 可综合子集 + pragma CFG / DFG 建模 约束空间 时钟周期 / 频率 DSP / BRAM / LUT 上限 接口协议与握手 输出 RTL 数据通路 + 控制 FSM BRAM / URAM / 寄存器 AXI4 / AXI-Lite / Stream 调度 Scheduling → 绑定 Binding → 资源分配 Allocation 决定操作在哪个周期执行、映射到哪个功能单元 延迟 Latency 首输出所需周期数 吞吐率 Throughput 每周期/每秒处理量 II 启动间隔 流水线迭代间隔

三、接口综合:模块如何与外界对话

接口综合决定 RTL 模块的“引脚长什么样”。常见接口类型包括:

  • AXI4-Lite:用于寄存器配置,低带宽、简单握手。
  • AXI4:用于高带宽存储器映射访问,支持突发传输。
  • AXI4-Stream:用于数据流管道,无地址、连续传输,适合视频/信号处理。
  • ap_hs / ap_vld / ap_ovld:简单握手与有效信号,适合小模块。

接口不仅影响连线,还影响调度:一个 AXI4 读操作可能被综合成多周期握手,从而改变整体延迟。因此接口约束必须与算法结构一起考虑。

四、延迟、吞吐率与 II:三个最易混淆的术语

术语 定义 典型单位 优化手段
延迟 Latency 从输入有效到首个输出有效的时钟周期数 周期(cycles) 减少串行依赖、提高并行度、缩短关键路径
吞吐率 Throughput 单位时间内完成的数据量或事务数 样本/周期、MB/s 流水线、展开循环、增加并行通道
启动间隔 II 流水线中相邻两次迭代开始之间的周期数 周期(cycles) 消除循环携带依赖、合理分配资源

三者关系可用一个直觉公式描述:吞吐率 ≈ 1 / II(在稳定流水后)。而延迟是“第一份结果等多久”,吞吐率是“后续结果多快出来”。II=1 表示每个周期都能启动一次新迭代,是流水线的理想状态。

五、案例:同一段 FIR 的两种综合结果

考虑一个 8 抽头 FIR 滤波器,输入 16 位,系数固定。若不加任何 pragma,HLS 默认可能生成一个“一次乘加、循环 8 次”的串行结构:延迟约 8×N 周期,吞吐率低,但面积小。若对循环加 #pragma HLS PIPELINE II=1 并展开,工具会生成 8 个乘法器并行 + 加法树,II=1,吞吐率提升约 8 倍,但 DSP 消耗也增加约 8 倍。

void fir8(ap_int<16> x[8], ap_int<32> *y) {
#pragma HLS PIPELINE II=1
    ap_int<32> acc = 0;
    for (int i = 0; i < 8; i++) {
#pragma HLS UNROLL
        acc += x[i] * coeff[i];
    }
    *y = acc;
}

这段代码展示了 HLS 的核心权衡:面积换速度。约束(II=1、UNROLL)直接改变了调度与绑定结果,从而改变 RTL 的并行度与资源占用。

建议:在 HLS 中先确定“延迟优先”还是“吞吐率优先”,再选择 pragma。盲目加 UNROLL 会导致布线拥塞和时序违例,反而降低实际频率。

六、常见误区与复盘

  • 误区一:把 C 仿真通过当作 HLS 通过。 C 仿真只验证功能,不验证时序、接口握手和资源可行性。必须做 C/RTL 协同仿真。
  • 误区二:忽略接口延迟。 AXI4 的握手可能引入数十周期延迟,若算法假设“输入立即可用”,综合后行为会偏离。
  • 误区三:II 与延迟混用。 II=1 不代表延迟为 1;延迟可能仍有几十周期,只是流水线稳定后每周期出一个结果。
  • 误区四:约束给得太紧。 目标周期小于关键路径物理极限时,工具会插入大量寄存器或直接失败,需先做时序估算。

复盘方法:每次综合后对比“C 仿真周期数”与“RTL 仿真周期数”,检查 II、延迟、资源报告三项是否与预期一致。若不一致,优先检查循环依赖、接口握手和数组映射方式。

一句话总结:HLS 是在“行为描述”与“物理实现”之间做约束驱动的空间搜索。掌握输入语言、输出 RTL、时序/资源约束、接口综合、延迟/吞吐率/II 这六个术语,就掌握了与 HLS 工具对话的基本词汇。