3. 硬件描述语言基础
FPGA 设计的本质是使用硬件描述语言(HDL)对数字电路进行建模与实现。Verilog 与 VHDL 作为两大主流 HDL,虽然语法风格迥异,但底层描述的都是组合逻辑、时序逻辑与状态机这三种基本电路结构。本章将聚焦于 FPGA 图像采集与实时处理系统中最高频使用的 HDL 核心语法、设计范式与工程方法,帮助学员建立从“软件思维”向“硬件思维”转换的关键认知。
3.1 核心原理:HDL 的本质是描述硬件结构而非执行程序
许多初学者常犯的错误是将 Verilog 或 VHDL 当作 C 语言来写,试图用“顺序执行”的思维去理解代码。实际上,HDL 描述的是电路中信号如何连接、逻辑门如何组合、寄存器何时采样。在 FPGA 中,所有 always 块、process 语句、assign 语句在硬件实现上都是并行工作的,它们之间通过信号(wire/reg/signal)进行通信。例如,在图像采集系统中,像素时钟 PCLK 驱动的行有效信号(HREF)与帧同步信号(VSYNC)同时进入 FPGA,它们分别被不同的 always 块采样,但彼此之间没有先后顺序,而是由硬件电路天然并行处理。
理解并行性是掌握 HDL 的第一步。在图像处理流水线中,一个典型的并行结构是:像素输入 → 灰度转换 → 中值滤波 → 边缘检测 → 输出。每一级都由独立的 always 块或 assign 语句实现,它们在同一时钟沿触发下同时工作,数据像流水线上的工件一样逐级传递。这种并行架构使得 FPGA 能够以极高的吞吐率处理实时视频流,而这是串行处理器(如 MCU/DSP)难以企及的。
| 对比维度 | Verilog | VHDL | FPGA 工程应用建议 |
|---|---|---|---|
| 语法风格 | 类 C,简洁灵活 | 类 Ada,严谨冗长 | 国内主流,推荐 Verilog |
| 数据类型 | reg/wire/integer | signal/variable/std_logic | VHDL 强类型适合大型团队 |
| 并行描述 | always 块、assign | process、concurrent | 两者均支持,注意敏感列表 |
| 状态机实现 | case/if-else | case/when | 三段式写法通用 |
| 参数化 | parameter/localparam | generic/constant | 用于图像分辨率、位宽配置 |
3.2 组合逻辑设计:数据通路的构建基石
组合逻辑的输出仅取决于当前输入,没有记忆功能。在图像处理中,色彩空间转换(RGB→YUV)、像素阈值比较、乘法累加运算等都属于组合逻辑。Verilog 中常用 assign 连续赋值或 always@(*) 过程赋值描述组合逻辑;VHDL 则使用 when-else 或 process 中的信号赋值。
设计组合逻辑时,必须避免产生锁存器(Latch)。当 always 块的条件分支不完整时,综合器会推断出锁存器,导致时序不可控。例如,在图像二值化模块中,若只写了 if (pixel > threshold) out = 1; 而没有 else 分支,综合工具会生成一个锁存器来保持 out 的旧值,这在高速图像处理中会引入毛刺和亚稳态风险。正确的做法是:要么补全 else 分支,要么在 always 块开头给输出赋默认值。
组合逻辑设计检查清单:
- 所有输入信号是否列入了敏感列表(Verilog)或 process 的敏感信号(VHDL)?
- 每个分支路径是否都有明确的输出赋值?
- 是否存在反馈回路(输出又作为输入)?
- 位宽是否匹配?避免截断导致数据错误。
以 3×3 中值滤波为例,该模块需要读取 9 个像素值并排序取中间值。若用组合逻辑实现,需要 9 个输入比较器级联,形成深度为 4 的比较树。此时必须考虑组合逻辑的传播延迟——在 150MHz 像素时钟下,单个时钟周期约 6.7ns,而 4 级比较器延迟可能超过 5ns,加上布线延迟,时序裕量会非常紧张。解决方案是插入流水线寄存器,将比较树拆分为 2-3 级,每级之间用寄存器打拍,虽然增加了 2-3 个时钟周期的延迟,但换取了更高的时钟频率和系统稳定性。
3.3 时序逻辑设计:系统节拍与数据同步
时序逻辑的输出不仅取决于当前输入,还取决于历史状态,由时钟沿触发。在 FPGA 图像系统中,时序逻辑承担着像素缓存、行/帧同步、计数器、FIFO 读写控制等核心任务。Verilog 中标准写法是 always@(posedge clk),VHDL 中为 process(clk) 配合 rising_edge(clk)。
时序逻辑设计中最关键的原则是非阻塞赋值(<=)。在同一个 always 块中,所有非阻塞赋值在时钟沿到来时同时采样旧值,然后并行更新新值。这一特性保证了多个寄存器之间的数据交换不会产生竞争。例如,在行缓存写入控制中,写地址计数器与写使能信号必须同步更新:
// Verilog 行缓存写控制
always @(posedge pclk or negedge rst_n) begin
if (!rst_n) begin
wr_addr <= 0;
wr_en <= 0;
end else if (href) begin // 行有效期间
wr_en <= 1;
wr_addr <= wr_addr + 1;
end else begin
wr_en <= 0;
wr_addr <= 0; // 行结束清零
end
end
上述代码中,wr_addr 和 wr_en 在同一时钟沿更新,不会出现“先更新地址再更新使能”的时序错乱。若误用阻塞赋值(=),则 wr_addr 会立即更新,导致 wr_en 采样到新地址,产生一个时钟周期的偏差,在图像数据写入时会造成像素错位。
时序设计风险提示:跨时钟域(CDC)信号必须使用两级同步器或异步 FIFO 处理。例如,摄像头输出的 PCLK 与 FPGA 系统时钟 SCLK 不同频,直接采样 VSYNC 信号会导致亚稳态。正确做法是:先用 PCLK 域打两拍,再转换到 SCLK 域。
3.4 状态机设计方法:控制逻辑的骨架
状态机(FSM)是 FPGA 控制逻辑的核心,在图像采集系统中用于 I2C 寄存器配置、帧同步状态切换、DDR 读写调度等场景。推荐使用三段式状态机:第一段描述状态转移(时序逻辑),第二段描述次态判断(组合逻辑),第三段描述输出(时序逻辑或组合逻辑)。这种写法将状态寄存器、次态逻辑和输出逻辑分离,代码清晰且易于维护。
| 状态机风格 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 一段式 | 代码紧凑 | 输出易产生毛刺,难维护 | 简单控制,不推荐 |
| 两段式 | 状态转移与输出分离 | 组合输出仍有风险 | 中等复杂度 |
| 三段式 | 时序输出,无毛刺 | 代码量稍大 | 图像处理控制,推荐 |
以 I2C 配置摄像头寄存器为例,状态机需要经历:IDLE → START → SEND_ADDR → SEND_REG → SEND_DATA → STOP → WAIT_ACK 等状态。每个状态对应一个时钟周期或若干时钟周期,状态转移条件取决于 I2C 总线的 ACK 信号和计数器。若采用一段式写法,输出信号(如 SCL、SDA)直接由组合逻辑产生,在状态切换瞬间可能出现毛刺,导致摄像头误判。而三段式写法将 SCL/SDA 输出寄存器化,在时钟沿稳定输出,彻底消除了毛刺风险。
状态机设计经验:务必包含默认状态(default),并设置异常恢复机制。例如,当 I2C 从设备无响应时,状态机应超时跳回 IDLE,而不是卡死在 WAIT_ACK 状态。在图像采集系统中,若 VSYNC 信号异常丢失,状态机应能自动复位重新等待帧起始。
3.5 实战案例:图像采集系统中的行场同步状态机设计
本案例基于 OV5640 摄像头模组,通过 MIPI 接口输出 1080p@30fps 图像。FPGA 需要解析 MIPI 协议,提取行同步(HSYNC)和帧同步(VSYNC)信号,并生成正确的写入控制时序。
背景与约束:MIPI 时钟频率为 400MHz(DDR),像素时钟 PCLK 为 74.25MHz。FPGA 内部逻辑时钟为 150MHz,需要将 MIPI 域的数据安全地转移到逻辑时钟域。初始现象是图像出现随机撕裂和错位,分析发现是状态机在帧同步信号跨时钟域时发生亚稳态。
关键参数:帧有效信号(FVALID)持续 1125 行,每行有效像素 1920 个,行消隐 280 个 PCLK。状态机需要精确计数行数和像素数,以生成 DDR 写地址。
分析判断过程:通过 SignalTap 逻辑分析仪抓取 VSYNC 与 FVALID 波形,发现 FVALID 在跨时钟域后出现 1-2 个周期的抖动,导致行计数器误触发。进一步检查代码,发现直接使用两级同步器处理 VSYNC,但未同步 FVALID,导致两个信号在不同时钟域下时序不一致。
实际操作步骤:
- 将 MIPI 解包后的 FVALID 和 HSYNC 信号先同步到 150MHz 逻辑时钟域(打两拍)。
- 设计三段式状态机:
IDLE → LINE_VALID → LINE_BLANK → FRAME_END,其中 LINE_VALID 状态内使用像素计数器,计数到 1920 后跳转到 LINE_BLANK。 - 在 LINE_BLANK 状态中,行计数器加 1,当行数达到 1125 时跳转到 FRAME_END,并产生帧完成中断。
- 输出信号(如 wr_en、wr_addr)全部寄存器化,确保在 150MHz 时钟沿稳定输出。
结果对比:修改后,图像撕裂现象完全消失,SignalTap 抓取波形显示 FVALID 与行计数器严格对齐,DDR 写入带宽利用率从 72% 提升至 95%。
风险与代价:增加两级同步器引入了 2 个时钟周期的延迟,但相对于 33ms 的帧周期可忽略不计。状态机代码量增加约 30 行,但可维护性显著提升。
复盘经验:跨时钟域信号必须统一同步后再进入状态机;状态机的输出寄存器化是消除毛刺的关键;计数器位宽要留足余量,防止溢出导致状态跳转异常。
3.6 常见误区与总结复盘
- 误区一:在时序逻辑中使用阻塞赋值。这会导致仿真与综合结果不一致,且产生不可预测的硬件行为。
- 误区二:组合逻辑中缺少默认赋值,意外生成锁存器。解决方法是检查所有 if/case 分支是否完整。
- 误区三:状态机没有默认状态,当输入异常时进入未知状态。必须添加 default 分支并回到 IDLE。
- 误区四:忽略复位信号。FPGA 上电后寄存器初始值不确定,必须使用异步复位或同步复位将系统置于已知状态。
- 误区五:位宽不匹配。例如将 10 位像素数据赋给 8 位寄存器,导致图像灰度丢失。
本章核心要点回顾:
- HDL 是并行描述语言,不是顺序执行程序。
- 组合逻辑关注数据通路,避免锁存器;时序逻辑关注时钟节拍,使用非阻塞赋值。
- 三段式状态机是图像控制逻辑的最佳实践,输出寄存器化可消除毛刺。
- 跨时钟域处理是图像采集系统的关键,两级同步器是最低要求。
- 仿真验证必须覆盖边界条件,如帧起始/结束、行消隐、异常中断等。
掌握上述 HDL 设计方法后,学员应能够独立完成图像采集系统中像素同步、行缓存控制、帧状态切换等核心模块的设计与调试。下一阶段将进入 FPGA 开发工具链与仿真验证方法的学习,届时将把这些设计规范应用到实际的工程流程中。