FPGA筆記之verilog語言(基礎語法篇) 寫在前面: verilogHDL語言是面向硬件的語言,換句話說,就是用語言的形式來描述硬件線路。因此與C語言等軟件語言不同,如果想要在實際的電路中實現(xiàn),那么在進行verilog語言編寫時,就需要提前有個硬件電路的構(gòu)思和想法,同時,在編寫verilog語言時,應該采用可綜合的語句和結(jié)構(gòu)。 1. verilog 的基礎結(jié)構(gòu) 1.1 verilog設計的基本單元——module 在數(shù)字電路中,我們常常把一些復雜的電路或者具有特定功能的電路封裝起來作為一個模塊使用。以后在運用這種模塊化的封裝時,我們只需要知道:1.模塊的輸入是什么;2.模塊的輸出是什么;3.什么樣的輸入對應什么樣的輸出。而中間輸入是經(jīng)過什么樣的電路轉(zhuǎn)化為輸出就不是我們在使用時需要特別重視的問題。當很多個這樣的模塊相互組合,就能構(gòu)成一個系統(tǒng),解決一些復雜的問題。verilog語言的基礎結(jié)構(gòu)就是基于這種思想。verilog中最基本的模塊是module,就可以看做是一個封裝好的模塊,我們用verilog來寫很多個基本模塊,然后再用verilog描述多個模塊之間的接線方式等,將多個模塊組合得到一個系統(tǒng)。 那么一個module應該具有哪些要素呢?首先對于一個module,我們應該設計好其各個I/O,以及每個I/O的性質(zhì),用于與模塊外部的信號相聯(lián)系,讓使用者知道如何連線。其次,作為開發(fā)者,我們需要自己設計模塊內(nèi)部的線路來實現(xiàn)所需要的功能。因此需要對模塊內(nèi)部出現(xiàn)的變量進行聲明,同時通過語句、代碼塊等實現(xiàn)模塊的功能。綜上所述,我們把一個module分成以下五個部分: 模塊名 端口定義 I/O說明 內(nèi)部信號的聲明 模塊功能實現(xiàn) 例: ///////////////////////////////////////////////////////////////// //module 模塊名 (端口1,端口2,端口3); //I/O說明 //內(nèi)部信號說明 //模塊功能實現(xiàn) //endmodule //////////////////////////////////////////////////////////////// //////////////////////////////////// //module 模塊名 (端口1,端口2,端口3); //////////////////////////////////// module FreDevider ( Clock, rst, Clkout ); //////////////////////////////////// //I/O說明 //////////////////////////////////// input Clock; input rst; output Clkout; //////////////////////////////////// //內(nèi)部信號說明 //////////////////////////////////// reg Clkout; //////////////////////////////////// //模塊功能實現(xiàn) //////////////////////////////////// always@(posedge Clock or posedge rst) begin if(rst) Clkout<=0; else Clkout<=~Clkout; end //////////////////////////////////// //endmodule //////////////////////////////////// endmodule 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 1.2 module的使用 當我們已經(jīng)寫好了一種類型的module,那我們在使用的時候,就可以直接調(diào)用module。使用方法是 模塊名+實例名+端口聲明+信號聲明。一個模塊可以定義多個實例。 例如:使用上述已經(jīng)寫好的module //已經(jīng)定義過一個叫FreDevider的module FreDevider uut1( //模塊名:FreDevider 實例名:uut1 .Clock(clock_signal), // 端口聲明: .端口名 信號聲明: (信號名) .rst(rst_signal), .Clkout(clkout_signal) ); 1 2 3 4 5 6 這里涉及到了之前module定義和變量聲明等問題,首先module的定義決定了模塊名和端口名,其次module中的信號的處理方式?jīng)Q定了這里的信號名是reg還是wire型。信號可以看做是一個大的模塊的輸入輸出或中間變量。 1.3 I/O的說明 I/O的類型共有3類:input,output,inout 前兩個比較好理解,分別是輸入和輸出 而inout則是雙向端口,既可以當做輸入,也可以當做輸出,這種雙向端口具有雙向傳輸?shù)哪芰,比較節(jié)約端口,同時適合作為總線等需要但是要保證在某一時刻,其只進行某一方向的傳輸,也就是說,應避免兩個方向由需要傳輸這種情況。因此在實際工程實例中,我們在比較底層的模塊編寫時,是比較少的用inout端口,而是在較高一層的模塊,通過控制信號來實現(xiàn)inout端口的使用。同時inout端口在實際使用時還有許多問題,對于初學者,先理解整個架構(gòu),再對細節(jié)補充,所以這部分內(nèi)容放在高級語法篇講述。 I/O說明的形式: I/O的說明可以放在端口定義后,也可以在端口定義時同時說明。例: module FreDevider ( input Clock, input rst, output Clkout ); 1 2 3 4 5 同時,也可以在說明I/O口的時候,同時說明信號的位數(shù) 例: module FreDevider ( input Clock, input rst, input [15:0] x; //16位輸入 output [31:0] y; //32位輸出 ); 1 2 3 4 5 6 7 1.3 內(nèi)部信號的聲明 如果說端口的定義連接了模塊與外部世界,是一個模塊世界中可進可出的若干個大門,那么信號就是一個個行人。要實現(xiàn)模塊的功能,就要對各個信號進行處理和變換。有些信號是外部輸入的,有些信號是要輸出給外部的,還有些信號是信號變換的中間變量。就像行人有些是外面進來的,有些是要去往外面世界的,有些是住在模塊世界里的。這些參與到模塊功能實現(xiàn)的所有信號,都要對其信號的屬性進行規(guī)定,就像每個行人都有各自的身份?偠灾,分清楚端口和信號的區(qū)別,端口是固定的,信號是變化的。端口是門,信號是人。 信號的屬性有reg、wire、paramater三種種,其中reg又稱為寄存器型,wire又稱為線性,每個信號都要定義其屬性,但是對于模塊的輸入信號,其屬性必須不是reg型,一般為wire型。又因為對于沒有聲明的信號,其默認為wire型,因此在定義時,我們只需要定義輸出信號的類型和中間變量的類型即可。 reg a; wire b; 1 2 1.4 模塊功能的實現(xiàn) 對于一個硬件電路來說,已經(jīng)有了模塊名,端口和端口名,信號與信號屬性,剩下的就是通過硬件電路來實現(xiàn)各個信號之間的邏輯功能。這比部分的知識就和我們在大學時期學的數(shù)電的知識聯(lián)系緊密,通?梢苑譃闆]有時間,只有邏輯轉(zhuǎn)換的邏輯電路和有時間和狀態(tài)轉(zhuǎn)換的時序邏輯電路兩種形式,再加上通過調(diào)用已經(jīng)模塊化的實例元件來參與更高一級的模塊設計。所以在一個模塊功能的實現(xiàn)方法中,通常有三種類型: 用assign聲明語句 assign語句用于驅(qū)動線網(wǎng)型的變量,聲明語句右邊表達式的變量是敏感信號,當右邊的值發(fā)生改變時,立即計算左邊的結(jié)果,并進行表達。也就是說,當輸入變化時,輸出也隨之變化。這種特性就像是數(shù)字電路里的組合邏輯電路。 assign a_not=~a; assign c=a&b; 1 2 采用實例化的元件 采用實例的元件方法已經(jīng)在前面講過,除了采用module的實例化元件以外,還可以采用IP核的形式來實現(xiàn)。 采用always語句塊 always語句塊既能描述組合邏輯電路,又能描述時序邏輯電路。與assign不同的是,always語句后面的觸發(fā)條件是持續(xù)敏感,也就是每時每刻都在執(zhí)行或者判斷的。后面也會更加詳細的區(qū)別assign和always語句塊。 //生成時鐘信號 always #5 clk=~clk; //組合邏輯電路:二選一多路器 reg c_out; always @(a_in or b_in or sel) if(sel) c_out=a_in; else c_out=b_in; //時序邏輯電路:二分頻模塊 reg d; always @(posedge clk or posedge rst) begin if(rst) d<=1'b0; else d=~d; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1.5 第一章學習筆記 verilog是一門描述硬件電路的語言,在語言結(jié)構(gòu)上和C語言有相同的地方,所以學習起來比較容易,但是verilog終究還是要最終用硬件的方法去實現(xiàn),因此在編程時,不能空想內(nèi)部的邏輯,而是通過電路圖、時序圖等方式,將功能先用硬件的方法表示出來,然后再用軟件的語言來實現(xiàn)。 verilog的使用離不開對數(shù)字電路基礎知識的掌握,所以在進行verilog的學習的同時,應該建立在對數(shù)字電路基礎知識的學習之上。語言是描述思想的工具,思想才是解決問題的關鍵。 … 2. verilog 的數(shù)據(jù)類型 verilog的數(shù)據(jù)類型主要分為四類,整數(shù)型、實數(shù)型、字符串型、參數(shù)型,這四種類型都可以在編程中出現(xiàn),但是在實際硬件層面綜合時,卻并不能保證我們所寫的數(shù)據(jù)類型能夠被表達。因為在實際的電路中,無論是哪種類型的數(shù)據(jù),最后都要轉(zhuǎn)化為1、0、X、Z(X、Z分別是未知態(tài)和高阻態(tài))這四種信號狀態(tài)。因此掌握verilog的數(shù)據(jù)類型,最根本的就是掌握各種數(shù)據(jù)類型在實際電路中的表達。 2.1 整數(shù)型 2.1.1 數(shù)制 在verilog中,整數(shù)有四種數(shù)制,分別是 十進制:代號d/D 二進制:代號b/B 八進制:代號o/O 十六進制:代號h/H 一個整數(shù)對于進制的不同表達,只是數(shù)的表達形式不同,但是數(shù)字本身代表的數(shù)學含義是相同的。而在硬件電路中,所有的數(shù)字都是通過二進制來進行表達的。因此在編程過程中,對于數(shù)字的數(shù)學表示,我們可以采用不同的進制,但是對于數(shù)字的物理實現(xiàn),我們一定是采用二進制的方式來進行硬件上的實現(xiàn)。因此在實際編程過程中出現(xiàn)的數(shù)字,我們應該從二進制的表達中理解其變化的規(guī)律。 這種思想可以在verilog的數(shù)字表達中得到體現(xiàn),一般來說,我們通過以下的方式來表示一個整數(shù): <位寬><′><進制><數(shù)字> <位寬><'><進制><數(shù)字> <位寬>< ′ ><進制><數(shù)字> 舉例說明: 8'd23 //位寬8 十進制 數(shù)字23 8'b00010111 //位寬8 二進制 數(shù)字23 8'o27 //位寬8 八進制 數(shù)字23 8'h17 //位寬8 十六進制 數(shù)字23 1 2 3 4 對于任何一個數(shù)字,無論其后面是什么進制,什么數(shù)字,最后都要轉(zhuǎn)化為指定位寬的二進制數(shù)字來表達。因此有可能在進制表達時,出現(xiàn)實際的二進制數(shù)字和我們編程所寫的二進制數(shù)字不同。或者其他非標準的表達方法。下面我們說明幾種常見的情況: 位寬大于實際數(shù)字二進制的寬度 位寬大于實際二進制的寬度時,有四種可能: 1.二進制數(shù)字最高位為1,高位部分用0補全 2.二進制數(shù)字最高位為0,高位部分用0補全 3.二進制數(shù)字最高位為X,高位部分用X補全 4.二進制數(shù)字最高位為Z,高位部分用Z補全 例如: 6'b101 //實際上是 000101 6'b001 //實際上是 000001 6'bx11 //實際上是 xxxx11 6'bz11 //實際上是 zzzz11 1 2 3 4 位寬小于實際二進制的寬度 當位寬小于實際二進制寬度時,一般情況下是將超過的位數(shù)舍去,只保留給定位寬內(nèi)的數(shù),例如: 4'b101011 //實際上是 1011 1 位寬省略不寫 一般情況下,位寬省略不寫,默認為是32位寬的數(shù),或者根據(jù)操作系統(tǒng)和編程軟件的設定來定義。例如: 'b10111 //位寬為32的二進制數(shù) 代表數(shù)字23 1 數(shù)制位寬都省略不寫 這種情況下,默認為數(shù)字是32位寬的十進制數(shù)。例如 23 //位寬為32的十進制數(shù) 代表數(shù)字23 1 下劃線表達法 當需要表達的數(shù)字位數(shù)較多時,為了書寫和觀察方便,常常使用下劃線符號來輔助表達。采用下劃線將相鄰若干個數(shù)字分隔開,使其表達更清晰。但是注意下劃線只能出現(xiàn)在數(shù)字中,不能出現(xiàn)在位寬和進制中,并且數(shù)字的第一位不能是下劃線。例如: 16'b0100_1011_1101_0110 //正確 16'b_1110_1101_0101_0001 //錯誤 1 2 2.1.2 負數(shù) 負數(shù)在verilog中的表達是很簡單的,直接在數(shù)字的前面添加符號就表示負數(shù)。但是在實際的二進制編碼中,負數(shù)是通過其正數(shù)的補碼的形式進行實現(xiàn)的。 -8'd23 //就是-23,但是在二進制代碼中 是用8'b11101001來表示 1 此時對于一個8位的二進制數(shù),其第一位為符號位,后7位才為有效的數(shù)字位。 由此可見,一個二進制,當首位為1時,有可能表示的是一個正數(shù),也有可能表示的是一個負數(shù),那么在實際硬件電路中,如何區(qū)分這兩種情況呢? 一般來說,對于硬件中的信號,硬件處理時是默認為正整數(shù)的,因此當我們輸入一個負值,實際上產(chǎn)生的是我們輸入的值的正數(shù)部分的補碼所表示的正值(這里有點繞,距離來說,我輸入-8’d23,得到的是23的補碼8’b11101001,這個補碼被硬件默認是正數(shù),即8’d233)。那么我們在進行二進制的數(shù)據(jù)的變換時,就應該回歸其硬件中的表達,然后在此基礎上通過自己的程序來實現(xiàn)當為負數(shù)時的轉(zhuǎn)化。 例如:我之前寫過的粗插補算法 if(x_rough[15]==0) //當插補數(shù)據(jù)為正數(shù)時,將粗插補數(shù)據(jù)進行四分,并將余數(shù)添加到第一個精插補結(jié)果中 begin lx[1:0]=x_rough[1:0]; x4=(x_rough>>2); x3=(x_rough>>2); x2=(x_rough>>2); x1=((x_rough>>2)+lx); end else //當插補數(shù)據(jù)為負數(shù)時,將插補數(shù)據(jù)求補碼,進行四分等操作后,再將結(jié)果求補碼得到負數(shù)(對絕對值進行操作) begin x_complement[15:0]=(~(x_rough[15:0]-8'b0000000000000001)); lx[1:0]=x_complement[1:0]; x4=((~(x_complement>>2))+8'b0000000000000001); x3=((~(x_complement>>2))+8'b0000000000000001); x2=((~(x_complement>>2))+8'b0000000000000001); x1=((~((x_complement>>2)+lx))+8'b0000000000000001); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 這里就是根據(jù)最高位是1和0來決定進行什么樣的數(shù)據(jù)處理?偠灾,我們要明白,數(shù)據(jù)在硬件中的儲存是默認為正整數(shù)的二進制儲存方式,但是如何取理解這個數(shù),卻是使用這個數(shù)據(jù)的人自己規(guī)定的。這種思想不僅在處理負數(shù)時是這樣的,在碰到小數(shù)、字符等都是這么一個思想。0和1的世界是死的,但是解讀他們的規(guī)則卻是活的。 2.1.3 X和Z 我們都知道0和1代表的含義,但是對于verilog語言來說,還有X和Z兩種獨立于0和1之外的狀態(tài)。這兩種狀態(tài)分別是未知態(tài)和高阻態(tài)。 未知態(tài)X是指,無法確定此時信號的狀態(tài)是1還是0,但是能確定信號是有狀態(tài)的,不是1就是0,且這個狀態(tài)是能夠影響到與其相連的后續(xù)電路的,當我們用電表測量時,其值可能是1,可能是0,取決于被測當時硬件電路的當前狀態(tài)。而高阻態(tài)Z是指,當前的信號狀態(tài)既不是1,也不是0,而是沒有狀態(tài),或者可以認為是斷開,即此時信號的狀態(tài)已經(jīng)無法再影響到后續(xù)的電路。 而在實際電路中,某一時刻時只有1、0、高阻態(tài)三種狀態(tài)。 盡管X和Z表示的狀態(tài)不是傳統(tǒng)的1和0,但是X和Z也能參與到二進制的邏輯運算中來。在邏輯運算中,X和Z滿足如下的規(guī)律: 0 && X = 0 ; 1 && X = X ; 0 || X = X ; 1 || X = 1 ; 0 && Z = 0 ; 1 && Z = X ; 0 || Z = X ; 1 || Z = 1 ; 1 2 3 4 5 6 7 8 9 2.2 實數(shù)型和字符型 實數(shù)型和字符型放在一起,因為這兩種數(shù)據(jù)的表達有相似的地方。那就是實數(shù)型或者字符型的數(shù)據(jù)可以在verilog語言中出現(xiàn),但是卻不能通過硬件表達出來。而是常常出現(xiàn)在命令、顯示等非硬件參與的操作中。這與上面的整數(shù)的表達方式和意義是不同的。 2.2.1 實數(shù) 實數(shù)可以用十進制來表示,也可以用科學計數(shù)法來表示,例如: 12 //表示12 23e-3 //表示0.0023 23E4 //表示230000 1 2 3 雖然我們可以在verilog中寫出這些數(shù)據(jù),但是并不是所有我們寫出來的數(shù)據(jù)都能在硬件中表達。在實際的仿真過程中,所有的科學計數(shù)的表達例如23e-3或者23E4,在硬件中都是0,而硬件中也不能直接表達小數(shù)。也就是直接在verilog中寫出來的小數(shù),會被硬件識別為0。 但是小數(shù)或者指數(shù)的表達,可以通過使用者自己規(guī)定來獲得。例如我們常聽說的浮點數(shù)等,就是通過一些算法轉(zhuǎn)換,來將二進制的代碼表達成帶有小數(shù)或者指數(shù)的數(shù)字。 3. verilog 的變量類型 4. verilog 的運算符 5. verilog 的語句塊 點贊 10 ———————————————— 版權聲明:本文為CSDN博主「Dobolong」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。 原文鏈接:https://blog.csdn.net/DBLLLLLLLL/article/details/84306098 |