HLS可以用于將C語言函數轉換成硬件模塊。這是一個革命性的工具,從此軟件人員也可以創建硬件模塊。下面從軟件工程師的角度,介紹使用HLS創建硬件模塊時的注意事項。為了避免重復,請先閱讀UG871 《Vivado Design Suite Tutorial: High-Level Synthesis》。
進行硬件加速,要先準備好用來生成硬件模塊的函數,它需要保存在一個單獨的文件里。在創建工程時,指定它做為頂層函數。同時也要準備一個實現相同功能的函數,它不會被生成硬件模塊,用于驗證硬件模塊的功能是否正確。最后還要準備一個測試的main()函數,它分別調用前面所述的兩個軟件函數,并比較它們輸出的結果是否一致。在C語言驗證和RTL驗證時,HLS工具都會調用它。
創建HLS工程的具體步驟,請參考UG871 《Vivado Design Suite Tutorial: High-Level Synthesis》。假設已經創建好HLS工程。下面介紹最基本的約束。
下面是一個典型的C語言函數。
long long cmplx_dot_st_1port
(
long long* p_a,
unsigned int ui_vector_length,
unsigned int ui_test_param[2]
)
上面這個函數中,p_a是一個指針,用于存放有用于存儲大量輸入/輸出數據。CPU執行這個函數時,CPU指令會讀寫其中的數據。如果C語言函數轉換成硬件模塊,可以生成AXI Master接口,由硬件模塊自動讀取數據,相當于集成了一個DMA控制器;也可以生成AXI Stream接口,AXI Stream Slave接口接受其它數據源生成的數,AXI Stream master接口可以作為數據源。指針原來是32位的。為了提高帶寬,這個函數中將其改成了64位的指針。下面是為其生成AXI Master接口的約束:
#pragma HLS RESOURCE variable=p_a core=AXI4M
#pragma HLS INTERFACE ap_bus depth=1024 port=p_a
為指p_a生成AXI Stream接口的約束:
#pragma HLS INTERFACE axis port= p_a
ui_vector_length是一個輸入參數,指定buffer的長度。轉換成硬件模塊后,會生成一個可寫的寄存器,可以由AXI Lite總線訪問,由CPU寫入長度。為輸入參數ui_vector_length生成AXI Lite寄存器的約束:
#pragma HLS RESOURCE variable=ui_vector_length core=AXI4LiteS
HLS為它生成寄存器后,還會為其生成兩個函數,用于設置和讀取。
void XCmplx_dot_st_1port_SetUi_vector_length(XCmplx_dot_st_1port3a *InstancePtr, u32 Data)
u32 XCmplx_dot_st_1port_GetUi_vector_length(XCmplx_dot_st_1port3a *InstancePtr)
ui_test_param[2]是一個數組,既可以輸入參數又可以輸出參數。轉換成硬件模塊后,會生成可讀可寫的寄存器,因此硬件模塊既可以從它得到從CPU輸入的參數,也可以用它向CPU返回數據。由于硬件資源限制,這種數組不能太大。為數組生成寄存器的約束:
#pragma HLS RESOURCE variable=f_accum_param core=AXI4LiteS
#pragma HLS ARRAY_PARTITION variable=f_accum_param complete dim=1
HLS會為數組中每一個成員創建一個寄存器,也會創建對應寄存器的讀寫函數。
函數的返回值也需要創建寄存器,這樣CPU讀這個寄存器就可以得到返回值。為返回值創建寄存器的約束:
#pragma HLS RESOURCE variable=return core=AXI4LiteS
HLS同樣會為返回值寄存器創建讀寫函數。
u64 XCmplx_dot_st_1port_GetReturn(XCmplx_dot_st_1port3a *InstancePtr);
如果是用戶自己的函數,雖然參數個數和類型可能都不一樣,都可以參照上述類型做約束。HLS生成的硬件模塊的寄存器都是無符號32位數據。必要的時候,可以使用C語言的union來做類型轉換。
約束好與CPU的接口后,需要考慮性能優化。HLS有很多性能優化的技巧。但是作為軟件工程師,掌握基本的就可以了。“#pragma HLS DATAFLOW”是一個很有用的優化命令,可以給每個函數添加一個。“#pragma HLS PIPELINE”也是非常有用的優化命令,每個循環可以添加一個。這是最簡單的優化,而且比較有效果。
然后就可以綜合C語言的函數,HLS工具會報告生成的硬件模塊的性能、資源情況。接下來導出硬件模塊,做成IP,直接在Vivado IP Integrator里使用,做一個嵌入式系統。這種IP,可以給Zyqn中的ARM使用,也可以給MicroBlaze使用。最后編譯Vivado工程,生成bit文件。
如果要替換原來的C語言函數,還需要為硬件模塊寫驅動。HLS自動為每個輸入輸出參數生成了讀寫函數。它還為控制硬件模塊提供了下列控制函數。
void XCmplx_dot_Start(XCmplx_dot *InstancePtr);
u32 XCmplx_dot_IsDone(XCmplx_dot *InstancePtr);
u32 XCmplx_dot_IsIdle(XCmplx_dot *InstancePtr);
u32 XCmplx_dot_IsReady(XCmplx_dot *InstancePtr);
驅動函數的實現挺簡單。第一步是先調用XCmplx_dot_IsReady( )函數檢查硬件狀態。如果是就緒狀態,則進行第二步,調用輸入參數的寫函數,向硬件模塊寫入輸入參數。第三步則是等待硬件模塊完成運算,可以在循環中調用XCmplx_dot_IsDone( )函數檢查。如果完成,則進行第四步,讀取運算結果,比如調用XCmplx_dot_st_1port_GetReturn( ) 函數讀取返回值;如果結果在輸出參數中,則調用對應參數的讀取函數得到結果。HLS生成的寄存器,都是32位的寄存器。HLS生成的驅動,缺省認為結果是無符號32位數。如果是其它類型,可以使用指針或者union來做轉換。
比如要在64位數據、32位浮點數、32位整型數據之間做轉換,可以使用下面的數據結構
struct u32_cmplx{
unsigned int real;
unsigned int img;
};
union ll_data_u
{
long long ll_data;
struct sp_cmplx cmplx_data;
struct u32_cmplx u32_cmplx_data;
};
從寄存器讀到的數據,可以保存到u32_cmplx_data.real中;再以cmplx_data.real引用,就是浮點數。
用CPU做運算,CPU的運算能力通常是瓶頸。使用HLS生成硬件模塊加速后,運算能力大大提高,通常運算能力都不再是瓶頸。很多時候,存儲器和數據通道成了瓶頸。一個優化辦法,就是提高數據通道的帶寬。C語言里,經常用32位的指針。這種情況下,最好改成64bit的指針,可以提高數據通道的帶寬,降低運算時間。
評論