|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
引言
; H9 G# k0 G& c: Y8 r
: b4 i9 m- i, C" R, g在实际的数字电路设计中,状态机是最常用的逻辑,而且往往是全部逻辑的核心部分,所以状态机的质量,会在比较大的程度上影响整个电路的质量。( L: g3 k3 e) h9 u' C2 S) e
* w k! r& Y3 U1 b$ M2 b3 G本小节我们通过一个简单的例子(三进制脉动计数器)来说明一下状态机的4中写法。
\/ c2 j3 v- g' x) I
w4 b1 y; U q+ X. O8 r, e( s0 D1,模块功能
9 q/ g$ i& ~) }$ j! W) d8 i! h D+ D- y0 r
由于我们的目的在于说明状态机的写作方式,所以其逻辑越简单有利于理解。就是一个简单的脉动计数器,每个三个使能信号输出一个标示信号。
. r5 ^% h' M( j4 y# N
4 F, v/ W6 l/ o; F! Z6 f) e& k3 i9 e( l6 T
& p. \; y% I, F* E& l; q9 K& m. d
2,一段式
* U0 @( e! y! u2 \
, M" N! Q0 D5 e& ]+ F8 w% q" a状态机的写法,一般有四种,即一段式,两段式,三段式,四段式。对于一段式的写法,整个状态机的状态转移、转移条件、对应状态的输出都写在一个always块里,故称‘一段式’。那么,脉动计数器状态机的一段式写法该怎么写呢?如下所示:8 q) _" U4 o0 L! F! S
$ p1 s& n V2 ^
7 L6 @5 i5 J- {: n S( `* ?/ @0 E2 B- A
0 N% S" W. z) ^! ^6 F; [# l
- /*
- * 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
- - u- s- h/ U( ^# C, J! F3 }
* p+ N6 c4 c' Z P* Q ^9 n. i& l( E! q; G' N. O
3 Q8 S7 g: W- `" S* D% F3,两段式
$ f" Z9 K+ Y/ D. _( h* X
9 t8 N4 z; `1 s状态机的另外一种写法是‘两段式’的。两段式的写法,整个状态机由两个always块组成,第一个块只负责状态转移,第二个块负责转移条件和对应状态的输出。其中第一个块是时序逻辑,第二个块是组合逻辑。脉动计数器状态机的两段式写法又是怎样的呢?
% J7 z9 u M- I: \- a) ~6 ~% G. o( A* f$ I: A$ z
7 S, P8 E5 ?; K5 {# \8 D& l
) ]! t1 D1 B- C @* P7 n( B
- /*
- * 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
- 0 [2 u8 A+ l% M3 `3 `8 O2 g$ d
i& `4 `: q1 e. _( U8 x
+ ]) [9 S' J; X/ S$ T) z
- _5 T# p0 D5 X( {3 r4,三段式0 J( T1 z* w. E6 f
; D& [+ y1 l: C: l& G' l0 A; W
从上面可以看出,两段式的写法是从一段式发展而来的,将一段式的写法中将状态转移部分提取出来,作为一个独立的always块,就变成了两段式。按照这个思路继续推进,如果将两段式的第二个块中的转移条件提取出来,也作为一个独立的块,就变成了‘三段式’,三段式的写法中,状态转移块是时序逻辑,转移条件块是组合逻辑,对应状态的输出是时序逻辑。那么,脉动计数器状态机的三段式写法是怎样的呢?1 K7 h0 Q$ A/ L& B% [
9 f: t9 z9 ^8 d2 |5 ?5 i5 ^
. ~: z8 P; t6 L. R9 V
0 t/ @. W Q' `0 `- Z8 Q- /*
- * 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
- 1 Y/ k% f0 v+ p) A& b6 g O
, c8 V2 f) ^6 `
: W' G$ m ]6 e% h; I/ j& u6 g/ O: O, B u. A8 ?, X
5,四段式
- ?" |- A# T. N
9 F" [) f% t" [# {9 W& R5 m3 _上面的三种状态机的写法是我们经常提到的,也是经典的三种。这三种写法在逻辑上是完全等价的,也就是是说,无论采用哪种写法,模块的功能都是一样的,但前两种一般只出现在教科书中,在实际的项目中是很少见到的。原因在于生成网表的综合器,由于目前的综合器还不够智能,其优化算法对三种写法的敏感度不同,造成最终生成的电路有所区别,有时候区别较大,尤其是对于复杂的状态机。无数血与泪的实践证明,使用前面两种写法生成的电路在时序、性能、功耗和面积等方面的表现都不如三段式的写法,所以即使三段式的写法会让你多敲几次键盘,在实际的电路设计中尽量采用三段式的写法来描述状态机,多敲的那几次键盘换来的电路质量的提高是完全值得的。. _; A& R6 u( w3 W% D. [# ~' w/ ?
俗话说,“没有最好,只有更好”。三段式的写法是不是最好的呢?我认为不见得如此。上面说到,如果采用三段式的写法,代码会变长,如果是大的状态机,结果会更明显。那么,有没有一种写法,既能产生优质的电路,又能少敲几次键盘呢?答案是肯定的。
; U# t( E* @$ B4 b q7 f仔细观察上面三种写法,你会发现,无论是哪种写法,都会使用case语句,case语句不仅占用的代码行数最多,而且综合器对case语句还有不同的解析(full case和parallel case),如果我们将三段式的写法中的case语句换成assign语句,并将状态转移块进一步将当前状态和下一个状态拆分开,就变成了“四段式”,四段式的写法由状态识别,状态转移,转移条件和对应状态的输出四部分组成。那么,脉动计数器状态机四段式的写法又是如何实现的呢?
# t# A# p1 _* w0 E
b, D, A4 R4 M( H- s/ Y+ u. L" B1 L6 k1 _# O) R. V
: m1 C6 Y9 L- i' G6 q% w4 ], @3 r- /*
- * 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
- ; u; a6 h# i* S5 w+ D( Q% Q
: z7 C) b$ b6 L' X0 j
& a A7 w1 u @0 r* c
5 }( {! K7 h2 ^$ v$ x/ s% l" S' l6,验证
( C1 D" s5 x, k: o# h$ Z, a) A- c0 G' ]) L
通过对比,我们很容易就会发现,采用四段式写法写出来的状态机,代码数量会减少很多,不仅如此,由于使用的语句类型减少了(只有赋值语句),生成电路的质量也会有所改善。那是否在进行电路设计的时候采用四段式的写法就没有缺点了呢?还有句俗话叫“金无足赤,人无完人”,由于四段式的写法将状态机拆分的过于零散,以至于综合器都识别不出来它是一个状态机了,所以在做覆盖率(coverage)分析的时候,分析工具只会按一般的逻辑进行分析,各个状态之间的转换概率就分析不出来了。; O) X' X" L2 A; h
既然状态机有这么多种写法,在实际工作中采用哪一种呢?我认为三段式和四段式都是可以接受的(我个人习惯四段式的写法)。如果将来有一天综合器对四种写法综合出来的电路都差不多,那读者就可以根据自己的喜好来任意选择了。
6 M m- A/ C4 K+ F1 m9 B4 W上面提到,无论采用哪种写法,模块实现的功能都是完全相同的,倒底是不是呢?我们需要写一个简单的测试激励(testbench)来验证一下。
/ ^1 q$ N9 ], s3 f8 X* O( g/ c! a+ [3 G5 ?9 d
0 Y7 G" [. l8 J1 X* U8 X7 K2 P( A$ }! [, N
- /*
- * 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
- 0 B4 P; w8 B7 s9 O# ]4 f1 ?& a
( @, X7 _/ X1 @$ j7 Q3 }8 I0 ]
# T% f) T6 u) E/ L! w* V E' b& j& x! @5 C3 Z
7,modelsim下的波形; A+ G7 ^2 b9 i4 s. b) M
; g4 x& z, D' w3 l% g
! y* c+ b3 a% g2 Y
. n1 R0 a1 a! L2 F; x1 j# _8 `: D8 O* j: ^3 z' {0 s; P! V
8,ncsim的波形' O/ L! z7 K- q: M
0 ^* L" a3 u! ~+ i上面是用windows下的modelsim得到的仿真波形,如果我们用ncsim(IUS),并且在Linux下,我们最好写一个简单的脚本来进行仿真,提高工作效率。4 _( U; I2 `+ \, N, n: _
2 \4 b+ W' h" E
% p4 u2 p( N* a5 L0 H- #! /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!"
( J! q. _! e+ Y! E. w" d 6 S, F* c2 b; Y
# @) l2 F" _" A8 T8 E. f
. v* D) Z3 O/ P0 f9 M# D运行脚本:
4 u! }: q- j% n" N% S2 e# N _9 _/ M/ d+ M
- ./fsm.sh c
- ./fsm.sh w& W1 Z3 b5 X. a' e1 a: ~1 ^
) B4 Z: _1 _6 W; ?3 {
5 M9 z) A1 _2 U5 G2 x7 ^: Z( V. H* H* Z2 y; P
执行:
7 S6 U/ K, B! \' m' m4 n2 F1 v
) K# C1 g m2 u6 H0 ^0 |simvision wave/wave.trn) {: ]' t2 |$ M, |
6 u+ @) c6 Q" o8 M6 w) g
- C$ w+ V5 E! ^4 ^% K即可得到仿真波形,如下所示:
/ m/ t/ x3 h" [ D8 c0 N6 H: b& y/ O- F5 O7 B1 E
( `# C8 r& H; l+ N! n5 r+ M: E
0 `4 v5 c! y6 S/ K
) Q8 Y& S/ y, C7 n从中可以看出,ncsim和modelsim得到的仿真波形有所不同。原因在于前面的三种三段式写法是寄存器输出,第四种是组合逻辑输出。
. ^ g. Q5 u1 i: T& j& i
, I* K" K, x, }; X0 K
3 a1 P- j+ ]- M$ s5 k9 U! ~) v4 f5 v, _( |% P3 {! W
* a: S% Q: W# `2 ?2 f
9 t7 S3 m( \) P- P) l0 K8 L5 S) y, M" a8 O9 c0 q" e% W! }) `2 G" o. K9 s
|
|