:::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 比較

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

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:

### 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
## 示意圖

* **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) 使用
* : 表示一個指向 transaction object 的 **pointer**
* transaction object 結構 (稱為 generic payload):

# 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.