在 Verilog 硬件描述语言中,(* keep_hierarchy="yes" *) 是一个非常重要的 层次保留属性。它的核心作用是指示综合工具(如 Vivado、Design Compiler)保持模块的层次结构,防止其在优化过程中被展平或合并到其他模块中。
那么,为什么我们需要特意保留层次呢?这通常是为了便于调试、约束管理以及模块的复用。
一、属性作用与核心含义
添加此属性后,相当于给综合工具下达了一条明确的指令:
- 保持层次:防止工具打破模块之间的边界。
- 禁止展平:阻止该模块被“溶解”并优化到其父模块内部。
- 独立优化:允许综合工具在每个模块内部独立进行优化。
- 便于调试:使综合后的网表结构与原始 RTL 代码的层次保持一致,极大地方便了前后仿真的对照与问题定位。
二、语法格式
这个属性主要通过注释的形式嵌入在代码中。
// 最常见的用法:在模块定义时声明
(* keep_hierarchy = "yes" *)
module my_module (...);
// 模块内部代码
endmodule
// 某些工具也支持在模块实例化时使用
(* keep_hierarchy = "yes" *)
module_name instance_name (...);
三、主要使用场景与代码示例
1. 保护 IP 核或专用子模块
当你设计了一个需要映射到专用硬件资源(如 DSP、BRAM)的模块,或者它是一个需要保密的第三方 IP 时,保留其层次至关重要。
(* keep_hierarchy = "yes" *)
module dsp_multiplier (
input [15:0] a,
input [15:0] b,
output [31:0] p
);
// 这部分逻辑可能会被综合工具映射到 FPGA 的 DSP48E1 等专用乘法器上
assign p = a * b;
endmodule
module top_design (
input [15:0] data_a,
input [15:0] data_b,
output [31:0] result
);
// 这个实例的层次结构会被保留,便于识别和约束
dsp_multiplier u_mult (
.a(data_a),
.b(data_b),
.p(result)
);
endmodule
2. 保持功能模块的独立性
对于一些通用的功能模块,如 FIFO、仲裁器等,保留层次有利于在不同项目中复用,也使得模块内部的 逻辑 更清晰。
(* keep_hierarchy = "yes" *)
module fifo_controller (
input clk,
input rst_n,
input wr_en,
input rd_en,
input [7:0] data_in,
output [7:0] data_out,
output full,
output empty
);
reg [7:0] mem [0:15];
reg [3:0] wr_ptr, rd_ptr;
reg [4:0] count;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr <= 0;
rd_ptr <= 0;
count <= 0;
end else begin
// FIFO 控制逻辑...
end
end
assign full = (count == 16);
assign empty = (count == 0);
endmodule
3. 分层设计中的关键模块
在复杂的算法实现(如 AES 加密)中,保留每一轮操作的层次,有助于理解和验证数据流。
(* keep_hierarchy = "yes" *)
module aes_encryption_round (
input [127:0] data_in,
input [127:0] round_key,
output [127:0] data_out
);
wire [127:0] sub_bytes_out;
wire [127:0] shift_rows_out;
wire [127:0] mix_columns_out;
// AES 轮函数子模块
sub_bytes u_sub (.data_in(data_in), .data_out(sub_bytes_out));
shift_rows u_shift (.data_in(sub_bytes_out), .data_out(shift_rows_out));
mix_columns u_mix (.data_in(shift_rows_out), .data_out(mix_columns_out));
assign data_out = mix_columns_out ^ round_key;
endmodule
四、一个完整的通信接收机示例
下面这个 UART 接收机例子展示了如何在多个子模块上应用该属性。
// 1. 同步器模块 - 保持层次
(* keep_hierarchy = "yes" *)
module synchronizer (
input clk,
input rst_n,
input async_signal,
output sync_signal
);
reg sync_reg1, sync_reg2;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sync_reg1 <= 1'b0;
sync_reg2 <= 1'b0;
end else begin
sync_reg1 <= async_signal;
sync_reg2 <= sync_reg1;
end
end
assign sync_signal = sync_reg2;
endmodule
// 2. 解串器模块 - 保持层次
(* keep_hierarchy = "yes" *)
module deserializer (
input clk,
input serial_data,
output [7:0] parallel_data
);
reg [7:0] shift_reg;
always @(posedge clk) begin
shift_reg <= {shift_reg[6:0], serial_data};
end
assign parallel_data = shift_reg;
endmodule
// 3. 顶层接收机
module uart_receiver (
input clk,
input rst_n,
input rx_data,
output [7:0] received_data,
output data_valid
);
wire synchronized_rx;
// 这些被标记的子模块在综合后层次依然清晰
synchronizer u_sync (
.clk(clk),
.rst_n(rst_n),
.async_signal(rx_data),
.sync_signal(synchronized_rx)
);
deserializer u_deser (
.clk(clk),
.serial_data(synchronized_rx),
.parallel_data(received_data)
);
// 其他控制逻辑...
endmodule
五、深入理解应用场景
- 便于时序约束:对于跨时钟域(CDC)模块,保留层次后,可以方便地对整个模块添加特定的时序约束(如
set_false_path),而不是对分散的逻辑进行约束。
- 模块复用和团队协作:在大型项目中,明确的模块边界有利于不同团队并行开发。被保留层次的模块就像一个封装好的“黑盒”,接口清晰,易于集成和复用。
- 物理规划保留:在物理实现阶段,工具可以根据保留的层次信息进行更好的布局规划(Floorplanning),例如将某个关键模块约束在芯片的特定区域。
六、相关综合属性对比
| 属性 |
描述 |
作用层次 |
keep_hierarchy="yes" |
保持模块层次结构 |
模块级别 |
dont_touch="true" |
禁止对逻辑进行优化 |
模块、实例、信号级别 |
black_box="true" |
将模块视为黑盒,不进行综合 |
模块级别 |
buffer_type="none" |
禁止在端口插入缓冲器 |
端口级别 |
七、不同工具的具体语法
八、属性组合使用
有时我们需要对关键模块施加多重保护。
(* keep_hierarchy = "yes", dont_touch = "true" *)
module critical_submodule (
input clk,
input [15:0] data_in,
output [15:0] data_out
);
// 这个模块既不会被展平,其内部的逻辑也不会被优化掉
// ...
endmodule
九、使用注意事项
- 性能影响:过度使用可能会限制综合工具的跨模块
优化能力,从而影响最终设计的面积和时序。
- 面积代价:阻止了资源共享等优化,可能会稍微增加资源使用量。
- 时序考虑:保持层次可能会影响工具进行全局的时序优化。
- 调试优势:这是最大的好处,能显著改善网表调试和分析的便利性。
十、典型工作流程
- 在关键模块的定义处添加
(* keep_hierarchy = "yes" *) 属性。
- 运行综合,工具会识别该属性并保持模块边界。
- 在实现(Implementation)阶段,布局布线工具会参考这个层次信息。
- 在验证阶段,你可以轻松地对特定模块进行时序约束、功耗分析或调试。
十一、在大型 SoC 设计中的应用
在复杂的系统级芯片设计中,对核心模块保留层次是常见做法。
(* keep_hierarchy = "yes" *)
module cpu_core (
input clk,
input rst_n,
input [31:0] instruction,
output [31:0] result
);
// ...
endmodule
(* keep_hierarchy = "yes" *)
module dma_controller (
input clk,
input [31:0] src_addr,
input [31:0] dst_addr
);
// ...
endmodule
module soc_top (
input system_clk,
input system_rst_n
);
cpu_core u_cpu (.clk(system_clk), .rst_n(system_rst_n), ...);
dma_controller u_dma (.clk(system_clk), ...);
// 其他模块...
endmodule
总结
(* keep_hierarchy="yes" *) 属性是 Verilog 数字电路设计 中一项非常实用的功能,它平衡了综合优化与设计可维护性之间的关系。在以下场景中,你应该考虑使用它:
- 大型分层设计项目。
- IP核集成与复用。
- 需要清晰模块边界以便进行团队协作。
- 需要对特定模块施加独立时序约束。
- 物理规划驱动的设计。
- 调试和分析复杂系统时,希望网表与 RTL 结构高度对应。
掌握这个属性,能让你在追求电路性能的同时,更好地掌控设计的结构和流程。如果你对这类硬件描述语言的深层应用感兴趣,欢迎在 云栈社区 与其他开发者交流探讨。