實現穩健的微控制器到 FPGA SPI 介面:第 1 部分 - FPGA 挑戰

本貼文,我們將探討微控制器(uC)到 FPGA 序列周邊介面(SPI)。主要目標是使 FPGA 更容易控制。我們將探索在 DigilentBASYS-3(XilinxAtrix-7)開發板上開發,使用 Verilog 硬體語言,並在 Arduino Nano Every 上開發對應的微控制器程式碼。 它們一起提供具有 16 位元循環冗餘檢查(CRC)的分組資料傳輸,該檢查與經過測試的 16MHz SPI 序列時脈一起操作。Verilog 實行應該避免任何與 Xilinx 特定的依賴關係,使其以最小的修改可移植到其他平台。然而,這個跨平台的理想規定仍有待測試。

圖 1:uC 到 FPGA SPI 介面的工作台測試,採用 Digilent BASYS 3 和 Arduino Nano Every。uC 正在 Basys 3 板上的 SSD 和 16 個 LED 進行多工運行。

許多人將 FPGA 作為數位邏輯的一部分,使用原理圖輸入工具開發使用邏輯原語的簡單組合電路。許多人繼續學習專門介紹 Verilog 或 VHDL 的 FPGA。有些人繼續學習高級邏輯或在尖端專案中實現FPGA。遺憾的是,很少有人超越獨立模組,將多個模組整合到一個更大的系統。這是有原因的。

雖然 FPGA 是一項了不起的技術,但它亦以難以控製而聞名。與微控制器(uC)相比,FPGA 的複雜度高出一個數量級。硬體結構必須從頭開始建置或從教科書或網路上找到的範例實例化。

本系列貼文旨在協助您在FPGA中進行系統設計。並提供需要速度的時間關鍵、平行和確定性電路中使用 FPGA 的最佳屬性。 然後,我們利用微控制器的靈活性、相對易於編程和通訊堆疊(包括無線和雲端功能)的優勢。看待這種情況的一種方法是將 FPGA 視為透過中等高速資料匯流排連接的強大微控制器週邊裝置。

目標受眾是可以在 Verilog 中對 FPGA 進行程式設計以及對微控制器進行程式設計的個人或團隊。這可能很適合頂尖學生,因為團隊方法允許同時在 FPGA 和 uC 上工作。

由於我們的重點是 FPGA,因此希望盡量減少微控制器的工作量。之所以選擇 Arduino Nano Every,是因為它是一種常見且眾所周知的微控制器。本文的大多數讀者將非常熟悉微控制器和 C 程式設計。

而不是簡單地介紹 Verilog 和 uC 程式碼,我們將探索與設計流程相關的推理和挑戰。結果就是一系列說教性的文章。然而,我相信它們會提供有用的訊息。

舉例說明系統定義

系統是一起工作的組件的集合。對於 FPGA 應用,這可能包括用於資料收集、濾波、控制的模組,以及向使用者呈現資訊或整合到更大的自動化系統中的方法。

為了更好地定義這個術語,讓我們考慮一個具有挑戰性的基於 FPGA 和 uC 的範例。假設我們希望建立一個系統來測量三相 400 Hz 波形的真實功率和無功功率。設計要求是提供 RMS 電壓,RMS電流,以及準確測量訊號之間的相位差。讓我們進一步假設必須測量高達 20kHz 的線路諧波。

為了確保最佳性能,我們假設同時進行電壓和電流測量,這意味著需要 6 個獨立的類比數位轉換器(ADC)。奈奎斯特採樣要求我們以每秒至少 40,000 個樣本的速率進行測量。 總的來說,這需要每秒 24 萬個樣本。在此之上,系統必須執行 RMS 和相位角計算。RMS 計算機制應包括過濾,以呈現短期和長期的整合,允許快速響應瞬變,同時保持長期穩定。 最重要的是確定諧波的快速傅立葉變換(FFT)。

有很多方法可以設計這樣一個系統。一個高階統一通訊系統或幾個協調的統一通訊系統可以執行該任務。但這並不是本文的重點。相反,我們將認識到資料擷取和濾波器方面完全在入門級 FPGA 的能力範圍內。

基於 FPGA 的平行系統可以輕鬆完成這些任務。設計單一模組並不太難。事實上,許多人已經將單一 ADC 整合到 FPGA 中。真正的挑戰是將所有模組緊連在一起,在需要的時候將資料移動到需要的地方。

FPGA 緊連

就我個人而言,我遇到的最困難的 FPGA 學習挑戰之一是頑固地忘記統一通訊系統程式設計技術。Verilog 和 VHDL 是硬體描述語言,而像 C 這樣的語言是一種過程性的抽象語言,以消除硬體依賴。我花了很長時間才理解 FPGA 硬體描述中固有的並行性 – 所有的東西,都是一次性的。

對於我們的第一個例子,我們需要可視化 6 個 ADC。我們需要視覺化用於將它們連接在一起的各種控制線和數據線。接下來是保存中間結果的暫存器,執行均方根計算的平方和求和操作的乘數器和加法器,以及許多其他狀態機硬體來協調這些活動。

這種基於 FPGA 硬體的緊連是暫存器傳輸級(RTL)設計方法。 寄存器傳輸(RT)意味著一種用於將資料從一個暫存器傳輸到另一個暫存器的控制機制,通常在暫存器之間插入大量組合邏輯。這與微處理器中使用的管道過程有關。例如,在第一個時脈週期,資料被呈現給加法器。在第二個時脈週期,加法器執行運算。在第三個時脈週期,資料被傳送到記憶體。本例中的控制器是負責初始化 RT 流程的狀態機。出於我們的目的,我們將假設所有暫存器都屬於單一時脈域。

這個想法值得重複。

在我們的 RTL 設計中,一個狀態機或狀態機集合將控制和協調資料從一個暫存器到另一個暫存器的傳輸。所有的操作都假定在同一個時脈域內。可以參考相關貼子「在 Verilog 中實作時脈邊界同步器」。

回想一下,每個暫存器都是記憶體。這個術語適用於從單一 D型正反器到 FPGA 大塊記憶體實例化的任何事物。讓我們使用簡單的 8 位元暫存器來探索這個概念。

下面的例子包含了所有的 RTL 機制和我們的定時規定。Q輸出是一個暫存器。從 @(posedge clk) 語句和 Verilog 的非區塊「 <= 」操作符的使用可以看出,暫存器更新與時脈的正邊緣是同步的。

module reg_8bit(
    input clk,
    input load,
    input wire [7:0] D, 
    output reg [7:0] Q 
);
always @(posedge clk) begin
    if (load) 
        Q <= D;      
end
endmodule

考慮負載訊號的性質。它必須在時脈上升沿之前是穩定的。假設所有訊號都在同一時脈域中,並且假設負載訊號本身是由狀態機的一個暫存輸出驅動的,那麼合成工具將盡最大努力確保滿足此關鍵時序穩定性。

請注意,在時脈的上升沿上斷言了一個「load」命令列。 D 上的資料將在時脈的下一個上升沿上變成 Q。 這對新程式設計師來說是一個陷阱,會導致意外的單一時脈延遲。為了追蹤這種 RTL 行為,從狀態和狀態的角度來思考是很重要的。最後,要注意負載訊號的寬度(週期)。

在同步 RTL 系統中,訊號的導通時間可以不小於時脈週期(上升沿到上升沿)。這可以透過理解時脈上升沿上的相關狀態機更新來解釋。

程式設計技巧 :本文中提到的設計 RTL 限制限制了 FPGA 的效能,並可能導致不必要地使用 FGPA 結構。不過,用 @(posedge clk) 規定暫存所有訊號,一般會提高系統穩定性。 這是一個很好的起點,你可以稍後修改以滿足你的需求。

頻閃暫存器傳輸

在前面的例子中,我們注意到「載入」訊號的時間(寬度)可能會改變。因為這是一個同步系統,所以寬度總是時鐘的函數。最小 on time 是系統時脈的一個週期。這種短訊號有幾個不同的名稱,包括頻閃、滴答或脈衝。在本系列文章中,我們將使用頻閃這個術語。

前面,我們將 RTL 定義為具有一系列暫存器的設計方法。資料在暫存器之間傳輸,所有這些暫存器都被假設在相同的時脈域中。在暫存器之間放置組合邏輯以修改數據,並理解所有邏輯操作必須在域時脈週期內完成。 例如,對於 100MHz 時脈,所有 FPGA 訊號必須在 10ns 內穩定下來,以便為下一個時脈事件做好準備。

RTL 過程需要一個控制器或協調控制器的集合來控制暫存器。控制暫存器的一種方法是每個控制器產生一個頻閃訊號來推進感興趣的暫存器。

一個簡單的基於頻閃的控制器如下圖所示。此 RTL 將在給定 100 MHz 時脈的情況下以 20 kHz 的速率產生頻閃;它是一個 mod-5000 計數器。它是一個控制器,在這個意義上,頻閃器可以用來啟動一個每秒重複 20,000 次的過程,這樣的 ADC。

module pulse_20k (                          // mod 5000 for a 100 MHz clock
    input clk,
    output reg zero_strobe,
    output reg [12:0] count                 // 13 bits to hold numbers from 0 to 4999
);
always @(posedge clk) begin
    zero_strobe <= 1'b0; 			        // default
    count <= count + 1;
    if (count >= 4999) begin                // Count starts at 0
        count <= 13'd0;  
        zero_strobe <= 1'b1;
    end 
end
endmodule

觀察頻閃和零計數發生在同一個時脈週期。 這可能看起來有悖直覺,除非我們透過 state / state-next 鏡頭來觀察狀態機。當計數器處於狀態 4999 時,(count >= 4999)條件將為真。 在本例中,計數為 4999 是 mod-5000 計數器的最大計數。在時脈的下一個上升沿上,計數變回 0 並同時斷言 zero_strobe。這就像時鐘的最大分鐘數是60分鐘。

此時我們就可以開始設計更複雜的控制器了。 我們當然會在本文的後續部分介紹。目前,簡單的基於時間的控制器已經達到了它的目的。 我們知道,它會在時鐘域內產生一個同步的頻閃。 這個頻閃器可以用作一個更大的 RTL 系統的一部分。

雙緩衝

在這一點上,我們簡要地探討了 RTL 操作,並小心地在單一時脈域中保留同步暫存器傳輸。現在我們將探討當暫存器具有不同寬度時的 RTL 操作。這種情況很常見,特別是在使用 SPI 等通訊協定時。在這種情況下,SPI 通常透過在連續位元組上操作來處理數據,而相關的 FPGA 硬體可能是 2 到 4 位元組寬。

例如,考慮一個 10 位元脈寬調變器(PWM)。提供 reg_B1 和 reg_B0,可以執行以下操作:

assign reg_PWM = {reg_B1, reg_B0}[9:0];

那是一個合理的拼接。但事情出錯的可能性很高。問題是 reg_B1 和 reg_B0 的更新時間。如果它們的更新之間有任何延遲,reg_PWM 可能會以錯誤的值結束。

假設 reg_B1 和 reg_B0 是從 SPI 介面生成的。在這種情況下,暫存器將在不同的時間更新。作為最壞的情況,假設 PWM 指令從 255 上升到 256。在某個時刻,PWM 以 25% 的佔空比運行(命令 255 到 10 位 PWM)。現在假設 SPI 更新了 reg_B1,而 reg_B0 仍然存在。佔空比現在將跳到 50%(511 到 10 位 PWM 的指令)。它將保持在這個錯誤的值,直到 reg_B0 被 SPI 更新。這種 PWM 的跳變,即使是短時間的指令也會對系統穩定性產生不良影響。考慮一下如果 PWM 在閉環系統中控制電機,這將導致的不穩定性。

解決方案是增加一個稱為雙緩衝器的中間暫存器,如圖 2 所示。這允許 reg_B1 自然更新為 reg_B0。 之後,當兩個暫存器都知道要更新時,控制器可以將內容傳送到一個 2 位元組的暫存器,稱為雙緩衝區。這確保了 PWM 等裝置被更新為已知的完整暫存器,而不是中間值。

image

圖 2 :雙緩衝區 RTL 的方塊圖表示。

第一部分的結尾

在本文中,我們探討了 FPGA 設計的一些系統層級注意事項。雖然這當然不是一個完整的列表,但基本的 RTL 方法,具有單個時脈邊界的同步設計以及頻閃的使用應該在您的腦海中清晰。這些資訊將幫助我們理解下一期文章中介紹的 SPI 模組。本文提供的線索如何SPI模組的輸出頻閃可用來控制資料流從 主 uC 進入 FPGA。

第2部分已經發布

歡迎您的評論和建議。特別歡迎進一步討論高階的 RTL 系統設計方法論。