如果你有軟件工程師背景,想找一份數字設計工程師的工作,那么你需要做的第一件事就是盡可能早的學習時鐘概念。對很多從軟件工程師轉來的初級硬件設計工程師來說,時鐘概念都是一件惱人的事情。如果沒有時鐘,他們就可以將 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 中還有些其它的東西。
首先,箭頭表示路由延遲。(這個數字不是按比例繪制的,它僅僅是這個討論例子的示例。)每一塊邏輯都需要上一塊邏輯將結果傳遞給它。這意味著即使某個邏輯塊不需要時間執行——例如,只是重新排列線路或其它等等,將邏輯塊從一個芯片的末端移動到另一塊也是需要花費時間的。所以,即使你將操作極簡化了,每一輪數據的傳遞仍舊存在延遲。
其次,你可能注意到,沒有一個箭頭的起始處在時鐘刻度上,即沒有一個邏輯塊一直運行到下個時鐘開始。這是為了演示 啟動時間和維持時間的概念。觸發器電路,即一種捕獲數據并同步到時鐘的電路結構,在下一個時鐘到達前需要一定的時間,此刻數據也已經是固定和確定的。另外,盡管時鐘通常被認為是瞬時的,但它從來都不是。它在不同的時刻到達芯片的不同部位。而這再次要求操作之間需要一些緩沖。
通過以上討論,我們可以得出哪些結論呢?
邏輯實現需要花費時間。
邏輯越多花費的時間也越多。
完成兩個時鐘刻度之間的邏輯所花費的時間總和(包含例行的延遲、啟動和維持時間、時鐘不確定性等),限制了時鐘速度。時鐘之間的邏輯處理越多,時鐘速率就越慢。
完成最慢操作所需的時鐘速度,限制了最快操作的速度。正如上述例子中的加法操作。它的執行速度本可以比乘法以及任何一輪單獨的 AES 算法都更快,但它的速度在該設計中被其余的邏輯拖慢了。
硬件定義也會限制時鐘速度。即使操作中不包含任何的邏輯,也是需要花費時間的。
因此,平衡的設計嘗試在整個設計中將大量相同的邏輯放在時鐘之間。
時鐘之間應該放多少邏輯量?
現在你已經知道你必須處理時鐘,那么根據上述信息你該怎么修改和構思你的設計?答案是限制時鐘之間的邏輯數量。但問題是,這個數量是多少呢?你又該如何得到這個數量呢?
得出時鐘之間你能放置多少邏輯數量的一個方法便是,將時鐘速度設置為任意速度,然后在與你需要的硬件配套的工具套件中構建你的設計。無論何時,當你的設計無法滿足其計時需求時,都需要返回并拆分設計中的組件,或減慢時鐘速度。通過使用設計工具,你最終能夠找到那條最長的路徑。
如果你這樣做了,你將自學到一些探索方法,然后通過使用這些方法,就可以找到在運行的硬件的時鐘之間可以放置的具體邏輯數量。
例如,我傾向于在 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 時要在實際硬件上進行練習,而不僅僅是在模擬器上。與實際硬件組件相關聯的工具,其在檢查代碼和計算所需時間方面都很出色。 此外,以高速時鐘構建設計的想法是好的,但這不是硬件設計的最終結果。
記住,硬件設計是并行的。 一切都從時鐘開始。
-
FPGA
+關注
關注
1629文章
21736瀏覽量
603385 -
時鐘
+關注
關注
10文章
1733瀏覽量
131480 -
硬件設計
+關注
關注
18文章
396瀏覽量
44573
原文標題:學數字設計的軟件工程師該了解的時鐘知識
文章出處:【微信號:mcuworld,微信公眾號:嵌入式資訊精選】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論