前言
本实验的目标是将DDR3中的数据转换为1位串行输出,参考如下链接的方法,可以先将DDR3中的数据转换为32位stream数据,再通过AXI4-Stream Data Width Converter将32位stream数据转换为8位stream数据,最后通过FIFO Generator将并行数据转换为串行数据。本实验还将串行数据反向操作转换为32位数据保存到内存中,通过比较发送和接收数据验证FIFO环路传输的可靠性。
一 验证AXI4-Stream Data Width Converter模块的环路测试
硬件设计
DDR3中数据通过DMA转换为AXIS接口数据流,使用AXI-Stream Data Width Convert IP核,将32位数据转换为8位数据,再将8位数据转换为32位数据,保存到DDR3中。最后进行比较看数据经过环路前后是否一致。Block Design如下图所示:
其中axis_dwidth_converter_32_to_8模块的S_AXIS接口数据宽度设置为4bytes,M_AXIS接口数据宽度设置为1bytes。axis_dwidth_converter_8_to_32模块反之,其余设置保持默认或者参考70_AXI DMA环路测试实验(第一讲)_哔哩哔哩_bilibili
软件设计
软件部分可以参考71_AXI DMA环路测试实验(第二讲)_哔哩哔哩_bilibili
主要完成数据的发送、接收和比较。
运行结果
为了进一步观察接口信号可以使用ILA获取AXIS接口的时序图,如下所示
从ILA获取的时序图中可以看出,首先是DMA的M_AXIS_MM2S收到来自AXIS DataWidth Converter 32to8的READY信号,开始向其传输32位数据数据;32位数据分成4个8位数据,经过4个周期传输到AXIS DataWidth Converter 8to32,并且将VALID信号拉高;最后S_AXIS_S2MM传输32位数据到DMA中。
二 验证FIFO环路测试
硬件设计
在前面实验的基础上,使用FIFO Generator IP核将8位stream数据转换为1位串行数据,完成并转串。再反向实现串转并,将接收数据和发送数据进行比较,验证环路可靠性。Block Design如下图所示:
关于FIFO Generator IP核的介绍主要参考官方手册:PG057,以及以下两个链接:
Xilinx IP解析之FIFO Generator v13.2_徐晓康的博客的博客-CSDN博客_fifo generator
Vivado-FIFO Generator_Lzy金壳bing的博客-CSDN博客_fifo generator
本实验的FIFO Generator使用Native接口,Common Clock Block RAM实现方式;读模式采用FWFT,写数据宽度为8位,写深度为128,读数据宽度为1位,读深度为1024;复位使用同步复位。
根据 FIFO Generator 手册,连接FIFO接口和AXIS接口时需要注意,FIFO写端口的full信号需要取反才能作为ready信号,FIFO读端口的empty信号需要取反后才能作为valid信号。
以axis_dwidth_converter_32_to_8的M_AXIS接口和fifo_generator_8_to_1的FIFO_WRITE为例,如下图所示,数据接口可以直接相连,M_AXIS的valid信号作为FIFO_WRITE的wr_en信号,FIFO_WRITE的full信号需要取反后才能作为M_AXIS的ready信号。两个FIFO之间相连以及axis_dwidth_converter_8_to_32和fifo_generator_1_to_8接口之间相连也是同理。
其中counter_tlast_v1_0是自己编写的一个IP核用于给DMA的S_AXIS_S2MM接口产生tlast信号,表示接收完成。这里的Cnt Target需要与软件部分的MAX_PKT_LEN(传输长度)相同。
还有一点是counter_tlast模块和FIFO模块的复位信号使用的是DMA的s2mm_prmry_reset_out_n,当完成一次DMA传输时,需要使DMA产生一个复位信号,将last信号拉低,计数器的计数值清零,并将FIFO复位以准备下一次传输。
软件设计
软件部分如下:
int main(void)
{
int Status;
XAxiDma_Config *Config;
int Tries = NUMBER_OF_TRANSFERS;
int Index;
u8 *TxBufferPtr;
u8 *RxBufferPtr;
u8 Value;
TxBufferPtr = (u8 *)TX_BUFFER_BASE ;
RxBufferPtr = (u8 *)RX_BUFFER_BASE;
xil_printf("\r\n--- Entering main() --- \r\n");
/*查找DMA硬件配置信息*/
Config = XAxiDma_LookupConfig(DMA_DEV_ID);
if (!Config) {
xil_printf("No config found for %d\r\n", DMA_DEV_ID);
return XST_FAILURE;
}
/* Initialize DMA engine */
Status = XAxiDma_CfgInitialize(&AxiDma, Config);
if (Status != XST_SUCCESS) {
xil_printf("Initialization Failed %d\r\n", Status);
return XST_FAILURE;
}
if(XAxiDma_HasSg(&AxiDma)){
xil_printf("Device configured as SG mode \r\n");
return XST_FAILURE;
}
/* Set up Interrupt system */
Status = SetupIntrSystem(&Intc, &AxiDma, TX_INTR_ID, RX_INTR_ID);
if (Status != XST_SUCCESS) {
xil_printf("Failed intr setup\r\n");
return XST_FAILURE;
}
/* disable all interrupts before setup */
XAxiDma_Intrdisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DMA_TO_DEVICE);
XAxiDma_Intrdisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DEVICE_TO_DMA);
/* Enable all interrupts */
XAxiDma_IntrEnable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrEnable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DEVICE_TO_DMA);
/* Initialize flags before start transfer test */
TxDone = 0;
RxDone = 0;
Error = 0;
/*设置发送数据*/
Value = TEST_START_VALUE;
for(Index = 0; Index < MAX_PKT_LEN; Index ++) {
TxBufferPtr[Index] = Value;
Value = (Value + 1) & 0xFF;
}
/* Flush the SrcBuffer before the DMA transfer, in case the Data Cache
* is enabled
* 清空缓存,把缓存中的数据放到DDR中
*/
Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, MAX_PKT_LEN);
/* Send a packet */
for(Index = 0; Index < Tries; Index ++) {
/*
* 开启DMA传输和接收
* 这里需要注意,一定是先等到device中有数据后才开启接收传输,这是由AXIS协议决定的
*/
Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) RxBufferPtr,
MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) TxBufferPtr,
MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
* Wait TX done and RX done
* 判断中断
*/
while (!TxDone && !RxDone && !Error) {
/* nop */
}
/* Flush the SrcBuffer after the DMA transfer, in case the Data Cache
* is enabled
*/
Xil_DCacheFlush();
// Xil_DCacheFlushRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN); 使用这条语句会出问题,第一次接收的前几个与发送不符,但是经过打印语句后又一样了,所以认为是缓存的问题.
/*
* 判断传输是否出错
*/
if (Error) {
xil_printf("Failed test transmit%s done, "
"receive%s done\r\n", TxDone? "":" not",
RxDone? "":" not");
goto Done;
}
// /*打印发送数据和接收数据*/
// for(int i = 0; i < MAX_PKT_LEN; i++) {
// xil_printf("%x %x\r\n",TxBufferPtr[i], RxBufferPtr[i]);
// }
/*
* Test finished, check data
*/
Status = CheckData(MAX_PKT_LEN, TEST_START_VALUE);
if (Status != XST_SUCCESS) {
xil_printf("Data check Failed\r\n");
goto Done;
}
/*
* 完成一次传输,对DMA进行复位,s2mm_prmry_reset_out_n产生一个低电平,复位tlast信号和Fifo模块。
*/
XAxiDma_Reset(&AxiDma);
int TimeOut;
TimeOut = RESET_TIMEOUT_COUNTER;
while (TimeOut) {
if (XAxiDma_ResetIsDone(&AxiDma)) {
break;
}
TimeOut -= 1;
}
}
xil_printf("Successfully ran AXI DMA interrupt Example\r\n");
/* disable TX and RX Ring interrupts and return success */
disableIntrSystem(&Intc, TX_INTR_ID, RX_INTR_ID);
Done:
xil_printf("--- Exiting main() --- \r\n");
return XST_SUCCESS;
}
代码部分与前一实验基本相同,主要是将main函数传输循环里的
Xil_DCacheFlushRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN);
改为Xil_DCacheFlush();
并且在每次传输完成后将DMA复位。这两点是针对设计过程中遇到的两个问题进行进行改进的。
运行结果
每次传输256个字节,一共传输5次,程序运行结果如下,可见数据通过FIFO环路可以顺利发送并且正确接收。
为了进一步观察接口信号可以使用ILA获取各接口的时序图,如下所示
从上图可以看到32位stream数据首先由converter转换成8位数据,然后通过FIFO转换为1位数据,再由另一个FIFO反向操作将串行数据转换为8位并行数据,converter每接收到四个8位数据就拉高一次M_AXIS的vaild信号,将数据传输给DMA。
从上图可以看出当一次传输完成时,last信号会被拉高,控制DMA产生接收中断,表示接收完成。
从上图可以看出,当一次传输完成后,DMA会被复位从而s2mm_prmry_reset_out_n会被拉低,counter_tlast模块和FIFO Generator模块以此为复位信号也会被复位,其中last信号被拉低,fifo的输出信号置为0,表示复位完成。
三 遇到的问题
stream数据接收如何产生tlast信号
在设计过程遇到两个问题,第一个问题是DMA不能产生接收中断,运行代码的时候,由于没有接收中断错误会导致接收没有完成的问题,从而不能开启下一次DMA传输。
后来通过阅读下面这篇博客
ZYNQ AXI DMA调试细节_同年纪_的博客-CSDN博客
发现是由于DMA的S_AXIS_S2MM接口没有接收到tlast信号导致不能产生接收端。因为一开始的时候是直接将axis_dwidth_converter_8_to_32的M_AXIS连接到DMA的S_AXIS_S2MM接口上,但是在接收过程中FIFO不会产生tlast信号,converter也不会产生tlast信号,因此DMA也就无法判断什么时候接收完成,也就不会产生接收中断。一个解决办法就是添加一个计数器模块,当接收到指定字节数的时候就产生last信号给DMA的S_AXIS_S2MM接口。
计数器的代码如下,参考
【Verilog】【Vivado】计数器示例_GalaxyerKw的博客-CSDN博客_vivado 计数器
module counter_tlast
#(
parameter CNT_TARGET = 16'd20 //parameter定义参数,目标字节数
)
(
input wire sys_clk,
input wire sys_rst_n,
input wire cnt_sig, //计数信号
output reg last_sig //tlast信号
);
reg [15:0] cnt_num; //16位宽的计数器
/* 计数器cnt_num */
always@ (posedge cnt_sig or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0) //复位信号有效时(复位时),cnt_num清零
cnt_num <= 16'd0;
else if (cnt_num == (CNT_TARGET>>2)-1) //计数到预设值,cnt_num清零
cnt_num <= 16'd0;
else
cnt_num <= cnt_num + 16'd1; //每个cnt_sig上升沿cnt_num自增1
end
/* 由计数器控制的last_sig */
always@ (posedge cnt_sig or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0) //初始状态,需要使用dma的复位来给计数器进行复位
last_sig = 1'b0;
else if (cnt_num == (CNT_TARGET>>2)-1) //计数器cnt_num记满一次,last_sig拉高
last_sig = 1'b1;
else //cnt_num未记满,last_sig保持低电平
last_sig = 1'b0;
end
endmodule
首先目标字节数应该与ps中设置的传输长度相等,然后以converter的m_axis_tvailid信号为计数信号,捕捉该信号的上升沿,一个上升沿表示已经传输了32位数(4bytes),因此当计数等于预期值(CNT_TARGET/4-1)时,计数器产生一个tlast信号,并且将计数值cnt_num清零。
计数器的仿真测试代码如下:
`timescale 1ns / 1ps
module tb_counter_tlast();
reg sys_clk;
reg sys_rst_n;
reg cnt_sig;
//wire [15:0]cnt_num;
wire last_sig;
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
cnt_sig <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #5 sys_clk = ~sys_clk; //产生时钟信号:每5ns反转一次,T=100MHz
always #20 cnt_sig = ~cnt_sig;
counter_tlast //实例化counter_tlast,实例化名称写在宏定义后
#(
.CNT_TARGET(16'd20)
)counter_tlast_inst
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.cnt_sig(cnt_sig),
// .cnt_num(cnt_num),
.last_sig(last_sig)
);
endmodule
Vivado仿真方法以及Verilog语法可以参考
实验笔记——Vivado仿真模拟_菜是原罪,我有罪的博客-CSDN博客_vivado仿真
【Verilog语法简介】_文华武英的博客-CSDN博客_verilog语言
最后需要对计数器模块进行封装,IP核封装流程可以参考
Vivado自定义IP封装流程_mao0504的技术博客_51CTO博客
接收数据与发送数据不一致
第二个问题是,发现将接收下来的数据通过串口打印出来发现前几个接收数据与发送数据不一致。
一开始看了一些关于FIFO Generator的介绍
Xilinx IP解析之FIFO Generator v13.2_徐晓康的博客的博客-CSDN博客_fifo generator
Vivado-FIFO Generator_Lzy金壳bing的博客-CSDN博客_fifo generator
里面都有提到FIFO复位后需要等待几个周期后再进行写操作,否则会丢数据。但是通过ILA获取的时序图可以看出,再FIFO复位很久后(100多个采样点)才开始一次DMA传输,也就是FIFO的复位时间是足够的,并且DMA S_AXIS_S2MM上接收到的数据也是正确的。因此判断不是FIFO复位的问题。
其次在软件编写过程中,发现如果设置5次传输,总是第一次传输的前几个接收数据与发送数据不符,其余的都一致。并且如果在check data之前使用打印函数打印接收数据,则进入到check data时,接收数据又会与发送数据一致。
最后发现是cache语句的问题,在接收完成后,需要将cache中的数据放到内存中,在DMA的官方例程里使用的是
Xil_DCacheFlushRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN);
将这条语句改为
Xil_DCacheFlush();
则可以解决前几个接收数据与发送数据不一致的问题。