以下大部分內容摘自VerilogHDL掃盲篇:
學習VerilogHDL語言不像學習一些高級語言,對於高級語言來說它們已經是完成品了,其外它們還有很多被隱藏的指令,這些好處無疑是減輕了學習者的負擔。相反的VerilogHDL語言既是完成品,既不是完成品,就是因為它太自由了... 所以往往會讓學習者感到疑惑,很疲憊和浮躁(我不學了!)。學習VerilogHDL語言需要一段過渡期的,快則半年,普通則1~2年,慢則很多年。即使經過了過渡期這也不表示已經掌握VerilogHDL語言了。所以呀朋友,希望你們可以沉住氣,“欲速則不達”這是老祖先的智慧,它非常適合用在學習VerilogHDL語言的路上。VerilogHDL語言既不是順序語言而且也非常的自由,VerilogHDL語言不像C語言那樣有豐富的庫支持,甚至庫的概念也不適合用在它身上。但是即使它們是兩個世界的居民,但是偶爾VerilogHDL語言可以在很多地方向C語言借簽。相反的C語言就不能向VerilogHDL語言借簽了。此外VerilogHDL語言還有兩大陣列,就是綜合語言和驗證語言,這更是給學習者雪上加霜 太多的學習者會困惑在這兩種語言的中間,所謂的困惑是思路的困惑。在這里,筆者建議先無視驗證語言,先把綜合語言學好(綜合語言也沒什么好學的,就如在前面章節筆者所舉例的那樣,關鍵字和操作符少得可憐),最重要還是掌握結構(建模)和使用規則(用法)。它們就像揮動着倚天劍和屠龍寶刀的招式,沒有了這些招式倚天劍和屠龍寶刀不過是一件單純的金屬而已。至於驗證語言,在未來有需要的時候再學也不遲,當閱讀他人模塊的時候,不要過於轉牛角尖的看懂他人的思路,只要明白其中的內容就好。最重要還是如何使用自己的結構和方法去建立他人的思路,從中讀者會學得更多。這一點是絕對的事實。就算現在的讀者沒有能力建立自己的結構也沒有關系,來日方長讀者有的就是時間。如果讀者很鍾愛筆者所建立的結構和方法,筆者很樂意也很榮欣被應用(這也是筆者寫筆記的初衷)。
沉住氣朋友,掌握VerilogHDL語言需要的不只是技術而已,最重要是那顆安靜的心,安靜的心會帶讀者乘風破浪,一方通行。此外記錄筆記的習慣更為重要,向自己學習比起向他人學習更有學習的價值
一,建模是VerilogHDL語言的中心思想之二
二,時序是VerilogHDL語言的中心思想之二
在筆者的眼中,總結上C語言和VerilogHDL語言之間的區別會是如上的圖表。關於高級語言和VerilogHDL語言區別的內容筆者討論到這里就好了,讀者不要過於深入區分誰是誰,誰又不是誰,如此糾結對學習沒有任何好處,更多認識,當讀者們深入以后就會自然了解。
我們知道驅使(RTL級)模塊行動的最小單位就是時鍾,即時發生延遲也只是延遲一個時鍾,又或者2~3個時鍾,絕對不會出現什么延遲2ns~3ns(物理路徑延遲)等的問題。
在筆者的眼中“時序”的思想非常簡單,就是如何讀懂波形圖,如何把波形圖和模塊的內容作聯系。
例子1-reg 和wire的尷尬:
reg 和wire如果站在RTL級建模的角度上,reg 就是寄存器,作用是用來暫存內容,而且也提供操作空間;wire就是連線,作用僅此而已。但是站在組合邏輯級建模上reg和wire已經是傻傻分不清楚了
舉個例子:
moduleomg_module(output[7:0]Q);
wire[3:0]A=4'd0;
wire[3:0]B=4'd2;
wire[3:0]C=4'd3;
assignQ=A+B+C;
endmodule
以上的一段代碼,請問wire的作用是連線還是寄存內容了?呵呵,筆者沒有說這樣的使用方法有錯呀。在這里筆者只是提出一個有趣的例子而已,對於一些初學者來說可能會非常的疑惑,尤其是那些習慣“面向對象”思想的人們...
moduleomg_module(output[7:0]Q);
reg[3:0]A;
reg[3:0]B;
reg[3:0]C;
always@(*)
begin
A=4'd0;
B=4'd2;
C=4'd3;
end
assignQ=A+B+C;
endmodule
如果筆者換成這樣寫法的話,是不是覺得更有道理呢?always(*) 的使用暗示了這是一個組合邏輯,然而寄存器A的值是4'd0,寄存器B的值是4'd2,寄存器C的值是4'd3,Q的驅動輸出是寄存器A,B,C的值綜合。
更有趣得是,經過編譯兩段代碼所生成的RTL視圖都一樣!?(⊙o⊙)?... 在這里,筆者是要告訴讀者一個信息,當我們建模的時候“解讀能力”的優先級往往都高過“內容精簡性”。要一度過於“貪小便宜”而把內容的解讀能力跨下了,雖然這兩個代碼都沒有錯,而且結果一致。但是一個潛在的問題是,我們人類都是習慣把“東西分類”以便管理和理解,既然wire在字面上都是“連線”的意思了,就干脆把它當“連線”來使用...
例子2-always@() 的多樣性
1.always@(posedgeCLKornegedgeRSTn)// 當CLK和RSTn變化的時候
2.always@(*) // 什么時候都變化, 亦即默認為組合邏輯
3.always@(A) // 當A變化的時候
always@() 的用法很多,但是用得最多的就是第1個和第2個。
always@(posedgeCLKornegedgeRSTn)
if(!RSTn)
begin
i<=4'd0;
.......
end
else
case(i)
0:
.......
endcase
上面一段代碼是筆者最愛的“基於仿順序操作想法”的基本“用法模板”,在后期里它可是大展拳腳。
always@(*)A=4'd9; // 常數賦值
always@(*) // 選擇器
if(Start_Sig[0])rQ=U1_Q;
elseif(Start_Sig[1])rQ=U2_Q;
elseQ=1'bx;
至於第二種always@() 的用法,筆者估計它是用得最“泛濫”了,尤其是在組合邏輯建模里,不過筆者使用的比較不多, 除了“常數賦值”和“輸出選擇器”以外。
最后的第一種always@()的用法,基本上只要懂得上面兩種always@() 用法以后,自然而然也會了。
例子3- 最頭疼的=和<=賦值
基本上要搞懂這兩個賦值操作符號的作用,就必須把“時序”的概念搞懂先。
宏觀上,如同參考書中所說的一樣;微觀上,在時序中“=”是引發“即時事件”,“<=”則是引發“時間點事件”
例子4- 要慎用的*/%數學運算符
當讀者使用*/ 和%的數學運算符的時候,筆者請你們再三的三思(九思?呵呵!),因為使用它們的代價很大。如果讀者所使用的FPGA有內嵌硬件乘法器又或者除法器的話,那么這些乘法器和除法器就會被消耗。相反的,如果讀者說使用的FPGA是沒有包含這些東西的話,資源邏輯的消耗是很大的。一般上,如果是為了求出*2 *4 *8 *16又或者/2 /4 /8 /16 筆者建議使用位操作的運算符,亦即<<和>>,它們也可以求出同樣的結果。如果想要求出的結果是不在2N范圍之內的話,讀者還是求與其他的方法... 只有在用盡辦法的時候才使用它們。至於%的數學運算符,它真的是一個罪惡,沒有可以替代的第三方法....在這里筆者要強調一下,筆者不是說不可以使用它們,筆者只是建議如果可以的話不要過度依賴它們,它們盡是一些邏輯資源的大食怪,不把資源吃光才怪。
在這里,筆者再強調一下HDL語言是一種非常自由的語言,它沒有固定的結構,也沒有固定的使用方法。參考書在乎“如何使用HDL語言去完成設計”,然而筆者比較在乎“如何有效使用HDL語言”。這兩者之間都沒有問題,不過是筆者的筆記比較偏向學習的口味,沒有參考書那樣死沉沉的氣氛,比較愉快和輕松
話句話說,VerilogHDL語言可以應用的地方只適合“邏輯和底層設計”.... 不不不,這是天大的誤會。就這樣,隨之又產生“C語言驅動的東西,既然用VerilogHDL語言去驅動”類似的聲音。是誰規定C語言可以驅動的東西,VerilongHDL語言就不能驅動?相反的,C語言可以驅動的東西,如果讀者也能使用VerilogHDL語言去驅動,那么這才是真正的學習。之所以會產生如此的聲音,就如筆者在前幾章節講述的那樣:“VerilogHDL語言的結構自由,使用方法也自由,自由到好像沒有一樣”很多的設計都不包含結構和使用方法,只要設計可以發揮預期般的效果就Okay~如果讀者明白了這個簡單的道理,讀者自然會明白自定義VerilogHDL語言的結構和使用方法是非常的重要和基礎。VerilogHDL語言的建模不是越復雜就越偉大,反之越直接的建模才是學習的方向。在這里,聽筆者說:“當你放下偏見,你才可以接觸到真理”,這簡單的智慧在哪里都行得通,學習VerilogHDL語言也是這樣一回事。
筆者來說說為什么單文件主義遠離了VerilogHDL語言的本質呢?VerilogHDL語言,本質上是並行而且又有“面向對象”的味道。但是這“面向對象”的概念和C++語言中的概念有所不同,然而它更接近現實中的“管理系統”(詳解請看建模篇)。讀者嘗試想象,有沒有可能一個系統的操作,沒有部門,沒有團隊,沒有小組?對,就是不可能。單文件主義恰恰好就是違反了這個簡單的道理。
上文中所說的“沒有分類”的爛漫約會,如果也“沒有順序操作”的支持... 會是一個非常糟糕的情形。因為這個系統操作(約會過程)沒有結構的支持, 這個情形也反映出了單文件主義的致命缺點。筆者有一句很經典的話:“解讀能力差的模塊是最糟糕的”,這一句話完全迎合單文件主義下所建立的模塊。
低級建模的基本單元有:功能模塊,控制模塊,組合模塊。
� 功能模塊的內容包含了最基本的動作。
� 控制模塊的內容包含了動作的控制步驟。
� 組合模塊的內容包含了所有功能模塊和控制模塊之間的組合。
建模層次有:基礎(模塊)建模,仿順序操作建模,接口建模,系統建模。
� 基礎(模塊)建模的內容包含了最小功能的模塊。
� 仿順序操作建模,這一個比較特別,主要是模仿了C語言中的函數。
� 接口建模的內容包含了一個已經封裝完成的模塊。
� 系統建模的內容包含了一個特定功能的模塊。
舉個例子,就拿串口來作個比方:
在串口硬件模塊(串口系統建模)里,分類了發送接口和接受接口。發送發送接口包含了FIFO模塊,波特率產生模塊和串口發送控制模塊。串口接收串口包含了FIFO模塊,波特率產生模塊,串口接收控制模塊。串口發送模塊是組合了波特率產生模塊和串口發送控制模塊,串口接收模塊是組合了波特率產生模塊和串口接收控制模塊。
串口系統建模之間的模塊基本單元分類:
串口系統建模之間的層次分類:
在這里筆者只是簡介了筆者最愛的“低級建模”的結構分類而已,事實上每一個基本單元和每一個層次都有嚴謹的定義。建模是VerilogHDL語言的結構,越龐大的設計建模所帶來的后期影響是讀者/筆者遠遠都猜想不到的。故建模對於VerilogHDL語言來說是非常重要的基礎。VerilogHDL語言的結構是自由的,然而筆者的“低級建模”是筆者自定義的結構而已。當然讀者也可以建立自己的結構。
仿順序操作是什么?就是利用VerilogHDL語言自身的特質去模仿一些順序語言如C語言,故稱為“仿順序操作”
always@(posedgeCLKornegedgeRSTn)
if(!RSTn)
begin
i<=4'd0;
....
end
else
case(i)
0:
begin....i<=i+1'b1;end
1:
......
endcase
就以流水燈來說話吧:
always@(posedgeCLKornegedgeRSTn)
if(!RSTn)
begin
i<=4'd0;
rLED<=4'b0000;
end
else
case(i)
0:
beginrLED<=4'b0001;i<=i+1'b1;end
1:
if(Timer==T10MS)i<=i+1'b1;end
2:
beginrLED<=4'b0010;i<=i+1'b1;end
3:
if(Timer==T10MS)i<=i+1'b1;end
4:
beginrLED<=4'b0100;i<=i+1'b1;end
5:
if(Timer==T10MS)i<=i+1'b1;end
6:
beginrLED<=4'b1000;i<=i+1'b1;end
7:
if(Timer==T10MS)i<=4'd0;end
endcase
在case... endcase 之間,步驟i 等於0,2,4,6的時候是更新LED(流水操作),步驟i等於1,3,5,7的時候是延遲10ms。
此外,還可以把步驟i當成簡單的循環。上面一段代碼表達了,當步驟i等於0,2,4,6的時候就更新rLED,。反之,當步驟i等於1,3,5,7的時候就延遲10ms。在步驟i等於8的時候是步驟返回操作。
always@(posedgeCLKornegedgeRSTn)
if(!RSTn)
begin
i<=4'd0;
rLED<=4'b0001;
end
else
case(i)
0:
beginrLED<={rLED[0],rLED[3:1]};i<=i+1'b1;end
1
if(Timer==T10MS)i<=i-1'b1;end
endcase
又或者更進一步的壓縮步驟,使得代碼更直接而且更節省資源。在上面一段代碼當中,只有兩個步驟,亦即步驟0和1。步驟0是更新rLED,步驟1是延遲10ms,然而這兩個步驟之間交互交替使而產生流水燈效果
當然,步驟i的用法不僅而已,如果把“時序”的概念引入的話:
always@(posedgeCLKornegedgeRSTn)
if(!RSTn)
begin
i<=4'd0;
Mper<=8'd0;
Mcand<=8'd0;
Sum<=8'd0;
end
else
case(i)
0:
beginMper<=Mper_Sig;Mcand<=Mcand_Sig;Sum<=8'd0i<=i+1'b1;end
1
if(Mcand==0)i<=i+1'b1;
elseSum<=Sum+Mper;Mcand<=Mcand-1'b1;end
......
endcase
以上一段代碼是簡單的乘法運算,Mper是乘數的暫存器,Mcand是被乘數的暫存器,Sum是累加空間。當步驟i等於0的時候初始化相關的寄存器,在步驟i等於1的時候執行乘法操作... 在這里讀者也可這樣說:“在T0的時候初始化相關的寄存器,在接下來的時鍾執行乘法操作... (有點悶,看不太懂,實力還不夠(⊙o⊙))
總結之下,這個用法可以伸縮的范圍非常之大。除外,它所帶來的好處也非常之多:
� 提供了VerilogHDL語言順序操作的支持。
� 提高了模塊的表達能力。
� 提供了仿順序操作·建模的結構基礎。
但是它也帶來一些限制:
� 不推薦嵌套case...endcase 和if。
� 該用法不推薦出現過多在同一個模塊中。
這些限制是筆者標記下來的,這之間和“低級建模”有多少關系。當然,如果讀者不遵守的話也沒有問題。顯然這個用法也不是萬能的,尤其是一些緊密的RTL級建模,如:VGA驅動,它就顯得無用武之地了。事實上這個用法到目前為止,筆者還在不停的研究當中,越深入學習它,就越發現它的潛能很深...
認識RTL級設計(建模)
用凡人的話來說CLK代表了一個模塊的心跳節拍,這個心跳節拍提供模塊可以消耗的動力。但是CLK信號真正可以被模塊所用到不是它的高電平又或者低電平,而是上升沿(低電平到高電平的變化)和下降沿(高電平到低電平的變化)。
always@(posedgeCLK)
......
對於RTL級設計來說CLK是模塊的心跳,沒有心跳模塊就不能活動,沒有心跳就沒有時序圖。換另一句話說,構成RTL級最基本的設計需要“寄存器”為最小的建模單位,然后再加上模塊可以活動的CLK信號。
always@(posedgeCLK)
Counter1<=Counter1+1'b1;
always@(posedgeCLK1/2)
Counter2<=Counter2+1'b1;
上面有一段代碼是Counter1+CLK和Counter2+CLK1/2促成最簡單的RTL級設計。Counter1在每一個CLK時鍾內遞增,然而Counter2在每一個CLK1/2時鍾內遞增。到目前為止Counter1和Counter2還是獨立關系。
圖0.13b是Counter1+CLK和Counter2+CLK1/2產生的時序圖。在這里Counter2所使用的頻率是Counter1的一半。在每一個CLK的上升沿Counter1都遞增,然而在每一個CLK1/2的上升沿Counter2都遞增
always@(posedgeCLK)
Counter1<=Counter1+1'b1;
always@(posedgeCLK1/2)
Counter2<=Counter1;
假設筆者把Counter1和Counter2聯系起“關系”的話,如上面的一段代碼所述。又會產生怎樣的時序圖呢?
圖0.13c是Counter1和Counter2建立關系以后多產生的時序圖。在CLK是T0的時候,CLK_1/2也是在T0。由於在T0之前Counter1什么也沒有,所以Counter什么也讀不到(一般上0為復位值)。在CLK_1/2是T1,Counter2嘗試讀取Counter1的“過去值”,結果Counter2讀到值2,所以在CLK_1/2的T1,Counter2的“未來值”是2。類似的事情也發生在CLK_1/2的T2,T3和T4的時候。在這里讀者先不用管“過去值”和“未來值”的定義,這是筆者在時序篇里的專用詞。讀者需要焦距的是,每一次Counter1成功遞增是發生在CLK的上升沿,然而Counter2每一次成功讀取Counter1的值都是發生在CLK_1/2的上升沿。換句話說,CLK的上升沿是觸發Counter1遞增,CLK_1/2的上升沿是觸發Counter讀取Counter1的過去值。以上的內容就是RTL級設計最基本的思想。
至於組合邏輯級設計呢?在VerilogHDL語言中,如果我們把VerilogHDL語言看成是理想的語言,那么組合邏輯就可以直接無視被CLK的影響,因為組合邏輯取得的是即時的結果。假設讀者不把VerilogHDL語言看成是“理想”的話,組合邏輯會產生“物理”上的延遲。但是筆者還是建議讀者把VerilogHDL語言看成是一個理想的工具為好。換另一個角度來看的話,C語言和VerilogHDL語言都是工具,難道C語言會產生“物理”上的延遲嗎?此外這樣的想法對於VerilogHDL語言的設計會帶來很大的好處,尤其是看懂波形圖(時序圖)哪一環,效果會更加明顯。
原文:http://www.cnblogs.com/oomusou/archive/2008/06/17/c_verilog_mental_thinking.html
Abstract
Verilog由於在語法上向C靠攏,若熟悉C語言,學Verilog倍感親切,但也由於語法類似,若把Verilog當成C語言來思考,怎很難抓到硬體的精神。
Introduction
Verilog有3點思維與C語言不一樣
1.軟體是循序的,而硬體是並行的
C語言是一行一行的執行,就算組合語言也是一樣,或許你會說threading,但在微觀下仍是循序地執行。但硬體電路就不一樣,電路只要一插上電,所有電路就同時工作。
如以下的Verilog
2 e <= a & b;
3 f <= c & d;
4 end
雖然看起來是 e <= a & b; 在 f <= c & d;前面,但實際上合成電路後如下圖所示
由上圖得知,e和f並沒有先後之分,是並行的。
2.硬體要循序,要靠clock和FSM
或許你會說,『我的演算法就是要循序一步一步的做,如C語言那樣,那怎麼辦?』,若Verilog要這樣,就得靠clock並且搭配FSM,當一個state完成後,進入下一個state,這樣就能依照clock的進行,而達成循序的要求。
3.Verilog程式碼沒有先後之分
除了blocking assignment有先後執行順序,而nonblocking assignment同時執行外,Verilog的程式沒有前後順序之分,所以才稱為硬體『描述』語言,而非硬體『程式』語言,先寫的不代表先執行,後寫的也不代表後執行,只是代表硬體的架構的描述,也就是說,將原來的電路圖,變成文字描述而已。
4.多用RTL Viewer和ModelSim觀察自己寫的code
Verilog寫法小小的差異,合成出來的硬體就可能有天壤之別,多用RTL Viewer觀察合成出來的硬體是否和自己預期的一樣,並多用ModelSim觀察跑出來的波形,這樣會增加你對Verilog的掌握度。
Conclusion
很多人學了Verilog,還是把它當C語言寫,事實上他們只是語法類似,但背後的思維並不一樣,唯有『心中有硬體』,才能設計出好的電路。