從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測看到數據。

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