第4章 · 时序逻辑设计规范

时序逻辑,说白了就是带记忆的电路。它靠时钟沿来更新状态,这也是数字芯片里最核心的部分。我刚开始写RTL那会儿,总觉得时序逻辑就是加个 always @(posedge clk) 完事,后来被Lint工具和综合结果狠狠教育过几次,才明白里面的门道有多深。

4.1 always块(时序逻辑)的正确写法

写时序逻辑的 always 块,我个人习惯遵循三条铁律:

  • 敏感列表只包含时钟和复位 —— 别把其他信号塞进去,综合工具会直接忽略多余项,但仿真可能出偏差。
  • 非阻塞赋值(<=) —— 这是时序逻辑的标配。用阻塞赋值(=)大概率会搞出竞争,我在一个项目里见过同事因此丢数据,查了两天才定位到。
  • 每个 always 块只描述一个时钟域 —— 混着写多个时钟,Lint 工具会报 CDC 违规,后端也会找你麻烦。
✅ 推荐模板(同步复位)
always @(posedge clk) begin
    if (rst_n) begin
        q <= 1'b0;
    end else begin
        q <= d;
    end
end

你想想看,如果这里用了 if (!rst_n) 但 rst_n 是异步信号,综合后可能会产生意想不到的复位树。嗯,这里要注意:敏感列表里写 posedge clk or negedge rst_n 才是异步复位,别搞混。

4.2 同步复位 vs 异步复位 —— 怎么选?

这个问题几乎每个芯片工程师都会纠结。我自己的经验是:没有银弹,但可以按场景选。

对比项同步复位异步复位
复位信号要求必须满足时钟沿采样可随时生效,不依赖时钟
抗毛刺能力强(时钟沿采样)弱(毛刺直接触发)
综合实现节省寄存器资源(无额外复位引脚)需要专用复位引脚,可能增加面积
时序收敛容易满足复位时序复位释放需做异步复位同步释放
典型应用高速逻辑、DFT友好低功耗、快速唤醒模块
💡 我的个人建议: 除非有明确的低功耗或快速复位需求,否则优先用同步复位。异步复位最大的坑是「复位释放时序」—— 我曾经在一个MCU项目里因为异步复位释放时间不满足,导致寄存器进入亚稳态,整个状态机跑飞。从那以后,我只要用异步复位,一定会加同步释放电路。

异步复位的标准写法(带同步释放):

// 异步复位同步释放
reg rst_sync1, rst_sync2;
always @(posedge clk or negedge rst_async_n) begin
    if (!rst_async_n) begin
        rst_sync1 <= 1'b0;
        rst_sync2 <= 1'b0;
    end else begin
        rst_sync1 <= 1'b1;
        rst_sync2 <= rst_sync1;
    end
end
// 用 rst_sync2 作为模块内部复位

4.3 时钟域的概念 —— 别让信号「串门」

时钟域,就是由同一个时钟(或同源时钟)驱动的所有时序逻辑的集合。不同时钟域之间的信号传递,必须经过同步处理,否则就是 CDC(跨时钟域)问题。

我记得刚做第一个SoC项目时,把 A 时钟域的计数器直接接到 B 时钟域的组合逻辑上,仿真没问题,但样片一跑就随机出错。后来用 SpyGlass 跑 Lint,报了一堆 CDC 违例。说白了,时钟域边界是芯片设计里最容易出 Bug 的地方。

⚠️ 避坑指南: 单比特跨时钟域用双级同步器(打两拍);多总线信号建议用异步FIFO或握手协议。千万别图省事直接连,我曾经见过有人用单级寄存器同步多比特信号,结果不同比特在不同时钟沿变化,导致数据错乱。
时序逻辑设计规范 · 知识体系 时序逻辑设计规范 always块正确写法 同步 vs 异步复位 时钟域概念 非阻塞赋值 敏感列表规范 同步复位 异步复位+同步释放 单比特同步器 异步FIFO/握手 Lint检查:CDC、复位同步、赋值风格 规范/方法 复位策略

最后说一句,时序逻辑的 Lint 检查重点就是这三块:always 块赋值风格、复位结构、时钟域边界。用 SpyGlass 或 Design_Lint 跑一遍,基本能筛掉 80% 的低级错误。我个人习惯在代码合入前先本地跑 Lint,省得 review 时被怼。