1. TC4xx 单片机 PPU 内核详解

1.1 什么是PPU?

PPU(Parallel Processing Unit,并行处理单元) 是英飞凌(Infineon)AURIX™ TC4x系列单片机中的专用协处理器,专为加速高并行计算任务而设计。它与TriCore™ 1.8 CPU协同工作,负责卸载计算密集型任务,如数字信号处理(DSP)和神经网络推理,从而实现最高汽车安全等级(ASIL-D)的人工智能(AI)能力。

备注:本文demo基于tasking编译器分析,如有需求欢迎大家一起交流学习:

support@softor.com.cn

1.2 PPU的核心价值

PPU在AURIX™ TC4x系列中的作用:

  1. 1. 计算卸载:将计算密集型任务从TriCore转移到PPU,释放主CPU资源
  2. 2. 性能提升:在某些基准测试中,性能比单个TriCore 1.8核心提升高达78倍
  3. 3. 实时性保证:保持确定性的实时行为,满足汽车系统的严格时序要求
  4. 4. 安全保障:支持最高汽车安全等级(ASIL-D)
  5. 5. 异构计算:与TriCore形成异构多核架构,各展所长

1.3 PPU的硬件架构

1.3.1 核心设计

PPU基于Synopsys DesignWare ARC EV71架构实现,包含两个主要部分:

  1. 1. 32位RISC处理核心:标量处理单元
  2. 2. SIMD(单指令多数据)向量单元:并行向量处理单元

1.3.2 向量宽度

PPU的向量宽度取决于具体的产品型号:

向量宽度越大,单次可处理的数据越多,并行处理能力越强。

1.3.3 向量DSP单元特性

从英飞凌的技术文档中可以看到,PPU的向量DSP单元具有以下特性:

  1. 1. 多执行槽:支持多达3个并发向量指令,通过3个执行槽(Execution Slots)
  2. 2. 标量+向量并行:除了向量指令外,标量单元还可以同时发出标量指令
  3. 3. 多数据类型支持:可以处理8位、16位或32位的向量元素
  4. 4. 专用执行单元

1.3.4 内存架构

PPU的内存架构设计高效且灵活:

  1. 1. 本地存储层次:拥有自己的本地内存层次结构,包括指令和数据缓存或紧密耦合存储器(TCM)
  2. 2. 共享内存访问:通过片上互连结构连接到共享内存
  3. 3. DRE(Data Routing Engine):数据路由引擎,促进PPU、主存和其他外设之间的高效数据传输
  4. 4. DMA支持:可以使用直接内存访问(DMA)自主获取和存储数据到共享内存

1.4 PPU的主要应用场景

PPU专为汽车和工业应用设计,主要应用场景包括:

1.4.1 电动化和动力控制

1.4.2 高级驾驶辅助系统(ADAS)

1.4.3 域和区域控制器

1.4.4 工业应用

1.5 PPU的关键技术优势

1.5.1 并行处理能力

1.5.2 实时性保证

1.5.3 安全特性

1.5.4 AI能力

1.6 PPU在本项目中的应用

在本PPU demo项目中,PPU的作用包括:

  1. 1. IFFT计算加速:执行复数向量的逆快速傅里叶变换
  2. 2. TriCore卸载:将计算密集型任务从TriCore转移到PPU
  3. 3. 并行处理:利用PPU的SIMD能力加速FFT算法
  4. 4. 双模式演示:展示同步和异步两种调用模式
  5. 5. 性能验证:验证PPU比TriCore快3-5倍的性能优势

1.7 为什么选择PPU?

相比其他方案,使用PPU的优势:

特性
TriCore单独
TriCore + PPU
计算性能
基准
提升3-78倍
实时性
受限
大幅改善
开发复杂度
中等
低(RTE库简化)
安全性
ASIL-D
ASIL-D
AI能力
有限
强大
成本
中等(但性能提升显著)

1.8 总结

TC4xx系列单片机的PPU是一个强大的并行处理单元,它:

  1. 1. 基于ARC EV71架构:成熟的向量处理器设计
  2. 2. 支持128-512位SIMD:强大的并行处理能力
  3. 3. ASIL-D安全合规:适用于最高安全等级的汽车应用
  4. 4. 与TriCore无缝集成:通过RTE库简化开发
  5. 5. 适用多种场景:从电机控制到AI推理

本demo项目正是PPU强大能力的一个缩影,展示了如何通过TASKING SmartCode和RTE库,轻松利用PPU的并行计算能力加速信号处理任务!

2. 项目概述

这是一个基于TASKING PPU Math库和TASKING RTE(Runtime Environment)库的示例项目,演示如何在TriCore处理器上通过RTE库调用PPU(Parallel Processing Unit)执行复数向量的逆快速傅里叶变换(IFFT)操作。

重要提示:RTE库是TASKING SmartCode开发环境提供的一个标准特色库,专门用于简化多核(TriCore + PPU)之间的通信和任务管理。


1.1 项目目的

2. 项目结构

ppu_demo_inf/
├── ppu_math_ifft_cf32_app/        # PPU应用程序
│   ├── g_tsk_rte_config_ppu.c      # PPU RTE配置
│   ├── g_tsk_rte_init_ppu.c        # PPU RTE初始化
│   ├── ppu_main.c                  # PPU主函数
│   ├── ppu_math_ifft_cf32_app.c    # IFFT实现
│   ├── ppu_math_ifft_cf32_app.lsl  # 链接脚本
│   └── readme.txt                  # 说明文档
└── ppu_math_ifft_cf32_tcmain/      # TriCore主程序
    ├── cstart.c                    # C启动文件
    ├── g_tsk_rte_config.h          # RTE配置头文件
    ├── g_tsk_rte_tc0.c             # TriCore RTE实现
    ├── ppu_math_ifft_cf32_tcmain.c # 主程序实现
    ├── ppu_math_ifft_cf32_tcmain.lsl # 链接脚本
    ├── readme.txt                  # 说明文档
    └── user_*.h                    # 用户配置文件

3. TASKING RTE库详解

3.1 什么是TASKING RTE库?

TASKING RTE(Runtime Environment)库是TASKING SmartCode开发环境提供的一个标准特色库,专门为英飞凌AURIX系列芯片(如TC49x)的多核开发而设计。

3.1.1 RTE库的核心价值

RTE库解决了多核开发中的以下关键问题:

  1. 1. 核间通信复杂性:TriCore和PPU是两个完全不同的处理器核,指令集和架构都不同,直接通信非常复杂
  2. 2. 内存管理:需要管理共享内存的分配、释放和一致性
  3. 3. 任务调度:需要在不同核之间传递任务
  4. 4. 同步机制:需要处理同步和异步通信
  5. 5. 代码可移植性:提供统一的API,简化多核开发

3.1.2 RTE库的组成部分

RTE库包含两个主要部分:

  1. 1. TriCore侧库libppu_rte.a):
  2. 2. PPU侧库libppu_rte.a):

3.2 RTE库的核心功能

3.2.1 任务管理

RTE库提供了完整的任务生命周期管理:

任务生命周期:
创建 → 配置 → 提交 → 执行 → 完成 → 通知
  ↓      ↓      ↓      ↓      ↓      ↓
TriCore  TriCore  RTE队列  PPU     PPU    TriCore

**关键API(TriCore侧):

关键API(PPU侧)

3.2.2 内存管理

RTE库提供了预分配的内存池机制:

**内存池的优势:

  1. 1. 避免碎片化:预先分配,运行时不动态分配
  2. 2. 快速访问:使用VCCM高速内存
  3. 3. 简化管理:简单的claim/release机制
  4. 4. 一致性保证:处理缓存一致性问题

关键API

3.2.3 DPF(数据处理函数)机制

这是RTE库最特色的功能之一,允许TriCore调用PPU上的函数:

DPF工作原理

  1. 1. 在PPU侧注册函数到DPF表
  2. 2. TriCore通过function_id调用
  3. 3. RTE负责传递参数和返回结果

预定义的DPF函数(来自user_dpf_functions.h):

3.2.4 通知机制

RTE库支持两种通知方式:

  1. 1. 同步通知TSK_RTE_NOTIFY_BLOCK):
  2. 2. 异步通知TSK_RTE_NOTIFY_CALLBACK):

3.3 RTE库与SmartCode的集成

3.3.1 编译时集成

从makefile可以看到RTE库的使用:

**PPU应用程序链接:

-t "${ECLIPSE_HOME}/../carc/lib/tc49x/libppu_math.a" 
-t "${ECLIPSE_HOME}/../carc/lib/tc49x/libppu_rte.a"
-Wl-lppu_rte

**TriCore主程序链接:

-t "${ECLIPSE_HOME}/../ctc/lib/tc18/libppu_rte.a"

3.3.2 链接时集成

最关键的是--new-task选项:

--new-task=ppu,"ppu_math_ifft_cf32_app.out",...

这个选项是TASKING SmartCode链接器的特色功能,它:

  1. 1. 将PPU程序嵌入到TriCore的ELF文件中
  2. 2. 自动处理两核间的内存布局
  3. 3. 启动时自动加载PPU程序

3.3.3 配置工具集成

RTE库的配置文件(如g_tsk_rte_config.huser_pools_config.h)通常由SmartCode的配置工具自动生成,确保配置的一致性。

3.4 为什么选择TASKING RTE库?

使用TASKING RTE库相比手动实现多核通信的优势:

  1. 1. 降低开发难度
  2. 2. 提高性能
  3. 3. 增强可靠性
  4. 4. 改善可维护性
  5. 5. 工具链无缝集成

4. 核心功能分析

4.1 PPU应用程序

PPU应用程序负责实际执行IFFT操作,主要包含以下文件:

ppu_main.c

ppu_math_ifft_cf32_app.c

3.2 TriCore主程序

TriCore主程序负责准备数据、调用PPU执行IFFT、验证结果,主要包含以下功能:

同步模式示例 (synchronous_example函数)

异步模式示例 (asynchronous_example函数)

5. 技术实现细节

5.1 内存管理

5.2 完整流程说明

5.2.1 整体流程图

┌─────────────────────────────────────┐
│ TriCore 侧                          │
├─────────────────────────────────────┤
│ 1. 初始化 RTE                       │
│ 2. 准备输入数据和预期结果           │
│ 3. 申请队列、任务和内存资源         │
│ 4. 配置任务参数和操作序列           │
│ 5. 设置通知方式(同步/异步)         │
│ 6. 提交任务到队列                   │
│ 7. 等待任务完成(同步/回调)         │
│ 8. 读取结果并验证                   │
└───────────────┬─────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│ 任务队列(RTE管理)                 │
└───────────────┬─────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│ PPU 侧                              │
├─────────────────────────────────────┤
│ 1. 初始化 RTE                       │
│ 2. 循环处理任务                     │
│ 3. 从队列获取任务                   │
│ 4. 解析任务参数                     │
│ 5. 从共享内存读取输入数据           │
│ 6. 初始化 FFT(旋转因子、位反转表) │
│ 7. 执行 IFFT 计算                   │
│ 8. 将结果写回共享内存               │
│ 9. 通知 TriCore 任务完成            │
└─────────────────────────────────────┘

5.2.2 详细步骤说明

步骤1:系统初始化

步骤2:数据准备

步骤3:资源申请

步骤4:任务配置

步骤5:设置通知方式

步骤6:提交任务

步骤7:PPU执行任务

步骤8:结果获取

步骤9:结果验证

步骤10:循环执行

5.3 任务执行流程(简化版)

  1. 1. 数据准备:TriCore准备输入数据(复数数组)和预期结果
  2. 2. 资源申请:TriCore申请队列、任务和内存资源
  3. 3. 任务配置
  4. 4. 任务提交:通过RTE库将任务发送到PPU的任务队列
  5. 5. PPU执行
  6. 6. 结果获取
  7. 7. 结果验证:TriCore比较实际结果与预期结果,统计失败次数

5.4 数据结构

4.4 PPU计算过程详解(结合代码)

代码示例ppu_math_ifft_cf32_app.c中的custom_tsk_ppu_math_ifft_cf32函数

// 1. 数据获取
__vccm _Complex float *a_vccm = (_Complex float __vccm*)(int)tsk_rte_ppu_buffer_get_address(
        unpacked_payload.buffer_id_in0
);

__vccm _Complex float *c_vccm = (_Complex float __vccm*)(int)tsk_rte_ppu_buffer_get_address(
        unpacked_payload.buffer_id_out0
);

unsigned
 int length = unpacked_payload.length;

__vccm float *twiddles = ( float __vccm* )(int)tsk_rte_ppu_buffer_get_address(
        unpacked_payload.buffer_id_twiddles
);

__vccm unsigned short *rev = (unsigned short __vccm*)(int)tsk_rte_ppu_buffer_get_address(
        unpacked_payload.buffer_id_rev
);

// 2. FFT初始化

tsk_fft_init_cf32(twiddles, rev, length);

// 3. IFFT执行

tsk_ifft_cf32(a_vccm, c_vccm, twiddles, rev, length);

详细解释

  1. 1. 数据获取
  2. 2. FFT初始化
  3. 3. IFFT执行
  4. 4. 结果存储

4.5 TriCore与PPU的数据传递机制(结合代码)

代码示例1:TriCore中的数据准备和任务配置(同步模式)

// 1. 资源申请
tsk_rte_queue_id_t
 queue_id = tsk_rte_job_queue_claim(TSK_RTE_QUEUE_0);
tsk_rte_job_id_t
 job_id = tsk_rte_job_claim(TSK_RTE_JOB_0);
tsk_rte_mem_id_t
 buf_a = tsk_rte_mem_claim(TSK_RTE_MEM_VCCM_4);
tsk_rte_mem_id_t
 buf_out = tsk_rte_mem_claim(TSK_RTE_MEM_VCCM_5);
tsk_rte_mem_id_t
 buf_twiddles = tsk_rte_mem_claim(TSK_RTE_MEM_VCCM_6);
tsk_rte_mem_id_t
 buf_rev = tsk_rte_mem_claim(TSK_RTE_MEM_VCCM_7);

// 2. 配置任务参数

custom_tsk_ppu_math_ifft_cf32_payload_t
 custom_dpf_ifft_pay_load = {
        .function_id = CUSTOM_TSK_PPU_MATH_IFFT_CF32_FUNCTION_ID,
        .buffer_id_in0 = buf_a,         
        .buffer_id_out0 = buf_out,
        .buffer_id_twiddles = buf_twiddles,
        .buffer_id_rev = buf_rev,               
        .length = ARRAY_SIZE,
};

// 3. 配置任务操作

tsk_rte_job_init(job_id);
tsk_rte_op_write_memory(job_id, buf_a, a_c, sizeof(a_c));
tsk_rte_op_custom_dpf(job_id, (tsk_rte_function_payload_t*)&payload, sizeof(custom_tsk_ppu_math_ifft_cf32_payload_t));
tsk_rte_op_read_memory(job_id, buf_out, c_c, sizeof(c_c));

// 4. 设置通知方式(同步模式)

tsk_rte_job_set_notify(job_id, TSK_RTE_NOTIFY_BLOCK, NULL, NULL);

// 5. 提交任务

tsk_rte_job_id_t
 id;
tsk_rte_status_t
 status = tsk_rte_queue_add_job(queue_id, job_id, &id);

代码示例2:TriCore中的异步模式实现

// 设置回调函数
tsk_rte_job_set_notify(job_id, TSK_RTE_NOTIFY_CALLBACK, notify_test_callback, (void*)&status);

// 提交任务

tsk_rte_job_id_t
 id;
tsk_rte_status_t
 job_add_status = tsk_rte_queue_add_job(queue_id, job_id, &id);

// 等待回调触发

while
 (async_trigger == false)
{
        __nop(); __nop(); __nop(); __nop(); __nop(); __nop(); __nop();
}

详细解释

  1. 1. 共享内存机制
  2. 2. 缓冲区管理
  3. 3. 任务队列
  4. 4. 通知机制

4.6 PPU加速原理(结合代码和技术细节)

代码示例:PPU主循环

int main(void) 
{
        tsk_rte_ppu_init();
        while
 (true)
        {
                tsk_rte_ppu_core_process();
        }
}

详细解释

  1. 1. 并行处理架构
  2. 2. 专用硬件
  3. 3. 内存访问优化
  4. 4. 算法优化
  5. 5. 计算卸载

性能对比示例

具体加速效果

6. 使用方法

6.1 构建项目

  1. 1. 使用TASKING SmartCode Eclipse打开项目
  2. 2. 先构建PPU应用程序 ppu_math_ifft_cf32_app
  3. 3. 再构建TriCore主程序 ppu_math_ifft_cf32_tcmain

6.2 运行项目

6.3 测试数据

7. 技术要点

7.1 PPU与TriCore通信

7.2 IFFT实现

7.3 性能考虑

8. 代码优化建议

  1. 1. 错误处理改进:当前代码中的错误处理较为简单,可增加更详细的错误诊断和处理机制
  2. 2. 内存管理优化
  3. 3. 参数可配置化
  4. 4. 结果验证增强
  5. 5. 代码注释完善

9. 从编译角度分析调用机制

9.1 编译工具链

项目使用TASKING SmartCode v10.4r1编译器,包含两个独立的工具链:

9.1.1 PPU工具链 (ARC)

9.1.2 TriCore工具链

8.2 编译过程

8.2.1 PPU应用程序编译

编译命令示例(来自ppu_math_ifft_cf32_app的subdir.mk):

# 编译ppu_math_ifft_cf32_app.c
ccarc -o ppu_math_ifft_cf32_app.o ..\ppu_math_ifft_cf32_app.c \
  -Ctc49x \
  -t \
  -Wa-H"sfr/regppu.def" \
  -Wa-gAHLs \
  --emit-locals=-equs,-symbols \
  -H"sfr/regppu.sfr" \
  -I"carc/include.ppu_rte" \
  -I"carc/include.ppu_math" \
  -I"ppu_math_ifft_cf32_tcmain" \
  --iso=99 \
  -O2 \
  --tradeoff=4 \
  -g \
  -c

关键编译选项说明

8.2.2 TriCore主程序编译

编译命令示例(来自ppu_math_ifft_cf32_tcmain的subdir.mk):

# 编译ppu_math_ifft_cf32_tcmain.c
cctc -o ppu_math_ifft_cf32_tcmain.o ..\ppu_math_ifft_cf32_tcmain.c \
  -Ctc49x \
  --lsl-core=tc0 \
  -t \
  -I"ctc/include.ppu_rte" \
  --iso=11 \
  --fp-model=clznrT \
  --fpu=dp \
  -O2 \
  --tradeoff=4 \
  --loop=Vlfist \
  -g \
  -c

关键编译选项说明

8.3 链接过程

8.3.1 PPU应用程序链接

链接命令(来自ppu_math_ifft_cf32_app的makefile):

ccarc 

# 链接选项内容:

# -o ppu_math_ifft_cf32_app.out

# g_tsk_rte_config_ppu.o

# g_tsk_rte_init_ppu.o

# ppu_main.o

# ppu_math_ifft_cf32_app.o

# -Ctc49x

# -t "lib/tc49x/libppu_math.a"

# -t "lib/tc49x/libppu_rte.a"

# -Wl-OtxycL

# -Wl--map-file=ppu_math_ifft_cf32_app.mapxml:XML

# -Wl-mcrfiklSmNoduQ

# --link-only

# -Wl-lppu_rte

# -g

链接的库文件

  1. 1. libppu_math.a:PPU数学库,包含FFT/IFFT函数
  2. 2. libppu_rte.a:PPU运行时环境库
  3. 3. libc_fpu.a:带浮点支持的C标准库
  4. 4. librt.a:运行时库

8.3.2 TriCore主程序链接(关键步骤)

链接命令(来自ppu_math_ifft_cf32_tcmain的makefile第27行):

--new-task=ppu,"D:	ianpb\ppu_demo_inf\ppu_math_ifft_cf32_app\Debug\ppu_math_ifft_cf32_app.out",-Mppu_math_ifft_cf32_app.mapxml:XML

这是最关键的一步!--new-task选项将PPU程序整合到TriCore的ELF文件中。

完整链接过程

TriCore主程序链接流程:
├─ TriCore程序 (ppu_math_ifft_cf32_tcmain.o)
├─ RTE库 (libppu_rte.a)
├─ PPU程序 (ppu_math_ifft_cf32_app.out)  ← 通过--new-task嵌入
│  └─ PPU应用代码
│  └─ PPU RTE库
│  └─ PPU Math库
└─ 生成最终ELF (ppu_math_ifft_cf32_tcmain.elf)

8.4 库函数调用关系

8.4.1 PPU侧库函数(来自map文件)

ppu_math_ifft_cf32_app.map中可以看到从库中提取的符号:

| tsk_ppu_math_fix_bin_vec.o | libppu_math.a | tsk_fft_init_cf32 |
| tsk_ppu_math_fix_bin_vec.o | libppu_math.a | tsk_ifft_cf32 |
| tsk_rte_job_handling.o     | libppu_rte.a  | tsk_rte_ppu_core_process |
| tsk_rte_ppu_buffer.o       | libppu_rte.a  | tsk_rte_ppu_buffer_get_address |
| tsk_rte_ppu_init.o         | libppu_rte.a  | tsk_rte_ppu_init |

8.4.2 函数调用链

TriCore → PPU的完整调用链

TriCore侧:
main()
  ├─ tsk_rte_init()                          [RTE库]
  ├─ synchronous_example() / asynchronous_example()
  │  ├─ tsk_rte_job_queue_claim()           [RTE库]
  │  ├─ tsk_rte_job_claim()                 [RTE库]
  │  ├─ tsk_rte_mem_claim()                 [RTE库]
  │  ├─ tsk_rte_job_init()                  [RTE库]
  │  ├─ tsk_rte_op_write_memory()           [RTE库]
  │  ├─ tsk_rte_op_custom_dpf()             [RTE库]
  │  ├─ tsk_rte_op_read_memory()            [RTE库]
  │  ├─ tsk_rte_job_set_notify()            [RTE库]
  │  └─ tsk_rte_queue_add_job()             [RTE库]

→ 任务通过RTE队列发送到PPU

PPU侧:
main()
  ├─ tsk_rte_ppu_init()                      [PPU RTE库]
  └─ tsk_rte_ppu_core_process()             [PPU RTE库] (循环调用)
     └─ custom_tsk_ppu_math_ifft_cf32()    [用户代码]
        ├─ tsk_rte_ppu_buffer_get_address() [PPU RTE库]
        ├─ tsk_fft_init_cf32()             [PPU Math库]
        └─ tsk_ifft_cf32()                  [PPU Math库]

8.5 运行时加载与执行

8.5.1 系统启动流程

1. 硬件上电/复位
   ↓
2. TriCore启动 (从启动ROM或Flash执行)
   ↓
3. TriCore执行cstart.c中的启动代码
   ↓
4. TriCore初始化RTE (tsk_rte_init())
   ↓
5. TriCore加载并启动PPU程序
   (通过RTE和--new-task嵌入的PPU代码)
   ↓
6. PPU初始化RTE (tsk_rte_ppu_init())
   ↓
7. PPU进入主循环,等待任务 (tsk_rte_ppu_core_process())
   ↓
8. TriCore主函数开始执行,提交IFFT任务
   ↓
9. 任务在TriCore和PPU之间通过RTE队列传递

8.5.2 内存布局(来自map文件)

PPU使用的内存区域:

8.6 DPF(数据处理函数)注册机制

g_tsk_rte_config_ppu.cuser_dpf_functions.h可以看到:

  1. 1. DPF函数声明:在user_dpf_functions.h中声明自定义函数
  2. 2. DPF函数实现:在ppu_math_ifft_cf32_app.c中实现函数
  3. 3. DPF注册:在g_tsk_rte_config_ppu.c中注册到DPF表
  4. 4. 函数ID分配:在g_tsk_rte_function_id.h中分配唯一函数ID

调用流程

TriCore:
  tsk_rte_op_custom_dpf(job_id, payload, size)
    └─ payload包含function_id = CUSTOM_TSK_PPU_MATH_IFFT_CF32_FUNCTION_ID

→ RTE传递到PPU

PPU:
  tsk_rte_ppu_core_process()
    └─ 从DPF表中查找function_id
       └─ 调用对应的函数custom_tsk_ppu_math_ifft_cf32()

9. 我的角度:深入技术分析

基于对代码、编译产物和配置文件的全面分析,让我从技术底层的角度进行更深入的解析。

9.1 DPF(数据处理函数)完整注册与调用机制

让我详细拆解DPF机制的每个环节:

9.1.1 四步注册流程

第一步:Payload结构体定义(user_dpf_config.h)

typedef struct
{
    tsk_rte_function_id_t
   function_id;    // 函数ID
    tsk_rte_mem_id_t
        buffer_id_in0;   // 输入缓冲区ID
    tsk_rte_mem_id_t
        buffer_id_twiddles; // 旋转因子缓冲区
    tsk_rte_mem_id_t
        buffer_id_rev;    // 位反转表缓冲区
    tsk_rte_mem_id_t
        buffer_id_out0;   // 输出缓冲区ID
    unsigned
 int             length;           // 数据长度
} custom_tsk_ppu_math_ifft_cf32_payload_t;

这个结构体是TriCore和PPU之间数据传递的契约。

第二步:联合体重载(g_tsk_rte_dpf.h)

typedef union
{
    tsk_rte_function_payload_t
 function_payload;
    // ... 其他预定义的payload ...

    custom_tsk_ppu_math_ifft_cf32_payload_t
 custom_tsk_ppu_math_ifft_cf32_payload;
} tsk_rte_payload_t;

使用联合体的好处是:可以通过同一个内存空间传递不同类型的payload,节省内存。

第三步:函数声明(user_dpf_functions.h)

extern tsk_rte_status_t custom_tsk_ppu_math_ifft_cf32(
    __uncached tsk_rte_function_payload_t* payload
);

这里使用的是通用的tsk_rte_function_payload_t*指针,而不是具体的payload类型,这是为了统一函数签名。

第四步:DPF表注册(g_tsk_rte_config_ppu.c)

tsk_rte_dpf_t tsk_rte_dpf_table[] = { 
    tsk_rte_ppu_write,              // 索引0
    tsk_rte_ppu_write_2d,            // 索引1
    tsk_rte_ppu_read,               // 索引2
    tsk_rte_ppu_read_2d,            // 索引3
    tsk_rte_ppu_add,                // 索引4
    tsk_rte_ppu_sub,                // 索引5
    tsk_rte_ppu_mul,                // 索引6
    tsk_rte_ppu_scalar_mul,         // 索引7
    custom_tsk_ppu_math_ifft_cf32   // 索引8 ← 我们的函数
};

关键! 函数ID与数组索引的对应关系(g_tsk_rte_function_id.h):

typedef enum
{
    TSK_RTE_PPU_WRITE_FUNCTION_ID = 0,         // 对应索引0
    TSK_RTE_PPU_WRITE_2D_FUNCTION_ID = 1,     // 对应索引1
    TSK_RTE_PPU_READ_FUNCTION_ID = 2,          // 对应索引2
    TSK_RTE_PPU_READ_2D_FUNCTION_ID = 3,       // 对应索引3
    TSK_RTE_PPU_ADD_FUNCTION_ID = 4,           // 对应索引4
    TSK_RTE_PPU_SUB_FUNCTION_ID = 5,           // 对应索引5
    TSK_RTE_PPU_MUL_FUNCTION_ID = 6,           // 对应索引6
    TSK_RTE_PPU_SCALAR_MUL_FUNCTION_ID = 7,    // 对应索引7
    CUSTOM_TSK_PPU_MATH_IFFT_CF32_FUNCTION_ID = 8, // 对应索引8
    TSK_RTE_FUNCTION_ID_INVALID = 0x7fffffff
} tsk_rte_function_id_t;

这是一个优雅的设计:枚举值直接作为数组索引,无需任何映射!

9.1.2 实际调用流程(结合源码)

让我们看TriCore侧如何构建和提交任务(ppu_math_ifft_cf32_tcmain.c):

// 第1步:创建具体的payload
custom_tsk_ppu_math_ifft_cf32_payload_t
 custom_dpf_ifft_pay_load = {
    .function_id = CUSTOM_TSK_PPU_MATH_IFFT_CF32_FUNCTION_ID, // 设为8
    .buffer_id_in0 = buf_a,         
    .buffer_id_out0 = buf_out,
    .buffer_id_twiddles = buf_twiddles,
    .buffer_id_rev = buf_rev,               
    .length = ARRAY_SIZE,
};

// 第2步:赋值给联合体

tsk_rte_payload_t
 payload;
memset
(&payload, 0, sizeof(payload));
payload.custom_tsk_ppu_math_ifft_cf32_payload = custom_dpf_ifft_pay_load;

// 第3步:添加操作到任务

tsk_rte_op_custom_dpf(
    job_id, 
    (tsk_rte_function_payload_t*)&payload,  // 再次转换为通用指针
    sizeof
(custom_tsk_ppu_math_ifft_cf32_payload_t)
);

然后看PPU侧如何解析和调用(ppu_math_ifft_cf32_app.c):

tsk_rte_status_t custom_tsk_ppu_math_ifft_cf32(
    __uncached tsk_rte_function_payload_t* payload  // 接收通用指针
)
{
    // 第1步:转换回父级指针

    __uncached tsk_rte_payload_t* parent_payload = 
        (__uncached tsk_rte_payload_t*)payload;
    
    // 第2步:提取具体的payload

    custom_tsk_ppu_math_ifft_cf32_payload_t
 unpacked_payload = 
        parent_payload->custom_tsk_ppu_math_ifft_cf32_payload;
    
    // 第3步:使用payload中的数据

    __vccm _Complex float *a_vccm = (_Complex float __vccm*)(int)
        tsk_rte_ppu_buffer_get_address(unpacked_payload.buffer_id_in0);
    
    // ... 执行计算 ...

}

RTE内部的调用过程(从map文件推断)

  1. 1. TriCore调用tsk_rte_queue_add_job() → 任务写入队列
  2. 2. PPU的tsk_rte_ppu_core_process()循环检查队列
  3. 3. 发现新任务 → 提取function_id
  4. 4. 使用function_id作为索引:tsk_rte_dpf_table[function_id]()
  5. 5. 调用对应的DPF函数

9.2 内存池机制深度解析

让我们看内存是如何管理的(user_pools_config.h和g_tsk_rte_config_ppu.c):

9.2.1 内存缓冲区定义

// user_pools_config.h
__aligned__(TSK_RTE_PPU_BUFFER_ALIGNMENT) static __vccm _Complex float fc_a[512];
__aligned__(TSK_RTE_PPU_BUFFER_ALIGNMENT) static __vccm _Complex float fc_c[512];
__aligned__(TSK_RTE_PPU_BUFFER_ALIGNMENT) static __vccm float twiddles[32];
__aligned__(TSK_RTE_PPU_BUFFER_ALIGNMENT) static __vccm unsigned short rev[32];

关键点

9.2.2 内存池注册

// g_tsk_rte_config_ppu.c
__no_sda __uncached tsk_rte_memory_pool_entry_t tsk_rte_memory_partition_pool[] = { 
    { .in_use = 0, .mem_id = {.internal = {5, TSK_RTE_MEM_VCCM_1}}, {(void*)a, sizeof(a)} },
    { .in_use = 0, .mem_id = {.internal = {6, TSK_RTE_MEM_VCCM_2}}, {(void*)b, sizeof(b)} },
    { .in_use = 0, .mem_id = {.internal = {7, TSK_RTE_MEM_VCCM_3}}, {(void*)c, sizeof(c)} },
    { .in_use = 0, .mem_id = {.internal = {8, TSK_RTE_MEM_VCCM_4}}, {(void*)fc_a, sizeof(fc_a)} },
    { .in_use = 0, .mem_id = {.internal = {9, TSK_RTE_MEM_VCCM_5}}, {(void*)fc_c, sizeof(fc_c)} },
    { .in_use = 0, .mem_id = {.internal = {10, TSK_RTE_MEM_VCCM_6}}, {(void*)twiddles, sizeof(twiddles)} },
    { .in_use = 0, .mem_id = {.internal = {11, TSK_RTE_MEM_VCCM_7}}, {(void*)rev, sizeof(rev)} }
};

内存池工作原理

  1. 1. 每个条目有一个in_use标志
  2. 2. tsk_rte_mem_claim()查找in_use == 0的条目,设为1并返回ID
  3. 3. tsk_rte_mem_release()in_use设回0
  4. 4. mem_id包含内部索引和类型信息

9.2.3 缓冲区地址转换

// 在PPU侧
__vccm _Complex float *a_vccm = (_Complex float __vccm*)(int)
    tsk_rte_ppu_buffer_get_address(unpacked_payload.buffer_id_in0);

这里发生了什么?

  1. 1. tsk_rte_ppu_buffer_get_address():根据buffer_id在内存池中查找,返回物理地址
  2. 2. (int):可能是为了处理指针类型转换
  3. 3. (_Complex float __vccm*):转换为具体类型的指针,并标记为VCCM内存

9.3 任务和队列机制

让我们看任务是如何管理的:

9.3.1 任务池

__no_sda __uncached tsk_rte_job_pool_entry_t tsk_rte_job_pool[] = { 
    { .in_use = 0, .job_id = {.internal = {1, TSK_RTE_JOB_0}}, .job = { .operations = { .ops_size = 5 }}},
    { .in_use = 0, .job_id = {.internal = {2, TSK_RTE_JOB_1}}, .job = { .operations = { .ops_size = 5 }}}
};

这里只有2个任务槽位,但每个任务可以包含最多5个操作!

9.3.2 队列池

__no_sda __uncached tsk_rte_queue_pool_entry_t tsk_rte_queue_pool[] = { 
    { .in_use = 0, .queue_id = {.internal = {3, TSK_RTE_QUEUE_0}}, .queue = { .job_table = { .jobs_size = 4, }}},
    { .in_use = 0, .queue_id = {.internal = {4, TSK_RTE_QUEUE_1}}, .queue = { .job_table = { .jobs_size = 4, }}}
};

每个队列最多容纳4个任务!

9.4 编译和链接的技术细节

9.4.1 为什么需要两个工具链?

PPU使用ARC架构

TriCore使用TriCore架构

两者指令集完全不同,必须分别编译!

9.4.2 –new-task是如何工作的?

从TriCore的makefile第27行:

--new-task=ppu,"D:	ianpb\ppu_demo_inf\ppu_math_ifft_cf32_app\Debug\ppu_math_ifft_cf32_app.out",...

这个链接器选项的作用

  1. 1. 读取PPU的.out文件
  2. 2. 将其代码段、数据段提取出来
  3. 3. 在TriCore的ELF文件中创建一个新的”任务”段
  4. 4. 记录PPU程序的入口点和内存布局
  5. 5. 运行时,TriCore的RTE会将这个任务加载到PPU并启动

为什么要嵌入而不是单独烧录?

9.5 从运行时角度看完整流程

让我总结一下上电后的完整执行流程:

上电/复位
   ↓
TriCore从启动ROM执行
   ↓
TriCore执行cstart.c(设置堆栈、初始化硬件)
   ↓
TriCore调用main()
   ↓
TriCore调用tsk_rte_init()
   ├─ 初始化内存池
   ├─ 初始化任务池
   ├─ 初始化队列池
   ├─ 查找嵌入的PPU程序(通过--new-task)
   ├─ 将PPU代码复制到PPU的代码内存
   ├─ 配置PPU的堆栈和寄存器
   └─ 启动PPU核
         ↓
      PPU开始执行
         ↓
      PPU调用tsk_rte_ppu_init()
         ├─ 初始化PPU侧的RTE
         └─ 准备接收任务
         ↓
      PPU进入主循环:tsk_rte_ppu_core_process()
         ↓
TriCore继续执行main()
   ↓
TriCore进入循环:
   ├─ synchronous_example()
   │  ├─ 申请资源
   │  ├─ 准备payload
   │  ├─ 配置任务操作
   │  ├─ 提交任务到队列
   │  ├─ 阻塞等待
   │  └─ 验证结果
   └─ asynchronous_example()
      ├─ 类似,但使用回调
      └─ 轮询等待标志
         ↓
      任务在队列中
         ↓
      PPU的tsk_rte_ppu_core_process()发现任务
         ↓
      提取function_id
         ↓
      查找DPF表:tsk_rte_dpf_table[function_id]
         ↓
      调用custom_tsk_ppu_math_ifft_cf32()
         ├─ 获取缓冲区地址
         ├─ 初始化FFT
         ├─ 执行IFFT
         └─ 返回
         ↓
      PPU通知TriCore(通过中断或标志)
         ↓
      TriCore唤醒(同步模式)或回调触发(异步模式)
         ↓
      TriCore读取结果
         ↓
      验证并统计
         ↓
      循环继续...

9.6 关键设计模式

这个系统体现了几个优秀的设计模式:

  1. 1. Command模式:任务封装了要执行的操作
  2. 2. Factory模式:通过function_id动态创建/调用函数
  3. 3. Object Pool模式:任务池、内存池、队列池
  4. 4. Strategy模式:同步和异步两种执行策略
  5. 5. Bridge模式:RTE作为TriCore和PPU之间的桥梁

10. 我的视角:整体架构深度分析

在深入分析了整个PPU demo项目后,让我从系统架构的角度来总结这个项目的设计思想和技术亮点。

10.1 整体架构设计理念

这个demo项目体现了异构多核计算的设计理念,具有以下特点:

10.1.1 任务分工明确

10.1.2 分层抽象设计

系统采用了清晰的分层设计:

应用层(用户代码)
    ↓
RTE库层(任务管理、内存管理、通信)
    ↓
硬件抽象层(寄存器访问、中断处理)
    ↓
硬件层(TriCore、PPU、共享内存)

这种分层设计带来的好处:

  1. 1. 降低开发复杂度:开发者不需要直接操作底层硬件
  2. 2. 提高代码可移植性:RTE库抽象了硬件差异
  3. 3. 便于维护和升级:各层可以独立演进

10.1.3 资源预分配策略

所有关键资源(任务、队列、内存)都采用预分配策略:

10.2 数据流向分析

让我用一个更清晰的方式来描述数据在整个系统中的流动:

TriCore侧:
1. 输入数据 a_c[] (在TriCore的本地内存)
       ↓
2. tsk_rte_op_write_memory()  → 复制到VCCM共享内存
       ↓
3. 任务提交到队列

PPU侧:
4. 从队列获取任务
       ↓
5. 从VCCM读取输入数据
       ↓
6. 执行IFFT计算
       ↓
7. 结果写回VCCM
       ↓
8. 通知TriCore

TriCore侧:
9. tsk_rte_op_read_memory()  → 从VCCM复制到c_c[]
       ↓
10. 验证结果

关键观察点

10.3 性能瓶颈分析

基于对代码的分析,我识别出以下可能的性能瓶颈:

10.3.1 数据传输开销

10.3.2 任务调度开销

10.3.3 优化建议

  1. 1. 批量处理:一次提交多个任务,减少调度开销
  2. 2. 数据驻留:让数据长期驻留在VCCM中,避免重复复制
  3. 3. 流水线处理:TriCore准备下一批数据时,PPU处理上一批数据
  4. 4. 选择合适的调用模式

10.4 设计模式的巧妙运用

正如前面提到的,这个项目运用了多个优秀的设计模式,让我深入分析一下:

10.4.1 Command模式的完美应用

任务对象封装了:

这种封装使得:

10.4.2 Object Pool模式的实时性考虑

预分配的资源池(任务池、内存池、队列池):

10.4.3 Strategy模式的灵活性

同步和异步两种通知策略:

11. 实际应用扩展指南

基于这个demo项目,让我提供一些实际应用的扩展指导,帮助您将这个示例应用到真实的项目中。

11.1 如何添加自定义DPF函数

如果您想添加自己的PPU计算函数,只需按照以下步骤:

步骤1:定义Payload结构体

user_dpf_config.h中添加:

typedef struct
{
    tsk_rte_function_id_t
   function_id;
    tsk_rte_mem_id_t
        buffer_id_input;
    tsk_rte_mem_id_t
        buffer_id_output;
    unsigned
 int             param1;
    float
                    param2;
} custom_my_function_payload_t;

步骤2:添加到联合体

g_tsk_rte_dpf.h中添加:

typedef union
{
    tsk_rte_function_payload_t
 function_payload;
    // ... 现有的payload ...

    custom_my_function_payload_t
 custom_my_function_payload;
} tsk_rte_payload_t;

步骤3:声明函数

user_dpf_functions.h中添加:

extern tsk_rte_status_t custom_my_function(
    __uncached tsk_rte_function_payload_t* payload
);

步骤4:分配函数ID

g_tsk_rte_function_id.h中添加:

typedef enum
{
    // ... 现有的ID ...

    CUSTOM_MY_FUNCTION_FUNCTION_ID = 9,
    TSK_RTE_FUNCTION_ID_INVALID = 0x7fffffff
} tsk_rte_function_id_t;

步骤5:注册到DPF表

g_tsk_rte_config_ppu.c中添加:

tsk_rte_dpf_t tsk_rte_dpf_table[] = { 
    // ... 现有的函数 ...

    custom_my_function
};

步骤6:实现函数

在PPU应用程序中添加:

tsk_rte_status_t custom_my_function(
    __uncached tsk_rte_function_payload_t* payload
)
{
    // 1. 转换payload

    __uncached tsk_rte_payload_t* parent_payload = 
        (__uncached tsk_rte_payload_t*)payload;
    custom_my_function_payload_t
 unpacked_payload = 
        parent_payload->custom_my_function_payload;
    
    // 2. 获取缓冲区地址

    __vccm float* input = (__vccm float*)(int)
        tsk_rte_ppu_buffer_get_address(unpacked_payload.buffer_id_input);
    __vccm float* output = (__vccm float*)(int)
        tsk_rte_ppu_buffer_get_address(unpacked_payload.buffer_id_output);
    
    // 3. 执行您的计算

    // ... 您的代码 ...

    
    return
 TSK_RTE_STATUS_OK;
}

步骤7:在TriCore中调用

// 准备payload
custom_my_function_payload_t
 my_payload = {
    .function_id = CUSTOM_MY_FUNCTION_FUNCTION_ID,
    .buffer_id_input = buf_in,
    .buffer_id_output = buf_out,
    .param1 = 100,
    .param2 = 3.14f
};

// 赋值给联合体

tsk_rte_payload_t
 payload;
payload.custom_my_function_payload = my_payload;

// 添加操作

tsk_rte_op_custom_dpf(job_id, (tsk_rte_function_payload_t*)&payload, 
                      sizeof
(custom_my_function_payload_t));

11.2 如何处理更大的数据量

当前demo只处理32个复数,如果需要处理更大的数据量,可以考虑:

11.2.1 增加缓冲区大小

修改user_pools_config.h中的数组大小:

__aligned__(TSK_RTE_PPU_BUFFER_ALIGNMENT) 
static
 __vccm _Complex float fc_a[1024];  // 从512增加到1024
__aligned__(TSK_RTE_PPU_BUFFER_ALIGNMENT) 
static
 __vccm _Complex float fc_c[1024];

11.2.2 分块处理

对于超大数据量,可以考虑分块处理:

  1. 1. 将大数据分成多个小块
  2. 2. 逐个提交到PPU处理
  3. 3. 在TriCore中合并结果

11.2.3 使用异步模式+流水线

时间线:
TriCore: 准备块1 → 准备块2 → 准备块3 → ...
PPU:              处理块1 → 处理块2 → 处理块3 → ...

这样可以最大化利用两个核的并行性。

11.3 如何集成到真实项目

11.3.1 确定任务划分策略

首先需要确定:

11.3.2 设计通信接口

定义清晰的接口:

11.3.3 考虑错误处理

在真实项目中,需要考虑:

12. 最佳实践总结

基于对这个demo项目的分析,让我总结一些多核开发的最佳实践:

12.1 开发流程最佳实践

  1. 1. 先在TriCore上验证算法
  2. 2. 从小数据量开始
  3. 3. 充分利用demo代码

12.2 性能优化最佳实践

  1. 1. 合理选择调用模式
  2. 2. 优化数据传输
  3. 3. 充分利用PPU的并行性

12.3 调试最佳实践

  1. 1. 分阶段调试
  2. 2. 充分利用调试工具
  3. 3. 添加日志和统计

12.4 代码组织最佳实践

  1. 1. 清晰的模块划分
  2. 2. 充分的注释
  3. 3. 错误处理

13. 常见问题FAQ

在学习和使用PPU的过程中,您可能会遇到以下问题:

13.1 基础问题

Q: PPU和TriCore是什么关系?
A: PPU是TriCore的协处理器,两者通过共享内存和RTE库进行通信。TriCore负责控制,PPU负责计算。

Q: 必须使用RTE库吗?
A: 理论上可以直接操作硬件,但RTE库大大简化了开发,建议使用。

Q: 这个demo可以在没有硬件的情况下运行吗?
A: 不行,这个demo需要真实的TC49x硬件。不过可以在仿真器中运行。

13.2 开发问题

Q: 如何确定我的函数是否适合放到PPU?
A: 考虑以下因素:

Q: 同步模式和异步模式如何选择?
A:

Q: 如何增加内存池的大小?
A: 修改user_pools_config.h中的数组大小,以及相关的配置文件。

13.3 性能问题

Q: 为什么我的PPU程序没有预期的快?
A: 可能的原因:

Q: 如何测量PPU的执行时间?
A: 可以使用硬件定时器,在TriCore侧测量从提交任务到获取结果的时间。

Q: VCCM大小有限怎么办?
A:

13.4 调试问题

Q: 为什么我的PPU断点不触发?
A: 检查:

Q: 如何查看共享内存的数据?
A: 使用调试器的内存查看功能,输入VCCM区域的地址。

Q: 双核调试时如何协调两个核?
A:

14. 总结

本PPU demo项目展示了如何使用TASKING PPU Math库和RTE库在TriCore处理器上执行复数向量的IFFT操作。通过将计算密集型任务卸载到PPU,可显著提高系统性能,同时释放TriCore资源用于其他任务。

10.1 从编译角度的核心要点

  1. 1. 双工具链编译
  2. 2. 关键链接步骤
  3. 3. 库依赖关系
  4. 4. DPF函数注册机制

10.2 从我的角度的核心发现

  1. 1. 优雅的DPF机制
  2. 2. 完善的资源池管理
  3. 3. 精巧的链接技术

该示例提供了同步和异步两种调用模式,适合不同的应用场景:

项目结构清晰,代码实现规范,是学习PPU编程和信号处理的良好示例。

11. 使用iSystem winIDEA进行调试教程

本教程将指导您如何使用iSystem winIDEA调试器来调试这个PPU demo项目。


11.1 硬件准备

  1. 1. 调试器:iSystem调试器(如iC5700/ic7pro)
  2. 2. 目标板:搭载TC49x芯片的开发板
  3. 3. 连接线:调试器与目标板之间的连接线缆(通常是JTAG或DAP)
  4. 4. 电源:为目标板供电

11.2 软件准备

  1. 1. 安装winIDEA:从iSystem官网下载并安装最新版winIDEA
  2. 2. 安装TASKING SmartCode:已在前面的编译步骤中完成
  3. 3. 确认编译产物:确保已生成以下文件:

11.3 创建winIDEA工作区

11.3.1 新建工作区

  1. 1. 启动winIDEA
  2. 2. 选择 File → New Workspace
  3. 3. 选择工作区保存位置,建议保存在项目根目录:d: ianpb\ppu_demo_inf\
  4. 4. 工作区名称:ppu_demo_inf.xjrf

11.3.2 配置硬件

  1. 1. 在winIDEA中,选择 Hardware → Select Hardware
  2. 2. 选择您的iSystem调试器型号(如iC5700/ic7pro)
  3. 3. 点击 OK
  4. 4. 如果调试器已连接,winIDEA会自动检测

11.3.3 选择CPU

  1. 1. 选择 CPU → Select
  2. 2. 在CPU选择对话框中:
  3. 3. 点击 OK

11.4 配置调试文件

11.4.1 加载ELF文件

  1. 1. 选择 File → Download File
  2. 2. 浏览到:d: ianpb\ppu_demo_inf\ppu_math_ifft_cf32_tcmain\Debug\
  3. 3. 选择:ppu_math_ifft_cf32_tcmain.elf
  4. 4. 点击 Open

11.4.2 配置符号文件

winIDEA会自动从ELF文件中加载调试符号,包括:

11.5 基本调试操作

11.5.1 下载程序

  1. 1. 点击工具栏上的 Download 按钮(或按 F8
  2. 2. 程序将被下载到目标板的Flash或RAM中
  3. 3. 下载完成后,程序会停在main()函数入口处

11.5.2 设置断点

在TriCore代码中设置断点

  1. 1. 在winIDEA中打开文件:ppu_math_ifft_cf32_tcmain.c
  2. 2. 找到synchronous_example()函数
  3. 3. 在要调试的行号左侧点击,设置断点

在PPU代码中设置断点

  1. 1. 打开文件:ppu_math_ifft_cf32_app.c
  2. 2. 找到custom_tsk_ppu_math_ifft_cf32()函数
  3. 3. 在以下位置设置断点:

11.5.3 运行控制

常用调试命令:

11.6 双核调试技巧

11.6.1 查看TriCore和PPU的状态

winIDEA支持多核调试,您可以:

  1. 1. 查看两个核的寄存器
  2. 2. 在两个核之间切换调试上下文
  3. 3. 同时监控两个核的执行

11.6.2 内存查看

查看输入数据(TriCore侧)

  1. 1. 选择 View → Memory
  2. 2. 在地址栏输入:&a_c(输入数组地址)
  3. 3. 选择显示格式为 Float Complex 或 Hex

查看输出结果

  1. 1. 在地址栏输入:&c_c(输出数组地址)
  2. 2. 与预期结果&expected_result进行对比

查看VCCM共享内存(PPU侧)

  1. 1. 在地址栏输入VCCM内存区域的地址
  2. 2. 查看PPU实际访问的数据

11.6.3 变量监视

  1. 1. 选择 View → Watch
  2. 2. 添加以下变量到监视窗口:

11.7 调试完整流程示例

让我们调试一个完整的同步模式IFFT操作:

步骤1:准备调试

  1. 1. 在TriCore的synchronous_example()函数开始处设置断点
  2. 2. 在PPU的custom_tsk_ppu_math_ifft_cf32()函数开始处设置断点
  3. 3. 下载程序并运行

步骤2:TriCore准备数据

  1. 1. 程序在synchronous_example()断点处停止
  2. 2. 单步执行,观察资源申请过程
  3. 3. 查看a_c数组的值,确认输入数据正确

步骤3:提交任务

  1. 1. 继续执行到tsk_rte_queue_add_job()调用后
  2. 2. 此时任务已提交到队列

步骤4:PPU接收任务

  1. 1. 点击运行,程序会在PPU的断点处停止
  2. 2. 查看PPU的调用栈,确认是从tsk_rte_ppu_core_process()调用的
  3. 3. 检查unpacked_payload参数是否正确

步骤5:执行IFFT计算

  1. 1. 单步执行到tsk_ifft_cf32()调用
  2. 2. 可以选择进入该函数(如果有库源码)或跳过
  3. 3. 执行完成后,查看c_vccm指向的内存

步骤6:验证结果

  1. 1. 继续运行,让TriCore读取结果
  2. 2. 程序回到TriCore,在结果验证处停止
  3. 3. 比较c_cexpected_result的值
  4. 4. 确认failed_jobs_sync是否为0

11.8 常见问题解决

问题1:无法连接到调试器

问题2:下载程序失败

问题3:PPU断点不触发

问题4:无法查看变量

问题5:双核同步问题

11.9 高级调试功能

11.9.1 性能分析

winIDEA提供性能分析工具:

  1. 1. 选择 Profiler → Start Profiling
  2. 2. 运行程序一段时间
  3. 3. 停止分析,查看:

11.9.2 跟踪功能

如果您的调试器支持跟踪:

  1. 1. 配置跟踪选项
  2. 2. 开始跟踪
  3. 3. 运行程序
  4. 4. 查看跟踪记录,分析:

11.9.3 脚本自动化

您可以使用winIDEA的脚本功能自动化调试:

  1. 1. 编写Python或IsystemScript脚本
  2. 2. 自动设置断点
  3. 3. 自动运行测试
  4. 4. 自动收集结果

11.10 保存和分享工作区

  1. 1. 选择 File → Save Workspace
  2. 2. 工作区文件(.xjrf)包含了所有配置
  3. 3. 可以分享给团队成员,确保一致的调试环境

通过以上步骤,您应该能够使用winIDEA成功调试这个PPU demo项目,深入理解TriCore和PPU之间的交互过程!

12. TASKING SmartCode 编译器介绍

12.1 什么是TASKING SmartCode?

TASKING SmartCode 是英飞凌(Infineon)官方推荐的AURIX系列芯片开发工具链,专为多核实时控制系统设计。它提供了完整的开发环境,包括编译器、调试器、分析工具和丰富的标准库。

12.2 SmartCode的核心特性

12.2.1 多核心支持

12.2.2 优化的编译器

12.2.3 完整的标准库

12.2.4 集成开发环境

12.2.5 调试和分析工具

12.3 SmartCode在本项目中的应用

在本PPU demo项目中,SmartCode展现了以下优势:

  1. 1. 双工具链支持
  2. 2. 特色链接技术
  3. 3. 标准库集成
  4. 4. 配置工具

12.4 SmartCode的优势

相比其他开发工具,TASKING SmartCode具有以下优势:

  1. 1. 深度优化:专为英飞凌AURIX芯片优化,性能优异
  2. 2. 多核支持:原生支持TriCore+PPU架构
  3. 3. 开发效率:RTE库等特色功能大幅简化开发
  4. 4. 工具集成:编译器、调试器、分析工具无缝集成
  5. 5. 官方支持:英飞凌官方推荐,技术支持及时
  6. 6. 可靠性:经过大量工业项目验证,稳定可靠

12.5 适用场景

TASKING SmartCode特别适合以下场景:

12.6 总结

TASKING SmartCode是一款专业、强大的嵌入式开发工具链,特别适合英飞凌AURIX系列多核芯片的开发。通过本PPU demo项目,我们可以看到它如何通过RTE库、多工具链支持和特色链接技术,大幅简化了多核开发的复杂性,提高了开发效率和系统性能。

对于需要开发高性能实时控制系统的工程师来说,TASKING SmartCode是一个值得投资的工具,可以显著缩短开发周期,提高代码质量和系统可靠性。


欢迎大家一起交流学习:

support@softor.com.cn

tianpengbo@softor.com.cn


作者与交流

作者:tianpengbo / 田朋博。大家如果在项目中遇到相关技术问题,欢迎联系我交流。
support@softor.com.cn
tianpengbo@softor.com.cn

作者与交流

作者:tianpengbo / 田朋博。大家如果在项目中遇到相关技术问题,欢迎联系我交流。
support@softor.com.cn
tianpengbo@softor.com.cn

在线留言