何为 DataMover
DataMover 是 DMA 的一种形式。Direct Memory Access 对我们来说是一个更熟悉的名字。在不需要 CPU 干预的情况下,DMA 可以进行数据的搬运,包括但不仅限于将数据从外部存储,比如 DDR,搬运到内部寄存器,或者搬运到外部存储的另一个位置。这些都只需要 CPU 一句话的事:
DataMover 的接口
那么 DataMover 是如何进行他的工作呢,我们可以从他的端口来了解。这里以 DM 的读通道为例。DataMover 共有三路接口(status 一般在调试中用于观察状态),一路 AXI 总线,两路 AXIS 总线。
读通道,将数据从如 DDR 这样的外部存储,搬运到 FPGA 的逻辑模块中。DDR 在 FPGA 上通过 MIG IP 访问,即 Memory 访问接口。MIG 提供了一个 AXI4 Slave 接口。DataMover 的 Master 接口连接到 MIG 的 Slave 接口,AXI4 协议提供了一种基于地址的访问 DDR 能力。关于 AXI4 对存储介质的地址访问,可以参考以下的文章,该文章中访问的是 BRAM ,但总线操作和访问 DDR 类似。
对zynq器件来讲,存在多种方式用于PS和PL数据交互。常见有:
1.利用AXI总线直接传输数据,直接读写DDR。
2.利用DMA进行传输,读写DDR,由PS侧进行控制。
3.利用BRAM进行数据交互。
4.利用Data Mover进行数据传输,完全由PL控制。
datamover能直接让PL侧对挂在到PS的DDR进行读写,PS侧不进行过多参与,十分方便。
目标:写一段逻辑,输入为想要发送到dram的数据,输出为想从dram读出的数据。数据均伴随有效信号,同时还需告知这个module读写数据的长度,地址,数据模式默认为递增模式。
先看看datamover都有哪些接口。拉出一个datamover,配置相关参数
datamover的引脚如下图所示,引出的都是与PL侧相关的,红框代表datamover和其他AXI设备交互数据的通路,不需要PL侧参与控制
PL侧操作的接口为绿色框部分:
CMD接口*2:写入命令,包括地址及相关控制信号
MM2S接口:读出数据,即memory map to stream
S2MM接口:写入数据,即stream to memory map
STS接口*2: 状态信号,用于调试
datamover读取写入数据都是通过valid-ready这样的握手协议,如果在数据搬运期间出现ready拉低的情况,会导致个别数据出错,要写逻辑解决这个问题又比较麻烦,同时也为了保证数据的连续性,干脆在读取和写入之前都加入一个stream的fifo进行缓冲。
写代码时仿真没问题,综合失败,出现[Synth 8-91] ambiguous clock in event control,这个在另外一个笔记细说。
关于数据会出错的问题,虽然已解决,但还在研究原因中。
代码如下:
`timescale 1ns / 1ps
/*//
描述:将64bit数据通过datamover转发到zynq 的DDR,或将数据读回
接口说明:
时钟——clk
复位——rst
64bit待发送数据—— fpga2dram_data
数据有效信号——fpga2dram_valid
启动信号——fpga2dram_start,该信号配置CMD,最好比数据信号提前一段时间
写地址——write_address
写数据长度——write_length,字节数 Bytes
64bit待接收数据—— dram2fpga_data
数据有效信号——dram2fpga_valid
启动信号——dram2fpga_start
读地址——read_address
读数据长度——read_length,字节数 Byte
其余信号直接引出到TOP层,打包成IP时会自己生成接口
//*/
module datamover_device
#(
parameter DATA_WIDTH = 64,
parameter DATA_BYTES = 8
)
(
input clk,
input rst_n,
///fpga 2 dram
input wire [DATA_WIDTH-1:0] fpga2dram_data,//数据发送到dram
input wire fpga2dram_valid,//有效信号
input wire fpga2dram_start, //开始信号,比valid信号提前几个周期,用于配置写到dram的地址等
/// dram 2 fpga
output wire [DATA_WIDTH-1:0] dram2fpga_data,//数据从dram来
output wire dram2fpga_valid,//有效信号
input wire dram2fpga_start, //开始信号,配置读dram的地址等
///地址和长度
input wire [31:0] write_address,//写dram的地址
input wire [31:0] read_address,//读dram的地址
input wire [22:0] write_length,//写的的总长度
input wire [22:0] read_length,//读的总长度
//data mover接收数据信号
//CMD信号
output wire [71:0] M_AXIS_MM2S_CMD_TDATA,
input wire M_AXIS_MM2S_CMD_TREADY,
output reg M_AXIS_MM2S_CMD_TVALID,
//数据信号
input wire [DATA_WIDTH-1:0] S_AXIS_MM2S_TDATA,
input wire [7:0] S_AXIS_MM2S_TKEEP,
input wire S_AXIS_MM2S_TLAST,
output wire S_AXIS_MM2S_TREADY,
input wire S_AXIS_MM2S_TVALID,
//sts状态信号
input wire [7:0] S_AXIS_MM2S_STS_TDATA,
input wire S_AXIS_MM2S_STS_TKEEP,
input wire S_AXIS_MM2S_STS_TLAST,
output wire S_AXIS_MM2S_STS_TREADY,
input wire S_AXIS_MM2S_STS_TVALID,
//data mover发送数据信号
//CMD信号
output wire [71:0] M_AXIS_S2MM_CMD_TDATA,
input wire M_AXIS_S2MM_CMD_TREADY,
output reg M_AXIS_S2MM_CMD_TVALID,
//数据信号
output wire [DATA_WIDTH-1:0] M_AXIS_S2MM_TDATA,
output wire [7:0] M_AXIS_S2MM_TKEEP,
output reg M_AXIS_S2MM_TLAST,
input wire M_AXIS_S2MM_TREADY,
output wire M_AXIS_S2MM_TVALID,
//sts状态信号
input wire [7:0] S_AXIS_S2MM_STS_TDATA,
input wire [0:0] S_AXIS_S2MM_STS_TKEEP,
input wire S_AXIS_S2MM_STS_TLAST,
output wire S_AXIS_S2MM_STS_TREADY,
input wire S_AXIS_S2MM_STS_TVALID
);
//检测fpga2dram的起始信号上升沿
wire fpga2dram_start_edge;
reg fpga2dram_start_buff;
always @(posedge clk) begin
fpga2dram_start_buff <= fpga2dram_start;
end
assign fpga2dram_start_edge = ~fpga2dram_start_buff & fpga2dram_start; ///检测上升沿
//检测dram2fpga的起始信号上升沿
wire dram2fpga_start_edge;
reg dram2fpga_start_buff;
always @(posedge clk) begin
dram2fpga_start_buff <= dram2fpga_start;
end
assign dram2fpga_start_edge = ~dram2fpga_start_buff & dram2fpga_start; ///检测上升沿
/fpga 传输数据到dram
assign M_AXIS_S2MM_TKEEP = 8'hff;
wire fpga2dram_fifo_wr_valid = fpga2dram_valid;
wire fpga2dram_fifo_wr_ready; //应该是一直ready的
axis_data_fifo axis_data_fifo_fpag2dram (
.s_axis_aresetn(rst_n), // input wire s_axis_aresetn
.s_axis_aclk(clk), // input wire s_axis_aclk
.s_axis_tvalid(fpga2dram_fifo_wr_valid), // input wire s_axis_tvalid
.s_axis_tready(fpga2dram_fifo_wr_ready), // output wire s_axis_tready
.s_axis_tdata (fpga2dram_data), // input wire [63 : 0] s_axis_tdata
.m_axis_tvalid(M_AXIS_S2MM_TVALID), // output wire m_axis_tvalid
.m_axis_tready(M_AXIS_S2MM_TREADY), // input wire m_axis_tready
.m_axis_tdata (M_AXIS_S2MM_TDATA) // output wire [63 : 0] m_axis_tdata
);
///产生last信号
reg [22:0] fpga2dram_count;
reg puul_down_last;//拉低last信号
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
fpga2dram_count<=0;
M_AXIS_S2MM_TLAST<=0;
puul_down_last<=0;
end
else if(puul_down_last)begin
M_AXIS_S2MM_TLAST<=0;
fpga2dram_count<=0;
puul_down_last<=0;
end
else if(M_AXIS_S2MM_TVALID & M_AXIS_S2MM_TREADY)begin
fpga2dram_count<=fpga2dram_count+1'b1;
if(fpga2dram_count==write_length/DATA_BYTES-2)begin
M_AXIS_S2MM_TLAST<=1;
puul_down_last<=1;
end
end
end
///写CMD命令
wire [71:0] fpga2dram_cmd = {
4'b0000,
4'b0000,
write_address,
1'b0,//DRR
1'b1,//EOF
6'b000000,//DSA
1'b1,//type 1:incr 0:fixed
write_length
};
assign M_AXIS_S2MM_CMD_TDATA = fpga2dram_cmd;
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
M_AXIS_S2MM_CMD_TVALID <= 0;
end
else if(fpga2dram_start_edge&M_AXIS_S2MM_CMD_TREADY)begin ///外部配置信号拉高 同时ready
M_AXIS_S2MM_CMD_TVALID <= 1;
end
else begin
M_AXIS_S2MM_CMD_TVALID <= 0;//拉低cmd 这之后的握手就是fifo自己操作的部分了 不需要用户参与
end
end
///ila调试sts信号
assign S_AXIS_S2MM_STS_TREADY =1'b1 ;
ila_fpga2dram ila_fpga2dram_inst (
.clk(clk), // input wire clk
.probe0(S_AXIS_S2MM_STS_TDATA), // input wire [7:0] probe0
.probe1(S_AXIS_S2MM_STS_TKEEP), // input wire [0:0] probe1
.probe2(S_AXIS_S2MM_STS_TLAST), // input wire [0:0] probe2
.probe3(S_AXIS_S2MM_STS_TREADY), // input wire [0:0] probe3
.probe4(S_AXIS_S2MM_STS_TVALID) // input wire [0:0] probe4
);
///监控fifo到datamover的信号
ila_fpga2dram_data3 ila_fpga2dram_data3_inst (
.clk(clk), // input wire clk
.probe0(M_AXIS_S2MM_TVALID), // input wire [0:0] probe0
.probe1(M_AXIS_S2MM_TREADY), // input wire [0:0] probe1
.probe2(M_AXIS_S2MM_TDATA), // input wire [63:0] probe2
.probe3(M_AXIS_S2MM_TLAST) // input wire [0:0] probe3
);
/dram 传输数据到fpga
axis_data_fifo axis_data_fifo_dram2fpga (
.s_axis_aresetn(rst_n), // input wire s_axis_aresetn
.s_axis_aclk(clk), // input wire s_axis_aclk
.s_axis_tvalid(S_AXIS_MM2S_TVALID), // input wire s_axis_tvalid
.s_axis_tready(S_AXIS_MM2S_TREADY), // output wire s_axis_tready
.s_axis_tdata(S_AXIS_MM2S_TDATA), // input wire [63 : 0] s_axis_tdata
.m_axis_tvalid(dram2fpga_valid), // output wire m_axis_tvalid
.m_axis_tready(1'b1), // input wire m_axis_tready
.m_axis_tdata(dram2fpga_data) // output wire [63 : 0] m_axis_tdata
);
wire [71:0] dram2fpga_cmd = {
4'b0000,
4'b0000,
read_address,
1'b0,//DRR
1'b1,//EOF
6'b000000,//DSA
1'b1,//type 1:incr 0:fixed
read_length
};
assign M_AXIS_MM2S_CMD_TDATA = dram2fpga_cmd;
reg [2:0] dram2fpga_count;
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
M_AXIS_MM2S_CMD_TVALID <= 0;
dram2fpga_count <=0;
end
else if(dram2fpga_start_edge)begin
dram2fpga_count <= dram2fpga_count+1'b1;
end
else if(dram2fpga_count==1 && M_AXIS_MM2S_CMD_TREADY)begin ///外部配置信号拉高 同时ready
M_AXIS_MM2S_CMD_TVALID <= 1;
dram2fpga_count <= dram2fpga_count+1'b1;
end
else if(dram2fpga_count==2)begin
M_AXIS_MM2S_CMD_TVALID <= 0;//拉低cmd 这之后的握手就是fifo自己操作的部分了 用户就用有效信号和数据信号
dram2fpga_count <=0;
end
end
assign S_AXIS_MM2S_STS_TREADY = 1'b1;
ila_dram2fpga ila_dram2fpga_inst (
.clk(clk), // input wire clk
.probe0(S_AXIS_MM2S_STS_TDATA), // input wire [7:0] probe0
.probe1(S_AXIS_MM2S_STS_TKEEP), // input wire [0:0] probe1
.probe2(S_AXIS_MM2S_STS_TLAST), // input wire [0:0] probe2
.probe3(S_AXIS_MM2S_STS_TREADY), // input wire [0:0] probe3
.probe4(S_AXIS_MM2S_STS_TVALID) // input wire [0:0] probe4
);
endmodule