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


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 (預設)

* SC_RND

* OVFLW: overflow mode
* SC_WRAP (預設)
丟棄超出表示範圍的位元
* SC_SAT

* SC_SAT_ZERO

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

* `.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 範例程式
架構:

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