1. 不可读时序单元的综合保留机制解析
在数字IC设计流程中,HDL Compiler作为RTL综合的核心工具,其优化行为直接影响最终网表质量。其中对"不可读时序单元"的处理是工程师必须掌握的要点。所谓不可读状态,指的是设计中的触发器、锁存器等时序单元或其驱动的组合逻辑,无法通过任何路径传播到模块输出端口。这种"孤立逻辑"在默认情况下会被综合工具优化掉,但在某些特殊场景下我们需要保留它们。
1.1 不可读单元的判定标准
HDL Compiler通过数据流分析判断单元的"可读性"。如图1所示的典型场景:
- 直接不可读:如例1中的a_r2_reg,其输出仅驱动内部信号d,而d未连接至任何输出端口
- 间接不可读:驱动a_r2_reg的组合逻辑(a & b)由于下游触发器被删除,也会被连带优化
verilog复制// 例1重现
module unread(input a, b, c, clk, output z);
reg a_r1, a_r2;
wire d;
assign z = a_r1; // 唯一输出路径
assign d = a_r2 & c; // 内部信号无输出
always@(posedge clk) begin
a_r1 <= a; // 可读触发器
a_r2 <= a & b; // 不可读触发器
end
endmodule
关键提示:综合报告默认不会列出被优化的不可读单元,这给调试带来挑战。建议在初期使用
hdlin_preserve_sequential = all全局保留,逐步分析优化效果。
1.2 保留机制的技术实现
HDL Compiler采用两级保留策略:
- 语法分析阶段:通过
preserve_sequential指令标记需保留的寄存器 - 综合优化阶段:根据
hdlin_preserve_sequential变量决定全局保留策略
保留的时序单元会以SEQGEN原语形式存在于GTECH网表中,其驱动逻辑会根据数据流决定是否保留。如图2所示,虽然a_r2_reg被保留,但其驱动的与门仍被优化,因为该组合逻辑仍被视为不可读。
2. hdlin_preserve_sequential变量详解
2.1 变量取值与作用范围
该变量提供六种精细控制模式:
| 取值 | 保留对象 | 包含循环变量 |
|---|---|---|
| none (默认) | 不保留任何不可读单元 | - |
| all / true | 所有时序单元(FF/Latch) | 否 |
| all+loop_variables | 所有时序单元(FF/Latch) | 是 |
| ff | 仅触发器(Flip-flop) | 否 |
| ff+loop_variables | 仅触发器(Flip-flop) | 是 |
| latch | 仅锁存器(Latch) | 否 |
| latch+loop_variables | 仅锁存器(Latch) | 是 |
2.2 循环变量的特殊处理
例2展示了循环索引变量的处理差异:
verilog复制module test(input clk, rst, error);
parameter N = 4;
reg [N-1:0] fail;
integer i; // 循环变量
always @(posedge clk or posedge rst)
if (rst) fail <= 0;
else for(i=0; i<N; i=i+1) // i作为循环索引
fail[i] <= fail[i] | error;
endmodule
- 默认情况:integer类型的i不会被推断为触发器
- all模式:仅保留fail_reg[3:0]
- all+loop_variables:额外保留i_reg(32位触发器)
实践经验:循环变量保留可能导致面积显著增加,建议仅在调试阶段启用。生产综合时应使用更精确的preserve_sequential指令替代。
3. preserve_sequential指令实战应用
3.1 指令语法规范
该指令通过特殊注释嵌入代码:
verilog复制reg [3:0] debug_reg /* synopsys preserve_sequential */;
或行内声明:
verilog复制always @(posedge clk) begin
temp <= in1 & in2; /* synopsys preserve_sequential */
end
3.2 保留效果对比分析
通过例3和例4的对比实验,我们发现:
-
组合逻辑保留条件:
- 仅当被保留时序单元直接驱动时,上游组合逻辑会被间接保留
- 下游组合逻辑仍需独立保留指令(如例4中将save声明为reg)
-
网表差异:
- 例3中sum2_reg被保留,但save组合逻辑被优化(图5)
- 例4中通过reg声明+指令,完整保留数据路径(图6)
verilog复制// 例4关键修改
reg sum2, save /* synopsys preserve_sequential */;
always @(posedge clk) begin
save <= sum1 & sum2; // 完整保留
end
3.3 调试技巧与陷阱规避
-
网表验证方法:
- 使用
write -hierarchy -output unread.gv导出GTECH网表 - 在Design Vision中查看保留的SEQGEN单元
- 使用
-
常见问题排查:
- 指令未生效:检查注释格式是否正确(必须包含synopsys前缀)
- 组合逻辑丢失:确保驱动路径上的所有寄存器都已被保留
- 面积异常增大:避免过度使用全局保留模式
-
版本兼容性:
- 2022版新增的
report_transformed_registers命令可显示优化过程 - 旧版本需通过前后网表对比验证保留效果
- 2022版新增的
4. 工程实践中的典型应用场景
4.1 调试信号保留
在复杂状态机中保留中间状态寄存器:
verilog复制reg [2:0] state, next_state /* synopsys preserve_sequential */;
always @(*) begin
case(state)
3'b000: next_state = ...;
//...
endcase
end
4.2 安全关键路径保护
对安全相关的冗余逻辑进行保留:
verilog复制reg parity_check, parity_error /* synopsys preserve_sequential */;
always @(posedge clk) begin
parity_check <= ^data_bus; // 奇偶校验
parity_error <= (parity_check != parity_bit);
end
4.3 动态配置寄存器
保留可能通过DFT或调试接口访问的配置寄存器:
verilog复制reg [7:0] feature_enable /* synopsys preserve_sequential = {8{1'b1}} */;
重要提示:生产综合时应清理不必要的保留指令,通常保留率控制在5%以下为宜。过度保留会导致:
- 面积增加10-30%
- 时序路径复杂度上升
- 功耗分析准确性下降
5. 与其他综合约束的协同
5.1 与dont_touch的区别
| 特性 | preserve_sequential | dont_touch |
|---|---|---|
| 作用阶段 | HDL Compiler读取阶段 | 综合优化阶段 |
| 保留对象 | 仅时序单元 | 任意设计对象 |
| 组合逻辑影响 | 间接保留驱动逻辑 | 完全保留 |
| 常用场景 | RTL调试阶段 | 物理实现约束 |
5.2 与Design Compiler优化的交互
即使使用保留指令,后续优化仍可能发生:
- 常量传播:无驱动输入会被连接至常量
- 寄存器合并:相同驱动信号的寄存器可能被共享
- 时钟门控:低活跃度寄存器可能被添加门控
建议配合以下约束使用:
tcl复制set_optimize_registers false -design [current_design]
set_dont_touch [get_cells *reg*]
6. 工具版本差异与迁移建议
不同版本工具的行为差异:
| 版本特性 | DC 2018 | DC-NXT 2022 |
|---|---|---|
| 循环变量处理 | 需显式+loop_variables | 自动识别部分循环变量 |
| 报告功能 | 仅基础推断报告 | 新增transform报告 |
| 指令兼容性 | 部分新选项不支持 | 完整支持所有保留模式 |
迁移检查清单:
- 验证保留指令在新版本的效果
- 更新Tcl约束中的变量命名(如hdlin_new_preserve_rules)
- 利用新版report_transformed_registers优化保留策略
在实际项目中,我通常会建立保留寄存器白名单机制:通过脚本自动提取设计中的debug信号,生成对应的preserve_sequential指令。这既能保证关键信号可见性,又避免过度保留导致的面积膨胀。一个典型的保留策略可能包含:
- 所有状态机寄存器(全局保留)
- 数据通路上的校验寄存器(指令保留)
- 性能计数器的关键位(选择性保留)