4、FPGA逻辑设计入门:Verilog/VHDL基础、Block Design流程、IP核集成(如AXI DMA)

好,咱们进入第四章。这一章是FPGA设计的核心入门课。说实话,很多新手一上来就被Verilog语法吓住了,或者被Vivado的Block Design搞得晕头转向。别急,我带你一步步捋清楚。

4.1 Verilog/VHDL基础:硬件描述语言没那么玄乎

先说说语言选择。我个人习惯用Verilog,因为它在工业界更流行,尤其是AMD/Xilinx的生态里。VHDL当然也很好,语法更严谨,但写起来啰嗦一点。你选哪个都行,关键是理解硬件描述语言的本质——它不是写软件,是在画电路。

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

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

你看,这跟C语言里的if语句完全不是一回事。assign语句对应的是组合逻辑,综合工具会把它映射成真实的LUT(查找表)。我在项目中遇到过有人把Verilog当C写,满屏的always块和for循环,结果综合出来资源爆炸——嗯,这就是没理解硬件思维。

我的小技巧:写Verilog时,脑子里要想着「这行代码会生成什么电路」。是寄存器?是组合逻辑?还是状态机?想清楚了再下笔。

4.2 组合逻辑 vs 时序逻辑

这是FPGA设计里最基础也最容易搞混的概念。说白了:

  • 组合逻辑:输出只取决于当前输入,没有记忆功能。比如加法器、多路选择器。
  • 时序逻辑:输出还取决于之前的状态,需要时钟驱动。比如计数器、移位寄存器。

写代码时怎么区分?看敏感列表:

// 组合逻辑:always @(*) 或 assign
always @(*) begin
    if (sel)
        out = a;
    else
        out = b;
end

// 时序逻辑:always @(posedge clk)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        cnt <= 8'd0;
    else if (en)
        cnt <= cnt + 1'b1;
end

注意看,时序逻辑里我用的是非阻塞赋值 <=,组合逻辑用的是阻塞赋值 =。这个区别很重要。我曾经因为混用这两种赋值方式,导致仿真和实际行为不一致,查了整整两天——从那以后我再也不敢乱用了。

4.3 Block Design流程:图形化搭积木

Vivado的Block Design(简称BD)是AMD平台的一大特色。说白了,就是让你像搭积木一样,把各种IP核拖拽连接,自动生成顶层代码。我个人非常喜欢这个流程,尤其是做复杂系统时,省去了大量手写连线的时间。

基本步骤:

  1. 创建Vivado工程,选择目标芯片(比如Zynq-7000或Versal)。
  2. 点击「Create Block Design」,给设计取个名字。
  3. 从IP Catalog里拖出需要的IP核,比如Zynq Processing System、AXI DMA、BRAM Controller等。
  4. 用鼠标连线:数据走AXI总线,控制走GPIO或中断。
  5. 右键点击「Validate Design」,检查有没有漏连或冲突。
  6. 生成顶层HDL文件,然后综合、实现、生成比特流。
避坑指南:我曾经在BD里漏连了时钟复位,结果综合出来一堆奇怪的警告。后来我养成了习惯:每次连线完,先点「Validate Design」,再点「Generate Output Products」。这两步能帮你省掉80%的调试时间。

4.4 IP核集成:以AXI DMA为例

IP核集成是FPGA设计的重头戏。咱们拿AXI DMA来开刀,这个IP核在高速数据传输里用得特别多。

AXI DMA的作用很简单:它能把数据从内存搬到FPGA逻辑,或者反过来。你想想看,如果没有DMA,CPU得一条一条指令去搬数据,效率极低。有了DMA,CPU只需要告诉它「从地址A搬N个字节到地址B」,然后就可以去干别的事了。

配置AXI DMA时,有几个关键参数:

参数 说明 我的建议
Width of Buffer Length Register 一次传输的最大字节数 一般设成14(16KB),够用
Enable Scatter Gather 是否支持分散聚合模式 新手先关掉,用Simple模式
Stream Data Width 数据位宽,通常32或64位 跟你的AXI总线宽度匹配
Allow Unaligned Transfers 是否允许非对齐传输 一般勾上,省得后面出问题

集成到BD里后,你还需要写一个简单的控制模块,用来启动DMA传输。代码大概长这样:

// 启动一次DMA读传输
always @(posedge clk) begin
    if (start_transfer) begin
        // 设置源地址(内存)
        axi_dma_0_s2mm_da   <= src_addr;
        // 设置传输长度
        axi_dma_0_s2mm_len  <= transfer_len;
        // 启动传输
        axi_dma_0_s2mm_go   <= 1'b1;
    end
end

嗯,这里要注意:DMA传输完成后会拉高一个中断信号。你需要在PS端(处理器系统)响应这个中断,或者用轮询方式检查状态寄存器。我个人习惯用中断,因为轮询会浪费CPU时间。

重要提醒:AXI DMA的缓存一致性问题是个大坑。如果PS端和PL端(可编程逻辑)共享同一块内存,记得在PS端配置好Cache策略,或者用DMA的Coherent模式。我曾经因为没注意这个,数据读出来全是乱的——嗯,那感觉就像代码里有个幽灵。

4.5 实战建议:从简单开始

如果你刚开始学FPGA逻辑设计,我的建议是:

  • 先写几个简单的Verilog模块,比如计数器、移位寄存器、状态机。别一上来就搞AXI总线。
  • 然后用Block Design搭一个最小系统:Zynq PS + AXI GPIO + LED。跑通再说。
  • 最后再尝试集成AXI DMA,配合一个简单的数据生成器(比如LFSR)做回环测试。

记住,FPGA设计是个实践活。看十遍教程不如自己跑一遍仿真。我当年学的时候,光是搞懂always块的敏感列表就花了三天——但一旦理解了,后面就顺了。

下一章咱们会深入讲AXI总线的时序和协议,那是连接PS和PL的命脉。准备好了吗?