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)正点原子资料更新和新品发布,请加正点原子公众号:正点原子 关注方法:微信→添加好友→公众号→输入:正点原子
第五章彩条显示实验
AXI4-Stream总线协议由ARM公司提出,该协议专门针对视频、音频、数组等数据在片内通信设计。在本章我们将彩条显示实验,来学习如何使用Vivado HLS工具生成一个带有AXI4-Stream总线接口的IP核,以及在Vivado中对综合结果进行验证的流程。
本章包括以下几个部分:
55.1简介
5.2实验任务
5.3HLS设计
5.4IP验证
5.5下载验证
5.1简介
AXI4-Stream协议一般被翻译为AXI流协议,是AXI总线的一种演化版本。AXI4流协议作为一个标准接口,用于连接进行数据交换的组件。接口可以用来连接一个单一的主机,主机向接收数据的单一从机发送数据,也可用于连接若干个主机和从机的组件。协议支持共用一组信号线的多个数据流,允许构建一个通用互联。相比于AHB/APB,AXI流协议提出了数据包、数据帧以及传输操作等概念,这也是其被称为流(Stream)的原因。
关于AXI Stream的基本概念解释如下:
传输(Transfer):通过 AXI4 流接口进行的一个单一数据传输。一个单一数据传输由TVALID和TREADY握手信号定义。
包(Packet):通过 AXI4 流接口被一起传输的一组字节,包类似于 AXI4 的突发。
帧(Frame):一个 AXI4 流中最高级别的字节编组。一帧可以包含很大数量的字节数,例如,一个完整的视频帧缓存。
数据流(Data Stream):从一个源设备到一个目标设备传输的数据。
两个模块之间进行数据传输,需要事先约定好这两个模块之间的传输协议,这是两个信号握手的概念。TVALID和TREADY信号的握手包含三种情况:TVALID先于 TREADY 的握手、TREADY先于 TVALID 的握手、TVALID 和 TREADY 同时发生的握手。
下图中,主机发出了数据和控制信息并将TVALID 信号置为高。一旦主机驱动了 TVALID ,主机发出的数据或控制信息必须保持不变,直到从机驱动 TREADY 信号为高表示可以接收数据和控制信息。在这种情况下,一旦从机设置 TREADY 为高,传输就会发生。箭头标示出了传输发生的位置。
图 5.1.1 TVALID先于TREADY的握手
下图中,从机在数据和控制信息有效之前驱动TREADY为高。这表示目标设备可以在一个ACLK周期内接收数据和控制信息。在这种情况下,一旦主机驱动 TVALID 为高,则传输就会发生。箭头标示出了传输发生的位置。
图 5.1.2 TREADY先于 TVALID 的握手
下图中,主机驱动TVALID为高,从机在同一时钟(ACLK)周期内也驱动TREADY为高。在这种情况下,如图中箭头标注,传输在同一周期内发生。
图 5.1.3 TVALID 和 TREADY 同时发生的握手
本次实验我们需要使用Vivado HLS工具生成带有AXI4-Stream接口的IP核,并将此IP核的AXI4-Stream接口连接到“AXI4-Stream to Video Out”模块中的AXI4-Stream接口,如下图所示:
图 5.1.4 AXI4-Stream to Video Out模块接口
我们重点关注图中的“s_axis_video_tlast”和“s_axis_video_tuser”信号,其中“s_axis_video_tlast”是AXI4-Stream协议中“TLAST”信号,这个信号设置为高表示一行像素传输结束,“s_axis_video_tuser”是AXI4-Stream协议中的“TUSER”信号,这个信号设置为高表示一帧图像传输开始。时序图如下图所示:
图 5.1.5 行结束和帧开始信号
图中的“EOL”表示“End of line”是行传输结束信号,它在一行图像像素传输结束的时候拉高一个时钟周期;图中的“SOF”表示“Start of frame”是帧传输开始信号。它在一帧图像像素传输开始的时候拉高一个时钟周期。
5.2实验任务
本节的实验任务是使用Vivado HLS设计彩条显示的IP核,并在Vivado中对设计出来的IP核进行验证。
5.3HLS设计
我们在电脑中的“F:ZYNQHigh_Level_Synthesis”目录下新建一个名为lcd_rgb_colorbar的文件夹,作为本次实验的工程目录。然后打开Vivado HLS工具,创建一个新的工程。设置工程名为“lcd_rgb_colorbar”,选择工程路径为刚刚创建的文件夹。需要注意的是,工程名以及路径只能由英文字母、数字和下划线组成,不能包含中文、空格以及其他特殊字符。如下图所示:
图 5.3.1 工程配置界面
设置好工程名及路径之后,点击“Next”,进入如下界面设置顶层函数:
图 5.3.2 设置顶层函数
工程创建完成后,在工程面板中的“source”目录上点击右键,然后在打开的列表中选择“New File”新建源文件,在弹出的对话框中输入源文件的名称“lcd_rgb_colorbar.c”,如图 5.3.3所示。源文件默认的保存路径为HLS工程目录,为方便源文件的管理,我们在工程目录下新建一个名为“src”的文件下,将源文件保存在src目录下。
图 5.3.3 输入源文件名
我们在图 5.3.3中输入的源文件的后缀名为“.c”,即使用C语言进行设计。如果使用C++语言进行设计,那么后缀名需要设置为“.cpp”。设置好文件名和路径之后,点击“保存”。
“lcd_rgb_colorbar.c”文件源代码如下:
- 1 //引入数据类型头文件
- 2 #include "ap_cint.h"
- 3
- 4 //定义axi4-stream类型的结构体
- 5 typedef struct ap_axi_stream{
- 6 uint24 data;
- 7 uint1 user; //帧传输开始信号
- 8 uint1 last; //行传输结束信号
- 9 }axi_stream;
- 10
- 11 //彩条显示顶层函数
- 12 void lcd_rgb_colorbar(axi_stream* color,int rows,int cols)
- 13 {
- 14 //Vivado HLS接口定义
- 15 #pragma HLS INTERFACE s_axilite port=cols
- 16 #pragma HLS INTERFACE s_axilite port=rows
- 17 #pragma HLS INTERFACE axis port=color
- 18 #pragma HLS INTERFACE ap_ctrl_none port=return
- 19
- 20 int x_pos,y_pos;
- 21 int color_edge = cols/7; //显示7个彩条数据
- 22
- 23 for(y_pos=0; y_pos<rows; y_pos++){
- 24 for(x_pos=0; x_pos<cols; x_pos++){
- 25 //帧传输开始拉高一个时钟周期
- 26 if((x_pos==0) && (y_pos==0)){
- 27 (*color).user = 1;
- 28 }else{
- 29 (*color).user = 0;
- 30 }
- 31 //行传输结束拉高一个时钟周期
- 32 if(x_pos == (cols-1)){
- 33 (*color).last = 1;
- 34 }else{
- 35 (*color).last = 0;
- 36 }
- 37 //像素点彩条数据赋值
- 38 if(x_pos < color_edge){
- 39 (*color).data = 0xff0000; //红色
- 40 }else if(x_pos >= color_edge && x_pos < color_edge*2){
- 41 (*color).data = 0xff7f00; //橙色
- 42 }else if(x_pos >= color_edge*2 && x_pos < color_edge*3){
- 43 (*color).data = 0xffff00; //黄色
- 44 }else if(x_pos >= color_edge*3 && x_pos < color_edge*4){
- 45 (*color).data = 0x00ff00; //绿色
- 46 }else if(x_pos >= color_edge*4 && x_pos < color_edge*5){
- 47 (*color).data = 0x00ffff; //青色
- 48 }else if(x_pos >= color_edge*5 && x_pos < color_edge*6){
- 49 (*color).data = 0x0000ff; //蓝色
- 50 }else if(x_pos >= color_edge*6 && x_pos < cols){
- 51 (*color).data = 0x8b00ff; //紫色
- 52 }
- 53 }
- 54 }
- 55 }
在代码的第1行,包含了一个名为“ap_cint.h”的头文件,这个是xilinx提供的库文件,里面包含了本章节所需要用到的数据类型。在代码的第5行到第9行定义了“axi_stream”类型的结构体,结构体中包含了3个成员变量,分别是data、user和last。其中“data”信号是像素传输的数据,“last”信号是行像素传输结束标志信号,“user”信号是帧像素传输开始标志信号。我们这里将data置为24位,是因为采用RGB888格式的数据进行传输,数据位宽为24。
在代码的第12行,定义了顶层函数“lcd_color_bar”,这里我们传入axi_stream结构体类型的指针是因为vivodo HLS规定如果用户想要生成带有AXI4-Stream接口的IP核,只能传入指针或者数组的类型作为输出接口。“rows”表示图像行数,“cols”表示图像列数。这里我们可以通过ZYNQ的PS端来配置这些参数,从而兼容不同分辨率的LCD屏幕。
在代码的第15行到第17行,“axis”指定了颜色数据传输采用AXI4-Stream总线协议,“s_axilite”指定了行和列控制信号传输采用AXI4-Lite总线协议。在代码的第18行“ap_ctrl_none”表明没有添加包级别的协议,而是完全在端口接口级别用端口级别协议来做控制。在代码的第21行,定义了一个变量“color_edge”,
通过对比像素点位置“x_pos”值与“color_edge”值大小来显示7个彩条数据。
代码的第26行到第30行是一个条件判断语句,通过判断像素点的位置来设置“user”和“last”信号的值。(“x_pos==0”&&“y_pos==0”)表示一帧图像数据开始传输,此时将user信号置高一个时钟周期,在其它情况下将user信号置低。代码的第32行到第36行,“x_pos == (cols – 1)”表示一行像素传输到最后一个位置,这里我们将cols减一是因为cols是从零开始计数。当一行数据传输结束的时候将last信号置高一个时钟周期,在其它情况下将last信号置低。
在代码的第38行到第51行,通过比较“x_pos”和“color_edge”的大小,来对像素点的颜色数据进行赋值。其中“0xff0000”表示RGB888格式的数据高8位为1,从而像素点显示红色。
代码输入完成后,按快捷键Ctrl+S保存。然后点击工具栏中向右的绿色三角形对C代码进行综合,如下图所示:
图 5.3.4 运行C综合
综合完成后,会自动打开综合结果(solution)的报告,如下图所示:
图 5.3.5 综合报告
图中所示的综合报告中,给出了设计的性能评估、资源评估以及接口等信息。本次实验中,我们重点关注综合工具为我们生成的接口信息,如下图所示:
图 5.3.6 接口信息
图中Protocol一栏,“s_axi”和“axis”分别表示Vivado HLS生成了一个带有“AXI4-Lite”从接口和“AXI4-Stream”总线接口的IP核。其中“AX4-Lite”总线接口用于控制彩条显示的分辨率,“AXI4-Stream”总线接口用于传输彩条数据。
在工具栏中点击黄色的“田”字按钮,导出RTL,如下图所示:
图 5.3.7 导出RTL
在弹出的对话框中保持默认设置,直接点击“OK”,如下图所示:
图 5.3.8 将设计导出成IP
设计导出完成后,HLS设计部分就结束了,我们在HLS工程目录下可以找到导出的IP核,如下图红色方框所示:
图 5.3.9 导出得到的IP
我们到计算机工程目录所指向的文件夹中同样可以看到以ZIP压缩文件形式存在的IP核,如下图所示:
图 5.3.10 文件夹中的IP核
HLS设计结束之后,我们将在Vivado中对导出的IP核进行验证。
5.4IP验证
在IP验证环节,我们会使用Vivado工具的IP集成器将生成的IP核添加到Block Design中,然后完成设计后将程序下载到领航者开发板上进行验证。
我们在《领航者ZYNQ之嵌入式开发指南》第十八章“PS通过VDMA驱动LCD显示实验”中已经实现了彩条数据的显示。本次实验可以在这个实验的基础上进行,将《领航者ZYNQ之嵌入式开发指南》中的第18个实验“_vdma_lcd”的Vivado工程重新另存为“lcd_rgb_colorbar_ip_test”的Vivado工程。为了方便工程管理,我们将Vivado工程的目录与HLS工程目录保持一致,如下图所示:
图 5.4.1 创建Vivado工程
Vivado工程创建完成之后,本次实验的工程目录如下图所示:
图 5.4.2 按键控制LED实验工程目录
工程创建完成后,需要先将HLS设计过程中导出的IP核和本次工程所需要的IP核拷贝到Vivado工程目录下。我们在Vivado工程目录下新建一个名为“ip_repo”的文件夹,然后将图1.3.18中的压缩包拷贝到该文件夹中并解压,解压完成后如下图所示:
图 5.4.3 拷贝并解压IP
由于本次实验不需要用到VDMA,所以删除下图所示的IP核。
图 5.4.4 VDMA IP核
将Vivado HLS生成的彩条显示IP核添加到原理图设计中并连线,如下图所示:
图 5.4.5 连线图
然后点击“Run Connnection Automation”,下面列出了会自动连接的模块及其接口,勾选“AllAutomation”, 然后点击“OK”按钮。最终完成的设计如下图所示:
图 5.4.6 完成后的Block Design
到这里我们的Block Design就设计完成了,在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
接下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。
最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。
在生成 Bitstream 之后,在菜单栏中选择 File > Export > Export hardware 导出硬件,并在弹出的对话框 中,勾选“Include bitstream”。然后在菜单栏选择 File > Launch SDK,启动 SDK 软件。
将“main.c”的代码修改为如下所示:
- 1 #include <stdio.h>
- 2 #include <stdlib.h>
- 3 #include <string.h>
- 4 #include "xil_types.h"
- 5 #include "xil_cache.h"
- 6 #include "xparameters.h"
- 7 #include "xgpio.h"
- 8 #include "display_ctrl/display_ctrl.h"
- 9 #include "xlcd_rgb_colorbar.h"
- 10
- 11 //宏定义
- 12 #define DYNCLK_BASEADDR XPAR_AXI_DYNCLK_0_BASEADDR //动态时钟基地址
- 13 #define DISP_VTC_ID XPAR_VTC_0_DEVICE_ID //VTC器件ID
- 14 #define AXI_GPIO_0_ID XPAR_AXI_GPIO_0_DEVICE_ID //PL端 AXI GPIO 0(lcd_id)器件ID
- 15 #define AXI_GPIO_0_CHANEL 1 //PL按键使用AXI GPIO(lcd_id)通道1
- 16
- 17 //全局变量
- 18 DisplayCtrl dispCtrl;
- 19 XGpio axi_gpio_inst; //PL端 AXI GPIO 驱动实例
- 20 VideoMode vd_mode;
- 21 unsigned int lcd_id=0; //LCD ID
- 22
- 23 int main(void)
- 24 {
- 25 XLcd_rgb_colorbar colorbarInstance;
- 26 //获取LCD的ID
- 27 XGpio_Initialize(&axi_gpio_inst,AXI_GPIO_0_ID);
- 28 lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL);
- 29 xil_printf("LCD ID: %xrn",lcd_id);
- 30
- 31 //根据获取的LCD的ID号来进行video参数的选择
- 32 switch(lcd_id){
- 33 case 0x4342 : vd_mode = VMODE_480x272; break; //4.3寸屏,480*272分辨率
- 34 case 0x4384 : vd_mode = VMODE_800x480; break; //4.3寸屏,800*480分辨率
- 35 case 0x7084 : vd_mode = VMODE_800x480; break; //7寸屏,800*480分辨率
- 36 case 0x7016 : vd_mode = VMODE_1024x600; break; //7寸屏,1024*600分辨率
- 37 case 0x1018 : vd_mode = VMODE_1280x800; break; //10.1寸屏,1280*800分辨率
- 38 default : vd_mode = VMODE_800x480; break;
- 39 }
- 40
- 41 XLcd_rgb_colorbar_Initialize(&colorbarInstance, XPAR_LCD_RGB_COLORBAR_0_DEVICE_ID);
- 42
- 43 //初始化Display controller
- 44 DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);
- 45 //设置VideoMode
- 46 DisplaySetMode(&dispCtrl, &vd_mode);
- 47 DisplayStart(&dispCtrl);
- 48
- 49 while(1)
- 50 {
- 51 XLcd_rgb_colorbar_Set_cols(&colorbarInstance, vd_mode.width);
- 52 XLcd_rgb_colorbar_Set_rows(&colorbarInstance, vd_mode.height);
- 53 sleep(20);
- 54 }
- 55 return 0;
- 56 }
在代码的第9行引入了“xlcd_rgb_color.h”头文件,这个头文件是Vivado HLS工具生成的,里面声明了彩条显示IP核的驱动函数。其路径如下图所示:
在代码的第41行通过“XLcd_rgb_colorbar_Initialize”函数来初始化Vivado HLS生成的IP核,这个函数在“xlcd_rgb_color.h”头文件中声明。在代码的第51行通过传入“vd_mode.width”形参来设置彩条显示的列数,在代码的第52行通过传入“vd_mode.height”形参来设置彩条显示的列数。
5.5下载验证
首先我们将下载器与领航者底板上的 JTAG 接口连接,下载器另外一端与电脑连接。接下来使用 FPC 排线一端与 RGB LCD 液晶屏上的 接口连接,另一端连接领航者底板上的 RGB LCD 接口,如下图所示。连接时,先掀开 FPC 连接器上的黑色翻盖,将 FPC 排线蓝色面朝上插入连接器,最后将黑色盖压下以固定 FPC 排线。
图 5.5.1 RGB LCD液晶屏
图 5.5.2 开发板连接图
在 SDK 软件下方的 SDK Terminal 窗口中点击右上角的加号设置并连接串口。然后下载本次实验硬件 设计过程中所生成的 BIT 文件,来对 PL 进行配置。最后下载软件程序,下载完成后,下载完成后,在下方 的 SDK Terminal 中可以看到应用程序打印的信息,如下图所示:
图 5.5.3 Terimnal 打印的信息
由于本次实验连接的是 7 寸液晶屏(分辨率:800*480),因此串口打印的 LCD ID 为 7084。接下来观察RGB LCD液晶屏,可以看到,LCD 上显示出彩条,彩条颜色从左到右依次为红橙黄绿青蓝紫,与写入的彩虹色数据相同,如下图所示。
图 5.5.4 RGB LCD液晶屏显示彩条