學數字設計的軟體工程師該瞭解的時鐘知識

發表於2017-12-26

如果你有軟體工程師背景,想找一份數字設計工程師的工作,那麼你需要做的第一件事就是儘可能早的學習時鐘概念。對很多從軟體工程師轉來的初級硬體設計工程師來說,時鐘概念都是一件惱人的事情。如果沒有時鐘,他們就可以將 HDL(Hardware Description Language,硬體描述語言)轉換為一種程式語言,如 $display,if 和 for 迴圈,如同其他的任何程式語言一樣。 然而,這些初級設計師所忽視的時鐘,通常是數字設計中最基礎的部分。

沒有什麼時候會比在審查初級 HDL 設計工程師第一次設計的產品的時候,所發現的問題更多。現在,我已經和幾位在我參與的論壇上發表過問題的人交談過了。而當我深入瞭解後,發現他們正在做的是什麼時,我為他們感到很尷尬。

以我遇到的一個學生為例,他不理解為什麼網路上沒有人重視他的高階加密標準 (AES) 的HDL實現。此處,為了不讓他因為名字或專案而尷尬,我暫且將他稱之為學生。(不,我不是教授。)這個“學生”建立了一個 Verilog 設計,進行不止一輪的 AES 加密,但每一輪都是組合邏輯,且兩者之間沒有時鐘。 我不記得他是在做 AES-128、AES-192 還是 AES-256,但 AES 需要進行 10 到 14 輪計算。 我記得,他的加密引擎在模擬器中執行完美,然而它只使用一個時鐘來加密或解密他的資料。 他為自己的工作感到自豪,但不明白為什麼那些看過這個專案的人都對他說,他的思考方式像一個軟體工程師,而不是一個硬體設計師。

事實上,我現在有機會給像這個“學生”一樣的 HDL 新手軟體工程師解釋疑惑。 他們中的許多人對待 HDL 語言,就像物件另一種軟體程式語言一樣。 在程式設計之前,他們去尋找任何軟體程式語言的基礎知識:如何宣告變數,如何建立一個 if 語句或 case 語句,如何編寫迴圈等等。然後,他們編寫程式碼就像編寫一個計算機程式——一切都是順序執行(圖1),而完全忽略了數字設計中的基本事實,即所有執行都是並行的。

學數字設計的軟體工程師該瞭解的時鐘知識

圖1:軟體執行是順序的

有時這些程式設計師會使用模擬器,如 Verilator,iverilog 或者 EDA 平臺。 然後,他們在邏輯程式碼中使用一堆$ display命令,將它們視為順序的“printf”,並通過這些命令來使程式碼執行,而不是使用時鐘。 他們的設計在模擬器中只是被單獨地“執行”組合邏輯程式碼。

這些學生向我描述他們的設計,並解釋稱他們的設計“不需要時鐘就能執行”。

天啊,這都是什麼想法?

實際上,任何數字邏輯設計如果使用沒有時鐘都是不能工作的。總有一些物理過程會建立輸入。而 這些輸入必須在某個開始時間都有效 ——這個時間在其設計中形成第一個時鐘刻度。 同樣地,在接收這些輸入一段時間後就要輸出。 對於給定的一組輸入,所有輸出的有效時間在“無時鐘”設計中形成下一個“時鐘”。 或許第一個時鐘刻度是當他們調整好電路板上的最後一個開關的時候,而最後一個時鐘刻度是當他們讀取到結果的時候。時鐘是如何形成的沒關係:有一個時鐘就可以。

而這導致的結果就是,那些聲稱他們的設計“沒有時鐘”的人解釋他正在以不切實際的方式使用模擬器,或者設計中存在一個外部時鐘用於設定輸入和讀取輸出 ——而這正是另外一種方式,表明設計中的確存在一個時鐘。

如果你發現你自己正試圖以這種方式理解數字邏輯必須有一個時鐘才能執行,或你認識的某人也是這麼嘗試理解的,那麼本文正適合你們。

接下來,讓我們花一兩分鐘來討論時鐘,以及為什麼圍繞時鐘來構建和設計你的數字邏輯很重要。

第一部分:硬體設計是並行的

硬體設計學習中的第一部分也是最難的部分,就是硬體設計是並行設計。所有的程式碼指令並不是依次執行,如同一條指令連著下一條指令(如圖1所示),就像計算機程式一樣。相反,所有的指令在同一時刻執行,如圖2所示。

學數字設計的軟體工程師該瞭解的時鐘知識

圖2:硬體邏輯並行執行

正是這一點,讓很多東西變得不一樣。

首先需要改變的是開發人員。你需要學習以並行的方式思考。

如果要通過舉例說明兩者的區別,硬體迴圈也許會是個不錯的例子。

軟體設計中,一個迴圈是由一連串的指令構成,如圖3所示。這些指令構造了一組初始化條件,而真正的邏輯在迴圈內部執行。通過使用一個迴圈變數來構造和定義一個迴圈邏輯,並且這個變數在每次迴圈中通常都是增加的。計算機CPU不停地重複執行迴圈中的指令和邏輯,直到迴圈變數達到了終止條件。迴圈執行的次數越多,它在程式中執行的時間也就越長。

學數字設計的軟體工程師該瞭解的時鐘知識

圖3:軟體迴圈

而基於硬體的硬體描述語言迴圈與軟體迴圈完全不同。恰恰相反,HDL 合成工具使用迴圈來使得所有邏輯的副本同時並行執行。而用來構造迴圈邏輯的程式碼,如定義索引、索引增長、檢查索引是否達到終止條件等等,是不需要合成的,通常會被移除。此外,由於合成工具正在構建物理線路和邏輯塊,所以執行迴圈的次數在合成時間之後不能改變。之後,硬體的數量是固定的,不能再改變。

導致的結果便是,硬體迴圈結構(如下圖4所示),與軟體迴圈結構(如上圖3所示)有很大的區別。

學數字設計的軟體工程師該瞭解的時鐘知識

圖4:HDL迴圈

這有幾個後果。例如,硬體迴圈迭代與軟體迴圈迭代不同,它不必依賴前期的迴圈迭代的輸出。這導致的結果是,執行一個包含一組資料的邏輯迴圈很難在下一個時鐘得到響應。

但是…現在讓我們再次回到時鐘概念。

時鐘是任何 FPGA 設計的核心。一切都圍繞著它展開。事實上,我認為所有的邏輯設計開發都應該從時鐘開始。時鐘不應該在設計完成後新增, 而是在你一開始思考如何設計架構時就要考慮。

為什麼時鐘很重要?

第一步,你要理解數字邏輯設計的一切操作在硬體上執行時都是需要時間的。不僅如此,不同的操作需要的時間總量也是不同的。從晶片上的一部分移動到另一部分也要花費時間。

或許用圖表的方式能更直觀的解釋這一點。我們將輸入置於演算法的頂部,邏輯放在中間,而輸出則放在底部。時間為軸,從上到下執行,從一個時鐘到下一個時鐘。這種視覺效果看起來就如下圖 5 所示:

學數字設計的軟體工程師該瞭解的時鐘知識

圖5:三個操作的邏輯耗時

圖例 5 展示了幾個不同的操作:加法、乘法、以及多輪的 AES 演算法——儘管為了討論的目的,它可以是多輪任意其它演算法。我在垂直方向上使用了方框的尺寸,以表示每個操作可能需要多少時間。此外,將依賴於其它操作的方框堆疊起來。因此,如果你想要在一個時鐘裡面做很多輪的 AES 演算法,你要明白第二輪演算法在第一輪演算法結束後才會開始。因此,適應這一邏輯將會增加時鐘之間的時間間隔,並減慢整體的時脈頻率。

現在讓我們關注這個粉色方框。

這個粉色框表示在硬體電路中浪費的執行能力,即你本可以用來做更多事情的時間,但因為需要等待時鐘,或者等待輸入被處理,而導致你什麼也不能做。例如,在上面的概念圖中,完成乘法運算所花費的時間不需要長達一輪 AES 演算法的時間,加法也是。然而,當 AES 演算法正在執行時,你不能夠對這兩個操作結果進行任何的動作,因為這些操作都需要等待下一個時鐘來獲取他們的下一個輸入。這就是圖 5 中粉色方框所表達的:空閒電路。另外,由於每一輪 AES 演算法都會推遲下一個時鐘的到來,所以圖 5 中存在大量的空閒電路。因此,該設計的執行速度不會像硬體允許的那麼快。

如果我們只使用 AES 演算法,那麼每一個時鐘都正好完成一輪的 AES 計算。如此一來,就可以減少執行能力的浪費,從而讓整個設計執行得更快。

圖 6 展示了這種設計思想。

學數字設計的軟體工程師該瞭解的時鐘知識

圖6:分解操作加快時脈頻率

由於我們將操作分解為更小的操作,每一個都能在時鐘單元內完成,因此,我們提高了執行能力。甚至,我們可以通過管道加密演算法,而不是一次只加密一個資料塊。這種邏輯設計的結果不會比上圖 5 所示的更快,但是如果可以保持管道充滿,則可以提高 AES 加密吞吐量至 10-14x 之間。

所以,這個設計更贊。

還可以有其它更好的方案嗎?當然!如果你熟悉 AES ,你就知道 AES 演算法的每一輪計算中都有一些獨立的步驟。這些步驟可再次分解,從而可以再次提高每輪邏輯演算法的整體時鐘速度。而這可以增加你能執行的加法和乘法運算的次數,以及加密引擎的微管道,以便你能在每個時鐘的基礎上執行更多的資料。

設計不錯。

不過,上圖 6 中還有些其它的東西。

首先,箭頭表示路由延遲。(這個數字不是按比例繪製的,它僅僅是這個討論例子的示例。)每一塊邏輯都需要上一塊邏輯將結果傳遞給它。這意味著即使某個邏輯塊不需要時間執行——例如,只是重新排列線路或其它等等,將邏輯塊從一個晶片的末端移動到另一塊也是需要花費時間的。所以,即使你將操作極簡化了,每一輪資料的傳遞仍舊存在延遲。

其次,你可能注意到,沒有一個箭頭的起始處在時鐘刻度上,即沒有一個邏輯塊一直執行到下個時鐘開始。這是為了演示 啟動時間和維持時間的概念。觸發器電路,即一種捕獲資料並同步到時鐘的電路結構,在下一個時鐘到達前需要一定的時間,此刻資料也已經是固定和確定的。另外,儘管時鐘通常被認為是瞬時的,但它從來都不是。它在不同的時刻到達晶片的不同部位。而這再次要求操作之間需要一些緩衝。

通過以上討論,我們可以得出哪些結論呢?

  1. 邏輯實現需要花費時間。
  2. 邏輯越多花費的時間也越多。
  3. 完成兩個時鐘刻度之間的邏輯所花費的時間總和(包含例行的延遲、啟動和維持時間、時鐘不確定性等),限制了時鐘速度。時鐘之間的邏輯處理越多,時鐘速率就越慢。
  4. 完成最慢操作所需的時鐘速度,限制了最快操作的速度。正如上述例子中的加法操作。它的執行速度本可以比乘法以及任何一輪單獨的 AES 演算法都更快,但它的速度在該設計中被其餘的邏輯拖慢了。
  5. 硬體定義也會限制時鐘速度。即使操作中不包含任何的邏輯,也是需要花費時間的。

因此,平衡的設計嘗試在整個設計中將大量相同的邏輯放在時鐘之間。

時鐘之間應該放多少邏輯量?

現在你已經知道你必須處理時鐘,那麼根據上述資訊你該怎麼修改和構思你的設計?答案是限制時鐘之間的邏輯數量。但問題是,這個數量是多少呢?你又該如何得到這個數量呢?

得出時鐘之間你能放置多少邏輯數量的一個方法便是,將時鐘速度設定為任意速度,然後在與你需要的硬體配套的工具套件中構建你的設計。無論何時,當你的設計無法滿足其計時需求時,都需要返回並拆分設計中的元件,或減慢時鐘速度。通過使用設計工具,你最終能夠找到那條最長的路徑。

如果你這樣做了,你將自學到一些探索方法,然後通過使用這些方法,就可以找到在執行的硬體的時鐘之間可以放置的具體邏輯數量。

例如,我傾向於在 Xilinx 7 系列零件中進行 100MHz 時鐘速率的設計。這些設計通常執行在大約 80MHz 速率的Spartan-6 上,或者50MHz的 iCE40上——儘管這些都不是硬性關係。把在一個晶片上正常執行的程式放在另一個上執行,可能會超載,亦可能會時鐘檢查失敗。

下面有一些我曾經在使用時鐘時得到的一些粗略的探索性經驗。由於只是個人經驗,這些方法並不適合所有的設計:

1.通常,我在設計一個32 位的加法時,會使用一個時鐘內有 4-8個條目的多路複用器。

如果要使用一個較快的時鐘,例如頻率為 200 MHz,可能就需要將加法操作從多路複用器上剝離下來。

ZipCPU 的最長路徑,實際上是從 ALU 的輸出到 ALU 的輸入。

這聽起來很簡單。它甚至符合前面的經驗法則。

但 ZipCPU 的問題在於,如何在較快的速度下將輸出路由回輸入。

讓我們跟蹤一下這個路徑:跟隨 ALU,邏輯路徑首先通過一個4路多路複用器來決定是否ALU,記憶體或分頻輸出需要回寫。 然後將該回寫結果饋送到旁路電路中,以確定是否需要將其立即傳入 ALU 作為其兩個輸入之一。 只有在該多路複用器末端並且旁路路徑執行 ALU 操作時,多路複用器才會產生。 因此,所有這些邏輯步驟都會在通過 ALU 時造成壓力。 然而,由於ZipCPU的設計結構,任何在此路線的時鐘都可能會按比例減緩 ZipCPU 執行速度。 這意味著有可能這條最長線路仍然是 ZipCPU 中最長的一段線路。

我曾經對以更高的速度執行 ZipCPU 感興趣,這是我嘗試分解和優化的第一個邏輯路徑。

2.16×16位乘法器需要一個時鐘。

有時,在某些硬體上,我可以在一個時鐘上執行 32×32 位的乘法。而在其他硬體上,我需要分解這個操作。 因此,如果我需要一個簽名的 32×32 位乘法,我使用我專門為此建立的流水線例程。 該例程包含其中的幾種乘法方法,允許我從適合我目前正在工作的硬體的選項中進行選擇。

你的硬體可能也還支援 18×18 位乘法。 一些 FPGA 還支援在一個優化的硬體時鐘內進行乘法和累加。只要你對使用的硬體足夠熟悉,你就知道你能用它做什麼。

3. 訪問任何 RAM 塊都需要一個時鐘。 如果可以的話,應儘量避免在該時鐘週期調整索引。 同樣,避免在此時鐘期間做任何有關輸出的事情。

儘管我認為這是一條很好的規則,但我已經在 100MHz 的 Xilinx 7 系列裝置上違反了其中的兩個部分,而沒有產生(嚴重的)影響。 (在 iCE40 裝置上有問題。)

例如,ZipCPU 從暫存器讀取資料,給結果加上一個即時數,然後從結果中選擇是否應該在暫存器、PC上,還是條件程式碼暫存器中加上即時數——都在一個時鐘內。

另外一個例子就是,長期以來 Wishbone Scope 根據當前時鐘是否從儲存器進行讀取,確定從緩衝區內讀取的地址。 從這個依賴中斷它,需要新增另一個延遲時鐘,所以當前版本不會再破壞這個(自我強加的)規則。

這些規則只是我隨著時間積累下來的方法經驗,用來判定單個時鐘內可容納的邏輯數量。 這些經驗法則與裝置和時鐘速度有關,因此它們可能不適用於你的設計開發。 我建議你積累自己的探索經驗,以便你知道在時鐘週期之間能做些什麼。

下一步

也許我能夠提供給任何新的 FPGA 開發人員的最後建議,就是學習 HDL 時要在實際硬體上進行練習,而不僅僅是在模擬器上。與實際硬體元件相關聯的工具,其在檢查程式碼和計算所需時間方面都很出色。 此外,以高速時鐘構建設計的想法是好的,但這不是硬體設計的最終結果。

記住,硬體設計是並行的。 一切都從時鐘開始。

最後,如果本文有助於你更好地瞭解HDL,或者它讓你更加困惑了,請隨時聯絡告訴我。 這會讓我知道,我將來是否有必要再次回來討論這個話題。 謝謝!

相關文章