3、FPGA基础回顾:Verilog/VHDL语法速览、组合逻辑与时序逻辑、状态机设计模式

各位同学,欢迎来到第三章。这一章我们聊聊FPGA设计的基础。我知道很多人觉得语法枯燥,但别急,我会结合实战经验来讲。说白了,FPGA设计就是搭积木,而Verilog和VHDL就是你的积木说明书。

3.1 Verilog/VHDL语法速览:我选Verilog的理由

先说说语言选择。我个人习惯用Verilog,原因很简单——在量化交易领域,Verilog的生态更成熟。你想想看,大部分高频交易团队的代码都是Verilog写的,遇到问题更容易找到人交流。

不过VHDL也有它的优势,比如类型检查更严格。我在项目中遇到过一位同事,用VHDL写状态机,编译时抓出了好几个类型不匹配的bug。要是用Verilog,这些bug可能要到仿真才能发现。

这里我给大家一个速查表,对比一下两种语言的核心语法:

功能 Verilog VHDL
模块定义 module name (ports); entity name is port (...);
输入输出 input wire a; a : in std_logic;
组合逻辑 assign c = a & b; c <= a and b;
时序逻辑 always @(posedge clk) process(clk) if rising_edge(clk)
参数化 parameter WIDTH = 8; generic (WIDTH : integer := 8);

嗯,这里要注意:无论你用哪种语言,核心思想是一样的——描述硬件行为,而不是写软件。我见过太多新手把Verilog当C语言写,结果综合出来的电路完全不是那么回事。

3.2 组合逻辑与时序逻辑:两个世界的分界线

这是FPGA设计最核心的概念。我经常跟团队说:组合逻辑是「现在」的电路,时序逻辑是「下一拍」的电路

组合逻辑:没有记忆的电路

组合逻辑的输出只取决于当前输入。比如一个加法器,输入A和B,输出就是A+B,跟历史状态无关。

// 组合逻辑示例:4位加法器
module adder_4bit (
    input  [3:0] a,
    input  [3:0] b,
    output [4:0] sum
);
    assign sum = a + b;
endmodule

我在项目中遇到过一个问题:有人用组合逻辑写了很长的级联加法,结果路径延迟太大,时序跑不过。后来我改成流水线结构,一拍加一级,频率直接从200MHz飙到了500MHz。这就是组合逻辑的坑——它不占寄存器,但会吃掉你的时序预算。

时序逻辑:有记忆的电路

时序逻辑就不一样了。它依赖时钟沿,输出不仅跟当前输入有关,还跟之前的状态有关。说白了,就是带寄存器的电路。

// 时序逻辑示例:带使能的8位寄存器
module reg_8bit (
    input        clk,
    input        rst_n,
    input        en,
    input  [7:0] d,
    output reg [7:0] q
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            q <= 8'b0;
        else if (en)
            q <= d;
    end
endmodule

你想想看,在量化交易系统中,时序逻辑有多重要?每个交易信号的生成、每个订单的发送,都需要精确的时钟控制。我曾经调试过一个bug,就是因为组合逻辑和时序逻辑混用,导致信号提前了一个时钟周期到达,差点造成误交易。

核心原则:组合逻辑用assignalways @(*),时序逻辑用always @(posedge clk)。千万别混着写,否则综合工具会给你「惊喜」。

3.3 状态机设计模式:交易系统的灵魂

状态机是FPGA设计的精髓。在量化交易中,从订单管理到风险控制,到处都有状态机的身影。我个人习惯把状态机分成三类:

  • Moore型:输出只取决于当前状态
  • Mealy型:输出取决于当前状态和输入
  • 混合型:两者结合,实际项目中最常用

下面这张图展示了状态机在交易系统中的应用框架:

交易系统状态机框架 IDLE 等待信号 SIGNAL_DETECT 解析行情 ORDER_GEN 生成订单 WAIT_ACK 等待交易所确认 DONE 交易完成 ERROR 异常处理 行情触发 信号有效 发送订单 确认收到 超时/拒绝 准备下一笔 错误恢复 状态说明 ● 实线箭头:正常交易流程 ● 虚线箭头:状态复位/恢复 ● 每个状态都有超时保护,防止死锁

下面是一个典型的三段式状态机写法,我个人强烈推荐这种风格:

// 三段式状态机:交易订单状态管理
module order_fsm (
    input        clk,
    input        rst_n,
    input        signal_valid,
    input        ack_received,
    input        timeout,
    output reg   order_sent,
    output reg   error_flag
);

    // 状态编码
    localparam IDLE         = 3'b001;
    localparam SIGNAL_DETECT = 3'b010;
    localparam ORDER_GEN    = 3'b100;
    
    reg [2:0] state, next_state;
    
    // 第一段:状态寄存器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state <= IDLE;
        else
            state <= next_state;
    end
    
    // 第二段:次态逻辑(组合逻辑)
    always @(*) begin
        next_state = state;  // 默认保持
        case (state)
            IDLE: begin
                if (signal_valid)
                    next_state = SIGNAL_DETECT;
            end
            SIGNAL_DETECT: begin
                if (signal_valid)  // 信号持续有效
                    next_state = ORDER_GEN;
                else if (timeout)
                    next_state = IDLE;
            end
            ORDER_GEN: begin
                if (ack_received)
                    next_state = IDLE;
                else if (timeout)
                    next_state = IDLE;  // 超时重试
            end
            default: next_state = IDLE;
        endcase
    end
    
    // 第三段:输出逻辑(时序逻辑)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            order_sent <= 1'b0;
            error_flag <= 1'b0;
        end else begin
            case (next_state)
                ORDER_GEN: order_sent <= 1'b1;
                default:   order_sent <= 1'b0;
            endcase
            // 错误检测
            if (state == ORDER_GEN && timeout)
                error_flag <= 1'b1;
        end
    end
    
endmodule

我的经验:三段式状态机的好处是「状态跳转」和「输出逻辑」分离,调试起来特别方便。我曾经在实盘环境中遇到一个bug,状态机卡在某个状态出不来。用三段式写法,我直接看状态寄存器的值就定位到了问题——原来是某个条件没满足,导致次态逻辑永远跳不出去。

避坑指南:我曾经在状态机编码上吃过亏。用二进制编码(00,01,10)时,如果状态跳转出现毛刺,可能会跳到非法状态。后来我改用独热码(001,010,100),虽然多用了寄存器,但可靠性高了很多。在交易系统中,可靠性永远优先于资源占用。

3.4 实战要点:从代码到硬件

最后,我总结几个实战中必须注意的点:

  • 同步复位 vs 异步复位:我建议用异步复位、同步释放。这样既能保证复位可靠,又能避免亚稳态问题。
  • 时钟域处理:交易系统里经常有多个时钟域(比如行情时钟和交易时钟)。跨时钟域信号一定要用两级同步器,别偷懒。
  • 资源评估:写代码前先算算要用多少LUT和FF。我见过有人写了个很大的状态机,结果芯片装不下,最后只能砍功能。

好了,这一章的内容就到这里。记住,FPGA设计不是写软件,而是画电路。每一行代码都要想清楚:它综合出来是什么样子?延迟多少?会不会成为瓶颈?带着这些问题去写代码,你的设计水平会提升得很快。


专注资料整理