原文链接:
- part1: https://discuss.pynq.io/t/tutorial-pynq-dma-part-1-hardware-design/3133
- part2: https://discuss.pynq.io/t/tutorial-pynq-dma-part-2-using-the-dma-from-pynq/3134
DMA读写数据
DMA读数据即PS的数据经过DMA流向自己的IP
DMA写数据即自己的IP的数据经过DMA流向PS
DMA使用了两种AXI接口
- AXI Master
- M_AXI_MM2S 读通道 连自己的IP
- M_AXI_S2MM 写通道 连PS
- AXI Stream
- M_AXIS_MM2S 读通道 连PS
- S_AXIS_S2MM 写通道 连自己的IP
数据流向
DMA读数据
DMA读数据时的数据流向
- PS —> M_AXI_MM2S —> M_AXIS_MM2S —> 自己的IP
如果IP没有准备好从M_AXIS端口接收数据,那么这个端口将停滞。你也可以使用AXI流FIFO。
DMA写数据
DMA写数据时的数据流向
- 自己的IP —> S_AXIS_S2MM —> M_AXI_S2MM —> PS
如果IP试图写回数据,但DMA写还没有开始,那么S_AXIS通道将使IP停滞。同样,如果需要,可以使用FIFO。
其他
DMA有一些内置的缓冲,所以如果你试图调试你的设计,你可能会看到一些(或全部)数据从内存中读取,但它不一定被发送到你的IP,可能在内部或HP端口FIFO中排队。
PYNQ只支持从连续的内存缓冲区的DMA。PYNQ不支持DMA的散点收集功能,即:数据可以从零散的或不连续的内存位置传输。
可以在DMA上启用Scatter-Gather,以允许多达8,388,608字节的多次传输(来自连续的内存缓冲区)。如果你这样做,你需要使用M_AXI_SG端口而不是M_AXI端口。这在本教程中没有涉及。
SG的一个替代方案是在软件中把内存传输分割成67,108,863或更小的块,并运行多个DMA传输。
AXI DMA IP设置
Width of Buffer Length Register
这个值决定了单个DMA传输的最大数据包大小。width=26允许传输67,108,863(226-1)字节——DMA支持的最大尺寸。
可以将其设置为较小的值并节省少量的PL资源。
当使用DMA时,如果你试图进行传输,但只看到缓冲区的第一部分被传输,请在你的硬件设计中检查Width of Buffer Length Register
的值,并检查你正在传输多少数据。将默认值设置为14位是一个常见的错误,它将限制DMA的传输量为16,384(214)字节。如果你试图发送超过这个数量的数据,一旦支持的最大字节数传输完毕,传输就会终止。记住要检查传输的字节数大小。
Address width
检查Address width
是否设置为32。在这个例子中,我将把DMA连接到Zynq的32位PS存储器上。如果你将其连接到更大的存储器上,例如你使用Zynq Ultrascale+或者你的DMA连接到PL连接的存储器上,你可以将其设置为64位。
DMA read and write channels
这个例子将同时使用DMA的读和写通道,但你可能只需要启用其中一个通道。你的设计中也可以有多个DMA。
-
在这个设计中,让读和写两个通道都处于启用状态
-
将内存映射的数据宽度设置为64,与HP端口相匹配(在PYNQ图像中定义并在启动时应用)。
-
你可以增加
burst width
以提高数据传输的效率。通常,当你增加最大突发的大小时,硬件资源的利用率会增加,但这不应该对总体利用率产生明显的影响。 -
确保
Allow unaligned transfers
没有被启用。PYNQ不支持这一点。
DMA AXI主端口需要连接到PS DRAM。这将通过Zynq HP(AXI Slave)端口完成。这些端口在默认情况下是不启用的。在内部,有两个连接到PS存储器,四个HP端口被连接到。HP0和HP1共用一个开关到一个端口,HP2和HP3共用一个开关到另一个。对于这个例子和一些设计来说,这种差别可能并不明显,但是当只需要两个HP端口时,将它们连接到不共享一个开关的HP端口上会更有效率,即HP0和HP2或HP1和HP3一起。
硬件连接
软件代码
from pynq import Overlay
# .bit和.hwh文件需要命名为相同的名字
# .bit文件所在路径: {project_name}/{project_name}.runs/impl_1/{top verilog file}.bit
# .hwh文件所在路径: {project_name}/{project_name}.srcs/sources_1/bd/{bd name}/hw_handoff/{bd name}.hwh
ol = Overlay("../../hardware/HW_DMA_Tutorial/design_1_wrapper.bit")
print(ol.ip_dict) # 找到dma的对象名
dma = ol.dma
dma_send = ol.dma.sendchannel
dma_recv = ol.dma.recvchannel
DMA读数据:数据从PS流向FIFO
from pynq import allocate
import numpy as np
# input_buffer初始化
data_size = 1000
input_buffer = allocate(shape=(data_size,), dtype=np.uint32)
for i in range(data_size):
input_buffer[i] = i + 0xcafe0000
print(hex(input_buffer[i]))
# DMA读
dma_send.transfer(input_buffer)
DMA写数据:数据从FIFO流向PS
# output_buffer初始化
output_buffer = allocate(shape=(data_size,), dtype=np.uint32)
# 查看output_buffer的初值
for i in range(10):
print('0x' + format(output_buffer[i], '02x'))
# DMA写
dma_recv.transfer(output_buffer)
dma_recv.idle
可以取True和False
- True: 可以执行dma_recv.transfer
- False: 不能执行dma_recv.transfer,若执行会报错
print(dma_recv.error)
print(dma_recv.idle)
print(dma_recv.running)
其他
print(dma.register_map)
print("Input buffer address :", hex(input_buffer.physical_address))
print("Output buffer address :", hex(output_buffer.physical_address))
print("---")
print("DMA Source address :", hex(dma.register_map.MM2S_SA.Source_Address))
print("DMA Destination address:", hex(dma.register_map.S2MM_DA.Destination_Address))
# 释放内存缓冲区,以避免内存泄漏
del input_buffer, output_buffer