状态机的四种写法

引言

在实际的数字电路设计中,状态机是最常用的逻辑,而且往往是全部逻辑的核心部分,所以状态机的质量,会在比较大的程度上影响整个电路的质量。

本小节我们通过一个简单的例子(三进制脉动计数器)来说明一下状态机的4中写法。


1,模块功能

由于我们的目的在于说明状态机的写作方式,所以其逻辑越简单有利于理解。就是一个简单的脉动计数器,每个三个使能信号输出一个标示信号。


2,一段式

状态机的写法,一般有四种,即一段式,两段式,三段式,四段式。对于一段式的写法,整个状态机的状态转移、转移条件、对应状态的输出都写在一个always块里,故称‘一段式’。那么,脉动计数器状态机的一段式写法该怎么写呢?如下所示:



   
   
  1. /*
  2. * file : fsm1.v
  3. * author: Rill
  4. * date : 2014-05-11
  5. */
  6. module Mfsm1
  7. (
  8. clk,
  9. rst,
  10. enable,
  11. done
  12. );
  13. input wire clk;
  14. input wire rst;
  15. input wire enable;
  16. output reg done;
  17. parameter s_idle = 4'd0;
  18. parameter s_1 = 4'd1;
  19. parameter s_2 = 4'd2;
  20. parameter s_3 = 4'd3;
  21. reg [3:0] state;
  22. always @(posedge clk)
  23. begin
  24. if(rst)
  25. begin
  26. done <=1'b0;
  27. state <= s_idle;
  28. end
  29. else
  30. begin
  31. case( state)
  32. s_idle:
  33. begin
  34. if( enable)
  35. state <= s_1;
  36. done <= 1' b0;
  37. end
  38. s_1:
  39. begin
  40. if( enable)
  41. state <= s_2;
  42. done <= 1' b0;
  43. end
  44. s_2:
  45. begin
  46. if( enable)
  47. begin
  48. state <= s_3;
  49. done <= 1' b1;
  50. end
  51. else
  52. begin
  53. done <= 1' b0;
  54. end
  55. end
  56. s_3:
  57. begin
  58. state <= s_idle;
  59. done <= 1' b0;
  60. end
  61. default:
  62. begin
  63. state <= s_idle;
  64. done <= 1' b0;
  65. end
  66. endcase
  67. end
  68. end
  69. endmodule


3,两段式

状态机的另外一种写法是‘两段式’的。两段式的写法,整个状态机由两个always块组成,第一个块只负责状态转移,第二个块负责转移条件和对应状态的输出。其中第一个块是时序逻辑,第二个块是组合逻辑。脉动计数器状态机的两段式写法又是怎样的呢?



   
   
  1. /*
  2. * file : fsm2.v
  3. * author: Rill
  4. * date : 2014-05-11
  5. */
  6. module Mfsm2
  7. (
  8. clk,
  9. rst,
  10. enable,
  11. done
  12. );
  13. input wire clk;
  14. input wire rst;
  15. input wire enable;
  16. output reg done;
  17. parameter s_idle = 4'd0;
  18. parameter s_1 = 4'd1;
  19. parameter s_2 = 4'd2;
  20. parameter s_3 = 4'd3;
  21. reg [3:0] current_state;
  22. reg [3:0] next_state;
  23. always @(posedge clk)
  24. begin
  25. if(rst)
  26. begin
  27. current_state <= s_idle;
  28. end
  29. else
  30. begin
  31. current_state <= next_state;
  32. end
  33. end
  34. always @(*)
  35. begin
  36. case( current_state)
  37. s_idle:
  38. begin
  39. if( enable)
  40. next_state = s_1;
  41. done = 1' b0;
  42. end
  43. s_1:
  44. begin
  45. if( enable)
  46. next_state = s_2;
  47. done = 1' b0;
  48. end
  49. s_2:
  50. begin
  51. if( enable)
  52. next_state = s_3;
  53. done = 1' b0;
  54. end
  55. s_3:
  56. begin
  57. next_state = s_idle;
  58. done = 1' b1;
  59. end
  60. default:
  61. begin
  62. next_state = s_idle;
  63. done = 1' b0;
  64. end
  65. endcase
  66. end
  67. endmodule



4,三段式

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



   
   
  1. /*
  2. * file : fsm3.v
  3. * author: Rill
  4. * date : 2014-05-11
  5. */
  6. module Mfsm3
  7. (
  8. clk,
  9. rst,
  10. enable,
  11. done
  12. );
  13. input wire clk;
  14. input wire rst;
  15. input wire enable;
  16. output reg done;
  17. parameter s_idle = 4'd0;
  18. parameter s_1 = 4'd1;
  19. parameter s_2 = 4'd2;
  20. parameter s_3 = 4'd3;
  21. reg [3:0] current_state;
  22. reg [3:0] next_state;
  23. always @(posedge clk)
  24. begin
  25. if(rst)
  26. begin
  27. current_state <= s_idle;
  28. end
  29. else
  30. begin
  31. current_state <= next_state;
  32. end
  33. end
  34. always @(*)
  35. begin
  36. case( current_state)
  37. s_idle:
  38. begin
  39. if( enable)
  40. next_state = s_1;
  41. end
  42. s_1:
  43. begin
  44. if( enable)
  45. next_state = s_2;
  46. end
  47. s_2:
  48. begin
  49. if( enable)
  50. next_state = s_3;
  51. end
  52. s_3:
  53. begin
  54. next_state = s_idle;
  55. end
  56. default:
  57. begin
  58. next_state = s_idle;
  59. end
  60. endcase
  61. end
  62. always @( posedge clk)
  63. begin
  64. if( rst)
  65. begin
  66. done <= 1' b0;
  67. end
  68. else
  69. begin
  70. case( next_state)
  71. s_idle:
  72. begin
  73. done <= 1' b0;
  74. end
  75. s_1:
  76. begin
  77. done <= 1' b0;
  78. end
  79. s_2:
  80. begin
  81. done <= 1' b0;
  82. end
  83. s_3:
  84. begin
  85. done <= 1' b1;
  86. end
  87. default:
  88. begin
  89. done <= 1' b0;
  90. end
  91. endcase
  92. end
  93. end
  94. endmodule



5,四段式

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



   
   
  1. /*
  2. * file : fsm4.v
  3. * author: Rill
  4. * date : 2014-05-11
  5. */
  6. module Mfsm4
  7. (
  8. clk,
  9. rst,
  10. enable,
  11. done
  12. );
  13. input wire clk;
  14. input wire rst;
  15. input wire enable;
  16. output done;
  17. parameter s_idle = 4'd0;
  18. parameter s_1 = 4'd1;
  19. parameter s_2 = 4'd2;
  20. parameter s_3 = 4'd3;
  21. reg [3:0] current_state;
  22. wire c_idle = (current_state == s_idle);
  23. wire c_1 = (current_state == s_1);
  24. wire c_2 = (current_state == s_2);
  25. wire c_3 = (current_state == s_3);
  26. wire n_idle = c_3;
  27. wire n_1 = c_idle & enable;
  28. wire n_2 = c_1 & enable;
  29. wire n_3 = c_2 & enable;
  30. wire [3:0] next_state = {4{n_idle}} & s_idle |
  31. {4{n_1}} & s_1 |
  32. {4{n_2}} & s_2 |
  33. {4{n_3}} & s_3;
  34. always @(posedge clk)
  35. begin
  36. if(rst)
  37. current_state <= s_idle;
  38. else if( n_idle | n_1 | n_2 | n_3)
  39. current_state = next_state;
  40. end
  41. assign done = c_3;
  42. endmodule



6,验证

通过对比,我们很容易就会发现,采用四段式写法写出来的状态机,代码数量会减少很多,不仅如此,由于使用的语句类型减少了(只有赋值语句),生成电路的质量也会有所改善。那是否在进行电路设计的时候采用四段式的写法就没有缺点了呢?还有句俗话叫“金无足赤,人无完人”,由于四段式的写法将状态机拆分的过于零散,以至于综合器都识别不出来它是一个状态机了,所以在做覆盖率(coverage)分析的时候,分析工具只会按一般的逻辑进行分析,各个状态之间的转换概率就分析不出来了。
既然状态机有这么多种写法,在实际工作中采用哪一种呢?我认为三段式和四段式都是可以接受的(我个人习惯四段式的写法)。如果将来有一天综合器对四种写法综合出来的电路都差不多,那读者就可以根据自己的喜好来任意选择了。 
上面提到,无论采用哪种写法,模块实现的功能都是完全相同的,倒底是不是呢?我们需要写一个简单的测试激励(testbench)来验证一下。



   
   
  1. /*
  2. * file : tb.v
  3. * author: Rill
  4. * date : 2014-05-11
  5. */
  6. module tb;
  7. reg clk;
  8. reg rst;
  9. reg enable;
  10. wire done1;
  11. wire done2;
  12. wire done3;
  13. wire done4;
  14. Mfsm1 fsm1
  15. (
  16. .clk(clk),
  17. .rst (rst),
  18. .enable(enable),
  19. .done(done1)
  20. );
  21. Mfsm2 fsm2
  22. (
  23. .clk(clk),
  24. .rst (rst),
  25. .enable(enable),
  26. .done(done2)
  27. );
  28. Mfsm3 fsm3
  29. (
  30. .clk(clk),
  31. .rst (rst),
  32. .enable(enable),
  33. .done(done3)
  34. );
  35. Mfsm4 fsm4
  36. (
  37. .clk(clk),
  38. .rst (rst),
  39. .enable(enable),
  40. .done(done4)
  41. );
  42. always #1 clk = ~clk;
  43. integer loop;
  44. initial
  45. begin
  46. clk = 0;
  47. rst = 0;
  48. enable = 0;
  49. loop = 0;
  50. repeat (10) @(posedge clk);
  51. rst = 1;
  52. repeat (4) @(posedge clk);
  53. rst = 0;
  54. repeat (100) @(posedge clk);
  55. for(loop=1;loop <10;loop=loop+1)
  56. begin
  57. enable = 1;
  58. @(posedge clk);
  59. enable = 0;
  60. @(posedge clk);
  61. end
  62. repeat (100) @(posedge clk);
  63. $stop;
  64. end
  65. endmodule


7,modelsim下的波形




8,ncsim的波形

上面是用windows下的modelsim得到的仿真波形,如果我们用ncsim(IUS),并且在Linux下,我们最好写一个简单的脚本来进行仿真,提高工作效率。



   
   
  1. #! /bin/bash
  2. #
  3. # fsm.sh
  4. # usage: ./fsm.sh c/w/r
  5. # Rill create 2014-09-03
  6. #
  7. TOP_MODULE=tb
  8. tcl_file=run.tcl
  9. if [ $# != 1 ];then
  10. echo "args must be c/w/r"
  11. exit 0
  12. fi
  13. if [ $1 == "c" ]; then
  14. echo "compile lib..."
  15. ncvlog -f ./vflist -sv -update -LINEDEBUG;
  16. ncelab -delay_mode zero -access +rwc -timescale 1ns/10ps ${TOP_MODULE}
  17. exit 0
  18. fi
  19. if [ -e ${tcl_file} ];then
  20. rm ${tcl_file} -f
  21. fi
  22. touch ${tcl_file}
  23. if [ $1 == "w" ];then
  24. echo "open wave..."
  25. echo "database -open waves -into waves.shm -default;" >> ${tcl_file}
  26. echo "probe -shm -variable -all -depth all;" >> ${tcl_file}
  27. echo "run" >> ${tcl_file}
  28. echo "exit" >> ${tcl_file}
  29. fi
  30. if [ $1 == "w" -o $1 == "r" ];then
  31. echo "sim start..."
  32. ncsim ${TOP_MODULE} -input ${tcl_file}
  33. fi
  34. echo "$(date) sim done!"

运行脚本: 



   
   
  1. ./fsm.sh c
  2. ./fsm.sh w

执行:


simvision wave/wave.trn
   
   

即可得到仿真波形,如下所示:




从中可以看出,ncsim和modelsim得到的仿真波形有所不同。原因在于前面的三种三段式写法是寄存器输出,第四种是组合逻辑输出。


             转自https://blog.csdn.net/rill_zhen/article/details/39585367
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值