當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > FPGA技術(shù)江湖

很多人都比較反感用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ì)影響仿真嗎?



本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉