第二讲:数字逻辑基础回顾——组合逻辑与时序逻辑、时钟与复位、建立时间与保持时间

各位同学,咱们今天聊点“老本行”。

做SoC设计,说白了就是在跟数字逻辑打交道。你写再多RTL代码,最后综合出来无非就是两种东西:组合逻辑和时序逻辑。嗯,听起来简单,但我在项目中见过太多人在这上面栽跟头。今天咱们就把这块地基夯实了。

2.1 组合逻辑 vs 时序逻辑

先问个问题:你写的Verilog代码,哪些会综合成门电路,哪些会综合成触发器?

组合逻辑,就是输出只取决于当前输入。没有记忆能力。比如一个与门、一个加法器、一个多路选择器。你给它什么输入,它立马给你什么输出,不拖泥带水。

时序逻辑,输出不仅取决于当前输入,还取决于之前的状态。说白了,它有“记忆”。触发器(Flip-Flop)就是最典型的代表。它靠时钟沿来采样数据,把上一个时刻的值存下来。

核心区别一句话:组合逻辑没有状态,时序逻辑有状态。

我在项目中遇到过一位同事,写了一个计数器,但忘了在always块里把敏感列表写全。结果综合出来一堆奇怪的锁存器(Latch)。嗯,那会儿调试了整整两天。所以大家记住:

  • 组合逻辑用 assignalways @(*)
  • 时序逻辑用 always @(posedge clk)

千万别混了。你想想看,如果你把组合逻辑写在 always @(posedge clk) 里,综合工具会给你插一堆触发器,面积和功耗直接爆炸。

2.2 时钟与复位

时钟,是数字系统的“心跳”。没有时钟,时序逻辑就没法工作。

我个人习惯,在设计初期就把时钟树想清楚。单时钟域还是多时钟域?同步复位还是异步复位?这些决定了你后续的时序收敛难度。

2.2.1 时钟的基本属性

属性 说明
周期 时钟重复的时间间隔,单位ns
占空比 高电平时间占整个周期的比例,通常50%
抖动 时钟边沿的随机偏移,影响时序裕量
偏斜 时钟到达不同触发器的延迟差异

做SoC时,时钟偏斜是个大问题。我曾经在一个高性能芯片项目里,因为时钟树综合没做好,导致两个触发器之间的时钟偏斜超过了保持时间的裕量。结果芯片跑起来偶尔会采错数据。那叫一个头疼。

2.2.2 复位策略

复位,让系统回到一个已知的初始状态。常见的复位方式有两种:

  • 同步复位:只在时钟沿有效时复位。优点是抗毛刺,但复位信号必须满足建立时间。
  • 异步复位:复位信号不依赖时钟,立即生效。优点是响应快,但容易受毛刺干扰。

我建议,除非有特殊要求,否则用异步复位、同步释放的方式。既保证了复位的快速响应,又避免了异步复位带来的亚稳态问题。

小技巧:写RTL时,复位信号尽量用高电平有效还是低电平有效?我个人习惯用低电平有效(rst_n),因为很多标准库和IP都这么用,方便集成。

2.3 建立时间与保持时间

这是数字设计的“铁律”。不懂这个,你没法做时序分析。

建立时间(Setup Time):在时钟沿到来之前,数据必须保持稳定的最短时间。

保持时间(Hold Time):在时钟沿到来之后,数据必须保持稳定的最短时间。

为什么要有这两个时间?因为触发器内部需要时间来完成数据的采样和锁存。如果你数据变得太快,或者变得太慢,触发器就会进入亚稳态——输出既不是0也不是1,而是介于两者之间的不确定状态。

警告:亚稳态是数字设计的“癌症”。一旦出现,可能导致整个系统逻辑混乱,而且很难复现。我曾经调试过一个芯片,偶尔会在特定温度下出现数据错误,最后定位到是一条路径的保持时间违例。从那以后,我对STA(静态时序分析)再也不敢马虎了。

2.3.1 时序约束示例

在SDC(Synopsys Design Constraints)文件中,我们这样约束时钟:

# 创建时钟,周期10ns,占空比50%
create_clock -name clk -period 10 [get_ports clk]

# 设置输入延迟
set_input_delay -clock clk -max 5 [get_ports data_in]
set_input_delay -clock clk -min 2 [get_ports data_in]

# 设置输出延迟
set_output_delay -clock clk -max 6 [get_ports data_out]
set_output_delay -clock clk -min 1 [get_ports data_out]

你想想看,如果建立时间不满足,你可以通过降低时钟频率、减少组合逻辑级数、或者插入流水线寄存器来解决。但保持时间违例就麻烦多了——它跟时钟频率无关,只能通过插入延迟单元或者调整时钟偏斜来修复。

2.3.2 建立时间与保持时间的检查公式

检查项 公式 说明
建立时间 T_clk - T_co - T_logic - T_setup > 0 时钟周期减去触发器的输出延迟、组合逻辑延迟和建立时间,必须大于0
保持时间 T_co_min + T_logic_min - T_hold > 0 最小路径延迟必须大于保持时间

嗯,这里要注意:建立时间检查的是最慢路径(max path),保持时间检查的是最快路径(min path)。所以你在做时序优化时,要分别考虑这两种情况。

避坑指南:我曾经在一个项目中,为了优化建立时间,把组合逻辑级数压得很低。结果保持时间大面积违例,因为路径太快了,数据在时钟沿之后变化得太快,触发器来不及锁存。后来不得不插入一些缓冲器来增加延迟。所以,时序优化是个平衡的艺术,不能只盯着一个指标。

2.4 小结

这一讲的内容,说白了就是数字设计的“内功”。组合逻辑和时序逻辑的区别,决定了你写RTL时的思维方式。时钟和复位,决定了系统的同步基础。建立时间和保持时间,决定了芯片能不能稳定工作。

我建议大家在写代码之前,先画个简单的时序图,把数据路径和时钟关系理清楚。别急着敲键盘,想清楚了再动手。这样能省掉后面大量的调试时间。

下一讲,咱们聊聊流水线设计的基本原理。到时候我会拿一个实际的多周期乘法器来举例,看看怎么用流水线把吞吐量提上去。


公众号:蓝海资料掘金营,微信deep3321