hls_zcu102_0_基礎流程與放棄


本系列文檔通過實例說明 Vivado HLS 的基礎開發流程。

遺憾的是,HLS 沒有必要進行更爲深入的研究。因爲在本人看來,HLS 僅僅是一個在 FPGA 上快速實現算法的工具,稍有些複雜的算法就幾乎無法得到足夠的運行效率。原本對 HLS 的幻想是像 C/C++ 代碼一樣在 FPGA 中執行計算,但是在後續的進階實驗中越發感受到 HLS 的侷限,無論是代碼本身的優化或者使用 Directive 配置都沒有理想的結果。可能 HLS 面向的使用對象是不瞭解 FPGA 運行原理的 C/C++ 工程師,僅在對代碼運行效率沒有要求的情況下使用。

開發環境:

  • zcu102 開發板
  • Windows 10
  • Vivado 2018.2.1
  • Visual Studio Code
  • Matlab 2017b

主要參考資料包括:


本文檔說明了使用 Vivado HLS 實現算法功能的基本流程,並且配置 VS Code 爲基礎的代碼編輯器(僅用於編輯代碼)。

建立 HLS 工程

Vivado Design Suite 完整安裝後,在 Win10 開始菜單中打開 HLS:

在這裏插入圖片描述

HLS 啓動後,在菜單中選擇 File > New Project

在這裏插入圖片描述

在彈出窗口中選擇工程名稱和路徑,點擊 Next 按鈕

在這裏插入圖片描述

後續的添加源文件(source files)和測試文件(testbench files)界面默認不做操作,直接按 Next 按鈕

在這裏插入圖片描述

在這裏插入圖片描述

在 solution 界面設置解決方案名稱、時鐘頻率,並選擇器件,最後點擊 Finish 按鈕

注意:同一個 HLS 工程對應一組源代碼和測試代碼,但是可以包含多個解決方案,每個解決方案可以對代碼的 FPGA 實現做不同的配置。

在這裏插入圖片描述

工程建立後的 Explorer 界面如下:

在這裏插入圖片描述

配置 VS Code

配置 include 路徑

首先確保 VS Code 已安裝 C/C++ 插件和 C++ Intellisense 插件

在這裏插入圖片描述

進入 HLS 工程所在目錄,在右鍵菜單選擇“通過 Code 打開”

在這裏插入圖片描述

在 VS Code 中通過菜單或者快捷鍵打開 Command Palatte

在這裏插入圖片描述

在彈出的 Command Palatte 中輸入 edit,並選擇:

C/C++: Edit Configurations (UI)

在這裏插入圖片描述

在配置界面的包含路徑中添加 HLS 工程 Includes 下全部的路徑

注意:${workspaceFolder}/** 路徑是點擊輸入框自動添加

在這裏插入圖片描述

其它配置項保持默認,關閉配置界面。

在 HLS 工程文件夾內添加了 .vscode 文件夾,打開其中的c_cpp_properties.json 即爲前述的配置內容

在這裏插入圖片描述

配置默認文件頭

參考:https://www.jianshu.com/p/07a7fd95954f

在 VS Code 菜單選擇 File > Preferences > User Snippets

在這裏插入圖片描述

在彈出命令框中選擇 C++

在這裏插入圖片描述

在打開的 cpp.json 文件中添加以下內容,通過關鍵詞 hls 添加與 Vivado 建立 HDL 文件相同的文件頭

{
	"HLS CPP Title": {
		"prefix": "hls",
		"body": [
			"//////////////////////////////////////////////////////////////////////////////////",
			"// Company:",
			"// Engineer:",
			"//",
			"// Create Date: $CURRENT_YEAR/$CURRENT_MONTH/$CURRENT_DATE $CURRENT_HOUR:$CURRENT_MINUTE:$CURRENT_SECOND",
			"// Design Name:",
			"// Module Name: $TM_FILENAME_BASE",
			"// Project Name:",
			"// Target Devices:",
			"// Tool Versions:",
			"// Description:",
			"//",
			"// Dependencies:",
			"//",
			"// Revision:",
			"// Revision 0.01 - File Created",
			"// Additional Comments:",
			"//",
			"//////////////////////////////////////////////////////////////////////////////////",
			"\n",
			"\n"
		],
		"description": "HLS CPP Title"
	}
}

注意:prefix 後的內容是 snippet 的提示文字,在 cpp 文件中輸入 hls 後在彈出的 suggestion 中選擇 HLS CPP Title。當然必須保證在 VS Code 中打開 suggestion 功能。

在這裏插入圖片描述

編寫代碼

一般情況下 HLS 工程裏的源文件都使用 C++,而不是 C,原因是 C++ 可以通過模板功能自定義的整數位寬和函數功能。

源文件的功能是定義一個函數,將輸入的 8 位 led 數值循環移位,Verilog 代碼如下:

led_out[7:0] <= {led_in[6:0], led_in[7]};

在 VS Code 的 Explorer 的右鍵菜單中選擇 New FIle

在這裏插入圖片描述

建立源代碼文件: shift_led.cpp 、shift_led.h

測試代碼文件:tb_shift_led.cpp

代碼:shift_led.h

#ifndef _SHIFT_LED_H_
#define _SHIFT_LED_H_

// 時鐘頻率爲 125 MHz,for 循環 PERIOD 次即爲 1 秒
#define PERIOD 125000000

// 函數聲明
void shift_led(unsigned char* led_out, unsigned char led_in);

#endif

代碼:shift_led.cpp

#include "shift_led.h"

void shift_led(unsigned char* led_out, unsigned char led_in)
{
	// 保存輸入數值
	unsigned char temp;
	temp = led_in;

	// 用 for 循環計時 1 秒
	unsigned int i;

	for (i = 0; i < PERIOD; ++i)
	{
		;
	}

	// 循環移位 temp <= {temp[6:0], temp[7]};
	temp = (temp << 1) | (temp >> 7);

	//輸出數據
	*led_out = temp;
}

代碼:tb_shift_led.cpp

#include <stdio.h>
#include "shift_led.h"

// 運行 HLS 函數的次數 
#define TIME 17

// 人工觀察運行結果
int main()
{
	unsigned char led_in = 0x7F;
	unsigned char led_out = 0;

	for (int i = 0; i < TIME; ++i)
	{
		// 調用 HLS 函數
		shift_led(&led_out, led_in);

		// 將 HLS 函數輸出值賦給輸入值用於下一輪計算
		led_in = led_out;

		// 打印輸出值
		printf("current led value: %x\n", led_in);
	}

	// 返回 0 值表示測試正確,非 0 值表示測試錯誤
	return 0;
}

向 HLS 工程添加代碼

HLS 工程的代碼包括 2 種:源代碼(Source)和測試代碼(Test Bench)。

源代碼用於綜合生成 RTL 代碼在 FPGA 中運行,測試代碼中實現 main 函數調用源代碼中定義的函數。

回到 HLS 工程,在 Explorer 的 Source 項的右鍵菜單上選擇 Add Files,並且在彈出窗口中選擇前述的 shift_led.cpp

注意:shift_led.h 文件不能添加進 HLS 工程

在這裏插入圖片描述

在 Explorer 的 Test Bench 項的右鍵菜單選擇 Add Files,添加 tb_shift_led.cpp

在這裏插入圖片描述

完成後的 Explorer 顯示如下:

在這裏插入圖片描述

在 HLS 工程中打開代碼文件之前應當先配置 HLS 環境(Eclipse)使用 UTF-8 的文本編碼格式與 VS Code 一致

VS Code 中的配置如下圖所示:

在這裏插入圖片描述

在 HLS 工程菜單中打開 Window > Preferences > Workspace 頁,配置以下 2 項:

  • Text file encoding: Other UTF-8
  • New text file line delimiter: Other Unix

在這裏插入圖片描述

在 HLS 工程中可以雙擊打開 cpp 代碼文件

C Simulation

在 HLS 菜單選擇 Project > Run C Simulation

彈出窗口保持默認,點擊 OK 按鈕

在這裏插入圖片描述

根據 Console 窗口提示,前述代碼的仿真測試正確完成。

如果將 tb_shift_led.cpp 中 main 函數返回值改爲 1,再次運行 C Simulation,則 Console 中提示仿真出錯:

在這裏插入圖片描述

注意:C Simulation 完成後在 Explorer 的 solution1 中出現了 csim 文件夾,在 report 中的 _csim.log 即爲仿真測試的記錄

在這裏插入圖片描述

C Synthesis

在 HLS 的菜單中選擇 Project > Project Settings

在這裏插入圖片描述

在 Synthesis 頁設置 Top Function 爲 shift_led,點擊 OK 按鈕

在這裏插入圖片描述

在 HLS 菜單選擇 Solution > Run C Synthesis > Active Solution

在這裏插入圖片描述

運行完成後,Explorer 的 solution1 中出現 impl 和 syn 文件夾,雙擊打開綜合報告:shift_led_csynth.rpt

在這裏插入圖片描述

在綜合報告的最下方 Interface 爲綜合生成 RTL 模塊的端口列表:

在這裏插入圖片描述

也可以在 Explorer 中打開 solution1 > syn > verilog > shift_led.v 查看

在這裏插入圖片描述

注意:此時生成的 RTL 文件根據 ug902 的說明,不建議直接用於 Vivado 工程使用,而是應當[生成 IP 供 Vivado 工程調用](#Vivado 工程)

Run C/RTL Cosimulation

只有在完成 C Synthesis 之後才能進行 C 與 RTL 的聯合仿真。

在 HLS 菜單選擇 Solution > Run C/RTL Cosimulation

在這裏插入圖片描述

在彈出窗口中選擇 Vivado Simulator 和 Dump Trace all (用於記錄仿真波形),點擊 OK 按鈕

在這裏插入圖片描述

仿真完成後在 Explorer 的 solution1 中出現 sim 文件夾,打開仿真報告文件:

solution1 > sim > report > shift_led_cosim.rpt

顯示通過仿真測試

在這裏插入圖片描述

打開波形文件==(調用 Vivado)==:

solution1 > sim > verilog > shift_led.wdb

注意:無法在 HLS 中雙擊打開,而必須在 Win10 文件夾中雙擊打開該文件

在這裏插入圖片描述

在 Vivado 中 Scope 頁右鍵點擊 inst_shift_led,選擇 Add to Wave Window

在這裏插入圖片描述

除此以外,時鐘和復位信號如下圖所示:

在這裏插入圖片描述

添加完成後的波形如下圖所示,用於觀察生成的 RTL 模塊的端口時序

在這裏插入圖片描述

修改

在前文的波形中可以發現 1 個時鐘週期就完成移位,而沒有按照代碼的設計等待 1 秒,很可能是編譯器將 shift_led.cpp 代碼中空執行的 for 循環優化清理掉了

於是修改 shift_led.cpp 代碼如下:

#include "shift_led.h"

void shift_led(unsigned char* led_out, unsigned char led_in)
{
	// 保存輸入數值
	unsigned char temp;
	temp = led_in;

	// 用 for 循環計時 1 秒
	unsigned int i;

	for (i = 0; i < PERIOD; ++i)
	{
		if (i == 0)
		{
			// 循環移位 temp <= {temp[6:0], temp[7]};
			temp = (temp << 1) | (temp >> 7);
		}
	}

	//輸出數據
	*led_out = temp;
}

並且爲了仿真方便,在 shift_led.h 文件中修改 PERIOD 數值如下:

#ifndef _SHIFT_LED_H_
#define _SHIFT_LED_H_

// 時鐘頻率爲 125 MHz,for 循環 125000000 次即爲 1 秒
// 爲減少仿真時間,將 PERIOD 值改爲 125
#define PERIOD 125

// 函數聲明
void shift_led(unsigned char* led_out, unsigned char led_in);

#endif

代碼修改完成後,重新執行以下步驟:

  • [C Simulation](#C Simulation)
  • [C Synthesis](#C Synthesis)
  • [Run C/RTL Cosimulation](#Run C/RTL Cosimulation)

最後得到正確的波形:

在這裏插入圖片描述

導出 HLS IP

在導出 HLS IP 之前,修改 shift_led.h 中的 PERIOD 數值爲 125000000,以實現 1 秒延時。

修改完成後,直接進行 [C Synthesis](#C Synthesis)。

C Synthesis 正確完成後,在 HLS 菜單中選擇 Solution > Export RTL

在這裏插入圖片描述

在彈出窗口中如下圖配置後,點擊 OK 按鈕

在這裏插入圖片描述

完成操作後,在 HLS 的 Explorer 的 solution1 項的 impl 目錄下出現 ip 文件夾

在這裏插入圖片描述

Vivado 工程

Vivado 的使用細節本文檔不再詳述,具體方法可以參考之前的文檔:

zuc102_0_PL 端 LED 閃爍

Vivado 工程導入 IP

建立 Vivado 工程 hls_test 之後,在 Flow Navigator 的 PROJECT MANAGER 中點擊 Settings

在這裏插入圖片描述

在 IP > Repository 頁點擊 + 按鈕添加 HLS 生成的 IP 文件夾,完成後點擊 OK 按鈕

在這裏插入圖片描述

在 Vivado 的 Flow Navigator 中點擊 IP Catalog 打開 IP 列表界面

在這裏插入圖片描述

在搜索框內輸入 shift_led 找到 HLS 生成的 IP

在這裏插入圖片描述

雙擊 IP,並且在彈出窗口中保持默認,點擊 OK 按鈕

在這裏插入圖片描述

根據提示,保持默認配置完成 IP 生成

在 Sources 框內出現剛生成的 IP

在這裏插入圖片描述

HDL 代碼實現

在 Vivado 工程中建立頂層模塊 hls_top.v

添加以下代碼,shift_led 的端口時序參考[仿真波形](#Run C/RTL Cosimulation)

module hls_top(
	input clk_p,
	input clk_n,
	output [7:0] leds
	);

	wire clk;// 125MHz工作時鐘
    
    // 用 IBUFDS 原語實現差分時鐘轉單端
    IBUFDS
    #(
    .IOSTANDARD("DEFAULT")// DEFAULT默認所有情況
    )
    IBUFDS_inst
    (
    .O(clk),// 輸出
    .I(clk_p),// P端輸入
    .IB(clk_n)// N端輸入
    );

	// 用計數器產生復位信號
	reg [15:0] cnt_rst = 16'd0;
	
	wire rst;
	assign rst = ~(cnt_rst[15]);// 計數器最高位取反作爲高有效復位信號

	always @(posedge clk) begin
		case (cnt_rst)
			16'hFFFF: begin
				// 計數結束,數值保持
				cnt_rst <= cnt_rst;
			end

			default: begin
				// 計數器增 1
				cnt_rst <= cnt_rst+16'd1;
			end
		endcase
	end

	// 例化 IP
	reg start = 1'b0;
	reg [7:0] led_in = 8'h7F;
	wire [7:0] led_out;
	wire led_out_vld;

	shift_led_0 shift_led_u (
	.ap_clk(clk),                  // input wire ap_clk
	.ap_rst(rst),                  // input wire ap_rst
	.ap_start(start),              // input wire ap_start
	.ap_idle(),                // output wire ap_idle
	.ap_done(),                // output wire ap_done
	.ap_ready(),              // output wire ap_ready
	.led_out(led_out),                // output wire [7 : 0] led_out
	.led_out_ap_vld(led_out_vld),  // output wire led_out_ap_vld
	.led_in(led_in)                  // input wire [7 : 0] led_in
	);

	// IP 功能控制
	always @(posedge clk) begin
		if (rst == 1'b1) begin
			start <= 1'b0;
			led_in <= 8'h7F;
		end
		else begin
			// 啓動 IP 工作
			start <= 1'b1;

			// 產生有效輸出 led_out 時,向 led_in 賦值
			if (led_out_vld == 1'b1) begin
				led_in <= led_out;
			end
			else begin
				led_in <= led_in;
			end
		end
	end

endmodule

之後按照與zuc102_0_PL 端 LED 閃爍文檔相同的方法,添加引腳約束和時鐘約束,並且生成 bit 配置文件。

測試

按照zuc102_0_PL 端 LED 閃爍文檔的說明,在開發板的 FPGA 上燒寫 bit 配置文件,觀察板上 8 個 led 燈按照預期工作。

TODO:待上板測試。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章