文章目录
本文配套源码工程已上传至 https://download.csdn.net/download/botao_li/10907037
AXI_STREAM的时序
AXI_STREAM接口一般用于大规模持续的无地址映射关系的流数据传输
数据从master单向流动至slave,
参考自定义IP生成的示例代码进行说明
master端接口如下:
// Master Stream Ports. TVALID indicates that the master is driving a valid transfer, A transfer takes place when both TVALID and TREADY are asserted.
output wire M_AXIS_TVALID,
// TDATA is the primary payload that is used to provide the data that is passing across the interface from the master.
output wire [C_M_AXIS_TDATA_WIDTH-1 : 0] M_AXIS_TDATA,
// TSTRB is the byte qualifier that indicates whether the content of the associated byte of TDATA is processed as a data byte or a position byte.
output wire [(C_M_AXIS_TDATA_WIDTH/8)-1 : 0] M_AXIS_TSTRB,
// TLAST indicates the boundary of a packet.
output wire M_AXIS_TLAST,
// TREADY indicates that the slave can accept a transfer in the current cycle.
input wire M_AXIS_TREADY
slave端接口如下:
// Ready to accept data in
output wire S_AXIS_TREADY,
// Data in
input wire [C_S_AXIS_TDATA_WIDTH-1 : 0] S_AXIS_TDATA,
// Byte qualifier
input wire [(C_S_AXIS_TDATA_WIDTH/8)-1 : 0] S_AXIS_TSTRB,
// Indicates boundary of last packet
input wire S_AXIS_TLAST,
// Data is in valid
input wire S_AXIS_TVALID
在master端:
M_AXIS_TDATA, M_AXIS_TSTRB, M_AXIS_TLAST信号全部与M_AXIS_TVALID同步,在M_AXIS_TVALID与M_AXIS_TREADY同时为1的情况下完成M_AXIS_TDATA的一次传输
M_AXIS_TLAST用于指示此次流传输的最后1个M_AXIS_TDATA
在slave端:
在S_AXIS_TREADY和S_AXIS_TVALID都为1时取用数据
S_AXIS_TSTRB用于指示当前S_AXIS_TDATA内的有效字节,S_AXIS_TLAST表示此次流传输结束,根据功能需要执行流传输结束的相应处理
代码实现时,master和slave主要通过tvalid和tready相互配合完成传输,可以在master端先设置tvalid为1等待slave端的tready,也可以slave端将tready设置为1等待master端的tvalid
注意:
- 由于PS模块的AXI接口是地址映射接口,如果PL需要通过axi_stream与PS进行数据传输,则需要经由dma模块进行axi_stream与axi_full传输协议的相互转化
- 在AXI术语中,TVALID指示有效的TDATA被正确响应表示一次Burst,多个Burst组成1个Packet,Packet中用TLAST表示最后1个Burst对应的TVALID位置
AXI Direct Memory Access
axi_dma用于实现axi_stream流传输的地址映射
在Block Design中加入axi_dma模块,进行如下图所示的配置
- Scatter Gather Engine用于散内存传输,使用连续的虚拟内存地址映射至非连续的物理内存地址,效率较低并且使用更复杂,暂不研究
- Micro DMA是简化的DMA,暂不研究
- Width of Buffer Length Register是DMA的32位传输长度寄存器中用于表示一次传输(Transfer)字节数目的位数,上图配置为10,表示调用传输函数一次最大传输的数据量为210-1字节
- Max Burst Size表示axi_dma模块所有AXI接口可接受的最大的Burst数目,只有小于该值的Burst数目的传输才能被axi_dma模块正确解析
在Block Design中的axi_dma端口如下:
- S_AXI_LITE:用于与PS端的AXI Master连接,接收PS的控制指令,执行DMA写入或者DMA接收
- S_AXIS_S2MM:axi_dma模块的写接口,将数据从PL发送给PS
- M_AXIS_MM2S:axi_dma模块的读接口,将数据从PS送入PL
- M_AXI_S2MM:与PS的AXI SLAVE连接,用于PL向PS写入数据
- M_AXI_MM2S:与PS的AXI SLAVE连接,用于PL读入PS数据
- s2mm_introut:完成一次数据从PL发送给PS的Transfer后产生的中断信号,连接PS的PL中断接口
- mm2s_introut:完成一次数据从PS送入PL的Transfer后产生的中断信号,连接PS的PL中断接口
注意:
mm表示memory mapped,即内存映射,代表AXI_FULL协议,对应PS端的AXI接口
s表示stream,代码AXI_STREAM协议
AXIS中的S表示Stream
数据由PL至PS的工作流程:S2MM
- PS通过S_AXI_LITE接口送入指令,指令包含目标内存地址,即memory map
- S_AXIS_S2MM的s_axis_s2mm_tready信号拉高,允许数据进入axi_dma模块,外部与之连接的MASTER可以通过该tready信号判断写入时机
- M_AXI_S2MM按照AXI_FULL的协议,用写指令将数据送给PS模块
数据由PS至PL的工作流程:MM2S
- PS通过S_AXI_LITE接口送入指令,指令包含数据源内存地址,即memory map
- M_AXI_MM2S按照AXI_FULL的协议,用读指令从PS模块中读出数据
- M_AXIS_MM2S将m_axis_mm2s_tvalid信号拉高,表示有数据可以送出,外部与之连接的SLAVE可以通过该tvalid信号判断读出时机
AXI4-Stream Data FIFO
axi_dma不具备数据缓冲的能力,高速数据传输时PL很难完全配合PS发送DMA指令的时机,因此需要使用FIFO进行数据缓冲
本实验对PL至PS的数据进行FIFO缓冲
在Block Design中加入AXI4-Stream Data FIFO模块,进行如下图所示的配置
- Enabele Packet Mode表示按Packet传输,见AXI_STREAM的时序说明
- Signal Porperties与FIFO出入数据的AXI接口对应就可以了
- TID,TDEST,TUSER暂不考虑
在Block Design中的模块端口如下:
- S_AXIS:数据写接口,FIFO可写入的情况下s_axis_tready信号会拉高,只要对端MASTER送入s_axis_tvalid高电平即可以进行数据写入
- M_AXIS:数据读接口,FIFO数据可读出的情况下m_axis_tvalid信号会拉高,只要对端SLAVE送入m_axis_tready高电平即可进行数据读取
- 读接口Packet中的Burst数目与写接口保持一致
自定义数据源IP
在Vivado的Tools菜单,选择Create and Package New IP,建立Create a new AXI peripheral
根据之前文档的说明建立AXI STREAM协议的Master接口模块
代码如下:注意第39行及其注释,见[Block Design中的说明](#完整Block Design)
`timescale 1 ns / 1 ps
module data_src_v1_0_M00_AXIS #
(
// Users to add parameters here
// User parameters ends
// Do not modify the parameters beyond this line
// Width of S_AXIS address bus. The slave accepts the read and write addresses of width C_M_AXIS_TDATA_WIDTH.
parameter integer C_M_AXIS_TDATA_WIDTH = 32,
// Start count is the number of clock cycles the master will wait before initiating/issuing any transaction.
parameter integer C_M_START_COUNT = 32
)
(
// Users to add ports here
// User ports ends
// Do not modify the ports beyond this line
// Global ports
input wire M_AXIS_ACLK,
//
input wire M_AXIS_ARESETN,
// Master Stream Ports. TVALID indicates that the master is driving a valid transfer, A transfer takes place when both TVALID and TREADY are asserted.
output wire M_AXIS_TVALID,
// TDATA is the primary payload that is used to provide the data that is passing across the interface from the master.
output wire [C_M_AXIS_TDATA_WIDTH-1 : 0] M_AXIS_TDATA,
// TSTRB is the byte qualifier that indicates whether the content of the associated byte of TDATA is processed as a data byte or a position byte.
output wire [(C_M_AXIS_TDATA_WIDTH/8)-1 : 0] M_AXIS_TSTRB,
// TLAST indicates the boundary of a packet.
output wire M_AXIS_TLAST,
// TREADY indicates that the slave can accept a transfer in the current cycle.
input wire M_AXIS_TREADY
);
//BURST数目,必须与axi_dma模块的s2mm一次transfer的数据量大小一致,目前在SDK中设置为128字节
parameter BURST_NUM = 8'd32;
//BUSRT数目减1,用于生成tlast
//注意不能用计算公式用于比较判断,仿真正确,但是在Block Design中可能会自动扩位导致出错
wire [7:0] BURST_NUM_M1;
assign BURST_NUM_M1 = (BURST_NUM-8'd1);
//导入时钟
wire clk;
assign clk = M_AXIS_ACLK;
//导入复位
wire rstn;
assign rstn = M_AXIS_ARESETN;
//定义输出端口
reg tvalid = 1'b0;
reg [31:0] tdata = 32'd0;
reg tlast = 1'b0;
assign M_AXIS_TVALID = tvalid;
assign M_AXIS_TDATA = tdata;
assign M_AXIS_TLAST = tlast;
assign M_AXIS_TSTRB = 4'b1111;
//burst计数器
reg [7:0] cnt_burst = 8'd1;
//测试状态机
reg state = 1'b0;
always @(posedge clk) begin
if (rstn == 1'b0) begin
state <= 1'b0;
end
else begin
case (state)
1'b0: begin
if (cnt_burst == 8'd10) begin
state <= 1;
end
else begin
state <= state;
end
end
1'b1: begin
if ({tvalid, M_AXIS_TREADY, cnt_burst} == {1'b1, 1'b1, BURST_NUM}) begin
state <= 1'b0;
end
else begin
state <= state;
end
end
endcase
end
end
//计数器用于控制状态机转移
always @(posedge clk) begin
if (rstn == 1'b0) begin
cnt_burst <= 8'd1;
end
else begin
case (state)
1'b0: begin
if (cnt_burst == 8'd10) begin
cnt_burst <= 8'd1;
end
else begin
cnt_burst <= cnt_burst+8'd1;
end
end
1'b1: begin
if ({tvalid, M_AXIS_TREADY} == 2'b11) begin
if (cnt_burst == BURST_NUM) begin
//一次流传输最后1个burst被slave接收
cnt_burst <= 8'd1;
end
else begin
cnt_burst <= cnt_burst+8'd1;
end
end
else begin
cnt_burst <= cnt_burst;
end
end
default: begin
cnt_burst <= 8'd1;
end
endcase
end
end
//输出接口
always @(posedge clk) begin
if (rstn == 1'b0) begin
tvalid <= 1'b0;
tdata <= 32'd0;
tlast <= 1'b0;
end
else begin
case (state)
1'b1: begin
if ({tvalid, M_AXIS_TREADY, cnt_burst} == {1'b1, 1'b1, BURST_NUM}) begin
//与状态转移同步
tvalid <= 1'b0;
end
else begin
tvalid <= 1'b1;
end
//slave确认接收数据后,数值增1
if ({tvalid, M_AXIS_TREADY} == 2'b11) begin
tdata <= tdata+32'd1;
end
else begin
tdata <= tdata;
end
//倒数第2个burst被接收
if ({tlast, tvalid, M_AXIS_TREADY, cnt_burst} == {1'b0, 1'b1, 1'b1, BURST_NUM_M1}) begin
tlast <= 1'b1;
end
else if ({tlast, tvalid, M_AXIS_TREADY, cnt_burst} == {1'b1, 1'b1, 1'b1, BURST_NUM}) begin
//与状态转移同步
tlast <= 1'b0;
end
else begin
//保持
tlast <= tlast;
end
end
default: begin
tvalid <= 1'b0;
tlast <= 1'b0;
tdata <= tdata;
end
endcase
end
end
endmodule
关于上述代码有以下几点说明:
- module关键字后接#的方式声明parameter可以让parameter被Block Design识别,在Block Design双击模块可以对parameter进行数值设置
- 无论输入输出端口都必须加上wire关键字声明
- 上述代码第163行的cnt_burst与BURST_NUM_M1的比较不能在比较代码中用计算公式
- 使用(BURST_NUM-8’d1)进行比较