跳到主要内容

01 - TPUPerf 系统架构总览

TPUPerf 是什么

TPUPerf 是 Sophgo SG 系列芯片(SG2260/SG2262)的周期精确 SystemC 仿真器。它运行在硅前(pre-silicon)阶段,用于:

  • 预测真实硬件的性能(TPS、带宽、MFU)
  • 验证编译器生成的指令序列在硬件时序上的正确性
  • 为性能调优提供 cycle 级别的 Profiling 数据

输入:芯片配置 JSON + TIU 指令文件(.BD)+ DMA 指令文件(.GDMA

输出:逐指令 cycle 统计、带宽占用报告、tiuRegInfo_core*.csv / tdmaRegInfo_core*.csv

TPUPerf 基于 SystemC 2.3 + TLM-2.0 实现,完全事件驱动,能精确建模计算延迟、内存 bank conflict、总线仲裁、跨芯片通信等。


SystemC 仿真基础

理解 TPUPerf 代码必须先理解 SystemC 的两种进程原语。

SC_METHOD

特点:每次触发时执行完整函数体,函数执行期间不可挂起
触发:sensitive 列表中的信号边沿或电平变化

TPUPerf 中的用途:

  • Tiu::run():TIU 状态机,每 tiu_clock_ 上升沿触发一次
  • Tdma::cmd_dispatch_mth():DMA 命令调度,响应 tiu_sync_id_ 变化

SC_THREAD

特点:可调用 wait() 阻塞挂起,下次被触发时从挂起点恢复
触发:显式 wait(event) 或 wait(time)

TPUPerf 中的用途:TDMA 内部的 20+ 个数据通路线程(分段、AXI 传输、Cache 路由、响应处理等),每个线程建模一个流水线阶段。

Delta Cycle

信号写入(signal.write(val))在当前 delta cycle 结束后才对其他进程可见,保证因果关系正确性。这解释了为什么 TIU 写 tiu_sync_id_ 后,DMA 的 cmd_dispatch_mth 是在下一个 delta cycle 才重新触发。


顶层模块层次

TPUPerf 的模块层次如下(来自 tpu_subsys.hc2c.hcdma.h):

sc_main()
├── TpuManyCore # 多核顶层容器(SG2262 最多 64 核)
│ │
│ ├── TpuSubsys[0] # 单核子系统(每核完全独立)
│ │ ├── Tiu # TIU 计算引擎(tiu_clock_)
│ │ │ ├── TiuModuleCubeTop # Cube 运算子引擎(CONV / MM / MM2)
│ │ │ └── TiuModuleTop # 向量运算子引擎(AR / SFU / LIN 等)
│ │ ├── Tdma [GDMA 实例] # 全局 DMA:LMEM ↔ DDR(dma_clk_)
│ │ ├── Tdma [SDMA 实例] # 共享 DMA:DDR ↔ DDR(dma_clk_)
│ │ ├── Hau # HAU 协处理器(clock_)
│ │ ├── Lmem # 本地内存 SRAM(dma_clk_)
│ │ └── LmemConflictChecker # Bank Conflict 检测器(BCC)
│ │
│ ├── TpuSubsys[1..N-1] # 其余核(与 [0] 结构完全相同)
│ │
│ ├── SimpleBus # 片内 NxM 总线(GDMA/SDMA → DDR)
│ ├── DDR[0..N-1] # DDR 模型(每核对应独立 bank 区域)
│ ├── gs_cache # Cache(DDR 访问前置)
│ │
│ ├── C2C[0..K-1] # Chip-to-Chip 物理链路(多卡时)
│ ├── Cdma # 跨芯片 DMA(多卡时)
│ └── FakeChip # 远端芯片 stub(接收 C2C 流量)

└── Profiler(全局单例) # 性能数据收集器
└── Brahma::Profiler # 输出 CSV / JSON Profiling 文件

重要细节Tdma 类被实例化了两次,分别作为 GDMA 和 SDMA。两者代码完全相同,区别在于:

  • GDMA 连接到 LMEM 和 DDR(通过 SimpleBus),负责 LMEM ↔ DDR 搬运
  • SDMA 只连接到 DDR,负责 DDR ↔ DDR 搬运,且其 tiu_sync_id_ 输入固定为 0(无依赖)

时钟域划分

TpuSubsys 内部存在三个独立时钟域,来自 tpu_subsys.h 的端口绑定代码:

// tpu_subsys.h(构造函数内)
tiu_instance_->clock_(tiu_clock_); // TIU 专用时钟
tdma_instance_->clock_(dma_clk_); // GDMA 用 DMA 时钟
sdma_instance_->clock_(dma_clk_); // SDMA 用 DMA 时钟
hau_instance_->clock_(clock_); // HAU 用系统时钟
lmem_instance_->clock_(dma_clk_); // LMEM 跟 DMA 时钟
时钟信号连接的模块作用
clock_TpuSubsys SC_METHOD、Hau系统级协调
tiu_clock_TiuTIU 运算专用,频率对应实际 TIU 工作频率
dma_clk_GDMA、SDMA、LmemDMA 搬运专用,与内存接口同频

TpuSubsys 内部关键信号

TpuSubsys 内部用 sc_signal 连接各子模块,以下是最关键的几组信号(来自 tpu_subsys.h):

TIU-DMA 双向同步信号

sc_signal<unsigned int> tdma_sync_id_, tiu_sync_id_;

这两个信号构成 TPUPerf 最核心的同步机制:

信号写方读方语义
tiu_sync_id_Tiu(在 finish() 中写)GDMA、HAU表示 TIU 已完成 cmd_id ≤ X 的所有指令
tdma_sync_id_GDMA(在 cmd_collect_th 中写)Tiu、HAU表示 GDMA 已完成 cmd_id ≤ Y 的所有指令

发射条件tiu.cc: Tiu::init()):

// TIU 指令的发射条件:依赖的 DMA 指令已完成
bool is_valid_issue_cmd = get_front_cmd_gdma_id() <= (int)tdma_sync_id_.read();

发射条件tdma.cc: Tdma::cmd_dispatch_mth()):

// DMA 指令的发射条件:依赖的 TIU 指令已完成
if (tdma_cmd_list_.front().cmd_id_dep_ <= tiu_sync_id_.read())

这一机制允许 TIU 和 DMA 并行执行不互相依赖的指令,只在有数据依赖时通过 cmd_id 强制同步。

SG 保护信号

sc_signal<bool> is_sg_lmem_enable_;

当 TIU 执行 SG(Scatter/Gather,tsk_typ=5)指令时,TIU 将此信号拉低,通知 LMEM 暂停接受 GDMA 的访问请求,避免同时操作同一 bank 导致的冲突。

// tiu.cc: Tiu::init()
if (cur_task_->des_tsk_typ == 6) { // SG 指令
sg_enable_lmem_.write(false); // 禁止 LMEM 接受 DMA 访问
} else {
sg_enable_lmem_.write(true);
}

Idle 状态信号

sc_signal<bool> is_tdma_idle_, is_tiu_idle_, is_hau_idle_, is_sdma_idle_;

各引擎指令队列为空时写 true。TpuManyCore 轮询所有核的 idle 信号,全部为 true 时调用 sc_stop()

TLM Socket 连接

// tpu_subsys.h
tdma_instance_->lmem_external_socket_.bind(lmem_instance_->tsocket_);
for (auto i = 0; i < cfg.gdma_gmem_ports_; i++)
tdma_instance_->gmem_external_socket_[i].bind((*gdma_gmem_socket_)[i]);
sdma_instance_->gmem_external_socket_[0].bind(*sdma_gmem_socket_);
  • GDMA 通过 lmem_external_socket_ 直连 Lmem(TLM 非阻塞传输)
  • GDMA/SDMA 通过 gmem_external_socket_ 连接到上层提供的 socket,最终连接到 SimpleBus → DDR

LmemConflictChecker(BCC)

LmemConflictChecker 通过 sc_port 绑定到 Tiu、GDMA、SDMA 三个引擎:

// tpu_subsys.h
tiu_instance_->bcc_port_(lmem_conflict_checker_);
tdma_instance_->bcc_port_(lmem_conflict_checker_);
sdma_instance_->bcc_port_(lmem_conflict_checker_);

它的作用是在同一 cycle 内检测多个引擎是否访问了 LMEM 的同一 bank,并在 Tiu::finish() 中记录 bank conflict 统计:

// tiu.cc: Tiu::finish()
bcc_port_->set_tiu_latest_cmd_depid(get_front_cmd_gdma_id());
bcc_port_->b_clr_tiu_tsk_typ(cur_task_->des_tsk_typ);

仿真主流程

sc_main()

├── 1. 解析命令行参数(芯片配置 JSON、指令文件路径、核数)

├── 2. 创建时钟信号(clock_、tiu_clock_、dma_clk_)

├── 3. 实例化 TpuManyCore
│ └── 内部实例化 N 个 TpuSubsys + SimpleBus + DDR[N] + Cache
│ (多卡时还有 C2C[K] + Cdma + FakeChip)

├── 4. 将指令列表注入各核
│ ├── tiu_instance_->cmd_list ← 通过 sc_attribute<deque<tiu_reg_t>>
│ └── tdma_instance_->cmd_list_ ← 通过 sc_attribute<deque<gdma_des_t>>

├── 5. sc_start() → SystemC 内核接管,推进事件队列
│ │
│ ├── 每 tiu_clock_ 上升沿 → Tiu::run() (SC_METHOD)
│ │ └── 状态机:INIT → COMPUTE → FINISH
│ │
│ ├── tiu_sync_id_ 变化 → Tdma::cmd_dispatch_mth() (SC_METHOD)
│ │ └── 触发 SC_THREAD 流水线(分段 → AXI 传输 → 响应)
│ │
│ └── 所有 idle 信号 = true → sc_stop()

└── 6. Profiler::finalize()
└── 输出 tiuRegInfo_core*.csv、tdmaRegInfo_core*.csv、profile_*.json

Profiler 数据流

Profiler 是全局单例,在各引擎完成(finish)阶段被回调,记录精确的 start_time 和 end_time:

// tiu.cc: Tiu::finish()
start_time_ = sc_time_stamp(); // 在 init() 中记录
end_time_ = sc_time_stamp(); // 在 finish() 中记录
cur_task_->start_time_ = start_time_;
cur_task_->end_time_ = end_time_;

Profiler::record_tiu_packet(cur_task_); // 记录到 CSV
Brahma::Profiler::get_instance()->dump_profiler(cur_task_); // Brahma 格式

sc_time_stamp() 返回当前 SystemC 仿真时间(以 ns 为单位),因此记录的是仿真时钟时间,不是真实壁钟时间。cycle 数 = (end_time - start_time) / clock_period。


架构图

01-system-architecture.drawio

  • Page 1:模块层次图(嵌套矩形,展示 TpuSubsys 内各引擎与共享资源)
  • Page 2:TIU-DMA 同步信号流图(展示 tiu_sync_id_ / tdma_sync_id_ 的写读关系)