VDK中接收memcpy传递结构体时,interface被访问多次问题
文章目录
- memcpy
- RISC-V 汇编代码分析:内存复制函数
- 1. 初始检查和参数设置
- 2. 64字节块复制(优化路径)
- 64字节复制循环体
- 3. 8字节块复制
- 8字节复制循环体
- 4. 单字节复制(剩余部分)
- 5. 函数返回
- 关键优化点
- 结论
问题现象:在VDK软件中,SDMA接受从SCP/AP侧发来的命令帧后自动解析命令帧,然后执行相应操作,但是在使用SCP 给 SDMA发送命令帧时,发现SDMA的回调调用了多次,因此查看反汇编出来的代码,是否给SDMA的interface发送了多次的transaction,造成了回调调用多次。
猜测:memcpy时,是多次transaction
SCP测试代码如下:
SDMA_BRUST_HEAD sdma_frame_;
uintptr_t addr_sdma_ = (0x11002000);
memcpy(addr_sdma_, &sdma_frame_, sizeof(SDMA_BRUST_HEAD));
需要调试的sdma代码如下
/* 1 */
lui a5, 0xe0 # 将 0xe0 << 12 = 0xe0000 加载到 a5 高20位
c.addi a5, 20 # a5 = 0xe0000 + 20 = 0xe0014
c.sd a5, 32(sp) # 将 a5 的值存储到 [sp+32] 的内存位置
c.ld a5, 32(sp) # 从 [sp+32] 重新加载到 a5 (看起来冗余)
c.ld a5, 32(sp) # 再次加载 (可能是编译器生成的冗余指令)
sw zero, 0(a5) # 将 0 写入 [a5+0] 的内存位置
/* 2 */
lui a5, 0x11002 # 加载 0x11002000 到 a5
c.sd a5, 24(sp) # 存储目标地址到 [sp+24]
c.mv a5, sp # a5 = sp (栈指针)
c.li a2, 1 # a2 = 1 (复制长度=1字节)
c.mv a1, a5 # a1 = sp (源地址)
c.ldsp a0, 24(sp) # a0 = [sp+24] = 0x11002000 (目标地址)
jal ra, 0xe000000000fd <memcpy> # 调用 memcpy
- 地址加载与存储(这段代码将0xe0014地址处的内存清零)
- 准备memcpy参数(准备参数并调用memcpy,将栈上1字节数据复制到0x11002000地址)
最关键的部分来了
memcpy
c63d c.beqz a2,0xe00001046
82aa c.mv t0, a0
83b2 c.mv t2, a2
fc03f31 andi t1, t2, -64
9316 c.add t1, t0
0255863 bge a0, t1, 0xe000f4c
6190 c.ld a2, 0(a1)
6594 c.ld a3, 8(a1)
6998 c.ld a4, 16(a1)
6d9c c.ld a5, 24(a1)
e110 c.sd a2, 0(a0)
e514 c.sd a3, 8(a0)
e918 c.sd a4, 16(a0)
ed1c c.sd a5, 24(a0)
7190 c.ld a2, 32(a1)
7594 c.ld a3,40(a1)
7998 c.ld a4,48(a1)
7d9c c.ld a5,56(a1)
f110 c.sd a2, 32(a0)
f514 c.sd a3, 40(a0)
f918 c.sd a4, 48(a0)
fd1c c.sd a5, 56(a0)
04058593 addi a1, a1, 64
04050513 andi a0, a0, 64fc654ce3 blt a0, t1,0xe000000fe8 <memcpy+0x10>
ff83f313 andi t1, t2, -8
0053033 addi a0, t1, t0
0655863 bge a0, t1,0xe00000102c <_loader_start+0x2c>
6190 c.ld a2, 0(a1)
0521 c.addi a1, 8
e110 c.sd a2, 0(a0)0521 c.addi a0, 8
fe654ce3 blt a0, t1,0xe000001020 <_loader_start+0x20>
00728333 add t1, t0, t2
00655a63 bge a0, t1,0xe000001044 <_loader_start+0x44>
005c603 lbu a2, 0(a1)
0585 c.addi a1, 1
00c50023 sb a2, 0(a0)
0505 c.addi a0, 1
fe654ae3 blt a0, t1,0xe000001034 <__loader_start+0x34>
8516 c.mv a0, t0
8082 c.jr ra
RISC-V 汇编代码分析:内存复制函数
这段代码实现了一个优化的内存复制函数(memcpy),我将其分为几个关键部分进行分析:
1. 初始检查和参数设置
c63d c.beqz a2,0xe00001046 # 如果复制长度(a2)为0,直接跳转到结束
82aa c.mv t0, a0 # 保存原始目标地址到t0
83b2 c.mv t2, a2 # 保存原始长度到t2
2. 64字节块复制(优化路径)
fc03f31 andi t1, t2, -64 # 计算64字节对齐的长度
9316 c.add t1, t0 # t1 = 目标地址 + 对齐长度
0255863 bge a0, t1, 0xe000f4c # 如果不需要64字节复制,跳转到小数据复制
64字节复制循环体
6190 c.ld a2, 0(a1) # 从源地址加载8字节
6594 c.ld a3, 8(a1) # 加载+8字节
6998 c.ld a4, 16(a1) # 加载+16字节
6d9c c.ld a5, 24(a1) # 加载+24字节
e110 c.sd a2, 0(a0) # 存储到目标地址
e514 c.sd a3, 8(a0) # 存储+8字节
e918 c.sd a4, 16(a0) # 存储+16字节
ed1c c.sd a5, 24(a0) # 存储+24字节
7190 c.ld a2, 32(a1) # 加载+32字节
7594 c.ld a3,40(a1) # 加载+40字节
7998 c.ld a4,48(a1) # 加载+48字节
7d9c c.ld a5,56(a1) # 加载+56字节
f110 c.sd a2, 32(a0) # 存储+32字节
f514 c.sd a3, 40(a0) # 存储+40字节
f918 c.sd a4, 48(a0) # 存储+48字节
fd1c c.sd a5, 56(a0) # 存储+56字节
04058593 addi a1, a1, 64 # 源地址+64
04050513 addi a0, a0, 64 # 目标地址+64
fc654ce3 blt a0, t1,0xe000000fe8 # 循环直到完成所有64字节块
3. 8字节块复制
ff83f313 andi t1, t2, -8 # 计算8字节对齐的长度
0053033 add t1, t0, t1 # t1 = 目标地址 + 对齐长度
0655863 bge a0, t1,0xe00000102c # 如果不需要8字节复制,跳转到单字节复制
8字节复制循环体
6190 c.ld a2, 0(a1) # 加载8字节
0521 c.addi a1, 8 # 源地址+8
e110 c.sd a2, 0(a0) # 存储8字节
0521 c.addi a0, 8 # 目标地址+8
fe654ce3 blt a0, t1,0xe000001020 # 循环直到完成所有8字节块
4. 单字节复制(剩余部分)
00728333 add t1, t0, t2 # t1 = 原始目标地址 + 原始长度
00655a63 bge a0, t1,0xe000001044 # 检查是否已完成
005c603 lbu a2, 0(a1) # 加载1字节
0585 c.addi a1, 1 # 源地址+1
00c50023 sb a2, 0(a0) # 存储1字节
0505 c.addi a0, 1 # 目标地址+1
fe654ae3 blt a0, t1,0xe000001034 # 循环直到完成所有单字节
5. 函数返回
8516 c.mv a0, t0 # 返回原始目标地址
8082 c.jr ra # 返回调用者
关键优化点
- 分层复制策略:先尝试64字节块复制,然后是8字节块,最后是单字节
- 寄存器重用:使用a2-a5作为临时寄存器加载/存储数据
- 循环展开:64字节复制部分完全展开,避免循环开销
- 压缩指令:大量使用c.xxx压缩指令减少代码大小
这个实现是典型的memcpy优化,优先处理大块对齐数据以获得最佳性能,然后处理剩余的小数据。
结论
答案已经很明显了,结果就是因为memcpy会分层复制,导致了给interface 传不定次的 transaction, 解决方案也很简单,根据帧格式,去解析指令,每次来的transaction,提取其中的数据,将其push_back 到 vector中,每当收集到tail帧,就解析整个帧,执行相应逻辑后,clear整个vector,问题完美解决,最后贴一下解决的代码
void SysDMA::handle_sdma_disp_cmd_if_protocol_engine_b_transport(tlm::tlm_generic_payload& trans, sc_core::sc_time& time) {// FLYME: Default implementation. Implement this.for (int i = 0; i < trans.get_data_length(); ++i) {dispatch_frame._push_back(*(trans.get_data_ptr() + i));}for (int i = 0; i < dispatch_frame_size(); ++i) {if (i % 16 == 0 && dispatch_frame_size() % 16 == 0) {uint8_t temp = dispatch_frame[i] & 2;if (temp) {HandleTransactionData(dispatch_frame_data());dispatch_frame.clear();}}trans.set_response_status(tlm::TLM_OK_RESPONSE);}
}
代码分析:
- 这是一个SystemC TLM模型的DMA处理函数
- 首先将传输数据(trans)逐个字节推入dispatch_frame缓冲区
- 然后检查dispatch_frame中的数据,每16字节检查一次特定标志位(第2位)
- 如果标志位被设置,则调用HandleTransactionData处理数据并清空缓冲区
- 最后设置传输响应状态为OK