3.2 數字3.2.1數字表示方式 在Verilog中的數字表示方式,最常用的格式是:<位寬>’<基數><數值>,如,4’b1011。 位寬:描述常量所含位數的十進(jìn)制整數,是可選項。4’b1011中的4就是位寬。通俗理解就是4根線(xiàn)。如果沒(méi)有這一項,可以從常量的值推斷出。例如’b1011可知位寬是4;’b10010可推斷出位寬為5。 基數:表示數值是多少進(jìn)制?梢允莃,B,d,D,o,O,h或者H,分別表示二進(jìn)制、十進(jìn)制、八進(jìn)制和十六進(jìn)制。如果沒(méi)有此項,則缺省默認為十進(jìn)制數。 例如,二進(jìn)制的4’b1011,可以寫(xiě)成十進(jìn)制的4’d11,也可以寫(xiě)成十六進(jìn)制的4’hb,或者八進(jìn)制的4’o13,還可以不寫(xiě)基數直接寫(xiě)成11。不管怎么樣,只要二進(jìn)數相同,寫(xiě)成十進(jìn)制、八進(jìn)制和十六進(jìn)制,都是同樣的數字。 數值:是由基數所決定的表示常量真實(shí)值的一串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的情況,數值符可以是任意的十進(jìn)制數:0到9。但不可以是x或z。 例如,4’b12是錯誤的,因為b表示二進(jìn)制,數值只能是0、1、x或者z,不有是2。 32’h12等同于32’h00000012,也就是數值未寫(xiě)完整時(shí),高位補0。 3.2.2二進(jìn)制是基礎 在數字電路中,如果芯片A給芯片B傳遞數據,例如傳遞0或者1信息?梢詫⑿酒珹和芯片B,通過(guò)一個(gè)管腳進(jìn)行相連。然后由芯片A控制該管腳為高電平或者低電平,通過(guò)高低電平來(lái)表示0和1。例如,芯片B檢測到該管腳為低電平時(shí),表示收到0;當芯片B檢測到該管腳為高電平時(shí),表示收到1。 如果用低電平表示收到1,用高電平表示收到0,這可不可以呢?當然可以,只要芯片A和芯片B事先協(xié)定。芯片A要發(fā)數字1時(shí),會(huì )將該管腳置為低電平。芯片B檢測到該管腳為低電平,知道收到了數字1,通信完成。 一個(gè)管腳擁有高低電平兩種狀態(tài),可以分別表示數字0和1兩種情況。如果芯片A要發(fā)數字0、1、2、3給芯片B,那該怎么辦呢? 可以讓芯片A和芯片B連接兩根管腳,即兩條線(xiàn),a和b。當兩條線(xiàn)都為低電平時(shí),表示發(fā)送數字0;當a為高電平,b為低電平時(shí),表示發(fā)送數字1;當a為低電平,b為高電平時(shí),表示發(fā)送數字2;當兩條線(xiàn)都是高電平時(shí),表示數字3。 按照同樣的道理,芯片A要發(fā)送數據4,5,6,7給芯片B的時(shí)候,只要再添加一條線(xiàn)就可以了。三根線(xiàn)一共有8種狀態(tài),可以表示8個(gè)數字。 綜上所述,我們可能通過(guò)線(xiàn)的不同電平狀態(tài),表示不同的含義。有多少個(gè)不同狀態(tài),就可以表示多少個(gè)數字。 如果芯片A要發(fā)送+1,-1,0,+2等數字給芯片B,這里有正負了,那又該如何表示呢?參考前面的思想,線(xiàn)的高低電平表示的含義,是由芯片雙方向事先約定好的。既然是這樣,那么我們拿一根線(xiàn)出來(lái),例如低電平表示正數,高電平表示負數。 上面就是三根線(xiàn),我們用線(xiàn)c表示正負,0表示正數,1表示負數。用線(xiàn)a和線(xiàn)b表示數值。 3’b111,可以解釋為十進(jìn)制數7,也可以解釋為有符號數原碼“-3”,也可以解釋為有符號數補碼“-1”,這取決于工程師對二進(jìn)制數的定義。只要這個(gè)定義不影響到電路之間的通信那就絕對不會(huì )有問(wèn)題。 所以,數字中的“0”和“1”不僅可以表示含義,也可以表示其他意義,如正負符號等。同樣的道理, 在數字電路中,二進(jìn)制數是其他如八進(jìn)制、十進(jìn)制、十六進(jìn)制、有符號數、無(wú)符號數、小數等的根本。在FPGA設計中,不清楚小數、有符號數的計算方法,最根本的原因是不清楚這些數據所對應的二進(jìn)制值。只要理解了它所對應的二進(jìn)制值,很多問(wèn)題都可以解決。 例如,有初學(xué)者經(jīng)常問(wèn),FPGA中如何實(shí)現小數計算,如“0.5+0.25”這個(gè)功能。 首先,眾所周知的,0.5+0.25的結果為0.75。 其次,我們可以考慮,0.5、0.25和0.75用二進(jìn)制該如何表示?這取決于工程師的做法,因為這種表示方法有很多種,例如定點(diǎn)小數,浮點(diǎn)小數,甚至如前面所討論,用幾根線(xiàn)自行來(lái)定義,只要能正常通信,那就絕對沒(méi)有問(wèn)題。 假設,某工程師用三根線(xiàn),自行定義了二進(jìn)制值所表示的小數值。 為了說(shuō)明二進(jìn)制值的意義是可以隨便定義的,我特意將數字順序打亂。當然,有讀者可能說(shuō)為什么只有這幾種小數呢?這是因為我假定本系統就只有這幾種數字,如果想表示更多數字,那就增加線(xiàn)就行了。 有了上面定義之后,要實(shí)現“0.5+0.25”就很容易了,其實(shí)就是3’b001和3’b100“相加”,期望得到3’b010。如果我們直接使用3’b001 + 3’b100,結果為“101”了,不是想要的結果。那怎么辦呢?可以這么寫(xiě): 當然,這是其中一種寫(xiě)法?傊,只要能實(shí)現所對應的功能,結果正確就可以。 有讀者問(wèn),按上面的表格0.1+0.8應該為0.9,但上面沒(méi)有0.9的表示。這個(gè)其實(shí)是設計者這個(gè)表格定義有缺陷,或者設計者認為不會(huì )出現這個(gè)情況吧?傊,筆者要表達的是,只要定義好所對應的二進(jìn)制數,很多功能是很容易設計的。 當然,實(shí)際的工程中,我們通常會(huì )遵守約定成俗的做法,沒(méi)必要自己搞得另類(lèi)。例如下面是常用的定點(diǎn)小數的定義。 現在要實(shí)現0+0.5=0.5,也就是3’b000和3’b100相加,期望能得到3’b100。我們發(fā)現直接用二進(jìn)制3’b000+3’b100就可以得到3’b100。 要實(shí)現0.125+0.75=0.8725,也就是3’b001和3’b110相加,期望能得到3’b111。我們發(fā)現直接用二進(jìn)制3’b001+3’b110就可以得到3’b111。 要0.5+0.75=1.25,這個(gè)1.25已經(jīng)超出了表示范圍,要不就增加信號位寬,要不只能表示小數位。如果只是表示小數位,那結果就是0.25。也就是3’b100和3’b110相加,期望得到3’b010。我們發(fā)現3’b100 + 3’b110 = 4’b1010,用3位表示就是3’b010,也就是0.25了。 綜上所述,對于定點(diǎn)小數的計算很簡(jiǎn)單,就是直接相加。 3.2.3不定態(tài) 前面講過(guò),數字電路只有高電平和低電平,分別表示1和0。但代碼中經(jīng)常能看到x和z,如1’bx,1’bz。那么這個(gè)x和z是什么電平呢?答案是沒(méi)有實(shí)際的電平來(lái)對應。這個(gè)x和z是更多地用來(lái)表示設計者的意圖或者用于仿真目的,告訴仿真器和綜合器怎么解釋這段代碼。 X態(tài),稱(chēng)之為不定態(tài),設計者常用于判斷條件,用于告訴綜合工具,設計者不關(guān)心它的電平是多少,是0還是1都可以。 上面的例子,條件是din==4’b10x0,這個(gè)條件等價(jià)于din==4’b1000||din==4’b1010,其中“||”是“或”符號。 明德?lián)P則建議,直接寫(xiě)成din==4’b1000||din==4’b1010,好于寫(xiě)成“din==4’b10x0”,直接簡(jiǎn)單明了。 仿真的時(shí)候,有些信號產(chǎn)生了不定態(tài),那么設計者就要認真分析,這個(gè)不定態(tài)是不是應該的。如果真的不關(guān)心它是0還是1,那么可以不解決。但明德?lián)P建議,所有信號都不應該處于不定態(tài),是0還是1,寫(xiě)清楚,不要給設計添加“思考”的麻煩。 3.2.4高阻態(tài) Z態(tài),一般稱(chēng)之為高阻態(tài),表示設計者不驅動(dòng)這個(gè)信號(既不給0也不給1),通常用于三態(tài)門(mén)接口當中。 上圖就是三態(tài)總線(xiàn)的應用案例。圖中的連接總線(xiàn)對于CPU和FPGA來(lái)說(shuō),既當作輸入又當作輸出,是雙向接口。一般的硬件電路中,會(huì )將該線(xiàn)接上一個(gè)上拉電阻(弱上拉)或下拉電阻(弱下拉)。 當CPU和FPGA都不驅動(dòng)該總線(xiàn)時(shí),A點(diǎn)保持為高電平。當FPGA不驅動(dòng)該總線(xiàn),CPU驅動(dòng)該總線(xiàn)時(shí),A點(diǎn)的值就由CPU決定。當CPU不驅動(dòng)該總線(xiàn),FPGA驅動(dòng)該總線(xiàn)時(shí),A點(diǎn)的值就由FPGA決定。FPGA和CPU不能同時(shí)驅動(dòng)該總線(xiàn),否則A的電平就不確定了。通常FPGA和CPU何時(shí)驅動(dòng)總線(xiàn),是按協(xié)議事先協(xié)商好的。 上圖是典型的I2C的時(shí)序。I2C的總線(xiàn)SDA就是一個(gè)三態(tài)信號。I2C協(xié)議已規定好上面的時(shí)間中,哪段時(shí)間是由主設備驅動(dòng),哪段時(shí)間是由從設備驅動(dòng),雙方都要遵守協(xié)議,不能存在同時(shí)驅動(dòng)的情況。 那么FPGA在設計中,是如何做到“不驅動(dòng)”這一行為呢?這是因為FPGA內部有三態(tài)門(mén)。 三態(tài)門(mén)是一個(gè)硬件,上圖是它的典型結構。三態(tài)門(mén)有四個(gè)接口,例如上圖中的寫(xiě)使能wr_en、寫(xiě)數據wr_data、讀數據rd_data和與外面器件相連的三態(tài)信號data。 注意寫(xiě)使能信號,當該信號有效時(shí),三態(tài)門(mén)會(huì )將wr_data的值賦給三態(tài)線(xiàn)data,此時(shí)data的值由wr_data決定,當wr_data為0時(shí),data值就為0;當wr_data為1時(shí),data值就為1。 當寫(xiě)使能信號無(wú)效時(shí),則不管wr_data值是多少,都不會(huì )對外面的data值有影響,也就是不驅動(dòng)。 那么在Verilog中,是通過(guò)如下兩行代碼來(lái)描述這一功能的。 綜合器看到這兩行代碼,就知道要綜合成三態(tài)門(mén)了。 這個(gè)高阻z的作用就在于這里。而且注意到,硬件上用三態(tài)線(xiàn)是為了減少管腳,而在FPGA內部沒(méi)有必要減少連線(xiàn),所以使用三態(tài)信號是沒(méi)有意義的。 也就是說(shuō),明德?lián)P的設計建議,FPGA內部不要使用高阻態(tài)“z”,沒(méi)有必要給自己添加“思考”的麻煩。當然,使用了也不會(huì )報錯,也能實(shí)現功能。 總結一點(diǎn),高阻態(tài)“z”是表示“不驅動(dòng)總線(xiàn)”這個(gè)行為,實(shí)際上數字電路就是高電平或者低電平,不存在其他電平的情況。 3.2 數據類(lèi)型 Verilog HDL的信號類(lèi)型有很多種,但主要包括兩種數據類(lèi)型:線(xiàn)網(wǎng)類(lèi)型(net type) 和寄存器類(lèi)型(reg type)。明德?lián)P的設計,也是只會(huì )使用這兩個(gè)類(lèi)型。 3.2.1線(xiàn)網(wǎng)類(lèi)型wire 線(xiàn)網(wǎng)類(lèi)型用于對結構化器件之間的物理連線(xiàn)的建模。如器件的管腳,內部器件如與門(mén)的輸出等。以上面的加法器為例,輸入信號A,B是由外部器件所驅動(dòng),異或門(mén)X1的輸出S1是與異或門(mén)X2輸入腳相連的物理連接線(xiàn),它由異或門(mén)X1所驅動(dòng)。 由于線(xiàn)網(wǎng)類(lèi)型代表的是物理連接線(xiàn),因此它不存貯邏輯值。必須由器件所驅動(dòng)。通常由assign 進(jìn)行賦值。如 assign A = B ^ C; wire 類(lèi)型定義語(yǔ)法如下: wire [msb: lsb] wire1, wire2, . . .,wireN; Ø msb 和lsb 定義了范圍,表示了位寬。例如[7:0]是8位位寬,也就是可以表示成8’b0至8’b1111_1111; Ø msb和lsb必須為常數值; Ø 如果沒(méi)有定義范圍,缺省值為1位; Ø 信號沒(méi)有定義數據類(lèi)型時(shí),缺省為wire 類(lèi)型。 Ø 對數組類(lèi)型,請按降序方式,如[7:0] ;不要寫(xiě)成[0:7]。 wire [3:0] Sat; // S a t 為4 位線(xiàn)型信號 wire Cnt; //1 位線(xiàn)型信號 wire [0:31] Kisp, Pisp, Lisp ;// Kisp, Pisp, Lisp 都是32位的線(xiàn)型信號,不建議這樣定義。 3.3.2寄存器類(lèi)型reg reg 是最常用的寄存器類(lèi)型,寄存器類(lèi)型通常用于對存儲單元的描述,如D型觸發(fā)器、ROM 等。存儲器類(lèi)型的信號當在某種觸發(fā)機制下分配了一個(gè)值,在分配下一個(gè)值之時(shí)保留原值。但必須注意的是,reg 類(lèi)型的變量,不一定是存儲單元,如在always 語(yǔ)句中進(jìn)行描述的必須用reg 類(lèi)型的變量。 reg 類(lèi)型定義語(yǔ)法如下: reg [msb: lsb] reg1, reg2, . . . r e g N; Ø msb 和lsb 定義了范圍,表示了位寬。例如[7:0]是8位位寬,也就是可以表示成8’b0至8’b1111_1111; Ø msb和lsb必須為常數值; Ø 如果沒(méi)有定義范圍,缺省值為1位; Ø 信號沒(méi)有定義數據類(lèi)型時(shí),缺省為wire 類(lèi)型,不是reg型。 Ø 對數組類(lèi)型,請按降序方式,如[7:0] ;不要寫(xiě)成[0:7]。 例如: reg [3:0] Sat; // S a t 為4 位寄存器。 reg Cnt; //1 位寄存器。 reg [1:32] Kisp, Pisp, Lisp ; 3.3.3Wire和reg定義的場(chǎng)合區分 Reg型信號不一定生成寄存器。那么什么時(shí)候使用wire類(lèi)型,什么時(shí)候用reg類(lèi)型,明德?lián)P總結出一套方法:在本模塊中,使用always設計的信號都定義為reg型;其他都用wire型。 上面代碼中,cnt1是用always設計的,所以要用reg型。Add_cnt1和end_cnt不是由always產(chǎn)生的,所以定義為wire型。 上面代碼中,x是用always設計的,所以要定義為reg型。注意,實(shí)際的電路中,x不是寄存器,但我們仍然定義為reg型。 上面是例化的代碼,其中df是例化模塊的輸出。由于df不是由always產(chǎn)生的,而是例化產(chǎn)生的,所以要定義成wire型。 注:(本博客連載的內容將出版成圖書(shū),并將錄制視頻,免費公開(kāi)學(xué)習,歡迎大家留意。本連載前面是基礎部分,與一般教材無(wú)異,后面是項目實(shí)踐,是本連載的特色。如果你有一定的基礎(能看懂verilog代碼即可),那么可跳過(guò)前面部分,直接學(xué)習后面的項目實(shí)踐。 本連載學(xué)習效果:不難看能懂代碼,還能知道每一行代碼怎么寫(xiě),怎么設計 |