* [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

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

* 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:
> 
* 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

* `dut.cc` 即為要合成成 RTL 的 SystemC module
Architecture:

* `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


* `cynw_utilities` functions

* `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

* `HLS_FLATTEN_ARRAY(array)`
將 array 的每一個 element 都 flatten 成一個 register。小型 array 建議可以 flatten,flatten 後有機會增加 constant propagation

* `HLS_MAP_TO_REG_BANK(array)`

* 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 );
> ```
>
> 
* `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 );
> ```
>
> 
* `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
> 
> 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
> 
> throughput = 1 output / 4 cycles
> latency = 4
> 每個 cycle 內最多需要 1 個 adder
> 2. Loop pipelining, II = 1
> 
> throughput = 1 output / 1 cycle
> latency = 4
> 每個 cycle 內最多需要 3 個 adder
> 3. Loop pipelining, II = 1
> 
> throughput = 1 output / 1 cycle
> latency = 3
> 每個 cycle 內最多需要 3 個 adder
* stall 方式
* `HARD_STALL`: 在沒有新的 input 被讀取進來、或沒有新的 output 被輸出,pipeline 就會 stall 住以 keep 住 state,需要等下一個 input 輸入/output 輸出,pipeline 才會停止 stall 並輸出/輸入
> ex:
> 
> cycle 6 沒有 input 被讀取進來,output D 前被 stall
* `SOFT_STALL`: `HARD_STALL` 的情況改成不會讓 pipeline stall
> ex:
> 
> 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:
> 
> ++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;
> }
> }
> ```
>
> 時序圖會變成這樣:
> 
> cycle 4 會需要一次 output 兩個。但此 module 只有 1 個 output port
:::
* `HLS_CONSTRAIN_LATENCY(最小允許的延遲 cycle 數, 最大允許的延遲 cycle 數, "constraint 名稱")`
* 嘗試將此 block 的延遲控制在此區間
* 第二個參數可為 `HLS_ACHIEVABLE`,表示盡可能減少至最低的 latency upper bound