找回密码
 注册
关于网站域名变更的通知
查看: 316|回复: 1
打印 上一主题 下一主题

你知道状态机的四种写法都是什么吗?

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-7-2 11:17 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
引言5 }) C, X' G! m+ {4 _  M1 ], t7 r  o
2 L$ [1 N7 Z/ M3 V0 r
在实际的数字电路设计中,状态机是最常用的逻辑,而且往往是全部逻辑的核心部分,所以状态机的质量,会在比较大的程度上影响整个电路的质量。6 Q0 r' X% I# N. W( l: A8 w- f

$ x6 W4 z% i/ Y( R/ h2 T9 ^9 l& @7 w本小节我们通过一个简单的例子(三进制脉动计数器)来说明一下状态机的4中写法。
- s! a0 {, Z) i8 E$ W! {+ s& y! R  F6 _( L, r0 \" U, S( w
1,模块功能6 ~4 h: j, [/ P% J# A% C

4 r; p6 Q; E/ Q# c! b由于我们的目的在于说明状态机的写作方式,所以其逻辑越简单有利于理解。就是一个简单的脉动计数器,每个三个使能信号输出一个标示信号。* N! z$ g" Z: q/ y/ r
( O- u& {8 {- I0 F8 H

, J% h$ _6 U+ Z, O% H% T$ p+ n5 y1 w$ {; S: p3 }
2,一段式
6 Y8 M: D# V, ^7 t; H3 V$ U% ?
0 G' i! Q9 i' e* D3 Q3 ?+ |状态机的写法,一般有四种,即一段式,两段式,三段式,四段式。对于一段式的写法,整个状态机的状态转移、转移条件、对应状态的输出都写在一个always块里,故称‘一段式’。那么,脉动计数器状态机的一段式写法该怎么写呢?如下所示:7 @1 ?9 g- n/ }% W. Y
: s6 P- U' [& P! ~2 ^  `1 \
! a, g7 i1 D, e2 F2 N+ s: ]. l, ]

+ p. q, S( L2 \$ z4 V* _
  • /*
  • * 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
  • . e3 w$ g+ \1 C5 o* u: Y0 V$ ]
+ {* t5 Y4 R* P2 W3 _0 j) \
( R: [2 _. ^7 H& k2 X

% M5 p# P4 N- J! W" S) \/ O3,两段式
2 W4 k% C% @2 @" @( N1 y, v
( P# j; R3 U9 k9 V- T, T状态机的另外一种写法是‘两段式’的。两段式的写法,整个状态机由两个always块组成,第一个块只负责状态转移,第二个块负责转移条件和对应状态的输出。其中第一个块是时序逻辑,第二个块是组合逻辑。脉动计数器状态机的两段式写法又是怎样的呢?
! q8 V1 t! u. i- _" S, ^1 z2 U  j5 M# H/ j# ^/ H; P
( y* g* B3 u4 u8 _/ G

4 B7 b) R- D* T. S
  • /*
  • * 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 i0 c$ x5 P% m) A9 R0 C) p
+ P) n( F6 o9 G0 B( x$ I. u: q# d* ~
/ _* F/ V8 b: G9 i

" I: l* x4 g. t8 z/ F4,三段式# q6 @6 S3 y2 }& ~* }! D: q

! t8 t+ j3 M6 U8 a) Q从上面可以看出,两段式的写法是从一段式发展而来的,将一段式的写法中将状态转移部分提取出来,作为一个独立的always块,就变成了两段式。按照这个思路继续推进,如果将两段式的第二个块中的转移条件提取出来,也作为一个独立的块,就变成了‘三段式’,三段式的写法中,状态转移块是时序逻辑,转移条件块是组合逻辑,对应状态的输出是时序逻辑。那么,脉动计数器状态机的三段式写法是怎样的呢?% O' Q% k. J8 T/ }

% j0 T" F; r2 l. p) h- c0 C7 G/ v, b8 U4 d# P
; J" x% q! s' F
  • /*
  • * 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

  • + o/ Z8 x" U- q  t5 X

5 t1 F/ {; s% I! K. _
5 a! I% u" j2 `/ b
5 Y, w/ n: U9 a8 b/ [4 ?5,四段式4 Y- A) k! o4 `% S, m% J3 A

* t! X" T# h8 ~& j上面的三种状态机的写法是我们经常提到的,也是经典的三种。这三种写法在逻辑上是完全等价的,也就是是说,无论采用哪种写法,模块的功能都是一样的,但前两种一般只出现在教科书中,在实际的项目中是很少见到的。原因在于生成网表的综合器,由于目前的综合器还不够智能,其优化算法对三种写法的敏感度不同,造成最终生成的电路有所区别,有时候区别较大,尤其是对于复杂的状态机。无数血与泪的实践证明,使用前面两种写法生成的电路在时序、性能、功耗和面积等方面的表现都不如三段式的写法,所以即使三段式的写法会让你多敲几次键盘,在实际的电路设计中尽量采用三段式的写法来描述状态机,多敲的那几次键盘换来的电路质量的提高是完全值得的。/ }3 M* P" W& p
俗话说,“没有最好,只有更好”。三段式的写法是不是最好的呢?我认为不见得如此。上面说到,如果采用三段式的写法,代码会变长,如果是大的状态机,结果会更明显。那么,有没有一种写法,既能产生优质的电路,又能少敲几次键盘呢?答案是肯定的。
9 K% H! z2 A, i仔细观察上面三种写法,你会发现,无论是哪种写法,都会使用case语句,case语句不仅占用的代码行数最多,而且综合器对case语句还有不同的解析(full case和parallel case),如果我们将三段式的写法中的case语句换成assign语句,并将状态转移块进一步将当前状态和下一个状态拆分开,就变成了“四段式”,四段式的写法由状态识别,状态转移,转移条件和对应状态的输出四部分组成。那么,脉动计数器状态机四段式的写法又是如何实现的呢?
4 r  j, Y; m8 e% g' @
6 |4 ]8 h6 [& p% c& E$ z+ a6 L5 k0 O- `* V

1 B- K% a; @/ ~$ M& c3 O6 a$ P
  • /*
  • * 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

  • % ^3 m- c) j* s, b0 ?# `

' L9 y: v9 W6 H- S" |! R, T7 Y# P) j  v- O1 I

) w1 b# _7 i5 n0 u/ e  q6,验证
4 {4 S$ N, L6 w( x& h* R2 H
; n5 b  _" [+ L8 w通过对比,我们很容易就会发现,采用四段式写法写出来的状态机,代码数量会减少很多,不仅如此,由于使用的语句类型减少了(只有赋值语句),生成电路的质量也会有所改善。那是否在进行电路设计的时候采用四段式的写法就没有缺点了呢?还有句俗话叫“金无足赤,人无完人”,由于四段式的写法将状态机拆分的过于零散,以至于综合器都识别不出来它是一个状态机了,所以在做覆盖率(coverage)分析的时候,分析工具只会按一般的逻辑进行分析,各个状态之间的转换概率就分析不出来了。
2 U1 x( b1 ?' c" a0 V- x* _* \* d既然状态机有这么多种写法,在实际工作中采用哪一种呢?我认为三段式和四段式都是可以接受的(我个人习惯四段式的写法)。如果将来有一天综合器对四种写法综合出来的电路都差不多,那读者就可以根据自己的喜好来任意选择了。 / t* m0 N' t0 o& \; ]" w9 E
上面提到,无论采用哪种写法,模块实现的功能都是完全相同的,倒底是不是呢?我们需要写一个简单的测试激励(testbench)来验证一下。
* q- n# o2 Z2 }+ S5 N0 c3 E2 L7 d7 W! W

( g: ^6 O4 h/ c$ `7 h" G6 g
) n& w! U% L- t$ G, |' 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
  • 4 g: C" p2 u1 f- E

3 `/ v" `0 F4 }  W
+ n$ b3 F2 {5 E2 l  N' n2 X2 _7 l" s  w7 w
7,modelsim下的波形
/ |+ S+ d8 @7 H7 d
1 ?. j& n+ k- O0 S/ H: {# n# e # u1 L: V, t& e4 e6 L- Q
2 f% R6 t3 B" d  t
8 w7 M' Q4 r& U- }
8,ncsim的波形
$ y4 i) d/ i* y9 c8 `
/ F$ M5 X' l- C# G/ o上面是用windows下的modelsim得到的仿真波形,如果我们用ncsim(IUS),并且在Linux下,我们最好写一个简单的脚本来进行仿真,提高工作效率。
' R4 ~0 `; f8 A; B  _2 O3 q) |# f
* A  w) x" O( ^  h) E# L2 Y1 {
% W2 ?9 J: g9 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 b" w& E+ [6 n; F6 x8 E
4 @* `2 q$ P, B% q9 C( x
6 C% Y" @) V) m  `  s; K! ^. E4 H( @
0 t# d8 k  l$ u  H. G
运行脚本: 3 L" O. k$ |- Z2 Z1 ^! |4 ^

, Y: P) O2 D. M' M! H
  • ./fsm.sh c
  • ./fsm.sh w) b3 E% P, [$ n7 _( {

3 p3 Y- {6 Y9 M" Q& e; L
- h2 w! I0 |8 f: Z  q6 |+ ]9 ]; _, y  L( _3 t1 b
执行:
0 U  L, r! E" a, [- j4 J6 c1 e. g8 |' J% L9 e: K
simvision wave/wave.trn
1 e( T7 u0 H3 h5 c9 T8 G
* x. r. C2 r: X% k: {
- c* |/ l8 @! S. B6 O' F即可得到仿真波形,如下所示:% x3 [- \( M% }
0 M! e4 |+ [% P$ G" w) F4 F

6 n2 l) w' z6 B6 j: X! F9 i. Y8 D
- P) U' @& w, _/ B' r5 d( \* \
6 j. S; ~( c: W. ]5 S2 |$ f从中可以看出,ncsim和modelsim得到的仿真波形有所不同。原因在于前面的三种三段式写法是寄存器输出,第四种是组合逻辑输出。
) i4 G( i+ ~% V" a1 D  P9 i4 N2 y  Y7 X
" G$ x# k' K% X6 E
/ e% g4 v- `+ \5 x4 k6 n# s
" P* L5 h6 P9 u' ^+ H* M2 x

, ~: ~) N; d4 X4 E5 K" O5 M! B. T: }1 o; h+ I

该用户从未签到

2#
发表于 2020-7-2 13:18 | 只看该作者
好详细的文章
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-6-29 11:20 , Processed in 0.078125 second(s), 26 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表