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.h、user_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.c和user_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_c和expected_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

在线留言