3. RTL设计与综合:从代码到网表的必经之路

RTL设计与综合,说白了就是把你的想法变成FPGA能理解的电路。很多新手觉得写Verilog就是写代码,其实不然——你是在描述硬件。这个观念不转过来,后面综合会出各种奇怪问题。

我个人习惯把RTL设计比作画建筑图纸。你写的是结构,不是流程。综合工具就是那个施工队,把你的图纸变成真正的电路。今天我们就聊聊这个转化过程。

3.1 Verilog/VHDL基础回顾:别在细节上翻车

先快速过一遍基础。我见过太多项目因为最基本的语法问题导致综合失败,浪费好几天时间。

3.1.1 组合逻辑 vs 时序逻辑

这是最核心的概念。组合逻辑输出只依赖当前输入,时序逻辑还依赖历史状态。

类型 Verilog写法 综合结果 典型用途
组合逻辑 always @(*) 或 assign LUT + 连线 译码器、多路选择器
时序逻辑 always @(posedge clk) 触发器 + LUT 计数器、状态机、寄存器
锁存器 组合逻辑中条件不完整 Latch(通常不想要) 尽量避免
注意:组合逻辑中如果if-else或case条件不完整,综合工具会推断出锁存器。我曾经在一个项目中排查了整整两天,最后发现是一个case语句少了default分支,导致综合出了几十个Latch。从那以后,我写组合逻辑必加default或else。

3.1.2 阻塞赋值与非阻塞赋值

这个坑几乎每个FPGA工程师都踩过。记住一个原则:

  • 组合逻辑用阻塞赋值(=)
  • 时序逻辑用非阻塞赋值(<=)

为什么?非阻塞赋值在时钟沿同时更新,模拟了触发器的并行行为。阻塞赋值是顺序执行,适合组合逻辑的连续赋值。

// 正确的时序逻辑写法
always @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    count <= 8'd0;
  else if (en)
    count <= count + 1'b1;
end

// 错误的写法——会导致仿真与综合不一致
always @(posedge clk) begin
  a = b;
  c = a;  // 这里a已经更新了,但综合出来是两个串联的触发器
end
小技巧:如果你不确定该用哪种赋值方式,就想想这个信号是不是在时钟沿触发的。是,就用非阻塞;不是,就用阻塞。

3.2 Vivado综合策略:让工具为你服务

Vivado的综合引擎很强大,但你不告诉它怎么做,它就用默认策略。默认策略不一定适合你的设计。

3.2.1 综合模式选择

Vivado提供几种综合模式,我一般这样选:

模式 适用场景 特点
Default (Vivado Synthesis Default) 常规设计 平衡面积和性能
Area Optimized (High) 资源紧张 牺牲时序换面积
Performance Optimized (High) 高速设计 牺牲面积换速度
Flow_PerfOptimized_High 极限性能 综合+布局布线联合优化

我个人习惯:前期调试用Default,最后冲刺时序用Performance Optimized。如果资源快满了,再切到Area Optimized看看。

3.2.2 综合属性设置

有几个综合属性我几乎每个项目都会调:

  • flatten_hierarchy:默认是rebuild,建议改成full。这样综合器会跨模块优化,效果很明显。
  • fsm_extraction:状态机提取。默认是auto,但如果你的状态机跑不快,可以试试one_hot或gray。
  • keep_equivalent_registers:默认关闭。如果打开,综合器不会合并等效寄存器,方便调试但浪费资源。

经验之谈:我曾经接手一个项目,综合后时序一直不过。折腾了三天,最后发现是flatten_hierarchy设成了off。改成full之后,关键路径缩短了30%。有时候问题就这么简单。

3.3 综合报告分析:读懂工具在说什么

综合报告不是给你看的摆设。每一行都有意义。

3.3.1 关键报告解读

Vivado综合后会生成几个关键报告:

  1. 综合日志(Synthesis Log):最基础的信息,包括警告、错误、资源使用。
  2. 时序摘要(Timing Summary):WNS(最差负时序裕量)、TNS(总负时序裕量)、WHS(最差保持时序裕量)。
  3. 资源利用率(Utilization Report):LUT、FF、BRAM、DSP用了多少。
  4. 时钟报告(Clock Report):时钟网络、抖动、延迟。

我每次综合完第一件事就是看WNS。如果WNS是正的,说明时序有裕量。如果是负的,就要看负多少——-0.1ns以内可能布局布线能救,-1ns以上基本要改代码。

3.3.2 常见警告处理

综合警告很多,但不是每个都要管。我总结了几类必须处理的:

警告类型 含义 处理方式
[Synth 8-327] 推断出锁存器 检查组合逻辑条件是否完整
[Synth 8-3335] 寄存器被优化掉 检查是否被误优化,加(* keep = "true" *)
[Constraints 18-5210] 时钟约束未找到 检查XDC文件是否正确
[Synth 8-3936] 连接了多个驱动 检查多驱动问题,通常是代码bug
特别注意:“寄存器被优化掉”这个警告要格外小心。有时候综合器觉得某个寄存器没用就优化了,但你可能在调试时需要它。我习惯在关键信号上加(* keep = "true" *)属性,防止被优化。

3.4 综合流程知识图谱

下面这张图总结了RTL设计到综合的完整流程,以及每个环节的关键点:

RTL设计与综合流程 RTL设计 Verilog / VHDL 综合策略设置 模式 / 属性 / 约束 综合执行 synthesize 综合报告分析 时序 / 资源 / 警告 代码检查 语法 / 风格 / 可综合 迭代优化:修改代码 → 重新综合 关键要点 • 综合前先检查代码可综合性,避免锁存器和多驱动 • 综合策略要根据设计目标(面积/性能)选择,不要用默认值走到底 • 综合报告中的WNS是核心指标,负值越大问题越严重

3.5 实战建议:少走弯路

最后分享几个我在项目中积累的经验:

  • 写代码时就想着综合:不要写仿真专用的语法(如initial、$display)到综合代码里。我见过有人把$display写进always块,综合报错还一脸懵。
  • 综合前先跑语法检查:Vivado的语法检查能发现很多低级错误。别省这一步。
  • 保留综合报告:每次综合的报告都存档。这样如果后面时序变差了,可以对比看看是哪次改动引起的。
  • 不要迷信综合工具:工具再聪明,也猜不到你的设计意图。代码风格直接影响综合质量。
我的习惯:每次写完一个模块,先单独综合看看资源占用和时序。不要等到所有模块都写完了再综合,那时候问题堆在一起,排查起来很痛苦。

嗯,RTL设计与综合这块就聊这么多。记住一点:综合不是终点,它只是把你写的代码翻译成电路。真正的好设计,是在写代码时就考虑好了综合结果。多练、多看报告、多总结,你也能成为综合高手。