在HLS中插入HDL代碼
掃描二維碼
隨時(shí)隨地手機(jī)看文章
很多人都比較反感用C/C++開發(fā)(HLS)FPGA,大家第一拒絕的理由就是耗費(fèi)資源太多。但是HLS也有自己的優(yōu)點(diǎn),除了快速構(gòu)建算法外,還有一個(gè)就是接口的生成,尤其對(duì)于AXI類接口,按照標(biāo)準(zhǔn)語(yǔ)法就可以很方便地生成相關(guān)接口。
那么有沒(méi)有能利用HLS的優(yōu)點(diǎn),又囊括HDL的優(yōu)點(diǎn)的方法呢?今天就來(lái)介紹一種在HLS中插入HDL代碼的方式,結(jié)合兩者的優(yōu)勢(shì)為FPGA開發(fā)打造一把“利劍”。
說(shuō)明
接下來(lái),將介紹如何創(chuàng)建 Vitis-HLS 項(xiàng)目并將其與自定義 Verilog 模塊集成一起。
將插入兩個(gè)黑盒函數(shù) - 第一個(gè)在流水線區(qū)域(線路接口,ap_none),第二個(gè)在數(shù)據(jù)流區(qū)域(FIFO 接口,ap_ctrl_chain)。
步驟
1. 創(chuàng)建C/C++源文件(基于C的HLS模型+Testbench)
創(chuàng)建模塊的 C/C++ 模型,其中包括函數(shù)源代碼(模塊預(yù)期行為)和測(cè)試平臺(tái)(io 刺激和結(jié)果檢查)。
根據(jù)ug1399-vitis-hls rtl黑盒,rtl黑盒受到幾個(gè)因素的限制:
- 應(yīng)該是Verilog(.v)代碼。
- 必須具有唯一的時(shí)鐘信號(hào)和唯一的高電平有效復(fù)位信號(hào)。
- 必須有一個(gè) CE 信號(hào),用于啟用或停止 RTL IP。
- 可以使用 ap_ctrl_chain 或 ap_ctrl_none 塊級(jí)控制協(xié)議。
- 僅支持 C++。
- 無(wú)法連接到頂層接口 I/O 信號(hào)。
- 不能直接作為被測(cè)設(shè)計(jì)(DUT)。
- 不支持結(jié)構(gòu)或類類型接口。
main.cpp ——C/C++ 測(cè)試臺(tái)。
#include?"add.hpp" int?main?(void)?{ ????static?uint32_t?a[1024]; ????static?uint32_t?b[1024]; ????static?uint32_t?c[1024]; ????static?uint32_t?c_stream[1024]; for (uint32_t?i?=?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\n",?c[i],?a[i]?+?b[i]); return -1; ????????} if (c[i]?!=?c_stream[i])?{ printf("Data?does?not?match.?%d?vs?%d\n",?c[i],?c_stream[i]); printf("Add?modules?have?different?results.\n"); return -2; ????????} ????} printf("Test?succesfull.\n"); return 0; }
add.hpp——函數(shù)聲明。
#ifndef?ADD_HPP #define?ADD_HPP #include #include?void?add(uint32_t?a,?uint32_t?b,?uint32_t?&c); void?add_stream( ????hls::stream&a, ????hls::stream&b, ????hls::stream&c ); void?scalar_to_stream(uint32_t?a,?hls::stream&a_stream); void?stream_to_scalar(hls::stream&a_stream,?uint32_t?&a); void?wrap(uint32_t?a,?uint32_t?b,?uint32_t?&c); void?top_module(uint32_t?*a,?uint32_t?*b,?uint32_t?*c,?uint32_t?*c_stream); #endif
add.cpp——函數(shù)源代碼。
#include?"add.hpp" void?add(uint32_t?a,?uint32_t?b,?uint32_t?&c)?{ ????c?=?a?+?b; }; void?add_stream( ????hls::stream&a, ????hls::stream&b, ????hls::stream&c )?{ ????c.write(a.read()?+?b.read()); }; void?scalar_to_stream(uint32_t?a,?hls::stream&a_stream)?{ ????a_stream.write(a); }; void?stream_to_scalar(hls::stream&a_stream,?uint32_t?&a)?{ ????a?=?a_stream.read(); }; void?wrap(uint32_t?a,?uint32_t?b,?uint32_t?&c)?{ #pragma?HLS?DATAFLOW hls::streamc_s; ????hls::streama_s; ????hls::streamb_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); }; void?top_module(uint32_t?*a,?uint32_t?*b,?uint32_t?*c,?uint32_t?*c_stream)?{ #pragma?HLS?INTERFACE?mode=m_axi?port=a?depth=1024?bundle=first #pragma?HLS?INTERFACE?mode=m_axi?port=b?depth=1024?bundle=second #pragma?HLS?INTERFACE?mode=m_axi?port=c?depth=1024?bundle=first #pragma?HLS?INTERFACE?mode=m_axi?port=c_stream?depth=1024?bundle=second #pragma?HLS?INTERFACE?mode=s_axilite?port=return main_loop_pipeline: for (uint32_t?i?=?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創(chuàng)建配置文件
Vitis HLS需要配置文件來(lái)構(gòu)建項(xiàng)目。基本配置文件應(yīng)包含
- Part——FPGA 部件編號(hào)。
- syn.top——頂級(jí)函數(shù)名稱。
- tb.file——測(cè)試臺(tái)文件。
- 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. 創(chuàng)建并構(gòu)建最小項(xiàng)目
啟動(dòng) Vitis,選擇工作區(qū)并點(diǎn)擊“創(chuàng)建 HLS 組件”。
更改組件位置和名稱,單擊下一步。
選擇從現(xiàn)有配置文件創(chuàng)建,點(diǎn)擊下一步。
項(xiàng)目結(jié)構(gòu)如下所示:
無(wú)需添加額外的標(biāo)志,只需仔細(xì)檢查頂部函數(shù)是否是“top_module”,然后單擊下一步。
選擇芯片(默認(rèn)部分應(yīng)該是cfg文件中寫的),單擊下一步
確認(rèn)flow_target和package.output.format,點(diǎn)擊next。
檢查摘要并單擊完成。
最后運(yùn)行所有步驟以確保所有配置均已配置并正常運(yùn)行。
4.創(chuàng)建blackbox函數(shù)json
在此步驟中,我們將用 blackbox verilog 代碼替換我們的添加函數(shù)。在pipeline區(qū)域:
右鍵單擊 hls_component 并單擊“創(chuàng)建 RTL blackbox”,將生成 JSON 文件,描述 verilog 模塊與其 C 函數(shù)之間的連接。
選擇包含 C 模塊描述的文件。
選擇端口方向并填寫RTL組配置(verilog模塊中的端口名稱)。
選擇verilog文件,如有必要再填寫其他框,單擊下一步。
刪除 ap_ctrl_chain_protocol 字符串,保留空白。單擊完成。
對(duì) add_stream 函數(shù)重復(fù)所有這些步驟。
輸入先進(jìn)先出:
輸出先進(jìn)先出:
概括:
不要修改 ap_ctrl_chain 信號(hào),因?yàn)樵撃K將使用 ap_ctrl_chain 協(xié)議。
此后,hls_component 文件夾中應(yīng)該會(huì)生成兩個(gè) 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" } }
主文件夾應(yīng)與此類似:
hls_config.cfg 文件應(yīng)該添加兩新行( 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.創(chuàng)建Verilog黑盒函數(shù)
函數(shù)“add”必須具有ap_none接口,并且 ap_none 作為模塊接口。(有關(guān)模塊接口的更多信息,請(qǐng)查看https://docs.amd.com/r/en-US/ug1399-vitis-hls/JSON-File-for-RTL-Blackbox 。)
根據(jù)UG1399,端口a和b是32位寬度的輸入端口,輸出c端口也是32位寬度,但帶有額外的有效信號(hào),我們稱之為c_vld。模塊還需要ap_clk,ap_ce,ap_rst端口。
Verilog 如下所示:
add.v
`timescale?1ns/1ps module?add?( ????input?[31:0]?a, ????input?[31:0]?b, ????output?[31:0]?c, ????output?c_vld, ????input?ap_ce, ????input?ap_rst, ????input?ap_clk ); ????reg?[31:0]?c_d; ????reg?c_vld_d; ????assign?c?=?c_d; ????assign?c_vld?=?c_vld_d; ????always?@(posedge?ap_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
運(yùn)行 C 綜合和 C/RTL 協(xié)同仿真。能夠在 HLS 模塊中看到打包的 add.v 文件。
單擊 hls_config.cfg 文件,在 Vitis GUI 的幫助下將 cosim.trace_level 更改為全部并運(yùn)行聯(lián)合仿真。
單擊波形查看器。Vivado 會(huì)彈出 XSIM。
將 grp_add_fu_134 信號(hào)添加到 wcfg
函數(shù)行為很奇怪,接下來(lái)在 json 中更改黑盒函數(shù) II,看看它如何影響仿真。打開 add.json 并將 II 更改為 10。再次運(yùn)行 C 綜合并重新運(yùn)行 C/RTL 協(xié)同仿真。
add.v 模塊是否良好且可以正常工作?其行為是否正確?模塊是否正常工作由哪些因素決定?“fixing”模塊對(duì)資源使用有何影響?
那么 add_stream 呢?函數(shù)位于數(shù)據(jù)流區(qū)域,并且必須包含 fifo 端口和 ap_ctrl_chain 協(xié)議。
add_stream.v
`timescale?1ns/1ps module?add_stream?( ????input?[31:0]?a, ????input?a_empty_flag, ????output?a_read_enable, ????input?[31:0]?b, ????input?b_empty_flag, ????output?b_read_enable, ????output?[31:0]?c, ????input?c_full_flag, ????output?c_write_enable, ????output?ap_idle, ????input?ap_start, ????output?ap_ready, ????output?ap_done, ????input?ap_continue, ????input?ap_ce, ????input?ap_rst, ????input?ap_clk ); ????reg?a_read_enable_d; ????reg?b_read_enable_d; ????reg?c_write_enable_d; ????reg?[31:0]?c_d; ????assign?a_read_enable?=?a_read_enable_d; ????assign?b_read_enable?=?b_read_enable_d; ????assign?c_write_enable?=?c_write_enable_d; ????assign?c?=?c_d; ????assign?ap_idle?=?!ap_start; ????assign?ap_ready?=?ap_start; ????assign?ap_done?=?ap_start; ????//Flags?are?negated... ????assign?flags_good?=?a_empty_flag?&&?b_empty_flag?&&?c_full_flag; ????assign?hs_good?=?ap_start?&&?ap_continue; ????always?@(posedge?ap_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
看起來(lái)放置在數(shù)據(jù)流區(qū)域的模塊工作正常:
打開 add_stream.json 并將延遲更改為 10。再次運(yùn)行 C 綜合并重新運(yùn)行 C/RTL 協(xié)同仿真。這會(huì)影響仿真嗎?