在這一節, 將學習在 Spatial 中有關下列元件:
- 應用程式骨架( import 宣告, 應用程式創建, Accel 範圍, Host 範圍)
- DRAM
- SRAM
- ArgIn
- ArgOut
- HostIO
- Reg
- 型別系統(Typing system)
- host 與 accel 間的資料傳輸
- 基本的 debugging 攔截
請注意大量的 Spatial 應用程式可以在此找到.
Overview
在本節中, 將了解如何組合建立最基本的 Spatial 應用程式. 由於這些程式碼並沒有做任何”有意義” 的工作, 它演示了幾乎所有應用程式都使用的基本指令, 並且旨在作為硬體的 “Hello, world!” 程式. 以產生作為加速器與 host 互動的輸入與輸出暫存器作開始, 接著加入在 off-chip DRAM 與 on-chip SRAM 之間的 tile 傳輸. 然後學習哪些利用 Host 提供作為測試的功能. 最後將學習針對不同輸出基本的編譯流程: 測試演算的功能性, 所產生 RTL 的 cycle-accurate simuation, 以及對支援的 FPGA 與架構產生佈署用的 bitstream.
上圖為在本教學中的操作的視覺化. 一開始有著 Host 與 FPGA, 兩者都連接著 DRAM. 然後將實體化所有可以讓兩個處理器互動的不同方法. 將創建位於 FPGA 之中的 RTL, 以及一些在 Host 中的 C++ 程式碼. Spatial 自動地實體化一個稱為 “Fringe” 的盒框, 這是一個 FPGA-agnostic (champ: 表示著可以跨越不同的 FPGA) 的硬體設計, 允許 RTL 與周邊, DRAM, PCIe 匯流排以及在 SoC 或 FPGA 板上俱備的其他硬體作互動.
Application Template
所有 Spatial 程式有著一些基本元件. 下列的程式碼在一個稱為 HelloSpatial 的應用中示範了每一個那些元件. 這是一個完整的應用, 能夠任意嘗試與編譯它. 它並沒有作任何事情, 但也不會阻止你如何地使用它.
針對特定平台編譯此應用程式, 請參考 Target 頁面
import spatial.dsl._
@spatial object HelloSpatial extends SpatialApp { def main(args: Array[String]): Void = { // Host Code Accel { // Acceleratable Code } // More Host Code } }
ArgIn/ArgOut Interfaces
接著繼續發展這個應用程式, 加入 ArgIns, ArgOuts 與 HostIOs.
最基本讓資料進出 FPGA 的方式是在 Accel 與 Host 之牽傳遞個別參數. 一些 ArgIns 的使用範例像是: 傳遞應用參數到 Accel, 像是在 PageRank 中的 damping factor 或是像是 GEMM 等應用的資料結構的維度; 以及 ArgOuts 的使用範例像是: 輸出內積的純量結果. 將在 Accel 區塊之上定義一些暫存器, 以便 CPU 能夠配置它們.
這時, 可能已經注意到持續以中括號指定所有為 Int. 中括號是 Scala 如何傳遞型別參數的方式. Spatial 是一個硬體語言, 支援了一些標準 32-bit 整數外的型別, 能夠任意的定義使用他們. 一些範例包含了:
type T = FixPt[FALSE,_16,_0] type Flt = Float
請注意在 println, 雙引號前帶有 “r” 字母. 這會在程式運作時透過 ${value} 以其實際數值的形式替換字串.
import spatial.dsl._
@spatial object HelloSpatial extends SpatialApp { def main(args: Array[String]): Void = { // Create Args val x = ArgIn[Int] val y = ArgOut[Int] val z = HostIO[Int] // Set `x` to the value of the first command line argument setArg(x, args(0).to[Int]) // Set `z` to the value of the second command line argument setArg(z, args(1).to[Int]) Accel { // Set `y` to the sum of `x` and `z` y := x + z } // Report the answer println(r"Result is ${getArg(y)}") } }
Reg, SRAM, and Basic Control
接著將展示如何加入記憶體結構到 Accel. 對於一個完整的記憶體列表, 請參考說明文件. 在這個範例中僅示範 Reg 與 SRAM.
在下列程式碼中, 取得參數 x. 並且使用此來索引 Accel 中的一個記憶體. 範例中建立了記憶體 s 與 r. 在 Spatial 中 SRAM 最高能以 5D 的方式創建, 而 Reg 為 single-element 資料結構.
這裡建立了 Foreach 迴圈來存取 s 的每個單位. 第一個針對 Foreach 的參數集合指定了迴圈的 counter 範圍. 在此範例中, 指出第一個迴圈索引最高到 16, 而第二個應最高到 32. 最常見的是,迴圈範圍可以指定為:
until by par
在此例中,
16 by 1
等同於 0 until 16 by 1 par 1
. 下限, 上限與步距都允許負值,然而 par 必須為正值. 後續的教學將討論更多複雜的控制結構與 counter 的使用情況. 但現在不會平行或巢狀任何迴圈.
第二個針對
Foreach
迴圈的參數綁定了 counter 數值到此例中的變數i
與 j
中.這能指定每個 iteration 所採取的行為.
請注意為了寫入到
SRAM
, 必須指定位置並且使用=
. 為了寫入到Reg
, 必須使用:=
.要自SRAM
讀取能夠直接傳入位置, 而要自Reg
讀取必須使用.value
.import spatial.dsl._ @spatial object HelloSpatial extends SpatialApp { def main(args: Array[String]): Void = { // Create ArgIn val x = ArgIn[Int] // Set `x` to the value of the first command line argument setArg(x, args(0).to[Int]) Accel { // Create 16x32 SRAM and a Register val s = SRAM[Int](16,32) val r = Reg[Int] // Loop over each element in SRAM Foreach(16 by 1, 32 by 1){(i,j) => s(i,j) = i + j } // Store element into the register, based on the input arg r := s(x,x) // Print value of register (only shows in Scala simulation) println(r"Value of SRAM at (${x.value},${x.value}) is ${r.value}") } } }
DRAMs and Transfers
在多數的情形, 會想使用 Spatial 來撰寫利用 FPGA 作為加速器來分擔 CPU 計算的應用程式. 這表示將有大量的資料結構想要在此兩著間共享. 為此目的導入了
DRAM
, 藉此資料結構能夠被 CPU 所配置並且能夠被 FPGA 所讀寫.
在下面的例子, 創建了 DRAM , 在 Host 端設定了 DRAM 的內容,告知加速器修改內容與自 Host 讀回. 建立一個
DRAM
實際上是一個 malloc
, Host 配置一個記憶體區域並且傳遞此區域的 pointer 到 FPGA. 接著在 Host 端使用Array
建立一個資料結構, 並且對此陣列每個單位填入數值 0. 請參見文件來以更複雜的方式建立 Arrays, Matrices, and Tensors. 以Array
的內容來設定 DRAM,本質上為memcpy
此記憶體區域.
藉由
DRAM
的設定與載入, 能夠建立一個在加速器中的 local memory s
, 並且傳送d
的內容到此SRAM
. 請注意資料並不能在 DRAM 中直接操作, 並且自 DRAM 到 SRAM 的傳輸是以 burst-granularity 的方式進行. 這表示對一個像是 ZC706 SoC, 一個自 DRAM 的 burst 提供了 512 bits. 若載入了一個無法有效以 burst 大小整除的單位數目, 將犧牲一些頻寬在被加速器所忽略的資料上.
在此例中, 使用
(0::16)
來載入並存取自d
. 這表示自位置 0 到位置 16 的單位將依序傳輸. 更常見的是, 可能以語法(:: by par )
加入了平行化. 有著平化參數允許傳輸硬體在 bus 上同時放置更多單位, 並且以額外的硬體開銷提升了傳輸的速度.
使用一個 1D 的
Foreach
來修改在s
中的每個單元,接著寫回到原本的DRAM
. 在加速器之外, 能夠使用printArray
來讓 Host 印出記憶體內容.
如同 SRAM, DRAM 能最高有著 5D.
import spatial.dsl._ @spatial object HelloSpatial extends SpatialApp { def main(args: Array[String]): Void = { // Create DRAM (malloc) val d = DRAM[Int](16) // Set DRAM (memcpy) val data = Array.fill[Int](16)(0) setMem(d, data) Accel { // Create 16-element SRAM val s = SRAM[Int](16) // Transfer data from d to s s load d(0::16) // Add number to each element Foreach(16 by 1){i => s(i) = s(i) + i} // Transfer data back to d d(0::16) store s } // Print contents in memory printArray(getMem(d), "Result: ") } }
沒有留言:
張貼留言