3. 硬件平台设计:Block Design基础、AXI总线协议简介、自定义IP核封装流程

好,咱们进入第三章。这一章是Zynq开发的基石——硬件平台设计。说白了,就是怎么在Vivado里把PS(处理器系统)和PL(可编程逻辑)给连起来,让ARM核和FPGA逻辑能愉快地聊天。

我个人习惯,拿到一个Zynq项目,第一件事不是写代码,而是先把Block Design搭好。这就像盖房子先画图纸,图纸画错了,后面砌墙再漂亮也白搭。

3.1 Block Design 基础

Block Design,简称BD,是Vivado里图形化的硬件设计工具。你不需要手写一大堆Verilog连线,而是像搭积木一样,把IP核拖出来,用鼠标点一点就连上了。

我刚开始用的时候也觉得这玩意儿有点花哨,不如直接写代码来得痛快。但后来发现,对于Zynq这种异构架构,BD能帮你自动处理很多复杂的互联逻辑,尤其是时钟、复位和AXI总线。

核心要点: Block Design 的本质是 IP Integrator。它生成的是硬件描述文件(.bd),最终会被综合成网表。你看到的图形化连线,背后对应的是实际的物理信号。

一个典型的BD设计流程是这样的:

  1. 创建Block Design:在Vivado里新建一个BD文件。
  2. 添加IP核:把Zynq Processing System IP核拖进来。这是必须的,它是系统的大脑。
  3. 配置PS:双击Zynq IP核,配置DDR、UART、I2C、时钟频率等。这一步很关键,配置错了板子可能起不来。
  4. 添加外设IP:比如AXI GPIO、AXI UART、你自己的自定义IP核。
  5. 自动连线:点击“Run Block Automation”和“Run Connection Automation”,Vivado会自动帮你连好时钟、复位和AXI接口。
  6. 验证设计:Validate Design,检查有没有未连接的端口或地址冲突。
  7. 生成顶层HDL:Create HDL Wrapper,生成一个顶层Verilog或VHDL文件,把BD例化进去。

嗯,这里要注意:自动连线虽然方便,但它不一定完全符合你的需求。比如,它可能会默认把所有的AXI从机都挂在同一个AXI Interconnect上。如果你的系统对带宽有要求,可能需要手动调整。

3.2 AXI总线协议简介

AXI(Advanced eXtensible Interface)是ARM公司推出的高性能总线协议。在Zynq里,PS和PL之间的通信全靠它。你可以把它理解成一条高速公路,数据在上面跑。

AXI协议有好几个版本,Zynq里最常用的是 AXI4 和 AXI4-Lite。还有一个 AXI4-Stream,主要用于高速数据流。

协议类型 特点 典型应用场景
AXI4(Full AXI) 支持突发传输,高性能,地址/数据通道分离 DMA、DDR控制器、高速数据搬运
AXI4-Lite 轻量级,不支持突发,一次只传一个数据 配置寄存器、状态寄存器、控制接口
AXI4-Stream 无地址,纯数据流,无限突发 视频流、音频流、高速ADC/DAC数据

为什么会有这么多变种?你想想看,如果我只是想读一个寄存器的值,用AXI4 Full就太浪费了,握手信号一大堆,延迟还高。用AXI4-Lite就刚刚好,简单直接。

AXI总线的核心是 握手信号。每个通道都有VALID和READY信号。发送方拉高VALID,表示数据有效;接收方拉高READY,表示可以接收。当VALID和READY同时为高时,传输发生。

避坑指南: 我曾经在一个项目里,自定义IP核的AXI接口死活不工作。查了两天,最后发现是READY信号在复位期间没有拉低,导致PS端认为从机一直处于可接收状态,数据发出去就丢了。记住:AXI协议要求复位期间READY必须为低。

AXI4的地址和数据通道是分离的。这意味着,你可以先发一个写地址,然后连续发多个写数据,实现高效的突发传输。这在搬运大块数据时特别有用。

3.3 自定义IP核封装流程

Vivado里有一个非常强大的工具叫 IP Packager。它可以把你的Verilog/VHDL代码封装成一个标准的IP核,然后像搭积木一样在BD里使用。

我建议,只要你的逻辑模块需要被PS访问,或者需要复用到多个项目,就把它封装成IP核。这能极大提高开发效率。

封装流程大致如下:

  1. 准备RTL代码:确保你的代码是参数化的,端口定义清晰。
  2. 创建IP核工程:Tools → Create and Package New IP。
  3. 选择封装方式:可以选择从RTL文件封装,或者从已有工程封装。
  4. 添加AXI接口:这是关键一步。Vivado提供了模板,你可以选择AXI4-Lite Slave、AXI4 Master等。
  5. 映射端口:把你的用户逻辑端口映射到AXI接口上。比如,把内部寄存器映射到AXI4-Lite的寄存器空间。
  6. 配置IP参数:添加GUI参数,比如数据位宽、FIFO深度等。这样在BD里双击IP核就能配置。
  7. 审查并打包:Review and Package,生成IP核文件(.xci或.zip)。

下面是一个简单的AXI4-Lite从机接口的Verilog模板片段,它实现了两个寄存器:一个只读的状态寄存器,一个可读写的控制寄存器。

module my_axi_lite_slave #(
    parameter C_S_AXI_DATA_WIDTH = 32,
    parameter C_S_AXI_ADDR_WIDTH = 4
)(
    input  wire                      S_AXI_ACLK,
    input  wire                      S_AXI_ARESETN,
    input  wire [C_S_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR,
    input  wire                      S_AXI_AWVALID,
    output wire                      S_AXI_AWREADY,
    input  wire [C_S_AXI_DATA_WIDTH-1:0] S_AXI_WDATA,
    input  wire                      S_AXI_WVALID,
    output wire                      S_AXI_WREADY,
    output wire [1:0]                S_AXI_BRESP,
    output wire                      S_AXI_BVALID,
    input  wire                      S_AXI_BREADY,
    input  wire [C_S_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR,
    input  wire                      S_AXI_ARVALID,
    output wire                      S_AXI_ARREADY,
    output wire [C_S_AXI_DATA_WIDTH-1:0] S_AXI_RDATA,
    output wire [1:0]                S_AXI_RRESP,
    output wire                      S_AXI_RVALID,
    input  wire                      S_AXI_RREADY
);

    // 用户寄存器
    reg [C_S_AXI_DATA_WIDTH-1:0] control_reg;
    reg [C_S_AXI_DATA_WIDTH-1:0] status_reg;

    // 写地址和写数据通道的握手
    assign S_AXI_AWREADY = 1'b1; // 简单实现,始终准备好
    assign S_AXI_WREADY  = S_AXI_AWVALID & S_AXI_WVALID; // 地址和数据同时有效才接收

    // 写响应
    assign S_AXI_BRESP  = 2'b00; // OKAY
    assign S_AXI_BVALID = S_AXI_AWVALID & S_AXI_WVALID & S_AXI_BREADY;

    // 写操作
    always @(posedge S_AXI_ACLK) begin
        if (!S_AXI_ARESETN) begin
            control_reg <= 32'h0;
        end else if (S_AXI_AWVALID & S_AXI_WVALID) begin
            if (S_AXI_AWADDR[3:2] == 2'b00) // 地址偏移0x0
                control_reg <= S_AXI_WDATA;
        end
    end

    // 读地址通道
    assign S_AXI_ARREADY = 1'b1;

    // 读数据通道
    assign S_AXI_RRESP = 2'b00;
    assign S_AXI_RVALID = S_AXI_ARVALID;

    assign S_AXI_RDATA = (S_AXI_ARADDR[3:2] == 2'b00) ? control_reg :
                         (S_AXI_ARADDR[3:2] == 2'b01) ? status_reg :
                         32'hDEADBEEF; // 未定义地址返回死牛肉

endmodule

这段代码虽然简单,但包含了AXI4-Lite从机的核心握手逻辑。实际项目中,你还需要处理地址对齐、响应信号等细节。

重要提醒: 封装IP核时,地址映射一定要规划好。每个从机IP核在PS的地址空间中都有一个基地址。如果两个IP核的地址范围重叠,系统会崩溃。Vivado的Address Editor里可以手动分配,也可以自动分配。我建议手动分配,心里有数。

为了让你更直观地理解整个流程,我画了一张图:

Zynq 硬件平台设计流程 1. 创建Block Design 新建BD文件 2. 添加Zynq PS核 配置DDR/时钟/UART 3. 添加外设IP AXI GPIO/自定义IP 4. 自动连线 Run Connection Automation 5. 验证设计 Validate Design 6. 生成顶层HDL Create HDL Wrapper 自定义IP核封装流程 (可并行进行) 准备RTL代码 添加AXI接口 映射端口/寄存器 打包生成IP核 关键:地址映射不能重叠,AXI握手信号必须符合协议规范,复位期间READY必须为低 建议手动分配地址,避免自动分配导致的地址冲突

这张图把整个流程串起来了。左边是标准的BD设计流程,右边是自定义IP核的封装流程。两者是并行的,你可以先封装好IP核,再在BD里添加。

嗯,最后再啰嗦一句。AXI协议虽然复杂,但Zynq开发中你只需要掌握它的核心握手机制和三种变体的区别就够了。不要试图去背协议文档,那是浪费时间。多用Vivado的IP Packager,多看看生成的示例代码,慢慢就熟了。