【转载】"30年---我与赛灵思FPGA的故事”:ZYNQ-7000使用总结(8)——用户IP设计和应用程序设计流程
通过前面的几个例子,我们可以看出,在使用Zynq做设计时,合理使用Xilinx已有的IP核非常的关键,可以极大地方便与简化我们的开发。但是有的时候我们需要根据自己的需要设计自己的IP核,即所谓的用户IP。这篇文章就通过一个完整的例子介绍如何设计用户IP,并且会对驱动级的应用程序设计流程进行一个详细的介绍。
用户IPCore
用户IPCore就是用户自定义的IP核,用以和官方提供的IP核做区别。IPCore属于PL部分,所以在Zynq上创建IPCore时就需要考虑如何跟PS交换数据。对于一般的IP核来说,有两种可行的方案,一是通过EMIO交换数据(GPIO、SPI等),二是制作满足AXI协议的IP核。第一个方案其实是将PL的IP核看做系统的外设,在数据交互性能和效率上都有很大的欠缺,常用的方法是第二种,Xilinx提供的IP核也都属于第二种。但是第二种方案的问题在于,制作的IP核和AXI协议密切相关,这提高了IP核设计的难度。为了降低难度,Xilinx提供了用户IP向导(Wizard)。它会自动生成总线(AXI)相关的代码,做好地址译码逻辑、读写控制逻辑,并在用户工作区生成一些寄存器。我们写的PL逻辑通过读写这些寄存器和PS交互。这里我们介绍一个PWM发生器的例子,介绍一下用户IPCore的开发流程。这个PWM发生器内部只有两个寄存器,一个是调节周期的周期寄存器,另一个是调节占空比的占空比寄存器。其中周期寄存器的最高位是状态位,控制PWM波最后是否产生。该例参考自《ZYNQ嵌入式系统软硬件协同设计实战指南》一书。
设计流程
PS:下面的设计流程中,很多是和以前一样的,所以相同部分不再详细讲解,如果忘了,可以回头再去看一下《ZYNQ-7000使用总结(3)——PS和PL部分配合使用》中的流程。
PlanAhead中的工作——1
和前面的几个例子一样,我们现在PlanAhead里面创建工程(我创建的工程名为pwm_ip),并且创建system.xmp文件,然后会打开XPS。因为前面已经有比较详细的图文并茂式的介绍,这里就直接略过。
XPS中的工作
打开XPS后,我们首先和以前一样是添加处理器实例IP,然后导入自带的模板,我这里导入的zc702的模板。接下来我们正式开始用户IPCore的创建。
在菜单栏,依次打开Hardware——Create and Import Peripheral,然后便会进入到用户IP创建向导:
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12917-1.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12918-2.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12919-3.png)
这里我们选择创建IP,而不是导入IP,存储位置的话,选择默认即可。然后Next,
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12920-4.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12921-5.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12922-6.png)
上面第一幅图中,我们填入要创建的IP名称,名称只能使用小写字母或下划线,这里起名为pwm_ip,Wizard会帮我们编一个版本号。
第二幅图选择AXI4总线类型(前面的文章已经介绍过),因为我们要制作的IP核只受一个Master控制,而且没有考虑大数据传输等,所以这里选择AXI4-Lite,单击Next。
第三幅图总共有四个选项。“User logic master support”表示IP核内部是否要加入Master接口,这一版用在比较复杂的外设里。下面的“Software reset”和“Include data phase timer”分别表示是否开启软件中断和数据周期计时器。我们这个IP核比较简单,只选择“User logic Software register”就行了。这样的选项最后生成的IP核没有多余的模块,例如没有AXI Master接口、Software复位模块、数据周期计数器模块等。
继续点Next往下走,
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12923-7.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12924-8.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12925-9.png)
上面第一幅图选择我们使用的寄存器的数量,根据前面的介绍,我们这里只需要2个即可,分别是PWM波的占空比寄存器和周期寄存器。这个选项设置的寄存器数量是我们最后可以使用的寄存器。这个Wizard可以帮我们自动为所有的寄存器做好地址译码和控制逻辑布局。这样我们在写user_logic的时候,就可以直接使用这些寄存器了。
第二幅图是显示我们可以使用的IPIC信号。选中每个信号,右边就会显示它的功能,我们可以根据自己的需要做删减,这里我们保持默认,点击Next。
第三幅图的选项是让Wizard帮我们生成一个测试用的Master,那么我们可以在IP核加入系统之前就对他进行功能测试。这个测试用的Master叫仿真支持平台,具有一些AXI总线的控制能力。可以通过阅读平台的README文件得到关于指令的信息。这里我们不选。
点击Next,继续。
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12926-10.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12927-11.png)
上面第一幅图中有三个选项,第一个选项表示生产的User Logic使用Verilog(默认使用VHDL),这里根据自己对语言的掌握进行选择,我使用Verilog,所以勾选该选项。第二个选项表示同时生成ISE工程,便于调试和测试IPcore。第三个选项表示生产软件驱动库文件,方便在SDK里使用IPcore。
点击Next后,便进入最后的总览界面,确认无误后,点击Finish。
这样我们的IP核就建立好了。我们发现IP Catalog中多出来一个USER分类,我们的IP核就在里面,然后双击将该IP核添加到我们的系统之中。
接下来我们需要编辑三个文件:
第一个文件:mpd文件。在Bus Interface选项卡中找到该IP,右击选择View
MPD,进入MPD文件,我们在END之前添加pwm_out端口,方向是输出,位数为1。保存关闭。这个文件里面的端口会在system
assembly view中的ports界面中映射出添加的pwm_out接口。添加的代码见下图:
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12928-12.png)
第二个文件:vhd文件。右键IP,选择Browse HDL
Sources,在hdl文件夹下的vhdl文件夹里面选择vhd文件,添加代码。这个步骤是声明刚才在MPD中设置的接口名称和位宽(这里叫pwm_out,位宽为1),并且将这个接口一直连接到用户逻辑设备。添加的代码见下图后两幅图(这里的文件内容格式比较像,注意别添加错位置了):
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12929-13.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12930-14.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12931-15.png)
第三个文件:v文件。右键IP,选择Browse HDL
Sources,在文件夹下的verilog文件夹里面选择.v文件,添加代码,见下图。第一幅图声明pwm_out接口。第二幅图定义pwm_out为输出,而且宽度为1。第三幅图是用户实现段,令slv_reg0为周期寄存器,slv_reg1为占空比寄存器。此外我们还需要一个计数器(pwm_counter)、下一个周期的开始信号(Over
Period)和预输出信号(pre_pwm_out)。
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12932-16.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12933-17.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12934-18.png)
至此,我们三个文件就添加完毕了。我们用户自定义的IP核主要就是修改这三个文件。
然后我们在菜单栏中选择Project——Rescan User
Repositories,然后就可以在port选项卡中看到我们新添加的引脚pwm_out。选择External
Ports把引脚引出去。再打开Address选项卡,单击右上角的Generated
Addresses按钮,就可以看到系统给每个模块分配的地址。这里的地址在我们后面软件中会用到。
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12935-19.png)
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12936-20.png)
然后单击Project——Design Rule Check,如果Console中没有提示错误,那我们XPS中的工作就算完成了。关闭XPS,回到PlanAhead。
PS:如果DRC后没有错误,但是在后面的PlanAhead里面综合时还是提示XPS中有错误,那可以在XPS中选择Generate Netlist定位一下问题所在。
PlanAhead中的工作——2
PlanAhead中的工作,和以前一样,先是右击system.xmp文件,选择Create Top HDL。
然后单击左边的Add Source,创建约束文件(前面文章已经介绍过),约束文件起名为system。
完成后点击左边的Run Synthesis,在综合过程结束后弹出的对话框中选择Open Synthesized Design。然后在打开的I/O便签中添加约束,这里我们将pwm输出引脚约束到开发板上的一个LED灯(DS19)上面,然后保存。然后可以查看system.ucf文件中已经增加了我们的约束信息。
然后单击左边的Generate Bitstream。完成后和以前一样导出我们的硬件平台到SDK。
SDK中的工作
和以前一样,我们在SDK中以导出的硬件为平台,使用Hello World模板创建应用程序,前面的文章已经讲过,这里略过。我创建的应用程序名为LED_IP。
创建好以后,选择Xilinx Tools——Repositories。单击New,加入我们刚才Wizard生成的edk目录,里面有我们创建的IP核信息,如下图:
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12937-21.png)
然后在BSP文件(我的为LED_IP_bsp)上右击,选择Board Support Package Setting。找到drivers中的pwm_ip_0,修改Driver,选择pwm_ip,
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12938-22.png)
这样我们便将我们的IP核的Driver添加了进来。然后在helloworld.c中添加如下代码:
/*
* helloworld.c: simple test application
*/
#include
#include "platform.h"
#include "pwm_ip.h"
#include "xparameters.h"
int main()
{
init_platform();
printf("This is a PWM IP Creation Demo.");
PWM_IP_mWriteReg(XPAR_PWM_IP_0_BASEADDR, 0, 10000);
PWM_IP_mWriteReg(XPAR_PWM_IP_0_BASEADDR, 4, 0x80000000+5000); // 占空比5000/10000=50%
cleanup_platform();
return 0;
}
然后连接开发板,用导出的bit文件配置FPGA,编译运行程序,我们会看到LED(DS19)的亮度只有其它的一半。更准确的我们可以用万用表测一下pmod1_0引脚的电压(DS19和pmod1_0连在一起),只有Vcc(3.3V)的一半1.65V。证明产生了占空比50%的方波。
按照上述的步骤,在编译程序时应该会出错,这是由一些已知的软件本身Bug造成的(我的软件版本是14.4),我们做以下几个点的修改:
打开Wizard帮助我们生成的驱动文件,我的目录是E:\FPGA\workspace\xilinx\demo_ip\demo_ip.srcs\sources_1\edk\system\drivers\pwm_ip_v1_00_a\src,打开里面的pwm_ip_selftest.c文件,添加下图中框起来的那行宏定义:
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201407/7333-12939-23.png)
编译时提醒找不到xio.h文件。解决方法是在上面所说的目录里打开pwm_ip.h和pwm_ip_selftest.h文件,把里面的xio.h全部用xil_io.h替换。如果还编译出错,就在这两个文件里面添加#include “xil_types.h”。具体可参见Xilinx官方: http://china.xilinx.com/support/answers/54352.html#description 。
一些知识点:
我们一般在硬件中添加IP核以后,在软件设计中就需要写对应的程序来配置和使用IP核。那么这个时候我们该怎样去写这个程序呢?两方面的途径。不论是Xilinx官方的IP核,还是我们自己创建的IP核,都会附带实例程序,比如这个我们创建的PWM_IP核,我们就可以在前面所说的目录里面找到示例驱动程序(即pwm_ip.h、pwm_ip.c、pwm_ip_selftest.c)文件。官方的IP核,我们可以软件安装目录里面找到对应的文档和示例程序,比如我的目录是C:\Xilinx\14.4\ISE_DS\EDK\hw\XilinxProcessorIPLib\pcores。当然,我们也可以在SDK中的system.mss中直接打开文档和例子程序。其实,我们在建立一个应用程序的时候,它帮我们自动生成的BSP,就是直接拷贝这些地方的示例代码。比如我们按照上面所说的方法解决掉bug以后,我们可以去bsp文件里面找到我们这个IP核,查看他的src,就会发现它也已经改变了。另一个途径就是去Xilinx官方找对应的IP核文档,这个只能针对Xilinx提供的IP核。
关于xparameters.h文件。这个文件是系统自动生成的,对于我们的开发至关重要,前面的程序中,我们有使用到一些宏,那些宏就是在这个文件里面定义的。打开这个文件,我们就会发现里面基本上全是宏定义,主要是定义一些设备的基地址、高地址、偏移量、中断号等。我们进行程序设计的时候,一般都需要去这个文件里面找对应地址的宏定义。
至此,这篇文章也写完了。IP核的创建对于我们日常的开发还是非常重要的,我们需要很好的掌握。另一方面,这篇文章也介绍了一些驱动级的应用程序设计,大家可以结合相关文件里面的代码好好体会一下Zynq的软硬件协同设计。
转载自:http://xilinx.eetrend.com/blog/7333