:::info 都是以 **blocking transport interface** 說明 non-blocking transport interface tutorial: [1](https://www.doulos.com/knowhow/systemc/tlm-20/example-4-non-blocking-transport-payload-event-queues-memory-management/) [2](https://www.doulos.com/knowhow/systemc/tlm-20/example-5-temporal-decoupling-multiple-initiators-and-targets/) [3](https://www.doulos.com/knowhow/systemc/tlm-20/example-6-multi-sockets-non-blocking-transport/) ::: # TLM (Transaction Level Modeling) 重點 ## RTL, Pure SystemC, SystemC + TLM 比較 ![image](https://hackmd.io/_uploads/Hk1P1_Cnyx.png) * RTL * 若要做到 cycle accurate 可能會非常慢 * Pure SystemC * 為了確保時間的正確性,呼叫 process 前都會回到 kernel 做 context switch,故 **整體時間 bottleneck 會在 SystemC kernel** * 考慮兩個功能完全相同的 module,僅 interface 不一樣。其中一個 module 會不能使用一樣的 module 連在一起 * 如果兩邊 module 使用的 interface 不同就不能連在一起 * SystemC + TLM * 使用 **memory-mapped** 的概念直接呼叫 target module 的 function call ## Coding Styles 比較 * **Loosely-timed** * **blocking** transport interface (同一時間只能有一組 (兩個) points 可以做 transcation) * 不 synchronize 就不會和 SystemC kernel 同步時間 (故需要自己決定要 synchronize 在什麼時間點 (稱為 **Temporal Decoupling**)) ![image](https://hackmd.io/_uploads/r11-fdCnJl.png) CS 即為 context switch (在 process 被 context switch 至 kernel 時才會與 kernel synchronize 時間) Δq 為一個 **quantum**,大小可自己 trade-off,越大表示越重視執行效率、越不重視模擬精確度 * **Approximately-timed** * **non-blocking** transport interface (同一時間可以有多組 points 可以做 transaction) * 在做 transaction 時會記錄 1. request start time 2. request end time 3. response start time 4. response end time ,不會在同一個 quantum * protocol: ![image](https://hackmd.io/_uploads/r1PYddA3ke.png) ### Use Cases * 要做 software development: 不需要 cycle 資訊,用 loosely-timed 即可 * 要做 software performance: 可根據 application 可以部份使用 loosely-timed 部份使用 approximately-timed * 要做 architectural analysis: 需要看 architecture 及 cycle,可根據 application 可以部份使用 loosely-timed 部份使用 approximately-timed * 要做 hardware verification: 需要高 cycle 精確度,用 approximately-timed ## 示意圖 ![image](https://hackmd.io/_uploads/rJ_fu_R31x.png) * **initiator**: initiate transactions 的 module * **target**: transaction 的最終目的地 module * **interconnect component**: 考慮 initiator 要連接到兩個以上 target 的情況,有 interconnect component 時,由 interconnect component 管理所有 targets 的 address、memory-mapped 的位置等,initiator 不需要知道要傳輸到哪個 address,故可以簡化 initiator 的實作,更可以將 initiator reuse 到其他 system * socket 傳輸方向都只能是 **單向** 的 * backward path: [Backward DMI Interface](https://hackmd.io/x70Gwt6ERqKXp6oyX4oiRw#Backward-DMI-Interface) 使用 * ![image](https://hackmd.io/_uploads/SJHWKdRnyg.png): 表示一個指向 transaction object 的 **pointer** * transaction object 結構 (稱為 generic payload): ![image](https://hackmd.io/_uploads/BkZDuu0hye.png) # Blocking Transport Inteface :::success include: ```cpp #include "tlm.h" #include "tlm_utils/simple_initiator_socket.h" #include "tlm_utils/simple_target_socket.h" ``` ::: ## socket * Initiator socket `tlm_utils::simple_initiator_socket<宣告在該個 module 內的 module 名稱> socket名稱` * `.bind(target socket)` * 讓 initiator socket 知道在呼叫 `socket->b_transport()` 時要呼叫哪個 target 的 function call * `.b_transport(transaction, delay)` * 呼叫 **target socket 註冊的 blocking transport function** * Target socket `tlm_utils::simple_target_socket<宣告在該個 module 內的 module 名稱> socket名稱` * `.register_b_transport(this, &(要註冊的 blocking transport function 名稱))` * 註冊 socket 的 blocking transport function,使得 initiator socket 在呼叫 `b_transport()` 時 **即會呼叫到此註冊的 function** ## payload * `tlm::tlm_generic_payload* transaction名稱 = new tlm::tlm_generic_payload` * `.set_command(tlm::tlm_command type 的 command)`、`.get_command()` * 設定/取得此 transaction 要做的 command * `tlm::TLM_READ_COMMAND`: 讀取 target 內的 data 並寫入至 transaction * `tlm::TLM_WRITE_COMMAND`: 將 transaction 內的 data 寫入至 target * `.set_address(address)`、`.get_address()` * 設定/取得 command 的目標 address * `.set_data_ptr(data 的 pointer)`、`.get_data_ptr()` * 設定/取得 transaction 的 data pointer * `.set_data_length(整數)`、`.get_data_length()` * 設定/取得 transaction 的 data length * `.set_streaming_width(整數)`、`.get_streaming_width()` * 設定/取得 **burst** 的長度 **burst**: interconnection hardware 內建的功能,只要 send 一個 command,並指定要 send 的單位 (ex: 一個單位 = 4 byte),而 initiator 會依序 send 出這些 data, target 要維護一個 counter 依序取得這些單位的 data > 假設 initiator 為 8 byte 元件,target 為 4 byte 元件,initiator 做 sd (store double word),但 interconnection 為 4 byte,此時 data length 不能設 8 byte (否則 transaction 數量就錯了) * stream width == data length 表示不使用此功能 * `.set_byte_enable_ptr(一個 unsigned char 的 pointer 或 unsigned char array)`、`.get_byte_enable_ptr()` * 設定/取得 transaction 中哪些 byte 是有效、哪些 byte 是無效 * 參數為 0 表示不使用此功能 * > ex: > ```cpp > unsigned char byte_enable[4] = {0xFF, 0x00, 0xFF, 0x00}; // 只啟用第0和第2個位元組 > trans.set_byte_enable_ptr(byte_enable); > ``` * `.set_dmi_allowed(true/false)`、`.get_dmi_allowed()` * 設定/取得是否允許 initiator 對 target 使用 [DMI](https://hackmd.io/x70Gwt6ERqKXp6oyX4oiRw#DMI) * `.set_response_status(tlm::tlm_response_status type 的 status)` * 指定目前此 transaction response 的狀態 * `tlm::TLM_INCOMPLETE_RESPONSE`: 尚未 response,initiator 初始化此 transaction 時一定要設成這個值 * `tlm::TLM_OK_RESPONSE`: 已完成 response,可在 target 完成 command 後順便設定為此值 * `.is_response_error()` * 回傳是否 reponse error ## `b_transport()` 實作 ```cpp virtual void b_transport(tlm::tlm_generic_payload& trans, sc_time& delay) { tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 adr = trans.get_address() / 4; // transaction 內放的是 byte address (以 byte 為單位的 address),除以 4 表示轉換成 word address (以 word 為單位) unsigned char* ptr = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); unsigned char* byt = trans.get_byte_enable_ptr(); unsigned int wid = trans.get_streaming_width(); // ... ``` :::info ++Remark++ 1. 假設 `b_transport()` 定義在 target class 內,則 `b_transport()` 的 scope 是在 **target class**,可以 access target class 的 member。但是 **並不會 context switch 至 target**,故假設在 `b_transport()` 內呼叫 `wait()`,會增加的是 **initiator** 的 simulation time (但不建議在 `b_transport()` 內呼叫 `wait()`,也不建議 target 有 thread 呼叫 `wait()`) 2. 通常會在 `b_transport()` 內檢查 transaction 是否滿足硬體限制 > ex: > ```cpp > if (adr >= sc_dt::uint64(SIZE) || byt != 0 || len > 4 || wid < len) > SC_REPORT_ERROR("TLM-2", "Target does not support given generic payload transaction"); > ``` 3. 第二個參數 (`delay`) 為 value-result argument,要在 `b_transport()` 內修改為在做這個 transaction 時的 delay,並在 return 回 initiator 後讓 initiator `wait()` 這個 delay ::: ## 補充 ### Debug Transport Interface Debug TLM 時使用,使用方式和 Blocking Transport 一樣,除了 **不會有 timing behavior (delay)** 及 **不會改變系統的行為** 語法: * Initiator * `socket.register_transport_dbg(this, &(要註冊的 debug transport function 名稱));` * `socket->transport_dbg(*trans);` * Target * `virtual unsigned int transport_dbg(tlm::tlm_generic_payload& trans) {}` * `b_transport()` 有第二個參數 (timing behavior),`transport_dbg()` 沒有 * 此 function 內部不做改變 timing behavior 及改變系統的行為 # Quantum Keeper 即為 [Temporal Decoupling](https://hackmd.io/x70Gwt6ERqKXp6oyX4oiRw?both=&stext=1115%3A19%3A0%3A1745746697%3AGHd2vU) 的 quantum。 每個 module 內放一個 quantum keeper,quantum keeper 內記錄一個 **local time** 表示目前這個 module 的 local 模擬時間。 * `tlm_utils::tlm_quantumkeeper quantum名稱` * `.set_global_quantum(sc_time object)` * 設定 TLM 的 **global** quantum 大小 * `.get_local_time()` * 取得目前的 local time * `.reset()` * 設定 quantum keeper 的 local time 為 0 * `.set(sc_time object)` * 設定 quantum keeper 的 local time * `.inc(sc_time object)` * increase quantum keeper 的 local time * `.need_sync()`、`.sync()` * `.need_sync()` 回傳 true 表示 **local time >= global quantum**,此時 **需要** 呼叫 `.sync()`,則 `sync()` **會自動呼叫 `wait(目前的 local time)`,並把 local time 設為 0** :::warning object 內需要主動呼叫 `.need_sync()` 檢查 local time 是否已超過 time quantum,以達成 Loosely-timed 同步 ::: :::spoiler Example ```cpp= struct Initiator: sc_module { tlm_utils::simple_initiator_socket<Initiator> init_socket; tlm_utils::tlm_quantumkeeper m_qk; SC_CTOR(Initiator) : init_socket("init_socket") { // ... m_qk.set_global_quantum( sc_time(1, SC_US) ); m_qk.reset(); } void thread() { for (int i = 0; i < RUN_LENGTH; i += 4) { // ... init_socket->b_transport( trans, delay ); m_qk.inc(delay); if ( m_qk.need_sync() ) m_qk.sync(); } } }; ``` ::: # DMI DMI 可以讓 initiator 不用 transaction,直接 access target 內的 memory 只用在 use case 為 software development、software performance 時 (因為 DMI 沒有 architecture 資訊) ## Initiator ```cpp= // ... tlm::tlm_dmi dmi_data; trans.set_dmi_allowed(true); socket->b_transport(*trans, delay); // 此 transaction 目的只是讓 target 知道要使用 DMI if (trans->is_dmi_allowed()) { dmi_data.init(); dmi_ptr_valid = socket->get_direct_mem_ptr(*trans, dmi_data); if (dmi_ptr_valid) { if ( cmd == tlm::TLM_READ_COMMAND ) { assert( dmi_data.is_read_allowed() ); // DMI 取得的 address 的 pointer 即為 dmi_data.get_dmi_ptr() wait( dmi_data.get_read_latency() ); // wait DMI 的 read latency,或用 Time Quantum } else if ( cmd == tlm::TLM_WRITE_COMMAND ) { // ... } } else { // target 不接受 DMI,使用一般 TLM 方法傳輸 } } ``` ## Target ```cpp= // ... socket.register_get_direct_mem_ptr(this, &(要註冊的 DMI function 名稱)); // ... virtual bool get_direct_mem_ptr(tlm::tlm_generic_payload& trans, tlm::tlm_dmi& dmi_data) { // 被註冊的 DMI function。trans 通常不會用到 dmi_data.allow_read_write(); // 允許 DMI read/write access dmi_data.set_dmi_ptr(reinterpret_cast<unsigned char*>( /* 允許 initiator access 的 address 的 pointer */ )); dmi_data.set_start_address(0); dmi_data.set_end_address( /* end address */ ); dmi_data.set_read_latency( /* read latency */ ); dmi_data.set_write_latency( /* write latency */ ); return true; } ``` ## 補充 ### Backward DMI Interface 由 **target 對 initiator** 發出,通常用在拒絕 initiator 對 target DMI 一個不允許的記憶體區段 語法: * `socket.register_invalidate_direct_mem_ptr(this, &(要註冊的 invalidate direct mem ptr function 名稱));` * `virtual void invalidate_direct_mem_ptr(sc_dt::uint64 start_range, sc_dt::uint64 end_range) {}` * etc.