很多人都比较反感用C/C++开发(HLS)FPGA,大家第一拒绝的理由就是耗费资源太多。但是HLS也有自己的优点,除了快速构建算法外,还有一个就是接口的生成,尤其对于AXI类接口,按照标准语法就可以很方便地生成相关接口。
那么有没有能利用HLS的优点,又囊括HDL的优点的方法呢?今天就来介绍一种在HLS中插入HDL代码的方式,结合两者的优势为FPGA开发打造一把“利剑”。
说明
接下来,将介绍如何创建 Vitis-HLS 项目并将其与自定义 Verilog 模块集成一起。
将插入两个黑盒函数 - 第一个在流水线区域(线路接口,ap_none),第二个在数据流区域(FIFO 接口,ap_ctrl_chain)。
步骤
1. 创建C/C++源文件(基于C的HLS模型+Testbench)
创建模块的 C/C++ 模型,其中包括函数源代码(模块预期行为)和测试平台(io 刺激和结果检查)。
根据ug1399-vitis-hls rtl黑盒,rtl黑盒受到几个因素的限制:
应该是Verilog(.v)代码。
必须有一个 CE 信号,用于启用或停止 RTL IP。
可以使用 ap_ctrl_chain 或 ap_ctrl_none 块级控制协议。
仅支持 C++。
无法连接到顶层接口 I/O 信号。
不能直接作为被测设计(DUT)。
不支持结构或类类型接口。
main.cpp ——C/C++ 测试台。
#include"add.hpp" intmain(void){ staticuint32_ta[1024]; staticuint32_tb[1024]; staticuint32_tc[1024]; staticuint32_tc_stream[1024]; for(uint32_ti=0;i< 1024; ++i) { a[i] = i; b[i] = i; c[i] = 0; c_stream[i] = 0; } top_module(a, b, c, c_stream); for (uint32_t i = 0; i < 1024; ++i) { if (c[i] != a[i] + b[i]) { printf("Data does not match. %d vs %d ", c[i], a[i] + b[i]); return -1; } if (c[i] != c_stream[i]) { printf("Data does not match. %d vs %d ", c[i], c_stream[i]); printf("Add modules have different results. "); return -2; } } printf("Test succesfull. "); return 0; }
add.hpp——函数声明。
#ifndefADD_HPP #defineADD_HPP #include#include voidadd(uint32_ta,uint32_tb,uint32_t&c); voidadd_stream( hls::stream &a, hls::stream &b, hls::stream &c ); voidscalar_to_stream(uint32_ta,hls::stream &a_stream); voidstream_to_scalar(hls::stream &a_stream,uint32_t&a); voidwrap(uint32_ta,uint32_tb,uint32_t&c); voidtop_module(uint32_t*a,uint32_t*b,uint32_t*c,uint32_t*c_stream); #endif
add.cpp——函数源代码。
#include"add.hpp" voidadd(uint32_ta,uint32_tb,uint32_t&c){ c=a+b; }; voidadd_stream( hls::stream&a, hls::stream &b, hls::stream &c ){ c.write(a.read()+b.read()); }; voidscalar_to_stream(uint32_ta,hls::stream &a_stream){ a_stream.write(a); }; voidstream_to_scalar(hls::stream &a_stream,uint32_t&a){ a=a_stream.read(); }; voidwrap(uint32_ta,uint32_tb,uint32_t&c){ #pragmaHLSDATAFLOW hls::stream c_s; hls::stream a_s; hls::stream b_s; scalar_to_stream(a,a_s); scalar_to_stream(b,b_s); add_stream(a_s,b_s,c_s); stream_to_scalar(c_s,c); }; voidtop_module(uint32_t*a,uint32_t*b,uint32_t*c,uint32_t*c_stream){ #pragmaHLSINTERFACEmode=m_axiport=adepth=1024bundle=first #pragmaHLSINTERFACEmode=m_axiport=bdepth=1024bundle=second #pragmaHLSINTERFACEmode=m_axiport=cdepth=1024bundle=first #pragmaHLSINTERFACEmode=m_axiport=c_streamdepth=1024bundle=second #pragmaHLSINTERFACEmode=s_axiliteport=return main_loop_pipeline:for(uint32_ti=0;i< 1024; ++i) { uint32_t c_o; uint32_t const a_t = a[i]; uint32_t const b_t = b[i]; add(a_t, b_t, c_o); c[i] = c_o; } main_loop_stream: for (uint32_t i = 0; i < 1024; ++i) { wrap(a[i], b[i], c_stream[i]); } };
2. 为 Vitis HLS创建配置文件
Vitis HLS需要配置文件来构建项目。基本配置文件应包含
Part——FPGA 部件编号。
syn.top——顶级函数名称。
tb.file——测试台文件。
syn.file — HLS 中使用的文件。
在此示例中,cfg 文件的最小版本如下所示:
part=xc7z007sclg225-1 [hls] syn.top=top_module tb.file=main.cpp syn.file=add.cpp syn.file=add.hpp package.output.format=ip_catalog flow_target=vivado
3. 创建并构建最小项目
启动 Vitis,选择工作区并点击“创建 HLS 组件”。
更改组件位置和名称,单击下一步。
选择从现有配置文件创建,点击下一步。
项目结构如下所示:
无需添加额外的标志,只需仔细检查顶部函数是否是“top_module”,然后单击下一步。
选择芯片(默认部分应该是cfg文件中写的),单击下一步
确认flow_target和package.output.format,点击next。
检查摘要并单击完成。
最后运行所有步骤以确保所有配置均已配置并正常运行。
4.创建blackbox函数json
在此步骤中,我们将用 blackbox verilog 代码替换我们的添加函数。在pipeline区域:
右键单击 hls_component 并单击“创建 RTL blackbox”,将生成 JSON 文件,描述 verilog 模块与其 C 函数之间的连接。
选择包含 C 模块描述的文件。
选择端口方向并填写RTL组配置(verilog模块中的端口名称)。
选择verilog文件,如有必要再填写其他框,单击下一步。
删除 ap_ctrl_chain_protocol 字符串,保留空白。单击完成。
对 add_stream 函数重复所有这些步骤。
输入先进先出:
输出先进先出:
概括:
不要修改 ap_ctrl_chain 信号,因为该模块将使用 ap_ctrl_chain 协议。
此后,hls_component 文件夹中应该会生成两个 json 文件。
add.json
{ "c_files":[ { "c_file":"add.cpp", "cflag":"" } ], "c_function_name":"add", "rtl_files":[ "add.v" ], "c_parameters":[ { "c_name":"a", "c_port_direction":"in", "rtl_ports":{ "data_read_in":"a" } }, { "c_name":"b", "c_port_direction":"in", "rtl_ports":{ "data_read_in":"b" } }, { "c_name":"c", "c_port_direction":"out", "rtl_ports":{ "data_write_out":"c", "data_write_valid":"c_vld" } } ], "rtl_top_module_name":"add", "rtl_performance":{ "II":"0", "latency":"0" }, "rtl_resource_usage":{ "BRAM":"0", "DSP":"0", "FF":"0", "LUT":"0", "URAM":"0" }, "rtl_common_signal":{ "module_clock":"ap_clk", "module_reset":"ap_rst", "module_clock_enable":"ap_ce", "ap_ctrl_chain_protocol_idle":"", "ap_ctrl_chain_protocol_start":"", "ap_ctrl_chain_protocol_ready":"", "ap_ctrl_chain_protocol_done":"", "ap_ctrl_chain_protocol_continue":"" } }
add_stream.json
{ "c_files":[ { "c_file":"add.cpp", "cflag":"" } ], "c_function_name":"add_stream", "rtl_files":[ "add_stream.v" ], "c_parameters":[ { "c_name":"a", "c_port_direction":"in", "rtl_ports":{ "FIFO_empty_flag":"a_empty_flag", "FIFO_read_enable":"a_read_enable", "FIFO_data_read_in":"a" } }, { "c_name":"b", "c_port_direction":"in", "rtl_ports":{ "FIFO_empty_flag":"b_empty_flag", "FIFO_read_enable":"b_read_enable", "FIFO_data_read_in":"b" } }, { "c_name":"c", "c_port_direction":"out", "rtl_ports":{ "FIFO_full_flag":"c_full_flag", "FIFO_write_enable":"c_write_enable", "FIFO_data_write_out":"c" } } ], "rtl_top_module_name":"add_stream", "rtl_performance":{ "II":"0", "latency":"0" }, "rtl_resource_usage":{ "BRAM":"0", "DSP":"0", "FF":"0", "LUT":"0", "URAM":"0" }, "rtl_common_signal":{ "module_clock":"ap_clk", "module_reset":"ap_rst", "module_clock_enable":"ap_ce", "ap_ctrl_chain_protocol_idle":"ap_idle", "ap_ctrl_chain_protocol_start":"ap_start", "ap_ctrl_chain_protocol_ready":"ap_ready", "ap_ctrl_chain_protocol_done":"ap_done", "ap_ctrl_chain_protocol_continue":"ap_continue" } }
主文件夹应与此类似:
hls_config.cfg 文件应该添加两新行( syn.blackbox.file)
part=xc7z007sclg225-1 [hls] flow_target=vivado csim.code_analyzer=0 syn.top=top_module syn.blackbox.file=add.json syn.blackbox.file=add_stream.json tb.file=main.cpp syn.file=add.cpp syn.file=add.hpp
5.创建Verilog黑盒函数
函数“add”必须具有ap_none接口,并且 ap_none 作为模块接口。(有关模块接口的更多信息,请查看https://docs.amd.com/r/en-US/ug1399-vitis-hls/JSON-File-for-RTL-Blackbox 。)
根据UG1399,端口a和b是32位宽度的输入端口,输出c端口也是32位宽度,但带有额外的有效信号,我们称之为c_vld。模块还需要ap_clk,ap_ce,ap_rst端口。
Verilog 如下所示:
add.v
`timescale1ns/1ps moduleadd( input[31:0]a, input[31:0]b, output[31:0]c, outputc_vld, inputap_ce, inputap_rst, inputap_clk ); reg[31:0]c_d; regc_vld_d; assignc=c_d; assignc_vld=c_vld_d; always@(posedgeap_clk)begin if(ap_rst==1'b1)begin c_d<= 32'b0; c_vld_d <= 1'b0; end else begin c_d <= (a + b) & {32{ap_ce}}; c_vld_d <= ap_ce; end end endmodule
运行 C 综合和 C/RTL 协同仿真。能够在 HLS 模块中看到打包的 add.v 文件。
单击 hls_config.cfg 文件,在 Vitis GUI 的帮助下将 cosim.trace_level 更改为全部并运行联合仿真。
单击波形查看器。Vivado 会弹出 XSIM。
将 grp_add_fu_134 信号添加到 wcfg
函数行为很奇怪,接下来在 json 中更改黑盒函数 II,看看它如何影响仿真。打开 add.json 并将 II 更改为 10。再次运行 C 综合并重新运行 C/RTL 协同仿真。
add.v 模块是否良好且可以正常工作?其行为是否正确?模块是否正常工作由哪些因素决定?“fixing”模块对资源使用有何影响?
那么 add_stream 呢?函数位于数据流区域,并且必须包含 fifo 端口和 ap_ctrl_chain 协议。
add_stream.v
`timescale1ns/1ps moduleadd_stream( input[31:0]a, inputa_empty_flag, outputa_read_enable, input[31:0]b, inputb_empty_flag, outputb_read_enable, output[31:0]c, inputc_full_flag, outputc_write_enable, outputap_idle, inputap_start, outputap_ready, outputap_done, inputap_continue, inputap_ce, inputap_rst, inputap_clk ); rega_read_enable_d; regb_read_enable_d; regc_write_enable_d; reg[31:0]c_d; assigna_read_enable=a_read_enable_d; assignb_read_enable=b_read_enable_d; assignc_write_enable=c_write_enable_d; assignc=c_d; assignap_idle=!ap_start; assignap_ready=ap_start; assignap_done=ap_start; //Flagsarenegated... assignflags_good=a_empty_flag&&b_empty_flag&&c_full_flag; assignhs_good=ap_start&&ap_continue; always@(posedgeap_clk)begin if(ap_rst==1'b1)begin a_read_enable_d<= 0; b_read_enable_d <= 0; c_write_enable_d <= 0; c_d <= 0; end else if (ap_ce == 1'b1) begin a_read_enable_d <= flags_good && hs_good; b_read_enable_d <= flags_good && hs_good; c_write_enable_d <= flags_good && hs_good; c_d <= a + b; end end endmodule
看起来放置在数据流区域的模块工作正常:
打开 add_stream.json 并将延迟更改为 10。再次运行 C 综合并重新运行 C/RTL 协同仿真。这会影响仿真吗?
-
FPGA
+关注
关注
1620文章
21494浏览量
598396 -
函数
+关注
关注
3文章
4232浏览量
61930 -
代码
+关注
关注
30文章
4656浏览量
67698 -
HLS
+关注
关注
1文章
128浏览量
23898
原文标题:在HLS中插入HDL代码
文章出处:【微信号:Open_FPGA,微信公众号:OpenFPGA】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
编程是一种思维方式,而代码是一种表现形式,硬件只不过是对思维方式的物理体现
Vivado中进行HDL代码设计
【正点原子FPGA连载】第一章HLS简介-领航者ZYNQ之HLS 开发指南
一种基于信号延迟的光网络攻击方式
![<b class='flag-5'>一种</b>基于信号延迟的光网络攻击<b class='flag-5'>方式</b>](https://file.elecfans.com/web2/M00/49/53/pYYBAGKhtEaAVUSnAAAOIu6L2Qw201.jpg)
使用教程分享:在Zynq AP SoC设计中高效使用HLS IP(一)
![使用教程分享:<b class='flag-5'>在</b>Zynq AP SoC设计中高效使用<b class='flag-5'>HLS</b> IP(<b class='flag-5'>一</b>)](https://file1.elecfans.com//web2/M00/A6/A5/wKgZomUMP2GAfzGJAAAxmJ2OQAk438.png)
并行CRC电路HDL代码的快速生成
![并行CRC电路<b class='flag-5'>HDL</b><b class='flag-5'>代码</b>的快速生成](https://file.elecfans.com/web1/M00/E7/9F/pIYBAGBf3J2AZgEGAABVqU4DXv4074.jpg)
评论