偶然发现了DataMover这个IP,确实解决了很多问题,一般我们在PL端要读写AXI Slave Port的PS时候,总是很不喜欢写复杂的AXI Full协议.就算实现AXI Lite协议也觉得很累啊.

DataMover就是支持AXIS转AXI接口,配合其他IP可以解决非常多问题,常见情况下PL遇到什么问题.
- PS 端口位宽远比 PL 设计的大得多. - 使用AXI4-Stream Data Width Conveter解决.
- PS 端口的读取性能远比 PL 产生数据速度大得多 - 使用AXI4-Stream Data FIFO解决.
- PS 端口通常使用AXI Full协议,PL实现AXI Full需要重复写的轮子有点多. - 使用AXI DataMover解决.
- PS 端口通常有限,使用AXI Interconnect解决,这是传统方法就不额外说明了.
搜索PG022可以找到AXI DataMover的IP文档.这里暂时单纯讨论到PS的写方向,反过来原理也是一样的,总共分为.对于PS侧,PS永远是从机.
- S_AXIS_S2MM - 数据从这里输入,AXIS格式.
- S_AXIS_S2MM_CMD - 如何传输从这里输入,AXIS格式.
- M_AXI_S2MM - 连接PS HP/HPD接口,或者连接AXI Interconnect.
- M_AXIS_S2MM_STS - 传输结果反馈

我们传输数据进去时候,同时要声明我们传输的一些细节,即命令接口.


数据映射规则是这样的,假设我们传输的地址是32位.
- BTT[22:0] 要传输的数据量.
- Type[23] 对应突发类型AxBURST.
- DSA[29:24] 当使用数据重新排列引擎(DRE)时,对齐方式的设置,这个可以阅读AXI4-Stream文档了解.
- EOF[30] 如果为1,这一次AXIS传输完要给AXI Slave发送TLAST.
- DRR[31] 前面有讲到.
- SADDR[63:32] 传输的地址,如果是INCR模式就是起始地址了.
- TAG[67:64] 调试用标记
- xCACHE[75:72] 对应传输的AxCACHE
- xUSER[79:76] 对应传输的AxUSER
传输结果.


刚才设置的TAG会传递过来,可以用来区分不同的传输.如果最高位是1,那么传输就OK了.
那么整体设计就明朗了.

pl_ddr是按8位数据写的,所以使用位宽转换成64位,由于PL DDR一次提供1字节,而DataMover一次要8字节,明显带宽上有很大差距,所以又连接了一个Data FIFO,要注意STS的READY要拉高,不然第二次传输他就以为是错误了.
PL代码参考.
module pl_ddr(
input wire clk,
(* X_INTERFACE_PARAMETER = "POLARITY ACTIVE_HIGH" *)
input wire rst,
input wire [7:0] i_data,
input wire i_trigger,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 M_AXIS TLAST" *)
output reg m_axis_tlast,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 M_AXIS TKEEP" *)
output reg m_axis_tkeep,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 M_AXIS TDATA" *)
output reg [7:0] m_axis_tdata,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 M_AXIS TREADY" *)
input wire m_axis_tready,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 M_AXIS TVALID" *)
output reg m_axis_tvalid,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 M_AXIS_S2MM_CMD TVALID" *)
output reg m_axis_s2mm_cmd_tvalid,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 M_AXIS_S2MM_CMD TREADY" *)
input wire m_axis_s2mm_cmd_tready,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 M_AXIS_S2MM_CMD TDATA" *)
output reg [71:0] m_axis_s2mm_cmd_tdata,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 S_AXIS_S2MM_STS TVALID" *)
input wire s_axis_s2mm_sts_tvalid,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 S_AXIS_S2MM_STS TREADY" *)
output wire s_axis_s2mm_sts_tready,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 S_AXIS_S2MM_STS TLAST" *)
input wire s_axis_s2mm_sts_tlast,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 S_AXIS_S2MM_STS TKEEP" *)
input wire s_axis_s2mm_sts_tkeep,
(* X_INTERFACE_INFO = "xilinx.com:interface:axis:1.0 S_AXIS_S2MM_STS TDATA" *)
input wire [7:0] s_axis_s2mm_sts_tdata
);
reg [2:0] sync_trigger_ff;
// 信号为高时发生上升沿
wire sync_trigger = !sync_trigger_ff[2] && sync_trigger_ff[1];
reg [15:0] counter;
reg [1:0] state;
assign s_axis_s2mm_sts_tready = 1'b1;
always @ (posedge clk) begin
if(rst) begin
sync_trigger_ff <= 0;
end else begin
sync_trigger_ff <= {sync_trigger_ff[1:0],i_trigger};
end
end
always @ (posedge clk) begin
if(rst) begin
m_axis_tdata <= i_data;
counter <= 0;
state <= 0;
end else begin
case (state)
2'b00: begin
counter <= 0;
m_axis_tdata <= i_data;
if(sync_trigger) begin
state <= 2'b01;
end else begin
state <= 2'b00;
end
end
2'b01: begin
if (counter < 8192) begin
if(m_axis_tready) begin
m_axis_tdata <= m_axis_tdata + 1'b1;
counter <= counter + 1'b1;
end
end else begin
state <= 2'b00;
end
end
endcase
end
end
// TVALID
always @ (posedge clk) begin
if(rst) begin
m_axis_tvalid <= 0;
end else begin
if(state == 2'b01 && counter < 8192) begin
m_axis_tvalid <= 1'b1;
end else begin
m_axis_tvalid <= 1'b0;
end
end
end
// TLAST
always @ (posedge clk) begin
if(rst) begin
m_axis_tkeep <= 0;
end else begin
if(state == 2'b01) begin
m_axis_tkeep <= 4'b1111;
end else begin
m_axis_tkeep <= 4'b0000;
end
end
end
// TKEEP
always @ (posedge clk) begin
if(rst) begin
m_axis_tkeep <= 0;
end else begin
if(state == 2'b01) begin
m_axis_tkeep <= 4'b1111;
end else begin
m_axis_tkeep <= 4'b0000;
end
end
end
// 锁存器可以暂时锁存CMD直到TREADY
always @ (posedge clk) begin
if(rst) begin
m_axis_s2mm_cmd_tvalid <= 0;
m_axis_s2mm_cmd_tdata <= 0;
end
else if(sync_trigger) begin
m_axis_s2mm_cmd_tvalid <= 1'b1;
m_axis_s2mm_cmd_tdata <= {9'd0,32'h1000_0000,1'b0,1'b1,6'd0,1'b1,23'd8192};
end else if(m_axis_s2mm_cmd_tready) begin
m_axis_s2mm_cmd_tvalid <= 0;
m_axis_s2mm_cmd_tdata <= 0;
end
end
endmodule
在ILA中验证,AXI总线已经反馈OKAY,并且STS也是回报OK的.

我的VIO配置.

PS读取偷懒的话,就失能全部Cache验证.

另外也可以使用Store and Forward功能,这样就算输入输出不同宽度,也是可以暂存,但是他只在Full模式有效,不如多使用几个额外IP.