---
# System prepended metadata

title: TLM (Transaction Level Modeling) 筆記
tags: [SystemC]

---

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