4. Verilog基础速通:模块化设计,组合逻辑与时序逻辑,状态机实战

好,咱们直接进入正题。这一章我打算带大家快速过一遍Verilog的核心概念。别怕,咱们不搞那些花里胡哨的语法大全,只讲FPGA加速AI推理时最常用的那三板斧:模块化设计、组合逻辑、时序逻辑,还有状态机。

说实话,我见过不少新手,一上来就抱着几百页的Verilog语法书啃。结果呢?啃完还是写不出能用的模块。我的建议是:先动手,再翻书。你写多了,自然就熟了。

4.1 模块化设计:把大问题拆成小零件

FPGA设计跟写软件不一样。软件你可以写一个几千行的main函数,但FPGA不行。你想想看,一个几百万门的芯片,你全塞在一个模块里,那调试起来得多痛苦?

模块化设计的核心思想就四个字:高内聚,低耦合。每个模块只干一件事,模块之间通过明确的接口通信。

模块的基本结构

module adder (
    input  wire [7:0] a,      // 输入端口
    input  wire [7:0] b,
    output wire [8:0] sum     // 输出端口
);
    assign sum = a + b;
endmodule

你看,一个加法器模块就这么简单。输入两个8位数,输出一个9位和。我在项目中经常把这种小模块当成乐高积木,拼出复杂的计算单元。

模块化设计有几个好处:

  • 可复用:写好的模块,下次直接拿来用
  • 可调试:哪个模块出问题,就查哪个
  • 可维护:改一个模块,不影响其他模块

我的习惯:每个模块文件只放一个module,文件名跟模块名保持一致。这样找代码特别方便。

4.2 组合逻辑:信号一来,立马响应

组合逻辑,说白了就是没有记忆的逻辑。输入变了,输出立刻跟着变,不需要时钟控制。

Verilog里描述组合逻辑有两种方式:

方式一:assign语句

assign y = (a & b) | c;

方式二:always @(*) 块

always @(*) begin
    if (sel)
        y = a;
    else
        y = b;
end

这里要注意一个坑:在always块里,组合逻辑必须用阻塞赋值(=),不能用非阻塞赋值(<=)。

我曾经踩过的坑:有一次写多路选择器,顺手用了非阻塞赋值。仿真看起来没问题,但综合出来的电路多了一堆没用的锁存器。从那以后,我写组合逻辑always块时,都会先看一眼赋值符号。

4.3 时序逻辑:让电路记住过去

时序逻辑跟组合逻辑最大的区别就是:它有记忆。它靠时钟沿来触发,把当前的状态存下来,留给下一个时钟周期用。

最典型的时序逻辑就是寄存器:

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        q <= 8'b0;
    else
        q <= d;
end

注意看,这里用的是非阻塞赋值(<=)。为什么?因为时序逻辑里,多个赋值语句是并行执行的。非阻塞赋值能保证所有赋值都在同一个时钟沿完成。

我刚开始学的时候,总觉得阻塞和非阻塞差不多。直到有一次写一个移位寄存器,用了阻塞赋值,结果数据全串了。嗯,从那以后我再也不敢乱用了。

黄金法则

  • 组合逻辑用阻塞赋值(=)
  • 时序逻辑用非阻塞赋值(<=)
  • 别混着用,除非你很清楚自己在干什么

4.4 状态机实战:控制逻辑的核心

状态机,说白了就是有限个状态之间跳来跳去。AI推理加速里,状态机无处不在:控制数据流、管理计算单元、处理握手协议……

我习惯用三段式状态机,结构清晰,不容易出错。

三段式状态机模板

// 第一段:状态转移
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state <= IDLE;
    else
        state <= next_state;
end

// 第二段:次态逻辑(组合逻辑)
always @(*) begin
    case (state)
        IDLE:   if (start) next_state = READ;
                else       next_state = IDLE;
        READ:   if (done)  next_state = CALC;
                else       next_state = READ;
        CALC:   if (done)  next_state = WRITE;
                else       next_state = CALC;
        WRITE:  if (done)  next_state = IDLE;
                else       next_state = WRITE;
        default: next_state = IDLE;
    endcase
end

// 第三段:输出逻辑(组合逻辑或时序逻辑)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        data_out <= 0;
    else case (state)
        READ:   data_out <= data_in;
        CALC:   data_out <= data_out + 1;
        default: ; // 保持
    endcase
end

为什么用三段式?因为:

  • 第一段只管状态跳转,简单明了
  • 第二段管下一步去哪,纯组合逻辑
  • 第三段管输出,可以组合也可以时序

这样写出来的状态机,可读性强,综合结果也好。我在做AI加速器时,控制路径全是这么写的。

避坑指南:状态编码尽量用独热码(one-hot)或者格雷码。二进制编码虽然省寄存器,但容易产生毛刺。我一般用独热码,面积大一点,但稳定。

4.5 知识体系总览

下面这张图,是我自己总结的Verilog基础速通知识体系。你看一眼,心里就有谱了。

Verilog基础速通知识体系 模块化设计 端口定义 模块实例化 参数化设计 组合逻辑 assign连续赋值 always @(*) 块 阻塞赋值 = 时序逻辑 寄存器建模 非阻塞赋值 <= 时钟与复位 状态机实战 一段式(不推荐) 二段式(可用) 三段式(推荐) 核心原则:高内聚低耦合 | 组合逻辑无记忆 | 时序逻辑靠时钟 | 状态机三段式 AI推理加速中,控制路径多用状态机,数据路径多用组合+寄存器流水

这张图把咱们今天讲的内容串起来了。模块化是骨架,组合逻辑和时序逻辑是血肉,状态机是大脑。你写代码的时候,心里要有这张图。

好了,这一章就到这里。记住:多写、多仿真、多总结。Verilog这东西,上手不难,但想写好,得靠积累。


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