3. Verilog HDL 基础与 RTL 设计:组合逻辑、时序逻辑、状态机与流水线

各位同学,咱们今天聊点硬核的。Verilog 这玩意儿,说白了就是硬件工程师的“普通话”。你写出来的每一行代码,最后都会变成实实在在的门电路和触发器。我见过不少新手,把 Verilog 当 C 语言写,结果综合出来的电路自己都看不懂。嗯,咱们今天就把这事掰扯清楚。

3.1 组合逻辑:信号一进来,输出立马变

组合逻辑的特点很简单:输出只取决于当前的输入。没有记忆,没有时钟,信号路径上走多快,输出就变多快。我在项目中遇到过最典型的组合逻辑就是地址译码器——地址线一变,片选信号立刻跟着跳。

Verilog 里描述组合逻辑,我习惯用 assign 连续赋值,或者 always @(*) 过程块。看个例子:

// 方式一:assign 连续赋值
assign sel = (addr >= 8'h20) && (addr <= 8'h2F) ? 1'b1 : 1'b0;

// 方式二:always 块(组合逻辑)
always @(*) begin
    if (addr >= 8'h20 && addr <= 8'h2F)
        sel = 1'b1;
    else
        sel = 1'b0;
end
⚠ 避坑指南: 我曾经在 always 块里漏写了 else 分支,结果综合工具给我生成了一个锁存器(latch)。锁存器这东西在 FPGA 里是毒药,时序难收敛,功耗还大。记住:组合逻辑的 always 块,所有条件分支必须写全,否则默认值就是保持——保持就是锁存。

3.2 时序逻辑:靠时钟吃饭,有记忆

时序逻辑就不一样了。它靠时钟沿干活,输出不仅看当前输入,还得看之前的状态。说白了,它是有记忆的。寄存器(flip-flop)是时序逻辑的基本单元。

我写时序逻辑,几乎只用 always @(posedge clk) 或者 always @(negedge clk)。复位方式我推荐同步复位,除非有特殊要求。为什么?因为异步复位容易引起亚稳态,我在调试一块 ISP 管线时就吃过这个亏——复位释放时刻刚好在时钟沿上,整个状态机跑飞了。

// 带同步复位的 D 触发器
always @(posedge clk) begin
    if (rst_n)
        q <= 1'b0;
    else
        q <= d;
end
💡 个人习惯: 我写时序逻辑时,所有赋值都用非阻塞赋值 <=。组合逻辑用阻塞赋值 =。这个规矩别打破,否则仿真和综合结果不一致,你查三天都查不出来。

3.3 有限状态机:ISP 控制逻辑的骨架

有限状态机(FSM)是数字设计的核心。ISP 管线里的曝光控制、增益切换、白平衡校准,背后全是状态机在调度。我习惯把状态机分成三段式来写:

  • 第一段: 时序逻辑,描述状态跳转
  • 第二段: 组合逻辑,描述下一状态的条件
  • 第三段: 组合逻辑,描述输出

为什么用三段式?因为可读性强,综合出来的电路也干净。我曾经接手过一个项目,前任工程师把所有逻辑塞在一个 always 块里,状态机写得像意大利面条。我花了整整两天才理清楚。

// 三段式状态机示例
// 第一段:状态寄存器
always @(posedge clk or posedge rst) begin
    if (rst)
        state <= IDLE;
    else
        state <= next_state;
end

// 第二段:下一状态逻辑
always @(*) begin
    case (state)
        IDLE:   next_state = start ? READ : IDLE;
        READ:   next_state = done ? PROCESS : READ;
        PROCESS: next_state = done ? IDLE : PROCESS;
        default: next_state = IDLE;
    endcase
end

// 第三段:输出逻辑
always @(*) begin
    case (state)
        IDLE:   data_valid = 1'b0;
        READ:   data_valid = 1'b0;
        PROCESS: data_valid = 1'b1;
        default: data_valid = 1'b0;
    endcase
end
🔑 关键点: 状态编码我推荐用独热码(one-hot)——每个状态只有一个 bit 为 1。虽然多用了几个触发器,但译码逻辑简单,时序容易收敛。在 ISP 这种高速场景下,时序比面积重要。

3.4 流水线设计:用面积换速度

流水线是 ISP 硬件加速的灵魂。你想想看,一个像素处理要经过黑电平校正、去噪、白平衡、颜色插值……如果每个模块都串行做,一帧图像要等多久?流水线的思路就是:把一个大任务拆成多个小阶段,每个阶段用寄存器隔开,所有阶段同时工作。

我举个例子。假设一个简单的加法链:a + b + c + d。非流水线实现需要三级加法,每级都要等上一级算完。流水线实现就是:

// 非流水线
always @(posedge clk)
    sum <= a + b + c + d;  // 组合路径太长,时序容易崩

// 三级流水线
reg [7:0] sum1, sum2;
always @(posedge clk) begin
    sum1 <= a + b;          // 第一级
    sum2 <= sum1 + c;       // 第二级
    sum  <= sum2 + d;       // 第三级
end

流水线每级只做一点点事,路径短,频率就能跑高。代价是多了几级寄存器,还有 3 个时钟周期的延迟。在 ISP 里,这种延迟完全可以接受——你想想看,一帧图像有几百万个像素,流水线填满后,每个时钟周期都能出一个结果。

⚠ 注意: 流水线不是万能的。如果数据流有反馈环路(比如迭代算法),流水线就不好使了。我在做 ISP 的自动曝光算法时就遇到过——曝光值要依赖上一帧的结果,流水线反而把反馈延迟搞大了。这时候就得用其他技巧,比如提前预测。

3.5 知识体系总览

下面这张图把本章的核心逻辑串起来了。你仔细看看,组合逻辑和时序逻辑是基础,状态机管控制,流水线管加速——三者配合,才能搭出高效的 ISP 硬件。

Verilog RTL 设计核心知识体系 组合逻辑 时序逻辑 有限状态机 流水线设计 RTL 设计基础 📌 核心关系: 组合逻辑 + 时序逻辑 → 构成 RTL 基本单元 有限状态机 → 用组合+时序实现控制逻辑 流水线 → 在时序路径中插入寄存器,提升吞吐率

3.6 实战建议:从代码到电路

最后给你几条实在的建议:

  • 写代码前先画电路: 我习惯在纸上先画出寄存器、多路选择器、加法器,再动手写 Verilog。代码是电路的描述,不是软件逻辑。
  • 仿真看波形: 别只盯着 log 文件。打开波形图,看看每个时钟沿数据对不对。我调试 ISP 管线时,90% 的问题都是看波形发现的。
  • 综合后看报告: 看看工具给你推断了多少寄存器、多少 LUT。如果综合出来的电路跟你预期不一样,那代码肯定有问题。
💡 我的经验: 刚开始学 RTL 设计时,别追求花哨的写法。老老实实用三段式状态机、非阻塞赋值、完整的 case 分支。代码干净了,电路自然就干净。