時序電路的狀態(tài)是一個狀態(tài)變量集合,這些狀態(tài)變量在任意時刻的值都包含了為確定電路的未來行為而必需考慮的所有歷史信息。 狀態(tài)機采用VerilogHDL語言編碼,建議分為三個always段完成。 三段式建模描述FSM的狀態(tài)機輸出時,只需指定case敏感表為次態(tài)寄存器, 然后直接在每個次態(tài)的case分支中描述該狀態(tài)的輸出即可,不用考慮狀態(tài)轉(zhuǎn)移條件。 三段式描述方法雖然代碼結(jié)構(gòu)復(fù)雜了一些,但是換來的優(yōu)勢是:使FSM做到了同步寄存器輸出,消除了組合邏輯輸出的不穩(wěn)定與毛刺的隱患,而且更利于時序路徑分組,一般來說在FPGA/CPLD等可編程邏輯器件上的綜合與布局布線效果更佳。 示列如下: //第一個進程,同步時序always模塊,格式化描述次態(tài)寄存器遷移到現(xiàn)態(tài)寄存器 always @ (posedge clk or negedge rst_n) //異步復(fù)位 if(!rst_n) current_state <= IDLE; else current_state <= next_state; //注意,使用的是非阻塞賦值 //第二個進程,組合邏輯always模塊,描述狀態(tài)轉(zhuǎn)移條件判斷 always @ (current_state) //電平觸發(fā) begin next_state = x; //要初始化,使得系統(tǒng)復(fù)位后能進入正確的狀態(tài) case(current_state) S1: if(...) next_state = S2; //阻塞賦值 ... endcase end //第三個進程,同步時序always模塊,格式化描述次態(tài)寄存器輸出 always @ (posedge clk or negedge rst_n) ...//初始化 case(next_state) S1: out1 <= 1'b1; //注意是非阻塞邏輯 S2: out2 <= 1'b1; default:... //default的作用是免除綜合工具綜合出鎖存器 endcase end 兩段式有限狀態(tài)機與三段式有限狀態(tài)機的區(qū)別 FSM將時序部分(狀態(tài)轉(zhuǎn)移部分)和組合部分(判斷狀態(tài)轉(zhuǎn)移條件和產(chǎn)生輸出)分開,寫為兩個always語句,即為兩段式有限狀態(tài)機。 將組合部分中的判斷狀態(tài)轉(zhuǎn)移條件和產(chǎn)生輸入再分開寫,則為三段式有限狀態(tài)機。 區(qū)別: 二段式在組合邏輯特別復(fù)雜時適用,但要注意需在后面加一個觸發(fā)器以消除組合邏輯對輸出產(chǎn)生的毛刺。三段式?jīng)]有這個問題,由于第三個always會生成觸發(fā)器。 設(shè)計時注意方面: 1.編碼原則,binary和gray-code適用于觸發(fā)器資源較少,組合電路資源豐富的情況(CPLD),對于FPGA,適用one-hot code。這樣不但充分利用FPGA豐富的觸發(fā)器資源,還因為只需比較一個bit,速度快,組合電路簡單。 2.FSM初始化問題: GSR(Gobal Set/Reset)只是在加電時清零所有的reg和片內(nèi)ram,并不保證FSM能進入初始化狀態(tài),要利用GSR,方案是適用one-hot code with zero idle,即初始狀態(tài)編碼為全零。已可以適用異步復(fù)位rst 3.FSM輸出可以適用task 4FSM中的case最好加上default,默認態(tài)可以設(shè)為初始態(tài) 5.尤其注意: 第二段的always(組合部分,賦值用=)里面判斷條件一定要包含所有情況!可以用else保證包含完全。 6第二段always中,組合邏輯電平要維持超過一個clock,仿真時注意。
關(guān)于VHDL狀態(tài)機:不聽老人言,吃虧在眼前。
以 前看了不少關(guān)于如何寫VDHL狀態(tài)機的文章,都是提倡使用二段式或三段式的寫法,都建議避免使用一段式的寫法,但看了之后,都沒什么體會。象我們寫軟件出 身的,心理上總喜歡一段式的寫法,覺得思路比較連貫,而且可以寫在一個process里,“內(nèi)聚性”比較高。軟件工程師是最討厭多個函數(shù)共用全局變量的 了。 但對于硬件開發(fā),就不一樣了。因為VHDL還是無法完全屏蔽掉硬件的物理特性,不好的布局,會使得寫的邏輯錯誤執(zhí)行。最近寫的一個狀態(tài)機,就遇到了這個麻 煩。因為喜好的緣故,加上狀態(tài)機里面有計數(shù)器,用組合邏輯寫比較麻煩,于是我用了一段式的寫法。結(jié)果實際運行的時候,發(fā)現(xiàn)狀態(tài)機經(jīng)常無故鎖死,用邏輯分析 儀看,發(fā)現(xiàn)陷入了非法的狀態(tài),而且when others語句也無法使?fàn)顟B(tài)機回到IDLE狀態(tài)。開始懷疑邏輯上有錯誤,折騰幾天后,把狀態(tài)切換部分獨立出來放在一個同步process里,問題解決 了。雖然偶爾還會發(fā)現(xiàn)落入非法狀態(tài),但狀態(tài)機會自動恢復(fù)到初始狀態(tài),不會鎖死了,而程序邏輯沒有做如何修改。看來以后還是得規(guī)規(guī)矩矩用二段或三段式的寫法 了。為了便于記憶,把二段、三段式的特點終結(jié)成幾句話: 二段式:狀態(tài)切換用時序邏輯,次態(tài)輸出和信號輸出用組合邏輯。 三段式:狀態(tài)切換用時序邏輯,次態(tài)輸出用組合邏輯,信號輸出用時序邏輯。信號輸出的process中,case語句用next state做條件,可以解決比組合邏輯輸出慢一拍的問題。 有時候判斷次態(tài)需要用到計數(shù)器怎么辦呢(計數(shù)器是時序電路,用組合邏輯是實現(xiàn)不了的)?方法是獨立實現(xiàn)一個計數(shù)器,而在組合邏輯里用使能信號(或清除、置位等)來控制它。
狀態(tài)機描述時關(guān)鍵是要描述清楚幾個狀態(tài)機的要素,即如何進行狀態(tài)轉(zhuǎn)移,每個狀態(tài)的輸出是什么,狀態(tài)轉(zhuǎn)移的條件等。具體描述時方法各種各樣,最常見的有三種描述方式:
1、一段式:整個狀態(tài)機寫到一個always模塊里面,在該模塊中既描述狀態(tài)轉(zhuǎn)移,又描述狀態(tài)的輸入和輸出;
2、二段式:用兩個always模塊來描述狀態(tài)機,其中一個always模塊采用同步時序描述狀態(tài)轉(zhuǎn)移;另一個模塊采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件,描述狀態(tài)轉(zhuǎn)移規(guī)律以及輸出;
3、 三段式:在兩個always模塊描述方法基礎(chǔ)上,使用三個always模塊,一個always模塊采用同步時序描述狀態(tài)轉(zhuǎn)移,一個always采用組合邏 輯判斷狀態(tài)轉(zhuǎn)移條件,描述狀態(tài)轉(zhuǎn)移規(guī)律,另一個always模塊描述狀態(tài)輸出(可以用組合電路輸出,也可以時序電路輸出)。
一般而言,推薦的FSM 描述方法是后兩種。這是因為:FSM和其他設(shè)計一樣,最好使用同步時序方式設(shè)計,以提高設(shè)計的穩(wěn)定性,消除毛刺。狀態(tài)機實現(xiàn)后,一般來說,狀態(tài)轉(zhuǎn)移部分是同步時序電路而狀態(tài)的轉(zhuǎn)移條件的判斷是組合邏輯。
第 二種描述方法同第一種描述方法相比,將同步時序和組合邏輯分別放到不同的always模塊中實現(xiàn),這樣做的好處不僅僅是便于閱讀、理解、維護,更重要的是 利于綜合器優(yōu)化代碼,利于用戶添加合適的時序約束條件,利于布局布線器實現(xiàn)設(shè)計。在第二種方式的描述中,描述當(dāng)前狀態(tài)的輸出用組合邏輯實現(xiàn),組合邏輯很容 易產(chǎn)生毛刺,而且不利于約束,不利于綜合器和布局布線器實現(xiàn)高性能的設(shè)計。
第三種描述方式與第二種相比,關(guān)鍵在于根據(jù)狀態(tài)轉(zhuǎn)移規(guī)律,在上一狀態(tài)根據(jù)輸入條件判斷出當(dāng)前狀態(tài)的輸出,從而在不插入額外時鐘節(jié)拍的前提下,實現(xiàn)了寄存器輸出。
----------------以下是三段式狀態(tài)機模板---------------------------------------
時序電路的狀態(tài)是一個狀態(tài)變量集合,這些狀態(tài)變量在任意時刻的值都包含了為確定電路的未來行為而必需考慮的所有歷史信息。
狀態(tài)機采用VerilogHDL語言編碼,建議分為三個always段完成。這是為什么呢? 設(shè)計FSM的方法和技巧多種多樣,但是總結(jié)起來有兩大類:第一種,將狀態(tài)轉(zhuǎn)移和狀態(tài)的操作和判斷等寫到一個模塊(process、block)中。另一種是將狀態(tài)轉(zhuǎn)移單獨寫成一個模塊,將狀態(tài)的操作和判斷等寫到另一個模塊中(在Verilog代碼中,相當(dāng)于使用兩個“always”block)。其中較好的方式是后者。其原因如下。 首先FSM和其他設(shè)計一樣,最好使用同步時序方式設(shè)計,好處不再累述。而狀態(tài)機實現(xiàn)后,狀態(tài)轉(zhuǎn)移是用寄存器實現(xiàn)的,是同步時序部分。狀態(tài)的轉(zhuǎn)移條件的判斷是通過組合邏輯判斷實現(xiàn)的,之所以第二種比第一種編碼方式合理,就在于第二種編碼將同步時序和組合邏輯分別放到不同的程序塊(process,block)中實現(xiàn)。這樣做的好處不僅僅是便于閱讀、理解、維護,更重要的是利于綜合器優(yōu)化代碼,利于用戶添加合適的時序約束條件,利于布局布線器實現(xiàn)設(shè)計。 三段式建模描述FSM的狀態(tài)機輸出時,只需指定case敏感表為次態(tài)寄存器,然后直接在每個次態(tài)的case分支中描述該狀態(tài)的輸出即可,不用考慮狀態(tài)轉(zhuǎn)移條件。 三段式描述方法雖然代碼結(jié)構(gòu)復(fù)雜了一些,但是換來的優(yōu)勢是使FSM做到了同步寄存器輸出,消除了組合邏輯輸出的不穩(wěn)定與毛刺的隱患,而且更利于時序路徑分組,一般來說在FPGA/CPLD等可編程邏輯器件上的綜合與布局布線效果更佳。 示例如下: //第一個進程,同步時序always模塊,格式化描述次態(tài)寄存器遷移到現(xiàn)態(tài)寄存器 always @ (posedge clk or negedge rst_n) //異步復(fù)位 if(!rst_n) current_state <= IDLE; else current_state <= next_state;//注意,使用的是非阻塞賦值 //第二個進程,組合邏輯always模塊,描述狀態(tài)轉(zhuǎn)移條件判斷 always @ (current_state) //電平觸發(fā) begin next_state = x; //要初始化,使得系統(tǒng)復(fù)位后能進入正確的狀態(tài) case(current_state) S1: if(...) next_state = S2; //阻塞賦值 ... endcase end //第三個進程,同步時序always模塊,格式化描述次態(tài)寄存器輸出 always @ (posedge clk or negedge rst_n) ...//初始化 case(next_state) S1: out1 <= 1'b1; //注意是非阻塞邏輯 S2: out2 <= 1'b1; default:... //default的作用是免除綜合工具綜合出鎖存器。 endcase end 三段式并不是一定要寫為3個always塊,如果狀態(tài)機更復(fù)雜,就不止3段了。 //注:================================================================ 1. 三段always模塊中,第一個和第三個always模塊是同步時序always模塊,用非阻塞賦值(“ <= ”);第二個always模塊是組合邏輯always模塊,用阻塞賦值(“ = ”)。 2. 第二部分為組合邏輯always模塊,為了抑制warning信息,對于always的敏感列表建議采用always@(*)的方式。 3. 第二部分,組合邏輯always模塊,里面判斷條件一定要包含所有情況!可以用else保證包含完全。 4. 第二部分,組合邏輯電平要維持超過一個clock,仿真時注意。 5. 需要注意:第二部分case中的條件應(yīng)該為當(dāng)前態(tài)(current_state),第三部分case中的條件應(yīng)該為次態(tài)(next_state)。 6. 編碼原則,binary和gray-code適用于觸發(fā)器資源較少,組合電路資源豐富的情況(CPLD),對于FPGA,適用one-hot code。這樣不但充分利用FPGA豐富的觸發(fā)器資源,還因為只需比較一個bit,速度快,組合電路簡單。 7. 初始化狀態(tài)和默認狀態(tài)。 一個完備的狀態(tài)機(健壯性強)應(yīng)該具備初始化狀態(tài)和默認狀態(tài)。當(dāng)芯片加電或者復(fù)位后,狀態(tài)機應(yīng)該能夠自動將所有判斷條件復(fù)位,并進入初始化狀態(tài)。需要注明的一點是,大多數(shù)FPGA有GSR(Global Set/Reset)信號,當(dāng)FPGA加電后,GSR信號拉高,對所有的寄存器,RAM等單元復(fù)位/置位,這時配置于FPGA的邏輯并未生效,所以不能保證正確的進入初始化狀態(tài)。所以使用GSR企圖進入FPGA的初始化狀態(tài),常常會產(chǎn)生種種不必一定的麻煩。一般的方法是采用異步復(fù)位信號,當(dāng)然也可以使用同步復(fù)位,但是要注意同步復(fù)位的邏輯設(shè)計。解決這個問題的另一種方法是將默認的初始狀態(tài)的編碼設(shè)為全零,這樣GSR復(fù)位后,狀態(tài)機自動進入初始狀態(tài)。 令一方面狀態(tài)機也應(yīng)該有一個默認(default)狀態(tài),當(dāng)轉(zhuǎn)移條件不滿足,或者狀態(tài)發(fā)生了突變時,要能保證邏輯不會陷入“死循環(huán)”。這是對狀態(tài)機健壯性的一個重要要求,也就是常說的要具備“自恢復(fù)”功能。對應(yīng)于編碼就是對case,if-else語句要特別注意,要寫完備的條件判斷語句。VHDL中,當(dāng)使用CASE語句的時候,要使用“When Others”建立默認狀態(tài)。使用“IF...THEN...ELSE”語句的時候,要用在“ELSE”指定默認狀態(tài)。Verilog中,使用“case”語句的時候要用“default”建立默認狀態(tài),使用“if...else”語句的注意事項相似。 8. 另外提一個技巧:大多數(shù)綜合器都支持Verilog編碼狀態(tài)機的完備狀態(tài)屬性--“full case”。這個屬性用于指定將狀態(tài)機綜合成完備的狀態(tài),如Synplicity的綜合工具(Synplify/Synplify Pro,Amplify,etc)支持的命令格式如下: case (current_state) // synthesis full_case 2’b00 : next_state <= 2’b01; 2’b01 : next_state <= 2’b11; 2’b11 : next_state <= 2’b00; //這兩段代碼等效 case (current_state) 2’b00 : next_state <= 2’b01; 2’b01 : next_state <= 2’b11; 2’b11 : next_state <= 2’b00; default : next_state <= 2bx; 9. Synplicity還有一個關(guān)于狀態(tài)機的綜合屬性,叫“synthesis parallel_case”,其功能是檢查所有的狀態(tài)是“并行的”(parallel),也就是說在同一時間只有一個狀態(tài)能夠成立。 10. 狀態(tài)機的定義可以用parameter定義,但是不推薦使用`define宏定義的方式,因為`define宏定義在編譯時自動替換整個設(shè)計中所定義的宏,而parameter僅僅定義模塊內(nèi)部的參數(shù),定義的參數(shù)不會與模塊外的其他狀態(tài)機混淆。 11. 對于狀態(tài)比較多的狀態(tài)機,可以將所有狀態(tài)分為幾個大狀態(tài),然后再使用小狀態(tài),可以減少狀態(tài)譯碼的時間。 12. 在代碼中添加綜合器的綜合約束屬性或者在圖形界面下設(shè)置綜合約束屬性可以比較方便的改變狀態(tài)的編碼。 如VHDL的示例: Synplicity: attribute syn_encoding : string; attribute syn_encoding of <signal_name> : type is "value "; -- The syn_encoding attribute has 4 values : sequential, onehot, gray and safe. Exemplar: -- Declare TYPE_ENCODING_STYLE. attribute -- Not needed if the exemplar_1164 package is used type encoding_style. is (BINARY, ONEHOT, GRAY, RANDOM, AUTO); attribute TYPE_ENCODING_STYLE. encoding_style; ... attribute TYPE_ENCODING_STYLE. of <typename> : type is ONEHOT; Verilog示例: Synplicity: Reg[2:0] state; /* synthesis syn_encoding = "value" */; // The syn_encoding attribute has 4 values : sequential, onehot, gray and safe. Exemplar: Parameter /* exemplar enum <type_name> */ s0 = 0, s1 = 1, s2 = 2, s3 = 3, S4 = 4; Reg [2:0] /* exemplar enum <type_name> */ present_state, next_state ; 13. 小技巧:仔細檢查綜合器的綜合報告,目前大多數(shù)的綜合器對所綜合出的latch都會報“warning”,通過綜合報告可以較為方便地找出無意中生成的latch。 //=============================================================
|