第四章:时钟与复位——芯片的“心跳”与“重启键”

时钟和复位,是数字芯片里最基础、也最容易出问题的两个信号。我常说,搞不定时钟和复位,后面的验证工作就像在沙滩上盖楼。今天咱们就聊聊时钟怎么生成、复位怎么处理,以及分频倍频那些事儿。

核心要点:时钟是时序逻辑的基准,复位是初始状态的保证。两者处理不当,芯片功能必然紊乱。

4.1 时钟生成方法

时钟信号怎么来?说白了,要么从外部晶振直接进来,要么通过PLL(锁相环)在芯片内部生成。我个人习惯在验证环境里用两种方式:

  • 直接生成法:用initial块或always块产生周期信号。适合简单测试。
  • 系统时钟模型:模拟真实PLL行为,包含锁定时间、抖动等。适合系统级验证。

先看一个最基础的时钟生成代码:

// 基础时钟生成 - 50MHz,周期20ns
reg clk;
initial begin
    clk = 0;
    forever #10 clk = ~clk;  // 每10ns翻转一次
end

嗯,这里要注意:forever必须放在initial块里,否则仿真器会报错。我在项目中遇到过有人把forever直接写在always块外面,结果仿真直接卡死。

小技巧:如果需要多个不同频率的时钟,可以用一个基础时钟通过计数器分频得到。这样能保证所有时钟同源,避免跨时钟域问题。

4.2 复位信号的同步与异步处理

复位信号,你想想看,它决定了芯片上电后所有寄存器从什么状态开始跑。处理不好,芯片可能永远进不了正常工作状态。

复位分两种:

类型 特点 适用场景
异步复位 复位信号不依赖时钟,立即生效 上电复位、紧急复位
同步复位 复位信号需在时钟沿采样后才生效 功能复位、软件复位

我曾经在一个项目中,设计人员用了异步复位,但没做同步释放处理。结果复位撤销时刚好碰上时钟沿,导致部分寄存器复位失败,芯片功能时好时坏。排查了整整两天才找到原因。

正确的做法是——异步复位,同步释放:

// 异步复位同步释放电路
reg rst_n_sync1, rst_n_sync2;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        rst_n_sync1 <= 1'b0;
        rst_n_sync2 <= 1'b0;
    end else begin
        rst_n_sync1 <= 1'b1;
        rst_n_sync2 <= rst_n_sync1;
    end
end

// 使用同步后的复位
always @(posedge clk or negedge rst_n_sync2) begin
    if (!rst_n_sync2)
        data_out <= 'b0;
    else
        data_out <= data_in;
end

警告:千万不要在RTL里直接用异步复位驱动多个触发器!每个触发器到复位源的距离不同,会导致复位撤销时间不一致,引发亚稳态。

4.3 时钟分频与倍频

分频和倍频,说白了就是改变时钟频率。分频用计数器就能搞定,倍频通常需要PLL。

4.3.1 时钟分频

偶数分频最简单,计数器计到一半翻转就行。奇数分频稍微麻烦点,需要同时用上升沿和下降沿触发。

// 3分频电路(占空比50%)
reg clk_div3;
reg [1:0] cnt;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) cnt <= 0;
    else if (cnt == 2) cnt <= 0;
    else cnt <= cnt + 1;
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) clk_div3 <= 0;
    else if (cnt == 0) clk_div3 <= ~clk_div3;
end

为什么这样写?因为3分频需要3个时钟周期完成一个完整周期。计数器从0到2,在0时刻翻转一次,就能得到50%占空比。我刚开始做分频时,总想着用if (cnt == 1)翻转,结果占空比怎么调都不对。

4.3.2 时钟倍频

倍频在FPGA里通常调用PLL IP核,在ASIC里用模拟PLL。验证时我们一般用行为模型模拟:

// PLL行为模型 - 2倍频
reg pll_clk;
real period = 10.0;  // 输入时钟周期10ns
always begin
    #(period/4) pll_clk = 1;
    #(period/4) pll_clk = 0;
end

这个模型很简单,但忽略了PLL的锁定时间。实际项目中,PLL从启动到稳定输出需要几百微秒。我曾经在验证时没等PLL锁定就直接用时钟,结果前1000个时钟周期全是乱的,仿真波形惨不忍睹。

避坑指南:验证环境中一定要模拟PLL的锁定过程。可以用一个pll_locked信号,在锁定时间到达后才拉高,所有逻辑只有在pll_locked为高时才工作。

4.4 本章知识体系

下面这张图总结了时钟与复位的核心知识点,我建议你保存下来,写代码前先过一遍:

时钟与复位知识体系 时钟生成 直接生成法 PLL模型生成 forever #10 clk=~clk pll_locked 信号 复位处理 异步复位 同步复位 立即生效 时钟沿采样 时钟分频与倍频 偶数分频(计数器) 奇数分频(双沿触发) cnt == N/2 时翻转 posedge + negedge 倍频:PLL IP核 / 行为模型

这张图把时钟生成、复位处理、分频倍频三个模块串起来了。你写代码时,先想清楚用的是哪种时钟源、复位怎么同步、分频系数是多少,基本就不会出大问题。

个人经验:我习惯在验证环境里加一个时钟和复位的checker,自动检查时钟频率是否稳定、复位释放是否同步。这样跑回归测试时,一旦时钟或复位出问题,第一时间就能发现,不用等到波形分析才发现。

好了,时钟和复位就聊到这儿。记住一句话:时钟是芯片的心跳,复位是芯片的重启键。这两样搞定了,你的验证工作就成功了一半。

可可精品资料库