前段时间帮一个项目组定位异步SAR ADC控制逻辑的问题,现象非常典型:RTL仿真跑了几百个转换周期全部通过,综合之后的门级仿真就开始出现偶发跳码,回片测试更是时好时坏。折腾了好几天,最后发现根因不在模拟前端,而在综合工具对异步握手逻辑的一系列"误判"上。这已经不是我第一次遇到同类问题了。异步SAR逻辑(Asynchronous SAR Logic)本身没有主时钟,完全靠比较器输出事件逐级触发位循环,这种电路放到以同步时序为基本假设的逻辑综合工具里,天然就是"水土不服"。这篇文章把我在实际调试中遇到过的综合后出错原因梳理了一遍,给同样被这类问题折磨的同行一个排查参考。
内容主要面向做ADC/DAC数字控制逻辑、混合信号芯片数字部分,以及遇到"RTL仿真对了、综合后就不对"这类问题的数字后端和验证工程师。前半部分讲清楚异步SAR逻辑和综合工具的冲突根源,中间部分给出五类高发故障的现象和排查入口,后半部分是能直接落地的约束和编码建议。
1. 先把异步SAR的控制逻辑拆开看:它和同步设计的区别不只是"没有时钟"
很多人一听到异步SAR,第一反应是"没有全局时钟嘛,逻辑简单"。实际恰恰相反,异步控制逻辑的时序关系比同步电路复杂得多,而综合工具根本不理解这套时序关系。
1.1 SAR转换的核心思想:二分搜索
SAR(逐次逼近寄存器)ADC的转换过程就是一次二分搜索。一个N位的SAR,需要N个位周期,每个周期做三件事:把当前候选位写入DAC,比较器比较输入信号和DAC输出,根据比较结果决定这一位保留还是清零。整个过程从MSB开始,逐位逼近到LSB。
举个例子,一个10位SAR,采样完成后,先把最高位(第9位)置1,DAC输出变成满量程的一半,比较器判断输入比这个值大还是小。比大,最高位保留;比小,最高位清零。然后开始处理第8位,依此类推。每一位的结果都依赖于前一位的比较结果,所以这是一个严格的串行依赖链。
在同步实现里,这个串行链靠一个主时钟来驱动,每个时钟沿处理一位。主时钟频率必须足够高,至少是采样频率的(N+若干)倍,N位转换需要N个时钟周期,前后还要留出采样和复位的周期。
1.2 同步SAR与异步SAR:一个是节拍器,一个是多米诺骨牌
同步SAR的逻辑设计对综合工具非常友好。所有状态转移都发生在时钟沿,组合逻辑只需要满足时钟周期内的建立时间,综合工具只要拿到时钟约束,就能把每条路径优化到位。
异步SAR不一样。它的每个位周期不是由时钟沿触发的,而是由前一个阶段的"完成事件"触发的。典型结构是这样:采样完成后产生一个转换开始脉冲,这个脉冲送到位控制器,位控制器把候选位送到DAC;DAC输出稳定后,比较器开始比较;比较器输出一个valid(就绪)信号,这个信号告诉位控制器"我已经比较完了,你可以根据结果锁存这一位,然后进入下一位"。下一位的触发信号又来自这一位的valid信号。
这套机制就像多米诺骨牌,一张牌倒下触发下一张牌。整个转换链路的节奏由信号传播延迟决定,而不是由基准时钟决定。
这样做有两个明显好处。第一,不需要高速主时钟,功耗能压得很低,这也是异步SAR在低功耗ADC里流行的原因。第二,每一位的周期时间可以自适应,高位需要DAC建立时间长一点就多等一会,低位建立快就少等一会,平均转换速度反而快。
但问题也藏在这里。同步电路里,寄存器给组合逻辑提供了明确的"起跑线"和"终点线";异步SAR里,这条线变成了一个又一个事件脉冲,而这些脉冲信号在综合工具眼里,和普通数据信号没有区别。
| 对比维度 | 同步SAR控制器 | 异步SAR控制器 |
|---|---|---|
| 时序基准 | 全局主时钟沿 | 前级事件/valid信号 |
| 位周期长度 | 固定N个时钟周期 | 每位自适应,长短不一 |
| 状态存储 | 时钟沿采样的寄存器 | 事件触发的寄存器/锁存器 |
| 对综合工具友好度 | 高,常规SDC即可覆盖 | 低,大量路径无时钟约束 |
1.3 综合工具眼里的"正常电路"和异步SAR的反差
逻辑综合工具在做优化的时候,脑子里装的是一个非常固定的模型:输入信号在时钟沿被寄存器采样,经过组合逻辑,在下一个时钟沿之前必须稳定在下一个寄存器的输入端。建立时间和保持时间就是围绕这个模型定义的。工具做的所有优化——逻辑化简、公共子表达式提取、资源共享、单元重定时——都以这个时序模型为约束。
异步SAR的控制逻辑长什么样呢?它有一个比较器valid信号直接触发锁存动作的"事件时钟",有从位控制器反馈回DAC和比较器的组合回路,还有一串靠信号传播延迟工作的自定时链。这些结构在综合工具看来,要么是没有时钟域的悬空路径,要么是危险的组合环,要么是会推理出锁存器的可疑代码。
工具不会说"我不认识这种电路所以我放弃优化",它只会按照自己的模型去理解、去尝试,而这个理解过程和真实电路行为经常是错位的。这就是综合后出错的根源——不是你的RTL逻辑错了,而是综合工具用同步时序的思维方式,把异步电路"理解"成了另一种东西。
2. 综合工具为什么会有"天然误判":三个根本原因
把问题归到"工具不理解异步"还不够,得具体到它不理解哪些东西。我总结了三个最核心的误判来源,几乎所有综合后出错都能追溯到这三个点之一。
2.1 组合逻辑环:工具最怕的回路
组合逻辑环指的是信号经过一串组合逻辑后,又绕回自己的输入端,中间没有任何寄存器和锁存器打断。综合工具收到这类电路,一般会采取两种行动:报一个组合环warning,然后在优化的时候尝试"打断"这个环,或者干脆原样保留并警告。
问题在于,异步SAR里组合环有时候是故意的。比如一个典型的自我保护结构:valid信号经过几个逻辑门产生触发脉冲,触发脉冲又去复位valid信号本身,形成一个边沿检测/窄脉冲生成回路。这种环的时序行为完全依赖门的延迟,RTL仿真用的是理想延迟,看不出问题;综合之后,工具可能因为优化把环里的某级逻辑合并了,或者把某条路径上的缓冲器删掉了,环的传播延迟变了,脉冲宽度缩了,下游锁存器就采不到正确的值。
更要命的是,如果这个组合环同时出现在时序分析的路径上,STA(静态时序分析)会直接报infinite loop或者无法分析。很多团队的处理方式是粗暴地set_false_path,把这个环的所有路径都设成假路径。这一设,工具确实不报警了,但也意味着工具对这个环完全不负责——后续任何时序优化都看不到这里,一旦工艺角变化导致延迟漂移,出问题就只能靠实测发现。
2.2 锁存器推断:有意与无意的边界
异步设计中,锁存器(latch)是一个非常重要的器件。电平触发的锁存器天然适合做握手逻辑的存储单元:信号valid为高时数据透明通过,valid为低时锁存保持。很多异步SAR控制器里确实会用到锁存器,这不是错误。
但综合工具推断锁存器的逻辑很机械:某个信号在某个always块或条件分支里没有全覆盖赋值,就会被推断成锁存器。写RTL的时候稍微不注意——比如if语句少了else,case语句少了default——就会出现一个你根本没想要、也没有约束管理的锁存器。
这个锁存器在RTL仿真里表现正常,因为RTL仿真默认零延迟,锁存器的透明时间和时钟沿完美对齐。到了综合后的门级仿真,锁存器有真实的建立保持时间要求,触发它的使能信号如果来得太晚、去得太平滑,锁存器就可能采到亚稳态或者数据错位。更麻烦的是,有些综合工具对锁存器时钟端和使能端的处理非常粗糙,不会像对待寄存器时钟那样给你做完整的检查,等于把这个锁存器裸奔在时序分析之外。
2.3 没有时钟约束的信号,优化就没有方向
这是我认为最普遍、最容易踩的坑。综合工具做路径优化时,第一步就是看你给了哪些时钟约束。一个信号如果没有被任何时钟覆盖,工具就不知道这条路径该在多长时间内完成,于是它会做两种事情之一:要么按默认的极宽松要求优化,要么把这条路径上多余的逻辑尽量压缩。
听起来压缩是好事?对异步电路不一定是。异步SAR里,有些路径需要"足够长"而不是"尽可能短"。比如比较器的valid信号需要维持一个最小脉冲宽度,才能让位控制器的锁存器可靠采样。如果工具把valid路径的逻辑压缩得太厉害,脉冲宽度缩到几百皮秒,锁存器根本来不及采样。
这类问题在时序报告里往往什么都看不到,因为无约束路径默认不报违规。你必须主动去查约束覆盖率,看哪些寄存器、哪些引脚没有被时钟约束覆盖。这一步不做,后面的排查都像在黑暗里摸象。
3. 综合后出错的高发源头:五种现象、根因与验证方法
下面这几类故障是我在实际项目里见到过的,也是社区里问得最多的。每种我都按"现象-根因-排查入口"的结构来写,最后汇总成一张对照表。
3.1 触发脉冲变窄或出现毛刺,位循环被"多走一步"或"跳过一步"
现象:门级仿真里,转换结果有时候比预期值大或小很多,波形上看位控制器的状态跳变顺序不对,中间某一位被跳过去了,或者某一位被重复处理了一次。
根因:异步SAR的位循环依赖"完成事件"触发下一步,这个事件通常是一个窄脉冲。综合工具在做逻辑优化时,可能会把产生这个脉冲的组合逻辑里的缓冲结构合并掉,导致脉冲宽度变窄;或者因为布线后的负载变化,脉冲沿变缓,使能端眼睛睁开的时间窗口不够,后级锁存器没采到。还有一种情况是,valid信号经过的路径上出现了竞争,综合工具优化后改变了不同支路的相对延迟,产生了一个毛刺,这个毛刺被当成有效沿触发了一次错误的动作。
排查入口:在门级网表里找到触发脉冲的产生路径,用波形工具看脉冲宽度和理想值的差距。对比综合前后的网表,看那几条路径上的缓冲器数量是不是变少了。这种情况通常需要在关键路径上加约束,或者用dont_touch保护特定的延迟单元。
3.2 逐位链的保持时间违例:比较器还没稳定就翻位
现象:转换结果在低几位频繁出错,出错模式不是固定的,换工艺角或者换电压就变。RTL仿真空跑良好,门级仿真加SDF延迟后开始出现。
根因:异步SAR的逐位链是一条"触发-比较-valid-锁存-再触发"的闭环,这个环的最小延迟必须满足两个条件:一是比较器有足够时间完成比较,二是valid信号建立后要保持足够时间让锁存器采样。如果综合优化让环上某段逻辑变短了——尤其是比如valid到锁存器使能端这段——就可能出现锁存器已经被触发、但比较器还没稳定的情况。这本质上是保持时间违例,只是因为这条路径没有时钟约束,时序报告根本不会报。
排查入口:手动计算环路上的最小延迟路径,把每个门的典型延迟加起来,看是否满足比较器的最差比较时间。更直接的方法是做门级仿真,在出错的位周期里看比较器的输入建立时间和valid到达锁存器的时间差。一旦确认是保持问题,解决方案不是去拉长逻辑,而是要在这条环路上插入明确的延迟单元,并且约束工具不许删。
3.3 组合环在优化中被改写,RTL仿真与门级仿真结果背离
现象:同一个测试向量,RTL仿真结果正确,门级仿真结果错误,而且错误点稳定复现,定位到某个内部节点时发现该节点的逻辑函数和RTL里的表达式已经不完全等价。
根因:综合工具在做逻辑优化时,会尝试打破组合环以方便计算和优化。它可能把环上的某个节点重新映射,或者用布尔化简把环的一条支路等价变换了。在纯同步设计里这种变换通常是无害的,因为等价变换保持了逻辑功能;但在异步电路里,同样的布尔函数用不同的门级结构实现,延迟特性完全不同。一个环的振荡条件、脉冲宽度、竞争行为都和门级的拓扑结构密切相关。工具的等价变换虽然在功能上等价,但动态行为已经变了,表现就是"综合之后逻辑变味了"。
排查入口:把综合前后的网表做一遍结构对比,找出被工具改写的环路径。如果工具支持,在综合选项里关掉对组合环的优化重写,或者对环上的节点设置dont_touch。另外,最好在RTL仿真阶段就识别出组合环,不要等到综合后再处理。
3.4 异步复位释放和状态初始化冲突
现象:转换结果一开始是对的,跑着跑着突然出现某个位卡死或者整个控制器停摆,复位信号释放后第一个转换周期就错。
根因:异步SAR控制器通常有复位信号,复位释放后控制器进入初始状态,等待采样信号到来。问题出在复位释放的时机和握手信号的相位关系。如果复位释放发生在某个握手事件正在传播的过程中,控制器的部分状态已经进入下一个阶段,另一部分还被按在复位状态,就会出现功能错乱。综合工具在做复位树综合或者复位逻辑优化的时候,可能改变复位信号的到达时间,让不同模块的复位释放产生偏移。另外,如果复位信号在RTL里写成了异步复位,但综合时没有对复位路径做约束,STA也不会检查复位释放时间。
排查入口:门级仿真里单独看复位释放前后几个周期,观察每个寄存器和锁存器的复位端释放时刻是否一致。检查综合报告里有没有对复位树的优化处理。最稳妥的写法是让控制器的复位信号尽量简单,不要让它穿过握手逻辑。
3.5 综合优化吃了"不确定态",功能对不上
现象:门级仿真的结果和RTL仿真结果偶尔不一致,但不是固定出现在某一位上,看起来毫无规律。综合报告里有一堆关于don't care的注释。
根因:RTL里如果写了case的default,或者if的else分支,让某个信号在某些条件下处于不确定态,综合工具会利用这些don't care做逻辑化简。化简本身没有错,但如果化简时吃掉的是一个异步事件路径上的条件,就可能把某个本来会出现的使能条件优化掉。异步电路对这些"边角条件"特别敏感,因为事件触发只发生在很窄的时间窗口里,任何一个使能条件消失,整个状态机就错位。
排查入口:仔细读综合报告和网表里关于don't care优化部分的日志,查看哪些节点被标记为unreachable或者don't care。在RTL里,尽量把异步握手信号相关的条件分支写完整,不要留default让工具自由发挥。
| 故障现象 | 高发环节 | 常见根因 | 首选排查入口 |
|---|---|---|---|
| 触发脉冲变窄/毛刺 | 事件脉冲生成路径 | 逻辑优化删缓冲,竞争毛刺 | 网表波形对比 |
| 保持时间违例 | 位循环闭环 | 路径过短,比较器未稳定 | 门级SDF仿真,手动计算最小延迟 |
| 组合环功能偏离 | 反馈回路 | 工具改写了环的拓扑 | 综合前后网表对比 |
| 复位释放错位 | 复位树/握手接口 | 复位到达时间偏移 | 复位释放窗口波形分析 |
| don't care被优化 | 状态机条件分支 | 不确定态被工具利用 | 综合日志don't care检查 |
4. 从编译报告到门级波形:一步步定位综合后的错误
遇到综合后出错,最忌讳的就是一头扎进门级仿真里盲试。我习惯按固定链路来排查,每一层都能过滤掉一批可能性。
4.1 第一步:编译日志里的三个关键词
综合完成后,先不要急着做任何分析,打开编译日志搜三个词:latch、combinational loop、unconstrained。这三个词出现的位置和数量,直接告诉你问题可能出在哪里。
看到latch的warning,去RTL里确认这些锁存器是不是有意为之。如果不是,改成全覆盖赋值;如果是,就得在约束里把这些锁存器的使能路径管起来,否则它们就是时序死角。
看到combinational loop的warning,先给这个环画一张图,确认环路上有哪些节点。如果这个环是异步握手必需的,记录下来,准备在后面用约束和dont_touch保护它;如果这个环根本不该存在,那说明RTL写错了,综合工具能帮你发现这个问题其实是好事。
unconstrained的warning是最容易被忽略的,因为很多工程师不把warning当回事。但对异步电路来说,"没有约束"就意味着"工具随便优化"。把所有unconstrained的端口和引脚都找出来,逐条判断它们是不是握手路径,是的话就纳入约束管理。
4.2 第二步:检查约束覆盖率,找出"裸奔"的路径
综合完以后,用工具的命令把所有时钟报告和时序路径报告导出来,重点看覆盖率。具体来说,我会做三件事。
第一,列出所有时钟,包括生成的时钟。异步SAR里如果位控制器内部产生了触发时钟,工具可能把它当成普通数据信号而没有识别成时钟。要检查SDC里有没有用create_generated_clock把这些内部时钟定义出来。第二,抽取所有寄存器和锁存器的时钟引脚,逐个确认它们被哪个时钟覆盖。任何没被时钟覆盖的存储单元,都要标成重点怀疑对象。第三,检查异步输入路径,比如比较器的valid信号从模拟域进来,它和内部逻辑之间是异步关系,要在SDC里对这条路径做合适的异步约束处理。
这步做完,通常能找出20%到40%的问题路径,它们全都是潜在故障点。
4.3 第三步:用门级仿真加SDF延迟定位功能偏差
约束覆盖率没问题之后,做门级仿真。用和RTL仿真相同时的激励,加上SDF反标延迟,跑同样的测试向量,然后逐拍对比输出。
对比的时候不要只看最终转换结果,要把内部控制节点的波形并排拉出来。我一般会在RTL和门级网表里找到同一组关键信号:位控制器的状态、valid信号、DAC输入码、比较器输出、锁存器使能。如果某个节点在门级仿真里出现了RTL里没有的毛刺、窄脉冲、或者电平不完全翻转,这个节点就是定位点。
定位到节点以后,往上追它的驱动逻辑,看这个节点的门级实现和RTL原表达式之间的结构差异。这个差异往往就是根因所在。比如RTL里一个简单的与门,门级网表里可能被映射成了一个或非门加反相器,逻辑等价,但延迟特性变了。对一个事件触发路径来说,这几十皮秒的差异就可能决定成败。
4.4 第四步:用形式化等价性检查兜底
如果第三步找不到明显波形差异,但门级仿真仍然出错,就上形式化等价性检查。这类工具会对比RTL参考网表和综合门级网表的逻辑功能是否一致,还能定位到不等价的节点。
有个细节要注意:形式化工具对组合环的处理方式和综合工具不一样,可能会把环上的节点报告成不等价。这时候不要慌,要在工具里设定环的打断点,指定从哪个节点切开环,再做比较。打断点的选择最好和你在综合时保留的环结构对应,这样两边才能对齐。
等价性检查过了,不代表时序就对了;但等价性检查不过,功能一定有问题。它能帮你把"功能改变"和"时序改变"这两类问题彻底分开,排查思路会清晰很多。
5. 从源头规避:异步SAR逻辑的编码与约束实践
排查经验总结到最后,不如一开始就写对。异步SAR逻辑不是不能综合,而是要按它的规律来写,从源头减少综合工具的误判空间。
5.1 握手信号编码:单脉冲和电平握手不要混用
异步SAR的握手信号有两种风格:单脉冲握手和电平握手。单脉冲握手指的是valid信号是一个确定宽度的脉冲,后级靠脉冲沿触发。电平握手则是valid信号拉高表示完成,后级靠这个电平的控制,等ack信号回来后再拉低。两种风格各有适用场景,但千万别在同一个环路上混用,否则综合工具分不清哪个信号是边沿敏感的,哪个是电平敏感的,优化的时候非常容易出错。
我的做法是:位循环内部尽量统一用电平握手,因为电平握手对脉冲宽度的敏感度更低,综合工具优化时造成的延迟变化对功能的影响更小。对外接口(比如到DAC的触发)再用单脉冲,让接口简洁。混用的边界要在代码注释里写清楚,并在SDC里对接口路径单独约束。
5.2 约束文件里至少要有这几样(附示例)
异步SAR的逻辑综合约束,最核心的是把那些"没有全局时钟"的内部时钟和握手路径补出来。下面是一个典型的SDC片段,根据项目实际信号调整后可以直接参考:
# 1. 外部基础时钟(如果有) create_clock -name clk_ref -period 40 [get_ports clk_ref] # 2. 内部由比较器事件产生的触发时钟 # 源是比较器valid信号经过的某个寄存器输出,把它定义为生成时钟 create_generated_clock -name clk_bit \ -source [get_pins u_cmp/vld_stage/Q] \ -divide_by 1 \ [get_pins u_sar/bit_cycle_reg/CK] # 3. 握手路径按异步处理前,先验证功能,再写约束 set_false_path -from [get_pins u_cmp/vld_stage/Q] \ -to [get_pins u_dac/code_reg/D] # 4. 位循环闭环里不允许工具缩短的最小延迟路径 set_min_delay -from [get_pins u_sar/cycle_trigger] \ -to [get_pins u_sar/phase_latch/EN] 0.8 # 5. 比较器valid到锁存器的路径,给一个合理的时间窗口 set_max_delay -from [get_pins u_cmp/vld_stage/Q] \ -to [get_pins u_sar/phase_latch/EN] 1.2几个关键点:create_generated_clock的source尽量选择真实存在的节点,不要管它是不是"标准时钟源",异步电路里内部事件就是时钟。set_false_path不要滥用,只对那些经过验证确实不需要时序检查的握手路径设置。set_min_delay是异步设计里的好朋友,它能告诉工具"这条路径不准优化到更短",这比拉长逻辑更直接有效。
5.3 用"设计余量"替代"精确延迟"
异步电路最常见的一个错误思路,是想精确控制某条路径的延迟,让信号在"刚刚好"的时间到达。这个思路在综合和布线之后完全不可行,工艺角、电压、温度一变,所有延迟都会漂移,精确延迟就是精确翻车。
正确做法是两个方向。第一个方向是加大时间余量:比如比较器最差情况下需要2ns稳定,你的环路设计就不要卡着2ns,而是留到3ns,让工具和PVT变化都有空间。第二个方向是改变设计结构,把"延迟匹配"变成"事件确认"。比如不要假设valid信号到达时比较器一定已经稳定,而是在valid信号本身由比较器的完成事件产生,用一个额外的确认触发器锁存比较器输出,这样即使延迟漂移,也不会踩到比较器的建立时间。
5.4 保护关键结构:保留层次和dont_touch的应用
综合工具默认会打平层次、跨模块优化。对异步SAR来说,模块边界有时候是时序的自然分割线,被打平之后,工具可能把本应保持相对延迟关系的逻辑重新排列。
两种做法可以组合使用。一是保留关键模块的层次,在综合时设置模块级compile之后不优化跨模块边界;二是对关键单元设置dont_touch,主要是延迟单元、脉冲产生单元、以及握手接口上的特定缓冲器。dont_touch要少而精,用多了会影响面积和功耗,全路径都设dont_touch还不如不综合。
我通常只对三类结构设dont_touch:一是自定时链上用于产生最小延时的延迟单元,二是跨时钟握手接口的同步单元,三是比较器valid输入路径上的隔离缓冲。这三类结构要么影响时序安全,要么影响信号完整性,优化它们省不了多少面积,反而可能带来一堆问题。
5.5 验证策略:断言和协议检查
最后补一句验证的题外话。异步SAR逻辑的调试成本高,很大程度是因为错误发生得隐蔽——RTL仿真零延迟掩盖了所有时序问题,门级仿真又太慢,跑不了几百万个向量。
建议在验证环境里加上协议断言,盯着握手信号的时序关系不放。比如"valid信号高电平宽度必须大于某值"、"valid和ack不能同时为高"、"位循环状态跳变顺序必须严格从MSB到LSB"。这些断言在RTL仿真阶段就能暴露出结构性问题,在门级仿真阶段能快速定位延迟导致的问题。写断言花的时间,比事后在波形里一根一根信号去找原因少得多。
写在最后
每次处理完这类综合后出错的问题,我的体会都是同一个:异步电路不是不能综合,而是它要求设计者对综合工具的"思维方式"有足够认知。工具是按同步模型设计的,你不主动把异步结构的需求告诉它,它就会按照自己的理解去修改你的电路,结果就是RTL和门级行为脱节。
如果只让我留一条经验,我会说:把异步SAR里每个握手事件都当成一个"隐形的时钟域"来管理,给每个这样的时钟域明确定义触发源、覆盖范围、最小最大延迟要求。你把这个思维建立起来,约束文件自然就有章法,综合后出错的概率会下降一大半。
最后再分享一个小技巧:在做完综合后,先跑一个只含几个转换周期的短门级仿真,专门检查每一个握手脉冲的宽度和到达时间,确认它们都在设计余量之内。这个动作只要五分钟,却能提前拦住大部分回片才能发现的坑。