引言
在實際的數字電路設計中,狀態機是最常用的邏輯,而且往往是全部邏輯的核心部分,所以狀態機的質量,會在比較大的程度上影響整個電路的質量。
本小節我們通過一個簡單的例子(三進制脈動計數器)來說明一下狀態機的4中寫法。
1,模塊功能
由于我們的目的在于說明狀態機的寫作方式,所以其邏輯越簡單有利于理解。就是一個簡單的脈動計數器,每個三個使能信號輸出一個標示信號。
2,一段式
狀態機的寫法,一般有四種,即一段式,兩段式,三段式,四段式。對于一段式的寫法,整個狀態機的狀態轉移、轉移條件、對應狀態的輸出都寫在一個always塊里,故稱‘一段式’。那么,脈動計數器狀態機的一段式寫法該怎么寫呢?如下所示:
/*
* file : fsm1.v
* author: Rill
* date : 2014-05-11
*/module Mfsm1
(
clk,
rst,enable,
done
);input wire clk;
input wire rst;
input wire enable;
output reg done;parameter s_idle = 4'd0;
parameter s_1 = 4'd1;
parameter s_2 = 4'd2;
parameter s_3 = 4'd3;reg [3:0] state;always @(posedge clk)
beginif(rst)begindone <=1'b0;state <= s_idle;endelsebegincase(state)s_idle:beginif(enable)state <= s_1;done <= 1'b0;end s_1:beginif(enable)state <= s_2;done <= 1'b0;end s_2:beginif(enable)beginstate <= s_3;done <= 1'b1;endelsebegindone <= 1'b0;endends_3:beginstate <= s_idle;done <= 1'b0;enddefault:beginstate <= s_idle;done <= 1'b0;endendcaseend
endendmodule
3,兩段式
狀態機的另外一種寫法是‘兩段式’的。兩段式的寫法,整個狀態機由兩個always塊組成,第一個塊只負責狀態轉移,第二個塊負責轉移條件和對應狀態的輸出。其中第一個塊是時序邏輯,第二個塊是組合邏輯。脈動計數器狀態機的兩段式寫法又是怎樣的呢?
/*
* file : fsm2.v
* author: Rill
* date : 2014-05-11
*/module Mfsm2
(
clk,
rst,enable,
done
);input wire clk;
input wire rst;
input wire enable;
output reg done;parameter s_idle = 4'd0;
parameter s_1 = 4'd1;
parameter s_2 = 4'd2;
parameter s_3 = 4'd3;reg [3:0] current_state;
reg [3:0] next_state;always @(posedge clk)
beginif(rst)begincurrent_state <= s_idle;endelsebegincurrent_state <= next_state;end
endalways @(*)
begincase(current_state)s_idle:beginif(enable)next_state = s_1;done = 1'b0;end s_1:beginif(enable)next_state = s_2;done = 1'b0;end s_2:beginif(enable)next_state = s_3;done = 1'b0;ends_3:beginnext_state = s_idle;done = 1'b1;enddefault:beginnext_state = s_idle;done = 1'b0;endendcase
endendmodule
4,三段式
從上面可以看出,兩段式的寫法是從一段式發展而來的,將一段式的寫法中將狀態轉移部分提取出來,作為一個獨立的always塊,就變成了兩段式。按照這個思路繼續推進,如果將兩段式的第二個塊中的轉移條件提取出來,也作為一個獨立的塊,就變成了‘三段式’,三段式的寫法中,狀態轉移塊是時序邏輯,轉移條件塊是組合邏輯,對應狀態的輸出是時序邏輯。那么,脈動計數器狀態機的三段式寫法是怎樣的呢?
/*
* file : fsm3.v
* author: Rill
* date : 2014-05-11
*/module Mfsm3
(
clk,
rst,enable,
done
);input wire clk;
input wire rst;
input wire enable;
output reg done;parameter s_idle = 4'd0;
parameter s_1 = 4'd1;
parameter s_2 = 4'd2;
parameter s_3 = 4'd3;reg [3:0] current_state;
reg [3:0] next_state;always @(posedge clk)
beginif(rst)begincurrent_state <= s_idle;endelsebegincurrent_state <= next_state;end
endalways @(*)
begincase(current_state)s_idle:beginif(enable)next_state = s_1;ends_1:beginif(enable)next_state = s_2;ends_2:beginif(enable)next_state = s_3;ends_3:beginnext_state = s_idle;enddefault:beginnext_state = s_idle;endendcase
endalways @(posedge clk)
beginif(rst)begindone <= 1'b0;endelsebegincase(next_state)s_idle:begindone <= 1'b0;end s_1:begindone <= 1'b0;end s_2:begindone <= 1'b0;ends_3:begindone <= 1'b1;enddefault:begindone <= 1'b0;endendcaseendendendmodule
5,四段式
上面的三種狀態機的寫法是我們經常提到的,也是經典的三種。這三種寫法在邏輯上是完全等價的,也就是是說,無論采用哪種寫法,模塊的功能都是一樣的,但前兩種一般只出現在教科書中,在實際的項目中是很少見到的。原因在于生成網表的綜合器,由于目前的綜合器還不夠智能,其優化算法對三種寫法的敏感度不同,造成最終生成的電路有所區別,有時候區別較大,尤其是對于復雜的狀態機。無數血與淚的實踐證明,使用前面兩種寫法生成的電路在時序、性能、功耗和面積等方面的表現都不如三段式的寫法,所以即使三段式的寫法會讓你多敲幾次鍵盤,在實際的電路設計中盡量采用三段式的寫法來描述狀態機,多敲的那幾次鍵盤換來的電路質量的提高是完全值得的。
俗話說,“沒有最好,只有更好”。三段式的寫法是不是最好的呢?我認為不見得如此。上面說到,如果采用三段式的寫法,代碼會變長,如果是大的狀態機,結果會更明顯。那么,有沒有一種寫法,既能產生優質的電路,又能少敲幾次鍵盤呢?答案是肯定的。
仔細觀察上面三種寫法,你會發現,無論是哪種寫法,都會使用case語句,case語句不僅占用的代碼行數最多,而且綜合器對case語句還有不同的解析(full case和parallel case),如果我們將三段式的寫法中的case語句換成assign語句,并將狀態轉移塊進一步將當前狀態和下一個狀態拆分開,就變成了“四段式”,四段式的寫法由狀態識別,狀態轉移,轉移條件和對應狀態的輸出四部分組成。那么,脈動計數器狀態機四段式的寫法又是如何實現的呢?
/*
* file : fsm4.v
* author: Rill
* date : 2014-05-11
*/module Mfsm4
(
clk,
rst,enable,
done
);input wire clk;
input wire rst;
input wire enable;
output done;parameter s_idle = 4'd0;
parameter s_1 = 4'd1;
parameter s_2 = 4'd2;
parameter s_3 = 4'd3;reg [3:0] current_state;wire c_idle = (current_state == s_idle);
wire c_1 = (current_state == s_1);
wire c_2 = (current_state == s_2);
wire c_3 = (current_state == s_3);wire n_idle = c_3;
wire n_1 = c_idle & enable;
wire n_2 = c_1 & enable;
wire n_3 = c_2 & enable;wire [3:0] next_state = {4{n_idle}} & s_idle |{4{n_1}} & s_1 |{4{n_2}} & s_2 |{4{n_3}} & s_3;always @(posedge clk)
beginif(rst)current_state <= s_idle;else if(n_idle | n_1 | n_2 | n_3)current_state = next_state;endassign done = c_3;
endmodule
6,驗證
通過對比,我們很容易就會發現,采用四段式寫法寫出來的狀態機,代碼數量會減少很多,不僅如此,由于使用的語句類型減少了(只有賦值語句),生成電路的質量也會有所改善。那是否在進行電路設計的時候采用四段式的寫法就沒有缺點了呢?還有句俗話叫“金無足赤,人無完人”,由于四段式的寫法將狀態機拆分的過于零散,以至于綜合器都識別不出來它是一個狀態機了,所以在做覆蓋率(coverage)分析的時候,分析工具只會按一般的邏輯進行分析,各個狀態之間的轉換概率就分析不出來了。
既然狀態機有這么多種寫法,在實際工作中采用哪一種呢?我認為三段式和四段式都是可以接受的(我個人習慣四段式的寫法)。如果將來有一天綜合器對四種寫法綜合出來的電路都差不多,那讀者就可以根據自己的喜好來任意選擇了。?
上面提到,無論采用哪種寫法,模塊實現的功能都是完全相同的,倒底是不是呢?我們需要寫一個簡單的測試激勵(testbench)來驗證一下。
/*
* file : tb.v
* author: Rill
* date : 2014-05-11
*/module tb;reg clk;
reg rst;
reg enable;
wire done1;
wire done2;
wire done3;
wire done4;Mfsm1 fsm1
(
.clk(clk),
.rst (rst),
.enable(enable),
.done(done1)
);Mfsm2 fsm2
(
.clk(clk),
.rst (rst),
.enable(enable),
.done(done2)
);Mfsm3 fsm3
(
.clk(clk),
.rst (rst),
.enable(enable),
.done(done3)
);Mfsm4 fsm4
(
.clk(clk),
.rst (rst),
.enable(enable),
.done(done4)
);always #1 clk = ~clk;integer loop;initial
beginclk = 0;rst = 0;enable = 0;loop = 0;repeat (10) @(posedge clk);rst = 1;repeat (4) @(posedge clk);rst = 0;repeat (100) @(posedge clk);for(loop=1;loop<10;loop=loop+1)beginenable = 1;@(posedge clk);enable = 0;@(posedge clk);endrepeat (100) @(posedge clk);$stop;endendmodule
7,modelsim下的波形
8,ncsim的波形
上面是用windows下的modelsim得到的仿真波形,如果我們用ncsim(IUS),并且在Linux下,我們最好寫一個簡單的腳本來進行仿真,提高工作效率。
#! /bin/bash#
# fsm.sh
# usage: ./fsm.sh c/w/r
# Rill create 2014-09-03
#TOP_MODULE=tbtcl_file=run.tclif [ $# != 1 ];then
echo "args must be c/w/r"
exit 0
fiif [ $1 == "c" ]; then
echo "compile lib..."
ncvlog -f ./vflist -sv -update -LINEDEBUG;
ncelab -delay_mode zero -access +rwc -timescale 1ns/10ps ${TOP_MODULE}
exit 0
fiif [ -e ${tcl_file} ];then
rm ${tcl_file} -f
fi
touch ${tcl_file}if [ $1 == "w" ];then
echo "open wave..."
echo "database -open waves -into waves.shm -default;" >> ${tcl_file}
echo "probe -shm -variable -all -depth all;" >> ${tcl_file}
echo "run" >> ${tcl_file}
echo "exit" >> ${tcl_file}
fiif [ $1 == "w" -o $1 == "r" ];then
echo "sim start..."
ncsim ${TOP_MODULE} -input ${tcl_file}
fiecho "$(date) sim done!"
運行腳本:?
./fsm.sh c
./fsm.sh w
執行:
simvision wave/wave.trn
即可得到仿真波形,如下所示:
從中可以看出,ncsim和modelsim得到的仿真波形有所不同。原因在于前面的三種三段式寫法是寄存器輸出,第四種是組合邏輯輸出。
總結
以上是生活随笔為你收集整理的数字集成电路设计-12-状态机的四种写法的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。