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.h、c2c.h、cdma.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_ | Tiu | TIU 运算专用,频率对应实际 TIU 工作频率 |
dma_clk_ | GDMA、SDMA、Lmem | DMA 搬运专用,与内存接口同频 |
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_ 的写读关系)