这一篇在干嘛?
综合器不是 C 编译器——它只把硬件可实现的子集翻译成电路。本篇先划清 TD 支持的 Verilog 边界(第 2 章),再讲 Read Design / Optimize RTL / Optimize Gate 三个综合阶段的关键开关(第 3 章),最后给出全部 20 个综合指令(attribute)的写法和作用(第 4 章)。编码技巧实战见第 2 篇。
TD 认识哪些 Verilog
TD 支持的 Verilog 语法覆盖了日常 RTL 设计的绝大部分,但边界必须清楚。写了不支持的语法,轻则报错、重则悄悄综合出错误电路,所以先看”不支持清单”:
- 数据类型:
tri0、tri1、trireg、wand、wor、real、realtime不支持。 - 程序分配:
assign、for(边界必须是静态的)支持;deassign、force、release、forever、repeat、delay(#)、event(@)、wait、parallel语句块、disable全都不支持——这些是仿真专用结构,综合器无法变成电路。 - 编译指令:
`define、`ifdef/`else/`endif、`include支持;`timescale不支持。 - 基本值:
0、1正常;x不支持(仅在 casex 分支中作用同?);z仅在管脚上支持(做三态总线用,见第 2 篇)。 - 支持的数据类型只有四类:
wire(网络)、localparam/parameter(常数)、integer/reg(寄存器)。
wire net1; // 1bit 线网
reg r1; // 1bit 寄存器
integer r2; // 整型寄存器
reg [15:0] bus1; // 16bit 寄存器
reg [7:0] mem[0:127]; // 8bit x 128 存储器
parameter state1 = 3'b001; // 3bit 常数Verilog-2001 的变量部分选择(indexed part-select)是支持的,做参数化设计很好用:
reg [3:0] data;
reg [3:0] select;
wire [7:0] byte = data[select +: 8]; // 从 select 起取 8 位系统任务方面:$readmemb/$readmemh(初始化内存,支持 txt/json/csv/xml/html/hex 文件)、$signed/$unsigned(符号强制转换)、$sin/$cos/$tan/$pow(仅常数实参)、$fatal/$warning/$error/$info 支持;$display、$write、$fclose、$fgets 等仿真输出类不支持。
常见坑:
$readmemb/$readmemh的初始化文件必须放在 .v 源文件目录或 .al 工程根目录下,否则综合找不到文件,RAM/ROM 初值全 0。
控制流里 for、while、generate(for/if/case 三种)、function、task 都支持,写法与标准 Verilog 一致。for 循环的边界必须是静态值;while 的循环次数必须有具体数值,禁止无限循环。函数必须在模块内声明并返回有符号/无符号类型:
function [3:0] min(input [3:0] lhs, rhs);
begin
min = lhs;
if (rhs < lhs) min = rhs;
end
endfunction
always @(posedge clk)
if (type) o = min(a, b); // 函数被展开成组合逻辑综合编译选项:三个阶段
TD 综合分三步:Read Design(语法语义分析,综合出原始行为级电路)→ Optimize RTL(MUX/数据通路优化、特殊功能模块识别,产生门级+特殊模块电路)→ Optimize Gate(逻辑映射优化,产生逻辑单元+专用功能单元)。操作入口:菜单栏 Process→Properties,或右键 FPGA Flow 中的 Syn Opt→Properties。
Read Design 关键参数——这几个开关直接决定你的算术逻辑映射到什么硬件资源:
| Property | 选项 | 作用 |
|---|---|---|
| infer_add | ON/OFF | ON:加减法长度≥min_ripple_len 时用硬件进位链实现,时序好、面积大;OFF:用 LUT 实现,省面积、时序差 |
| infer_mult | ON/OFF | ON:乘法用硬件 DSP;OFF:用 LUT |
| infer_ram | ON/OFF | ON:RAM 逻辑用硬件 BRAM/DRAM;OFF:用 LUT+触发器搭 |
| infer_rom | ON/OFF | ON:ROM 用硬件 BRAM;OFF:LUT+触发器 |
| infer_fsm | OFF/ONEHOT/AUTO | 状态机编码:不改 / 固定独热码 / 自动选择 |
| infer_reg | ON/OFF | ON:优化寄存器前面的复位/置位/使能逻辑 |
| default_reg_initial | AUTO/0/1 | 未指定初值且无异步置位的寄存器按 0(或 1)综合 |
| infer_shifter | ON/OFF | ON:移位寄存器用分布式存储实现;OFF:普通寄存器 |
Optimize RTL 关键参数:
| Property | 选项 | 作用 |
|---|---|---|
| merge_equal | ON/OFF | 搜索并合并电路中等价模块,省面积 |
| opt_arith | ON/OFF | 针对面积和时序优化加减法逻辑 |
| infer_gsr | ON/OFF | 是否使用全局异步 Reset/Set 资源 |
| max_fanout | 9999 | 线网扇出超过阈值就分裂成多条线网 |
| keep_hierarchy | FLATTEN/AUTO/MANUAL/ALL | 层次处理:全部打平 / 自动打平小的 / 听代码里的 keep_hierarchy / 全保留 |
| opt_mux | ON/OFF | 调整 MUX 树结构省面积,可能影响时序 |
| merge_mux | ON/OFF | 合并相同选择端的级联 MUX,降低逻辑级数 |
| opt_adder_fanout | ON/OFF | 优化加减法扇出以改善时序 |
| gate_sharing | ON/OFF | 提取共同 Gate 逻辑省面积 |
| min_ripple_len | auto | 用进位链实现的最小长度,小于该值直接用 LUT |
Optimize Gate 关键参数:
| Property | 选项 | 作用 |
|---|---|---|
| pack_seq_in_io | ON/OFF/AUTO | 是否把寄存器打包进 IO(IO 时序敏感时有用) |
| pack_effort | HIGH/MEDIUM/LOW | 打包力度:dff+lut 放进尽量少/中等/较多的 slice |
| opt_area | HIGH/MEDIUM/LOW | 面积优化力度,HIGH 可能影响时序 |
| opt_timing | AUTO | 时序驱动优化 |
| map_strategy | 1/2 | 工艺映射:1 均衡面积时序;2 用更大 LUT 压低组合逻辑级数 |
| cascade_eram | ON/OFF | 同一 RAM 逻辑的一组 ERAM 级联优化 |
常见坑:资源紧张时别无脑全开。
infer_add=ON+min_ripple_len太小会让小加法也吃进位链,面积暴涨;反之全部 OFF 会让 32 位比较器用一串 LUT 硬堆,时序崩。一般保持默认,仅针对具体瓶颈调整。
综合指令怎么写
综合指令(Synthesis Directive,也叫 attribute)让你不离开源代码就能控制综合行为。Verilog 区分大小写,指令字符保持小写。两种写法:
/*synthesis attribute = value*/ // 注释格式
(* attribute = value *) // Verilog-2001 括号格式(推荐)只给 attribute 不给 value 时,value 默认为 1/true。value 是数字型不加引号,字符型要加引号:
(* keep = "true" *) // 字符型加引号
(* keep = 1 *) // 数字型不加
(* keep *) // 省略 value,等价 true一个信号可以带多个 attribute,用空白符或逗号分隔:
(*attribute1 = value1, attribute2 = value2*) wire a;作用域规则要特别注意:约束写在同一行声明的哪些信号上,取决于分号位置:
wire a, b, c; /*synthesis keep*/ // keep 标记在 a、b、c 上
(*keep*) wire a, b, c; // 同上,标记在 a、b、c 上
wire a, b, c; /*synthesis keep*/; // 分号在后面,只标记在 c 上常见坑:
/*synthesis ...*/写在分号前和分号后是两种语义,在 generate 语句中会产生差异。要么用(*attr = val*)格式,要么明确把/*synthesis ...*/写在分号前。
VHDL 的规则类似但不区分大小写(attribute 名大小写均可,value 保持小写),需要先声明再指定:
signal sig1 : std_logic;
attribute keep : string;
attribute keep of sig1 : signal is "true";常用 attribute 速查
TD 支持的全部 attribute 汇总如下(详细案例见原书 4.2 节):
| Attribute | 可选值 | 作用 |
|---|---|---|
| translate_off/on | - | 成对使用,两注释之间的代码综合时忽略、仿真正常识别(只对 Verilog 生效) |
| full_case | - | case 状态不全时替代 default,避免生成锁存器 |
| parallel_case | - | 声明 case 各条件互斥并行,去掉隐含优先级 |
| keep | 0/1/true/false | 防止信号被优化掉,方便 ChipWatcher/ChipProbe 调试抓取 |
| keep_hierarchy | true/false | 保留指定模块层次不被打平(Flatten 模式下无效) |
| ram_style | none/dram/bram/bram_144k | 指定 Memory 用哪种资源实现;bram 可带 \_9k/_20k/_32k 与 (fast)/(power)/(area) 变形 |
| rom_style | none/dram/bram | 指定 ROM 实现方式,优先级低于 ram_style |
| max_fanout | >1 正整数 | 超过阈值自动复制寄存器降扇出、优化时序 |
| shifter_style | register/shifter/shifter_reg/reg_shifter/reg_shifter_reg | 移位链用寄存器还是 SRL 实现,默认 reg_shifter_reg |
| dsp_style | yes/no/auto | 乘法器用 DSP(yes)还是 LUT(no);写在 wire 上管单个乘法器,写在 module 上管整个模块 |
| fsm_encoding | sequential/one_hot | 状态机编码方式:顺序码 / 独热码 |
| allow_backward_retiming | true/false | 允许寄存器向前(数据流反方向)移动以优化时序 |
| allow_forward_retiming | true/false | 允许寄存器向后移动 |
| preserve | 0/1/true/false | 防止寄存器被合并/优化成常量,保留输出悬空的寄存器 |
| use_ripple | 0/1 | 0:加减法优先 LUT;1:优先进位链 |
| clock_buffer | gclk/sclk/lclk/mclk/none | 强制时钟线网用哪类时钟资源 |
| resource_sharing | true/false | mux 数据端有相同操作符时共享运算单元省面积 |
| force_ram | - | generate for 内定义的 Memory 综合器推断不出来时,强制按 RAM 实现 |
| no_ram | - | 强制某个 Memory 不用 ram 实现 |
| bound | auto/floating/fixed | 布局区域约束(通过 Tcl 的 create_bound 实现,不能写在 HDL 里) |
几个最常用的示范:
// translate_off:仿真专用的赋值在综合时被忽略
/* synthesis translate_off */
assign out[1] = a | b;
/* synthesis translate_on */
// keep:调试时抓不到信号就加它
(*keep*) reg [3:0] b;
wire tmp0/*synthesis keep*/;
// ram_style:强制小 RAM 走 LUT,省 BRAM
(*ram_style="dram"*) reg [7:0] mem [127:0];
// dsp_style:乘法器指定用 DSP
(*dsp_style = "yes"*) wire [4:0] mult;
assign mult = i0 * i1;
// fsm_encoding:状态寄存器上指定独热码
(*fsm_encoding = "one_hot"*) reg [1:0] state;Retiming 类指令的效果:允许综合器在功能不变的前提下移动寄存器位置,把逻辑从紧张的一侧挪到松弛的一侧,改善时序收敛。但 retiming 会改变网表结构和名称,导致前后网表难以对应,运行时间也更长,应尽量少用。

常见坑:full_case 和 parallel_case 都可能造成 综合前后仿真结果不一致——full_case 让综合器假设"未列出的状态不会出现",parallel_case 让它假设"分支互斥"。如果代码实际上不满足这些假设,硬件行为就和你仿真的不一样。根本解法是把 case 写规范(补 default、保证互斥),少依赖这两条指令。
通关标准:
- 不查资料说出 5 个 TD 不支持的 Verilog 结构,并解释为什么仿真语句不能综合。
- 说出 Read Design 阶段 infer_add/infer_mult/infer_ram 三个开关分别控制什么资源。
- 手写一个带
(*ram_style="bram"*)和(*max_fanout = 16*)的 RAM 声明,并解释约束标在哪些信号上。
为什么
wire a, b, c; /*synthesis keep*/;(末尾多个分号)只约束 c?注释格式约束的作用范围是从约束位置到该语句的分号为止。写在
c之后、语句结束分号之前,只覆盖最后一个声明的信号;写在整条声明语句的分号之后则成了”行约束”,会覆盖 a、b、c 三个。这种二义性在 generate 中会引发难以排查的差异,所以推荐统一用(*attr*)格式。
我写了一个 64 深度的小 RAM,综合出来却吃掉了 BRAM,怎么让它别用?
两种方法:一是在该 memory 的 reg 声明处加
(*ram_style="dram"*)(用 LUT 实现的分布式 RAM)或(*ram_style="none"*)(用寄存器+门实现);二是确认写口数量——DRAM 要求写口不超过 1,ERAM 要求深度不小于 64 且写口不超过 2,不满足条件时即使写了 ram_style 也可能回退到 SLICE 实现。
keep 和 preserve 有什么区别?
keep 加在 wire/reg 声明上,防止信号/寄存器在前端优化中被删除,主要用于 ChipWatcher、ChipProbe 调试抓取信号;preserve 加在寄存器上,防止寄存器被合并或优化成常量,并能保留输出悬空的寄存器,用于抗干扰冗余设计和调试。preserve 的效果等价于 Tcl 命令
force_keep -a {name}(注意 name 是实例名而非 net 名,要写全层级)。
综合时报了 latch 的 warning,但我没写 latch,哪里来的?
组合 always 块里 if-else 缺 else 分支、case 缺 default 分支、或者某分支给自己赋值(
o = o),都会让寄存器”保持原值”——这正是电平敏感的锁存行为。TD 不支持 latch,会报 warning 要求你排查去除。修复方法见第 2 篇。