第三章 硬件描述语言基础:Verilog/VHDL核心语法、模块化设计、组合逻辑与时序逻辑

各位同学,欢迎来到《FPGA量化交易性能调优指南》的第三讲。今天咱们聊聊硬件描述语言——说白了,就是FPGA的“编程语言”。我个人习惯用Verilog,但VHDL我也用过不少。别纠结选哪个,核心思想是一样的:用代码描述硬件。

你想想看,写软件代码和写硬件代码最大的区别是什么?软件是顺序执行的,硬件是并行工作的。这个思维转变,是入门的第一道坎。我刚开始学的时候,总想把Verilog当C语言写,结果综合出来的电路一塌糊涂。嗯,今天咱们就把这个坎迈过去。

3.1 Verilog/VHDL核心语法:从“是什么”到“怎么用”

先看Verilog。一个模块的基本结构长这样:

module counter (
    input  wire       clk,
    input  wire       rst_n,
    output reg  [7:0] count
);

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        count <= 8'd0;
    else
        count <= count + 1'b1;
end

endmodule

这段代码描述了一个8位计数器。注意那个 always @(posedge clk or negedge rst_n),这叫敏感列表。它告诉综合工具:这个always块在时钟上升沿或复位下降沿触发。说白了,这就是一个时序逻辑的模板。

VHDL的写法稍微啰嗦一点:

entity counter is
    port (
        clk   : in  std_logic;
        rst_n : in  std_logic;
        count : out std_logic_vector(7 downto 0)
    );
end entity;

architecture rtl of counter is
    signal count_reg : unsigned(7 downto 0);
begin
    process(clk, rst_n)
    begin
        if rst_n = '0' then
            count_reg <= (others => '0');
        elsif rising_edge(clk) then
            count_reg <= count_reg + 1;
        end if;
    end process;
    count <= std_logic_vector(count_reg);
end architecture;

我个人觉得,Verilog更简洁,适合快速原型开发;VHDL更严谨,适合大型工程。但说实话,在量化交易领域,速度才是王道,所以用Verilog的团队更多。

小技巧: 写Verilog时,wirereg别搞混。简单记:wire是连线,reg是寄存器。但注意,在always块里赋值的变量必须声明为reg,哪怕它综合出来只是组合逻辑。

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

FPGA设计不是写一个巨大的模块就完事了。你想想看,一个交易引擎可能有几十万行代码,全塞在一个文件里,谁看得懂?模块化设计就是要把系统拆成功能独立的子模块。

我在项目中遇到过这样一个场景:一个高频交易系统需要同时处理多个数据流。如果所有逻辑都写在一个模块里,调试起来简直噩梦。后来我把它拆成了数据接收模块、解析模块、计算模块、发送模块,每个模块单独测试,最后再拼起来。嗯,效率高多了。

模块化设计的原则:

  • 单一职责: 一个模块只做一件事。比如,只做加法,别又做加法又做乘法又做控制。
  • 接口清晰: 输入输出信号要明确,别搞一堆全局变量。
  • 层次化: 顶层模块只负责连线,底层模块实现具体功能。

举个例子,一个简单的交易信号生成模块:

// 顶层模块
module trading_engine (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [31:0] price,
    input  wire [31:0] threshold,
    output reg         buy_signal
);

wire [31:0] diff;
wire        cmp_result;

// 子模块实例化
subtractor u_sub (.a(price), .b(threshold), .diff(diff));
comparator  u_cmp (.a(diff), .b(32'd0), .result(cmp_result));

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        buy_signal <= 1'b0;
    else
        buy_signal <= cmp_result;
end

endmodule

你看,顶层模块只负责把减法器和比较器连起来,逻辑清晰,维护方便。

核心思想: 模块化设计不是把代码拆开就完事了,而是要保证每个模块可以独立仿真、独立测试。我在做交易系统时,每个子模块都有对应的testbench,确保功能正确后再集成。

3.3 组合逻辑与时序逻辑:理解硬件的“时间”概念

这是FPGA设计里最基础也最容易出错的地方。组合逻辑的输出只取决于当前输入,没有记忆功能。时序逻辑的输出不仅取决于当前输入,还取决于之前的状态——说白了,它有“记忆”。

组合逻辑的典型写法:

// 组合逻辑:输出只和输入有关
assign sum = a + b;

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

时序逻辑的典型写法:

// 时序逻辑:输出和时钟有关
always @(posedge clk) begin
    q <= d;
end

注意那个赋值符号的区别:组合逻辑用 =(阻塞赋值),时序逻辑用 <=(非阻塞赋值)。这个细节坑过不少人。我曾经在调试一个交易系统的流水线时,因为把非阻塞赋值用成了阻塞赋值,导致数据时序全乱了,查了两天才找到问题。

为什么会这样?因为阻塞赋值是“立即生效”的,而非阻塞赋值是“在时钟沿统一更新”的。如果你在时序逻辑里用阻塞赋值,综合出来的电路可能和你想象的不一样。

避坑指南: 我曾经在同一个always块里混用了阻塞和非阻塞赋值,结果综合工具报了一堆警告,仿真结果也不对。记住:时序逻辑用非阻塞赋值,组合逻辑用阻塞赋值,别混着用。

下面这张图展示了组合逻辑和时序逻辑在FPGA中的基本结构:

组合逻辑 (加法器、比较器等) 输入 输出 时序逻辑 (寄存器、计数器等) 需要时钟驱动 clk 组合逻辑 vs 时序逻辑 组合逻辑无记忆,时序逻辑有记忆

在实际的量化交易系统中,组合逻辑和时序逻辑是混合使用的。比如,一个交易信号生成模块:组合逻辑负责计算价格差值、比较阈值,时序逻辑负责在时钟沿锁存结果、生成交易信号。

最后,给大家一个实用的建议:写代码前,先在纸上画出电路结构。我每次设计新模块,都会先画一个简单的框图,标清楚哪些是组合逻辑,哪些是时序逻辑。这样写出来的代码,综合结果基本和预期一致。

总结一下:
  • Verilog和VHDL只是工具,核心是硬件思维
  • 模块化设计让复杂系统变得可控
  • 组合逻辑和时序逻辑是FPGA设计的基石,搞清楚了,后面学流水线、状态机就轻松了

好了,这一章就到这里。记住,多写代码,多仿真,多看看综合后的电路图。FPGA设计没有捷径,但有了正确的方法论,弯路可以少走很多。


专注资料整理