快速 SystemC 之旅(一)
- 一、前言背景
- 二、實驗環境
- 1. 安裝步驟
- 2. 驗證安裝
- 三、RTL 級硬件描述
- 1. 初看模塊
- 2. 二輸入與非門
一、前言背景
因項目需求,近期開始開展電子系統級設計(ESL)進行事務級建模(TLM),方案上選擇了 NVIDIA 的開源架構 NVDLA 。
NVDLA 基于 GreenSocs 的 QBOX 框架,也是一種 QEMU 和 SystemC 聯合仿真的解決方案。為學習該開源框架,為深入理解該開源架構,筆者開始系統學習 QEMU 與 SystemC。本文為學習 SystemC 過程中記錄的筆記,一方面便于日后查閱,另一方面也希望對想要學習相關知識的朋友一些幫助。
實驗環境基于 NVDLA 框架適配版本:SystemC 2.3.0,使用 C++11 標準編譯。
二、實驗環境
本節介紹 SystemC 2.3.0 的安裝過程。SystemC 本質上是一個基于 C++ 的建模擴展庫。
1. 安裝步驟
使用以下命令完成 SystemC 2.3.0 的下載、編譯與安裝:
sudo wget -O systemc-2.3.0a.tar.gz http://www.accellera.org/images/downloads/standards/systemc/systemc-2.3.0a.tar.gz
tar -xzvf systemc-2.3.0a.tar.gz
cd systemc-2.3.0a
sudo mkdir -p /usr/local/systemc-2.3.0/
mkdir objdir
cd objdir
../configure --prefix=/usr/local/systemc-2.3.0
make
sudo make install
2. 驗證安裝
安裝完成后,通過編寫一個簡單的 HelloWorld
項目驗證環境配置是否成功。
項目結構
.
├── CMakeLists.txt
└── main.cpp
CMakeLists.txt
:
cmake_minimum_required(VERSION 3.12)# === 項目信息 ===
set(CMAKE_PROJECT_NAME demo_systemc)project(${CMAKE_PROJECT_NAME} VERSION 2025.06.13 LANGUAGES C CXX
)# === 構建類型 ===
if(NOT CMAKE_BUILD_TYPE)set(CMAKE_BUILD_TYPE Debug)
endif()# === 編譯配置 ===
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")# === 可執行目標 ===
add_executable(${CMAKE_PROJECT_NAME})# === 打印信息 ===
message(STATUS "[Build type] : ${CMAKE_BUILD_TYPE}")
message(STATUS "[C++ Standard] : C++${CMAKE_CXX_STANDARD}")
string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE_UPPER)
set(_CXXFLAGS "CMAKE_CXX_FLAGS_${BUILD_TYPE_UPPER}")
message(STATUS "[Compiler Flags] : ${${_CXXFLAGS}}")# === SYSTEMC庫 ===
set(SYSTEM_HOME /usr/local/systemc-2.3.0)
set(SYSTEMC_INCLUDE_DIR ${SYSTEM_HOME}/include)
set(SYSTEMC_LIBRARY ${SYSTEM_HOME}/lib-linux64/libsystemc.a)# === 源文件 ===
file(GLOB SOURCES CONFIGURE_DEPENDS${PROJECT_SOURCE_DIR}/*.cpp
)
target_sources(${CMAKE_PROJECT_NAME} PRIVATE${SOURCES}
)# === 頭文件目錄 ===
target_include_directories(${CMAKE_PROJECT_NAME}PRIVATE${SYSTEMC_INCLUDE_DIR}
)# === 宏定義 ===
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE# Add user defined symbols
)# === 鏈接庫 ===
target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE# Add user defined library search paths
)
target_link_libraries(${CMAKE_PROJECT_NAME}PRIVATE${SYSTEMC_LIBRARY}
)
main.cpp
:
#include <systemc.h>SC_MODULE(Hello)
{SC_CTOR(Hello){SC_METHOD(run);}void run(){std::cout << "Hello SystemC" << std::endl;}
};int sc_main(int, char **)
{Hello hello("hello");sc_start();return 0;
}
編譯與運行
執行以下命令完成編譯與運行:
cmake . -B build/
cmake --build build/
./build/demo_systemc
運行結果如下,表明 SystemC 配置成功:
SystemC 2.3.0-ASI --- May 20 2025 10:22:36Copyright (c) 1996-2012 by all Contributors,ALL RIGHTS RESERVEDHello SystemC
三、RTL 級硬件描述
SystemC 目前能夠描述包括門級在內所有更高抽象級別的數字邏輯電路和軟件,在后續內容中,我們也主要討論基于 SystemC 的 RTL 級以上的硬件描述。但筆者認為 RTL 級建模是一個非常適合作為入門的 Demo,因此本節將用 RTL 級的實例幫助大家快速了解 SystemC 的基本語法與建模風格。
1. 初看模塊
回顧我們之前編寫的 HelloWorld
示例,這段簡潔的代碼已經體現出 SystemC 作為硬件描述語言的基本特征:其核心建模單元是模塊(SC_MODULE
)。
SC_MODULE
是 SystemC 中的基本構建塊,用于表示一個硬件組件。其本質是一個宏定義,展開后為繼承自 sc_core::sc_module
的類(C++ 中,struct
的默認訪問類型是 public
,class
的默認訪問類型是 private
):
#define SC_MODULE(user_module_name) \struct user_module_name : ::sc_core::sc_module
模塊的構造函數通常通過 SC_CTOR
宏定義,該宏除了定義構造函數外,還進行了一些類型聲明:
#define SC_CTOR(user_module_name) \typedef user_module_name SC_CURRENT_USER_MODULE; \user_module_name( ::sc_core::sc_module_name )
其中的 typedef 語句也可以通過單獨的 SC_HAS_PROCESS
宏顯式聲明:
#define SC_HAS_PROCESS(user_module_name) \typedef user_module_name SC_CURRENT_USER_MODULE
SC_HAS_PROCESS
用于注冊 SystemC 的進程(如 SC_METHOD
、SC_THREAD
、SC_CTHREAD
)到仿真內核。
例如,SC_METHOD(func)
用于將 func()
注冊為一個敏感事件觸發的仿真進程。
SystemC 提供了多個宏用于簡化模塊定義過程。根據模塊的構造方式和進程類型,選擇合適的宏可使代碼更簡潔:
- 如果模塊不包含仿真進程,無需使用
SC_CTOR
或SC_HAS_PROCESS
。 - 如果模塊構造函數僅包含
sc_module_name
,使用SC_CTOR
即可。 - 如果模塊構造函數包含額外參數,應使用
SC_HAS_PROCESS
并自行定義構造函數。
以下是一個帶額外參數和仿真進程的模塊示例:
SC_MODULE(module) {const int i;SC_HAS_PROCESS(module);module(sc_module_name name, int i) : i(i) {SC_METHOD(func);}void func() {cout << name() << ", addithonal input argument" << endl;}
};
這里我們僅做簡要介紹,意在帶大家快速上手,后續章節我們將進一步介紹 SystemC 中模塊、進程與敏感列表等關鍵概念。
2. 二輸入與非門
以一個典型的門級電路——二輸入與非門(NAND2)為例,展示 SystemC 在 RTL 級硬件建模中的基本用法。這可以更好的類比于其他的硬件描述語言,有助于大家快速建立認知。為簡潔起見,一些語法細節將于后續章節展開討論。
Nand2.h
:
#ifndef NADN2_H
#define NADN2_H#include <systemc.h>SC_MODULE(Nand2)
{sc_in<bool> input_a; sc_in<bool> input_b;sc_out<bool> output_x; SC_CTOR(Nand2){SC_METHOD(do_nand);sensitive << input_a << input_b;}
private:void do_nand(){output_x.write(!(input_a.read() && input_b.read()));}
}; /* Nand2 */#endif /* NADN2_H */
該模塊實現了一個二輸入與非門:
input_a
和input_b
是bool
型輸入端口。output_x
是bool
型輸出端口。- 成員函數
do_nand()
作為一個SC_METHOD
進程被注冊,其敏感列表包含input_a
和input_b
,即在任一輸入變化時觸發該進程完成對輸出信號output_x
的求值。
為驗證功能正確性,我們編寫一個測試模塊(Testbench):
tb/TB_Nand2.h
:
#ifndef TB_NADN2_H
#define TB_NADN2_H#include <systemc.h>SC_MODULE(TB_Nand2)
{sc_out<bool> output_a;sc_out<bool> output_b;sc_in<bool> input_x;sc_in_clk clk;SC_CTOR(TB_Nand2){SC_CTHREAD(gen_input, clk.pos());SC_METHOD(display_variable);sensitive << input_x << output_a << output_b;dont_initialize();}private:void gen_input(){wait();output_a.write(0); output_b.write(0);wait();output_a.write(0); output_b.write(1);wait();output_a.write(1); output_b.write(0);wait();output_a.write(1); output_b.write(1);wait(100);}void display_variable(){cout << "A = " << output_a.read() << " "<< "B = " << output_b.read() << " "<< "X = " << input_x.read() << endl;}}; /* tb_nand2 */#endif /* TB_NADN2_H */
該測試模塊中包含:
- 一個
SC_CTHREAD
進程gen_input()
依時鐘clk.pos()
生成所有輸入組合。 - 一個
SC_METHOD
進程display_output()
用于輸出當前輸入與輸出狀態。 dont_initialize()
意味著不要在仿真零時刻對SC_METHOD
類型進程display_output()
初始化。如果不使用該語句,運行結果將會出現A = 0 B = 0 X = 0
,這將不符合預期。
有了驗證程序,我們就可以在頂層模塊 Top
中將 Nand2
和 TB_Nand2
進行例化,并連接各個信號,以驗證設計的正確性。
Top.h
:
#ifndef TOP_H
#define TOP_H#include <systemc.h>
#include "Nand2.h"
#include "tb/TB_Nand2.h"SC_MODULE(Top)
{sc_signal<bool> sig_a, sig_b, sig_x;sc_in_clk clk;Nand2 na2;TB_Nand2 tb_na2;SC_CTOR(Top) : na2("NAND2"),tb_na2("TB_NAND2"){na2.input_a(sig_a);na2.input_b(sig_b);na2.output_x(sig_x);tb_na2.output_a(sig_a);tb_na2.output_b(sig_b);tb_na2.input_x(sig_x);tb_na2.clk(clk);}
}; /* Top */#endif /* TOP_H */
最后我們例化頂層模塊 Top
,并添加波形跟蹤:
main.cpp
:
#include "Top.h"int sc_main(int, char **)
{Top top("TOP");sc_clock clk("CLK", 20, SC_NS);top.clk(clk);sc_trace_file *tf = sc_create_vcd_trace_file("Nand2");tf->set_time_unit(1, SC_NS);sc_trace(tf, top.sig_a, "A");sc_trace(tf, top.sig_b, "B");sc_trace(tf, top.sig_x, "X");sc_trace(tf, top.clk, "CLK");sc_start(200, SC_NS);sc_close_vcd_trace_file(tf);return 0;
}
運行結果:
$ ./build/demo_systemc SystemC 2.3.0-ASI --- May 20 2025 10:22:36Copyright (c) 1996-2012 by all Contributors,ALL RIGHTS RESERVEDNote: VCD trace timescale unit is set by user to 1.000000e-09 sec.
A = 0 B = 0 X = 1
A = 0 B = 1 X = 1
A = 1 B = 0 X = 1
A = 1 B = 1 X = 1
A = 1 B = 1 X = 0
輸出驗證了一個 NAND 門的真值功能。
生成的波形文件 Nand2.vcd
可通過以下命令轉換為 Modelsim 支持的格式:
vcd2wlf Nand2.vcd Nand2.wlf
然后在 Modelsim 中加載 Nand2.wlf
文件,即可查看波形:
Ref:
[1] 李揮, 陳曦. SystemC電子系統級設計[M]. 北京: 科學出版社, 2010.
[2] LearnSystemC.com. Learn SystemC[EB/OL]. [2025-06-13]. https://www.learnsystemc.com/