在講解 680 中的 SIMD 單元 - HVX 之前, 還是先以
系列文 I 的 blocks diagram開頭, 並且今日重點會是文中提到第3點的官方文件
從 blocks diagram 中可以看到 HVX 由三個主要部分所組成
- VX : Vector Extension Unit, 也就是 HVX 的 SIMD 指令執行單元
- VPF : Vector Predicate register File, 這部分是用來處理 SIMD 的中較不易處理的 branch, if-else 等 divergence. 以 8bit 運算來說, 若需要提供 SIMD 中每個 lane 所需的 true/false boolean 資訊, 需要 1024/8 = 128 bit, 而 Predication Register 寬度, 基本上就是 128b.
- VRF : Vector Register File, 這即是 VX unit 中用來存放的暫存器, HVX 為 1024b vector, 因此基本上這部分為 1024b
而對外的介面有兩個:
- Co-Processor Instruction Port: 用來與 V6x 處理器溝通的, V6x DSP 是透過這個介面將指令送給 HVX 單元執行
- 兩組 512b-wide L2-cache/L2-TCM port: 相較 V6x 處理器對於 L2 的頻寬為 256b port, HVX 對於總寬度為 512b x2 , 這樣的設計很重要, 這讓 HVX 能夠更快的取得所需要的 vector 資料, 由於 HVX 為寬度 1024b vector 的 SIMD Unit, 因此這樣的寬度是有必要的. 至於為何是兩組 512b 而非一組 1024b, 這部分與其 thread model 有關, 在下一篇會介紹這個部分.
首先來談 VPF/VRF 的 Register File 部分, 在 HVX 中分別有著 32組 vector register 以
V0 ~ V31 表示, 而 predicate register 共有 4組, 以
Q0 ~ Q3 表示, 這些表示法用在 SDK toolchain 所產生的 .s assembly 中, 對於 V0 ~ V31, 並沒有區分資料型別, 因此為了表示使用的精確度, 在指令中對於 register 使用的方式必須有標記所
從語法中可以看出兩點, 首先是並不支援 float 運算(當然以這樣的notation 要新增也不是難事就是了), 再者是 SIMD 的 operation 與 data type 是脫勾的, 重點是搭配的 register data type, 以上面的例子來說
V2.h = VADD(V3.h, V4.h)
這個 asm 指令是指套用的是 add 指令, 並且資料型別是 h 所代表的 signed halfword (也就是通常 C 中的 short), 因此這行所代表的意義為: 將 V3 與 V4 的資料以 signed 16b 的方式相加後存入 V2. 這樣的 asm 設計也直接影響到了 HVX 在 C intrinsics 上的 coding style, 而這樣的設計其實有好有壞. 好處顯而易見的是 asm 很簡單, 而且能有效的降低寶貴的 register 浪費, 但這樣的設計當 export 到 C 的層次的時候, 就缺乏了對於各資料型別的 vector type, 因此沒有對應型別的 operator overloading, 但這點若採用 C++ 去自己實作也並不難就是了.
接著進入 HVX 的指令類別, HVX 有著六類的指令類型, 在系列文 I 當中也討論過 V6x 各個 slot 的支援, 而 HVX 也是使用相同 VLIW4 的設計, 針對這六類支援的指令, 於官方文件中有著下列表格:
從表格中可以出六類分別為:
- Aligned Memory Load
- Aligned Memory Store
- Unaligned Memory Load/Store
- Multiplies and special ALU
- Vextract
- Simple ALU, Permute, Shift
對於表格的呈現方式, 各位或許會覺得很奇怪, 為何不是像 V6x 處理器中那樣, 是以 slot 為主要分類, 而是以 instruction type 為主要分類. 這主因在於對於同一 cycle 中4個指令的使用上搭配要考慮的不是 slot 本身而已, 因為對於 HVX 來說支援每個 instruction type 所動用到內部的 "resource" 不同. 那什麼是 resource 呢?
在官方文件中有說明, HVX 本身由六種 resource 所組成, 這些 resource 讓 SIMD 指令得以運作
因此弄懂每個指令背後所動用到的 resource 也是重要的:
等等!!! 不是6類嗎, 怎麼變成11類了? 其實細看一下, 你會發現它只是將一些指令細分, 特別是第4類與第6類再去細分, 但是基本上還是那六類. 像是 Multiply 區分了 8b 與 16b 的部分, 這裡請注意, 8b 與 16b 這兩者分別使用的 Multiply Resource 數目不同, 所以這裡讓我們以官方在 Hotchips 中的 slides 對於效能作進一步解釋的說明:
上面這張 Qualcomm 官方投影片很常出現在一些淺談 Hexagon 680 與 HVX 的文章中, 但請注意其中的第2大點的 8b 與 16b 的乘法數目, 相信很多人很難理解(或者根本沒弄懂)為何 8b 的乘法數目為 16b 的 4倍, 因為 1024/8 = 128, 而 1024/16 = 64, 單以 SIMD 的寬度是無法解釋這樣的落差, 但這時若將指令背後 resource 的消耗數目納入考量就會很清楚了, 因為 HVX 處理器中提供了兩組 Multiply Resource, 而 16b 的乘法指令按照上面提供的表格一次即會消耗完兩組, 而 8b 的乘法指令只會消耗一組, 因此同一個 cycle 中, 至多只能塞入一個 16b SIMD 乘法 也就是 1024/16 = 64 個, 而 8b 可以一次執行兩個 8b SIMD 乘法, 也就是 (1024/8)*2 = 256 個.
所以若不考慮 data dependency 因素, 一個指令能否再塞進一個 VLIW 指令中以同一cycle執行, 這背後必須要同時考量 slot 是否 conflict, 以及 resource 是否有餘裕, 這兩者同時都滿足, 才能夠塞進同一個. 對於這樣的考量, 官方文件提供了一份整理好的總表
在這份總表中, 查找所需滿足的條件, 即反映了是否需要額外的 cycle 去執行所想加入的指令. 另外需要注意一件事,
請注意 resource 使用的平衡性, 像是 shift unit 只有一個, 千萬不要不明就裡, 將乘法全以 shift 替換就以為是優化, 在這樣的情況下, 有可能增加的 cycle 數目是以倍數來計, 對於這樣的處理器, 優化的策略在於儘量在乘法與Shift所
需要的運算數目中取得平衡. 其他比較常考慮的就是 load/store unit.
對於 HVX 指令中是否有讓我覺得印象深刻的? 我個人認為是 vdelta/vrdelta 以及 permute 所屬指令, 這個類別與一般 DSP 常提供的 PACK/UNPACK 太不同了, 它提供了一個 general permute network 來做兩個 vector register data 的次序調移.
其實當下我覺得這東西一時間我只想到 sorting network, 而且還搞不清楚狀況, 直到看到了一個
圖解, 發現這 1 cycle 運算真是潛力無窮, 所有 1-1 mapping 的方式的次序改變, 只要能算得出 Vv, 就可以快速地作 Vu 至 Vd 的轉換. 而對於像我腦子這麼簡單的人, 它也提供了入門版的 shuffle 與 pack 指令.
最後來談 HVX 的資料取得方式來源 - L2-cache/L2-TCM, 對於一般 DSP 在資料傳輸與核心運算的平行部分, 儘管 HVX 文件沒有特別強調 DMA, 但實際上 HVX 提供了對應功能的 L2FETCH, 以一個多媒體為訴求的 DSP 來說, 其也支援了 1D/2D 資料的傳輸.
而到此並沒有特別的地方. 值得一提的是, 這裡的 Level-2 是只對於 V6x 而言, 但對於 HVX 的角度來說, 它就是如同 L1 的存在, 最特別地方在於 V6x 架構中 L2 可以扮演兩個角色, 在 block diagram 中已經標明了
這設計是很有趣的, 因為這兩種記憶體各自有著不同且互補的特性:
- cache本身有著 transparent 特性, 對於 programmer 本身並不需要特別改變, 只需要注意沒有不利於 cache 管理機制的存取方式, cache 能夠帶來效能的 scalability (隨著 cache 的加大而效能改善), 功能上的 portability (無論 cache 大小, 功能本身的正確性不會影響), 這樣的特性對於一些高度 locality 的程式很有幫助, 也能降低程式的實作複雜度, 但是 cache 本身有著難以精確控制的特性, 對於一些資料的存取並不能有時間上的保證.
- Tightly-Coupled Memory (TCM), TCM 有著指定的位置, 距離 processor 很近, 有著存取時間上的保證, 但是由於使用上 Programmer 必須意識到它的存在, 並且自己管理, 所以 TCM 若容量增大也在不修改程式的情況下無法直接增加效能; TCM 若容量變小在不修改程式的情況下程式可能無法正常運作. 但是它卻能有時間上存取的保證, 因此對於一些像是 Lookup-Table (LUT) 的需求, TCM 顯得相當重要
在 V6x 中能將 L2-cache 做分割, 雖然相關機制未知, 但是運作上能將部分 L2 作為 Cache 而部分作為 TCM 是標注在 V6x 的 features list 中:
下篇文, 沒意外的話也是系列文最後一篇, 要談論的是 Hexagon 680 的 Thread Model 以及 Execution Model