从FPGA视角来看DMA中断

题外话

从学习一些高档的单片机就能看到DMA的作用。DMA可以让外设不经过CPU的干预,直接把数据搬运到内存。这样做不仅仅是体现在不需要CPU干涉,而且能够极大的提高外设数据的吞吐量。举个简单的例子,我们需要用AD转换器去采集50Hz的交流信号,假设采样率是1024次/T。那么需要的AD转换器的采样率是50*1024=51200。在非突发,并行采集的条件下,AD转换器每秒需要中断CPU 51200次。对于百兆的CPU来讲,这个速度还是可以接受的,但是如果要在保证数据不丢失,又要对数据做一些FIR,FFT等操作呢?显然,每次数据采集完成中断CPU的方法已经不能满足这种场景。这时候就可以利用DMA接收完成中断来触发数据处理。再引入一种乒乓操作:
A:
在这里插入图片描述
B:
在这里插入图片描述
如上图所示,我们需要在内存中开辟两块缓冲区,在写入A缓冲区的时候去读取B缓冲区,在写入B缓冲区的时候,去读取A缓冲区。对于缓冲,总会想到一个例子,当我们去食堂盛饭,总是需要一个餐具的,食堂工作人员需要先把饭盛到你的盘子中,而不是直接把饭让你去吃掉。想想下如果没有餐具的缓冲,食堂的拥挤程度会翻多少倍,对于数据通常也是这个样子。
言归正传,有个这两个缓冲区, == 我们把DMA的接收完成中断length’设置成51200,那么这个时候,DMA控制器以20ms的周期去中断CPU,加上每次中断切换缓冲区,最终我们为数据处理创造了将近20ms的时间。== 由此可见,在这个方面,DMA给单片机、DSP带来的好处是革命性的。

DMA控制器如何产生中断信号?

上一篇写了在zynq上实现的DMA数据的发送,对于发送来讲,DMA接收显得更加重要,接收属于异步事件,如果没有一种机制让CPU知道这个事件的来临,那么CPU需要一直去检测这个信号。这种资源的浪费是灾难性的,说道这里突然想起以前学习MCU时候的一个疑问,“在两个外设同时中断CPU情况下,优先级高的中断被处理,优先级低的中断有可能被flush掉吗?”现在来看这个问题可以分为两种情况了,地址优先级低的外设中断速度不是那么高,优先级高的中断服务程序执行时间不是那么长。也就是 低优先级外设两次中断间隔时间>高优先级中断的中断服务程序执行时间(这里不考虑中断响应时间)。这种情况下,低优先级中断标志是不会被flush掉的。
在这里插入图片描述看图,外设的中断是从它自己的控制器上给到CPU的。所以第一种情况是没问题的。第二种情况是 低优先级外设两次中断间隔时间<高优先级中断的中断服务程序执行时间(同样这里不考虑中断响应时间)。这个时候外设只有一条中断信号线,自然就不能再次通知CPU了。在想一下,如果相同是同一个中断,本次中断还没有执行完,下一次中断又来了会发生什么情况?那如果本次中断没有执行完,又来了两次中断呢?期间的屏蔽中断,清除中断标志又该怎么设置,留给读者。

补充上次的接收部分

上次只完成到中断发送,虽然道理都懂了,但是实验没有成功,不能安心的写出来。
在这里插入图片描述红线连起来的是DMA控制器到数据fifo的通路。其他模块通过AXI-stream把数据写到fifo,DMA控制器再把数据搬到DDR。

//FSM    
always @(posedge s2mm_clk)
begin
    if(!arst_n)
    begin
        s2mm_tdata  <=64'b0;     //clean all reg
        s2mm_tkeep  <=8'h0;
        s2mm_tlast  <=1'b0;
        s2mm_tvalid <=1'b0;
        c_state     <=P_IDLE;
    end
    else if(start)   
    begin
        case(c_state)
        P_IDLE:begin
            if(s2mm_tready)
                begin
                    c_state<= P_DATA1; 
                    s2mm_tlast  <=1'b0;  
                end
            else
                c_state<=P_IDLE;
             end
        P_DATA1:begin
            if(s2mm_tready)
                 begin
                    c_state<= P_DATA2;
                    s2mm_tdata<=64'd7654321;
                    s2mm_tkeep<=8'hff;
                    s2mm_tvalid<=1'b1;
                  end
            else
                c_state<=P_DATA1;
            end
        P_DATA2:begin
            if(s2mm_tready)
                begin
                    c_state<= P_DATA3;
                    s2mm_tdata<=64'd1020201;
                    s2mm_tkeep<=8'hff;
                    s2mm_tvalid<=1'b1;
                end
            else
                c_state<=P_DATA2;
            end
        P_DATA3:begin
            if(s2mm_tready)
                begin   
                    c_state<= P_DATA4;
                    s2mm_tdata<=64'd5467201;
                    s2mm_tkeep<=8'hff;
                    s2mm_tvalid<=1'b1;
                end
            else
                c_state<=P_DATA3;
            end
        P_DATA4:begin
            if(s2mm_tready)
                  begin
                    c_state<= P_DATA5;
                    s2mm_tdata<=64'd12301;
                    s2mm_tkeep<=8'hff;
                     s2mm_tlast  <=1'b1;
                 end
            else
                c_state<=P_DATA4;
            end  
            
         P_DATA5:begin
            if(s2mm_tready)
                  begin
                     s2mm_tdata<=64'd0;
                     s2mm_tkeep<=8'h00;
                     s2mm_tlast  <=1'b0;
                     s2mm_tvalid<=1'b0;
                     if(repeat_user)
                        c_state<=P_IDLE;
                      else
                        c_state<=P_DATA5;  
                 end
            else
                c_state<=P_DATA5;
            end    
        default:c_state <= P_IDLE;
        endcase
      end
      else
      c_state<=P_IDLE; 
end   

这是一段简单的AXI-stream从机的写数据状态机。这里功能简单,直接用”一段式”状态机来描述。其实多段式的并不会写☺。verilog是一种比机器码还要机器的语言,最起码以前经常用C的我是这么认为,每一句话都是在一个时钟跳变沿上写的。机器码最起码已经指令化了,但是verilog就好像在用D触发器和与非门在设计逻辑。
下面是PL部分的代码:

void dma_s2mm_test()
{
	    XAxiDma_Config *config;
		XAxiDma InstancePtr;
		int err = 0;
		config = XAxiDma_LookupConfig(XPAR_AXI_DMA_1_DEVICE_ID);
		err = XAxiDma_CfgInitialize(&InstancePtr, config);
		if(err)
			printf("dma controler init fail\n");
		else
			printf("dma controler init success\n");
		XAxiDma_Reset(&InstancePtr);
	    while(XAxiDma_ResetIsDone(&InstancePtr)==0);    //waiting reset finish
	    err = XAxiDma_SimpleTransfer(&InstancePtr, (u32)mm2s_buf1,(u32)65536,1); //mm2s
	     /*1这里要打断点!!!!!!!*/
	    XAxiDma_IntrEnable(&InstancePtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
        /*2这里要打断点!!!!!!!*/
	    Xil_DCacheInvalidateRange((u32)mm2s_buf1,65536);
}

程序逻辑都非常简单,但是调试确实一定要小心。想起一个故事,安培科拉顿与电磁感应现象的发现擦肩而过,当时电流周围能够产生磁场已经被大家公认,但是磁生电并没有被发现。安培为了减小线圈和磁铁对电流表的影响,他把发电装置(线圈和磁铁)放在一个屋子,检测装置(电流表)放在另一个屋子。他让线圈和磁铁产生 == 相对运动 == 以后,马上跑到另外一个屋子去看电流表,这时候电流表已经停止了摆动。6年后,也就是1931年,法拉第发现了电磁感应。之后又麦克斯韦又推导出了麦克斯韦方程组,把人类推向了电气时代。
总是跑题☺,不过这次因为实验的时机搞错了,也用了好久才发现。下看下正常的实验流程:
加载bitstream->设置ILA波形捕获条件,就用tready信号->烧写ARM固件->设置vio,使PL发送数据到PS->程序执行到1断点处!(很重要)这个时候DMA控制器被初始化完成(这个数据信号成功的被捕获,但是PS没存数据不对)->执行 Xil_DCacheInvalidateRange((u32)mm2s_buf1,65536);,内存数据被刷新。
在实验过程中,没有正确的搞清楚这个流程,一直在排查其他问题,花费了大量的时间。
在这里插入图片描述这是DMA中断接收的block以及一些重要信号的捕获点。
在这里插入图片描述FIFO depth:FIFO深度,关于FIFO深度的计算,FIFO的作用;
Memory type :一种是用block memory,另外一种是由ltu生成的;
independent clocks: 独立时钟,实现跨时钟域的数据交互;
CDC sync stages:相当于”打拍“
Packet mode :一次地址,多个数据;
在这里插入图片描述Enable Scatt Gatter Engine :此模式下回向不同的缓冲区写数据,这里不用;
中断产生的时机:
当AXI主机发送完成最后一个位的数据以后,携带一个tlast信号,将其置位,这时候,在DMA控制器的中断引脚产生中断信号;
在这里插入图片描述
在这里插入图片描述可以看到,当FIFO中的数据被读空的时候,剩下最后一位的时候,这时候产生了中断。主要还是tlast信号,接下来,我们把tlast信号不置位看看会有什么现象。
在这里插入图片描述改成了0;这样就能看出是不是这个last信号造成了接收完成中断;
再看一些AXI-stream上的信号:
在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述每个信号都在预期之内,但是调试的时候就是没有在PS测看到数据。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章