ZYNQ AXI IP 核 | 定制 / 应用 / 问题解析

注:本文为 “ZYNQ AXI IP 核 | 定制 / 应用” 相关文章合辑。

图片清晰度受引文原图所限。
略作重排,未整理去重。
如有内容异常,请看原文。


ZYNQ —— 定制 AXI IP 核

亦梦云 于 2018-05-25 12:47:08 发布

ZYNQ 的最大优点在于硬核 A9 处理器与 FPGA 的结合。处理器能够扩展出任何使用者所需的外设(数字逻辑外设),FPGA 与处理器通过 AXI 高速总线连接,提供了从处理器到 FPGA 的高速带宽(ZYNQ7000 最高可达 8 Gbps)。AXI 总线协议较为复杂,但 Xilinx 提供了 AXI 打包工具,开发人员只需在指定位置添加自己的逻辑功能代码,极大地简化了开发过程。ZedBoard 的官方示例已介绍如何将 Xilinx 提供的 AXI 总线 IP(如 AXI_Timer、AXI_GPIO 等)添加到工程中。接下来,我们将自行编写一个简单的 AXI 总线设备,用于读取板上 4 个 Switch 的状态,并控制 3 个 LED 的外设。

一、建立 LED 和开关的 AXI IP 核

首先查看开发板的原理图,确定 LED 和开关的引脚:

原理图

原理图

根据原理图,引脚分配如下表所示:

元件LED_RLED_GLED_BSW1SW2SW3SW4
引脚R14Y16Y17R19T19G14J14

1.1 创建工程及 Block Diagram

在 Vivado 开发环境中新建一个名为 LED_AXI 的工程,并生成一个名为 system 的 Block Diagram 文件。

新建工程

将 ZYNQ7 Processing System 内核添加到该原理图中,创建好的 Vivado 工程及 Block Diagram 如下:

添加内核

双击 ZYNQ7 Processing System,配置 ZYNQ 的 DDR 为 MT41K256M16 RE-125。

配置 DDR

配置 MIO48、MIO49 为 uart1 的引脚。

配置 UART

1.2 创建自定义 LED 和开关 IP 核

点击菜单 Tools -> Create and Package IP…

创建 IP

点击 Next,选择 Create a new AXI4 peripheral 选项。

选择 AXI4

修改 IP 的名称和存放位置。

修改名称

该 IP 显示了 AXI 总线接口的名称,接口为 Slave,数据位宽为 32 位,IP 寄存器为 4 个。点击 Finish 完成设计。

打开 IP Catalog 界面,可以看到 LED_IP_v1.0。此时该 IP 不具备任何功能。

IP Catalog

右键选中 LED_IP_v1.0,选择 Edit in IP Packager 项。点击 OK,软件会打开另一个 Vivado 窗口对该 IP 进行编辑。

编辑 IP

双击顶层文件 LED_IP_V1_0.v,添加 LED 和开关的引脚端口定义。

添加端口

在顶层文件 LED_IP_V1_0.v 中对 LED 和开关引脚进行例化。

例化引脚

打开 LED_IP_v1_0_S00_AXI.v 文件,添加 LED 和开关的引脚端口定义。

添加端口定义

屏蔽 AXI 总线对 slv_reg0 的操作。对于寄存器而言,输入端口不能自行拉低或拉高,因此写操作无效,必须屏蔽。

屏蔽操作

在程序的最后注释(//Add user logic here)位置添加代码,实现逻辑功能。以下是代码示例:

// Add user logic here
always @( posedge S_AXI_ACLK )
begin
    if ( S_AXI_ARESETN == 1'b0 )
    begin
        LED  <= 0;
    end 
    else
    begin    
        LED <= slv_reg0[2:0]; 
    end
end    
always @( posedge S_AXI_ACLK )
begin
    slv_reg0[3:0] <= SWITCH;
end    
// User logic ends

编译 LED_IP_v1_0 项目,确保无误。

双击 IP-XACT 下的 component.xml 文件,点击 ports and interfaces 项,点击 merge changes from ports and Interface wizard。

更新端口

在窗口中显示了在程序中定义的端口。

显示端口

对其他未勾选的 file groups 点击 Merge changes from file groups wizard,更新文件和驱动。

更新文件

选择 Review and Package 项,点击 Re-Package IP 按钮完成 IP 核的设计。

打包 IP

至此,自定义的 IP 核设计完成。关闭 IP 核的 Vivado 工程,返回 LED_AXI 工程。

1.3 设计系统原理

在 Diagram 窗口中搜索 LED,会出现刚才自定义的 IP 核:LED_IP_v1.0。

搜索 IP

双击 LED_IP_v1.0 添加,点击 Run Connection Automation,选中 All automation 进行总线的自动连接。

自动连接

点击 Run Block Automation 后完成系统设计,最终原理图如下所示。

原理图

此时,LED 和 SWITCH 的引脚并未自动生成为外部 port,需要手动设置这些引脚为外部引脚。右键选中 LED,选择 make external。

设置外部引脚

在 Source 窗口中选择 system.bd,右键点击并选择 Generate output Projects 和 Create HDL Wrapper 选项进行操作,完成后的界面如下:

生成项目

编译后配置引脚约束,然后综合生成 Bitstream 文件。

导出硬件:选择菜单 File -> Export -> Export Hardware…,勾选 include bitstream。

导出硬件

导出硬件后,选择菜单 File -> Launch SDK,启动 SDK 开发环境,进行裸机驱动编写以验证 IP 的正确性。

二、SDK 软件编程

2.1 创建工程

在 SDK 环境中新建一个名为 LED_test 的工程,软件会自动创建一个 LED_test_bsp 的工程。项目使用 HelloWorld 为模板,新建好的工程如下:

新建工程

为了能够通过串口输出到终端,需要设置 BSP 工程的属性,指定终端标准输入输出为串口 1,而不是默认的串口 0。

2.2 修改代码

修改 LED_test 工程的 helloworld.c,代码如下:

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "LED_IP.h"
#include "xil_io.h"
#include <stdlib.h>

// Look up in address editor
#define LED_BASEADDR 0x43C00000
int reg_led, reg_switch;

int main()
{
    int i = 0;
    init_platform();

    printf("======= AXI IP Test ======\n\r");
    printf("Read Switch register...\n");
    printf("Light on R G B LED...\n");

    while(1)
    {
        reg_switch = LED_IP_mReadReg(LED_BASEADDR, 0); // Switch status
        printf("switch=0x%0x i =%d\n", reg_switch, i);
        LED_IP_mWriteReg(LED_BASEADDR, 4, i);
        i++;
        if(i >= 8) i = 0;
        sleep(1);
    }

    cleanup_platform();
    return 0;
}

简要说明驱动代码:LED_BASEADDR 是 LED AXI 总线挂载在处理器上的地址,该地址在添加 AXI 外设时由软件自动分配,默认为 0x43c00000,也可以在软件的 Address editor 中编辑自定义地址,但必须在 AXI 外设地址空间范围内。LED_IP_mReadReg 函数用于读取自定义 AXI 外设的寄存器数值,而 LED_IP_mWriteReg 用于写 AXI 外设寄存器值。第一个参数是 AXI 外设的基地址,第二个参数是地址偏移。在设计 Verilog 逻辑代码时,开关使用的是 slv_reg0,因此读取开关状态的偏移地址为 0,而 LED 使用的是 slv_reg1,地址偏移为 4。这两个读写函数由 SDK 自动生成,也可以手动实现这些函数,此时需要使用系统库函数读写寄存器。

2.3 下载调试

首先下载 FPGA 程序,点击菜单 Xilinx Tools -> Program FPGA:

下载 FPGA

右键选择 LED_test,点击 Run as -> 1 launch on hardware。在串口终端中显示如下:

终端显示

同时,RGB 三色灯呈现 8 种颜色循环闪烁。至此,FPGA 中的 LED 和开关的 IP 核创建完成。

三、Linux 下的 IP 核驱动及应用

3.1 配置 Linux 驱动程序开发环境

在 Eclipse 中配置 Linux 驱动程序开发环境,配置方法可参考前面的《Eclipse 开发 ZYNQ 驱动程序》教程。编写代码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h> // GPIO 操作函数
#include <asm/io.h> // IO 读取函数
#include <asm/uaccess.h>

#define DEVICE_NAME "LED"
#define LED_MAJOR 252
#define LED_MINOR 0

#define LED_BASEADDR 0x43C00000
unsigned int* LED_Address = 0;

int led_open(struct inode* inode, struct file* pfile);
int led_release(struct inode* inode, struct file* pfile);
int led_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg);

static const struct file_operations led_fops =
{
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .unlocked_ioctl = led_ioctl
};

int led_open(struct inode* inode, struct file* pfile)
{
    printk("Open\n");
    LED_Address = ioremap(LED_BASEADDR + 4, 4);
    return 0;
}

int led_release(struct inode* inode, struct file* pfile)
{
    iounmap((void*)(LED_BASEADDR + 4));
    printk("LED release\n");
    return 0;
}

int led_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
{
    printk("ioctl\n");
    *LED_Address = arg;
    return 0;
}

static struct miscdevice LED_misc =
{
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVICE_NAME,
    .fops = &led_fops,
};

static int __init led_init(void)
{
    int ret;
    ret = misc_register(&LED_misc);
    if(ret)
    {
        printk("Error: misc_register failed!\n");
        return -1;
    }
    printk("LED module register successfully!\n");
    return 0;
}

static void __exit led_exit(void)
{
    misc_deregister(&LED_misc);
    printk("Exit module\n");
}

MODULE_AUTHOR("Xiong.guo");
MODULE_LICENSE("Dual BSD/GPL");

module_init(led_init);
module_exit(led_exit);

此处驱动不做过多解释,因为这是最简单的 Linux 驱动程序。编译驱动模块,得到 ZYNQ_LED.ko 文件。

3.2 编写 LED 测试程序

在 Eclipse 中新建 C++ 工程,配置方法见其他教程。编写 LED 的测试程序,代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int fd;

    int i = 0;
    printf("ZYNQ LED driver\n");
    fd = open("/dev/LED", 0);
    if(fd < 0)
    {
        printf("can't open LED\n");
        return -1;
    }

    while(1)
    {
        for(i = 0; i < 8; i++)
        {
            ioctl(fd, i, i);
            sleep(1);
        }
    }

    close(fd);
    printf("exit led\n");

    return 0;
}

编译程序得到 LED_test 文件。

3.3 运行程序测试

开发板与 Ubuntu 系统通过 NFS 文件传输,将 ZYNQ_LED.ko 和 LED_test 两个文件放入 NFS 的根目录下。在开发板的终端挂载 NFS 的目录到 /mnt 目录下,然后加载模块并运行程序:

$ cd /mnt/
$ insmod ZYNQ_LED.ko
$ ./LED_test

运行程序后,开发板上的三色 LED 循环闪烁。查看 /dev 目录,可以看到系统文件中多了一个名为 LED 的文件,这便是刚才的驱动程序所建立的文件节点。

四、总结

本文详细介绍了如何在 ZYNQ7000 中逐步建立自定义的 IP 核,并在裸机环境中使用仿真器进行验证。在裸机运行无误后,再在 Linux 系统中编写驱动程序进行测试,以及 Linux 应用程序的测试。后续教程大致会遵循这样一个调试步骤:FPGA -> 逻辑仿真 -> 裸机调试 -> Linux 驱动 -> Linux 应用。至此,ZYNQ SOC 的开发流程已基本熟悉。


自定义 AXI IP 核实验 —— FPGA Vitis 篇

BIGMAC_1017 于 2023-04-08 17:34:17

1. 前言

Xilinx 官方提供了大量 IP 核,可在 Vivado 的 IP Catalog 中查看。用户在构建系统时,往往需要创建属于自己的用户 IP 核。创建自定义 IP 核有诸多好处,例如实现系统设计的定制化、设计复用,还可以在 IP 核中加入 license,有偿提供给他人使用。此外,自定义 IP 核能够简化系统设计并缩短开发时间。在 ZYNQ 系统设计中,最常用的连接方式是通过 AXI 总线将 PS(处理系统)与 PL(可编程逻辑)部分的 IP 核连接起来。本实验将介绍如何在 Vivado 中构建 AXI 总线类型的 IP 核,该 IP 核用于产生一个 PWM 信号,通过该信号控制开发板上的 LED,实现呼吸灯效果。

2. Vivado 工程的编写

2.1 创建自定义 IP

  1. 本实验使用的 Vivado 工程沿用《ZYNQ 串口打印输出 —— FPGA Vitis 篇》中创建的工程。具体创建方法可参考该文章。
  2. 修改 Vivado 工程,创建自定义 IP。点击菜单 Tools -> Create and Package IP…,然后点击 Next。

创建 IP

  1. 选择创建一个新的 AXI4 设备。

选择 AXI4

  1. 填写 IP 名称为 AXI_pwm,描述可随意填写,然后选择一个合适的位置存放 IP。

填写信息

  1. 下方参数可指定接口类型、寄存器数量等。此处无需修改,使用 AXI Lite Slave 接口,4 个寄存器。点击 Next -> Finish,完成 IP 的创建。

完成创建

  1. 重新打开 IP Catalog,可以看到刚才创建的 IP。

查看 IP

  1. 此时的 IP 仅具备简单的 AXI 总线读写寄存器功能,尚未实现 PWM 功能。需要修改 IP,右键选择 IP,点击 Edit in IP Packager。在弹出的对话框中,不修改默认信息,直接点击 OK。

编辑 IP

  1. Vivado 打开了一个新的工程。

新工程

  1. 在 Vivado 的 Flow Navigator 中点击 Add Source,添加 PWM 功能代码。此处的 PWM 功能代码借用的是 Alinx 公司开发板提供的模块代码,用户也可以根据需要更改其他代码。

添加代码

代码示例

  1. 修改 AXI_pwm_v1_0.v 文件,增加 pwm 输出端口。

增加端口

  1. 修改 AXI_pwm_v1_0.v 文件,在例化 AXI_pwm_v1_0_S00_AXI 时添加 pwm 端口的例化。

  2. 修改 AXI_pwm_v1_0_S00_AXI.v 文件,添加 pwm 端口。该文件是实现 AXI4 Lite Slave 的核心代码。

添加端口

  1. 修改 AXI_pwm_v1_0_S00_AXI.v 文件,例化刚添加的 axi_pwm.v 文件,并将寄存器 slv_reg0 和 slv_reg1 用于 pwm 模块的参数控制。

例化模块

  1. 在工程源文件 Sources 中双击 component.xml 文件。在 File Groups 选项中点击 Merge changes from File Groups Wizard。

更新文件

  1. 同样在 Customization Parameters 选项中点击 Merge changes from Customization Parameters Wizard。

  2. 点击 Re-Package IP 完成 IP 的修改。

打包 IP

2.2 添加自定义 IP 到工程

  1. 在 Block Design 中,点击添加 IP,搜索 “pmw”,添加 AXI_pwm_v1.0 IP 核,并点击 Run Connection Automation。

添加 IP

  1. 导出 pwm IP 核的端口,保存设计,并 Generate Output Products。

生成产品

  1. 添加 XDC 文件,将 pwm_led 输出端口分配给 ZedBoard 上的一个 LED 灯,编译生成 Bit 文件。

  2. 点击 Vivado Flow Navigator 中的 Generate Bitstream,等待 Vivado 生成 bit 文件。生成完成后,在菜单栏 File -> Export -> Export Hardware… 导出硬件信息(.xsa 文件),该文件包含 PS 端的配置信息。如有疑问,可参考之前的文章《ZYNQ 串口打印输出 —— FPGA Vitis 篇》。

3. Vitis 工程的编写

  1. 点击 Vivado 菜单 Tools -> Launch Vitis IDE,启动 Vitis。
  2. 新建 Vitis 平台工程。Vitis 工程的创建方法可参考之前的文章《ZYNQ 串口打印输出 —— FPGA Vitis 篇》。
  3. 新建 Vitis 应用工程,创建一个名为 AXI_UserIPDemo 的工程,工程模板可选择 “Hello World”。
  4. 在 APP 工程目录下,可以找到对应开发 IP 的 AXI_pwm.h 文件,该头文件中包含了对自定义 IP 寄存器的读写宏定义。

头文件

  1. 在 helloworld.c 文件中,修改 main 函数。首先通过写寄存器 AXI_PWM_S00_AXI_SLV_REG0_OFFSET 控制 PWM 输出频率,然后通过写寄存器 AXI_PWM_S00_AXI_SLV_REG1_OFFSET 控制 PWM 输出的占空比。

修改代码

  1. 编译工程,并将工程下载到硬件板卡上(编译和下载方法可参考《ZYNQ 串口打印输出 —— FPGA Vitis 篇》)。

实验结果:板卡上的 LED0 灯呈呼吸状亮灭。

A. 工程源码下载

如果对本文感兴趣,可关注公众号 “FpgaHome”。该工程对应的源码可通过关注该微信公众号,在公众号输入 ZYNQ_UserIP 获取工程的下载链接。工程采用的是 Vivado 2021.1 版本。

参考文献:

[1] Alinx 官网
[2] Zynq-7000 SoC Technical Reference Manual (UG585). Xilinx 官方文档
[3] ZYNQ 串口打印输出 —— FPGA Vitis 篇. 图灵研究院


Zynq —— 自定义含 AXI4 接口 IP 核:PS 与 PL 的交互

墨漓_lyl 于 2021-01-27 22:22:21 发布

在本次实验中,我们将通过封装带有 AXI4 接口的 IP 核,实现 PS(Processing System)和 PL(Programmable Logic)之间的数据交换。自定义 IP 核能够实现系统设计的定制化,从而达到设计重用的目的,这在很大程度上可以简化系统设计并缩短产品上市时间。

实验任务

本次实验的任务是通过自定义一个含有 AXI 总线的加减法器 IP 核,在 PS 端随机生成数据并传输到 PL 端。PL 端完成计算后,将结果发送回 PS 端并通过 UART 打印出来。同时,利用 PL 端控制的 LED 灯显示当前计算的是加法还是减法。

实验步骤

步骤一:创建 IP 核

  1. 打开 Vivado,点击下图位置的 Manage IP,并在选项中选择 New IP Location

    在这里插入图片描述

  2. 在设置界面中,自行选择一个保存 IP 的地址,其他选项保持默认。点击 Finish 完成创建。

    在这里插入图片描述

  3. 进入设计后,在工具栏点击下图位置。

    在这里插入图片描述

  4. 在创建过程中,参数配置如下图所示,其他配置保持默认。

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

参数说明:

  • Interface Type(接口类型):共三种接口类型可选,分别是 Lite、Full 和 Stream。AXI4-Lite 接口是简化版的 AXI4 接口,用于较少数据量的存储映射通信;AXI4-Full 接口是高性能存储映射接口,用于较多数据量的存储映射通信;AXI4-Stream 用于高速数据流传输,非存储映射接口。本次实验只需少量数据的通信,因此接口类型选择默认的 Lite 接口。
  • Interface Mode(接口模式):接口模式有 Slave(从机)和 Master(主机)两种模式可选。AXI 协议是主机和从机通过“握手”的方式建立连接,这里选择默认的 Slave 接口模式。
  • Data Width(数据宽度):数据位宽保持默认,即 32 位。
  • Memory Size(存储器大小):在 AXI4-Lite 接口模式下,该选项不可设置。
  • Number of Registers(寄存器数量):用于配置寄存器的数量,这里保持默认。

点击 Next 按钮。

步骤二:编辑 IP 核

  1. 添加完成后,在 IP Catalog 界面可以看到新添加的 IP。接下来需要对这个 IP 进行编辑。选中该 IP,右键选择 Edit in IP Packager。弹出窗口后默认点击 OK,此时会新打开一个工程。打开这两个文件的 Verilog 源代码。

    在这里插入图片描述

  2. 该文件实现了 AXI4 协议下的读写寄存器功能。如下图所示,定义的四个寄存器是前面配置时设置的寄存器,对这些寄存器的读写可以实现 PS 与 PL 端的数据交互,且这些寄存器均为 32 位宽。

    在这里插入图片描述

    在这里插入图片描述

  3. 在工程的 Design Sources 上右键选择添加文件,并按照下图设置文件名并存放在该 IP 工程的 hdl 相对位置下。

    在这里插入图片描述

    在这里插入图片描述

  4. 在新创建的文件中输入以下代码:

    module calculate (
        input wire clk,
        input wire rst,
        input wire [9:0] add1, // 第一个数
        input wire [9:0] add2, // 第二个数
        input wire calculate_sign, // 1 表示加和,0 表示作差
        output wire led, // 亮表示此时计算加法,灭表示减法
        output reg [10:0] result
    );
    
    assign led = calculate_sign;
    
    always @(posedge clk or negedge rst) begin
        if (!rst) begin
            result <= 0;
        end
        else if (calculate_sign == 1) begin
            result <= add1 + add2;
        end
        else if (calculate_sign == 0) begin
            if (add1 > add2) begin
                result <= add1 - add2;
            end
            else begin
                result <= add2 - add1;
            end
        end
    end
    
    endmodule
    
  5. 将该模块实例化到前面打开的文件中:

    • Calculate_IP_v1_0_S00_AXI 文件中的合适位置,加入如下实例化过程:

      在这里插入图片描述

      在实例化时,需要清楚 calculate 模块中的引脚分别需要连接到哪里。时钟和复位模块可直接与 Calculate_IP_v1_0_S00_AXI 中的时钟和复位相连;需要与 PS 交互读写的几个变量可按照相同的位宽与四个寄存器合理相连;LED 灯引脚则需要与 PL 端的 LED 灯相连。

    • 由于 LED 灯需要与 PL 端资源相连,因此应在 Calculate_IP_v1_0_S00_AXI 模块的合适位置添加 LED 灯的输出:

      在这里插入图片描述

    • 此外,还需要在 Calculate_IP_v1_0 文件中的实例化和输出中分别加入 led 变量:

      在这里插入图片描述

      在这里插入图片描述

  6. 上述实例化过程完成了 PS 端到 PL 的数据传输。下面介绍 PL 到 PS 端数据传输的部分。以下是 Calculate_IP_v1_0_S00_AXI 文件中对四个寄存器进行写操作的代码:

    always @(posedge S_AXI_ACLK) begin
        if (S_AXI_ARESETN == 1'b0) begin
            slv_reg0 <= 0;
            slv_reg1 <= 0;
            // slv_reg2 <= 0;
            slv_reg3 <= 0;
        end
        else begin
            if (slv_reg_wren) begin
                case (axi_awaddr[ADDR_LSB + OPT_MEM_ADDR_BITS : ADDR_LSB]) // [3:2]
                    2'h0: begin
                        for (byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH / 8) - 1; byte_index = byte_index + 1) begin
                            if (S_AXI_WSTRB[byte_index] == 1) begin
                                slv_reg0[(byte_index * 8) +: 8] <= S_AXI_WDATA[(byte_index * 8) +: 8];
                            end
                        end
                    end
                    2'h1: begin
                        for (byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH / 8) - 1; byte_index = byte_index + 1) begin
                            if (S_AXI_WSTRB[byte_index] == 1) begin
                                slv_reg1[(byte_index * 8) +: 8] <= S_AXI_WDATA[(byte_index * 8) +: 8];
                            end
                        end
                    end
                    // 2'h2: begin
                    //     for (byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH / 8) - 1; byte_index = byte_index + 1) begin
                    //         if (S_AXI_WSTRB[byte_index] == 1) begin
                    //             slv_reg2[(byte_index * 8) +: 8] <= S_AXI_WDATA[(byte_index * 8) +: 8];
                    //         end
                    //     end
                    // end
                    2'h3: begin
                        for (byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH / 8) - 1; byte_index = byte_index + 1) begin
                            if (S_AXI_WSTRB[byte_index] == 1) begin
                                slv_reg3[(byte_index * 8) +: 8] <= S_AXI_WDATA[(byte_index * 8) +: 8];
                            end
                        end
                    end
                    default: begin
                        slv_reg0 <= slv_reg0;
                        slv_reg1 <= slv_reg1;
                        // slv_reg2 <= slv_reg2;
                        slv_reg3 <= slv_reg3;
                    end
                endcase
            end
        end
    end
    

    需要将 slv_reg2 作为 PL 到 PS 传输时的中转寄存器,因此需要将它的赋值屏蔽掉,即直接注释掉。并在该文件中加入以下代码:

    // Add user logic here
    // 屏蔽掉前面所有对 slv_reg2 的赋值
    always @(posedge S_AXI_ACLK) begin
        slv_reg2[10:0] <= result;
    end
    
    wire [10:0] result;
    
    calculate u_calculate (
        .clk (S_AXI_ACLK),
        .rst (S_AXI_ARESETN),
        .add1 (slv_reg0[9:0]),
        .add2 (slv_reg1[9:0]),
        .calculate_sign (slv_reg0[31]),
        .led (led),
        .result (result) // slv_reg2[10:0]
    );
    

完成上述步骤后,点击 Run Synthesis 进行编译。

步骤三:封装 IP 核

  1. 编译完成后,通过 IP-XACT 界面下的 component.xml 打开 Package IP 界面,开始设置 IP 的封装。依次按照下图步骤进行操作:

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

到此为止,自定义 IP 核配置完成。该 IP 工程可以直接关闭,接下来按照以往的步骤新建工程。

步骤四:新建工程并导入 IP 核

  1. 在新建的工程中,先将刚才建立的 IP 的地址导入到该工程的 IP 目录下,操作如下:

    在这里插入图片描述

  2. 创建 Block Design,先添加 Zynq 并对其进行设置:

    • 添加 UART。
    • 根据各自开发板更改 Clock Configuration 中 Basic Clocking 的输入频率。
    • 根据各自开发板对 DDR 进行配置。
  3. 搜索并添加刚才建立的 IP:Calculate_IP,然后直接点击 Run Block AutomationRun Connection Automation,最后在 LED 灯引脚上 Make External

    在这里插入图片描述

  4. 添加引脚约束文件,代码如下:

    set_property IOSTANDARD LVCMOS33 [get_ports {led_0}]
    set_property PACKAGE_PIN R14 [get_ports led_0]
    
  5. 对该 Block Design 依次执行以下操作:

    • Generate Output Products
    • Create HDL Wrapper
    • 点击保存
    • Generate Bitstream
    • 在耐心等待之后,点击 Export Hardware,并选择 Include bitstream
    • 点击 launch SDK

步骤五:SDK 编程

  1. 在 SDK 中新建 Empty Application。在 src 文件夹下添加新 source file,并输入以下代码:

    #include "stdio.h"
    #include "xparameters.h"
    #include "Calculate_IP.h"
    #include "xil_io.h"
    #include "sleep.h"
    #include "stdlib.h"
    
    #define CALCULATE_IP_BASEADDR XPAR_CALCULATE_IP_0_S00_AXI_BASEADDR // IP 的基地址
    #define CALCULATE_IP_REG0 CALCULATE_IP_S00_AXI_SLV_REG0_OFFSET // REG0 地址
    #define CALCULATE_IP_REG1 CALCULATE_IP_S00_AXI_SLV_REG1_OFFSET // REG1 地址
    #define CALCULATE_IP_REG2 CALCULATE_IP_S00_AXI_SLV_REG2_OFFSET // REG2 地址
    
    int main() {
        int result = 0, add1 = 1, add2 = 2, calculate_sign = 0;
    
        printf("test\n");
    
        while (1) {
            // 生成 0-999 的随机数
            add1 = rand() % 1000;
            add2 = rand() % 1000;
            calculate_sign = rand() % 2;
    
            if (calculate_sign == 1) {
                CALCULATE_IP_mWriteReg(CALCULATE_IP_BASEADDR, CALCULATE_IP_REG0, 0x80000000 + add1);
                printf("%d + %d = ", add1, add2);
            }
            else {
                CALCULATE_IP_mWriteReg(CALCULATE_IP_BASEADDR, CALCULATE_IP_REG0, add1);
                if (add1 > add2) {
                    printf("%d - %d = ", add1, add2);
                }
                else {
                    printf("%d - %d = ", add2, add1);
                }
            }
    
            CALCULATE_IP_mWriteReg(CALCULATE_IP_BASEADDR, CALCULATE_IP_REG1, add2);
    
            // 读取结果
            result = CALCULATE_IP_mReadReg(CALCULATE_IP_BASEADDR, CALCULATE_IP_REG2);
            printf("%d\n", result);
    
            sleep(1);
        }
    
        return 0;
    }
    
  2. 下载到开发板后,实验成功如下:

    在这里插入图片描述


Vitis 2024.1 无法正常编译 custom IP 的 bug(因为 Makefile 里的 wildcard)

Αλήθεια 已于 2025-03-19 05:00:34 修改

现象

如果在 Vivado 中添加了自己的 custom IP(例如 AXI4 IP),那么在 Vitis(2024.1)编译导出的 .xsa 文件时,会构建失败。报错代码如下:

"Compiling blank_test_ip..."
microblaze-xilinx-elf-gcc.exe: warning: (ildcard: linker input file unused because linking not done
microblaze-xilinx-elf-gcc.exe: error: (ildcard: linker input file not found: No such file or directory
microblaze-xilinx-elf-gcc.exe: warning: *.c): linker input file unused because linking not done
microblaze-xilinx-elf-gcc.exe: error: *.c): linker input file not found: Invalid argument
make[2]: *** [Makefile:18: libs] Error 1
make[1]: *** [Makefile:46: psu_pmu_0/libsrc/blank_test_ip_v1_0/src/make.libs] Error 2
make: *** [Makefile:18: all] Error 2

原因

自动生成的 Makefile 文件存在语法错误:

LIBSOURCES=($wildcard *.c)
OUTS = ($wildcard *.o)

$ 应该在 wildcard 括号外面。

解决办法(推荐方法 2)

  1. 方法 1:如果已经生成了 .xsa 文件,导入 .xsa 后,在 Vitis 内点击 Search,将该 platform project 中所有 ($wildcard 替换为 $(wildcard,然后重新 build。修改内容如下:

    LIBSOURCES=$(wildcard *.c)
    OUTS = $(wildcard *.o)
    
  2. 方法 2:如果可以重新生成 .xsa 文件,直接在 Vivado 中打包 IP 后,在 IP 的 Makefile 中直接修改一次。修改代码与上述方法 1 一致。升级 IP 并重新生成 .xsa 文件后,问题即可解决。

    在这里插入图片描述

后续问题

如果按照方法 1 修改,虽然还会有无法生成 image 的问题,但如果不启动系统,问题不大:

sdcard_gen --xpfm abcd.xpfm --sys_config abcd --bif abcd/Debug/system.bif --bitstream abcd/_ide/bitstream/mpsoc_preset_wrapper.bit --elf abcd/Debug/chip_test.elf,psu_cortexa53_0
creating BOOT.BIN using abcd/_ide/bitstream/mpsoc_preset_wrapper.bit
Error intializing SD boot data : Software platform XML error, sdx:qemuArguments value "abcd/qemu/pmu_args.txt" path does not exist abcd/qemu/pmu_args.txt, platform path abcd sdx:configuration , sdx:image standard
make: *** [makefile:37: package] Error 1

如果想解决这个问题,可以参考以下链接:

https://zhuanlan.zhihu.com/p/26294135403

方法 2 没有后续问题。

Vitis 到 2024 年居然还有这种低级 bug…


FPGA 菜鸟炖大锅VIVADO 自定义 IP 核后在导入硬件至 Vitis 时无法正常编译

nomoretime 发布于 2025-02-25 17:37・陕西

场景

在 Vivado 中自定义 IP 核,将生成的硬件(system_wrapper)导入 Vitis 后,无法正常更新编译硬件平台,也无法正常编译工程。

硬件平台:ZYNQ7Z020

软件平台:Vivado 2020.2;Vitis 2020.2

背景

工程开发到一半时,甲方要求增加 9 个电机。此时 PS 侧的 MIO 已被以太网等外设占满,因此不得不将这部分电机控制逻辑写成 RTL,并通过 AXI_Light 从 PS 侧往 PL 侧发送数据以实现控制。

问题 1:硬件导出后无法正常更新硬件平台,Makefile 报错

问题出现在更新这一步:

img

按照正常顺序更新了 .xsa 文件并复位了 BSP 源文件后,在清理并重新编译工程时,报出多个与 Makefile 相关的错误,格式如下:

make: *** [makefile:xx: xx] Error 1

img

从网上查到,在自定义 IP 核后,用户自行设计并导入的 IP 核需要自己编写 Makefile,Vitis 将不再提供完整的 Makefile 以供使用。因此,需要找到涉及自定义 IP 核的文件夹,对不完整的文件进行替换。具体操作如下:

  1. 根据编译输出找到具体的报错文件,一般来说有两到三个 Makefile 需要更改。
  2. 在工程的官方模块中找到一个完整的 Makefile,复制粘贴到出错的文件夹内进行替换即可。

例如:

img

上图中,motor_ctrl 是自定义的 IP,用下边中断模块中的 Makefile 进行了替换。其他两个文件夹中的操作类似。

img

这是第二个需要替换的文件夹。

img

这是第三个需要替换的文件夹。

替换完成后,可以正常编译,没有错误。

这里有一个现象比较有迷惑性,也耽误了大量时间:

  1. 第一次出现问题时,没有发现编译硬件平台出现了问题,因为平台编译的报错不在控制台中显示,需要点击旁边的 problems-error 才能看见报告。

  2. 直接编译工程时,工程会报错找不到头文件的错误,通常是系统内自带的头文件(如 xparameters.h)或系统外设的头文件(如 xgpiops.h),格式如下:

    fatal error: xparameters.h: No such file or directory
    

    最初以为是在更新 system_wrapper 时不小心动了相对路径,导致编译器找不到头文件,尝试重新添加路径,但手动添加的头文件虽然不会再报错,后续还会继续报相同的错误。

    最终解决方法是重新从 Vivado 中导出硬件,然后在 Vitis 中重新走一遍所有流程。

    如果没有备份,可以手动删除 wrapper 和包含的文件路径(应该不需要删除),然后重新从 Vivado 中导出硬件,再在 Vitis 中重新执行所有流程。

教训:如果发现应用工程莫名其妙地找不到头文件,先检查控制台旁边的报错,也检查左侧工程树中 system_wrapper 是否有 (out of date) 的黄色标识。不要一意孤行,强扭的瓜不甜。

问题 2:硬件平台编译通过后应用工程编译报错,提示缺少 qemu_args.txt

在替换完所有 Makefile 后,system_wrapper 顺利编译通过,但在编译应用工程时,又出现了另一个找不到文件的错误,格式如下:

make: *** [makefile:39: package] Error 1

实际报错内容如下:

Generating bif file for the system project
generate_system_bif.bat 53991 D:/Verilog/VIVADO/TCPIP_2UART/par/TCPIP/Vitis/system_wrapper/export/system_wrapper/system_wrapper.xpfm standalone_ps7_cortexa9_0 D:/Verilog/VIVADO/TCPIP_2UART/par/TCPIP/Vitis/TCPIP_system/Debug/system.bif

sdcard_gen --xpfm D:/Verilog/VIVADO/TCPIP_2UART/par/TCPIP/Vitis/system_wrapper/export/system_wrapper/system_wrapper.xpfm --sys_config system_wrapper --bif D:/Verilog/VIVADO/TCPIP_2UART/par/TCPIP/Vitis/TCPIP_system/Debug/system.bif --bitstream D:/Verilog/VIVADO/TCPIP_2UART/par/TCPIP/Vitis/TCPIP/_ide/bitstream/system_wrapper.bit --elf D:/Verilog/VIVADO/TCPIP_2UART/par/TCPIP/Vitis/TCPIP/Debug/TCPIP.elf,ps7_cortexa9_0

creating BOOT.BIN using D:/Verilog/VIVADO/TCPIP_2UART/par/TCPIP/Vitis/TCPIP/_ide/bitstream/system_wrapper.bit

Error initializing SD boot data: Software platform XML error, sdx:qemuArguments value “system_wrapper/qemu/qemu_args.txt” path does not exist D:/Verilog/VIVADO/TCPIP_2UART/par/TCPIP/Vitis/system_wrapper/export/system_wrapper/sw/system_wrapper/qemu/qemu_args.txt, platform path D:/Verilog/VIVADO/TCPIP_2UART/par/TCPIP/Vitis/system_wrapper/export/system_wrapper, sdx:configuration system_wrapper, sdx:image standard

make: *** [makefile:39: package] Error 1

解决方法非常简单:直接找到提示的路径,然后手动创建一个文件夹和文件。

img

更新完硬件平台后,原来的目录结构如下:

img

qemu 文件夹和 qemu_args.txt 文件都是手动新建的,txt 文件可以为空,无需添加任何内容。

完成上述操作后,即可成功编译并下载到开发板上,一切正常。

2025.2.25 Gugu


via:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值