---
# System prepended metadata

title: HLS (High Level Synthesis) 筆記
tags: [SystemC]

---

* [Stratus HLS User Guide](https://365nthu-my.sharepoint.com/:b:/g/personal/113065507_office365_nthu_edu_tw/EdQ8Otkd9tNDlMYwN11uwksBxDHfa5WHvMtT7FPJRboAGQ?e=WQyzbL)
* [Stratus HLS Reference Guide](https://365nthu-my.sharepoint.com/:b:/g/personal/113065507_office365_nthu_edu_tw/EeCSIwh-jpFGgZ2cYMuaUeMB7DqOXvB8EuHKuWnUTAU9vA?e=vwCTeH)

# HLS (High Level Synthesis) 重點

* HLS 使用 C/C++ 產生 RTL code (e.g. SystemVerilog) 使得不需要做 explicit details on FSM (Finite State Machine)。
    > 因為寫 RTL 時，FSM 都要定義好每個 state 在所有 cycle 的行為
* Quality: 實作 DSP 演算法下，HLS 合成的 RTL code 通常會有近似或更好於 hand-written RTL code 的 PPA (Power, Performance, Area)；而 CPU/DSP cores 通常 hand-written RTL code 會有更好的 PPA
    > 例如 cache，會有許多 resource allocation optimization 的演算法，這些 HLS 不容易合成

## Flow Chart

![Stratus_Ch1_Introduction_頁面_05](https://hackmd.io/_uploads/BJCrFui01g.png)

* simulation model (Lab 1 - Lab 4 做的) 跟 design model (SystemC 寫的 HLS) 可以 reuse 的好處:
    1. 可以快速驗證 interface (但執行效率差)
    2. simulation model 的 testbench 可以使用在 RTL 的 testbench

### High-level Synthesis

![Stratus_Ch1_Introduction_頁面_08](https://hackmd.io/_uploads/SyBXXUkklx.png)

* Control and Data Flow Extraction
    * 建立 **control flow diagram (graph) (CFG)** 及 **data flow diagram (graph) (DFG)**
        * control flow diagram (graph) (CFG)
            表達 multiplex, jump 等 control flow
        * data flow diagram (graph) (DFG)
            表達 data flow
            > ex:
            > ![image](https://hackmd.io/_uploads/rJ4xrLkyxe.png)
* Resource Allocation
    根據所有 operations 及 objective 計算需要用的 resources (multiplexer, register, memory, ...)
    > Example 1:
    > ```cpp=
    > if (a < b) {a = a + c; b = b - c;}
    > else {b = b + c; a = a - c;}
    > ```
    > 若 objective 為 area: 只要一個 adder resource 就好
    > 若 objective 為 performance: 使用兩個 adder resources
* Scheduling
    根據所有 operations 依照 Resource Allocation 產生的 resources 做每個 cycle 的 scheduling。最後可產生一 FSM
    > 在 [Example 1](https://hackmd.io/pzrTfGbjTbyT2SIKr_mF7A?both=&stext=1263%3A10%3A0%3A1744951305%3ACyM-uy) 若 objective 為 performance 時，schedule 兩個 add operation 執行在同一個 cycle
* Functional Unit & Register Binding
    * Functional Unit Binding
        依照 Resource Allocation 產生的 functional units (ex: ALU、multiplier)，binding operators 與這些 functional units
    * Register Binding
        依照 Resource Allocation 產生的 registers (ex: 變數或中間結果當需要用到 register 暫存結果時，Resource Allocation 會產生 registers)，binding 變數或中間結果與這些 registers

## Cadence Stratus Architecture

![image](https://hackmd.io/_uploads/BJjmpdXJlx.png)

* `dut.cc` 即為要合成成 RTL 的 SystemC module
    Architecture:
    ![image](https://hackmd.io/_uploads/rJfW0OQJge.png)
* `system.cc` 即將 testbench、dut 包成一個 top module

:::info
++Remark++

1. memory 的合成是用 **memory compiler**，因為 memory compiler 要根據製程決定，故不在 HLS 的工具裡面。memory compiler 會輸出所需 memory 的 area、power、latency 等。目前課堂的範例合成時都沒有用 memory compiler，是用 dummy 的
:::

# Coding Style

* 只使用 **static** data structure，不要做 dynamic memory allocation
    > 因為 dynamic memory 是由 OS 管理。HLS 不會做 OS 的事情。任何用 OS API 的都是不可 synthesizable 的。而且 dynamic 的行為在許多 optimization algorithm 不適用
* Hardware constraints
    * 分派 memory access pattern: 哪些資料要優先帶到 memory、盡量讓 memory access amortize (平攤)、盡量達到 memory reuse
        > 相關概念: Memory Arithmetic Intensity (每 byte memory 可以做多少 operation)
    * Computation complexity
        > ex: multiply 改成 shift
    * I/O protocols (什麼時候 I/O data)
* 多個 module 間不使用 global variables、沒有 pointers 指向其他 module 的空間。且 variable 的 scope 越小越好
* 當用參數來客製化一個 module 時，使用 **template** 比起宣告在 constructor 更容易讓 code 易讀
* 當使用 `cynw_p2p` 傳自定義的 struct 時，**需要手動寫此 struct 的 copy constructor, `==`、`=` operator overloading、`sc_trace()` function，否則會合成失敗!!!**
    :::spoiler Example (array)
    ```cpp
    struct input_array_t {
        input_t data[SIZE];
        
        // Default Constructor
        input_array_t() {}
        
        // Copy Constructor
        input_array_t( const input_array_t & obj ) {
            for(int i=0;i<SIZE;i++) {
                data[i]=obj.data[i];
            }
        }
        
        // == operator
        inline bool operator==(const input_array_t & other) {
            for(int i=0;i<SIZE;i++) {
                if(data[i]!=other.data[i]) return false;
            }
            return true;
        }
        
        // Assignment operator
        inline input_array_t & operator=(const input_array_t & other) {
            for(int i=0;i<SIZE;i++){
                data[i]=other.data[i];
            }
            return *this;
        }
        
        // sc_trace function
        inline void sc_trace(sc_trace_file * tf, const input_array_t & object, const sc_string & name) {
            for (int i=0; i < SIZE; i++ ) {
                char buf[16];
                sprintf(buf,".data%d",i);
                sc_trace(tf, object.data[i], name + sc_string(buf));
             }
         }
        
        // 非必要；可用來做 cout
        inline ostream & operator <<(ostream & os, const input_array_t & a) {
    #ifndef STRATUS
            for (int i=0; i < SIZE; i++ ) {
            os << " " << a.data[i].to_string().c_str();
             }
    #endif
            return os;
        }
        
        // 非必要；可用來做 cin
        inline istream & operator >> ( istream & is, input_array_t & obj ) {
            bool eof = !is.good();
            is >> std::ws;
            for (int i=0; i < SIZE; i++ ) {
                eof = is.eof();
                if(!eof) { is >> obj.data[i]; }
            }
            return is;
        }
    }
    ```
    :::
* 在 thread 內做 reset 而不是在 constructor
* 使用 Stratus 時就用他們的 fixed-point types (ex: `cynw_float`, `cynw_fixed`)，**Stratus 目前已不支援 `sc_fixed`**。並且 `cynw_fixed` 的 template parameter 如果沒有要使用 saturation 就不用宣告成要做 saturation (因為做 saturation 也會消耗時間)
* 能用 unsigned type 就用 unsigned，因為 signed 會耗更多的空間及運算 (二補數)。故把減法改成加法也可以避免出現 signed 情況
    > ex:
    > `if (y > x - 1)` 改成 `if (y + 1 > x)`
* 可以把 if 條件寫成互斥 (越清楚越好)，這樣 Stratus 做 optimization 時就能對此更容易做 optimization
    > ex:
    > ```cpp
    > if (a < b)
    >     // ...
    > if (a >= b)
    >     // ...
    > ```
    > 改成
    > ```cpp
    > tmp = a < b;
    > if (tmp)
    >     // ...
    > if (!tmp)
    >     // ...
* 使用 C++ native types 時，變數要先初始化 (除了 array，因為 array 初始化要用 loop，會花很多時間。可看 report 並試過 performance 再做決定)。SystemC type 會自己初始化
* **SystemC code 決定 functionality** (非 implementation details)；**Stratus directives 做 Design Space Exploration**
    => **除了 I/O，不要做太多決定 cycle-level 的事**，讓 HLS tool 自己做決定
* Bit Trimming: 在一個 expression 內，HLS tool 能自己推出 l-value 所需的 bit 數的地方就不要自己指定 bit 數，**除了 loop**，因為 HLS tool 無法準確推出 loop 內變數所需的 bit 數
* Bit Width Extension: 如果 expression 的 l-value 的 bit 數指定比 r-value 少，HLS tool **不會** 自己 trim r-value 的 bit 數 (因為可能會讓結果錯誤)
    > ex:
    > ```cpp=
    > sc_uint<5> a = f1();
    > sc_uint<16> b = f2();
    > sc_uint<9> c = f3();
    > sc_uint<16> result;
    >
    > result = (a * b) + c;
    > ```
    >
    > HLS tool 不會自己 trim r-value 成 16 bit => 可能會需要多 allocate 一個 22 bit register 暫存 a * b 的結果
* Constant Propagation: HLS tool 會自己做 constant propagation，減少不必要的硬體資源
    > ex:
    > ```cpp=
    > unsigned func(int val) {
    >    return (val * val);
    > }
    >
    > for(int i = 1; i < 4; i++ ) 
    >    result += func(i);
    > ```
    >
    > HLS tool 會自動轉成:
    > ```cpp=
    > result = (1 * 1) + (2 * 2) + (3 * 3);
    > ```
* 盡量使用前再宣告變數 (因為宣告變數就表示佔用一個 register)
* ++Def++ (Loop-Carried Dependency (LCD))
    An LCD is a *variable* that is live from one iteration of a loop to the next
    > ex: 
    > ```cpp=
    > for(int i = 1; i < 4; i++ )
    > {
    >     a = b*i;
    >     result += func(i)+a;
    > }
    > 
    > outputPort.put(result);
    > ```
    > `result` 變數即為 LCD
    > 
    可以看 HLS tool 產生的 LCD report (`--message_detail=1`)，如果裡面有不是自己設計需要的就可以自己改寫程式移除
* Synthesizable Data Types
    ![image](https://hackmd.io/_uploads/rkuFC6Slex.png)
    ![image](https://hackmd.io/_uploads/Hyoc06Segl.png)
* `cynw_utilities` functions
    ![image](https://hackmd.io/_uploads/Sk4W0MJWgl.png)
    * `div()` 會讓速度變慢、面積減少
* 由於 memory access 皆需要花 cycle 數，故建議不要寫重複 access 相同記憶體位置、相同值的 code
    > ex:
    > ```cpp=
    > a1 = mem[1] + mem[2]
    > a2 = mem[1] + mem[3]
    > ```
    > 至少需要 6 個 cycle (access mem[1], access mem[2], add, access mem[1], access mem[3], add)
    >
    > ```cpp=
    > a = mem[1]
    > a1 = a + mem[2]
    > a2 = a + mem[3[]
    > ```
    > 只至少需要 5 個 cycle 
* Loop
    * **Loop bound 不要用變數!!!** (因為 loop unroll, loop pipeline 都無法用在 loop bound 為未知數時)。解決方法為可以把 loop 整體變成一個 function template 
* Function
    * **預設 function 皆為 inline**
    * 可用 `HLS_SCHEDULE_REGION()` 強迫把 function 成為一個可 reuse 的 hardware

# 語法

:::success
範例可至 [Lab 5](https://hackmd.io/TZEWmooHSxytHxg5F6mmPA) 查看
:::

## Interface

* `cynw_p2p<datatype>` (channel)、`cynw_p2p<datatype>::in` 及 `cynw_p2p<datatype>::out` (port)
    * 即為包裝好的 [ready/valid interface](https://hackmd.io/bFO7RLWYR76UMaVvXV4LUQ#adder-rdyvld)

## Directives

放在 C++ 程式的一個 block 的開頭

* `HLS_DEFINE_PROTOCOL("protocol 名稱")`
    * 表示此 block 是一個 protocol，要當作一個 atomic operation，需合成成同一個 clock cycle 或 pipeline stage
    > ex: 
    > ```cpp
    > {
    >   HLS_DEFINE_PROTOCOL("reset");
    >   
    >   // 此 block 可以放所有 reset cycle 要做的事
    >
    > }
    > ```
* Array 相關
    * Mapping
        * `HLS_MAP_TO_MEMORY(array, "自訂memory名稱")`
            **預設方式**，將 array 合成為一 memory，r/w 一個 element 需要 **1** 個 cycle
            ![image](https://hackmd.io/_uploads/Sk8oyCBexg.png)
        * `HLS_FLATTEN_ARRAY(array)`
            將 array 的每一個 element 都 flatten 成一個 register。小型 array 建議可以 flatten，flatten 後有機會增加 constant propagation
            ![image](https://hackmd.io/_uploads/ByOZe0Hxle.png)
        * `HLS_MAP_TO_REG_BANK(array)`
            ![image](https://hackmd.io/_uploads/SJCLl0reex.png)
    * Splitting
        * `HLS_SPLIT_ARRAY_DATA(array, 要split的bit長度)`
            > ex:
            > ```cpp=
            > sc_uint<64> arr[1024];
            > HLS_MAP_TO_MEMORY(arr,"mem1024X8");
            > HLS_SPLIT_ARRAY_DATA( arr, 8 );
            > ```
            >
            > ![image](https://hackmd.io/_uploads/BJoAW6O-ge.png)
        * `HLS_SPLIT_ARRAY_ADDR_LSB(array, addr_bits)`
            * 根據第 addr_bits 個 LSB 作分割
            > ex:
            > ```cpp=
            > sc_uint<8> arr[1024];
            > HLS_SPLIT_ARRAY_ADDR_LSB( arr, 1 );
            > ```
            >
            > ![image](https://hackmd.io/_uploads/H1hhMTdZee.png)
        * `HLS_SPLIT_ARRAY_ADDR_MSB(array, addr_bits)`
            * 同理 `HLS_SPLIT_ARRAY_ADDR_LSB()`
    * Merging
        * `HLS_MERGE_ARRAYS_DATA()`
        * `HLS_MERGE_ARRAYS_ADDR()`
    * Struct Packing
        * `HLS_PACK_ARRAYS(一個struct的array)`
            * 原本 Stratus 會把一個 struct 內的每個 member 產生一個 separate 的 memory 或 register bank。此 directive 可以把一個 struct 的 array 內的每個 element 排成一行 array
* `HLS_UNROLL_LOOP(ON/OFF/數字[, count], "loop 名稱")`
    * unroll 此 loop
        > ex:
        > Loop unrolling with 2 copies
        > ![image](https://hackmd.io/_uploads/rJ2sndQkxe.png)
        > throughput = 2 outputs / 4 cycles
        > latency = 4
        > 每個 cycle 內最多需要 2 個 adder
    * `count` 為一整數表示每一個 iteration 只展開幾個 loop。第一個參數為 `COMPLETE`、`CONSERVATIVE` 及 `AGGRESSIVE` 會需要指定 `count` 參數
    * 第一個參數表示每次展開幾個 loop body
        * `OFF`: 不展開
        * `ON`: 展開全部 (不含 inner loop)
        * `COMPLETE`: 展開全部 (含所有 inner loop)
        * `CONSERVATIVE` (保守式展開): 如果此 loop 的 upper bound 不確定 (為變數)，此設定會自動 handle 避免 loop 次數無法被 `count` 整除的情況
        * `AGGRESSIVE` (攻擊性展開): 只有在此 loop 的 upper bound 確定、且 loop 次數可以被 `count` 整除時才可使用。會不合成迴圈終止條件的硬體，直接複製迴圈至指定次數
* `HLS_PIPELINE_LOOP(stall方式, initiation interval, "pipeline loop 名稱")`
    * **flatten 此 loop 及此 loop 的所有 inner loop**，並做 pipelining
        
        ++Def++ (Initiation Interval (II))
        A **Initiation Interval (II)** is the time period between the start times of two successive iterations of a loop-body execution
        > ex:
        > 1. No loop pipelining, II = 4
        >     ![image](https://hackmd.io/_uploads/rJxi5d71xl.png)
        >     throughput = 1 output / 4 cycles
        >     latency = 4
        >     每個 cycle 內最多需要 1 個 adder
        > 2. Loop pipelining, II = 1
        >     ![image](https://hackmd.io/_uploads/ry9csOXyxe.png)
        >     throughput = 1 output / 1 cycle
        >     latency = 4
        >     每個 cycle 內最多需要 3 個 adder
        > 3. Loop pipelining, II = 1
        >     ![image](https://hackmd.io/_uploads/SkEy2d71gx.png)
        >    throughput = 1 output / 1 cycle
        >    latency = 3
        >    每個 cycle 內最多需要 3 個 adder
    * stall 方式
        * `HARD_STALL`: 在沒有新的 input 被讀取進來、或沒有新的 output 被輸出，pipeline 就會 stall 住以 keep 住 state，需要等下一個 input 輸入/output 輸出，pipeline 才會停止 stall 並輸出/輸入
            > ex:
            > ![image](https://hackmd.io/_uploads/SySr62dbgl.png)
            > cycle 6 沒有 input 被讀取進來，output D 前被 stall
        * `SOFT_STALL`: `HARD_STALL` 的情況改成不會讓 pipeline stall
            > ex:
            > ![image](https://hackmd.io/_uploads/HkLKandbee.png)
            > cycle 6 沒有 input 被讀取進來，但output D 不會被 stall
    :::info
    ++Remark++

    1. HLS tools 可針對不同 latency, II 需求做 **Design Space Exploration!** (hand-written RTL 很難)
    2. 需注意太低的 initiation interval 可能造成 data dependency 無法滿足或 port conflict
        > ++Example 1 (data dependency)++
        > ```cpp=
        > for(i = 0; i < 4; i++) {
        >     HLS_PIPELINE_LOOP(HARD_STALL, 1, "loop_pipe");
        >     X = A * ( i + X );
        > }
        > ```
        >
        > 時序圖會變成這樣而 error:
        > ![Screenshot 2025-05-19 220608](https://hackmd.io/_uploads/Hke4jh_-gx.png)

        > ++Example 2 (port conflict)++
        > ```cpp=
        > while( true ) {
        >     HLS_PIPELINE_LOOP( HARD_STALL, 1, "main_loop_pipeline" );
        >     {
        >         HLS_DEFINE_PROTOCOL("read");
        >         in_rdy= 1;
        >         wait(1);
        >         A = A_in.read();
        >         B = B_in.read();
        >         in_rdy = 0;
        >     }
        >     {
        >         HLS_CONSTRAIN_LATENCY( 0, 1 "algorithm_latency" );
        >         X = A + B;
        >         Y = A * B;
        >     }
        >     {
        >         HLS_DEFINE_PROTOCOL("write");
        >         out_vld = 1;
        >         data_out.write( X ); wait(1);
        >         data_out.write( Y ); wait(1);
        >         out_vld= 0;
        >     }
        > }
        > ```
        > 
        > 時序圖會變成這樣:
        > ![Screenshot 2025-05-19 221335](https://hackmd.io/_uploads/HJ1an3_-ex.png)
        > cycle 4 會需要一次 output 兩個。但此 module 只有 1 個 output port
    :::
* `HLS_CONSTRAIN_LATENCY(最小允許的延遲 cycle 數, 最大允許的延遲 cycle 數, "constraint 名稱")`
    * 嘗試將此 block 的延遲控制在此區間
    * 第二個參數可為 `HLS_ACHIEVABLE`，表示盡可能減少至最低的 latency upper bound