第5節 功能描述-組合邏輯
5.1 程序語句
5.1.1 assign 語句
assign 語句是連續賦值語句,一般是將一個變量的值不間斷地賦值給另一變量,兩個變量之間就類似于被導線連在了一起,習慣上當做連線用。 assign 語句的基本格式是:
assign a = b (邏輯運算符) c …;
assign 語句的功能屬于組合邏輯的范疇,應用范圍可以概括為一下幾點:
(1)持續賦值;
(2)連線;
(3) 對 wire 型變量賦值, wire 是線網,相當于實際的連接線,如果要用 assign 直接連接,就用 wire 型變量, wire 型變量的值隨時發生變化。
需要說明的是,多條 assign 連續賦值語句之間互相獨立、 并行執行。
5.1.2 always 語句
always 語句是條件循環語句,執行機制是通過對一個稱為敏感變量表的事件驅動來實現的,下面會具體講到。 always 語句的基本格式是:
always @(敏感事件)begin
程序語句
end
always 是“一直、總是”的意思, @后面跟著事件。整個 always 的意思是:當敏感事件的條件滿足時,就執行一次“程序語句”。敏感事件每滿足一次,就執行“程序語句”一次。 (敏感事件中的敏感條件出現變化時,執行always條件循環語句中的內容)
這段程序的意思是: 當信號 a 或者信號 b 或者信號 d 發生變化時,就執行一次下面語句。在執行該段語句時,首先判斷信號 sel 是否為 0,如果為 0,則執行第 3 行代碼。 如果 sel 不為 0,則執行第 5 行代碼。需要強調的是, a、 b、 c 任意一個發生變化一次, 2 行至 5 行也只執行一次,不會執行第二次。
此處需要注意,僅僅 sel 這個信號發生變化是不會執行第 2 行到 5 行代碼的, 通常這并不符合設計者的想法。例如,一般設計者的想法是: 當 sel 為 0 時 c 的結果是 a+b;當 sel 不為 0 時 c 的結果是 a+d。但如果觸發條件沒有發生改變, 雖然 sel 由 0 變 1, 但此時 c 的結果仍是 a+b。 因此, 這并不是一個規范的設計思維。
因此,按照設計者的想法重新對代碼進行設計:當信號 a 或者信號 b 或者信號 d 或者信號 sel發生變化時,就執行 2 行至 5 行。這樣就可以確保 sel 信號值為 0 時, c 的結果一定為 a+b, 當 sel 不為 0 時, c 的結果一定是 a+d。 因此要在敏感列表中加入 sel, 其代碼如下所示。
當敏感信號非常多時很容易就會把敏感信號遺漏,為避免這種情況可以用“ * ”來代替。這個“ *”是指“程序語句”中所有的條件信號, 即 a、 b、 d、 sel(不包括 c) , 也推薦這種寫法,其具體代碼如下所示。
這種條件信號變化結果立即變化的 always 語句被稱為“組合邏輯”。
上述代碼敏感列表是**“ posedge clk”,其中 posedge 表示上升沿**。也就是說, 當 clk 由 0 變成1 的瞬間執行一次程序代碼,即第 2 至 5 行, 其他時刻 c 的值保持不變。要特別強調的是: 如果 clk沒有由 0 變成 1,那么即使 a、 b、 d、 sel 發生變化, c 的值也是不變的。
可以看到上述代碼的敏感列表是“ negedge clk”,其中 negedg 表示下降沿。也就是說,當 clk 由 1 變成 0 的瞬間執行一次程序代碼,即第 2 至 5 行, 其他時刻 c 的值保持不變。要特別強調的是,如果 clk 沒有由 1 變成 0,那么即使 a、 b、 d、 sel 發生變化, c 的值也是不變的。
上述代碼的敏感列表是“ posedge clk or negedge rst_n”,也就是說,當 clk 由 0 變成 1 的瞬間,或者 rst_n 由 1 變化 0 的瞬間,執行一次程序代碼,即第 2 至 8 行, 其他時刻 c 的值保持不變。這種信號邊沿觸發,即信號上升沿或者下降沿才變化的 always, 被稱為“時序邏輯”, 此時信號 clk 是時鐘。注意: 識別信號是不是時鐘不是看名稱,而是看這個信號放在哪里,只有放在敏感列表并且是邊沿觸發的才是時鐘。而信號 rst_n 是復位信號, 同樣也不是看名字來判斷,而是放在敏感列表中且同樣邊沿觸發,更關鍵的是“程序語句”首先判斷了 rst_n 的值, 這表示 rst_n 優先級最高,一般都是用于復位。
設計時需要注意以下幾點:
1、*組合邏輯的 always 語句中敏感變量必須寫全,或者用“ ”代替。
2、組合邏輯器件的賦值采用阻塞賦值“ =, 時序邏輯器件的賦值語句采用非阻塞賦值“ <=”,
具體原因見“阻塞賦值和非阻塞賦值”一節內容。
5.2 數字進制
5.2.1 數字表示方式
在 Verilog 中的數字表示方式,最常用的格式是: <位寬>’<基數><數值>,如 4’b1011。位寬:描述常量所含位數的十進制整數,是可選項。例如 4’b1011 中的 4 就是位寬, 通俗理解就是 4 根線。如果沒有這一項可以通過常量的值進行推斷。例如’b1011 可知位寬是 4,而’b10010 可推斷出位寬為 5。
基數:表示數值是多少進制。可以是 b, B, d, D, o, O, h 或者 H,分別表示二進制、十進制、八進制和十六進制。如果沒有此項,則缺省默認為十進制數。例如,二進制的 4’b1011 可以寫成十進制的 4’d11,也可以寫成十六進制的 4’hb 或者八進制的 4’o13,還可以不寫基數直接寫成 11。 綜上所述,只要二進數相同, 無論寫成十進制、八進制和十六進制都是同樣的數字。
數值:是由基數所決定的表示常量真實值的一串 ASCII 碼。如果基數定義為 b 或 B,數值可以是 0, 1, x, X, z 或 Z。如果基數定義為 o 或 O,數值可以是 2, 3, 4, 5, 6, 7。如果基數定義為h 或 H,數值可以是 8, 9, a, b, c, d, e, f, A, B, C, D, E, F。對于基數為 d 或者 D 的情況,數值符可以是任意的十進制數: 0 到 9, 但不可以是 x 或 z。例如, 4’b12 是錯誤的,因為 b 表示二進制,數值只能是 0、 1、 x 或者 z,不包含 2。 32’h12 等同于 32’h00000012, 即數值未寫完整時,高位補 0。
5.2.2 二進制是基礎
在數字電路中如果芯片 A 給芯片 B 傳遞數據,例如傳遞 0 或者 1 信息,可以將芯片 A 和芯片 B通過一個管腳進行相連,然后由芯片 A 控制該管腳輸出為高電平或者低電平,通過高低電平來表示 0和 1。芯片 B 檢測到該管腳為低電平時,表示收到 0, 芯片 B 檢測到該管腳為高電平時,表示收到 1。
反之, 如果用低電平表示收到 1,用高電平表示收到 0 可不可以呢?當然可以,只要芯片 A 和芯片 B 事先協定, 芯片 A 要發數字 1 時會將該管腳置為低電平。芯片 B 檢測到該管腳為低電平, 表示收到了數字 1,通信完成。
一個管腳擁有高低電平兩種狀態,可以分別表示數字 0 和 1 的兩種情況。如果芯片 A 要發數字0、 1、 2、 3 給芯片 B 又要如何操作呢?
可以讓芯片 A 和芯片 B 連接兩根管腳,即兩條線: a 和 b。當兩條線都為低電平時,表示發送數字 0;當 a 為高電平 b 為低電平時,表示發送數字 1;當 a 為低電平 b 為高電平時,表示發送數字 2;當兩條線都是高電平時,表示發送數字 3。
按照同樣的道理,芯片 A 要發送數據 4, 5, 6, 7 給芯片 B 時,只要再添加一條線就可以了。三根線一共有 8 種狀態,可以表示 8 個數字。綜上所述,線的不同電平狀態可以表示不同的含義, 有多少種不同狀態就可以表示多少個數字。
下面來思考一下如果芯片 A 要發送+1, -1, 0, +2 等數字給芯片 B,這里的正負又該如何表示呢?參考前面的思路, 線的高低電平表示的含義是由芯片雙方向事先約定好的, 既然如此則可以單用一根線來表示符號,例如低電平表示正數,高電平表示負數。
上圖所示的三根線中用線 c 表示正負, 其中 0 表示正數, 1 表示負數。用線 a 和線 b 表示數值,以 3’b111 為例,其可以解釋為十進制數 7,也可以解釋為有符號數原碼“ -3”,也可以解釋為有符號數補碼“-1”, 如何解釋取決于工程師對二進制數的定義。只要該定義不影響到電路之間的通信就不會發生問題。 因此數字中的“ 0”和“1”不僅可以表示字面上的數值含義,也可以表示其他意義,如正負符號等。同樣的道理,在數字電路中二進制數是八進制、十進制、十六進制、有符號數、無符號數、小數等其他數制的根本。在 FPGA 設計中,不清楚小數、有符號數的計算方法的最根本原因是不清楚這些數據所對應的二進制值, 只要理解了對應的二進制值,很多問題都可以解決。
下面通過例子讓同學們更好的理解這一概念, 很多初學者經常問, FPGA 中如何實現小數計算呢?以“0.5+0.25” 為例, 眾所周知 0.5+0.25 的結果為 0.75, 可以考慮 0.5、 0.25 和 0.75 用二進制該如何表示? 具體表示方法取決于工程師的做法,因為這種表示方法有很多種,例如定點小數,浮點小數,甚至如前面所討論,用幾根線自行來定義,只要能正常通信,那就沒有問題。假設某工程師用三根線自行定義了二進制值所表示的小數值,如下表所示。
二進制值 定義 二進制值 定義
3’b000 0.1 3’b100 0.25
3’b001 0.5 3’b101 0.3
3’b010 0.75 3’b110 0.8
3’b011 0.2 3’b111 0
為了說明二進制值的意義是可以隨便定義的,數字順序為亂序。 那為什么只有這幾種小數呢?這是因為假定中的系統就只有這幾種數字,如果想表示更多數字增加線的數量就可以了。完成上面定義之后,要實現“ 0.5+0.25”就很容易了,其實就是 3’b001 和 3’b100“相加”,期望得到 3’b010。 但是在該表中直接使用 3’b001 + 3’b100,結果為“101”, 這不是想要的結果,此時可以將代碼寫為:
當然,這只是其中一種寫法, 只要能實現所對應的功能且結果正確,任意寫法都可以。
此處可能存在疑慮, 0.1+0.8 應該為 0.9,但上面的表格中并沒有 0.9 的表示。這其實是設計者定義的這個表格有缺陷,或者設計者認為不會出現這一情況。 此處要表達的是: 只要定義好對應的二進制數,很多功能都是很容易設計的。
當然,實際的工程中通常會遵守約定成俗的做法,沒必要另辟蹊徑。例如, 下表是常用的定點小數的定義:
二進制值 定義 二進制值 定義
3’b000 0.0 3’b100 0.5
3’b001 0.125 3’b101 0.625
3’b010 0.25 3’b110 0.75
3’b011 0.3725 3’b111 0.8725
此時如果要實現 0+0.5=0.5,也就是 3’b000 和 3’b100 相加,期望能得到 3’b100。 可以發現直接用二進制 3’b000+3’b100 就能得到 3’b100。同樣地, 要實現 0.125+0.75=0.8725,也就是 3’b001 和 3’b110 相加,期望能得到 3’b111。 可以發現直接用二進制 3’b001+3’b110 就能得到 3’b111。
如果要實現 0.5+0.75=1.25 這一計算, 可以看出此時 1.25 已經超出了表示范圍, 可以通過增加信號位寬或只表示小數位的做法解決這一問題。如果只是表示小數位則結果就是 0.25,即 3’b100 和3’b110 相加,期望得到 3’b010。不難發現 3’b100 + 3’b110 = 4’b1010,用 3 位表示就是 3’b010,也就是 0.25。綜上所述可以看出,定點小數的計算并不復雜,定義好定點小數與二進制值之間的關系后直接進行計算即可。
5.2.3 不定態
前文中講過數字電路只有高電平和低電平,分別表示 1 和 0。但代碼中經常能看到 x 和 z,如 1’bx, 1’bz。那么這個 x 和 z 是什么電平呢?答案是并沒有實際的電平來對應兩者。 x 和 z 更多地是用來表示設計者的意圖或者用于仿真目的, 旨在告訴仿真器和綜合器如何解釋這段代碼。
X 態,稱之為不定態, 其常用于判斷條件, 從而告訴綜合工具設計者不關心它的電平是多少,是0 還是 1 都可以。
上面的例子中可以看出判斷條件是 din== 4’b10x0, 該條件等價于 din== 4’b1000||din==4’b1010,其中“||”是“或”符號。
然而在設計中直接寫成 din== 4’b1000||din == 4’b1010 要好于寫成“din == 4’b10x0”, 因為這樣的寫法更加直接和簡單明了。
在仿真的過程中有些信號產生了不定態,那么設計者就要認真分析這個不定態是不是合理的。如果真的不關心它是 0 還是 1,那么可以不解決。但建議所有信號都不應該處于不定態, 寫清楚其是 0還是 1,不要給設計添加“思考”的麻煩。
5.2.4 高阻態
Z 態,一般稱之為高阻態, 表示設計者不驅動這個信號(既不給 0 也不給 1),通常用于三態門接口當中。
上圖就是三態總線的應用案例, 圖中的連接總線對于 CPU 和 FPGA 來說既為輸入又為輸出,是雙向接口。一般的硬件電路中會將該線接上一個上拉電阻(弱上拉)或下拉電阻(弱下拉)。
當 CPU 和 FPGA 都不驅動該總線時, A 點保持為高電平。當 FPGA 不驅動該總線, CPU 驅動該總線時, A 點的值就由 CPU 決定。當 CPU 不驅動該總線, FPGA 驅動該總線時, A 點的值就由 FPGA 決定。 但 FPGA 和 CPU 不能同時驅動該總線,否則 A 的電平就不確定了, 通常 FPGA 和 CPU何時驅動總線是按事先協商的協議進行工作。
上圖是典型的 I2C 的時序。 I2C 的總線 SDA 就是一個三態信號。 I2C 協議已規定好上面的時間中,哪段時間是由主設備驅動,哪段時間是由從設備驅動,雙方都要遵守協議,不能存在同時驅動的情況。那么 FPGA 在設計中是如何做到“不驅動”這一行為呢?這是因為 FPGA 內部有三態門。
三態門是一個硬件,上圖是它的典型結構。三態門有四個接口,如上圖所示的寫使能 wr_en、寫數據 wr_data、讀數據 rd_data 以及與外面器件相連的三態信號 data。
需要注意的是寫使能信號,當該信號有效時三態門會將 wr_data 的值賦給三態線 data,此時 data 的值由 wr_data 決定,當 wr_data 為 0 時 data 值為 0;當 wr_data 為 1 時 data 值為 1。 而當寫使能信號無效時,則不論 wr_data 值是多少都不會對外面的 data 值有影響,也就是不驅動。
在 Verilog 中以上功能是通過如下代碼實現的:
當綜合器看到這兩行代碼則知道要綜合成三態門了,高阻 z 的作用正在于此。 此外可以注意到硬件上用三態線是為了減少管腳,而在 FPGA 內部沒有必要減少連線,所以使用三態信號是沒有意義的。 因此, 建議各位在進行設計時不要在 FPGA 內部使用高阻態“ z”, 因為沒有必要給自己添加“思考”的麻煩。當然, 如果設計中使用了高阻態也不會報錯,也可以實現功能。
總的來說高阻態“z”是表示“不驅動總線”這個行為,實際上數字電路就是高電平或者低電平,不存在其他電平的情況。
5.3 算術運算符
算術運算符包括加法“ +”、減法“-”、乘法“*”、除法“ / ”和求余“ % ”, 其中常用的算術運算符主要有 :加法“ + ”,減法“-”和乘法“ *”。
注意,常用的運算中不包括除法和求余運算符, 這是由于除法和求余不是簡單的門邏輯搭建起來的, 其所對應的硬件電路比較大。加減是最簡單的運算,而乘法可以拆解成多個加法運算, 因此加減法、乘法所對應的電路都比較小。而除法就不同了, 同學們可以回想一下除法的步驟, 其涉及到多次乘法、移位、加減法,所以除法對應的電路是復雜的,這也同時要求設計師在進行 Verilog 設計時要慎用除法。
5.3.1 加法運算符
首先學習加法運算符, 在 Verilog 代碼中可以直接使用符號“+”:
其電路示意圖如下所示:
綜合器可以識別加法運算符并將其轉成如上圖所示的電路。二進制的加法運算和十進制的加法相似,十進制是逢十進一,而二進制是逢二進一。二進制加法的基本運算如下:
5.3.2 減法運算符
減法運算符,在 Verilog 代碼中可以直接使用符號“-”:
其電路示意圖如下所示:
綜合器可以識別減法運算符并將其直接轉成上圖所示的電路。
二進制的減法運算和十進制的減法運算是相似的,也有借位的概念。十進制是借一當十,二進制則是借一當二。 1 位減法基本運算如下:
5.3.3 乘法運算符
————————————————
版權聲明:本文為CSDN博主「原來如此呀」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/Royalic/article/details/121196365
-
FPGA
+關注
關注
1626文章
21675瀏覽量
601961 -
組合邏輯
+關注
關注
0文章
47瀏覽量
10033 -
功能描述
+關注
關注
0文章
2瀏覽量
8392
發布評論請先 登錄
相關推薦
評論