|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
引言5 w# y+ t4 n( [, M
: N6 b& C, n* B在实际的数字电路设计中,状态机是最常用的逻辑,而且往往是全部逻辑的核心部分,所以状态机的质量,会在比较大的程度上影响整个电路的质量。0 f0 v" e$ }4 G" J
0 S% F$ h5 Y. I: B$ g& ?本小节我们通过一个简单的例子(三进制脉动计数器)来说明一下状态机的4中写法。3 ]3 k9 _9 r# l, `4 {
$ t- J8 V1 F( G1,模块功能
. G& P% Z |" j" [* N
) Q- v% v/ E3 Q# C |# _由于我们的目的在于说明状态机的写作方式,所以其逻辑越简单有利于理解。就是一个简单的脉动计数器,每个三个使能信号输出一个标示信号。# m- U$ l' \6 h8 f' o% R7 {
7 H. n3 B- [2 q% G, S) |2 I* {# h
4 `: [2 f) e/ _7 M, z2 d; X7 s% |( E
2,一段式
) @! U; u& q4 G! m" m8 x# p
1 I# r& H* k* A% H& \# X: S状态机的写法,一般有四种,即一段式,两段式,三段式,四段式。对于一段式的写法,整个状态机的状态转移、转移条件、对应状态的输出都写在一个always块里,故称‘一段式’。那么,脉动计数器状态机的一段式写法该怎么写呢?如下所示:
/ t, f* g& S9 {9 I) d
, {9 n3 X. ?; L. p3 s
" Y! X2 l. `# U; d% F! A6 Y; M7 O1 ~0 i% n+ }! J* f6 Z
- /*
- * 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
# Z6 E' I) M1 Q" {+ Y6 [
2 {' n; X9 ~- _" i* m- E% H3 S, b2 X( a" Z+ g/ \
# H- K' n( e l0 K
3,两段式) ^6 J: x# F. N( v8 l2 W+ _9 }. D
# L0 d* G* g o S5 O1 ]0 Z状态机的另外一种写法是‘两段式’的。两段式的写法,整个状态机由两个always块组成,第一个块只负责状态转移,第二个块负责转移条件和对应状态的输出。其中第一个块是时序逻辑,第二个块是组合逻辑。脉动计数器状态机的两段式写法又是怎样的呢?! d3 _" R9 l; j% D9 f" e
2 K, R1 v; N) T5 b0 |5 ?5 `6 s2 O2 \0 G
( K* g4 y* Q& K3 r9 C/ \% e* Q" Z5 A/ P% f) ]
- /*
- * 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
9 Y2 i# M) K9 ]0 P5 t, Y* J* t+ X
3 I0 a3 Y5 V2 K0 n) j
! v' [) M' g/ p6 q- H- {2 A1 [" l$ W# y
4,三段式
. G+ u2 ^) i$ A( y4 \( _( V ^
2 a7 N' d6 Z$ o# b2 |从上面可以看出,两段式的写法是从一段式发展而来的,将一段式的写法中将状态转移部分提取出来,作为一个独立的always块,就变成了两段式。按照这个思路继续推进,如果将两段式的第二个块中的转移条件提取出来,也作为一个独立的块,就变成了‘三段式’,三段式的写法中,状态转移块是时序逻辑,转移条件块是组合逻辑,对应状态的输出是时序逻辑。那么,脉动计数器状态机的三段式写法是怎样的呢?3 N$ U1 X0 d2 q6 P, @* S: z
P b8 q/ A# `2 D, P+ u ^7 {; c- b0 q+ v! i1 j, h# [1 w V- x
1 H2 J) B J) y8 f7 x y- /*
- * 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
. r( x y' u# S, f1 Q7 s* R 2 g/ L; I7 C& L7 P
9 P: R6 r9 P+ Q* u" ]& q
% t3 Y( s; c0 G: z- o. Q7 t5 H5,四段式' a* o+ A8 Q. E. ?, w( N
4 c# W# a l, \上面的三种状态机的写法是我们经常提到的,也是经典的三种。这三种写法在逻辑上是完全等价的,也就是是说,无论采用哪种写法,模块的功能都是一样的,但前两种一般只出现在教科书中,在实际的项目中是很少见到的。原因在于生成网表的综合器,由于目前的综合器还不够智能,其优化算法对三种写法的敏感度不同,造成最终生成的电路有所区别,有时候区别较大,尤其是对于复杂的状态机。无数血与泪的实践证明,使用前面两种写法生成的电路在时序、性能、功耗和面积等方面的表现都不如三段式的写法,所以即使三段式的写法会让你多敲几次键盘,在实际的电路设计中尽量采用三段式的写法来描述状态机,多敲的那几次键盘换来的电路质量的提高是完全值得的。$ y$ }+ H: ^1 |( |& H7 J8 z
俗话说,“没有最好,只有更好”。三段式的写法是不是最好的呢?我认为不见得如此。上面说到,如果采用三段式的写法,代码会变长,如果是大的状态机,结果会更明显。那么,有没有一种写法,既能产生优质的电路,又能少敲几次键盘呢?答案是肯定的。- D( q' w9 f$ P! @& {" ~" q4 g
仔细观察上面三种写法,你会发现,无论是哪种写法,都会使用case语句,case语句不仅占用的代码行数最多,而且综合器对case语句还有不同的解析(full case和parallel case),如果我们将三段式的写法中的case语句换成assign语句,并将状态转移块进一步将当前状态和下一个状态拆分开,就变成了“四段式”,四段式的写法由状态识别,状态转移,转移条件和对应状态的输出四部分组成。那么,脉动计数器状态机四段式的写法又是如何实现的呢?
% c% R# ~3 [* G; B+ C8 D+ K% p1 `) k6 z( R+ e# f/ ^
" h$ c [+ s* h* `: U
; B* {: `( w b. ?
- /*
- * 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
7 K$ m) y0 _/ U( c! i1 i3 q/ Z7 X ! M& U5 L. S* g2 L6 L
7 n1 r; r3 f! u* n
6 r. c8 t0 V) F8 t
6,验证; F# X6 i9 B6 o, [. R
0 {, D) t9 ], z5 F# b4 b5 V通过对比,我们很容易就会发现,采用四段式写法写出来的状态机,代码数量会减少很多,不仅如此,由于使用的语句类型减少了(只有赋值语句),生成电路的质量也会有所改善。那是否在进行电路设计的时候采用四段式的写法就没有缺点了呢?还有句俗话叫“金无足赤,人无完人”,由于四段式的写法将状态机拆分的过于零散,以至于综合器都识别不出来它是一个状态机了,所以在做覆盖率(coverage)分析的时候,分析工具只会按一般的逻辑进行分析,各个状态之间的转换概率就分析不出来了。: T' u) t3 T. B
既然状态机有这么多种写法,在实际工作中采用哪一种呢?我认为三段式和四段式都是可以接受的(我个人习惯四段式的写法)。如果将来有一天综合器对四种写法综合出来的电路都差不多,那读者就可以根据自己的喜好来任意选择了。 & P: E7 g1 }$ p
上面提到,无论采用哪种写法,模块实现的功能都是完全相同的,倒底是不是呢?我们需要写一个简单的测试激励(testbench)来验证一下。
( z- U1 c* |% y: q' t8 e0 M2 I
3 |8 Y+ m! F7 R' X. k/ H* }% x# V6 ~3 _9 ^& {" b: C- C* E) \. e
& l0 C- T3 S& e" |( 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
g5 j* X; d8 |- V1 D 3 z1 C, g6 @7 I7 M2 \/ `$ O2 r
, G# V9 k) ^1 B) S, I- N8 H9 ]) ~4 S0 F7 D$ f( M6 P
7,modelsim下的波形
3 W0 J( W1 F4 h7 e7 Y! e/ ?+ n) G2 c1 G# P) J: h7 w
% D" u, R; l" |/ y2 s: v, \! [/ u } s- T2 E1 z
2 T n8 P# \4 C; s0 Z
8,ncsim的波形4 q9 ]( }9 U, s' A: ?& L& O
+ ?- f( t, z8 E5 Z* ?上面是用windows下的modelsim得到的仿真波形,如果我们用ncsim(IUS),并且在Linux下,我们最好写一个简单的脚本来进行仿真,提高工作效率。
8 ]9 `" L7 R! n( P. M" m, o0 a! Y- x8 D- N5 c
+ r9 C$ j h4 {5 w
- #! /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!"- W+ e9 Y' G. M- `$ m7 N: v Q! Z
2 ]3 Q1 u( w1 y8 B# y+ A4 m7 P* X* M* n" {: H6 }& l( z: X2 E
" \! }( @0 U4 p* @/ h( \7 l
运行脚本:
9 n. g* }1 B' f: R) d d x( _/ [5 Z& v; g* Z c& w
- ./fsm.sh c
- ./fsm.sh w' }8 g2 _8 b8 j
- \* Y' K( a* X/ J6 @+ l3 ~
$ l9 ~, D6 g% }/ K8 g3 g- v. o) R; [. q! O1 {. L) @0 ]
执行:
7 }4 z' ~. E: O- M) K
0 s& C/ ^1 W' v z d3 ^; j2 tsimvision wave/wave.trn
( R8 o) u3 H& O+ r; K" M9 Q9 ^5 D# S& E, B
! l% L0 d4 M0 f4 y X即可得到仿真波形,如下所示:
* [, X& Y2 Y: ?3 t" L0 [: s* s+ c2 B$ u. q2 S/ p i0 l
6 W- D: D* c C3 z+ L
! s4 T$ L I1 o. ~* o# {, w3 f+ |! F0 Q1 _
从中可以看出,ncsim和modelsim得到的仿真波形有所不同。原因在于前面的三种三段式写法是寄存器输出,第四种是组合逻辑输出。& |2 T4 J2 g9 @4 m
2 m: F! V$ N- @: T$ w* u' I& G- C( l" s+ }9 h( O* M. i
5 P) s( e {, T* j8 [" O- c
* }( l9 J1 z& S- q" J7 _0 Q+ g. l, Z* x1 z' u# N
5 b* H( r# i8 N$ k) U* { |
|