數(shù)字集成電路設(shè)計-12-狀態(tài)機的四種寫法
來源:程序員人生 發(fā)布時間:2014-09-29 23:35:29 閱讀次數(shù):3610次
引言
在實際的數(shù)字電路設(shè)計中,狀態(tài)機是最常用的邏輯,而且往往是全部邏輯的核心部分,所以狀態(tài)機的質(zhì)量,會在比較大的程度上影響整個電路的質(zhì)量。
本小節(jié)我們通過一個簡單的例子(三進制脈動計數(shù)器)來說明一下狀態(tài)機的4中寫法。
1,模塊功能
由于我們的目的在于說明狀態(tài)機的寫作方式,所以其邏輯越簡單有利于理解。就是一個簡單的脈動計數(shù)器,每個三個使能信號輸出一個標(biāo)示信號。
2,一段式
狀態(tài)機的寫法,一般有四種,即一段式,兩段式,三段式,四段式。對于一段式的寫法,整個狀態(tài)機的狀態(tài)轉(zhuǎn)移、轉(zhuǎn)移條件、對應(yīng)狀態(tài)的輸出都寫在一個always塊里,故稱‘一段式’。那么,脈動計數(shù)器狀態(tài)機的一段式寫法該怎么寫呢?如下所示:
/*
* 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)
begin
if(rst)
begin
done <=1'b0;
state <= s_idle;
end
else
begin
case(state)
s_idle:
begin
if(enable)
state <= s_1;
done <= 1'b0;
end
s_1:
begin
if(enable)
state <= s_2;
done <= 1'b0;
end
s_2:
begin
if(enable)
begin
state <= s_3;
done <= 1'b1;
end
else
begin
done <= 1'b0;
end
end
s_3:
begin
state <= s_idle;
done <= 1'b0;
end
default:
begin
state <= s_idle;
done <= 1'b0;
end
endcase
end
end
endmodule
3,兩段式
狀態(tài)機的另外一種寫法是‘兩段式’的。兩段式的寫法,整個狀態(tài)機由兩個always塊組成,第一個塊只負(fù)責(zé)狀態(tài)轉(zhuǎn)移,第二個塊負(fù)責(zé)轉(zhuǎn)移條件和對應(yīng)狀態(tài)的輸出。其中第一個塊是時序邏輯,第二個塊是組合邏輯。脈動計數(shù)器狀態(tài)機的兩段式寫法又是怎樣的呢?
/*
* 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)
begin
if(rst)
begin
current_state <= s_idle;
end
else
begin
current_state <= next_state;
end
end
always @(*)
begin
case(current_state)
s_idle:
begin
if(enable)
next_state = s_1;
done = 1'b0;
end
s_1:
begin
if(enable)
next_state = s_2;
done = 1'b0;
end
s_2:
begin
if(enable)
next_state = s_3;
done = 1'b0;
end
s_3:
begin
next_state = s_idle;
done = 1'b1;
end
default:
begin
next_state = s_idle;
done = 1'b0;
end
endcase
end
endmodule
4,三段式
從上面可以看出,兩段式的寫法是從一段式發(fā)展而來的,將一段式的寫法中將狀態(tài)轉(zhuǎn)移部分提取出來,作為一個獨立的always塊,就變成了兩段式。按照這個思路繼續(xù)推進,如果將兩段式的第二個塊中的轉(zhuǎn)移條件提取出來,也作為一個獨立的塊,就變成了‘三段式’,三段式的寫法中,狀態(tài)轉(zhuǎn)移塊是時序邏輯,轉(zhuǎn)移條件塊是組合邏輯,對應(yīng)狀態(tài)的輸出是時序邏輯。那么,脈動計數(shù)器狀態(tài)機的三段式寫法是怎樣的呢?
/*
* 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)
begin
if(rst)
begin
current_state <= s_idle;
end
else
begin
current_state <= next_state;
end
end
always @(*)
begin
case(current_state)
s_idle:
begin
if(enable)
next_state = s_1;
end
s_1:
begin
if(enable)
next_state = s_2;
end
s_2:
begin
if(enable)
next_state = s_3;
end
s_3:
begin
next_state = s_idle;
end
default:
begin
next_state = s_idle;
end
endcase
end
always @(posedge clk)
begin
if(rst)
begin
done <= 1'b0;
end
else
begin
case(next_state)
s_idle:
begin
done <= 1'b0;
end
s_1:
begin
done <= 1'b0;
end
s_2:
begin
done <= 1'b0;
end
s_3:
begin
done <= 1'b1;
end
default:
begin
done <= 1'b0;
end
endcase
end
end
endmodule
5,四段式
上面的三種狀態(tài)機的寫法是我們經(jīng)常提到的,也是經(jīng)典的三種。這三種寫法在邏輯上是完全等價的,也就是是說,無論采用哪種寫法,模塊的功能都是一樣的,但前兩種一般只出現(xiàn)在教科書中,在實際的項目中是很少見到的。原因在于生成網(wǎng)表的綜合器,由于目前的綜合器還不夠智能,其優(yōu)化算法對三種寫法的敏感度不同,造成最終生成的電路有所區(qū)別,有時候區(qū)別較大,尤其是對于復(fù)雜的狀態(tài)機。無數(shù)血與淚的實踐證明,使用前面兩種寫法生成的電路在時序、性能、功耗和面積等方面的表現(xiàn)都不如三段式的寫法,所以即使三段式的寫法會讓你多敲幾次鍵盤,在實際的電路設(shè)計中盡量采用三段式的寫法來描述狀態(tài)機,多敲的那幾次鍵盤換來的電路質(zhì)量的提高是完全值得的。
俗話說,“沒有最好,只有更好”。三段式的寫法是不是最好的呢?我認(rèn)為不見得如此。上面說到,如果采用三段式的寫法,代碼會變長,如果是大的狀態(tài)機,結(jié)果會更明顯。那么,有沒有一種寫法,既能產(chǎn)生優(yōu)質(zhì)的電路,又能少敲幾次鍵盤呢?答案是肯定的。
仔細(xì)觀察上面三種寫法,你會發(fā)現(xiàn),無論是哪種寫法,都會使用case語句,case語句不僅占用的代碼行數(shù)最多,而且綜合器對case語句還有不同的解析(full case和parallel case),如果我們將三段式的寫法中的case語句換成assign語句,并將狀態(tài)轉(zhuǎn)移塊進一步將當(dāng)前狀態(tài)和下一個狀態(tài)拆分開,就變成了“四段式”,四段式的寫法由狀態(tài)識別,狀態(tài)轉(zhuǎn)移,轉(zhuǎn)移條件和對應(yīng)狀態(tài)的輸出四部分組成。那么,脈動計數(shù)器狀態(tài)機四段式的寫法又是如何實現(xiàn)的呢?
/*
* 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)
begin
if(rst)
current_state <= s_idle;
else if(n_idle | n_1 | n_2 | n_3)
current_state = next_state;
end
assign done = c_3;
endmodule
6,驗證
通過對比,我們很容易就會發(fā)現(xiàn),采用四段式寫法寫出來的狀態(tài)機,代碼數(shù)量會減少很多,不僅如此,由于使用的語句類型減少了(只有賦值語句),生成電路的質(zhì)量也會有所改善。那是否在進行電路設(shè)計的時候采用四段式的寫法就沒有缺點了呢?還有句俗話叫“金無足赤,人無完人”,由于四段式的寫法將狀態(tài)機拆分的過于零散,以至于綜合器都識別不出來它是一個狀態(tài)機了,所以在做覆蓋率(coverage)分析的時候,分析工具只會按一般的邏輯進行分析,各個狀態(tài)之間的轉(zhuǎn)換概率就分析不出來了。
既然狀態(tài)機有這么多種寫法,在實際工作中采用哪一種呢?我認(rèn)為三段式和四段式都是可以接受的(我個人習(xí)慣四段式的寫法)。如果將來有一天綜合器對四種寫法綜合出來的電路都差不多,那讀者就可以根據(jù)自己的喜好來任意選擇了。
上面提到,無論采用哪種寫法,模塊實現(xiàn)的功能都是完全相同的,倒底是不是呢?我們需要寫一個簡單的測試激勵(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
begin
clk = 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)
begin
enable = 1;
@(posedge clk);
enable = 0;
@(posedge clk);
end
repeat (100) @(posedge clk);
$stop;
end
endmodule
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=tb
tcl_file=run.tcl
if [ $# != 1 ];then
echo "args must be c/w/r"
exit 0
fi
if [ $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
fi
if [ -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}
fi
if [ $1 == "w" -o $1 == "r" ];then
echo "sim start..."
ncsim ${TOP_MODULE} -input ${tcl_file}
fi
echo "$(date) sim done!"
運行腳本:
./fsm.sh c
./fsm.sh w
執(zhí)行:
simvision wave/wave.trn
即可得到仿真波形,如下所示:

從中可以看出,ncsim和modelsim得到的仿真波形有所不同。
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學(xué)習(xí)有所幫助,可以手機掃描二維碼進行捐贈