这一篇在干嘛?

综合器不是 C 编译器——它只把硬件可实现的子集翻译成电路。本篇先划清 TD 支持的 Verilog 边界(第 2 章),再讲 Read Design / Optimize RTL / Optimize Gate 三个综合阶段的关键开关(第 3 章),最后给出全部 20 个综合指令(attribute)的写法和作用(第 4 章)。编码技巧实战见第 2 篇

TD 认识哪些 Verilog

综合编译选项:三个阶段

综合指令怎么写

常用 attribute 速查

TD 认识哪些 Verilog

TD 支持的 Verilog 语法覆盖了日常 RTL 设计的绝大部分,但边界必须清楚。写了不支持的语法,轻则报错、重则悄悄综合出错误电路,所以先看”不支持清单”:

  • 数据类型:tri0tri1triregwandworrealrealtime 不支持
  • 程序分配:assignfor(边界必须是静态的)支持deassignforcereleaseforeverrepeatdelay(#)event(@)waitparallel 语句块、disable 全都不支持——这些是仿真专用结构,综合器无法变成电路。
  • 编译指令:`define`ifdef/`else/`endif`include 支持;`timescale 不支持。
  • 基本值:01 正常;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_addON/OFFON:加减法长度≥min_ripple_len 时用硬件进位链实现,时序好、面积大;OFF:用 LUT 实现,省面积、时序差
infer_multON/OFFON:乘法用硬件 DSP;OFF:用 LUT
infer_ramON/OFFON:RAM 逻辑用硬件 BRAM/DRAM;OFF:用 LUT+触发器搭
infer_romON/OFFON:ROM 用硬件 BRAM;OFF:LUT+触发器
infer_fsmOFF/ONEHOT/AUTO状态机编码:不改 / 固定独热码 / 自动选择
infer_regON/OFFON:优化寄存器前面的复位/置位/使能逻辑
default_reg_initialAUTO/0/1未指定初值且无异步置位的寄存器按 0(或 1)综合
infer_shifterON/OFFON:移位寄存器用分布式存储实现;OFF:普通寄存器

Optimize RTL 关键参数

Property选项作用
merge_equalON/OFF搜索并合并电路中等价模块,省面积
opt_arithON/OFF针对面积和时序优化加减法逻辑
infer_gsrON/OFF是否使用全局异步 Reset/Set 资源
max_fanout9999线网扇出超过阈值就分裂成多条线网
keep_hierarchyFLATTEN/AUTO/MANUAL/ALL层次处理:全部打平 / 自动打平小的 / 听代码里的 keep_hierarchy / 全保留
opt_muxON/OFF调整 MUX 树结构省面积,可能影响时序
merge_muxON/OFF合并相同选择端的级联 MUX,降低逻辑级数
opt_adder_fanoutON/OFF优化加减法扇出以改善时序
gate_sharingON/OFF提取共同 Gate 逻辑省面积
min_ripple_lenauto用进位链实现的最小长度,小于该值直接用 LUT

Optimize Gate 关键参数

Property选项作用
pack_seq_in_ioON/OFF/AUTO是否把寄存器打包进 IO(IO 时序敏感时有用)
pack_effortHIGH/MEDIUM/LOW打包力度:dff+lut 放进尽量少/中等/较多的 slice
opt_areaHIGH/MEDIUM/LOW面积优化力度,HIGH 可能影响时序
opt_timingAUTO时序驱动优化
map_strategy1/2工艺映射:1 均衡面积时序;2 用更大 LUT 压低组合逻辑级数
cascade_eramON/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 各条件互斥并行,去掉隐含优先级
keep0/1/true/false防止信号被优化掉,方便 ChipWatcher/ChipProbe 调试抓取
keep_hierarchytrue/false保留指定模块层次不被打平(Flatten 模式下无效)
ram_stylenone/dram/bram/bram_144k指定 Memory 用哪种资源实现;bram 可带 \_9k/_20k/_32k(fast)/(power)/(area) 变形
rom_stylenone/dram/bram指定 ROM 实现方式,优先级低于 ram_style
max_fanout>1 正整数超过阈值自动复制寄存器降扇出、优化时序
shifter_styleregister/shifter/shifter_reg/reg_shifter/reg_shifter_reg移位链用寄存器还是 SRL 实现,默认 reg_shifter_reg
dsp_styleyes/no/auto乘法器用 DSP(yes)还是 LUT(no);写在 wire 上管单个乘法器,写在 module 上管整个模块
fsm_encodingsequential/one_hot状态机编码方式:顺序码 / 独热码
allow_backward_retimingtrue/false允许寄存器向前(数据流反方向)移动以优化时序
allow_forward_retimingtrue/false允许寄存器向后移动
preserve0/1/true/false防止寄存器被合并/优化成常量,保留输出悬空的寄存器
use_ripple0/10:加减法优先 LUT;1:优先进位链
clock_buffergclk/sclk/lclk/mclk/none强制时钟线网用哪类时钟资源
resource_sharingtrue/falsemux 数据端有相同操作符时共享运算单元省面积
force_ram-generate for 内定义的 Memory 综合器推断不出来时,强制按 RAM 实现
no_ram-强制某个 Memory 不用 ram 实现
boundauto/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 会改变网表结构和名称,导致前后网表难以对应,运行时间也更长,应尽量少用

Retiming 前后效果对比:寄存器位置被移动,两侧逻辑重新平衡

常见坑:full_case 和 parallel_case 都可能造成 综合前后仿真结果不一致——full_case 让综合器假设"未列出的状态不会出现",parallel_case 让它假设"分支互斥"。如果代码实际上不满足这些假设,硬件行为就和你仿真的不一样。根本解法是把 case 写规范(补 default、保证互斥),少依赖这两条指令。

通关标准:

  1. 不查资料说出 5 个 TD 不支持的 Verilog 结构,并解释为什么仿真语句不能综合。
  2. 说出 Read Design 阶段 infer_add/infer_mult/infer_ram 三个开关分别控制什么资源。
  3. 手写一个带 (*ram_style="bram"*)(*max_fanout = 16*) 的 RAM 声明,并解释约束标在哪些信号上。