FPGA 开发入门:Verilog 与逻辑综合

FreeGuideOnline 最新 2026-06-15

什么是 FPGA?

FPGA(Field-Programmable Gate Array,现场可编程门阵列)是一种可以通过硬件描述语言重新配置内部电路逻辑的芯片。与固定功能的 CPU 或 GPU 不同,FPGA 可以实现你自定义的数字电路,从而在特定任务上获得极高的并行处理能力和低延迟。

FPGA 能做什么?
信号处理、高速通信接口、人工智能加速、电机控制、图像处理……几乎任何需要低延迟、高吞吐或确定性的数字逻辑系统,都可以在 FPGA 上实现。

与 CPU 对比

  • CPU:顺序执行指令,适合复杂决策和通用计算。
  • FPGA:直接构建硬件电路,并行运行,适合流水线和数据处理管道。

FPGA 架构基础

现代 FPGA 内部主要由可编程逻辑块(Logic Block)、互联资源、I/O 模块和专用硬核(如 Block RAM、DSP)组成。

可编程逻辑块

每一个逻辑块通常包含:

  • 查找表(LUT):实现任意布尔函数,可以看作一个小型真值表。例如 4 输入 LUT 能实现任意 4 输入的逻辑组合。
  • 触发器(Flip-Flop, FF):用于存储状态,构建时序逻辑,如寄存器和计数器。

FPGA 通过将成千上万个这样的逻辑块连接在一起,实现任意复杂的数字电路。

其他重要资源

  • 可编程互连:由开关矩阵和连线组成,负责连接逻辑块和 I/O。
  • Block RAM(BRAM):嵌入式存储器,可配置为单端口或双端口 RAM/ROM。
  • DSP 块:专用乘法累加器,用于高速信号处理。
  • I/O 引脚:支持多种电平和标准,如 LVCMOS、LVDS 等。

FPGA 开发流程

典型的 FPGA 开发遵循 “编写代码 → 仿真 → 综合 → 实现 → 生成比特流 → 上板测试” 的顺序。

  1. 设计输入:使用 Verilog 或 VHDL 描述电路功能(RTL,寄存器传输级)。
  2. 功能仿真:用测试平台(testbench)验证逻辑行为是否正确,常用工具如 ModelSim、Vivado Simulator。
  3. 逻辑综合:将 RTL 代码转换为由逻辑门和触发器组成的网表。
  4. 布局布线(实现):将网表映射到 FPGA 的物理逻辑块上,并连接。
  5. 生成比特流:产生最终配置 FPGA 的二进制文件。
  6. 下载与验证:将比特流烧录到 FPGA,在实际硬件上观察或调试。

Verilog 快速入门

Verilog 是描述数字电路最常用的硬件描述语言之一,语法上与 C 语言有些相似,但思想完全不同——你在描述硬件结构,而不是软件指令。

模块与端口

Verilog 的基本设计单元是模块(module),类似一个芯片的封装,带有输入输出端口。

module and_gate (
    input  wire a,
    input  wire b,
    output wire y
);
    assign y = a & b;
endmodule

数据类型

  • wire:表示连线,不能存储值,由连续赋值驱动。
  • reg:表示寄存器(变量),在过程块(always)中赋值使用,但综合后不一定真的成为触发器,取决于描述方式。

组合逻辑描述

使用 assign 连续赋值语句,描述纯组合电路:

assign sum = a + b;
assign carry_out = (a & b) | (carry_in & (a ^ b));

或者用 always @(*) 过程块(行为级):

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

时序逻辑描述

使用 always @(posedge clk) 描述带时钟的寄存器行为:

reg [7:0] counter;

always @(posedge clk or posedge reset) begin
    if (reset)
        counter <= 8'd0;
    else
        counter <= counter + 1'b1;
end

这里使用非阻塞赋值 <=,是时序逻辑推荐写法,能正确模拟寄存器行为。

向量与位选择

wire [7:0] data;   // 8 位宽
assign data[3:0] = 4'b1010;  // 只赋值低 4 位

逻辑综合是什么?

逻辑综合是将你写的 Verilog/VHDL 代码(RTL 描述)自动转换为由基本门电路(与、或、非)和触发器组成的网表的过程。综合工具(如 Synopsys Design Compiler、Yosys,或 Vivado/Quartus 自带综合器)会进行优化,以满足面积、速度或功耗的要求。

综合如何工作?

  1. 编译与解析:理解 HDL 代码的语法和语义。
  2. 逻辑优化:布尔化简、资源共享、常量传播等。
  3. 工艺映射:将优化后的逻辑映射到目标 FPGA 的 LUT、FF 等具体资源上。
  4. 网表输出:产生描述各元件连接关系的文件。

关键点:所有可综合的代码都必须能映射到实际硬件上。循环在综合时会被展开,函数调用被内联,递归不可综合(除非静态确定)。

时序约束的必要性

综合工具需要知道设计的时钟频率、输入输出延迟等才能优化。你需要通过 .sdc 或约束文件来告诉工具:

  • create_clock:定义时钟周期和占空比。
  • set_input_delay / set_output_delay:设置 I/O 时序要求。 不提供约束的综合只是预优化,最终的布局布线必须满足时序约束才能正常工作。

实战:流水灯设计(点亮 LED)

我们从最简单的流水灯实验开始,理解从 Verilog 代码到 FPGA 上运行的完整路径。假设 FPGA 板载 50 MHz 时钟,4 个 LED 低电平点亮,需要设计一个让 LED 每隔约 0.5 秒向左循环移位一次的逻辑。

1. 设计文件:water_light.v

module water_light (
    input  wire       clk,   // 50 MHz
    input  wire       rst_n, // 低电平复位
    output wire [3:0] led    // 低电平点亮
);

    // 计数器分频,50M / 25M = 2 Hz? 实际上我们要0.5s = 2 Hz? 
    // 0.5s 切换一次,对应 2 Hz 频率。50M / 25M = 2 Hz (每 25M 个时钟周期翻转一次)
    // 计数范围 0 ~ 24,999,999 (需要25M个状态)
    parameter MAX_CNT = 25_000_000 - 1;
    reg [24:0] cnt;

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

    // 移位寄存器
    reg [3:0] led_reg;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            led_reg <= 4'b1110; // 初始点亮最低位LED
        else if (cnt == MAX_CNT)
            led_reg <= {led_reg[2:0], led_reg[3]}; // 循环左移
    end

    assign led = led_reg; // 假设LED低电平亮,但这里为直观输出低有效
    // 若LED高电平亮,将reg初始改为0001,并反向输出
endmodule

2. 仿真验证(可选)

编写一个简单 testbench,生成时钟和复位信号,在仿真工具中观察 led 信号是否按要求变化。这一步能极大减少debug时间。

3. 综合与引脚约束

将设计添加到 FPGA 开发软件(Vivado 或 Quartus)中,进行综合。综合后可查看原理图(Schematic),确认生成了计数器和移位寄存器。

然后编写约束文件(如 .xdc 文件),将 clk 分配到板载 50 MHz 时钟引脚,rst_n 分配到一个按钮,led 分配到对应的 LED 引脚,并创建时钟约束:

create_clock -period 20.000 [get_ports clk]
set_input_clock_uncertainty 0.1 [get_clocks]

4. 布局布线及生成比特流

运行实现(Implementation),检查时序报告,确保没有时序违例。生成比特流文件。

5. 下载并观察

连接编程器,将比特流烧录到 FPGA。按下复位键后,LED 应开始流水循环。

进阶提示

时序收敛

如果设计不满足时钟约束,需要分析关键路径,常用手段:

  • 添加流水线寄存器(pipeline)。
  • 减少组合逻辑级数。
  • 优化扇出,复制高负载寄存器。

使用 IP 核

FPGA 厂商提供了大量已验证的 IP 核(FIFO、PLL、以太网控制器等)。初学者可以先用 IP 核搭建系统,再逐步深入设计自己的模块。例如用 PLL 生成任意频率时钟,避免手动分频带来的时序问题。

状态机设计

复杂控制逻辑适合用有限状态机(FSM)描述。推荐使用三段式状态机写法(现态、次态、输出),清晰且易综合。

// 状态编码
localparam IDLE = 2'd0, WORK = 2'd1, DONE = 2'd2;

// 状态跳转(用时序块)
always @(posedge clk) begin
    if (rst) state <= IDLE;
    else state <= next_state;
end

// 次态逻辑(用组合块)
always @(*) begin
    case(state)
        IDLE: next_state = start ? WORK : IDLE;
        ...
    endcase
end

// 输出逻辑
always @(posedge clk) begin
    case(state)
        ...
    endcase
end

实例化与层次化设计

大设计应拆分为多个模块,顶层只做连线。模块实例化语法:

counter #(.WIDTH(8)) u_counter (
    .clk(clk),
    .reset(reset),
    .count(count_out)
);

参数化使代码复用性大大提高。

总结

FPGA 开发入门的关键在于转变思维:你不再编写顺序执行的程序,而是用硬件描述语言构造并行电路。学习路径建议如下:

  1. 熟悉 Verilog/Hardware Description Language 基础,特别是组合/时序逻辑描述。
  2. 理解 FPGA 内部结构(LUT、FF、BRAM)。
  3. 掌握开发流程:仿真→综合→约束→实现→下载。
  4. 多动手实践,从流水灯、按键消抖到 UART 通信,循序渐进。

工具链的熟练使用和时序分析能力会在后续项目中自然积累。当你能让一个看似简单的计数器在 FPGA 上稳定工作,并满足时序约束时,就意味着你已经迈入了数字硬件设计的大门。