• Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

  • Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

  • Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

Hiển thị các bài đăng có nhãn Transaction Level Modeling. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn Transaction Level Modeling. Hiển thị tất cả bài đăng

Chủ Nhật, 21 tháng 7, 2019

[SystemC][TLM] Bài 4 - Multi-sockets

Bài viết này trình bày về cách sử dụng và kết nối multi-socket trong TLM-2.0. Ví dụ được sử dụng trong bài viết là SPCU, lấy từ các bài viết trước (bài 1, bài 2, bài 3, bài 4, bài 5bài 6) về SystemC nên các bạn cần đọc các bài viết trước để hiểu chi tiết hơn. Về các loại socket khác, các bạn tham khảo các bài viết bài 1bài 2 và bài 3.

1) Tổng quan về multi-socket
Trong một model thực tế, một khối (block) có thể gửi dữ liệu của nó cho nhiều khối khác hoặc nhận dữ liệu từ nhiều khối khác nhau. Trong các bài trước (bài 1bài 2 và bài 3), chúng ta sử dụng kết nối điểm-điểm thông qua các simple socket để thực hiện việc truyền và nhận các gói dữ liệu. Gói dữ liệu ở đây là các transaction được gửi qua hàm b_transport.
Hình 1: Kết nối điểm-điểm sử dụng simple socket
Trong hình minh họa trên đây, một khối muốn truyền dữ liệu cho khối khác thì một cặp initiator socket và target socket được tạo ra để kết nối giữa khối truyền và khối nhận. Với dạng kết nối điểm-điểm, một khối như DECODER cần nhiều initiator socket và target socket để truyền/nhận dữ liệu từ/đến nhiều khối khác nhau.
Để giảm số lượng kết nối này, chúng ta sử dụng kết nối multi-socket bao gồm:
  • multi_passthrough_initiator_socket cho initiator
  • multi_passthrough_target_socket cho target
Hai loại socket trên là biến thể của tagged simple socket, một loại socket được sử dụng để giảm số lượng callback thông qua việc định danh socket bằng một số nguyên gọi là id hoặc tag.
Sử dụng multi_passthrough_initiator_socket giúp một initiator socket có thể được kết nối (bind) đến nhiều target socket.
Sử dụng multi_passthrough_target_socket giúp một target socket có thể được kết nối (bind) đến nhiều initiator socket.
Hình 2: Kết nối multi-socket sử dụng multi_passthrough_*_socket
Bên cạnh đó, chúng còn hỗ trợ kết nối theo thứ bậc (hierarchy). Trong khi các loại socket khác chỉ hỗ trợ kết nối (bind) giữa một initiator socket và một target socket thì multi_passthrough_initiator_socket và multi_passthrough_target_socket hỗ trợ kết nối (bind) dùng loại theo hierarchy. Nghĩa là, initiator socket của một instance con có thể được kết nối đến initiator socket của khối cha (child-to-parent). Target socket của một instance con có thể được kết nối đến Target socket của khối cha. Xem hình minh họa dưới đây.
Hình 3: Kết nối socket theo hierarchy sử dụng multi_passthrough_*_socket
Đoạn code sau đây là một ví dụ trong tài liệu IEEE_1666-2011 cho kết nối từ một instance (child) đến khối cha (parent).
  • Khai báo một initiator socket loại multi_passthrough_initiator_socket tại khối con
// Initiator component with a multi-socketstruct Initiator: sc_module{
  tlm_utils::multi_passthrough_initiator_socket<Initiator> socket; 
SC_CTOR(Initiator) : socket("socket") {  ...};
  • Tạo instance trong khối cha, khai báo một multi_passthrough_initiator_socket tại khối cha và kết nối (bind) socket của instance đến khối cha.
struct Initiator_parent: sc_module{ 
 tlm_utils::multi_passthrough_initiator_socket<Initiator_parent> socket; 
Initiator *initiator; 
SC_CTOR(Initiator_parent) : socket("socket") {    initiator = new Initiator("initiator");  
// Hierarchical binding of initiator socket on child to initiator socket on parent 
    initiator->socket.bind( socket );  
} 
};  
2) Ví dụ về multi-socket
Quay lại ví dụ SCPU, kết nối của khối DECODER sẽ được chuyển thành dạng multi-socket như hình minh họa sau đây.
Hình 4: Kết nối dạng multi-socket của DECODER
Như vậy, DECODER chỉ dùng một initiator socket là dc_output_socket và một target socket là dc_input_socket. Các điều chỉnh về SystemC code như sau:
  • Include header file mới trong scpu_decoder.h để sử dụng multi_pasthrough_*_socket
 #include "tlm_utils/multi_passthrough_initiator_socket.h"
 #include "tlm_utils/multi_passthrough_target_socket.h"
  • Khai báo lại initiator socket cho DECODER
tlm_utils::multi_passthrough_initiator_socket<scpu_decoder> dc_output_socket;
  • Khai báo lại target socket cho DECODER
tlm_utils::multi_passthrough_target_socket<scpu_decoder> dc_input_socket;
  • Đăng ký callback cho target socket
dc_input_socket.register_b_transport(this, &scpu_decoder::common_transport);
  • Viết hàm xử lý callback. Chú ý, để phân biệt gói dữ liệu (transaction) gửi đến target socket là từ FETCH hay EXECUTE. Nhóm tác giả sử dụng thêm trường address của tlm_generic_payload, nếu address=1 thì transaction đến từ FETCH, nếu address=2 thì transaction đến từ EXECUTE. Chú ý, các phần tô màu xanh là các điểm cần lưu ý của callback này.
virtual void common_transport (int id, tlm::tlm_generic_payload& common_trans, sc_time &delay) {
     tlm::tlm_command dc_cmd = common_trans.get_command();
     unsigned char*   dc_ptr = common_trans.get_data_ptr();
     unsigned int     dc_len = common_trans.get_data_length();
     unsigned char*   dc_byt = common_trans.get_byte_enable_ptr();
     unsigned int     dc_wid = common_trans.get_streaming_width();
     unsigned int     dc_addr= common_trans.get_address();
if (dc_cmd != tlm::TLM_WRITE_COMMAND)
       SC_REPORT_ERROR("TLM-2", "Target only support TLM_WRITE_COMMAND for the transaction from FETCH to DECODER");
     //
     if (dc_addr == 1) { //Get data from FETCH
       memcpy (&fetch2dc_pkt_tmp, dc_ptr, dc_len);
       fetch2dc_pkt = fetch2dc_pkt_tmp;
       //
       fetch_ir   = fetch2dc_pkt.range(23,16);
       fetch_dr   = fetch2dc_pkt.range(15,8);
       fetch_mem_dout  = fetch2dc_pkt.range(7,0);
     }
     else { //Get data from EXECUTE when dc_addr=2
       memcpy (&ex2dc_pkt_tmp, dc_ptr, dc_len);
       ex2dc_pkt = ex2dc_pkt_tmp;
       //
       ex_dout   = ex2dc_pkt.range(8,0);
     }
     // Set response status to indicate successful completion
     common_trans.set_response_status( tlm::TLM_OK_RESPONSE );
   }
  • Thêm trường address cho transaction fetch2dc_trans trong file scpu_fetch.cpp ở khối FETCH và transaction ex2dc_trans trong file scpu_execute.cpp ở khối EXECUTE
fetch2dc_trans->set_address( 1 ); //Cho khối FETCH
ex2dc_trans->set_address( 2 ); //Cho khối EXECUTE
  • Kết nối các socket ở file scpu_top.cpp
 scpu_decoder_inst.dc_output_socket.bind(scpu_fetch_inst.dc2fetch_socket);        scpu_decoder_inst.dc_output_socket.bind(scpu_execute_inst.dc2ex_socket);       scpu_execute_inst.ex2dc_socket.bind(scpu_decoder_inst.dc_input_socket);       scpu_fetch_inst.fetch2dc_socket.bind(scpu_decoder_inst.dc_input_socket);
Code hoàn chỉnh có thể được tải phía dưới bài viết này.

Dữ liệu có thể download:
pass (nếu có): vlsi_technology

Tham khảo:
1) IEEE Computer Society; IEEE Standard for Standard SystemC® Language Reference Manual - IEEE Std 1666™-2011; 9 January 2012

Lịch sử cập nhật:
1) 2019.July.21 - Tạo lần đầu

1. Trương Công Hoàng Việt
2. Lê Hoàng Vân
3. Nguyễn Hùng Quân

Chủ Nhật, 14 tháng 7, 2019

[SystemC][TLM] Bài 3 - Tagged simple socket

Như tiêu đề, bài viết này trình bày về "Tagged simple socket" và ứng dụng, một biến thể của simple socket trình bày trong bài 1bài 2. Bài viết này cũng sẽ lấy lại ví dụ SCPU (Simple CPU) và áp dụng "tagged simple socket" cho model này nên các bạn cũng cần xem lại các bài viết mô tả về thiết kế này (bài 1, bài 2, bài 3, bài 4, bài 5bài 6).

1) Tagged simple socket
"Tagged simple socket" là một simple socket được gắn thêm "thẻ định danh", gọi là tag. Thẻ định danh này là một số nguyên (integer) và còn được gọi là id.
id được sử dụng trong trường hợp "dùng một callback cho nhiều initiator socket hoặc nhiều target socket". Trường hợp này, id sẽ giúp callback phân biệt từng socket.
Callback được sử dụng ở target socket để lấy thông tin và dữ liệu của transaction gửi từ initiator socket; Đồng thời, gửi đáp ứng (response) về trạng thái transaction cho initiator. Nếu không sử dụng socket có gắn id, mỗi method b_transport của một kết nối socket cần một callback riêng.
Hình 1: Callback trên simple socket
Hình 2: Callback trên tagged simple socket
Một id sẽ được gán ở đối số cuối cùng của method đăng ký callback. Một id là đối số đầu tiên của method callback. Ví dụ:
struct my_target: sc_module
{
  tlm_utils::simple_target_socket_tagged<my_target> socket1;
  tlm_utils::simple_target_socket_tagged<my_target> socket2;
  //
 SC_CTOR(my_target): socket1("socket1"), socket2("socket2")
  {
 socket1.register_b_transport(this, &my_target::b_transport, 1); // Registered with id = 1
 socket2.register_b_transport(this, &my_target::b_transport, 2); // Registered with id = 2
  }
  void b_transport(int id, Transaction& trans, sc_time& delay); // May be called with id = 1 or id = 2
  ...
};
Đoạn code trên khai báo 2 target socket loại tagged socket1 socket2. Callback cho b_transport chỉ có một và được sử dụng trên cả socket1 và socket2, đó là:
void b_transport(int id, Transaction& trans, sc_time& delay);
Việc đăng ký callback cho b_transport trên socket1 socket2 sẽ được phân biệt bởi id, đối số cuối cùng được khai báo ở method register_b_transport.
2) Áp dụng cho ví dụ SCPU
Trong ví dụ SCPU, khối DECODER nhận dữ liệu từ hai khối FETCH và EXECUTE. Dữ liệu ở đây là giá trị các tín hiệu điều khiển được đóng gói thành một transaction có kiểu tlm_generic_payload (xem lại bài 1). DECODER có hai target socket là fetch2dc_socket và ex2dc_socket để nhận transaction từ FETCH và EXECUTE thông qua initiator socket tương ứng. Hai target socket đều là loại simple socket và có method callback riêng là fetch2dc_transport và ex2dc_transport.
Hình 3: Callback riêng trên mỗi target socket loại simple socket
Đăng ký callback cho target socket của DECODER trong constructor (SC_CTOR) của file scpu_decoder.h như sau:
fetch2dc_socket.register_b_transport(this, &scpu_decoder::fetch2dc_transport);
ex2dc_socket.register_b_transport(this, &scpu_decoder::ex2dc_transport);
Chú ý, phần tô xanh của đoạn code trên là tên method callback trên mỗi target socket. Code SystemC cho method callback fetch2dc_transport như sau:
virtual void fetch2dc_transport (tlm::tlm_generic_payload& fetch2dc_trans, sc_time &delay) {
 tlm::tlm_command dc_cmd = fetch2dc_trans.get_command();
    unsigned char*   dc_ptr = fetch2dc_trans.get_data_ptr();
     unsigned int     dc_len = fetch2dc_trans.get_data_length();
     unsigned char* dc_byt = fetch2dc_trans.get_byte_enable_ptr();
     unsigned int    dc_wid = fetch2dc_trans.get_streaming_width();
     //
     if (dc_cmd != tlm::TLM_WRITE_COMMAND)
       SC_REPORT_ERROR("TLM-2", "Target only support TLM_WRITE_COMMAND for the transaction from FETCH to DECODER");
memcpy (&fetch2dc_pkt_tmp, dc_ptr, dc_len);    fetch2dc_pkt = fetch2dc_pkt_tmp;    
fetch_ir   = fetch2dc_pkt.range(23,16); 
fetch_dr   = fetch2dc_pkt.range(15,8);      
fetch_mem_dout  = fetch2dc_pkt.range(7,0);   
// Set response status to indicate successful completion
    fetch2dc_trans.set_response_status( tlm::TLM_OK_RESPONSE );
   }
Code SystemC cho method callback ex2dc_transport như sau:
virtual void ex2dc_transport (tlm::tlm_generic_payload& ex2dc_trans, sc_time &delay) {
     tlm::tlm_command dc_cmd = ex2dc_trans.get_command();
     unsigned char*   dc_ptr = ex2dc_trans.get_data_ptr();
     unsigned int     dc_len = ex2dc_trans.get_data_length();
     unsigned char*   dc_byt = ex2dc_trans.get_byte_enable_ptr();
     unsigned int     dc_wid = ex2dc_trans.get_streaming_width();
     if (dc_cmd != tlm::TLM_WRITE_COMMAND)
       SC_REPORT_ERROR("TLM-2", "Target only support TLM_WRITE_COMMAND for the transaction from EXECUTE to DECODER");
memcpy (&ex2dc_pkt_tmp, dc_ptr, dc_len);  
ex2dc_pkt = ex2dc_pkt_tmp; 
ex_dout   = ex2dc_pkt.range(8,0);  
// Set response status to indicate successful completion
     ex2dc_trans.set_response_status( tlm::TLM_OK_RESPONSE );
   }
Các bạn có thể nhận thấy đoạn code của method callback fetch2dc_transport và ex2dc_transport chỉ khác nhau phần được tô màu xanh. Đây là phần code phân tách gói dữ liệu của transaction gửi từ FETCH và EXECUTE.
Trong trường hợp này, chúng ta có thể thay thế simple socket thành tagged simple socket. Trong đó:
  • fetch2dc_socket được gán id = 1
  • ex2dc_socket được gán id = 2
Một method callback duy nhất được sử dụng, tên là common_transport, sẽ dựa trên id để biết transaction được lấy từ target socket nào, của FETCH hat EXECUTE. Từ đó, phần code phân tách gói dữ liệu tương ứng sẽ được thực thi.
Hình 4: Sử dụng tagged simple socket cho khối DECODER
Trong file scpu_decoder.h, những phần code liên quan đến việc khai báo target socket và callback sẽ được thay đổi như sau:
  • Khai báo tagged simple socket:
tlm_utils::simple_target_socket_tagged<scpu_decoder> fetch2dc_socket;
tlm_utils::simple_target_socket_tagged<scpu_decoder> ex2dc_socket;
  • Đăng ký callback trong constructor (SC_CTOR) với cùng một method callback là common_transport nhưng với 2 id khác nhau.
fetch2dc_socket.register_b_transport(this, &scpu_decoder::common_transport, 1);
         ex2dc_socket.register_b_transport(this, &scpu_decoder::common_transport, 2);
  • Method callback xử lý chung cho trên cả 2 target socket:
virtual void common_transport (int id, tlm::tlm_generic_payload& common_trans, sc_time &delay) {     tlm::tlm_command dc_cmd = common_trans.get_command();
     unsigned char*   dc_ptr = common_trans.get_data_ptr();
     unsigned int     dc_len = common_trans.get_data_length();
     unsigned char*   dc_byt = common_trans.get_byte_enable_ptr();
     unsigned int     dc_wid = common_trans.get_streaming_width();
     if (dc_cmd != tlm::TLM_WRITE_COMMAND)
       SC_REPORT_ERROR("TLM-2", "Target only support TLM_WRITE_COMMAND for the transaction from FETCH to DECODER");
     //
     if (id == 1) { //Get data from FETCH 
       memcpy (&fetch2dc_pkt_tmp, dc_ptr, dc_len);
       fetch2dc_pkt = fetch2dc_pkt_tmp;
       //
       fetch_ir   = fetch2dc_pkt.range(23,16);
       fetch_dr   = fetch2dc_pkt.range(15,8);
       fetch_mem_dout  = fetch2dc_pkt.range(7,0);
     }
     else { //Get data from EXECUTE when id=2  
       memcpy (&ex2dc_pkt_tmp, dc_ptr, dc_len);
       ex2dc_pkt = ex2dc_pkt_tmp;
       //
       ex_dout   = ex2dc_pkt.range(8,0);
     }
     // Set response status to indicate successful completion
     common_trans.set_response_status( tlm::TLM_OK_RESPONSE );
   }
Code hoàn chỉnh của ví dụ SCPU sử dụng tagged simple socket có thể được tải dưới bài viết này.

Dữ liệu có thể download:
pass (nếu có): vlsi_technology

Tham khảo:
1) IEEE Computer Society; IEEE Standard for Standard SystemC® Language Reference Manual - IEEE Std 1666™-20119 January 2012

Lịch sử cập nhật:
1) 2019.July.14 - Tạo lần đầu

1. Trương Công Hoàng Việt
2. Lê Hoàng Vân
3. Nguyễn Hùng Quân

Chủ Nhật, 7 tháng 7, 2019

[SystemC][TLM] Bài 2 - Mảng socket

Bài viết này trình bày cách khai báo và kết nối một mảng socket. Trước khi đọc bài viết này các bạn cần tham khảo các bài viết về SystemCTLM trước đó để biết các khái niệm và ví dụ SCPU được trình bày trong bài viết này.

1) Kết nối sử dụng simple socket
Trong bài 1, nhóm tác giả đã trình bày về việc chuyển kết nối giữa các module từ dạng tín hiệu (signal) thành các kết nối TLM thông qua các socket. Trong ví dụ SCPU, nhóm tác giả đã thực hiện việc chuyển đổi này dựa trên ý tưởng chính như sau:
  1. Tập hợp các tín hiệu (signal) ngõ ra (output) của một module thành một gói dữ liệu tlm_generic_payload
  2. Gửi gói dữ liệu đã đóng gói qua một initiator socket đến một target socket bằng hàm b_transport
  3. Target socket đại diện cho các ngõ vào (input) của một module. Tại target socket, gói dữ liệu được tách ra thành các bit chức năng mong muốn và đưa đến các process.
  4. Thời gian truyền một gói dữ liệu qua socket phải trong một chu kỳ xung clock để đảm bảo các khối hoạt động đúng trong từng chu kỳ.
Các bạn hãy tải code của bài 1 để tìm hiểu trước khi tiếp tục đọc bài viết này để so sánh sự khác nhau giữa cách làm trong bài viết này và cách làm thực hiện ở bài 1.

Hình 1: Kết nối socket trong ví dụ SCPU
Hình trên mô tả lại các kết nối socket của SCPU trong bài 1. Các bạn hãy chú ý đến initiator socket của khối DECODER. Sau khi đóng gọi tất cả output của khối này, gói dữ liệu được truyền qua dc2fetch_socket để đến khối FETCH và qua dc2ex_socket để đến khối EXECUTE.
Chúng ta nhận thấy cấu trúc hàm truyền dữ liệu qua socket của scpu_decoder::DC2FETCH_SOCKET() và scpu_decoder::DC2EX_SOCKET() trong file scpu_decoder.cpp là như nhau. Hai hàm này chỉ khác nhau tên socket. Trong trường hợp này, thay vì khai báo nhiều socket khác nhau, chúng ta có thể sử dụng mảng socket. Mảng socket giúp giảm đáng kể lượng code khai báo và kết nối socket. Đồng thời, việc quản lý các socket cũng dễ dàng hơn trong trường hợp một module tạo ra output đến nhiều module khác nhau.
2) Tạo và kết nối mảng socket
Để thực hiện, chúng ta sẽ thay thế hai initiator socket của DECODER bằng một mảng socket 2 phần tử. Mỗi phần tử tương đương với một initiator socket độc lập nối đến target socket của FETCH và EXECUTE.
Hình 2: Thay thế 2 initiator socket độc lập bằng 1 mảng socket tên dc_output_socket
Trong file scpu_decoder.h, chúng ta sẽ thay đổi code như hình sau:
Hình 3: Code của scpu_decoder.h
Trong file scpu_decoder.cpp, chúng ta sẽ thay 2 method DC2FETCH_SOCKET và DC2EX_SOCKET bằng một method duy nhất. Một vòng lặp được sử dụng để đảm bảo gói dữ liệu của DECODER được gửi qua cả hai phần tử socket trong mảng. Bên cạnh đó, sau mỗi lần gửi, trạng thái của gói dữ liệu cũng được kiểm tra bằng hàm is_response_error() và in ra thông điệp lỗi nếu có.
Hình 4: System C của file scpu_decoder.cpp
Cuối cùng, mô tả kết nối socket trong scpu_top.cpp được thay đổi cho phù hợp với cách khai báo socket mảng.
Hình 5: Kết nối socket trong file scpu_top.cpp
3) Khai báo mảng socket dạng con trỏ
Ngoài cách khai báo "đối tượng mảng socket", các bạn cũng có thể khai báo mảng "con trỏ socket". Khi khai báo dạng này một số code sẽ được điều chỉnh như sau:
File scpu_decoder.h, khai báo socket sử dụng thêm "*".
tlm_utils::simple_initiator_socket<scpu_decoder>* dc_output_socket[2];
Bên cạnh đó, trong constructor SC_CTOR(scpu_decoder), code cấp phát bộ nhớ cho socket được điều khiển (handle) bởi mảng con trỏ được thêm vào:
for (unsigned int i = 0; i < 2; i++) {
  char socket_name[20];
  sprintf(socket_name, "dc_output_socket_%d", i);
 dc_output_socket[i] = new tlm_utils::simple_initiator_socket<scpu_decoder>(socket_name);
        }
Nếu thiếu đoạn code trên, lỗi sẽ sinh ra trong phá trình chạy model. Đối với trường hợp này, bạn có thể nhận được thông điệp lỗi "Segmentation fault (core dumped)" khi thực thi file test_top.exe.
File scpu_decoder.cpp, code gọi hàm b_transport trong method scpu_decoder::DC_OUTPUT_SOCKET() sẽ đổi thành:
(*dc_output_socket[i])->b_transport( *dc_output_trans, wire_delay );  // Blocking transport call
File scpu_top.cpp, code kết nối socket được đổi thành:
scpu_decoder_inst.dc_output_socket[0]->bind(scpu_fetch_inst.dc2fetch_socket);
   scpu_decoder_inst.dc_output_socket[1]->bind(scpu_execute_inst.dc2ex_socket);
Như vậy, qua bài viết này, các bạn có thể hiểu cách khai báo và sử dụng một mảng socket. Mọi ý kiến đóng góp và trao đổi xin comment dưới bài viết.

Dữ liệu có thể download:
pass (nếu có): vlsi_technology

Lịch sử cập nhật:
1. 2019.July.07 - Tạo lần đầu

1. Trương Công Hoàng Việt
2. Lê Hoàng Vân
3. Nguyễn Hùng Quân

Chủ Nhật, 16 tháng 6, 2019

[SystemC][TLM] Bài 1 - Tổng quan về TLM và simple socket

Bài viết này trình bày nhưng khái niệm và thuật ngữ cơ bản về "mô hình hóa mức transaction" Transaction Level Modeling (TLM) trước khi trình bày cách sử dụng TLM trong việc mô hình hóa phần cứng. Bên cạnh đó, bài này sẽ minh họa việc chuyển giao tiếp dạng tín hiệu (signal) giữa các module thành giao tiếp dựa trên socket TLM.
Chú ý, các bạn cần đọc các bài về High Level Design và SystemC mà nhóm tác giả đã trình bày để có các khái niệm cơ bản và hiểu mô hình minh họa trước khi đọc bài này.
1) Tổng quan về TLM
Các quy chuẩn về TLM được nghiên cứu và công bố bởi tổ chức "Open SystemC Initiative (OSCI)".
TLM (Transaction Level Modeling) là một phần của chuẩn SystemC. TLM được tích hợp với chuẩn SystemC vào tháng 7, 2012. Nó nằm thuộc các mục từ 9 đến 17 trong tài liệu IEEE Std. 1666™-2011 tên "IEEE Standard for Standard SystemC® Language Reference Manual".
Bạn đọc có thể biết thêm thông tin tại trang này.
Như tên gọi, TLM dùng để mô hình hóa mức transaction. TLM tập trung vào việc mô hình cách thông tin giữa các process thông qua việc gọi và sử dụng các chức năng có sẵn. Dưới góc nhìn phần cứng, TLM sẽ được sử dụng để mô hình cách trao đổi thông tin giữa các khối (block) hoặc module they vì dùng cách kết nối từng tín hiệu.
Hình 1: Sự khác nhau giữa kết nối theo tín hiệu và kết nối theo mô hình TLM
Đối với mô hình kết nối dạng tín hiệu, đây là dạng mô hình giống với phần cứng, mỗi tín hiệu là một kết nối vật lý giữa các chân của module. Giá trị trên các tín hiệu là tức thời và thay đổi theo xung clock hệ thống của module tạo ra output.
Đối với mô hình kết nối theo TLM, đây là dạng mô hình "cách truyền nhận các gói dữ liệu (transaction)". Module tạo ra output gọi là Initiator, bộ khởi tạo transaction. Module nhận transaction gọi là Target, bộ tiếp nhận transaction. Các input và output port được thay bằng socket. Output port tương ứng với một "Initiator socket". Input port tương ứng với một "Target socket".
Một module có thể vừa là Initiator và vừa là Target khi nó có cả Initiator socket (Output) để gửi dữ liệu đến module khác và có cả Target socket (Input) để nhận dữ liệu từ các module khác gửi đến.
Việc mô hình sử dụng TLM có những lợi điểm sau:
  1. Mô hình giao tiếp giữa các module đơn giản, dễ phát triển vì hai module chỉ cần quy định cấu trúc gói dữ liệu cần truyền và cách thức truyền rồi gọi method thực thi. Điều này giúp giảm thời gian mô tả kết nối các tín hiệu của hai module.
  2. Mô hình các module interconnection như BUS hệ thống sẽ đơn giản hơn. BUS trong một hệ thống SoC là thành phần chuyển dữ liệu từ master đến slave. BUS có thể được mô hình đơn giản bằng cách "nhận transaction từ master, tạo độ trễ như phần cứng thực tế, chuyển transaction đến slave theo địa chỉ được yêu cầu" mà không cần mô hình các chức năng phức tạp của decoder, hay arbiter hay tạo ra các tín hiệu điều khiển phức tạp như thực tế phần cứng.
Hình 2: Minh họa một kênh truyền dữ liệu từ master đến slave trong hệ thống sử dụng kết nối TLM
2) Làm thế nào để tạo TLM connection?
2.1) Cách thực hiện
Việc tạo TLM connection là tạo ra các socket kết nối các module và gọi method thực thi việc truyền nhận dữ liệu trên socket đã tạo. Để tạo một TLM connection, các hoạt động chính cần thực hiện là:
  • Tạo socket
  • Tạo method để truyền/nhận gói dữ liệu (transaction) qua socket
    • Initiator socket
      • Tạo gói dữ liệu
      • Gửi gói dữ liệu
      • Kiểm tra gói dữ liệu đã được nhận bởi Target
    • Target socket
      • Nhận và lưu gói dữ liệu
      • Kiểm tra và báo trạng thái nhận cho Initiator
Hình 3: Tạo kết nối TLM trong header file scpu_decoder.h
Hình 4: Một method dùng để tạo transaction và gửi transaction thông qua initiator socket tên dc2fetch_socket
2.2) Tạo socket
SystemC đã hỗ trợ các class và method cần thiết để dùng cho việc này. Bảng sau đây liệt kê các class có sẵn hỗ trợ việc tạo socket (tham khảo trong tài liệu chuẩn SystemC).
Hình 5: Các class có sẵn hỗ trợ tạo initiator và target socket
Trong bài viết này, nhóm tác giả sử dụng simple_initiator_socket và simple_target_socket (gọi chung là simple socket).
Để tạo một initiator socket, chúng ta cần thực hiện các bước cơ bản sau đây:
  • Include các file thư viện TLM trong file header ".h"
 #include "tlm.h"
 #include "tlm_utils/simple_initiator_socket.h"
  • Khai báo initiator socket với cú pháp:
tlm_utils::simple_initiator_socket<module_name> socket_name;
  • Khởi tạo initiator socket tại Constructor với cú pháp:
SC_CTOR(module_name) : socket_name_0("string_0"), socket_name_1("string_1"), ...{
Để tạo một target socket, chúng ta cần thực hiện các bước cơ bản sau đây:
  • Include các file thư viện TLM trong file header ".h"
 #include "tlm.h"
 #include "tlm_utils/simple_target_socket.h"
  • Khai báo target socket với cú pháp:
tlm_utils::simple_target_socket<module_namesocket_name;
  • Khởi tạo target socket tại Constructor với cú pháp:
SC_CTOR(module_name) : socket_name_0("string_0"), socket_name_1("string_1"), ...{
2.3) Method truyền/gửi transaction của Initiator socket
Các bước cơ bản để gửi một transaction hay một gói dữ liệu qua initiator socket:
  • Khai báo một method cho việc gửi dữ liệu:
void method_name();
  • Khởi tạo method trong Constructor dùng SC_THREAD. Chú ý, SC_METHOD không thể sử dụng vì SC_METHOD không chấp nhận các tác vụ lặp hay delay.
SC_THREAD (method_name);
  • Mô tả gói dữ liệu (transaction) và cách thức gửi gói dữ liệu trong method đã tạo
2.4) Method nhận transaction của Target socket
Các bước cơ bản để nhận một transaction hay một gói dữ liệu qua target socket:
  • Đăng ký callback cho method truyền nhận dữ liệu qua target socket trong Constructor:
socket_name.register_b_transport(this, &module_name::method_name);
    • Trong đó:
      • socket_name là tên target socket mà bạn sẽ lấy transaction từ nó.
      • register_b_transporst là method đăng ký callback cho hàm b_transport. b_transport là một loại hàm được sử dụng để truyền, nhận dữ liệu qua socket. Cái này sẽ được trình bày ngay trong phần sau đây.
      • module_name là tên module gắn target socket
      • method_name là tên method mà trong đó bạn sẽ viết code để lấy dữ liệu từ target socket.
  • Xây dựng method lấy dữ liệu từ target socket
2.5) Tạo và truyền/nhận gói dữ liệu (transaction)
Các phần trên đã trình bày "làm thế nào để tạo socket?". Vấn đề còn lại là "làm thế nào để truyền nhận dữ liệu qua socket?". Để giải đáp điều này, trước hết chúng ta cần hiểu về một gói dữ liệu (transaction) được gửi qua socket có cấu trúc như thế nào.
2.5.1) Tạo và gửi transaction tại initiator socket
Một gói dữ liệu (transaction) gửi qua socket được tạo bởi loại dữ liệu tlm_generic_payload. Đây là một cấu dữ liệu mặc định giúp mô hình hóa các transaction.
tlm::tlm_generic_payload* trans_name = new tlm::tlm_generic_payload;
Trong đó:
  • trans_name là tên transaction do người dung đặt
  • new là hàm tạo đối tượng trans_name
Một transaction được khai báo loại tlm_generic_payload sẽ được hỗ trợ sẵn các method cần thiết để người dùng có thể thiết lập các thuộc tính và dữ liệu mong muốn cho transaction ở phía initiator socket và đọc các thuộc tính, lấy dữ liệu của transaction tại target socket.
Để thiết lập thuộc tính và giá trị cho một transaction tại initiator socket, chúng ta dùng các hàm có tiền tố "set_".
  • set_command( const tlm_command );
    • Thiết lập loại transaction
    • Initiator sẽ thiết lập loại transaction để target đáp ứng phù hợp với từng loại
      • TLM_READ_COMMAND: Target sẽ lấy dữ liệu của nó gán đến vùng bộ nhớ xác định bởi con trỏ dữ liệu mà initiator đã gửi
      • TLM_WRITE_COMMAND: Target sẽ đọc dữ liệu từ vùng bộ nhớ xác định bởi con trỏ dữ liệu mà initiator đã gửi
      • TLM_IGNORE_COMMAND: Target không cần thực thi gì khi nhận được loại transaction này. 
  • set_address( const sc_dt::uint64 );
    • Thiết lập địa chỉ cho transaction
  • set_data_ptr( unsigned char* );
    • Thiết lập giá trị pointer trỏ đến vùng lưu dữ liệu cần truyền qua socket
  • set_data_length( const unsigned int );
    • Thiết lập độ dài dữ liệu theo byte
  • set_streaming_width( const unsigned int );
    • Thiết lập độ dài burst
  • set_byte_enable_ptr( unsigned char* );
    • Thiết lập giá trị pointer trỏ đến cùng lưu giá trị byte enable của transaction.
  • set_dmi_allowed( bool );
    • Thiết lập giá trị điều khiển cơ chế DMI
  • set_response_status( const tlm_response_status ); 
    • Thiết lập trạng thái của transaction. Target sẽ thiết lập một giá trị thích hợp để báo cho initiator biết trạng thái của transaction mà nó nhận được
      • TLM_OK_RESPONSE = 1 
      • TLM_INCOMPLETE_RESPONSE = 0
      • TLM_GENERIC_ERROR_RESPONSE = –1
      • TLM_ADDRESS_ERROR_RESPONSE = –2
      • TLM_COMMAND_ERROR_RESPONSE = –3
      • TLM_BURST_ERROR_RESPONSE = –4
      • TLM_BURST_ERROR_RESPONSE = –4 
Hình 6: Minh họa việc truyền transaction thông qua simple socket sử dụng b_transport
Trong phần này, nhóm tác giả sử dụng hàm b_transport để truyền transaction tại initiator socket.
socket_name->b_transport (trans_name, delay_value);
Trong đó:
  • socket_name là tên của initiator socket
  • trans_name là tên của transaction được khai báo bởi tlm_generic_payload.
  • delay_name là giá trị delay được khai báo loại sc_time.
2.5.2) Đăng ký callback và nhận transaction tại target socket
Việc đăng ký callback cho b_transport đã trình bày ở mục trên.
Để đọc thuộc tính và giá trị của một transaction tại target socket, chúng ta dùng các hàm có tiền tố "get_". Mỗi hàm "set_" của một tlm_generic_payload sẽ có một hàm "get_" tương ứng. Các hàm "get_" sẽ được sử dụng để lấy thông tin các thuộc tính của một transaction.
  • get_command();
  • get_address();
  • get_data_ptr();
  • get_data_length();
  • get_streaming_width();
  • get_byte_enable_ptr();
  • get_response_status();
2.6) Quá trình truyền nhận thông qua TLM socket
Quá trình truyền nhận sử dụng hàm b_transport thông qua TLM socket được tóm gọn như sau:
  • Initiator:
    • Tạo transaction tlm_generic_payload.
    • Gửi transaction bằng hàm b_transport
  • Target:
    • Nhận transaction
    • Báo lại trạng thái transaction cho Initiator bằng cách dùng hàm set_response_status.
3) Áp dụng simple TLM socket với thiết kế SCPU
Chú ý, ví dụ về SCPU thuộc loạt bài giới thiệu về HLD khả tổng hợp. Nhóm tác giả sẽ dùng ví dụ đã hoàn thiện này để chuyển đổi tất cả các kết nối tín hiệu giữa các module thành kết nối dùng simple TLM socket.
Chú ý, SCPU đã là một mô hình hoàn chỉnh chạy theo xung clock hệ thống. Việc chuyển đổi phải đảm bảo mô hình chức năng của SCPU vẫn chạy đúng. Ý tưởng chính của cách chuyển đổi là:
  • Chuyển đổi các port giao tiếp của module (sc_in, sc_out) thành các tín hiệu nội (sc_signal) và biến nội (sc_uint)
  • Tất cả các chân output (sc_out) của module sau khi chuyển thành tín hiệu hoặc biến nội sẽ được tập hợp thành một gói dữ liệu để gửi qua initiator socket
  • Tất cả các chân input (sc_in) của module sau khi được chuyển thành tín hiệu hoặc biến nội sẽ lấy giá trị từ gói dữ liệu nhận được trên một target socket.

Hình 7: Minh họa việc chuyển từ kết nối tín hiệu (signal) thành kết nối TLM socket
Xét lại hình minh họa 1, hình này thể hiện rõ kết nối kiểu tín hiệu giữa FETCH và DECODER. Nhóm tác giả sẽ giải thích rõ cách chuyển đổi trên ví dụ minh họa này. Các kết nối còn lại sẽ thực hiện tương tự.
FETCH là khối gửi 3 tín hiệu fetch_ir, fetch_dr fetch_mem_dout còn DECODER là khối nhận nên:
  • Tại module FETCH:
    • Các output fetch_ir, fetch_dr fetch_mem_dout sẽ chuyển từ khai báo sc_out thành biến nội với khai báo sc_uint.
    • Khai báo một initiator socket tên fetch2dc_socket
tlm_utils::simple_initiator_socket<scpu_fetch> fetch2dc_socket;
    • Khai báo một method để gửi transaction qua socket trên
 void FETCH2DC_SOCKET();
    • Tạo một biến nội để đóng gói tất cả các giá trị output mà FETCH cần gửi với khai báo sc_uint với độ rộng bit bằng độ rộng bit của 3 tín hiệu ngõ ra cộng lại.
 sc_uint<24> fetch_data;
    • Tạo một biến trung gian để nhận giá trị đóng gói từ biến sc_unit và có loại dữ liệu phù hợp kiểu dữ liệu sử dụng trong hàm set_data_ptr.
unsigned int fetch_data_tmp;
    • Tạo một transaction
tlm::tlm_generic_payload* fetch2dc_trans = new tlm::tlm_generic_payload;
    • Khai báo thông số delay cần dùng
sc_time wire_delay = sc_time(0, SC_PS);
sc_time trans_delay = sc_time(10, SC_PS);
    • Đóng gói dữ liệu cần truyền qua initiator socket
fetch_data = (fetch_ir, fetch_dr, fetch_mem_dout);
fetch_data_tmp = fetch_data;
    • Cấu hình transaction
      • Loại lệnh luôn là WRITE vì đây là output từ FETCH đến DECODER
tlm::tlm_command fetch_cmd = tlm::TLM_WRITE_COMMAND;
fetch2dc_trans->set_command( fetch_cmd );
      • Thiết lập con trỏ dữ liệu cần truyền đến biến tạm fetch_data_tmp
 fetch2dc_trans->set_data_ptr(reinterpret_cast<unsigned char*>(&fetch_data_tmp));
      • Thiết lập độ dài dữ liệu là 3 byte ứng với 24 bit. Nếu độ dài dữ liệu lớn hơn 24 nhưng nhỏ hơn 32 bit thì thiết lập là 4 byte.
 fetch2dc_trans->set_data_length( 3 );
      • Thiết lập độ dài burst bằng độ dài dữ liệu là 3. Điều này nghĩa là không sử dụng streaming
fetch2dc_trans->set_streaming_width( 3 );
      • Thiết lập byte enable là 0 nghĩa là không sử dụng cấu hình này
fetch2dc_trans->set_byte_enable_ptr( 0 );
      • Thiết lập DMI là false, đây là bắt buộc cho phía initiator socket
 fetch2dc_trans->set_dmi_allowed( false );
      • Thiết lập trạng thái response là TLM_INCOMPLETE_RESPONSE, đây là bắt buộc cho phía initiator socket. Sau khi nhận được transaction, target socket sẽ thiết lập lại trạng thái response đến 1 giá trị phù hợp.
fetch2dc_trans->set_response_status( tlm::TLM_INCOMPLETE_RESPONSE );
    • Gửi transaction với delay là 0
fetch2dc_socket->b_transport( *fetch2dc_trans, wire_delay );
    • Giám sát trạng thái response
if ( fetch2dc_trans->is_response_error() )
  SC_REPORT_ERROR("TLM-2", "Response error from b_transport");
    • Delay giữa hai lần truyền. Các module hoạt động theo cạnh lên xung clock nên dữ liệu truyền qua socket phải đảm bảo target nhận được dữ liệu đúng trong từng chu kỳ. Vì vậy, trans_delay được thiết lập rất nhỏ so với chu kỳ hoạt động cấp cho SCPU khi mô phỏng.
 wait(trans_delay);
  • Tại module DECODER:
    • Tất cả các input chuyển từ sc_in thành sc_signal.
 sc_signal<sc_uint<8> > fetch_ir;
 sc_signal<sc_uint<8> > fetch_dr;
 sc_signal<sc_uint<8> > fetch_mem_dout;
    • Tạo một biến có loại dữ liệu phù hợp với hàm get_data_ptr
 unsigned int fetch2dc_pkt_tmp; 
    • Tạo một biến sc_uint có độ rộng bit bằng với số bit mà khối FETCH truyền tới. Biến này sẽ được dùng để tách từng bit dữ liệu gán đến tín hiệu tương ứng
 sc_uint<24> fetch2dc_pkt;
    • Khai báo target socket
tlm_utils::simple_target_socket<scpu_decoder> fetch2dc_socket;
    • Đăng ký callback cho b_transport
fetch2dc_socket.register_b_transport(this, &scpu_decoder::fetch2dc_transport)
    • Đọc gói transaction gửi từ FETCH
tlm::tlm_command dc_cmd = fetch2dc_trans.get_command();
unsigned char*   dc_ptr = fetch2dc_trans.get_data_ptr();
unsigned int     dc_len = fetch2dc_trans.get_data_length();
unsigned char*   dc_byt = fetch2dc_trans.get_byte_enable_ptr();
unsigned int     dc_wid = fetch2dc_trans.get_streaming_width();
    • Cấp phát bộ nhớ để lưu lại dữ liệu từ transaction
memcpy (&fetch2dc_pkt_tmp, dc_ptr, dc_len);
fetch2dc_pkt = fetch2dc_pkt_tmp;
    • Tách các bit dữ liệu cần dùng gán đến tín hiệu mong muốn
fetch_ir   = fetch2dc_pkt.range(23,16);
fetch_dr   = fetch2dc_pkt.range(15,8);
fetch_mem_dout  = fetch2dc_pkt.range(7,0);
    • Trả response lại cho initiator socket
fetch2dc_trans.set_response_status( tlm::TLM_OK_RESPONSE );
Các bạn có thể tham khảo và chạy mô phỏng với source code đầy đủ được chia sẻ ở link dưới bài viết.

Dữ liệu có thể download:
pass (nếu có): vlsi_technology

Lịch sử cập nhật:
1. 2019.June.16 - Tạo lần đầu
2. 2019.July.06 - Thêm pass giải nén

Danh sách tác giả:
1. Trương Công Hoàng Việt
2. Lê Hoàng Vân
3. Nguyễn Hùng Quân