---
# System prepended metadata

title: SystemC 筆記
tags: [SystemC]

---

* [SystemC Reference](https://365nthu-my.sharepoint.com/:b:/g/personal/113065507_office365_nthu_edu_tw/ETQEfOsUnCNGqTZj4NkykXQBtiAF6CkbVSnowitjt6gHgA?e=9YTg3H)

# Language Architecture

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

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

Modules: 一個 abstraction，依照實際需要的而設計，可大可小 (e.g. ALU 是 CPU 的 sub-module)

# Starting Point

`main()` 已存在 SystemC library 內，會幫我們做一些初始設定。是用 `sc_main()` 作為撰寫起始點。

```cpp
int sc_main(int argc, char* argv[]) {
    // [ELABORATION]
    
    sc_start(); // Simulation begins & ends in this function!
    
    // [POST-PROCESSING]
    
    return EXIT_CODE;
}
```

* `sc_start([數值, 時間單位])` 表示只要跑多少時間的模擬

:::info
++Remark++
1. 在 elaboration 階段時 construct instances 時盡量不要用 **new** 做 dynamically construct，用 statically construct compiler 可以在 runtime 前就知道 modules 的 interconnections
:::

# Data Types

Namespace 為 **`sc_dt`**

:::info
++Remark++
1. native C++ data types (ex: `int8_t`) 會比用 SystemC 可自訂 bits 數的 types 用較少記憶體和較快速度，因為這些特殊 bits 的 type 可能會需要額外的空間儲存額外的資料
2. Performance:
    | Relative Speed | Data Type |
    | -------- | -------- |
    | Fastest     | Native C/C++ Data Types (e.g., `int`, `double` and `bool`) |
    | | `sc_int`, `sc_uint` |
    | | `sc_bv`|
    | | `sc_logic`, `sc_lv` |
    | | `sc_bigint`, `sc_biguint` |
    | | `sc_fixed_fast`, `sc_fix_fast`, `sc_ufixed_fast`, `sc_ufix_fast` |
    | Slowest | `sc_fixed`, `sc_fix`, `sc_ufixed`, `sc_ufix` |

:::

## Logic Vector

* bit vector: `sc_bv<W>`
    * 可用 C++ string 做 initialize
        > `sc_bv<5> positions = "01101";`
    * slice: `.range(MSb position, LSb position)` 或 `(MSb position, LSb position)`
* logic vector: `sc_lv<W>`
    * 四態邏輯 (0, 1, x (unknown，表示還沒初始化，是 0 或 1 電路都要正常運作), z)
    * 可用 C++ string 做 initialize
        > `sc_lv<5> positions = "01xz1";`
    * slice
* single-bit logic: `sc_logic`

## Integer

* **<= 64 bits** integer: `sc_int<W>`, `sc_uint<W>`
* **> 64 bits** integer: `sc_bigint<W>`, `sc_biguint<W>`

可用 C++ string 做 initialize
> sc_uint<8> I1 = "0x35";

concatenate: `()`
> I4 = (I3, I1.range(7,4), I2(3,0), I1(3,0));

轉成字串: `.to_string([SC_BIN | SC_HEX | ...])`
> sc_int<5> Int1 = "-0d13";
> cout << " SC_BIN=" << Int1.to_string(SC_BIN);

:::info
++Remark++
1. 盡量用 unsigned，因為 signed 要做二的補數運算，會較耗時間
2. 將 integer 分為 <= 64 bits 及 > 64 bits 是因為一個 register 只有 64 bits，大於 64 bits 需要兩個以上的 register，會需要記錄額外的資訊 (e.g. 長度 vector)，所以盡量用非 bigint
:::

## Fixed-Point Types

可以指定小數點在哪裡的浮點數表示法

* 一般 fixed-point types: `sc_fixed<WL,IWL[,QUANT[,OVFLW[,NBITS]>`, `sc_ufixed<WL,IWL[,QUANT[,OVFLW[,NBITS]>`
    * *\*ed* 表示 template 內的參數要是 **constant**
    * WL: 總 bit width
    * IWL: 整數 part 的 bit width
    * QUANT: quantization mode
        * SC_TRN (預設)
            ![image](https://hackmd.io/_uploads/B1ECGitq1l.png)
        * SC_RND
            ![image](https://hackmd.io/_uploads/Skek7oFq1l.png)
    * OVFLW: overflow mode
        * SC_WRAP (預設)
            丟棄超出表示範圍的位元
        * SC_SAT
            ![image](https://hackmd.io/_uploads/SkHMNstq1x.png)
        * SC_SAT_ZERO
            ![image](https://hackmd.io/_uploads/HyO7VoF9ye.png)
* **< 53 bits** fixed-point types: `sc_fixed_fast`, `sc_ufixed_fast`

:::info
++Remark++
1. **若程式內有使用到 Fixed-Point Data Types，則編譯時要加 flag `-DSC_INCLUDE_FX`**
2. 除非必要，浮點數運算首選使用 Fixed-Point Types，因一般的浮點數較 expensive
3. 注意在 SystemC 內寫一般 decimal 浮點數的話，若被轉成 Fixed-Point Types，首先需從 decimal 轉成 binary，此時就會造成 **error (誤差)**，再把 binary 轉成 Fixed-Point Types 時又會造成第二次 **error**
4. 另外有 `sc_fix`, `sc_ufix`, `sc_fix_fast` 及 `sc_ufix_fast` 可以讓 template 內的參數可以是 **variable**，但 **不要用**，因為我們在模擬 hardware，hardware 都是 fixed 的
:::

# Modules

* 宣告 module: `SC_MODULE(module_name) {}`
* 定義 constructor: `SC_CTOR(module_name) {}`
* 定義 **有參數的** constructor: 在 module 內新增一行 `SC_HAS_PROCESS(module_name);`，再用一般 C++ 的方式寫 constructor
    > ex:
    > ```cpp
    > SC_MODULE(module_name) {
    >     SC_HAS_PROCESS (module_name);
    >     module_name(sc_module_name instname, int i) : sc_module(instname), id(i) {
    >         SC_THREAD(my_thread);
    >     }
    >     void my_thread (void);
    >     int id;
    > };
    > ```
* process 可分為 **thread** 及 **method**
    * 註冊 thread: `SC_THREAD(thread_name)`
        * 適用於循序邏輯
        * 只會執行一次
        * 可呼叫 `wait()`
    * 註冊 method: `SC_METHOD(method_name)`
        * 適用於組合邏輯
        * 可被 trigger 多次
        * 不可呼叫 `wait()`

> ```cpp
> SC_MODULE(module_name) {
>     // module body
>     
>     SC_CTOR(module_name) {  // Constructor
>         // SC_THREAD(thread_name);
>         // SC_METHOD(method_name);
>     }
> };
> ```

:::info
++Remark++
1. 宣告 `SC_THREAD`, `SC_METHOD` 時會將此 process 註冊在 SystemC kernel 內，當執行 `sc_start()` 時才會開始 **一個一個** 執行註冊的 processes
2. SystemC 的所有 constructor **第一個參數** 一定要是一個 string (type 為 **sc_module_name**) 表示 name
:::

# Time & Wait & Event & Sensitivity & `next_trigger()`

## Time

* `sc_time time_name(數值, 單位)`
* 設定 time resolution (則所有 SystemC 內部表示的時間都會是以此為單位): `sc_set_time_resolution(double 數值, 單位)`
* 取得 **模擬時間 (初始為 0)**: `sc_time_stamp()`

## Wait

當呼叫 `wait()` 時，會將執行權限轉換給 SystemC kernel

* 模擬 wait 一指定時間: `wait(數值, 單位)`, `wait(sc_time object)`
* wait 一 event: `wait(sc_event object)`

:::info
++Remark++
1. `wait(event)` 要在 event `notify()` **前** 被執行，否則不會收到 notification
2. 在 `sc_start()` 開始模擬時，SystemC kernel 會依據 elaboration 階段已註冊的所有 process，將初始狀態為 ready 的 process 放入 runnable queue。每個 process 會被依序調度並執行，直到遇到 `wait()`、`suspend()` 或自然結束為止。當 runnable queue 清空後，SystemC kernel 會 **計算所有 thread 最短的 wait 時間，將模擬時間加上此時間**，接著再繼續執行 wait 最短時間的 thread
3. `wait(SC_ZERO_TIME)` (`SC_ZERO_TIME` 即為 `0`) 也是會讓此 thread 執行權限轉換給 SystemC kernel，根據 1.，這種語法就可以讓 **在 `wait(SC_ZERO_TIME)` 之後、下一個 `wait()` 之前的所有指令是在同一個模擬時間點所有指令的 *最後* 被執行**
    > ex:
    > ![image](https://hackmd.io/_uploads/H1lcALNsJe.png)
    > Stmt_D4 一定會是時間點 t0+t1 最後一個被執行的指令
:::

## Event

* `sc_event event_name`
* `.notify([數值, 時間單位])` 做 notification，參數可設定多久後才 notify

:::spoiler 範例程式
```cpp=
// ...

sc_uint<2> a;
sc_event event1;

void thread1() {
    while(true){
        wait(event1);
        cout << setw(12) << sc_time_stamp() << setw(12) << a.to_string(SC_BIN) << endl;
        wait(1, SC_NS);
    }
}

void thread2() {
    while(true){
        a++;
        if(a==3) event1.notify();
        wait(1, SC_NS);
    }
}

SC_CTOR(my_module) {
    SC_THREAD(thread1);
    SC_THREAD(thread2);
    a=0;
}
```

輸出: 
```
2ns 0b011
6ns 0b011
```
:::

:::info
++Remark++
1. `notify(SC_ZERO_TIME)` 可在 **下一個 delta cycle 的 evaluate phase** 才 notify
2. event 機制很容易出錯，除非已經非常熟悉要模擬的 component 的行為，沒事盡量不用 event 在 module 之間溝通，而改用 channel 較好
:::

## Sensitivity

在 `SC_CTOR` 內註冊完一個 process 後，宣告 sensitive list: **`sensitive << [event 或 time] [<< ...]`** 表示前面宣告的這個 process 只要該 event 或 timeout 發生就會被執行

:::spoiler 範例程式
```cpp=
SC_MODULE(MyModule) {
    sc_signal<bool> sig;
    void my_method() {
        cout << "Method triggered at " << sc_time_stamp() << endl;
    }
    SC_CTOR(MyModule) {
        SC_METHOD(my_method);
        sensitive << sig; // 靜態敏感性：當 sig 改變時觸發
    }
};
```
:::

:::info
++Remark++
1. sensitive list, `reset_signal_is()` 等這種對 process 的設定都只對上一個宣告的 process 有效
:::

## `next_trigger([event 或 time])`

* **只能放在 `SC_METHOD` 內部，此條件只有一次性**，被執行時表示當參數內定義的 event 或 timeout 發生時，此 `SC_METHOD` 會再被執行一次。這會 **暫時替代** sensitive list，當 `next_trigger()` 的情況被滿足後即回到原本 sensitive list 的觸發條件

# Channel

:::info
++Remark++
1. 只有 `sc_signal` synthesizable (可合成)
:::

++Def++ (delta cycle)
使用 delta cycle 是因為若所有值的更新和觸發都在同一時間生效的話，模擬結果可能會錯誤。
一個 delta cycle 分為兩個階段:
1. **evaluate phase**: 執行所有在此時刻可執行的 processes，若有 `sc_signal` 變化，這些變化會被排入 delta queue，不會立即更新
2. **update phase**: 更新 `sc_signal` 的值，可能會觸發新的 process

在 update phase 若有變化，則會進入下一個 delta cycle，直到沒有新的變化為止。
delta cycle 只會在相同的模擬時間內發生，並不會增加模擬時間，執行完所有 delta cycle 後，模擬時間才會往前。

## `sc_fifo<T>`

FIFO queue

class hierarchy (箭頭指向的為 parent):
![image](https://hackmd.io/_uploads/rka9CtajJe.png)

* `.read()`
    * blocking read
* `.nb_read()`
    * non-blocking read
* `.write(T&)`
    * blocking write
* `.nb_write()`
    * non-blocking write

:::info
++Remark++
1. non-blocking 通常使用在連接多個 channel 時
2. 如果只要使用特定 non-blocking/blocking input/output，可以直接使用這些 parent classes
:::

## `sc_mutex`

* `.lock()`
    * 即為 OS semaphore 的 wait()
* `int .trylock()`
    * non-blocking lock
* `.unlock()`
    * 即為 OS semaphore 的 signal()

## `sc_semaphore`

* 語法: `sc_semaphore 名稱(semaphore值)`
* `.wait()`
    * 即為 OS semaphore 的 wait()
* `int .trywait()`
    * non-blocking wait
* `.post()`
    * 即為 OS semaphore 的 signal()
* `int .get_value()`
    * 回傳 semaphore 值

## `sc_signal<T>` (evaluate-update channel, signal channel)

與一般變數不同，**修改 `sc_signal` 的值之後，直到下一個 delta cycle 值才會改變**

* `.write(T&)`
* `.read()`
* `.value_changed_event()`
    * 若 `sc_signal` 的值 **有改變**，到下一個 delta cycle 時回傳 event 表示值被改變
* `.event()`
    * 當 `sc_signal` 的值 **有被寫入**，無論值有沒有改變，**都會** 到下一個 delta cycle 時回傳 event
* `sc_clock`
    * 即為一個 `sc_signal<bool>` 加上一個 process 自動更新 0, 1, ...

:::spoiler 範例程式
```cpp=
SC_MODULE(xor_module) {
    SC_CTOR(xor_module) {
        SC_METHOD(main_method);
        sensitive << a_sig << b_sig;
        dont_initialize();
        SC_THREAD(test_thread);
    }
    
    void test_thread() {
        a_sig.write(sc_logic ('1'));
        b_sig.write(sc_logic ('0'));
        cout << sc_time_stamp() << " " << "a=" << a_sig << " " << "b=" << b_sig << " " << "c=" << c_sig << endl;
        wait(1, SC_NS);
        b_sig.write(sc_logic ('1'));
        cout << sc_time_stamp() << " " << "a=" << a_sig << " " << "b=" << b_sig << " " << "c=" << c_sig << endl;
        wait(1, SC_NS); 
        cout << sc_time_stamp() << " " << "a=" << a_sig << " " << "b=" << b_sig << " " << "c=" << c_sig << endl;
        a_sig.write(sc_logic ('0'));
        wait(1, SC_NS);
        cout << sc_time_stamp() << " " << "a=" << a_sig << " " << "b=" << b_sig << " " << "c=" << c_sig << endl;
    }
    
    void main_method() {
        c_sig.write(a_sig.read()^b_sig.read());
    }
    
    sc_signal<sc_logic> a_sig, b_sig, c_sig;
};

 int sc_main(int argc, char* argv[]) {
    xor_module xor1("xor1");
    sc_start();
    return 0;
 }
```

輸出:
```
0 s a=X b=X c=X
1 ns a=1 b=0 c=1
2 ns a=1 b=1 c=0
3 ns a=0 b=1 c=1
```
`main_method()` 與 `a_sig.write(sc_logic ('1'))`、`b_sig.write(sc_logic ('0'))` 在同一個 delta cycle 內被執行，所以 a, b, c 都會在 1 ns 時讀取到被更新的值
:::

:::info
++Remark++
1. 在同一個 delta cycle 內，**只能有一個 process 寫入同一個 `sc_signal`**
:::

# Port

Port 使用在讓一個 module 接到一個 channel。Port 即為一個在該 module 外的 channel 的 **pointer** 的包裝 (不全然是 pointer)

* 語法: `sc_port<channel type> port名稱`
* 使用 operator `->` 來 access channel
* 常用的一些 port macro
    * `sc_in<T>` == `sc_port<sc_signal_in_if<T>>`
    * `sc_out<T>` == `sc_port<sc_signal_out_if<T>>`
    * `sc_inout<T>` == `sc_port<sc_signal_inout_if<T>>`

:::spoiler 範例程式
架構:
![image](https://hackmd.io/_uploads/ryO0N3Toyl.png)

```cpp=
SC_MODULE(writer) {
    SC_CTOR(writer) {
        SC_THREAD(main_thread);
    }

    void main_thread() {
        int d1 = 0; int d2 = 0; char com = 'p'; 
        char count=0;
        while (true) {
            if (data->num_free() > 1) { //We have two items
                d1 = count; d2 = count+1; count++; com = (count%2)?'p':'m';
                data->write(d1);
                data->write(d2);
                command->write(com);
                cout << sc_time_stamp()<<":write "<<d1<<d2<<com<<endl;
                wait(1, SC_NS);
            } else {
                cout << sc_time_stamp()<<":FIFO full "<<data->num_free()<<endl;
                wait(1, SC_NS);
            }
        }
    }

    sc_port<sc_fifo_out_if<int>> data;
    sc_port<sc_fifo_out_if<char>> command;
};

SC_MODULE(computer) {
    SC_CTOR(computer) {
        SC_THREAD(main_thread);
    }

    void main_thread() {
        int d1 = 0; int d2 = 0; int output = 0; char com = 'x';
        wait(2, SC_NS);
        while (true) {
            d1 = data->read();
            d2 = data->read();
            com = command->read();
            switch (com) {
                case 'p’: output = d1 + d2; break;
                case 'm’: output = d1 * d2; break;
                default: output = 1;
            }
            cout << sc_time_stamp() << ":compute output=" << output << endl;
            wait(2, SC_NS);
        }
    }
    sc_port<sc_fifo_in_if<int>> data;
    sc_port<sc_fifo_in_if<char>> command;
};

SC_MODULE(top_module) {
    SC_CTOR(top_module) : writer1("writer1"), computer1("computer1"), data_channel(2), command_channel(1) {
        writer1.data(data_channel);
        writer1.command(command_channel);
        computer1.data(data_channel);
        computer1.command(command_channel);
    }

    writer writer1;
    computer computer1;
    sc_fifo<int> data_channel;
    sc_fifo<char> command_channel;
};

int sc_main(int argc, char* argv[]) {
    top_module top("top");
    sc_start(10, SC_NS);
    return 0;
}
```
:::

## Signal Port Specializations

此種 port 可以產生 event，則此 port 可以被加進 sensitive list 裡 (例如 `sc_in_clk` (為 `sc_clk` channel 的 port))

> ex:
> ```cpp
> sc_in_clk clock;  // singal port specialization
> ...
> sensitive << clock.pos();
> ```

# 補充

## SystemC AMS Extension

Analog mixed signal extension

RF interfaces, power electronics, sensors, actuators