一、声明
学门技术,最常见的就是点灯了,HLS也不例外。这里把LED的闪烁、流水、按键控制和呼吸模式都弄了。第一次搞,工程就按四个的搞吧,后面如果还有这种多模块的,就用setting方式。
二、闪烁灯
1、led_twinkle.h文件。
#ifndef __LED_TWINKLE_H__
#define __LED_TWINKLE_H__
// HLS提供的任意精度定点数文件
#include "ap_fixed.h"
#define DELAY 25000000
void led_twinkle(ap_int<3> *led);
#endif
2、led_twinkle.cpp文件。
#include "led_twinkle.h"
void led_twinkle(ap_int<3> *led){
int i = 0;
for(i = 0; i < DELAY; i++){
if(i < DELAY/2) *led = 1;
else *led = 6;
}
}
功能简单,不适用main.cpp做C simulation了。
3、接口更改。
不进行优化时,综合出来的接口中,clk和rst是必须的,因为使用了同步设计(for中计数值到,才同步触发)。模块的接口使用了ap_ctrl_hs控制握手协议,但为了简单,可以不用到这么多信号,改为ap_ctrl_none,即不需要额外的控制接口。
除了模块级的接口,还有输入输出端口,输出端口为led_V,使用了ap_vld的这种有效信号协议,但led功能简单,也是不需要的,直接改为ap_none。
#pragma HLS INTERFACE ap_none port=led
#pragma HLS INTERFACE ap_ctrl_none port=return
4、实验平台搭建测试。
实验管脚约束配置。
set_property PACKAGE_PIN N18 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]
set_property PACKAGE_PIN T19 [get_ports sys_rst_n[0]]
set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n[0]]
set_property PACKAGE_PIN G20 [get_ports led[0]]
set_property IOSTANDARD LVCMOS33 [get_ports led[0]]
set_property PACKAGE_PIN G18 [get_ports led[1]]
set_property IOSTANDARD LVCMOS33 [get_ports led[1]]
set_property PACKAGE_PIN J20 [get_ports led[2]]
set_property IOSTANDARD LVCMOS33 [get_ports led[2]]
出现下面这个错误,是因为复位的管脚约束弄到PS端的复位上了,这个是板子设计时用来refresh板子的,不能复用,从C8换到T19就没这错误了。但板卡不知是不是LED坏了,只好从正点的板卡上入手验证。后续打算涉及总线的,就到Zynq上验证,不涉及总线的逻辑开发,全到Artix7上验证。
[DRC UCIO-1]无约束逻辑端口:4个逻辑端口中有1个没有用户指定的特定位置约束(LOC)。这可能会导致I/O争用或与板的电源或连接不兼容,影响性能,信号完整性,在极端情况下会导致设备或其连接的组件损坏。要纠正这个错误,请指定所有的pin位置。这种设计将无法生成位流,除非所有逻辑端口都定义了用户指定的站点LOC约束。要允许使用未指定pin位置的比特流创建(不推荐),使用这个命令:set_property SEVERITY {Warning} [get_drc_checks UCIO-1]。注意:当使用Vivado运行基础设施(例如launch_runs Tcl命令)时,将这个命令添加到。Tcl文件中,并将该文件添加为实现运行的write_bitstream步骤的前钩子。问题端口:sys_rst_n[0]。
5、实验结果。果然,换了板卡直接就能用了,Zynq上半天没反应。。醉了。
吐槽,周末这两天没来实验室,或者来了没怎么搞板子,今晚实验,就不知咋出了问题,就很无语。还是得天天来啊!
三、流水灯
1、led_shift.h文件。
#ifndef _LED_SHIFT_H_
#define _LED_SHIFT_H_
#include "ap_fixed.h"
#define DELAY 100000000
#define SHIFT_FLAG DELAY-2
typedef ap_int<32> cnt32;
void led_shift(ap_int<4> *led);
#endif
2、led_shift.cpp文件。
#include "led_shift.h"
void led_shift(ap_int<4> *led)
{
#pragma HLS INTERFACE ap_none port=led
#pragma HLS INTERFACE ap_ctrl_none port=return
cnt32 i;
ap_int<4> temp;
for(i = 0;i < DELAY; i++)
{
if(i < DELAY/4) *led = 1;
else if((i > DELAY/4) && (i < DELAY/2)) *led = 2;
else if((i > DELAY/2) && (i < (DELAY*3)/4)) *led = 4;
else *led = 8;
}
}
3、实验平台搭建测试。
管脚约束配置。
#时钟周期约束
create_clock -period 20.000 -name clk [get_ports sys_clk]
#IO管脚约束
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS15} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U7 IOSTANDARD LVCMOS15} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN V9 IOSTANDARD LVCMOS15} [get_ports {led[0]}]
set_property -dict {PACKAGE_PIN Y8 IOSTANDARD LVCMOS15} [get_ports {led[1]}]
set_property -dict {PACKAGE_PIN Y7 IOSTANDARD LVCMOS15} [get_ports {led[2]}]
set_property -dict {PACKAGE_PIN W7 IOSTANDARD LVCMOS15} [get_ports {led[3]}]
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.COMPRESS true [current_design]
set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design]
set_property BITSTREAM.CONFIG.SPI_FALL_EDGE Yes [current_design]
综合结果。
4、实现结果。
四、键控灯
1、led_button.h文件。
#include "ap_fixed.h"
//#include <ap_cint.h>
#ifndef _KEY_LED_H_
#define _KEY_LED_H_
void led_button(ap_uint<2> key, ap_uint<2> * led);
#endif
2、led_button.cpp文件。
#include "led_button.h"
void led_button(ap_uint<2> key, ap_uint<2> * led)
{
#pragma HLS INTERFACE ap_none port=led
#pragma HLS INTERFACE ap_none port=key
#pragma HLS INTERFACE ap_ctrl_none port=return
// 定义了一个key_value变量来寄存按键的值
ap_uint<2> key_value = key;
/*
if(key_value == 3 ) *led = 3;
if(key_value == 2 ) *led = 2;
if(key_value == 1 ) *led = 1;
if(key_value == 0 ) *led = 0;*/
switch(key_value)
{
// no key
case 3: *led = 3;
break;
// key1
case 2: *led = 2;
break;
// key2
case 1: *led = 1;
break;
// all key
default: *led = 0;
break;
}
}
3、main.cpp文件。
#include "led_button.h"
int main()
{
ap_uint<2> key,led;
led_button(0,&led);
led_button(1,&led);
led_button(2,&led);
led_button(3,&led);
return 0;
}
4、一些说明。
1°为提高效率,使用测试平台验证C函数功能上是否正确。
2°按键key的C语言类型是变量,在RTL级别中被映射为输入端口。 led的C语言类型是指针,在RTL级别中被映射为输出端口。
3°参数类型支持的接口协议说明。
D表示默认,即HLS默认综合出来的接口。S表示HLS工具支持综合出来的接口。比如使用ap_none时,变量为形参,则接口只能综合成输入而不能是输出,当指针作形参时,接口可综合成输入、输出和双向端口。
5、C仿真调试说明。
在test bench中写好测试的top后,点击Run C Simulation,在弹出的配置界面中启动 C 调试器,并清除编译残留文件。Ok后就会仿真启动调试器,选择调试的方式,可以看到寄存器的变化。
6、一个很重要的踩坑点。
之前一直使用ap_int<2>的方式来定义变量的类型,但第一次综合生成RTL导入板卡后,却无法显示正常,学习了软件调试后,开始用C仿看变量数据,但实验中发现用ap_int<2>定义的数据,会出现负数的情况,于是改用uint2的方式定义变量,关于负数这个问题就解决了,可还是出现了一个新的问题,就是包含文件<ap_cint.h>的时候没有问题,但综合时没找到,并且提示下图这个错误。看来还是得包含ap_fixed.h文件,并把数据类型改为了ap_uint<2>,同时使用C仿真软件调试的方式,确定了没有负数产生,才进行综合并生成RTL。上面贴的代码是最终版的,但中途还是有一些测试过程的。
7、实验测试平台。
管脚约束文件。
#时钟周期约束
create_clock -period 20.000 -name clk [get_ports sys_clk]
#IO管脚约束
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS15} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U7 IOSTANDARD LVCMOS15} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN T4 IOSTANDARD LVCMOS15} [get_ports key[0]]
set_property -dict {PACKAGE_PIN T3 IOSTANDARD LVCMOS15} [get_ports key[1]]
set_property -dict {PACKAGE_PIN V9 IOSTANDARD LVCMOS15} [get_ports led[0]]
set_property -dict {PACKAGE_PIN Y8 IOSTANDARD LVCMOS15} [get_ports led[1]]
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.COMPRESS true [current_design]
set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design]
set_property BITSTREAM.CONFIG.SPI_FALL_EDGE Yes [current_design]
8、实验结果。
五、呼吸灯
1、基本原理。
PWM调制:在固定频率下,调整占空比,以实现LED灯亮度的变化。IF占空比为0,则LED不亮,反之最亮。如果将占空比从0%变到100%,从100%变到0%,就可以实现呼吸的效果。下图中,LED高电平的时间由长渐渐变短,再由短渐渐变长,如果LED灯是高电平点亮,则LED灯会呈现出亮度由亮到暗,再由暗到亮的过程。
2、实验内容。
实现一个频率可调,开关可控的呼吸灯,并需要通过一个接口来对频率与开关进行配置(*使用AXI_Lite)。重温下,AXI_Lite为单个外设数据传输,来访问一些低俗外设中的寄存器。AXI_Stream则如FIFO,数据传输不用地址,主从设备之间可以连续读写数据,常用于视频、高速AD、PCLe和DMA等高速数据传输场合。
3、led_breathe.h文件。
#include "ap_fixed.h"
#ifndef _BREATH_LED_H
#define _BREATH_LED_H_
void led_breathe(ap_uint<32> sw_ctrl, ap_uint<32> freq_step, ap_uint<1> * led);
#endif
4、led_breathe.cpp文件。
#include "led_breathe.h"
void led_breathe(ap_uint<32> sw_ctrl, ap_uint<32> freq_step, ap_uint<1>* led)
{
#pragma HLS INTERFACE ap_none port=led
// 频率和开关,使用slave axi接口进行设置
#pragma HLS INTERFACE s_axilite port=freq_step
#pragma HLS INTERFACE s_axilite port=sw_ctrl
// 将模块的start与done本来也可以由s_axi来控制的
// 但这里让模块一直工作,s_axilite接口就可以不用了,直接ap_ctrl_none
#pragma HLS INTERFACE ap_ctrl_none port=return
ap_uint<32> duty_cycle, period_cnt; // 占空比循环变化,周期循环变化变量
// 按键没按下时,是开的,按键按下后,关闭模块,led输出为0,0是关闭
if(sw_ctrl == 1){
// freq_step是外部设置的一个32位的值
// 先假设freq_step是个固定的值,这样就看占空比
// 不断增加每个 PWM 周期中的占空比,来实现呼吸灯从暗变亮
// 若freq_step是变化的,则可以控制呼吸灯占空比变化的“步长”,步长越大,占空比变化的越快
// 那么呼吸灯由暗到亮的时间越短,即呼吸频率变快
for(duty_cycle=0; duty_cycle<50000; duty_cycle=duty_cycle+freq_step){
for(period_cnt=0; period_cnt<50000; period_cnt++)
*led = (period_cnt <= duty_cycle) ? 1 : 0 ;
}
for(duty_cycle=50000; duty_cycle>0; duty_cycle=duty_cycle-freq_step){
for(period_cnt=0; period_cnt<50000; period_cnt++)
*led = (period_cnt <= duty_cycle) ? 1 : 0;
}
}else *led = 0;
}
5、main.cpp文件。
#include "led_breathe.h"
int main(void)
{
ap_uint<1> led;
// 开关打开,设置步长2000来验证呼吸灯功能。
led_breathe(1, 2000, &led); //打开呼吸灯,频率步长设置为 2000
led_breathe(0, 2000, &led); //关闭呼吸灯
printf("test passed!\n");
return 0;
}
6、C联合RTL仿真波形查看。
在模块完整工作一次期间,即done信号产生前,生成 PWM 波的占空比逐渐增大后又逐渐减小, 说明呼吸灯由暗到亮后又由亮变暗。功能是正确的。
7、实验平台搭建。
管脚约束。
set_property -dict {PACKAGE_PIN G20 IOSTANDARD LVCMOS33} [get_ports led]
消耗资源。
8、SDK开发。
#include "xled_breathe.h"
#include "sleep.h"
int main()
{
XLed_breathe led;
XLed_breathe_Initialize(&led, XPAR_LED_BREATHE_0_DEVICE_ID);
while(1)
{
XLed_breathe_Set_freq_step_V(&led,40);
XLed_breathe_Set_sw_ctrl_V(&led,1);
sleep(5);
XLed_breathe_Set_sw_ctrl_V(&led,0);
sleep(2);
XLed_breathe_Set_freq_step_V(&led,200);
XLed_breathe_Set_sw_ctrl_V(&led,1);
sleep(5);
}
return 0;
}
9、实验结果。
Note:Vivado HLS工具不支持“ ap_ctrl_none”包级别协议的仿真, 所以我们在仿真前先删除所有的优化指令。
六、时间线
一些关键时间点的记录:
2021年04月18日晚上:闪烁灯的实验,并解决了一些问题。
2021年04月19日早上:流水灯、键控灯的实验,同时使用了下纯C语言的仿真调试。
2021年04月19日中下午:呼吸灯的实验,搭建了下平台。
后续可能做的工作:暂无。