ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

gem5与SystemC联合仿真环境搭建实战指南

gem5与SystemC联合仿真环境搭建实战指南 万事开头难搭 gem5 和 SystemC 联合仿真环境这件事卡住的往往不是知识点本身而是版本匹配、依赖冲突、调度器时序这类“脏活”。我见过太多人卡在编译期还没碰到真正的联合仿真就放弃了。这篇就把从零到一的环境搭建过程完整拆开照着走至少能让你先把两边跑起来。开头先给结论联合仿真环境解决的是“处理器微架构模拟”和“SoC 外围硬件建模”之间的鸿沟。gem5 擅长把 CPU 流水线、Cache 层次、内存控制器模拟得很细SystemC 则擅长用 TLM事务级建模快速搭建总线、DMA、中断控制器这类外围设备。把二者放进同一个仿真框架实际上是在为芯片架构探索、SoC 虚拟样机验证、甚至毕业论文的实验平台做地基。1. 为什么需要 gem5 和 SystemC 联合仿真1.1 gem5 擅长什么又缺什么gem5 是学术界和工业界用得最广的体系结构仿真器之一它的 CPU 模型覆盖了从简单顺序执行到乱序执行的多个层次内存系统的描述也相当灵活配合 Python 配置系统可以很快速地搭出各种架构变体。但它的短板也很明显外设模型和总线协议细节相对粗放。如果你需要精确模拟一个 DMA 控制器的描述符搬运过程或者一个中断控制器在多个核之间的分发逻辑gem5 自带的那些外设模型往往不够用需要你自己写 C 模块去扩展。问题在于gem5 的端口抽象MemPort、MasterPort、SlavePort虽然设计得很干净但它与业界通用的 TLM 2.0 事务模型并不是一回事。在 gem5 里写一个复杂外设你需要理解它的端口语义、请求包结构、事件调度方式这套学习成本并不低。1.2 SystemC 恰好在另一个维度上发力SystemC 是 IEEE 1666 标准定义的 C 硬件建模库核心价值是让硬件工程师用 C 的语法来描述硬件结构同时提供离散事件仿真内核来模拟时间推进。在 TLM-2.0 层面它通过 socket、generic payload、时序约定来统一处理总线事务让模块间的通信建模变得极其高效。很多 SoC 设计团队积累了大量 SystemC 模型包括总线互联、内存控制器、时钟复位模块、外设 IP 等。这些模型在虚拟样机验证中被反复打磨精度和稳定性都很高。如果能让 gem5 的处理器核跑在这种 SystemC 总线环境里整个系统仿真的覆盖度和逼真度都会有本质提升。1.3 两种联合仿真组织形态想清楚再动手所谓联合仿真核心是谁是主调度者。我总结下来常见的有两种第一种叫 SystemC-in-Gem5以 gem5 为主调度者把 SystemC 代码作为模块嵌进 gem5 里。gem5 自带的src/systemc目录就是对 SystemC 运行时的实现或兼容层。这种方式适合只想在 gem5 中替换某几个模块的场景部署轻量但灵活性受限于 gem5 的模块框架。第二种叫 Gem5-in-SystemC以 SystemC 的sc_main作为整个仿真程序的入口gem5 被编译成共享库libgem5_shared.so在 SystemC 环境中作为一个“处理器模型”挂到总线上。这种架构更贴近虚拟样机的组织方式SoC 外围电路全部由 SystemC/TLM 搭建gem5 只负责提供 CPU 核心的计算能力。产业界做大规模虚拟平台时基本都走这条路。本文的实操部分以第二种为主这也是目前 gem5 官方文档推荐的联合仿真主路径。2. 环境准备与版本选型2.1 硬性依赖清单先列一下我在 Ubuntu 22.04 上验证过的依赖组合。如果你用的是其他发行版请对应修改包管理器命令。sudo apt update sudo apt install -y build-essential python3-dev m4 swig \ libprotobuf-dev protobuf-compiler \ libboost-dev libgoogle-perftools-dev \ pkg-config zlib1g-dev这里要特别说明几个关键项build-essential提供 gcc/g版本会直接决定后续 ABI 兼容。swig是 gem5 构建 Python 绑定所必需的没有它编译会在config相关步骤直接报错。libprotobuf-dev和protobuf-compiler用于 gem5 的 trace 相关功能缺了会触发编译错误。我建议先把gcc --version和python3 --version记下来后续排查问题方便对照。除了系统依赖还需要安装 scons。推荐直接用 pip 装pip3 install scons注意gem5 官方对 scons 有版本要求。如果安装后执行scons --version发现版本过高或过低后续编译 gem5 时可能出现语法不兼容。建议锁定 scons 4.x这是目前 gem5 主力支持的版本。2.2 SystemC 库的从零编译这里使用 SystemC 2.3.3它是目前应用最广、资料最全的稳定版本。3.0.0 我也试过但配套工具链的坑会多一些新手阶段不建议给自己加难度。从 Accellera 官方渠道下载源码包然后按以下步骤操作tar -xzf systemc-2.3.3.tar.gz cd systemc-2.3.3 mkdir build cd build ../configure --prefix$HOME/opt/systemc-2.3.3 --enable-pthreads make -j$(nproc) make install有几个参数需要解释清楚--prefix指定安装目录。我习惯装到用户目录下而不是直接扔进/usr/local这样日后想切换 SystemC 版本时改一个路径就行不会污染系统环境。--enable-pthreads开启线程支持。SystemC 的默认仿真调度器是单线程的但 gem5 侧存在多线程并行仿真的可能性开启 pthreads 可以避免后续遇到锁的问题。编译选项里建议加上CXXFLAGS-stdc17。gem5 从 23.0 版本开始默认按 C17 标准编译如果 SystemC 库本身是用 C11 或者 C14 编出来的在链接环节可能因标准库符号不同而造成 ABI 不匹配。安装完成后检查两个关键产物ls $HOME/opt/systemc-2.3.3/include/systemc.h ls $HOME/opt/systemc-2.3.3/lib-linux64/libsystemc.so第一次编译 SystemC 时有个很容易忽略的点make check那一堆测试用例其实可以不跑因为它们耗时很长实际并不能帮你验证与 gem5 的兼容性。把时间和精力留给真正的联调会更划算。2.3 gem5 源码获取与版本检查gem5 的源码托管在官方仓库建议直接 clonegit clone https://github.com/gem5/gem5.git cd gem5 git checkout v24.0.0.0 git submodule update --init --recursive为什么建议锁定版本而不是直接用 master原因是 gem5 的主干分支迭代很快API 变化也很快你按照教程写好的 SC 适配层可能两个月后就不兼容了。定版本号可以让整个实验环境具备可复现性这也是做研究工作的基本习惯。拿到源码后先做一次快速自检确认构建系统正常scons build/ARM/gem5.opt -j$(nproc)这一步会跑一个完整的 gem5 可执行程序编译。如果这一步都无法通过说明要么依赖没装齐要么编译器/系统环境存在兼容问题。先解决这些基础问题再继续往下走。注意首次编译 gem5 会消耗较长时间取决于 CPU 核心数。如果机器性能一般建议用-j4或-j2限制并行度避免内存不足导致编译进程被杀死。3. 编译 gem5 共享库与联合仿真工程搭建3.1 为什么选择 libgem5_shared.so在联合仿真中SystemC 侧的程序是整个仿真的顶层入口。要让 gem5 变成一个可被 SystemC 调用的模块最直接的做法是把 gem5 编译成一个共享库然后在 SystemC 的 C 工程里链接这个库调用其中暴露的接口。scons build/ARM/libgem5_shared.so就是干这个的。它把 gem5 的模拟核心包括 CPU 模型、内存系统、设备模型、事件队列打包成一个动态库并导出与 SystemC 交互相关的符号。有人会问为什么不直接链接gem5.opt因为这个可执行文件已经有main函数了SystemC 程序无法再包含自己的main。共享库则没有这个问题它只是提供符号真正的程序入口还是 SystemC 的sc_main。3.2 构建命令整理与参数释义执行以下命令编译共享库cd gem5 scons build/ARM/libgem5_shared.so -j$(nproc)编译完成后确认产物存在ls build/ARM/libgem5_shared.so如果你想调 DEBUG 版本可以把命令改为scons build/ARM/libgem5_shared.so --debug -j$(nproc)但我个人不建议 DEBUG 版本做联合仿真。DECUG 模式下 gem5 的仿真速度会下降一个数量级SystemC 侧本来调度开销就大两边叠加后运行时间长到你怀疑人生。DEBUG 版本只适合在排查具体问题时临时编译。3.3 联合工程的组织结构目录结构我个人习惯这样整理sim_platform/ ├── CMakeLists.txt ├── sc_main.cpp ├── modules/ │ ├── TLMInterconnect.cpp │ ├── TLMInterconnect.h │ └── TLMDevice.cpp └── scripts/ └── run_hello.shsc_main.cpp是 SystemC 仿真的入口负责实例化所有模块并启动仿真。modules/下放自写的 SystemC 外设模块。CMakeLists 负责把 gem5 共享库和 SystemC 库链接在一起。以下是 CMakeLists 的关键配置cmake_minimum_required(VERSION 3.16) project(gem5_sc_demo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(GEM5_SRC /path/to/gem5) set(GEM5_BUILD /path/to/gem5/build/ARM) set(SC_HOME /path/to/systemc-2.3.3) include_directories( ${GEM5_SRC} ${GEM5_BUILD} ${SC_HOME}/include ) link_directories( ${GEM5_BUILD} ${SC_HOME}/lib-linux64 ) add_executable(gem5_sc_demo sc_main.cpp modules/TLMInterconnect.cpp modules/TLMDevice.cpp ) target_link_libraries(gem5_sc_demo gem5 systemc pthread )这里的核心逻辑是把 gem5 源码目录和构建目录都加进 include 路径以获取其导出的头文件链接时先链接gem5再链接systemc。如果链接顺序反了在某些旧版 gcc 上会出现符号找不到的问题。3.4 关键头文件路径说明在写 SC 适配代码之前你需要知道 gem5 对外开放了哪些 SC 相关接口。不同版本的 gem5 在这方面差异很大建议老老实实去源码里查一查。以我当前使用的 v24.0 为例相关代码主要在src/SC/目录下你可以在 gem5 源码根目录里执行find . -path *SC* -name *.hh | grep -i sim_control看到具体路径后再把对应的 include 语句写进代码。切忌凭经验瞎猜头文件路径否则第一个编译错误就会卡住你十分钟。4. 最小可运行示例的搭建与验证4.1 在 SystemC 中创建顶层模块编写sc_main.cpp的时候大致骨架是这样#include systemc.h #include string #include gem5/SC/SimControl.hh int sc_main(int argc, char* argv[]) { sc_clock clk(clk, 1, SC_NS); sc_signalbool rst_n(rst_n); // 创建 gem5 仿真控制模块 // 这里会根据 gem5 版本暴露的 API 有所不同 // 核心目的是让 SystemC 调度器能够驱动 gem5 事件队列推进 sc_core::sc_time sim_end(0.5, SC_SEC); // 启动仿真 while (sc_time_stamp() sim_end) { sc_start(100, SC_NS); } return 0; }这段代码里SimControl.hh的路径和类名需要以你实际源码为准。我从工程实践的角度解释一下SystemC 的sc_start每推进一次都会触发联合仿真适配层向 gem5 的事件队列注入时间轮片gem5 内部的 CPU 模型、内存系统、总线事务就在这个时间片推进过程中完成仿真。为什么不是一下子sc_start(sim_end)因为 gem5 的事件队列可能因为等待 SystemC 侧某个端口的新事务而陷入空转每次只推进一小段时间让两个调度器反复交替运行启动阶段更稳。4.2 把 gem5 SimObject 接入 SystemC 总线这一步是整个联合仿真中最关键的衔接点gem5 侧是MemPort或者MemExternalPortSystemC 侧是 TLM 的tlm_utils::simple_target_socket两者需要一层适配代码来完成事务格式转换。整个映射关系其实可以类比为“翻译官”gem5 的FunctionalRequest描述的是读写的请求属性比如地址、大小、数据、命中和时间戳SystemC 的tlm_generic_payload也包含地址、数据指针、长度、响应状态等字段。适配层需要做的事就是把 gem5 的内存访问请求转成 TLM 事务再发送到 SystemC 总线上SystemC 总线的响应再原路返回给 gem5。在这个最小示例里挂一个最简单的 SystemC 设备即可比如一个寄存器组模型。核心目标是让数据在 gem5 CPU 和 SystemC 设备之间真正走通一轮而不是空跑仿真。注意不要把 gem5 的Packet直接塞进 SystemC 的事务队列。两者的生命周期管理、内存管理方式完全不同跨边界使用时很容易出现悬垂指针。4.3 编译、运行与预期输出编译时建议分两步走rm -rf build mkdir build cd build cmake .. make -j$(nproc)如果编译过程中出现 gem5 头文件与 SystemC 头文件冲突比如SC_API宏被重复定义首先是检查 CMakeLists 中 include 目录的先后顺序——把 SystemC 头文件路径放在 gem5 之前通常能规避一部分冲突。运行产物./gem5_sc_demo预期能看到两类日志交织输出。一类来自 SystemC 仿真运行时比如模块初始化信息、sc_start 推进的时间戳另一类来自 gem5 仿真内核比如 gem5 版本的 banner、模拟 CPU 的启动日志、运行统计信息。这两类日志同时出现就说明联合仿真环境已经工作起来了。如果 gem5 侧没有任何日志多半是因为 SystemC 调度器没有成功驱动 gem5 的事件循环。这时回查 SimControl 的注册和时钟绑定十有八九是适配层没生效。5. 常见问题与排查技巧实录5.1 编译期头文件与链接问题速查报错现象直接原因排查建议No such file or directory #include systemc.hSystemC 头文件路径未正确加入 include 目录确认$SC_HOME/include是否已加入undefined reference to sc_core::sc_simcontextSystemC 库没有链接或链接顺序错误检查 CMakeLists 中库顺序systemc要放在 gem5 之后gem5 头文件里报SC_API相关宏定义冲突gem5 自带 SystemC 运行时与外部 SystemC 同时包含调整 include 目录先后顺序最好只让外部 SystemC 参与编译编译卡在 protobuf 相关文件protobuf 版本或工具链不匹配检查protoc --version与 gem5 要求的版本是否一致boost 头文件与当前 gcc 版本冲突gcc 太新或太旧建议使用 Ubuntu 22.04 自带 gcc 11.x5.2 运行期时序与同步问题联合仿真最常见的一类故障是两边调度器互相等待导致进程挂起。表现为SystemC 的时间戳停滞在某个值不再推进gem5 侧的事件队列没有任何新的事件注入。排查思路很简单判断两边谁在主动推进时间。SystemC 侧如果只在sc_start之后才推进gem5 侧又只在收到 SystemC 驱动后才唤醒那双方就进入了死锁状态。我经常建议的做法是在仿真启动阶段先用短时间片驱动比如每次sc_start(1, SC_NS)推进逐步增加时间片长度观察是否存在卡死点。另外检查 gem5 SimControl 的启动参数看是否设置了合适的同步周期。同步周期太长事件堆积会导致时间跳跃太短调度开销又会拖慢整体仿真速度。通常情况下同步周期设为一个 SystemC 时钟周期比较合适。第二个常见问题是时钟频率不对齐。SystemC 里sc_clock设为 1 nsgem5 侧 CPU 时钟如果配置成 2 GHz那么两者的时间语义就差了 2 倍。虽然仿真不会崩但统计结果和事件序列都是错的。建议从一开始就把两边的时钟基准统一比如都按 1 ns 作为时间粒度基准gem5 的--cpu-clock参数按这个基准换算成周期数。5.3 老手才知道的避坑清单不要用gem5.opt去做 SystemC 联合仿真。请用libgem5_shared.so省去很多入口函数冲突的问题。首次联调时先让 SystemC 侧跑一个纯 SystemC 的设备不挂 gem5确认 SystemC 环境本身没问题。用build/ARM/gem5.opt先单独跑一个 hello 程序确认 gem5 本身没问题。两边单独都能跑再联调会轻松很多。每次改完 gem5 源码后记得重新编译共享库。很多人调了半天代码结果跑的还是旧库。SystemC 库和 gem5 共享库如果都是用 Debug 模式编译运行速度极慢。发布测试建议全部用 Opt 版本。写在最后的真心话搭这个环境第一次成功跑通时我最大的感受是“终于把两个时钟域拉到了同一条时间线上”。后来遇到的项目多了才发现联合仿真的难点从来不是编译命令而是你对两个仿真内核各自时间语义的理解。SystemC 的数据结构在推着你做细化模型gem5 的统计框架在推着你看微架构指标两者粘在一起才能做出既有时序细节又有系统层面的仿真实验。后续再深入就是把总线协议适配做扎实把多核场景的时钟同步理清楚。每一步都会建立在今天搭好的这套环境上所以第一步走稳了后面会省很多心。
返回列表