|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
引言
/ m5 ?3 m! [! M$ U* ^0 m; ?6 Z3 e
, i" o+ q2 Z2 p$ O( ^2 W在实际的数字电路设计中,状态机是最常用的逻辑,而且往往是全部逻辑的核心部分,所以状态机的质量,会在比较大的程度上影响整个电路的质量。
% d/ d- s% t/ z: h' t) i7 ~# E
; X0 c$ N, o' T* a本小节我们通过一个简单的例子(三进制脉动计数器)来说明一下状态机的4中写法。
8 O, `6 s }0 z9 X6 P7 e& Y7 E4 C; ]7 E$ w0 e( X
1,模块功能
! y( s' g* ]& A0 z$ b: k9 G# J1 G1 N5 Y4 R2 F, O- L
由于我们的目的在于说明状态机的写作方式,所以其逻辑越简单有利于理解。就是一个简单的脉动计数器,每个三个使能信号输出一个标示信号。5 `$ t" b2 P# Z
+ e; M, i5 t5 Y. g( p8 W( B5 [+ h3 K, E( i6 ^# r
2 k8 |' c; a0 S, I+ z
2,一段式
' W; Q3 G1 e) k+ K/ ?
& @8 u5 Z1 L3 j状态机的写法,一般有四种,即一段式,两段式,三段式,四段式。对于一段式的写法,整个状态机的状态转移、转移条件、对应状态的输出都写在一个always块里,故称‘一段式’。那么,脉动计数器状态机的一段式写法该怎么写呢?如下所示:2 B P/ l8 F- s
! n; t8 q( T- ~- e' _4 G G3 j' v! H6 Q" ^1 E) @7 t, H
2 `+ ?. t. Q5 I' [2 {9 ?4 a- N- /*
- * 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
- 6 R# Q8 h& N( R% k5 x& Q) T
" I5 f5 R1 o$ d- @4 H* J9 T8 g% U
+ ?9 K* i/ D( p( @4 `- w Q' k+ q8 @
3,两段式
* v- I7 N$ V/ }) p- R! a, @8 s; S& x' \
状态机的另外一种写法是‘两段式’的。两段式的写法,整个状态机由两个always块组成,第一个块只负责状态转移,第二个块负责转移条件和对应状态的输出。其中第一个块是时序逻辑,第二个块是组合逻辑。脉动计数器状态机的两段式写法又是怎样的呢?
; G" R- w: M# J N# D# k% V' h: M" v9 g K
7 A. Y4 J1 e \3 A1 t7 [9 q: b' |9 X: }# O
- /*
- * 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
- ( }* x% l- }" [6 I, p) ^
; V" v+ P& T' d- \4 S3 o2 a
@. L" c# A! K4 H! s- D6 V5 ^# L; r Q, d6 y; ?2 {, I
4,三段式, ]6 ?" H: j& p. L. b6 P0 i
' F! D3 ]& Y9 ]# F6 d }8 E& Q6 X
从上面可以看出,两段式的写法是从一段式发展而来的,将一段式的写法中将状态转移部分提取出来,作为一个独立的always块,就变成了两段式。按照这个思路继续推进,如果将两段式的第二个块中的转移条件提取出来,也作为一个独立的块,就变成了‘三段式’,三段式的写法中,状态转移块是时序逻辑,转移条件块是组合逻辑,对应状态的输出是时序逻辑。那么,脉动计数器状态机的三段式写法是怎样的呢?
; v; X7 t8 q& M) {/ F) W' I0 h) F# |* q" b" C7 N% L
6 L3 q: \' h6 i" K, w3 X" e
1 ] Z2 K2 ?, i3 v+ ]6 @# 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
0 Y7 L0 T' r2 H( b4 B& _& r ' G8 P( q+ q6 h- R! s
5 h6 A. ?/ F; w& i" p5 D' g5 i
7 Z5 ^$ Y% L2 b( P: c! k
5,四段式
+ \' m4 Z' M" R* V5 }' ?) d
1 l% `" m% p" Y$ u0 p/ y; `* M上面的三种状态机的写法是我们经常提到的,也是经典的三种。这三种写法在逻辑上是完全等价的,也就是是说,无论采用哪种写法,模块的功能都是一样的,但前两种一般只出现在教科书中,在实际的项目中是很少见到的。原因在于生成网表的综合器,由于目前的综合器还不够智能,其优化算法对三种写法的敏感度不同,造成最终生成的电路有所区别,有时候区别较大,尤其是对于复杂的状态机。无数血与泪的实践证明,使用前面两种写法生成的电路在时序、性能、功耗和面积等方面的表现都不如三段式的写法,所以即使三段式的写法会让你多敲几次键盘,在实际的电路设计中尽量采用三段式的写法来描述状态机,多敲的那几次键盘换来的电路质量的提高是完全值得的。
0 d% s5 N) t- R俗话说,“没有最好,只有更好”。三段式的写法是不是最好的呢?我认为不见得如此。上面说到,如果采用三段式的写法,代码会变长,如果是大的状态机,结果会更明显。那么,有没有一种写法,既能产生优质的电路,又能少敲几次键盘呢?答案是肯定的。
8 L0 [' K! W4 w) L/ j( I仔细观察上面三种写法,你会发现,无论是哪种写法,都会使用case语句,case语句不仅占用的代码行数最多,而且综合器对case语句还有不同的解析(full case和parallel case),如果我们将三段式的写法中的case语句换成assign语句,并将状态转移块进一步将当前状态和下一个状态拆分开,就变成了“四段式”,四段式的写法由状态识别,状态转移,转移条件和对应状态的输出四部分组成。那么,脉动计数器状态机四段式的写法又是如何实现的呢?
! i! A! t( G7 z. g9 t: i
( N# F2 P7 r2 L9 \3 S' |9 u b- b4 }$ b. O3 E7 a
i4 T/ }. M/ S# f
- /*
- * 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 ^; l$ r! ^- H' S3 s! e. v
2 a. j/ n" R1 z# f* r8 \, l7 h/ G/ v# ]7 L# [
$ C0 T& E2 f+ y1 ~2 T
6,验证
: e7 N ^7 s- X- H- {6 a" b- W# S4 w, B7 u, K n; ?. R) S9 X
通过对比,我们很容易就会发现,采用四段式写法写出来的状态机,代码数量会减少很多,不仅如此,由于使用的语句类型减少了(只有赋值语句),生成电路的质量也会有所改善。那是否在进行电路设计的时候采用四段式的写法就没有缺点了呢?还有句俗话叫“金无足赤,人无完人”,由于四段式的写法将状态机拆分的过于零散,以至于综合器都识别不出来它是一个状态机了,所以在做覆盖率(coverage)分析的时候,分析工具只会按一般的逻辑进行分析,各个状态之间的转换概率就分析不出来了。
' N9 l- g* \ H既然状态机有这么多种写法,在实际工作中采用哪一种呢?我认为三段式和四段式都是可以接受的(我个人习惯四段式的写法)。如果将来有一天综合器对四种写法综合出来的电路都差不多,那读者就可以根据自己的喜好来任意选择了。 * d: r# P+ t& ?
上面提到,无论采用哪种写法,模块实现的功能都是完全相同的,倒底是不是呢?我们需要写一个简单的测试激励(testbench)来验证一下。
# S& G4 F d' T/ M H( i( A8 e% F! ~( ~
8 s# I" y7 m6 {+ y
" f! _0 g; C' z$ [5 l- /*
- * 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
- ( `' _" d0 l$ h6 n, c4 o
! ]3 s" v/ J0 Q: M2 G% E
" X" j6 }7 A9 b4 I
z. H+ p9 D% l
7,modelsim下的波形: a5 K; y, S* G, j8 b# E Q
/ [& E" w) _$ R; l- |
( k" C( P0 Y! N& Z$ Q: g
, K% c* _5 S# ^1 O2 Z
$ p+ F+ n E/ r8,ncsim的波形) ?) ]$ Z: Q7 V3 A. F; _, P
& S* h7 Q; o2 c1 f/ _3 A4 W3 e上面是用windows下的modelsim得到的仿真波形,如果我们用ncsim(IUS),并且在Linux下,我们最好写一个简单的脚本来进行仿真,提高工作效率。! i1 _. {# f$ D, Q- V1 y& c6 p6 R/ c
j; H4 W, w/ U
w* l( A: y' W- l$ O- #! /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!"7 y3 d% O$ C1 j; x) X0 ^
I$ k$ m7 f- q. j$ H- R% ~/ S5 @& x; K) Q- C6 y2 Q
$ i- E( W& @5 [" Y$ j! B: J
运行脚本:
7 \) E2 H3 T- J5 x6 P
`% R' S" B; [' ~- A5 @) T- ./fsm.sh c
- ./fsm.sh w
. o; w3 G; @4 B " e/ ?5 G5 e7 u7 t# R. P N% g
4 ~6 r& c8 m7 Q$ e$ [& ?
( B' ]5 c9 _1 |& X5 K% m/ l执行:7 Y4 r* i5 T: m5 L
- X( A# }9 ~0 g; ~% @0 Jsimvision wave/wave.trn% W/ Q' r/ X d% ]- m
: D! j5 @9 v+ o+ F% ^5 X& e( I
3 a8 ?. T5 s! K/ S, J y即可得到仿真波形,如下所示:' |3 b/ W( V& q% ^- y' ^9 O! w, C
3 H o% |) j6 Q! r7 _2 v' q, c
' @0 e- T$ N6 @, d# z" ~. D
9 n. w, ]3 V0 w( U, q' t
; m0 b7 ]8 X1 U( P$ X从中可以看出,ncsim和modelsim得到的仿真波形有所不同。原因在于前面的三种三段式写法是寄存器输出,第四种是组合逻辑输出。
9 C& {4 Y2 R8 l1 ~0 O. ^: ]2 \
u1 ?3 N( x1 ~: j9 _" F/ F W+ r( h0 f" x
+ o$ u+ |4 d* S
+ `+ y$ E0 ^. f% C) E
+ N$ w( v6 a- q7 ^7 f* m! p) a0 X# n% ]6 A: r1 v
|
|