第1章:FPGA基础语法回顾

各位同学,欢迎来到《FPGA雷达信号处理加速实战教程》。我是你们的老朋友,一个在FPGA和雷达信号处理领域摸爬滚打了十几年的工程师。

这一章,咱们不急着上硬菜。先花点时间,把FPGA设计最核心的语法和概念捋一遍。你可能会觉得基础,但说实话,我见过太多项目翻车,都是因为基础不牢。咱们稳扎稳打,后面才能飞得起来。

1.1 Verilog与VHDL:我该选哪个?

这是个老生常谈的问题。我个人习惯用Verilog,因为它语法更灵活,写起来快,尤其在处理雷达信号这种大规模数据流时,代码量能少不少。但VHDL也有它的优势,比如类型检查更严格,适合大型团队协作。

不过,你想想看,现在主流的FPGA厂商(Xilinx、Intel)的IP核和参考设计,大部分都提供Verilog版本。所以,我建议初学者从Verilog入手。咱们课程里也统一用Verilog。

说白了,语言只是工具。核心是你能不能把硬件逻辑想清楚。

核心要点: 无论你用哪种语言,最终综合出来的都是硬件电路。语法只是描述方式,不是电路本身。

1.2 组合逻辑与时序逻辑:一个都不能少

这是FPGA设计的基石。我刚开始学的时候,也经常搞混。咱们用最直白的话说清楚。

组合逻辑

输出只取决于当前的输入。没有记忆功能。就像一根导线,输入变了,输出立刻跟着变。

举个例子,一个简单的与门:

// Verilog 组合逻辑示例
assign y = a & b;

这段代码综合出来就是一个与门。a和b变了,y马上变。没有时钟,没有寄存器。

时序逻辑

输出不仅取决于当前输入,还取决于之前的状态。它有记忆功能。靠时钟沿来触发更新。

最常见的时序逻辑就是D触发器:

// Verilog 时序逻辑示例
always @(posedge clk) begin
    q <= d;
end

这里,q只在时钟上升沿才更新为d的值。其他时间,q保持不变。

我的经验: 在雷达信号处理中,大部分算法都是流水线结构。组合逻辑负责计算,时序逻辑负责打拍子、对齐数据。两者配合好了,系统才能稳定跑在高频率。

1.3 状态机设计:控制逻辑的灵魂

状态机,说白了就是控制逻辑的大脑。它决定了你的电路在什么时候该干什么事。

我遇到过不少新手,写状态机喜欢用一大堆if-else嵌套,最后代码自己都看不懂了。我的建议是:用三段式状态机。清晰、稳定、好维护。

三段式状态机的写法:

  1. 第一段: 时序逻辑,描述状态跳转。
  2. 第二段: 组合逻辑,描述下一个状态的条件。
  3. 第三段: 时序逻辑,描述每个状态下的输出。

看个例子,一个简单的数据包检测状态机:

// 三段式状态机示例
// 第一段:状态跳转
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        current_state <= IDLE;
    else
        current_state <= next_state;
end

// 第二段:次态逻辑
always @(*) begin
    case (current_state)
        IDLE: begin
            if (data_in == 8'hAA)
                next_state = HEADER;
            else
                next_state = IDLE;
        end
        HEADER: begin
            if (data_in == 8'h55)
                next_state = DATA;
            else
                next_state = IDLE;
        end
        DATA: begin
            if (byte_count == 10)
                next_state = IDLE;
            else
                next_state = DATA;
        end
        default: next_state = IDLE;
    endcase
end

// 第三段:输出逻辑
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        data_valid <= 1'b0;
        byte_count <= 4'd0;
    end else begin
        case (current_state)
            IDLE: begin
                data_valid <= 1'b0;
                byte_count <= 4'd0;
            end
            HEADER: begin
                data_valid <= 1'b0;
                byte_count <= 4'd0;
            end
            DATA: begin
                data_valid <= 1'b1;
                byte_count <= byte_count + 1;
            end
        endcase
    end
end
避坑指南: 我曾经在项目里用了一段式状态机,结果输出有毛刺,导致后续模块误触发。后来全部改成三段式,问题迎刃而解。记住:输出用寄存器打一拍,能解决90%的时序问题。

1.4 同步设计与异步设计:别让时钟域成为你的噩梦

这是FPGA设计里最容易出问题的地方,没有之一。尤其是在雷达信号处理中,多个时钟域并存是常态。

同步设计

所有寄存器都在同一个时钟沿下工作。简单、可靠、时序分析容易。这是我们的首选。

异步设计

信号跨时钟域传输。比如,一个信号从100MHz的时钟域传到200MHz的时钟域。如果不做处理,大概率会出现亚稳态,导致数据错误。

处理跨时钟域,最常用的方法就是打两拍(双级同步器):

// 异步信号同步化
always @(posedge clk_dst or negedge rst_n) begin
    if (!rst_n) begin
        sync_reg1 <= 1'b0;
        sync_reg2 <= 1'b0;
    end else begin
        sync_reg1 <= async_signal;
        sync_reg2 <= sync_reg1;
    end
end

assign sync_signal = sync_reg2;

这里,async_signal是来自另一个时钟域的信号。经过两级寄存器同步后,sync_signal就可以安全地在clk_dst时钟域使用了。

我的建议: 对于单比特信号,打两拍足够了。但对于多比特数据总线,千万别用打拍的方式。要用异步FIFO。我见过有人用打拍的方式同步16位数据总线,结果数据老是错位,查了三天才找到原因。

1.5 本章小结

好了,这一章咱们把FPGA设计最核心的语法和概念过了一遍。组合逻辑、时序逻辑、状态机、同步异步设计,这些都是基本功。你想想看,后面我们要做的雷达信号处理,无论是FFT、数字下变频还是脉冲压缩,本质上都是这些基础元素的组合。

下一章,咱们开始进入正题,聊聊雷达信号处理的基本流程。到时候,你会发现,这些基础语法会反复用到。

嗯,今天就到这里。有什么问题,咱们课后交流。

课后思考: 如果你现在要设计一个简单的雷达目标检测模块,你会用状态机来控制数据流的处理顺序吗?试着画一下状态转移图。