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