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

    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

    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

    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 SystemC. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn SystemC. Hiển thị tất cả bài đăng

Thứ Bảy, 28 tháng 12, 2019

[SystemC-UVM] Bài 1 - Hướng dẫn cài đặt thư viện UVM-SystemC và SCV

Bài viết này hướng dẫn cách cài đặt và sử dụng thư viện SystemC UVM (Universal Verification Methodology) và SCV phục vụ cho việc xây dựng môi trường mô phỏng UVM-SystemC.
Danh sách các bài viết liên quan:

1) Tổng quan
Để xây dựng một môi trường mô phỏng UVM với SystemC chúng ta cần các thành phần thư viện như sau:
  1. Thư viện SystemC (SystemC Library) - Thư viện xây dựng trên ngôn ngữ C/C++ chứa và định nghĩa các class, method, kiểu dữ liệu và nhân mô phỏng (simulation kernel) cơ sở dùng cho việc mô hình hóa hệ thống gồm cả phần cứng và phần mềm.
  2. Thư viện các thành phần UVM (UVM-SystemC Library) - Thư viện chứa các class, method, và kiểu dữ liệu dùng để tạo các thành phần cho môi trường mô phỏng theo phương pháp UVM
  3. Thư viện SCV (SystemC Verification Library) - Thư viện chứa một tập các API (Application Programming Interface) chung dùng cho các hoạt động kiểm tra trên SystemC như tạo các giá trị ràng buộc, ghi chép lại transaction, ...

Các thưc viện trên có thể tải miễn phí với mã nguồn mở tại website của Accellera ở các liên kết sau đây:
  1. Thư viện SystemC
  2. Thư viện UVM-SystemC
  3. Thư viện SCV
Các bước cơ bản để cài đặt và sử dụng các thư viện của SystemC như sau:
  1. Tìm hiểu các ràng buộc về cài đặt và đặc điểm hỗ trợ của thư viện, xem trong file INSTALL, README và RELEASENOTES của gói cài đặt, như:
    • Phiên bản hệ điều hành (OS), để biết thư viện hoạt động tốt (đã được kiểm tra) trên nền tảng nào
    • Phiên bản trình biên dịch (compiler C/C++), để biết thư viện có thể được tổng hợp tốt (đã được kiểm tra) với trình biên dịch nào
    • Thư viện liên kết, để biết thư viện này sẽ yêu cầu cần có thư viện nào khác hay không? hoặc thư viện này có thể hoạt động chung với thư viện nào khác hay không?
  2. Chuẩn bị môi trường cài đặt chứa các phần mềm (tool) đúng với yêu cầu của thư viện gồm
    • Hệ điều hành
    • Cài đặt các công vụ liên quan:
    • Lưu ý, các bạn có thể dùng các phần mềm và công cụ như hệ điều hành, trình biên dịch, ... khác với phiên bản mà thư viện yêu cầu nhưng điều này không được đảm bảo (non-offical) vì việc kiểm tra trên các nền tảng này chưa được thực hiện đầy đủ
  3. Cài đặt thư viện
    1. Tải gói cài đặt và giải nén
    2. Tạo thư mục sẽ chứa dữ liệu sau khi cài đặt
    3. Tiến hành cài đặt (Thư viện SystemC phải được cài đặt đầu tiên trước các gói thư viện khác)
2) Môi trường cài đặt
Môi trường cài đặt và sử dụng các thư viện SystemC dùng để minh họa trong bài viết này có thông số như sau:
  • Hệ điều hành Windows 7 Professional 64-bit
  • Cygwin terminal 3.1.2, phiên bản 32-bit, chúng ta không dùng phiên bản Cygwin 64-bit vì việc chạy mô phỏng file .exe sau khi biên dịch code SystemC không như mong muốn như đã trình bày trong bài viết
    • Công cụ giải nén: tar phiên bản 1.29
    • Công cụ tạo các file thực thi và cấu hình từ mã nguồn: make phiên bản 4.2.1
    • Trình biên dịch C/C++: gcc phiên bản 7.4.0
Các gói thư viện được sử dụng:
  1. Thư viện SystemC phiên bản 2.3.3 (systemc-2.3.3)
    • Đây không phải phiên bản mới nhất, phiên bản mới nhất là 2.3.3 tính đến ngày 28.12.2019 nhưng không được sử dụng vì phiên bản mới nhất của SCV không hỗ trợ
  2. Thư viện SystemC-UVM phiên bản 1.0-beta2 (uvm-systemc-1.0-beta2)
    • Mới nhất, tính đến ngày 28.12.2019
  3. Thư viện SCV phiên bản 2.0.1 (scv-2.0.1)
    • Mới nhất, tính đến ngày 28.12.2019
Mọi lệnh được trình bày sau đây đều thực hiện trên Cygwin terminal. Các bạn hãy tham khảo cách cài đặt và sử dụng chi tiết ở bài viết sau.

3) Cài đặt thư viện SystemC
Hướng dẫn này đã được trình bày trong bài viết sau đây nên sẽ không trình bày lại.
Hiện nay, phiên bản mới nhất là 2.3.3 tuy nhiên nó chưa được kiểm tra với thư viện SCV. Thư viện SCV mới nhất chỉ hỗ trợ tốt trên phiên bản 2.3.1, 2.3.0 và 2.2.0 tùy vào hệ điều hành.
Các nền tảng cài đặt được hỗ trợ gồm:
   o Linux
     * Architectures
       - x86 (32-bit)
       - x86_64 (64-bit)
       - x86 (32-bit) application running on x86_64 (64-bit) kernel
         (../configure --host=i686-linux-gnu)
   o Mac OS X
     * Architectures
       - x86 (32-bit)
       - x86_64 (64-bit)
       - powerpc (32-bit)   [deprecated]
       - powerpc64 (64-bit) [deprecated]
  o Solaris
     * Architectures
       - SPARC (32-bit)
   o BSD
     * Architectures
       - x86 (32-bit)
       - x86_64 (64-bit)
   o Windows
     * Compatibility layer
       - Cygwin
       - MinGW / MSYS
     * Architectures
       - x86 (32-bit)
       - x86_64 (64-bit)
Trình biện dịch được hỗ trợ gồm:
  • GNU C++ (gcc), phiên bản 3.4 trở lên
  • Clang C++ phiên bản 3.0 trở lên
Giả sử sau khi cài đặt đường dẫn thư mục cài đặt thư viện SystemC của bạn là:
/cygdrive/d/20.Project/0.Library/001_SystemC/002_Lib/SystemC233_lib
Chú ý, đường dẫn này sẽ sử dụng cho các phần hướng dẫn tiếp theo.
4) Cài đặt thư viện SystemC-UVM
4.1) Tạo thư mục để cài đặt

Lệnh:
mkdir UvmSystemC_lib
Kết quả: ví dụ đường dẫn thư mục đã tạo là:
/cygdrive/d/20.Project/0.Library/001_SystemC/002_Lib/UvmSystemC_lib
4.2) Giải nén gói thư viện ở một thư mục bất kỳ

Lệnh:
tar -xzf uvm-systemc-1.0-beta2.tar.gz
Kết quả: thư mục chứa source cài đặt được tạo
uvm-systemc-1.0-beta2
4.3) Chuyển đến thư mục đã giải nén
Lệnh:
cd uvm-systemc-1.0-beta2
Nội dung thư mục source:
Hình 1: Thư mục source của thư viện SystemC-UVM
4.4) Cấu hình cài đặt
Lệnh: khai báo đường dẫn thư mục cài đặt ở prefix và đường dẫn thư viện SystemC đã cài đặt trước đó ở --with-systemc
./configure --prefix=/cygdrive/d/20.Project/0.Library/001_SystemC/002_Lib/UvmSystemC_lib --with-systemc=/cygdrive/d/20.Project/0.Library/001_SystemC/002_Lib/SystemC233_lib
Kết quả: Nếu không có bất kỳ lỗi nào phát sinh trong quá trình cấu hình, bạn sẽ nhận được một bảng tóm tắt thông báo các thông tin cấu hình:
  • Phiên bản thư viện UVM-SystemC 1.0-beta2
  • Nền tảng cài đặt là i686-pc-cygwin
  • Các thư mục đã được thiết lập cho việc cài đặt (Directory setup)
  • Nền tảng cài đặt (Target architecture): cygwin
  • Các thiết lập cho việc xây dựng (build) thư viện (Build settings)
Lưu ý, thư mục cài đặt vẫn rỗng sau bước này nếu nó là một thư mục được tạo mới.
Hình 2: Hiển trị trên Cygwin sau khicấu hình cài đặt UVM-SystemC
4.5) Cài đặt thư viện
Nếu thư mục cài đặt đã được cài thư viện trước đó và đây là lần cài đặt lại thì cần thực thi lệnh sau để xóa bỏ dữ liệu cũ trước khi cài đặt:
make clean
Lệnh cài đặt:
make install
Kết quả: Thư mục cài đặt sẽ xuất hiện các thành phần của thư viện UVM-SystemC:
  • docs: chứa các file hướng dẫn về thư viện UVM-SystemC
  • examples: các source code để bạn có thể tham khảo
  • include: các header (.h) file
  • lib-cygwin: cấu hình sử dụng thư viện UVM-SystemC. Lưu ý, tên thư mục này sẽ khác nhau tùy nên tảng cài đặt. Ở đây, tác giả cài trên cygwin nên tên thư mục là lib-cygwin
Hình 3: Thư mục UvmSystemC_lib sau khi cài đặt

Hình 4: Thông tin trên terminal khi kết thúc quá trình cài đặt thư viện UVM-SystemC
5) Cài đặt thư viện SCV
5.1) Tạo thư mục để cài đặt (nếu muốn)
Lệnh:
mkdir scv201_lib
Kết quả: ví dụ đường dẫn thư mục đã tạo là:
/cygdrive/d/20.Project/0.Library/001_SystemC/002_Lib/scv201_lib
Lưu ý, bạn có thể không cần tạo thư mục này mà có thể cài thư viện SCV vào cùng thư mục với thư viện SystemC.
5.2) Giải nén gói thư viện SCV ở một thư mục bất kỳ

Lệnh:
tar -xzf scv-2.0.1.tar.gz
Kết quả: thư mục chứa source cài đặt được tạo
scv-2.0.1
5.3) Chuyển đến thư mục đã giải nén
Lệnh:
cd scv-2.0.1
Nội dung thư mục source:
Hình 5: Thư mục source của thư viện scv-2.0.1
5.4) Cấu hình cài đặt
Thư viện SCV phiên bản 2.0.1 không hỗ trợ gcc 7.4 và nền tảng cygwin. Trong script cấu hình cài đặt SCV, phiên bản hệ điều hành và trình biên dịch sẽ được kiểm tra. Nếu script phát hiện nền tảng hoặc trình biên dịch đang dùng không nằm trong danh sách được hỗ trợ, nó sẽ báo lỗi và không thực hiện việc cấu hình cài đặt.
Hình 6: Báo lỗi "not supported" với trình biên dịch gcc và nền tảng cygwin
Để có thể sử dụng trên nền tảng Cygwin và trình biên dịch gcc 7.4, bạn cần chỉnh sửa lại script cấu hình trong thư mục mã nguồn, file scv-2.0.1/configure.
Đầu tiên, tìm đến dòng "checking for supported operating system" trong script, dòng 16109 và 16110. Bạn sẽ thấy đoạn code dưới đây:
case "$target_os" in solaris2.5*|solaris2.6|solaris2.7|solaris2.8|solaris2.9|solaris2.10|solaris2.11|hpux11*|linux-gnu|darwin*         |mingw32)
Đoạn code trên liệt kê một danh sách các hệ điều hành và nền tảng được hỗ trợ như mingw32, linux-gnu, solaris2.5, ... và không có cygwin. Bạn hãy thêm cygwin vào danh sách kiểm tra này.
case "$target_os" in solaris2.5*|solaris2.6|solaris2.7|solaris2.8|solaris2.9|solaris2.10|solaris2.11|hpux11*|linux-gnu|darwin*         |mingw32|cygwin)
Tiếp theo, tìm đến dòng 16084, bắt đầu với chuỗi ký tự  SUPPORTED_CXX_VERSIONS, nội dung dòng này liệt kê các phiên bản gcc được hỗ trợ. Bạn cần thêm 7.4 vào danh sách này.
SUPPORTED_CXX_VERSIONS="(3.2|3.4|4.1|4.2|4.3|4.4|4.5|4.6|4.7|4.8|4.9|5.4|6.1|6.3|7.4)"
Sau đó, chạy lệnh cấu hình cài đặt với đường dẫn thư mục thư viện SystemC đã cài trước đó:
./configure --with-systemc=/cygdrive/d/20.Project/0.Library/001_SystemC/002_Lib/SystemC233_lib
Lệnh trên không chứa tùy chọn "--prefix" nên thư viện SCV sẽ được cài vào thư mục SystemC233_lib, chung với thư viện SystemC. Trong quá trình, script configure thực thi, bạn sẽ thấy hiệu quả của việc chỉnh sửa trên qua hai thông báo sau trên cygwin terminal:
checking for supported C++ compiler... gcc 7.4 is supported
checking for supported operating system... cygwin is supported
Lưu ý, việc gcc 7.4 và cygwin không có trong danh sách được hỗ trợ là bởi vì thư viện chưa được kiểm tra đầy đủ và toàn diện trên các nền tảng này. Việc chỉnh sửa để có thể cài đặt và sử dụng có thể gây ra lỗi "nào đó". Nếu bạn không muốn gặp rủi ro thì hãy cài đặt và sử dụng trên các nền tảng đã được khuyến cáo.
HÌnh 7: Hiển thị trên cygwin khi quá trình cấu hình cài đặt scv-2.0.1 kết thúc
5.5) Cài đặt thư viện
Nếu thư mục cài đặt đã được cài thư viện trước đó và đây là lần cài đặt lại thì cần thực thi lệnh sau để xóa bỏ dữ liệu cũ trước khi cài đặt:
make clean
Tiếp theo, thực hiện một số chỉnh sửa ở file /scv-2.0.1/src/scv/scv_init_seed.cpp trước khi cài đặt như sau:
  • Thêm "#define _BSD_SOURCE" vào đầu file
  • Sửa dòng 52 từ " #include <time.h>" thành " #include <sys/time.h>"

Nếu các dòng trên không được chỉnh sửa, lỗi sau đây sẽ xuất hiện khi thực thi cài đặt:
Hình 8: Lỗi xuất hiện ở file scv_init_seed.cpp
Biên dịch lại mã nguồn:
make
Hình 9: Thông điệp hiển thị trên cygwin sau khi kết thúc lệnh make
Thực thi lệnh cài đặt:
make install
Kết quả: Trong thư mục /cygdrive/d/20.Project/0.Library/001_SystemC/002_Lib/SystemC233_lib/include sẽ có thư viện SCV gồm:
  • scv.h
  • scv/
Hình 10: Hiển thị trên cygwin sau khi kết thúc quá trình cài đặt SCV
6) Kiểm tra các thư viện đã cài đặt
  • Tải môi trường UVM-SystemC trên Github 
  • Vào thư mục apbUartUvmSystemC/sim, chỉnh sửa lại đường dẫn các thư viện và đường dẫn môi trườn trong file run.csh
setenv SYSTEMCLIB cygdrive/d/20.Project/0.Library/001_SystemC/002_Lib/SystemC233_lib
setenv UVMLIB cygdrive/d/20.Project/0.Library/001_SystemC/002_Lib/UvmSystemC_lib
setenv WDIR cygdrive/d/20.Project/3.Github/apbUartUvmSystemC
  • Thực thi lệnh ./run.csh trong thư mục sim
  • Kết quả: file testTop.exe được tạo ra và không có bất kỳ lỗi nào
Hình 11: Thư mục apbUartUvmSystemC/sim

Dữ liệu có thể tải:

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

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

Chủ Nhật, 17 tháng 11, 2019

[SystemC][SC_(C)THREAD] Bài 1 - Mô hình mạch tuần tự tổng hợp được bằng SC_THREAD và SC_CTHREAD

Việc mô hình mạch tổ hợp và tuần tự bằng SC_METHOD đã được giới thiệu trong một số bài viết trước đây. Bài viết này trình bày về cách mô hình mạch tuần tự tổng hợp được, mạch có Flip-Flop (FF), bằng SC_THREAD và SC_CTHREAD. Bên cạnh đó, bài viết cũng sẽ giải thích sự tương đồng giữa hành vi của SystemC code và hoạt động phần cứng.
Tham khảo bài viết về mô hình phần cứng dùng SC_METHOD ở các bài viết sau:
1) Gới hạn của SC_THREAD và SC_CTHREAD trong mô tả mạch tuần tự
SC_THREAD và SC_CTHREAD được phép dùng để mô tả mạch tuần tự (sequential circuit) khả tổng hợp nhưng phải tuân thủ một số yêu cầu nhất định để phù hợp với mô hình phần cứng thực tế.
  • Danh sách độ nhạy của một process phải được mô tả trong constructor của SC_MODULE chứa process đó.
  • Một process phải hoạt động theo một cạnh clock.
  • Một process phải có ít nhất một mô tả reset. Reset này là loại reset đồng bộ hoặc bất đồng bộ.
  • Reset đồng bộ được khai báo bằng:
reset_signal_is(<reset_name>, <active_level>)
  • Reset bất đồng bộ được khai báo bằng:
async_reset_signal_is(<reset_name>, <active_level>)
Trong đó:
  • reset_name là tên tín hiệu reset
  • active_level là mức tích cực của tín hiệu reset, nó là true, tích cực mức cao, hoặc false, tích cực mức thấp.
Trong SC_THREAD và SC_CTHREAD, để tạo trễ (tạm dừng) trong process, chỉ hai chức năng sau được hỗ trợ:
  • wait()
  • wait(<number>) – number là một số nguyên dương
Chỉ các vòng lặp sau được hỗ trợ:
  • while( 1 ) { }
  • while( true ) { }
  • do { } while ( 1 );
  • do { } while ( true );
  • for ( ; ; ) { }
2) Cấu trúc của SC_THREAD và SC_CTHREAD khi mô tả mạch tuần tự
Phần này sẽ trình bày cụ thể cách làm thế nào để sử dụng các thành phần đã liệt kê trên đây trong SC_THREAD và SC_CTHREAD để mô hình mạch tuần tự tổng hợp được.
Một mạch tuần tự dùng FF có hai hoạt động chính:
  • Hoạt động reset (có thể có hoặc không)
  • Hoạt động chức năng thông thường (phải có)
Một mạch tuần tự dùng SC_(C)THREAD cũng sẽ được mô hình hóa theo hai hành vi trên. Cấu trúc mô hình gồm:
  • Reset behavior: Gán giá trị reset cho các FF. Dùng cấu trúc sau để gán reset cho một thanh ghi (FF)
<signal_name>.write(<reset_value>)
Trong đó:
  • signal_name: tên thanh ghi (FF)
  • reset_value: giá trị reset muốn gán cho thanh ghi
  • wait(): Chờ đến chu kỳ clock kế tiếp sau khi kết thúc reset
  • Operational behavior: Một vòng lặp vô tận thực thi chức năng khi không có reset
    • Dùng while(1), while(true), do...while(1), do...while(true) hoặc vòng lặp for với điều kiện lặp vô tận để thực hiện ví dụ như for (int i=0; i == 0, i=0). Trong ví dụ của for, điều kiện thực hiện vòng lặp for là i bằng 0 và i luôn được gán bằng 0 nên vòng lặp for này là vòng lặp vô tận.
    • Read input: đọc các giá trị sử dụng làm đầu vào điều khiển mạch tuần tự gán vào một biến tạm sử dụng method read(). Mục đích của bước này là tránh phải dùng method read() nhiều lần trong khi mô tả hành vi mạch tuần tự.
    • Assign output: Dùng các biến tạm của bước "Read input" để mô tả hành vi mạch tuần tự. Bước này thực chất là gán giá trị cho các thanh ghi (FF) dựa trên các điều kiện ngõ vào lấy từ bước "Read input".
Hình 1: Cấu trúc của một SC_(C)THREAD dùng để mô hình hóa mạch tuần tự
Hình sau đây giải thích vì sao cấu trúc trên có thể mô hình hóa một mạch tuần tự. Hành vi reset (reset behavior) được thực thi khi reset tích cực, rst_n=0. Giá trị reset được duy trì đến cạnh lên clk đầu tiên bằng hàm wait() đầu tiên, nằm ngoài vòng lặp vô tận. Sau đó, ngõ ra Q của FF sẽ bắt đầu cập nhật giá trị từ D theo từng cạnh lên xung clock clk, nghĩa là cập nhật theo mỗi chu kỳ xung clock clk. Điều này được mô hình bởi một vòng lặp vô tận với một hàm wait() ở cuối vòng lặp.
Hình 2: Sự tương đồng giữa mô hình mạch tuần tự bằng SC_(C)THREAD với hành vi của một FF
3) Ví dụ về mô hình mạch tuần tự dùng SC_(C)THREAD
3.1) Yêu cầu
Giả sử, chúng ta cần mô hình hóa bộ đếm có mạch nguyên lý như sau:
  • rst_n là tín hiệu reset đồng bộ theo xung clock clk
  • plus_en là tín hiệu điều khiển việc bộ đếm sẽ tăng hay giảm 1 đơn vị theo xung clock clk
Hình 3: Mạch nguyên lý bộ đếm lên và xuống 1 đơn vị
Bộ đếm trên sẽ nằm trong một module tên counter để có thể tổng hợp và kiểm tra trên Vivado HDL.
Hình 4: Module counter
Một module trong SystemC code được mô tả trong 2 file là:
  • file header (.h)
  • file mô tả chức năng các process (.cpp)
3.2) Khai báo process và các biến (file .h)
Trong file này chúng ta cần khai báo:
  • Một process dùng để gán giá trị reset cho mạch tuần tự, initReset(). Đây chính là thành phần mô hình hóa hành vi reset (Reset behavior) như đã mô tả ở mục trên
  • Một process dùng để mô hình hóa mạch tuần tự, seqLogic(). Process này sẽ thực thi đầy đủ cấu trúc mô hình hóa mạch tuần tự như đã trình bày ở mục trên gồm:
    • initReset()
    • wait()
    • Vòng lặp vô tận dành cho "Operational behavior"
Ngoài ra, vì counter_value là port ngõ ra của module counter nên không thể dùng trực tiếp biến này trong process seqLogic() vì một port ngõ ra không được phép đọc lại giá trị để mô tả chức năng bộ đếm. Ví dụ, đoạn code sau đây là không hợp lệ:
counter_value.write(counter_value.read() + 1);
vì counter_value là sc_out nên counter_value.read() không được phép sử dụng.
Vì vậy, một biến trung gian, counter_value_o, được sử dụng để mô hình hóa bộ đếm. Sau đó, biến này sẽ được gán đến ngõ ra counter_value thông qua một mạch tổ hợp được mô hình bằng một SC_METHOD. Mạch tổ hợp này là combLogic().
Hình 5: Mạch tuần tự của bộ đếm trong module counter được mô hình bằng hai process là seqLogic và combLogic
Các biến và process của bộ đếm trong module counter được viết trong file .h như sau:
Ví dụ 1 - Nội dung file counter.h
#include "systemc.h"
SC_MODULE (counter) {
  //
  //(1) Input/Output
  //
  sc_in< bool > clk;
  sc_in< bool > rst_n;
  sc_in< bool > plus_en;
  sc_out< sc_uint<4> > counter_value;
  //
  //(2) Intenal variables
  //
  sc_signal< sc_uint<4> > counter_value_o;
  //(3) Reset process
  void initReset();
  //(4) Function processes
  void seqLogic();
  void combLogic();
  //(5) Constructor
  SC_CTOR(counter) {
    //Sequential model
    SC_CTHREAD(seqLogic, clk.pos());
      reset_signal_is(rst_n, false);
    //Output assignment
    SC_METHOD(combLogic);
      sensitive << counter_value_o;
  }
};
Trong đoạn code trên, SC_CTHREAD được dùng để mô hình mạch tuần tự, là bộ đếm. Các khai báo của SC_CTHREAD trong constructor như sau:
  • seqLogic là tên process
  • clk.pos() là cạnh lên clk
  • reset_signal_is khai báo một reset tên rst_n với mức tích cực thấp (false)
Nếu sử dụng reset bất đồng bộ thì khai báo sẽ là:
async_reset_signal_is(rst_n, false);
Nếu sử dụng SC_THREAD thì khai báo sẽ là:
  • Reset đồng bộ
SC_THREAD(seqLogic);
sensitive << clk.pos();
reset_signal_is( rst_n, false);
  • Reset bất đồng bộ
SC_THREAD(seqLogic);
sensitive << clk.pos();
async_reset_signal_is( rst_n, false);
3.3) Mô hình hóa mạch tuần tự (file .cpp)
Hành vi của process được mô tả trong file .cpp. File này sẽ gọi file header (.h) trước khi mô tả các process.
Ví dụ 2 - Nội dung file counter.cpp
#include "counter.h"
//(1) Reset process - assign the initial value to registers in reset
void counter::initReset() {
  counter_value_o.write(0);
} //initReset
//(2) Function process
void counter::seqLogic() {
  //Call initReset executed when reseting
  initReset();
  //Delay 1 cycle
  wait();
  //Function description
  //It is executed during normal operation when reset is inactive
  while (1) {
    //Read input
    bool plus_en_i = plus_en.read();
    sc_uint<4> counter_value_o_i=counter_value_o.read();
    //Assign output
    if (plus_en_i == 1) {
      counter_value_o.write(counter_value_o_i + 1);
    }
    else {
      counter_value_o.write(counter_value_o_i - 1);
    }
    //
    //Delay 1 cycle before executing next step
    wait();
  }
} //seqLogic
void counter::combLogic() {
  counter_value.write(counter_value_o.read());
} //combLogic
Trong đoạn code trên, process initReset() thực hiện việc gán một giá trị reset là 0 cho bộ đếm counter_value_o.
counter_value_o.write(0);
process này sẽ được gọi và thực thi đầu tiên trong process seqLogic(). Lưu ý, việc gán giá trị reset có thể được mô tả trực tiếp ngay đầu process seqLogic() mà không cần tạo process initReset(). Tuy nhiên, việc tạo một process riêng giúp bạn có thể gán giá trị reset cho nhiều biến trong một thiết kế. Điều này giúp code dễ đọc hiểu hơn.
Ví dụ 3 - Gán giá trị reset trực tiếp bên trong process seqLogic()
void counter::seqLogic() {
  //Call initReset executed when reseting

  counter_value_o.write(0);

  //Delay 1 cycle
  wait();
  //Function description
  //It is executed during normal operation when reset is inactive
  while (1) {
    ...
  }
} //seqLogic
Sau khi gán giá trị reset, một hàm wait() được gọi trước khi mô tả vòng lặp vô tận cho "operational behavior".
Trong vòng lặp vô tận, đoạn code đầu tiên sẽ đọc tất cả các giá trị cần sử dụng trong vòng lặp. Trong ví dụ này là:
bool plus_en_i = plus_en.read();
sc_uint<4> counter_value_o_i = counter_value_o.read();
Điều này giúp tránh phải mô tả <signal_name>.read() nhiều lần trong code khi một tín hiệu được sử dụng nhiều lần.
Tiếp theo code hành vi của bộ đếm khi không có reset được mô tả:
   if (plus_en_i == 1) {
      counter_value_o.write(counter_value_o_i + 1);
    }
    else {
      counter_value_o.write(counter_value_o_i - 1);
    }
Cuối vòng lặp, một wait() được khai báo.
Như đã trình bày, trong ví dụ này, chúng ta cần một process combLogic() mô hình mạch tổ hợp để gán giá trị bộ đếm counter_value_o đến ngõ ra counter_value.
4) Những quy định về việc sử dụng SC_(C)THREAD được mô tả ở đâu?
Hiện tại, các thành phần SystemC khả tổng hợp được mô tả trong một tài liệu tên SystemC Synthesizable Subset của tổ chức Accellera.
Tuy nhiên, nhiều phần mềm tổng hợp SystemC hiện tại không hỗ trợ đầy đủ như mô tả trong tài liệu này. Mỗi phần mềm tổng hợp SystemC sẽ kèm theo những quy định về việc mô tả SystemC code để có thể tổng hợp được tốt nhất với phần mềm đó.
Vì vậy, để đảm bảo một code SystemC có thể tổng hợp được. Người thiết kế cần tuân thủ thêm các yêu cầu mà nhà cung cấp phần mềm quy định. Một số phần mềm hỗ trợ tổng hợp SystemC như Stratus HLS (Cadence), CoCentric™ SystemC Compiler (Synopsys), LegUp HLS (LegUp Computing Inc.), CyberWorkBench (DEC), Vivado HLS (Xilinx), ...
5) Lưu ý khi dùng SC_(C)THREAD
Note 1: Một số trình tổng hợp có thể không hỗ trợ tổng hợp SC_(C)THREAD, ví dụ như SystemC Compiler của Synopsys, phiên bản từ năm 2001 trở về trước, tham khảo tài liệu #3.

Note 2: Theo tài liệu tham khảo #2, mục 4.2.1 Clock and Reset, tín hiệu reset phải được khai báo khi sử dụng SC_(C)THREAD cho dù các FF trong mạch tuần tự cần thiết kế có reset hay không.

Note 3: Theo tài liệu tham khảo #2, việc dùng SC_METHOD để mô tả mạch tuần tự cũng buộc phải khai báo reset cho dù các FF trong mạch tuần tự cần thiết kế có reset hay không, xem mục 4.1.2 Sequential SC_METHOD trong #2.

Note 4: Trong mô hình mạch tuần tự, hàm wait() đầu tiên có ý nghĩa phân tách giữa "reset process" và "operational process". Ngoài cách mô tả dùng 2 hàm wait() như đã trình bày trên đây, chúng ta có thể mô tả một mạch tổ hợp chỉ dùng một hàm wait() đặt ở đầu vòng lặp.
Hình 6: Mô hình mạch tuần tự chỉ dùng một hàm wait()
Note 5: Dùng wait(1) có tương đương với việc dùng wait() trong tổng hợp.

Note 6: Việc dùng nhiều hàm wait() được phân bổ khác với hai mô hình đã trình bày trên đây (hình 1 và hình 6) hoặc dùng wait(<number>) với number>1 có thể tổng hợp được. Tuy nhiên, cách mô tả này không nên sử dụng vì RTL code (Verilog, SV hoặc VHDL) được tạo ra ứng với hành vi wait() có thể khó kiểm soát hoặc không tối ưu như mong muốn người thiết kế. Điều này hoàn toàn phụ thuộc nào phần mềm. Vì vậy, nếu bạn sử dụng cách mô tả này, bạn cần biết chính xác RTL code sẽ được tạo ra bởi trình tổng hợp đang dùng. Để mô tả nhiều wait() hoặc wait(<number>) với number>1, bạn hãy thiết kế mạch logic (tư duy phần cứng) để tạo ra được các delay mong muốn. Nghĩa là chuyển tất cả về dạng mô tả mạch tổ hợp hoặc mạch tuần tự cơ bản như đã trình bày trên đây.
Ví dụ 4 - Mô hình mạch tuần tự với wait(<number>)
void counter::seqLogic() {
  //Call initReset executed when reseting
  initReset();
  //Function description
  while (1) {
    wait(10);
    //Read input
    bool plus_en_i = plus_en.read();
    sc_uint<4> counter_value_o_i=counter_value_o.read();
    //Assign output
    if (plus_en_i == 1) {
      counter_value_o.write(counter_value_o_i + 1);
    }
    else {
      counter_value_o.write(counter_value_o_i - 1);
    }
  }
} //seqLogic
Trong ví dụ trên, wait(10) được sử dụng để mô hình một mạch tuần tự. Với mô hình này, mạch hoạt động như sau:
  1. Reset counter_value_o
  2. Chờ 10 xung clock
  3. counter_value_o bắt đầu đếm lên hoặc xuống 1 đơn vị tùy vào giá trị của plus_en
  4. Chờ 10 xung clock
  5. Lặp lại từ bước 3
Mạch logic để tạo độ trễ 10 chu kỳ xung clock có thể được thực hiện bằng cách dùng một thanh ghi dịch 10 bit. Giá trị reset của thah ghi dịch là 10'b0000000001. Sau khi reset thanh ghi dịch sẽ thực thi dịch trái và xoay vòng. Bit 9 của thanh ghi dịch sẽ được dùng làm tín hiệu cho phép bộ đếm cập nhật giá trị. Mạch nguyên lý thể hiện trong hình vẽ sau đây.
Hình 7: Mạch nguyên lý của một RTL code được tổng hợp từ ví dụ 4
Vivado HLS 2017.4 đang tạo ra RTL code theo một cách gần tương tự như mô tả trên. Cách làm này tốn nhiều tài nguyên vì số lượng FF dùng cho chức năng tạo độ trên wait(10) lớn. Trong khi đó, nó có thể được thực hiện bằng một cách khác là dùng một bộ đếm 4 bit. Bộ đếm này sẽ được reset là 0 và đếm lên 1 đơn vị liên tục sau khi thôi reset. Khi bộ đếm bằng 9 thì nó sẽ được khởi tạo lại giá trị 0 và đếm lại. Giá trị bộ đếm khi bằng 9 sẽ là tín hiệu cho phép cập nhật counter_value.
Hình 8: Dùng bộ đếm 4 bit để tạo độ trễ 10 chu kỳ thay cho wait(10)
Theo cách thiết kế này, SystemC dùng wait(10) sẽ được chuyển về dạng cơ bản chỉ dùng dùng một wait() như sau.
Ví dụ 5 - SystemC code cho sơ đồ nguyên lý hình 8
//(1) Reset process - assign the initial value to registers in reset
void counter::initReset() {
  counter_value_o.write(0);
  delay_counter.write(0);
} //initReset
//(2) Function process
void counter::seqLogic() {
  //Call initReset executed when reseting
  initReset();
  //Function description
  while (1) {
    wait();
    //Read input
    bool plus_en_i = plus_en.read();
    sc_uint<4> counter_value_o_i=counter_value_o.read();
    sc_uint<4> delay_counter_i = delay_counter.read();
    //Assign output
    if (delay_counter_i == 9) {
      delay_counter.write(0);
    }

    else {
      delay_counter.write(delay_counter_i + 1);
    }
    //
    if (delay_counter_i == 9) {
      if (plus_en_i == 1) {
        counter_value_o.write(counter_value_o_i + 1);
      }
      else {
        counter_value_o.write(counter_value_o_i - 1);
      }
    }
  }
} //seqLogic
Note 8: Hành vi reset là hành vi được thực hiện khi tín hiệu reset tích cực. Một giá trị mặc định được gán cho biến trong constructor không phải là giá trị reset. Ví dụ, loại dữ liệu sc_uint có giá trị mặc định là 0 nhưng đây không được xem là giá trị reset. Giá trị reset là giá trị phải được mô tả khi xảy ra hành vi reset, giá trị gán trong initReset() ở các ví dụ đã trình bày.

Tài liệu tham khảo:
1) IEEE; IEEE Standard for Standard SystemC® Language Reference Manual; Jannuary, 2012
2) Accellera; SystemC Synthesizable Subset Version 1.4.7; March, 2016
3) Synopsys; CoCentric SystemC Compiler - RTL User and Modeling Guide; August, 2001

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

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

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 5 và bà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 1, bà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 1, bà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 1 và bà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 5 và bà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 là socket1 và 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 và 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™-2011; 9 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