ZYNQ学习之路3. 定制AXI IP核
亦梦云烟 2018-05-25 12:47:08 7694 收藏 47 分类专栏: ZYNQ开发 文章标签: AXI自定义IP ZYNQ 版权
ZYNQ开发 该内容包含在专栏中 25 篇文章4 订阅 订阅专栏 ZYNQ硬核是最大的优点A9处理器与FPGA处理器可以扩展任何用户想要的外设(数字逻辑外设),FPGA与处理器通过AXI连接高速总线,提供处理器到达FPGA的高速带宽(ZYNQ7000最高8Gbps)。AXI总线协议相当复杂。幸运的是,Xilinx提供了AXI对于包装工具,开发人员只需在指定位置添加自己的逻辑功能代码,大大简化了开发过程。ZedBoard已经介绍了如何推出官方例子Xilinx做好的AXI总线IP(如AXI_Timer、AXI_GPIO等)添加到项目中,让我们自己写一个简单的AXI读取板上的四个总线设备Swtich状态,并控制3LED的外设。
一. 建立LED和开关的AXI IP核
首先检查开发板的原理图,确定LED和开关的引脚:
根据原理图理图确定为:
表1. LED和开关引脚 元件 LED_R LED_G LED_B SW1 SW2 SW3 SW4 引脚 R14 Y16 Y17 R19 T19 G14 J14 1.1 在vivado在开发环境中新建一个LED_AXI该项目生成了一个名称system的Block Diagram文件
再添加ZYNQ7 Processing System在这个原理图中创建内核系统vivado工程及Bloack Diagram如下:
双击ZYNQ7 Processing System,配置ZYNQ的DDR为MT41K256M16 RE-125
配置MIO48,MIO49为uart1的引脚
1.2 下面开始创建自定义LED和开关IP核
点击菜单Tools->Create and Package IP...
点击next, 选择Create a new AXI4 peripheral项,上面三个是将当前工程打包。
修改IP名称及储存位置:
这里显示了AXI总线接口的名称接口时间Slave, 32位数据位宽,IP四个寄存器,点击finish完成设计。
打开IP Catalog我们可以看到界面LED_IP_v1.0,此时这个IP没有任何功能。
右键选中LED_IP_v1.0,然后选择Edit in IP Packager项。点击OK,另一个软件将被打开vivado窗口对这个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)在位置添加代码以实现其逻辑功能。以下代码是基于slv_reg读取开关状态,slv_reg1设置LED状态。如果您不理解,建议回顾以下内容verilog基础吧!
<span style="color:#33cc00;">// Add user logic here</span> 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 <span style="color:#33cc00;">// User logic ends</span>
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_这个项目,确保没有错误。
双击IP-XACT下的component.xml文件,点击ports and interfaces项,点击merge changesfrom ports and Interface wizard。
我们在程序中定义的端口在窗口中更多:
其他没有打钩的人file groups点击Merge changes from file groups wizard更新文件和驱动程序。
选择Review and Package点击项Re-Package IP按钮技术IP核的设计。
到目前为止,自定义IP设计完成,关闭IP核的vivado工程回到LED_AXI在项目中。
1.3 回到LED_AXI系统设计原理在工程中
在Diagram窗口中搜索LED刚才会有自定义IP核:LED_IP_v1.0.
双击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; } //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:
再右键选择LED_test, 点击Run as->1 launch on hardware. 在串口终端中显示如下:
同时RGB三色灯呈现8种颜色循环闪烁。到处位置,FPGA中的LED和开关的IP核创建完成。
三. Linux下的IP核驱动及应用
3.1 在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 0; } 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 在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 0; } while(1) { for(i=0;i<8;i++) { ioctl(fd,i,i); sleep(1); 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的开发流程基本都已经熟悉吧! ———————————————— 版权声明:本文为CSDN博主「亦梦云烟」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u010580016/article/details/80448063