1)摘自【正点原子】领航者ZYNQ之HLS 开发指南
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900
5)正点原子资料更新和新品发布,请加正点原子公众号:正点原子 关注方法:微信→添加好友→公众号→输入:正点原子
第三章按键控制LED实验
在第一章节我们通过LED闪烁实验学习了如何通过HLS来生成一个带有输出接口的IP核。在本章我们通过按键控制LED实验,来学习如何使用Vivado HLS工具生成一个带有输入和输出接口的IP核,并学习Vivado HLS工具仿真平台的使用,以及在Vivado中对综合结果进行验证的流程。
本章包括以下几个部分:
33.1简介
3.2实验任务
3.3HLS设计
3.4IP验证
3.5下载验证
3.1简介
在使用Vivado HLS工具进行设计的时候,如果设计完成之后发现功能不正确,然后确定问题产生的原因是非常耗时的。为了提高效率,我们使用测试平台在设计完成之后验证C函数在功能上是否正确。
Vivado HLS工具的输入和输出如下图所示:
![8baa945fea22cebdabee8fad020c9bb2.png](https://i-blog.csdnimg.cn/blog_migrate/a67d645d2af6475c79b53206282addf9.png)
图 3.1.1 vivado hls工具的输入和输出
从图中可以看出在Vivado HLS的设计流程中,设计的输入包括C/C++设计、Testbench(测试集)编写以及Constraints(约束)/directives(指令)的添加。Vivado HLS最主要的输出结果是以VHDL/Verilog语言描述的RTL实现,并打包成IP模块的形式以方便Xilinx设计工具使用,如System Generator等。
我们可以使用C testbench在综合之前对C函数进行仿真,验证函数的输出的正确性,这一过程被称之为C仿真,对应于图中的“C Simulation”。它还可以在综合之后对综合得到的RTL设计进行仿真,这一仿真过程同样是使用C testbench进行的,这一过程被称为C/RTL协同仿真,对应于图中的“RTL Simulation”。
3.2实验任务
本章的实验任务是使用领航者底板上的 PL_KEY0 和 PL_KEY1 按键来控制底板上的 PL_LED0 和 PL_LED1 两个 LED。没有按键按下时,两个 LED 保持常亮;如果按键 0 按下,则PL_LED0熄灭;如果 按键 1 按下,则PL_LED1熄灭。要求使用C语言完成设计,然后综合得到RTL级实现并进行验证。
3.3HLS设计
我们在电脑中的“F:ZYNQHigh_Level_Synthesis”目录下新建一个名为key_led的文件夹,作为本次实验的工程目录。然后打开Vivado HLS工具,创建一个新的工程。设置工程名为“key_led_hls”,选择工程路径为刚刚创建的文件夹。需要注意的是,工程名以及路径只能由英文字母、数字和下划线组成,不能包含中文、空格以及其他特殊字符。如下图所示:
![e0f7d7e3fde30e7b8d56872c0b79d2fa.png](https://i-blog.csdnimg.cn/blog_migrate/07613cf4ef2cccd8eff1be53d1c3b65b.png)
图 3.3.1 工程配置界面
设置好工程名及路径之后,点击“Next”,进入如下界面:
![977388db09be2e5696dcafbb54875cd6.png](https://i-blog.csdnimg.cn/blog_migrate/0fc05192139f28f612a97a2f3458212d.png)
图 3.3.2 设置顶层函数
工程创建完成后,在工程面板中的“source”目录上点击右键,然后在打开的列表中选择“New File”新建源文件,如下图所示;
![e4fe31b97f3d0af067d372b815fab90f.png](https://i-blog.csdnimg.cn/blog_migrate/076f0508bcaf259296b11d3a526a12b4.png)
图 3.3.3 新建源文件
然后在弹出的对话框中输入源文件的名称“key_led.c”,如图 2.3.11所示。源文件默认的保存路径为HLS工程目录,为方便源文件的管理,我们在工程目录下新建一个名为“src”的文件下,将源文件保存在src目录下。
![8897af058252879987e583ca6ca72737.png](https://i-blog.csdnimg.cn/blog_migrate/9ff2121473114e3081f280768d52c18e.png)
图 3.3.4 输入源文件名
我们在图 3.3.4中输入的源文件的后缀名为“.c”,即使用C语言进行设计。如果使用C++语言进行设计,那么后缀名需要设置为“.cpp”。设置好文件名和路径之后,点击“保存”。
在创建了源文件之后,在Vivado HLS工程面板中的Source目录下可以看到我们新建的源文件:key_led.c,同时信息面板自动打开了该文件的编辑界面,我们可以在里面输入C代码。如下图所示:
![32f005fe6b9e67586e92e8460a0ef829.png](https://i-blog.csdnimg.cn/blog_migrate/d43c137ee129c0242dc54880b9f2fe04.png)
图 3.3.5 新建的源文件
在图1.3.5中箭头所指示的位置输入本次实验的源代码:
- 1 #include "key_led.h"
- 2
- 3 void key_led(uint2 key, uint2* led)
- 4 {
- 5 #pragma HLS INTERFACE ap_none port=led
- 6 #pragma HLS INTERFACE ap_none port=key
- 7 #pragma HLS INTERFACE ap_ctrl_none port=return
- 8
- 9 uint2 key_value = key;
- 10
- 11 switch(key_value)
- 12 {
- 13 case 3:
- 14 *led = 3;
- 15 break;
- 16 case 2:
- 17 *led = 2;
- 18 break;
- 19 case 1:
- 20 *led = 1;
- 21 break;
- 22 default:
- 23 *led = 0;
- 24 break;
- 25 }
- 26 }
在代码的第1行,包含了一个名为“key_led.h”的头文件,这个是我们自己创建的头文件。“key_led.h”的创建方式和“key_led.c”的创建方式相同。创建此头文件的目的是为了函数声明,以便在测试文件中引入此头文件,从而在测试文件中可以调用我们所编写的函数。在代码的第3行,我们在定义函数参数类型时,使用了“uint2”这种数据类型,它表示2位无符号整数。
在代码的第5行到代码的第6行,#pragma为HLS优化指令,它表示led和key使用的是ap_none协议。在代码的第7行ap_ctrl_none表明没有添加包级别的协议,而是完全在端口接口级别用端口级别协议来做控制。在代码的第9行我们定义了一个key_value变量来寄存按键的值。
在第11行到第25行我们根据按键的不同值,来控制LED的状态。这是一个switch case语句。它根据不同的按键key的状态来切换LED的数值。比如:当两个按键的值是3(二进制11),表示没有按键按下,则给LED赋值为3(二进制11),从而点亮两个LED。
头文件“key_led.h”的代码如下:
- 1 #include <ap_cint.h>
- 2
- 3 #ifndef _KEY_LED_H_
- 4 #define _KEY_LED_H_
- 5
- 6 void key_led(uint2 key, uint2* led);
- 7
- 8 #endif
在代码的第1行我们引入了“ap_cint.h”的头文件来包含任意精度数据类型。在代码的第3行到第4行表示如果没有定义“key_led.h”头文件则定义这个头文件,这样可以避免头文件的重复定义。在代码第6行,我们声明了按键控制LED函数,从而在测试文件中可以调用此函数。
在工程面板中的“Test Bench”目录上点击右键,然后在打开的列表中选择“New File”新建测试文件, 在弹出的对话框中输入测试文件的名称“key_led_tb.c”,如图1.3.6所示。测试文件的保存路径为src目录。
![ebe901210ccabb5512648fe45486a866.png](https://i-blog.csdnimg.cn/blog_migrate/974db3fc563a5119e40f04621a75dc3f.png)
图 3.3.6 新建测试文件
在测试文件中编写如下代码:
- 1 #include "key_led.h"
- 2
- 3 int main()
- 4 {
- 5 uint2 key,led;
- 6
- 7 key_led(0,&led);
- 8 key_led(1,&led);
- 9 key_led(2,&led);
- 10 key_led(3,&led);
- 11
- 12 return 0;
- 13 }
在测试文件中,我们通过调用按键控制函数,并给按键赋不同的值,来实现按键控制的仿真。
代码输入完成后,按快捷键Ctrl+S保存。然后点击工具栏中向右的绿色三角形对C代码进行综合,如下图所示:
![0cb5d98bd6b871d5885add301cb2d3be.png](https://i-blog.csdnimg.cn/blog_migrate/16fc55d41c189ea955ef7aac27528a8c.png)
图 3.3.7 运行C综合
综合完成后,会自动打开综合结果(solution)的报告,如下图所示:
![2377d68d54d8eaf2037f3fe48021db3a.png](https://i-blog.csdnimg.cn/blog_migrate/b5d8ebc9d57bb4df4164f393c5104623.jpeg)
图 3.3.8 综合报告
在图1.3.8所示的综合报告中,给出了设计的性能评估、资源评估以及接口等信息。本次实验中,我们重点关注综合工具为我们生成的两个接口,分别为 “key”、“led”。 从接口信息中,我们可以看到按键key的C语言类型是变量,在RTL级别中被映射为输入端口。led的C语言类型是指针,在RTL级别中被映射为输出端口。
这是因为HLS规定了协议、端口类型和方向之间的相关性,在Vivado HLS开发过程中,考虑C/C++函数参数的类型是很重要的。Vivado HLS规定可以传入/传出C/C++函数的值有四种不同的数据类型,分别是:变量、指针、数组和引用。这也就是说,一种特定的参数类型只对应于有限的几种协议。比如,传入一个数组作为形参输入,能使用的协议就只有:ap_hs、ap_memory、bram、 ap_fifo、ap_bus、axis 和 m_axi,其中 ap_memory 是默认的。参数类型对应支持的接口协议如下图所示:
![b849c65d53837453dcf58fdbfffbe7de.png](https://i-blog.csdnimg.cn/blog_migrate/7921abffa894ada68a6fdfaa551d6104.jpeg)
图 3.3.9 参数类型和接口协议的对应图
本实验中,我们规定接口所使用的协议为ap_none。图中的“D”表示“default”,表示Vivado HLS工具默认综合出来的接口。“S”表示“support”,表示Vivado HLS工具支持综合出来的接口。从图中我们可以看出,使用ap_none时,当变量作为形参时,接口只能被综合成输入而不能被综合成输出,当指针变量作为形参时,接口可以被综合成输入、输出和双向端口。本节实验中,我们定义按键key的类型为变量,led的类型指针,从而实现了将按键key作为输入接口,led作为输出接口。
下面我们进行仿真,来测试HLS设计是否满足要求。点击工具栏中的仿真按钮,进入仿真,如下图所示:
![d05c912bd7bd87ebd2d0ad1f7c501182.png](https://i-blog.csdnimg.cn/blog_migrate/385864b98e5b0fcb17b574bd93081534.png)
图 3.3.10 运行C仿真
弹出仿真配置界面,我们在配置界面中启动C调试器,并清除编译残留文件。设置如下图所示:
![e923738d7905a2b0e3b5644663f0dadc.png](https://i-blog.csdnimg.cn/blog_migrate/bc3182751fdf68c2b229008ff5f973fc.png)
图 3.3.11 C调试器配置界面
点击“OK”按钮,打开C调试器,在C调试器工具栏中点击单步调试按钮,如下图所示:
![c0bb846a275a0fde0e8f594d0969b7d4.png](https://i-blog.csdnimg.cn/blog_migrate/5ba83175b614d24b18b7009001644a3e.png)
图 3.3.12 单步调试
执行单步调试,在右侧变量观察窗口中可以看到当按键值key为1的时候,*LED值变为了1。如下图所示:
![578794a7bcf7013b5340cce2d47b04c2.png](https://i-blog.csdnimg.cn/blog_migrate/845f0c9013a69de76e3aa4751dc6d611.png)
图 3.3.13 变量观察窗口
继续执行单步调试,可以看到当按键值key为2的时候,*led的值变为了2。当按键值key为3的时候,*led的值变为了3。这意味着我们已经可以通过按键来控制LED的状态,至此仿真设计完成,通过仿真我们验证了HLS设计的正确性。下面我们将设计打包成IP模块,以供Vivado开发套件中的其他工具(如IP集成器)使用。
在工具栏中点击黄色的“田”字按钮,导出RTL,如下图所示:
![8c4801221bf55f4bb66ab6a0dc096ef7.png](https://i-blog.csdnimg.cn/blog_migrate/175522ef45864cd6bd64e787fc09cd97.png)
图 3.3.14 导出RTL
在弹出的对话框中保持默认设置,直接点击“OK”,如下图所示:
![bea7eff9c3e4cf1fd8c1873203548dd2.png](https://i-blog.csdnimg.cn/blog_migrate/b9fc0a144e0e73a5b68f02c41f2fbb80.png)
图 3.3.15 将设计导出成IP
设计导出完成后,HLS设计部分就结束了,我们在HLS工程目录下可以找到导出的IP核,如下图红色方框所示:
![1a422d3e5a711eed25443dc25b0ccb05.png](https://i-blog.csdnimg.cn/blog_migrate/814b3b50f0453659305f19f6b516935e.jpeg)
图 3.3.16导出得到的IP
我们到计算机工程目录所指向的文件夹中同样可以看到以ZIP压缩文件形式存在的IP核,如下图所示:
![84ef31c351f3785bd0fb10a6d763a2de.png](https://i-blog.csdnimg.cn/blog_migrate/6d74b4b79d2a4e95c021a6bd473d1f44.png)
图 3.3.17 文件夹中的IP核
HLS设计结束之后,我们将在Vivado中对导出的IP核进行验证。
3.4IP验证
在IP验证环节,我们会使用Vivado工具的IP集成器将生成的IP核添加到Block Design中,然后完成设计后将程序下载到领航者开发板上进行验证。
我们在《领航者ZYNQ之嵌入式开发指南》第一章“Hello World实验”中详细介绍了如何使用Vivado创建工程,以及如何使用IP集成器(IP INTEGRATOR)创建Block Design。如果大家对这一部分内容不熟悉的话,一定要先按照《领航者ZYNQ之嵌入式开发指南》中第一章的描述,把这一流程完整的操作一遍,然后才能进行本章的IP验证环节。
在本次实验中,我们创建一个名为“key_led_ip_test”的Vivado工程。为了方便工程管理,我们将Vivado工程的目录与HLS工程目录保持一致,如下图所示:
![97d661aa8f046600e015f588db05d16d.png](https://i-blog.csdnimg.cn/blog_migrate/26bbca2d0bef714ad1480da2caa0249b.png)
图 3.4.1 创建Vivado工程
Vivado工程创建完成之后,本次实验的工程目录如下图所示:
![dc31ee8d424e5d5ad749a02dc7b63f07.png](https://i-blog.csdnimg.cn/blog_migrate/27ca3cef96b19072ef3319382f7a1579.png)
图 3.4.2 按键控制LED实验工程目录
图 3.4.2中,以“_hls”结尾的文件夹为Vivado HLS工程,以“_ip_test”结尾的是用于IP验证的Vivado工程。
工程创建完成后,需要先将HLS设计过程中导出的IP核拷贝到Vivado工程目录下。我们在Vivado工程目录下新建一个名为“ip_repo”的文件夹,然后将图 3.3.17中的压缩包拷贝到该文件夹中并解压,解压完成后如下图所示:
![5fecdaf10d66699489a44bce17d7fd87.png](https://i-blog.csdnimg.cn/blog_migrate/3101a2de902664c2f4f2bceaf17d6ad0.png)
图 3.4.3 拷贝并解压IP
接下来在Vivado中将该IP添加到工程的IP库中。添加IP核完成之后,创建一个名为“system”的Block Design。在设计中添加key_led IP核,最终完成的设计如下图所示:
![36d8e06021aa05fa11a7de8459a7baf6.png](https://i-blog.csdnimg.cn/blog_migrate/2620b46f84f4ee532211e93854bbdad0.png)
图 3.4.4 完成后的Block Design
到这里我们的Block Design就设计完成了,在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
接下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。
然后我们还要为设计创建约束文件navigator.xdc,并在文件中添加以下管脚约束信息:
- set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
- set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
- set_property -dict {PACKAGE_PIN L20 IOSTANDARD LVCMOS33} [get_ports key[0]]
- set_property -dict {PACKAGE_PIN J20 IOSTANDARD LVCMOS33} [get_ports key[1]]
- set_property -dict {PACKAGE_PIN J18 IOSTANDARD LVCMOS33} [get_ports {led[0]}]
- set_property -dict {PACKAGE_PIN H18 IOSTANDARD LVCMOS33} [get_ports {led[1]}]
最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。
3.5下载验证
连接开发板的电源和下载器,并打开电源开关。在工程编译之后,将生成的bit文件下载到开发板中。下载完成之后,底板上两个 PL LED 处于点亮状态。然后按下 PL_KEY0,可以看到PL LED 0熄灭;按下PL_KEY1,可以看到PLLED1熄灭。如下图所示:
![a74fee2cfda92648fbec40538b7b49af.png](https://i-blog.csdnimg.cn/blog_migrate/409ee13498af58bdbf4830f7012d2626.png)
图 3.5.1 按键控制LED实验现象