|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
引言, K* t1 l9 w8 n" M4 A+ m2 n
( J4 i2 G* f3 l; u% h在实际的数字电路设计中,状态机是最常用的逻辑,而且往往是全部逻辑的核心部分,所以状态机的质量,会在比较大的程度上影响整个电路的质量。
% t9 A# D5 ^1 [; m+ ]* ]
, y$ q7 e% o! _# ^本小节我们通过一个简单的例子(三进制脉动计数器)来说明一下状态机的4中写法。
9 @- X! }! j$ M6 S% j+ d
6 |$ l7 E1 h# ?' l! ^+ y1,模块功能
) L% Q j* r7 ~% J: ~
$ v2 T+ X) ~' X5 [( v由于我们的目的在于说明状态机的写作方式,所以其逻辑越简单有利于理解。就是一个简单的脉动计数器,每个三个使能信号输出一个标示信号。
8 I3 s" ^5 M- N& k$ I [
# I8 ^$ J9 u/ v
7 R2 ?$ f3 I; a% y; @
5 ]. u6 c/ a1 P T( n5 H2,一段式
7 K) y6 ^' R" K( y" y, D$ |
1 X3 F! Q6 Y/ t状态机的写法,一般有四种,即一段式,两段式,三段式,四段式。对于一段式的写法,整个状态机的状态转移、转移条件、对应状态的输出都写在一个always块里,故称‘一段式’。那么,脉动计数器状态机的一段式写法该怎么写呢?如下所示:7 [0 L, e% B+ K, E, Z- a
" ^4 F, l7 L/ V$ m
7 e8 t$ v4 V( w+ ?; y& A& s& L" q6 g4 h7 C1 {
- /*
- * 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
- 1 @- G3 S, I% I
. \0 f% x2 R; u2 R9 D/ s7 ~
3 D' k" Z7 j( g0 A" Q! X/ s
% q8 g3 A2 h$ [1 J7 h' j1 R3,两段式/ z n! k. g9 O$ o" q
( e& h3 [# f* @( [3 T
状态机的另外一种写法是‘两段式’的。两段式的写法,整个状态机由两个always块组成,第一个块只负责状态转移,第二个块负责转移条件和对应状态的输出。其中第一个块是时序逻辑,第二个块是组合逻辑。脉动计数器状态机的两段式写法又是怎样的呢?
& f f) l7 f }
& J; \" {1 t$ ^- w
0 a; j Z7 l, q* g3 p. V ^3 J2 q
- /*
- * 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
- ; G. }( m! Q, t% L. g4 r
4 b) ]' h+ r% R2 s# b3 C3 j4 L
}! c; Z) [7 u% }
% W% y; |0 L& h/ B( n/ S# @4,三段式. u* a ]. p( v& h( {! z1 u
* Y3 q. S# S- t! A* Q从上面可以看出,两段式的写法是从一段式发展而来的,将一段式的写法中将状态转移部分提取出来,作为一个独立的always块,就变成了两段式。按照这个思路继续推进,如果将两段式的第二个块中的转移条件提取出来,也作为一个独立的块,就变成了‘三段式’,三段式的写法中,状态转移块是时序逻辑,转移条件块是组合逻辑,对应状态的输出是时序逻辑。那么,脉动计数器状态机的三段式写法是怎样的呢?5 B' j1 n% K) f5 E: \, _& e
; }8 R; V1 A- s2 ^
2 d1 t. I! J @" b6 ]$ l
, x! Y3 f! r& I/ b- /*
- * 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
- 6 i+ h$ T/ D& t3 l
/ C5 F0 T( w' N$ O7 T
% c# l7 H4 b2 O7 A5 ?7 t' a' V# i& W) R5 @- _
5,四段式0 `$ F+ D! h+ s, d2 U- p% Y
! z; H7 p! e" ]! M上面的三种状态机的写法是我们经常提到的,也是经典的三种。这三种写法在逻辑上是完全等价的,也就是是说,无论采用哪种写法,模块的功能都是一样的,但前两种一般只出现在教科书中,在实际的项目中是很少见到的。原因在于生成网表的综合器,由于目前的综合器还不够智能,其优化算法对三种写法的敏感度不同,造成最终生成的电路有所区别,有时候区别较大,尤其是对于复杂的状态机。无数血与泪的实践证明,使用前面两种写法生成的电路在时序、性能、功耗和面积等方面的表现都不如三段式的写法,所以即使三段式的写法会让你多敲几次键盘,在实际的电路设计中尽量采用三段式的写法来描述状态机,多敲的那几次键盘换来的电路质量的提高是完全值得的。( F: t# o) x' a) v2 ]
俗话说,“没有最好,只有更好”。三段式的写法是不是最好的呢?我认为不见得如此。上面说到,如果采用三段式的写法,代码会变长,如果是大的状态机,结果会更明显。那么,有没有一种写法,既能产生优质的电路,又能少敲几次键盘呢?答案是肯定的。
0 {5 i6 ]4 w. K: T+ r9 f7 D6 H% h0 D3 x8 g: U仔细观察上面三种写法,你会发现,无论是哪种写法,都会使用case语句,case语句不仅占用的代码行数最多,而且综合器对case语句还有不同的解析(full case和parallel case),如果我们将三段式的写法中的case语句换成assign语句,并将状态转移块进一步将当前状态和下一个状态拆分开,就变成了“四段式”,四段式的写法由状态识别,状态转移,转移条件和对应状态的输出四部分组成。那么,脉动计数器状态机四段式的写法又是如何实现的呢?
2 z3 {% B" H, J. q
, v" t5 k4 h* `) p: @. }1 V
W, k( S2 g7 i# J+ B) q) d! D. k( z" }
- /*
- * 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
5 V, o& V# Z0 [+ W5 J) c, Q" \
' s1 q* X- ?/ y j( w+ U& S: n2 {5 A! q ]2 F! \8 k
8 s! ]( F5 K$ V0 e- A9 X- [9 q: H
6,验证* m" I7 P( { L. n! A& T! R
0 S" _- @ M4 g# o) f/ M通过对比,我们很容易就会发现,采用四段式写法写出来的状态机,代码数量会减少很多,不仅如此,由于使用的语句类型减少了(只有赋值语句),生成电路的质量也会有所改善。那是否在进行电路设计的时候采用四段式的写法就没有缺点了呢?还有句俗话叫“金无足赤,人无完人”,由于四段式的写法将状态机拆分的过于零散,以至于综合器都识别不出来它是一个状态机了,所以在做覆盖率(coverage)分析的时候,分析工具只会按一般的逻辑进行分析,各个状态之间的转换概率就分析不出来了。; A- [) v9 B: I
既然状态机有这么多种写法,在实际工作中采用哪一种呢?我认为三段式和四段式都是可以接受的(我个人习惯四段式的写法)。如果将来有一天综合器对四种写法综合出来的电路都差不多,那读者就可以根据自己的喜好来任意选择了。 ' @4 b* j7 g' o: Y# W# a
上面提到,无论采用哪种写法,模块实现的功能都是完全相同的,倒底是不是呢?我们需要写一个简单的测试激励(testbench)来验证一下。6 o5 U6 h2 s7 ^. g ^
& B( Z6 \7 D, f. y9 A
q+ i: j5 m# e, U: ~; j
- ^( f, R! O& U0 _3 d% C
- /*
- * 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
- ) g; `7 s+ C* l' U+ `7 S2 J
) J+ s2 a) p3 m
: W9 j c& [9 Y' o1 H% G0 \
& B, t8 j3 O; V3 K7,modelsim下的波形
2 ^3 n; R) c- T, u
" h( G8 Y+ n$ Q1 z, N4 L
& Q' b* E3 x l! Q/ h# A/ T& H+ c
" K$ [! T2 y Y* e0 U8 |" k$ f' n1 P
3 x' u6 n$ K, d8 m! P. w5 X8,ncsim的波形7 c( F' T r4 k3 m$ v* ^5 ]5 a
$ G" _* R3 ? N) f$ O
上面是用windows下的modelsim得到的仿真波形,如果我们用ncsim(IUS),并且在Linux下,我们最好写一个简单的脚本来进行仿真,提高工作效率。
& B+ j% n- k* [9 t1 |4 O6 y" d/ |9 h8 ], s, C: b4 u3 O
- {$ N" T. a; I) e1 C t; U0 c' y- #! /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!"
5 M4 Z/ e: J$ S, v1 `7 k4 d$ M
/ b# _3 Y! c* q- U( l% W J9 f t. h5 ?. r
+ Q/ J5 k6 R$ p" g7 Z0 @5 ]2 H运行脚本: 6 _! r9 {5 f; i0 f
- D1 h1 s: S! J) ]5 F2 B8 d0 x* [- ./fsm.sh c
- ./fsm.sh w8 t. p( P9 W! n
6 q8 K* U$ K# j$ g6 J2 y4 U: Q" j3 j2 F! a5 L3 f4 l* O' Y' T
+ e& I- G+ p3 X( l5 A6 m2 |执行:8 C6 ^/ ?( d. c* k$ Z
; c4 P$ G$ V/ ]) C
simvision wave/wave.trn
8 S) o4 I) H/ Z2 Z3 U0 j6 U
7 l: v( g9 T! t
) f- y, {5 }7 b4 J即可得到仿真波形,如下所示:' b. h: n5 d( I' z
5 p: P& ~. o/ f* \5 E
! m( a# z* X' T" c& b3 i1 K$ L' v" v
& [+ ?. v% E3 I) k5 T" y
从中可以看出,ncsim和modelsim得到的仿真波形有所不同。原因在于前面的三种三段式写法是寄存器输出,第四种是组合逻辑输出。
' w6 O* M8 E+ ^' ^
$ h! q: s5 Z6 g$ z7 P
% c- e2 v9 F5 O4 w9 F: n( Y7 Y* j4 \! M2 n, g7 s T
$ }$ v$ r a& d! P0 L8 Y
9 v( a, @+ x2 {. {
( a% C/ c5 i) i b, V$ s1 f" U |
|