# DCS Lab02 說明文檔 ###### tags: `DCS` `SystemC` ###### 上課時間 : 2024/03/14 [TOC] ## Basic of C++ - pointers and references - A pointer is a variable that holds the memory address of another variable. A pointer needs to be dereferenced with the * operator to access the memory location it points to. - A reference variable is an alias, that is, another name for an already existing variable. ![image](https://hackmd.io/_uploads/rkbaZQ066.png) - Template : Pass the data type as a parameter so that we don’t need to write the same code for different data types. ```cpp template<typename T> class TheClassName { // T can be treated as a type inside the class definition block } int main() { TheClassName<int> a; TheClassName<double> b; TheClassName<AnotherClassName> c; } ``` - Operator Overloading : giving special meaning to an existing operator in C++ without changing its original meaning ```cpp struct Complex { Complex( double r, double i ) : re(r), im(i) {} Complex operator+( Complex &other ); void Display( ) { cout << re << ", " << im << endl; } private: double re, im; }; // Operator overloaded using a member function Complex Complex::operator+( Complex &other ) { return Complex( re + other.re, im + other.im ); } int main() { Complex a = Complex( 1.2, 3.4 ); Complex b = Complex( 5.6, 7.8 ); Complex c = Complex( 0.0, 0.0 ); c = a + b; c.Display(); } ``` ## `sc_signal<T>`讀寫數據方式 要從`sc_signal`這個class讀取訊號,有兩種方法,可以call member function`T& read()`或是運算子重載`()`。 ```cpp sc_signal<int> s; // method 1 - member function std::cout << s.read(); // method 2 - operator overloading std::cout << (s); ``` 同理,要從`sc_signal`這個class寫入訊號,有兩種方法,可以call member function`void write(const T&)`或是運算子重載`=`。 ```cpp sc_signal<int> s; // method 1 - member function s.write(5); // method 2 - operator overloading s = 5; ``` ## 如何在`sc_signal<T>`選取指令bit數 (HW01寫ALU會用到) 給定指令 ```cpp sc_signal<sc_uint> instruction; ``` 想法 : `sc_uint`的class有提供成員函數 `range(int high, int low)`和運算子重載 `operator(int high, int low)`,所以可以... ```cpp instruction.range(4, 0); \\ compile error ``` 錯誤原因 : 雖然`sc_uint`的class有提供選取指令bit數,但`sc_signal`並沒有,所以需要先從`sc_signal`讀取出`sc_uint`的數據,再使用`sc_uint`的成員函數或運算子重載。 ```cpp sc_signal<sc_uint> instruction; // method 1 - member function / member function instruction.read().range(4, 0); // method 2 - member function / operator overloading (instruction.read())(4,0) // method 3 - operator overloading / member function (instruction).range(4, 0) // method 4 - operator overloading / operator overloading (instruction)(4, 0) ``` ## `SC_CTOR(module)`和`SC_HAS_PROCESS(module)`差別 兩者在`SC_MODULE`初始化,都會把`SC_MODULE`的member functions註冊到SystemC的simulation kernel,因此才能跑simulation process。 - `SC_CTOR(module)`有提供預設的constructor,這個constructor只有一個argument - module name。 - `SC_HAS_PROCESS(module)`並沒有提供預設的constructor,需要使用者自行定義constructor。 因此根據上述特性,當module不需要module name以外的參數進行初始化時,則使用`SC_CTOR(module)`,就能調用預設的constructor;而當module需要module name以外的參數進行初始化時,則使用`SC_HAS_PROCESS(module)`,由使用者自行定義constructor需要哪些constructor的argument。 ```cpp #include <systemc.h> SC_MODULE(MODULE_A) { // module without simulation processes doesn't need SC_CTOR or SC_HAS_PROCESS MODULE_A(sc_module_name name) { // c++ style constructor, the base class is implicitly instantiated with module name. std::cout << this->name() << ", no SC_CTOR or SC_HAS_PROCESS" << std::endl; } }; SC_MODULE(MODULE_B) { // constructor with module name as the only input argument SC_CTOR(MODULE_B) { // implicitly declares a constructor of MODULE_A(sc_module_name) SC_METHOD(func_b); // register member function to simulation kernel } void func_b() { // define function std::cout << name() << ", SC_CTOR" << std::endl; } }; SC_MODULE(MODULE_C) { // pass additional input argument(s) const int i; SC_HAS_PROCESS(MODULE_C); // OK to use SC_CTOR, which will also define an un-used constructor: MODULE_A(sc_module_name); MODULE_B(sc_module_name name, int i) : i(i) { // define the constructor function SC_METHOD(func_c); // register member function } void func_c() { // define function std::cout << name() << ", additional input argument" << std::endl; } }; int sc_main(int, char*[]) { MODULE_A module_a("module_a"); MODULE_B module_b("module_b"); MODULE_C module_c("module_c", 1); sc_start(); return 0; } ``` ## `SC_METHOD(func)`和`SC_THREAD(func)`差別 - `SC_METHOD(func)`沒有自己的執行緒,不會消耗模擬時間,不能呼叫函數`wait()`,使用`next_trigger()`就能建立dynamic sensitivity,當trigger就會執行,因此不需要使用無窮迴圈也能反覆執行。 - `SC_THREAD(func)`有自己的執行緒,可能會消耗模擬時間,使用`wait()`就能建立dynamic sensitivity,需要使用無窮迴圈。 - 另外一種`SC_CTHREAD(func, event)`是`SC_THREAD(func)`的特殊形式,只有static sensitivity,而且所sensitivity的event通常是設置為clock。 |Simulation Process|SC_METHOD|SC_THREAD|SC_CTHREAD| |---|---|---|---| |Execution|When trigger|Always execute|Always execute| |Suspend time|No|Yes|Yes| |Static sensitivity|By sensitive list|By sensitive list|By clock event| |Dynamic sensitivity|`next_trigger()`|`wait()`|X| |Inifinite Loop|No|Yes|Yes| |Applied model|RTL, synchronize|Behavioral|Clocked behavior| ```cpp #include <systemc.h> SC_MODULE(PROCESS) { sc_clock clk; // declares a clock SC_CTOR(PROCESS) : clk("clk", 1, SC_SEC) { // instantiate a clock with 1sec periodicity SC_METHOD(method); // register a method SC_THREAD(thread); // register a thread SC_CTHREAD(cthread, clk); // register a clocked thread } void method(void) { // define the method member function // no while loop here std::cout << "method triggered @ " << sc_time_stamp() << std::endl; next_trigger(sc_time(1, SC_SEC)); // trigger after 1 sec } void thread() { // define the thread member function while (true) { // infinite loop make sure it never exits std::cout << "thread triggered @ " << sc_time_stamp() << std::endl; wait(1, SC_SEC); // wait 1 sec before execute again } } void cthread() { // define the cthread member function while (true) { // infinite loop std::cout << "cthread triggered @ " << sc_time_stamp() << std::endl; wait(); // wait for next clk event, which comes after 1 sec } } }; int sc_main(int, char*[]) { PROCESS process("process"); // init module std::cout << "execution phase begins @ " << sc_time_stamp() << std::endl; sc_start(2, SC_SEC); // run simulation for 2 second std::cout << "execution phase ends @ " << sc_time_stamp() << std::endl; return 0; } ``` Result ```cpp // module instantiation finished execution phase begins @ 0 s // all processes are triggered once method triggered @ 0 s thread triggered @ 0 s cthread triggered @ 0 s // all processes are triggered again after 1 sec, using different methods method triggered @ 1 s thread triggered @ 1 s cthread triggered @ 1 s // simulation ends after 2 seconds execution phase ends @ 2 s ``` ## `sc_port<T>`用法 port指向channel的pointer,需要先port去connect (bind)到channel上,讀取資料時,需要先將port做解引用得到port所指向的channel,再使用channel的member function`write()`和`read()`讀取資料。 ```cpp #include <systemc.h> #include <string> SC_MODULE(MODULE1) { // top-level module sc_port<sc_signal_out_if<std::string>> p; // port SC_CTOR(MODULE1) { SC_THREAD(writer); } void writer() { std::string text = "DCS_Lab02_"; // init value while (true) { std::string write_data = text + std::to_string((int)sc_time_stamp().to_seconds()); p->write(write_data); // writes to channel through port std::cout << sc_time_stamp() << ":" << name() << " writes to channel, string=" << write_data << std::endl; wait(1, SC_SEC); } } }; SC_MODULE(MODULE2) { sc_port<sc_signal_in_if<std::string>> p; SC_CTOR(MODULE2) { SC_THREAD(reader); sensitive << p; // triggered by value change on the channel dont_initialize(); } void reader() { while (true) { std::cout << sc_time_stamp() << ":" << name() << " reads from channel, string=" << p->read() << std::endl; wait(); // receives from channel through port } } }; int sc_main(int, char *[]) { MODULE1 module1("module1"); // instantiate module1 MODULE2 module2("module2"); // instantiate module2 sc_signal<std::string> s; // define channel outside module1 and module2 module1.p(s); // bind module1's port to channel, for writing purpose module2.p(s); // bind module2's port to channel, for reading purpose sc_start(2, SC_SEC); return 0; } ``` Result ``` 0 s:module1 writes to channel, string=DCS_Lab02_0 0 s:module2 reads from channel, string=DCS_Lab02_0 1 s:module1 writes to channel, string=DCS_Lab02_1 1 s:module2 reads from channel, string=DCS_Lab02_1 ``` ## Lab02 Specifications ![port2port_v2](https://hackmd.io/_uploads/ryRlrr1C6.png) 同學需要完成15個程式空格。module1每隔5秒會生成並傳一次字串,完成module1 -> module2 -> module3 -> checker的傳輸,並限定使用`sc_port`指向channel來傳輸資料,另外module3限定使用 1. simulation process : `SC_METHOD` 2. instantiation : `SC_HAS_PROCESS` 以讓同學了解`SC_METHOD`和`SC_THREAD`之間差異、`SC_HAS_PROCESS`和`SC_CTRL`之間差異。 而程式會將從module1產生的字串直接傳給checker,與同學寫的module1 -> module2 -> module3 -> checker字串比對,以檢查傳輸是否正確。 ## Expected Result 每一個channel讀寫皆會輸出資料。 ![image](https://hackmd.io/_uploads/HyD1DBJAp.png) ## Lab02 Tips - (1、2、10、11) 將資料寫入 / 讀出channel要怎麼寫? 將port做解引用,並調用member function `write(), read()`,將生成出的字串寫入channel。語法請看[`sc_port<T>`用法](#sc_port<T>用法)。 - (3) wait on events in sensitivity list,語法請看[Combined Events](https://www.learnsystemc.com/basic/event_combined)。 - (4)定義port。語法請看[`sc_port<T>`用法](#sc_portltTgt用法)。 - (6)如何宣告`SC_HAS_PROCESS`。語法請看[`SC_CTOR(module)`和`SC_HAS_PROCESS(module)`差別](#SC_CTOR(module)和SC_HAS_PROCESS(module)差別) - (7、12) `SC_METHOD`要如何trigger?。語法請看[`SC_METHOD(func)`和`SC_THREAD(func)`差別](#SC_METHOD(func)和SC_THREAD(func)差別)。 - (8) 當p2的值發生改變時,會觸發`SC_METHOD(transfer)`執行,因此需要把`p2`加入sensitivity list。 - (9) ` SC_METHOD(transfer)`在程式初始化就會被呼叫?要如何避免他呼叫呢?語法請看老師講義lec2 P47。 - (13、14、15)如何將port bind到channel?語法請看[`sc_port<T>`用法](#sc_portltTgt用法)。 ## Command List 1. Extract files from TA’s directory ``` tar -xvf ~DCSTA01/Lab02.tar ``` 2. Compile & Run SystemC source code ``` ./01_systemc ``` 3. Clean SystemC executable file ``` ./09_clean ``` 4. Hand in `port2port.cpp` to E3 platform (如果上課有給助教demo過,而且也有登記過,就不需要再繳交到E3平台) ## 評分標準 - 當天上課晚上6點前完成,並舉手找助教報你的server編號`[DCS001 - DCS155]`,得100分。登記完後可以查看[Lab02登記表單連結](https://docs.google.com/spreadsheets/d/1BIdesrsKEkWzfEGECe9LQKsHCMJ-yiY1fIOnw0rjxKc/edit#gid=1718585434)確認助教是否有登記。**如果上課有給助教demo過,而且也有登記過,就不需要再繳交到E3平台**。 - 當天晚上11點59分以前繳交到E3則80分。 - 隔天60分,以此類推。 ## 解答 ```cpp //############################################################################ // 2024 DCS Spring Course // Lab02 : Port 2 Port Communication // Author : HsuChiChen (chenneil90121@gmail.com) //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Date : 2024.03.13 // Version : v2.0 // File Name : port2port.cpp //############################################################################ // moodule1 -> p -> module2 -> p2 -> module3 -> p3 -> checker // implement the following communication path // fill the 15 blanks in the code // Note : module3 use SC_METHOD instead of SC_THREAD and SC_HAS_PROCESS(MODULE3) instead of SC_CTOR(MODULE3) // moodule1 -> p_checker -> checker // check the data transfer is correct or not #include <systemc.h> #include <string> using namespace std; #define SEND_INTERVAL 5 #define SIMULATION_TIME 50 #define CORRECT_COUNT (ceil((float)SIMULATION_TIME / SEND_INTERVAL)) // module1 writes to channel SC_MODULE(MODULE1) { sc_port<sc_signal_out_if<string>> p; sc_port<sc_signal_out_if<string>> p_checker; SC_CTOR(MODULE1) { SC_THREAD(writer); } void writer() { string text = "DCS_Lab02_"; while (true) { // append current simulation time to text string write_data = text + to_string((int)sc_time_stamp().to_seconds()); p_checker->write(write_data); // 1. writes to channel through port p //================================================================ p->write(write_data); //================================================================ cout << sc_time_stamp() << ":" << name() << " writes to channel, string =" << write_data << endl; wait(SEND_INTERVAL, SC_SEC); // wait for 5 seconds } } }; // module2 reads from channel p // module2 writes to channel p2 SC_MODULE(MODULE2) { sc_port<sc_signal_in_if<string>> p; sc_port<sc_signal_out_if<string>> p2; string data; SC_CTOR(MODULE2) { SC_THREAD(transfer); sensitive << p; // triggered by value p change on the channel dont_initialize(); // do not initialize the thread } void transfer() { while (true) { this->data = p->read(); cout << sc_time_stamp() << ":" << name() << " reads from channel, string=" << this->data << endl; // 2. writes to channel through port p2 //================================================================ p2->write(data); //================================================================ cout << sc_time_stamp() << ":" << name() << " writes to channel, string=" << this->data << endl; // 3. wait on events in sensitivity list - receives from channel through port //================================================================ wait(); //================================================================ } } }; // module3 reads from channel p2 // module3 writes to channel p3 SC_MODULE(MODULE3) { sc_port<sc_signal_in_if<string>> p2; // 4. define a port for writing to channel p3 //================================================================ sc_port<sc_signal_out_if<string>> p3; //================================================================ // 5. define a string data; //================================================================ string data; //================================================================ // 6. Use SC_HAS_PROCESS(MODULE3) instead of SC_CTOR(MODULE3) //================================================================ SC_HAS_PROCESS(MODULE3); //================================================================ // explicit constructor declaration MODULE3(sc_module_name name) : sc_module(name) { // 7. Use SC_METHOD instead of SC_THREAD for transfer //================================================================ SC_METHOD(transfer); //================================================================ // 8. trigger by value p2 change on the channel //================================================================ sensitive << p2; //================================================================ // 9. do not initialize the thread //================================================================ dont_initialize(); //================================================================ } void transfer() { // do not use while loop in SC_METHOD // while (true) { // 10. read from channel through port and store in this->data //================================================================ this->data = p2->read(); //================================================================ cout << sc_time_stamp() << ":" << name() << " reads from channel, string=" << this->data << endl; // 11. writes to channel through port p3 //================================================================ p3->write(this->data); //================================================================ cout << sc_time_stamp() << ":" << name() << " writes to channel, string=" << this->data << endl; // do not use wait(); in SC_METHOD // 12. wait on events in sensitivity list - receives from channel through port //================================================================ next_trigger(); //================================================================ // } } }; // checker reads from channel p3 SC_MODULE(CHECKER) { sc_port<sc_signal_in_if<string>> p3; sc_port<sc_signal_in_if<string>> p_checker; int correct_count; string data; string golden_data; sc_event e; SC_CTOR(CHECKER) { SC_THREAD(checker); dont_initialize(); sensitive << p3; // triggered by value change on the channel SC_THREAD(receiver); dont_initialize(); sensitive << e; } void checker() { while (true) { this->data = p3->read(); cout << sc_time_stamp() << ":" << name() << " reads from channel, string=" << this->data << endl; e.notify(); // notify event wait(); // wait on events in sensitivity list - receives from channel through port } } void receiver() { this->correct_count = 0; while (true) { this->golden_data = p_checker->read(); if (this->data == this->golden_data) { cout << "\033[32m" << "data is correct at time " << sc_time_stamp() << "\033[0m" << endl; correct_count++; } else { cout << "\033[31m" << "data is incorrect at time " << sc_time_stamp() << "\033[0m" << endl; cout << "\033[31m" << "data = " << data << " golden_data = " << golden_data << "\033[0m" << endl; } wait(e); } } ~CHECKER() { if(correct_count == CORRECT_COUNT) { cout << "\033[32m" << "All data is correct" << "\033[0m" << endl; cout << "\033[35m" << "Raise Hand and register your [DCS001 - DCS155] to TA" << "\033[0m" << endl; } else { cout << "\033[31m" << "Some data is incorrect" << "\033[0m" << endl; } } }; int sc_main(int, char *[]) { MODULE1 module1("module1"); // instantiate module1 MODULE2 module2("module2"); // instantiate module2 MODULE3 module3("module3"); // instantiate module3 CHECKER checker("checker"); // instantiate checker sc_signal<string> s; // define channel outside module1 and module2 sc_signal<string> s2; // define channel outside module2 and module3 // 13. define channel outside module3 and checker //================================================================ sc_signal<string> s3; //================================================================ sc_signal<string> s_checker; // define channel outside module1 and checker module1.p_checker(s_checker); // bind module1's port to channel, for writing purpose module1.p(s); // bind module1's port to channel, for writing purpose module2.p(s); // bind module2's port to channel, for reading purpose module2.p2(s2); // bind module2's port to channel, for writing purpose module3.p2(s2); // bind module3's port to channel, for reading purpose // 14. bind module3's port to channel, for writing purpose //================================================================ module3.p3(s3); //================================================================ // 15. bind checker's port to channel, for reading purpose //================================================================ checker.p3(s3); //================================================================ checker.p_checker(s_checker); // bind checker's port to channel, for reading purpose sc_start(SIMULATION_TIME, SC_SEC); // start simulation with 10 seconds return 0; } ```