ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

逻辑综合:从RTL到门级网表的转换、约束与实战

逻辑综合:从RTL到门级网表的转换、约束与实战 刚入行那会儿我一直有个疑问RTL代码写完仿真波形也跑通了为什么不能直接拿去流片后来在项目里被现实教育了几次才明白从行为级描述到真正能画版图的晶体管网络之间横着一条必须靠逻辑综合来填平的沟。逻辑综合这个词听起来挺学术说白了就是让工具替你把人话翻译成电路话——你写的是a b | c工具给你吐出来的是一堆具体工艺库里真实存在的与非门、或非门、触发器还要保证时序、面积、功耗三方面都能交差。这篇文章我打算把逻辑综合里最常被含糊带过的那些概念拆开讲清楚它到底在流程里站什么位置、内部做了哪几件事、约束文件为什么长成那个样子、脚本怎么写、报告怎么读、以及最容易翻车的地方在哪。不管你是刚学完Verilog想上手工具的学生还是从FPGA转ASIC、第一次看到compile_ultra一脸懵的工程师把这篇看完至少能少走两三个月的弯路。1. 逻辑综合到底在做什么——从RTL到门级网表的这场翻译1.1 综合在数字设计流程里站的位置一个完整的数字芯片设计流程从需求到GDSII大致会走这么一条线架构设计、RTL编码、功能仿真、逻辑综合、形式验证、DFT插入、布局布线、时序签核、物理验证、流片。综合卡在RTL和物理实现之间是整条链路上第一个不可逆的关口——RTL阶段你想怎么改都行综合之后交付的是门级网表后面所有环节都建立在它之上。这个位置的特殊性在于它既要有全局视野又要做局部决策。所谓全局视野是指工具得同时看时序、面积、功耗、可测性、时钟结构所谓局部决策是指每一条路径上到底用几级逻辑、用哪个驱动强度的单元、要不要做buffer都得在毫秒级内定下来。一次编译动辄处理几十万甚至上百万门这个规模下人是不可能手工优化的所以综合本质上是一场用工具替代人工经验的成规模决策。我个人习惯把综合理解成带着约束去猜物理实现会怎么收拾你。因为在综合阶段布局布线还没做真实的走线长度是未知的。工具只能用线负载模型Wire Load Model或者物理感知综合Physical Synthesis来估算互连延迟。估算准不准直接决定了综合出来的网表在后端会不会崩。这就是为什么很多团队会把综合和布局布线放在同一个工具链里做或者干脆做top-level的物理综合——猜得越准返工越少。1.2 三大输入、一大输出记住这个骨架不管用哪个工具逻辑综合的输入永远是三样东西输出永远是一样东西这个骨架记住了看任何脚本都不慌。输入一RTL代码。可以是Verilog、SystemVerilog或者VHDL通常是可综合子集的代码。注意可综合三个字很关键——initial块里写延时、用#10、动态数组、实型运算这些仿真能过但综合工具会直接报错或者静默忽略。输入二工艺库。也就是标准单元库一般以.lib文本和.db二进制两种形式存在里面记录了每个单元的功能、面积、引脚电容、各工艺角下的延迟表。没有库工具甚至不知道与门长什么样。输入三约束文件。行业标准是SDCSynopsys Design Constraints本质是一堆Tcl命令告诉工具时钟周期是多少、输入输出延迟多大、哪些路径不用管。约束写错比代码写错更可怕因为代码错了仿真能抓到约束错了工具还会尽职尽责地给你优化出一个错的电路。输出门级网表通常是.v格式的Verilog结构网表加上配套的.sdf延时文件和一堆时序、面积、功耗报告。提示这三样输入里RTL和库是客观事实约束是主观意图。项目里出问题八成是主观意图表达得不对而不是客观事实有问题。1.3 为什么仿真器替代不了综合经常有新手问既然仿真能把功能验证通过为什么还要综合一遍因为仿真做的是功能层面的等价性检查它完全不管这条路径能不能在一个时钟周期内算完也不管你写了a*b工具要给你塞一个乘法器还是几百个加法器。仿真器眼里只有0和1没有延迟没有面积没有功耗。而综合要回答的恰恰是仿真不回答的问题这个设计跑100MHz够不够面积多少平方微米动态功耗多少毫瓦扫描链能不能插进去这些问题在RTL层面根本没有答案必须映射到具体的工艺库上才能算出来。所以综合是一个从抽象走向具体的过程它的产物是后续所有环节的物理依据。2. 综合内部发生了什么——翻译、优化、映射三步走2.1 翻译阶段把RTL拍扁成通用逻辑综合的第一步叫翻译Translation也有的工具叫Elaborate。这一步做的事情是把你的RTL描述变成工具内部的通用逻辑表示通常是一张布尔网络或者叫通用门级网表GTECH。在这个阶段工具还没开始考虑工艺也不知道你用的是28nm还是7nm它就是单纯地把always (posedge clk)展开成触发器加组合逻辑把case语句展开成多路选择器的雏形。这一步里最值得说的是参数化和generate的展开。你写的parameter WIDTH 8到了这一步会变成实实在在的8位宽逻辑。这也是为什么综合日志里经常出现Elaborating module xxx的提示——工具正在把层次化的、带参数的代码展开成扁平的结构。层次保留策略set_ungroup、set_boundary_optimization就是在这里生效的层次保留得好后面调试时序问题时能一眼看出是哪个模块拖后腿。翻译阶段还有一个隐藏动作推断Inference。工具会从代码风格里推断出你想要什么硬件。看到always (posedge clk)就推断触发器看到时钟门控风格的代码就推断集成时钟门控单元ICG看到case全覆盖但没写default就推断出组合逻辑加锁存器。推断这个东西是双刃剑写得好的代码工具能推断出漂亮的硬件写得含糊的代码工具就会给你塞一堆意料之外的东西。我见过最典型的是敏感列表写always (a)却在里面用了b仿真时波形看着没问题综合出来直接多一个latch后面调了半天才发现。2.2 逻辑优化布尔化简与资源共享翻译完了工具手里是一坨功能正确但极其臃肿的通用逻辑。接下来是优化Optimization这一步是综合工具的看家本领所在也是各家工具拉开差距的地方。优化的第一层是布尔化简利用卡诺图、奎因-麦克拉斯基算法之类的数学手段把冗余的逻辑项合并掉。比如你写了y ab | abc从逻辑上第二项完全被第一项覆盖工具会直接删掉。这一层是纯数学的跟工艺无关各家工具做出来的差异不大。第二层是资源共享与结构优化。这是真正体现代价的地方。假设你写了两个加法器分别在两个时钟周期用工具可以考虑让它们共用一个加法器加多路选择器代价是要多加控制逻辑。类似的还有公因子提取、公共子表达式消除、算符重排。这些东西工具默认会做但做的激进程度需要你来控制——set_resource_allocation、set_structure这一类命令就是干这个的。第三层是逻辑重构也叫技术无关优化。工具会尝试改变逻辑的实现结构比如把一棵深逻辑树改成平衡树以降低延迟把宽扇入的与门拆成多级。这一步的目标是让电路在时序和面积上更接近理想形态为后面的映射做准备。实操心得逻辑优化阶段工具的默认策略永远偏向多下功夫换性能代价是编译时间。第一次跑综合建议先用默认设置摸清楚设计的时序基线再去调优化力度别一开始就把compile_ultra的所有选项拉满。2.3 工艺映射落到具体的标准单元优化完的通用逻辑最后还是得变成具体工艺库里真实存在的单元这一步叫工艺映射Technology Mapping。工具会拿着库文件为每一个逻辑功能挑选合适的单元组合。挑的过程很讲究同一个逻辑功能可能有十几种实现方式用一个三输入与门实现还是用两个二输入与门串起来实现驱动能力选X1还是X4要不要插buffer这里的核心概念是单元驱动强度和负载匹配。标准单元库里同名逻辑功能往往有多个版本驱动能力从X1到X16甚至更大。驱动弱了带不动后级负载延迟大驱动强了输入电容大前一级又要费劲同时面积和功耗都上去了。工具会基于负载估算做一个平衡但估算的准确性完全取决于线负载模型或者物理信息。映射阶段的另一个重要动作是时钟树综合前的准备工具会识别所有触发器的时钟端把它们连到同一个时钟网络上为后面的时钟树综合做准备。如果你在约束里写了时钟不确定性uncertainty工具会在这一阶段就把这部分余量扣掉。2.4 时序驱动与面积驱动选哪个工具跑综合时有个全局策略的选择粗暴点说就是直径优先还是速度优先。默认情况下只要你的约束里有时钟定义工具走的就是时序驱动综合Timing-Driven Synthesis目标是在满足时序的前提下尽量省面积。如果约束里压根没有时钟工具就退化成面积驱动的它会疯狂复用逻辑、压缩单元数量跑出来的电路大概率时序惨不忍睹。这里有个新手特别容易踩的坑只在脚本里link了库却没source约束就编译。工具不报错反而跑得飞快报告里面积还特别漂亮。等你拿着网表去跑形式验证或者后端布局才发现所有路径都没约束全是未约束路径工具根本不知道要优化什么。所以每次跑完综合第一件事应该是report_clock和report_timing确认约束确实生效了。3. 综合的输入文件体系——库、RTL、约束三件套3.1 标准单元库.lib与.db的关系.lib是文本格式的库描述人能读.db是Synopsys工具用的二进制编译格式工具读得快。两者内容等价.db由.lib用lc_shell或者read_libwrite_lib编译而来。工程里一般两个都留.lib用于查阅和版本管理.db用于实际读入。一个完整的库文件里跟综合强相关的字段主要有这些cell单元定义包含area、pin、function。pin每个引脚的方向、电容、驱动能力。timing延时表按输入转换时间slew和输出负载load二维查表也就是NLDM模型。power功耗信息有些库放在单独的文件里。operating_conditions工作条件定义了电压和温度。工艺角Corner是绕不开的话题。同一个库会提供多个角SS慢管、低电压、高温度用于建立时间检查FF快管、高电压、低温度用于保持时间检查TT是典型角。综合阶段通常读SS角做时序优化但保持时间一般不在这时候修因为时钟树还没建修了也白修。这一点我在第6节会再展开。3.2 SDC约束体系时钟、IO、例外SDC这套东西看着命令多其实逻辑很清晰分成四类时钟定义create_clock、create_generated_clock。这是所有时序分析的起点时钟没定义后面全是空谈。时钟特性set_clock_uncertainty、set_clock_latency、set_clock_transition。描述时钟本身有多脏。边界条件set_input_delay、set_output_delay、set_driving_cell、set_load。描述芯片外部世界对内部的影响。时序例外set_false_path、set_multicycle_path、set_max_delay、set_min_delay。告诉工具哪些路径不用按默认规则检查。还有一类是设计规则约束set_max_transition、set_max_capacitance、set_max_fanout。这些不直接管时序但违反了会让后端很难受。转换时间太大后级单元延迟会非线性上升扇出太大时钟树不好平衡。我个人的习惯是把这三条当成硬性指标宁可在综合阶段多插几个buffer也不要留给后端去救。3.3 setup与hold的算账方式理解时序检查的最好办法是自己算一遍。以建立时间setup为例一条从触发器U1到触发器U2的路径要满足的条件是T_launch_edge T_cq T_comb T_setup T_capture_edge T_period - T_uncertainty把它换算成留给组合逻辑的时间T_comb_max T_period - T_uncertainty - T_setup - T_cq (T_capture_edge - T_launch_edge)举个具体数字。时钟周期10ns100MHz不确定性设为0.15ns含时钟抖动和偏斜预算触发器自身的setup时间是0.10ns时钟到输出的延迟T_cq是0.20ns。同一时钟沿发射和捕获那么T_comb_max 10 - 0.15 - 0.10 - 0.20 9.55ns也就是说这条路径上的所有组合逻辑包括走线延迟必须压在9.55ns以内。工具在综合时的目标就是这个数它会把超出部分通过逻辑重构、单元升级、插buffer等方式压下去。保持时间hold的公式是另一套T_cq T_comb_min T_hold T_uncertainty_hold可以看到hold检查跟时钟周期无关只跟路径本身的最短延迟有关。这就是为什么hold违例不能靠降频解决而setup违例可以通过降频缓解。也是为什么综合阶段通常不修hold——真实的最短路径延迟取决于时钟树插入后的偏斜综合阶段估不准修了大概率是白做工。3.4 一份可以直接抄的SDC骨架下面这份约束我用了很多年改改时钟名字和端口就能上手结构上覆盖了绝大多数同步设计的需求# 1. 定义主时钟 create_clock -name clk_core -period 10.0 -waveform {0 5.0} [get_ports clk] set_clock_transition 0.12 [get_clocks clk_core] set_clock_uncertainty -setup 0.15 [get_clocks clk_core] set_clock_uncertainty -hold 0.05 [get_clocks clk_core] set_clock_latency -source 0.60 [get_clocks clk_core] set_clock_latency 0.30 [get_clocks clk_core] # 2. 输入延时相对时钟沿注意区分max/min set_input_delay -clock clk_core -max 2.00 [remove_from_collection [all_inputs] [get_ports clk]] set_input_delay -clock clk_core -min 0.50 [remove_from_collection [all_inputs] [get_ports clk]] # 3. 输出延时 set_output_delay -clock clk_core -max 2.50 [all_outputs] set_output_delay -clock clk_core -min 0.30 [all_outputs] # 4. 驱动与负载 set_driving_cell -lib_cell BUF_X4 -pin Z [all_inputs] set_load 0.05 [all_outputs] # 5. 设计规则 set_max_transition 0.30 [current_design] set_max_capacitance 0.20 [current_design] set_max_fanout 24 [current_design] # 6. 时序例外 set_false_path -from [get_ports rst_n] set_false_path -from [get_ports test_mode] set_multicycle_path 2 -setup -from [get_cells u_ctrl/*_reg*] -to [get_cells u_dsp/*_reg*] set_multicycle_path 1 -hold -from [get_cells u_ctrl/*_reg*] -to [get_cells u_dsp/*_reg*]注意set_multicycle_path必须成对写setup设成Nhold就要设成N-1否则hold检查会跟着一起放宽工具会漏掉真实的违例。这个坑我在两个项目上都见过。4. 工具选型与工程组织4.1 主流综合工具横向对比工具选型这件事其实很多时候不是你选的是公司买的。但了解各自的脾气对排查问题很有帮助。工具厂商主要特点典型适用场景Design CompilerSynopsys生态最成熟脚本资料最多与Formality、ICC2衔接顺中大规模ASIC团队协作项目GenusCadence编译速度快与Innovus同源物理感知能力强大规模SoC物理综合流程Yosys开源免费脚本灵活配合ABC做映射教学、小规模设计、开源硬件Vivado SynthesisAMD面向FPGA与实现工具深度集成FPGA项目算法验证如果是学习阶段我建议先用Yosys把整个流程跑一遍因为它开源、日志透明、你能看到中间每一步的网表长什么样。等理解了翻译-优化-映射这三步再切到DC或者Genus会发现脚本命令只是换了名字思路完全一样。4.2 目录结构与脚本分层综合工程的目录结构如果不规范后期维护会非常痛苦。我一般按下面的方式组织syn/ ├── scripts/ │ ├── setup.tcl # 变量定义库路径、顶层名、工艺角 │ ├── read.tcl # 读RTL、读库、link │ ├── constrain.tcl # 加载SDC │ ├── compile.tcl # 编译策略 │ └── report.tcl # 输出报告和网表 ├── rtl/ # RTL源文件列表.f文件 ├── lib/ # .db/.lib ├── sdc/ # 约束文件 ├── work/ # 工具运行目录 └── reports/ # 输出的时序、面积、功耗报告关键点是setup.tcl单独抽出来放变量。因为一个项目往往要跑多个工艺角、多个配置如果路径写死在每个脚本里换个角就要改五六个文件出错概率陡增。抽出来之后换个角只需要改一个变量甚至可以写个循环批量跑。4.3 三种工具的脚本骨架Synopsys DC的典型骨架set_app_var search_path [list . ./lib] set_app_var target_library sc9_base_ss_0p81v_125c.db set_app_var link_library * $target_library dw_foundation.sldb set_app_var symbol_library sc9_base.sdb analyze -format sverilog -vcs defineSYNTHESIS [list top.sv sub.sv] elaborate top -architecture verilog -update current_design top link source ./sdc/top.sdc check_timing compile_ultra -gate_clock -no_autoungroup report_timing -max_paths 20 -nworst 3 ./reports/timing.rpt report_area -hierarchy ./reports/area.rpt report_power ./reports/power.rpt write -format verilog -hierarchy -output ./work/top_syn.v write_sdc ./work/top_syn.sdc write_sdf ./work/top_syn.sdfCadence Genus的对应写法set_db lib_search_path ./lib set_db library {sc9_base_ss_0p81v_125c.lib} set_db init_hdl_search_path ./rtl read_hdl -sv {top.sv sub.sv} elaborate top read_sdc ./sdc/top.sdc check_design -unresolved syn_generic syn_map syn_opt report_timing -nworst 20 ./reports/timing.rpt report_area ./reports/area.rpt write_hdl -mapped ./work/top_syn.v write_sdc ./work/top_syn.sdcYosys的极简版本read_verilog -sv top.sv sub.sv hierarchy -top top proc; opt; fsm; opt; memory; opt techmap; opt abc -liberty ./lib/sc9_base_ss_0p81v_125c.lib write_verilog ./work/top_syn.v stat三个脚本对比着看能很明显感受到DC和Genus是命令驱动、分阶段执行Yosys是流程管道、逐步下沉。理解了syn_generic对应synth、syn_map对应abc这种映射关系换工具基本没什么成本。5. 完整跑一遍综合从读库到出网表5.1 环境与库的读入第一步永远是设置搜索路径和目标库。目标库target_library是映射时真正使用的库链接库link_library用于解析设计中的实例引用一般写成* $target_library星号表示先搜内存里已有的设计再搜库。工艺角的选取有个容易忽略的细节综合用SS角但不要用最极端的角。有些团队直接把SS角拉到-40℃或者125℃的最坏组合结果综合时为了压时序插了一堆大驱动单元面积翻倍实际签核时却发现另一个角更紧。合理做法是先跟后端确认签核用的角综合就用同一个角去对齐。读RTL时有个实用技巧用-vcs参数把宏定义传进去这样RTL里的ifdef SYNTHESIS分支能正常生效。很多设计会在这个宏下把不可综合的仿真代码屏蔽掉不加这个参数会直接报语法错误。5.2 约束加载与检查source完SDC之后千万别急着编译先跑check_timing。这个命令会吐出一堆报告重点看这几项unconstrained_endpoints有多少寄存器的输入端没有被任何路径约束到。理想值是0。no_clock有多少寄存器的时钟端没接到时钟。这个极其危险意味着这些寄存器完全没被优化。no_input_delay / no_output_delay端口有没有设置延时。generated_clocks分频时钟有没有正确推导出来。我自己的经验是check_timing报出来的warning里no_clock和no_input_delay这两类必须清零才能往下走其他的视情况处理。有一次我拿到同事的脚本check_timing报了几百个no_clock一开始还以为是时钟名写错了排查半天发现是他把时钟定义在一个被条件注释掉的if块里工具根本没执行到。5.3 编译策略与增量优化compile_ultra是DC里最强的一档编译包含逻辑重构、时序驱动映射、自动取消分组等动作。它有几个常用开关-gate_clock在满足条件的地方自动插入集成时钟门控单元。这个对降低动态功耗非常有效实测在一颗中规模SoC上能省10%~25%的时钟树功耗。代价是增加了可测性设计的复杂度扫描链插入时要额外处理。-no_autoungroup禁止自动打散层次。默认情况下工具会把小的层次打散以获取更好的优化自由度但打散之后调试时序问题会很难定位。我一般在上线前的最后一次综合才允许打散。-retime寄存器重定时把组合逻辑跨越触发器边界重新分配。这个对某些路径特别有用但会让网表和RTL的寄存器边界不一致形式验证会更麻烦慎用。编译跑完如果还有违例不要直接改代码先走增量优化。compile_ultra -incremental会保留已有的映射结果只针对违例路径继续优化速度快而且对面积影响小。有时候来回跑两三次增量就能把违例清掉。5.4 输出文件与报告编译通过之后要落盘的东西不止网表文件命令用途门级网表write -f verilog给后端和形式验证用SDFwrite_sdf门级仿真用含延时信息SDCwrite_sdc传递给后端的约束时序报告report_timing自查和交付面积报告report_area面积预算核对功耗报告report_power功耗预算核对设计规则报告report_constraint -all_violators检查max_transition等网表输出建议加-hierarchy保留层次除非后端明确要求扁平网表。层次保留的好处是后期和RTL对照方便形式验证也更容易定位不匹配点。5.5 时序报告怎么读report_timing的输出看着密密麻麻其实结构很固定。一份标准的路径报告包含这几块Startpoint: u_ctrl/state_reg[2] (rising edge-triggered flip-flop clocked by clk_core) Endpoint: u_dsp/acc_reg[15] (rising edge-triggered flip-flop clocked by clk_core) Path Group: clk_core Path Type: max然后是路径明细每一行是一个单元或线网列出Incr该段延迟和Path累计延迟。最后是data arrival time、data required time和slack。slack是负的就是违例。读报告时我习惯按这个顺序看Path Type——是max还是min对应setup还是hold。Path Group——属于哪个时钟域跨时钟域路径要特别留意。slack数值——先看差多少差0.1ns和差2ns的应对策略完全不同。Incr最大的几行——延迟集中在哪个单元或哪段线网上这就指向了优化目标。有个细节很容易被忽略报告里的Incr如果某一级特别大可能是扇出过大或者驱动能力不足。如果整条路径的延迟分布很均匀那问题多半在逻辑级数太深需要从RTL层面重构。同样是违例这两种情况的处理方式完全相反一个是在工具里加buffer一个是要回去改代码。6. 常见问题与排查技巧实录6.1 时序违例先分类再动手拿到负slack别急着改代码先分类。我一般按下面这个判断树走第一类单条路径违例其余都在正余量。这通常是局部问题用compile_ultra -incremental或者手工加几个buffer就能解决。如果路径最后一级的负载特别大用set_max_fanout局部约束一下也能缓解。第二类某个模块整体违例且违例路径都经过同一个中间信号。这大概率是那个中间信号扇出太大成了瓶颈。解决办法是加一级buffer把扇出分流或者在RTL层面把广播信号改成局部解码。第三类所有路径都差一点点slack分布很均匀地都在-0.2ns左右。这种情况通常是约束写得乐观了——时钟不确定性给太小、输入输出延迟写得比实际小、或者线负载模型选得不合适。先怀疑约束再怀疑设计。第四类跨时钟域路径报违例。除非两个时钟有确定的相位关系否则这些路径应该用set_false_path或者set_clock_groups隔开。没隔开的话工具会拼命优化这些本来不该检查的路径白白浪费面积。6.2 面积爆炸与拥塞风险面积超预算是综合阶段很常见的问题原因往往不在工具而在代码。几个典型场景场景一位宽溢出。你写assign y a b c;如果a、b、c都是16位结果也声明成16位工具会老老实实做两个16位加法器。但如果结果只需要16位多出来的进位链就该被截掉。很多新手写的代码里全是全精度运算工具照单全收面积自然下不来。场景二重复的逻辑被实例化多次。比如一个译码逻辑在八个地方各写了一遍工具虽然能做公共子表达式消除但前提是它能看到全局。如果这八处分布在不同的层次里而你又开了-no_autoungroup工具就没法合并了。场景三case写成了优先级结构。综合工具看到不带parallel_case的case默认按优先级处理生成的是级联的比较器而不是并行的译码器。对于真正互斥的case加上parallel_case综合指示能大幅减小面积。但这个指示是你保证互斥如果实际不互斥仿真和综合就会不一致所以要谨慎。面积大了之后后端的拥塞风险也会上升。单元密度高的地方走线资源被占满布线绕不通最后还是要回头降面积。所以面积预算最好留10%~15%的余量不要顶格交差。6.3 综合与仿真对不上latch和不完整敏感列表形式验证报不匹配non-equivalent最常见的原因是锁存器被意外推断出来。前面提过组合逻辑的always块如果敏感列表不完整或者if/case没有覆盖所有分支工具就会推断出latch来保持原值。仿真时因为事件调度机制波形看起来是对的但综合出来的latch会让功能彻底变样。排查方法很直接跑report_latch或者看综合日志里的latch warning把所有推断出的latch列出来。真正需要的latch应该显式用always_latch或者带使能的写法不需要的就必须补全分支或者改写逻辑。另一个对不上的原因是综合指令和实际逻辑冲突比如前面说的parallel_case和full_case。这两个指示本质上是向工具承诺承诺不兑现就会出错。我的建议是除非你在RTL里已经用if-else把互斥性写得很清楚否则不要用这两个指示。还有一种情况是初始化值。RTL里写了reg [7:0] cnt 8d0;仿真时上电就是0但综合出来的网表里触发器没有初值上电状态是随机的。这个在FPGA上可以通过配置位解决在ASIC上必须靠复位逻辑。所以所有需要确定初值的寄存器都必须有复位不能依赖声明时的初始化。6.4 常见问题速查表现象可能原因排查动作解决方向check_timing报no_clock时钟未定义或被条件注释检查SDC是否被执行补时钟定义所有路径无约束编译前未source SDCreport_clock确认重跑先加载约束setup违例集中在某模块逻辑级数深或扇出大看Incr分布重构或分流扇出hold违例时钟树未插入估算不准确认是否综合阶段留给后端修面积超预算30%以上全精度运算、重复逻辑report_area -hierarchy优化RTL位宽形式验证不匹配推断出latchreport_latch补全分支功耗超预期无时钟门控检查是否加-gate_clock重编译加门控网表仿真出现X寄存器无复位检查复位覆盖补复位逻辑7. 几个踩过坑之后才明白的道理综合这件事工具本身的作用其实只有一半另一半全在工程师对约束和设计的理解上。我见过太多项目RTL写得漂漂亮亮脚本也跑得飞快结果到了后端处处爆雷回头一查全是综合阶段埋的雷。第一个体会是约束是合同不是注释。很多团队把SDC当成能过就行的东西随便抄一份改改周期就用了。但SDC里每一条命令都在告诉工具我要什么你写错了工具不会提醒只会照着错的做。我有一个习惯每次综合完成后必做的三件事是看check_timing、看report_clock、比对report_area和上次的差异。这三件事花五分钟能挡住八成的事故。第二个体会是别迷信工具的默认设置。默认的compile_ultra适合标准的同步设计但对异步、多时钟域、低功耗设计来说默认设置经常不是最优甚至不合适。set_max_transition这种设计规则约束工具在没有约束时根本不会管你得自己提出来。同样工艺角的选取、线负载模型的精度、时钟门控的插入策略这些都是要基于项目实际情况做取舍的没有一套参数能吃遍所有项目。第三个体会是综合和布局布线要联合考虑。早期我总想着综合阶段先把时序压到全正后面就轻松了。实际做下来发现综合阶段过度优化会导致面积膨胀、单元分布不合理后端反而更难收敛。后来学乖了综合阶段只要保证没有大的违例比如超过周期10%的小幅度的负余量留给后端去收敛反而整体周转更快。这个留一点空间给后面的思路是我做了两三个项目之后才慢慢有感觉的。第四个体会是报告要存档而且要能对比。每次综合跑完把时序、面积、功耗、约束检查这四份报告按版本号归档。碰到回归问题时能直接和上一版对比很快就知道是本次改动引入的问题还是历史遗留。这个习惯看起来笨但真的省时间。最后一个想说的是动手跑一遍胜过看十篇文档。逻辑综合的概念其实不多翻译、优化、映射加上约束的三四类命令核心就这些。但每个概念背后都有大量细节只有真正跑过、报过错、修过违例才能形成自己的判断。找个开源的小设计用Yosys跑一遍看中间网表再找台有DC或者Genus的机器跑一遍对比报告收获会比单纯读手册大得多。对了还有个实用的小技巧如果你手头只有RTL没有库可以用Yosys自带的cmos或者开源PDK里的库先跑通流程把脚本框架搭起来。等拿到真实库只需要换掉target_library这一行整个流程不用动。这样在项目前期就能验证RTL的可综合性早发现早修改比等到集成阶段再返工要划算得多。
返回列表