2、Verilog基础回顾:模块化设计、时序逻辑、组合逻辑、状态机、仿真与综合基础

在高速接口协议(如PCIe、JESD204B、MIPI、LVDS)的FPGA实现中,Verilog HDL是描述硬件行为的核心语言。本章并非语法手册,而是聚焦于从“能写代码”到“会做设计”的关键跨越。我们将围绕模块化设计、组合逻辑、时序逻辑、状态机以及仿真与综合这五大支柱,建立一套适用于高速接口工程的硬件思维框架。理解这些基础概念在FPGA内部的实际映射方式,是后续进行高速收发器配置、时钟数据恢复(CDR)和链路训练的前提。

FPGA设计基础架构与数据流映射 模块化设计 (Top-Down) 接口隔离 / 参数化 / 复用 组合逻辑 assign / always@(*) 无记忆,即时输出 时序逻辑 always@(posedge clk) 寄存器 / 计数器 / 移位 状态机 (FSM) 三段式 / 状态编码 链路训练 / 协议控制 仿真与综合 Testbench / 时序约束 RTL-门级-时序收敛 数据流:并行数据 → 串行化 → 高速差分输出 数据通路 控制通路

上图展示了FPGA设计中的核心逻辑关系:模块化设计作为顶层架构,向下分解为组合逻辑与时序逻辑两大基本电路类型,而状态机则作为控制核心,协调数据在组合与时序逻辑间的流动。仿真与综合贯穿整个设计流程,用于验证功能正确性并将RTL代码映射为实际的硬件电路。下面我们逐一深入剖析。

2.1 模块化设计:从系统级到RTL的层次化分解

核心原理

模块化设计是FPGA工程管理的基石。在高速接口项目中,一个完整的PCIe Gen3 x4链路往往包含物理层(PHY)、数据链路层(DLL)和事务层(TL),若将所有逻辑堆叠在单一模块中,代码可读性、仿真效率和时序收敛难度都将失控。模块化设计通过自顶向下(Top-Down)的分解策略,将复杂功能划分为若干功能独立、接口清晰的子模块。每个子模块内部可以独立优化,模块间仅通过明确的端口(Port)进行通信。

判断方法或操作流程

进行模块划分时,应遵循以下原则:

  • 高内聚低耦合:将相关性强的逻辑放在同一模块,模块间交互尽量少。
  • 接口最小化:端口数量不宜过多,避免复杂的握手信号交织。
  • 参数化设计:使用parameter定义数据位宽、FIFO深度、计数阈值等,便于通过不同的参数实例化出不同配置的模块,适应不同速率等级的接口。
// 参数化FIFO接口示例
module sync_fifo #(
    parameter DATA_WIDTH = 32,
    parameter DEPTH      = 16
)(
    input  wire                  clk,
    input  wire                  rst_n,
    input  wire                  wr_en,
    input  wire [DATA_WIDTH-1:0] din,
    input  wire                  rd_en,
    output wire [DATA_WIDTH-1:0] dout,
    output wire                  full,
    output wire                  empty
);
    // 内部实现省略
endmodule

案例拆解或实战场景

以JESD204B接口设计为例,该协议用于ADC/DAC与FPGA之间的高速数据传输。我们将设计拆分为应用层、传输层和物理层三个模块。物理层负责处理高速串行收发器(GTX/GTY)的硬核配置,传输层负责8B/10B编码、帧对齐和通道对齐,应用层则处理用户数据格式。通过这种划分,当需要从JESD204B Subclass 0切换到Subclass 1时,只需修改传输层的确定性延迟逻辑,物理层和应用层代码无需改动。

常见误区或总结复盘

误区:过度模块化导致顶层连线繁琐,且小模块间的信号延迟难以控制。在高速接口设计中,跨模块的走线延迟可能成为时序瓶颈。建议将高速串行收发器(SerDes)相关的控制逻辑尽量靠近硬核放置,避免频繁跨越模块层级。

2.2 组合逻辑与时序逻辑:硬件描述的两大支柱

核心原理

组合逻辑的输出仅取决于当前输入,不包含任何存储单元,对应FPGA中的查找表(LUT)和进位链。而时序逻辑的输出不仅取决于当前输入,还取决于过去的输入状态,由触发器(Flip-Flop)实现。在Verilog中,组合逻辑常用assign连续赋值或always@(*)过程赋值描述;时序逻辑则统一在always@(posedge clk)或always@(negedge clk)块中描述。

判断方法或操作流程

在编写RTL代码时,必须明确每个信号是组合信号还是寄存器信号。判断标准如下:

特征 组合逻辑 时序逻辑
敏感列表 所有输入信号(或使用*) 时钟边沿(posedge/negedge)
赋值方式 阻塞赋值 = 非阻塞赋值 <=
硬件映射 LUT + 多路选择器 D触发器 + 时钟网络
时序特性 存在竞争冒险风险 受建立/保持时间约束

案例拆解或实战场景

在LVDS接收端,我们需要对输入的串行数据进行位对齐。该逻辑包含两部分:一是组合逻辑实现的“位滑动”选择器,根据对齐状态选择采样的比特位置;二是时序逻辑实现的“对齐状态寄存器”,记录当前对齐进度。若将位滑动选择器误写成时序逻辑,会导致对齐操作延迟一个时钟周期,在高速率下可能造成数据采样错误。

// 正确示例:组合逻辑 + 时序逻辑分离
reg [3:0] bit_slip_cnt; // 时序逻辑
wire [7:0] aligned_data;
assign aligned_data = rx_data[bit_slip_cnt +: 8]; // 组合逻辑

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

常见误区或总结复盘

经验:在always块中,组合逻辑必须使用阻塞赋值,时序逻辑必须使用非阻塞赋值。混用会导致仿真与综合结果不一致,产生难以排查的竞争问题。建议在代码规范中强制区分,并通过Lint工具检查。

2.3 状态机设计:协议控制的核心引擎

核心原理

有限状态机(FSM)是描述协议交互、链路训练、数据调度等控制逻辑的标准模型。一个完整的FSM包含状态寄存器、次态组合逻辑和输出组合逻辑三部分。在高速接口中,状态机的稳定性直接决定了协议交互的可靠性。推荐使用三段式状态机:第一段描述状态转移(时序逻辑),第二段描述次态判断(组合逻辑),第三段描述输出逻辑(可时序可组合)。

判断方法或操作流程

设计状态机的步骤:

  1. 状态定义:列出所有必要的状态,避免冗余状态。
  2. 状态编码:对于高速接口,推荐使用独热码(One-Hot),虽然占用触发器较多,但组合逻辑简单,时序性能更好;对于状态密集的控制逻辑,可使用格雷码减少翻转功耗。
  3. 转移条件:明确每个状态在何种输入条件下跳转到下一状态,注意处理超时和异常分支。
// 三段式状态机模板
localparam IDLE = 3'b001, SEND = 3'b010, WAIT = 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
    case (state)
        IDLE: next_state = start ? SEND : IDLE;
        SEND: next_state = done ? WAIT : SEND;
        WAIT: next_state = timeout ? IDLE : WAIT;
        default: next_state = IDLE;
    endcase
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) tx_en <= 1'b0;
    else begin
        case (state)
            SEND: tx_en <= 1'b1;
            default: tx_en <= 1'b0;
        endcase
    end
end

案例拆解或实战场景

以PCIe链路训练为例,LTSSM(链路训练和状态状态机)包含Detect、Polling、Configuration、L0、Recovery等多个状态。在L0状态,需要持续监控信号丢失(Signal Loss)和错误计数器。若在Recovery状态中,状态机未能正确判断重放超时,可能导致链路反复训练失败。通过三段式状态机,我们可以将超时计数器的使能信号独立输出,便于在仿真中单独验证。

常见误区或总结复盘

风险:状态机输出信号在组合逻辑中产生毛刺,直接驱动外部接口可能导致协议错误。建议对关键输出信号(如发送使能、复位信号)在状态机第三段使用时序逻辑打一拍,消除毛刺。

2.4 仿真与综合基础:从RTL到硬件的验证桥梁

核心原理

仿真用于验证RTL代码的功能正确性,通过Testbench产生激励并检查响应。综合则是将RTL代码映射为FPGA底层资源(LUT、FF、BRAM、DSP)的过程。在高速接口设计中,仿真不仅要验证功能,还要进行时序仿真,确保在门级延迟下满足建立/保持时间要求。

判断方法或操作流程

仿真流程通常包括:

  • 功能仿真:使用initial块和task生成激励,检查关键信号波形。
  • 综合约束:在综合前必须添加时钟约束(create_clock)、输入输出延迟约束(set_input_delay/set_output_delay),否则综合工具无法优化时序。
  • 门级仿真:综合后使用网表文件和SDF(标准延迟格式)文件进行仿真,验证时序收敛。
// 简单的Testbench时钟生成
reg clk;
initial clk = 0;
always #2.5 clk = ~clk; // 200MHz时钟

initial begin
    rst_n = 0;
    #100;
    rst_n = 1;
    #50;
    start = 1;
    #20;
    start = 0;
    // 等待完成
    wait (done);
    $display("Test Passed");
    $finish;
end

案例拆解或实战场景

在调试MIPI CSI-2接收链路时,我们通过仿真发现LP(低功耗)信号与HS(高速)信号切换时存在毛刺。通过添加set_input_delay约束模拟实际PCB走线延迟,并在门级仿真中观察到了该毛刺。随后在RTL中增加了同步器,消除了亚稳态风险。这一案例说明,仅通过功能仿真无法发现时序问题,必须结合综合后的时序报告进行迭代。

常见误区或总结复盘

总结:仿真通过不代表综合后功能正确。务必检查综合报告中的WNS(最差负时序裕量)和TNS(总负时序裕量)。对于高速接口,建议WNS大于0.1ns以上,为温度电压波动留出余量。同时,在Testbench中应加入错误注入机制,模拟链路抖动和位错误,验证状态机的恢复能力。

本章内容为后续高速接口协议的学习奠定了坚实的语言和设计基础。掌握模块化划分、时序与组合逻辑的区分、状态机的稳健设计以及仿真综合的闭环流程,是驾驭复杂FPGA工程的必备技能。