状态机的Verilog写法

一、状态机分类

1.Moore型:状态机的状态变化仅和当前状态有关;时序逻辑电路的输出只取决于当前状态。设计高速电路时常用此类状态机,把状态变化直接用作输出。

2.Mealy型:状态机的状态变化不仅与当前的状态有关,还取决于当前的输入条件;时序逻辑的输出不但取决于状态还取决于输入。平常使用较多的是此类状态机。

“其实这几种状态机之间,只要做一些改变,便可以从一种形式转变为另一种形式。把状态机精确的分为这类或那类,其实并不重要,重要的是设计者如何把握输出的结构能满足设计的整体目标,包括定时的准确性和灵活性。”

二、状态机编码

状态机的参数定义采用的都是独热码,和格雷码相比,虽然独热码多用了触发器,但所用组合电路可以省一些,因而使电路的速度和可靠性有显著提高,而总的单元数并无显著增加。采用独热编码后有了多余的状态,就有一些不可达到的状态。为此在case语句的最后需要增加default分支向。这可以用默认项表示该项,也可以用确定项表示,以确保回到初始状态。一般综合器都可以通过综合指令的控制来合理地处理默认项。

三、实例分析

状态机一般有三种不同的写法,即一段式、两段式和三段式的状态机写法,他们在速度、面积、代码可维护性等各个方面互有优劣,不要对任何一种写法给出“一棍子打死”的定论。手头上刚好有一个状态机的例子,借此记录一下三种状态机的Verilog写法。

3.1 要求

售货机里有价值4元的脉动饮料,支持1元和2元硬币。请设计一个状态机,检测投入的硬币,当累计投入币值大于等于脉动价格时,售货机自动找零并弹出1瓶脉动饮料。硬币和商品都是一个一个的进出,不会出现一次性投很多个硬币弹出很多瓶脉动的情况。

信号含义
clk时钟信号
rst_n复位信号
in输入信号,币值,有1和2两种,投钱
out输出信号,币值,有1和2两种,找零
out_vld输出信号,脉动,为1则输出1瓶脉动
3.2 状态转移图

根据要求,我们先把状态转移图画出来,绘画软件:Visio,如果没有安装也可以用wps自带应用的“流程图”功能:

状态机的Verilog写法 - 第1张
3.3 testbench
  1. `timescale 1ns/1ps //时间精度
  2. `define Clock 20 //时钟周期
  3. module FSM_3_tb;
  4. //--------------------< 端口 >------------------------------------------
  5. reg clk ;
  6. reg rst_n ;
  7. reg [1:0] in ;
  8. wire [1:0] out ;
  9. wire out_vld ;
  10. //----------------------------------------------------------------------
  11. //-- 模块例化
  12. //----------------------------------------------------------------------
  13. FSM_3 u_FSM_3
  14. (
  15. .clk (clk ),
  16. .rst_n (rst_n ),
  17. .in (in ),
  18. .out (out ),
  19. .out_vld (out_vld )
  20. );
  21. //----------------------------------------------------------------------
  22. //-- 状态机名称查看器
  23. //----------------------------------------------------------------------
  24. localparam S0 = 4'b0001 ;
  25. localparam S1 = 4'b0010 ;
  26. localparam S2 = 4'b0100 ;
  27. localparam S3 = 4'b1000 ;
  28. //2字符16位
  29. reg [15:0] state_name ;
  30. always@(*)begin
  31. case(u_FSM_3.state_c)
  32. S0: state_name = "S0";
  33. S1: state_name = "S1";
  34. S2: state_name = "S2";
  35. S3: state_name = "S3";
  36. default:state_name = "S0";
  37. endcase
  38. end
  39. //----------------------------------------------------------------------
  40. //-- 时钟信号和复位信号
  41. //----------------------------------------------------------------------
  42. initial begin
  43. clk = 1;
  44. forever
  45. #(`Clock/2) clk = ~clk;
  46. end
  47. initial begin
  48. rst_n = 0; #(`Clock*20+1);
  49. rst_n = 1;
  50. end
  51. //----------------------------------------------------------------------
  52. //-- 设计输入信号
  53. //----------------------------------------------------------------------
  54. initial begin
  55. #1;
  56. in = 0;
  57. #(`Clock*20+1); //初始化完成
  58. //情况1--------------------------
  59. in = 1; //1块钱
  60. #(`Clock*1);
  61. in = 0;
  62. #(`Clock*1);
  63. in = 1; //1块钱
  64. #(`Clock*1);
  65. in = 0;
  66. #(`Clock*1);
  67. in = 1; //1块钱
  68. #(`Clock*1);
  69. in = 0;
  70. #(`Clock*1);
  71. in = 1; //1块钱
  72. #(`Clock*1);
  73. in = 0;
  74. #(`Clock*10);
  75. //情况2--------------------------
  76. in = 1; //1块钱
  77. #(`Clock*1);
  78. in = 0;
  79. #(`Clock*1);
  80. in = 1; //1块钱
  81. #(`Clock*1);
  82. in = 0;
  83. #(`Clock*1);
  84. in = 1; //1块钱
  85. #(`Clock*1);
  86. in = 0;
  87. #(`Clock*1);
  88. in = 2; //2块钱
  89. #(`Clock*1);
  90. in = 0;
  91. #(`Clock*10);
  92. //情况3--------------------------
  93. in = 1; //1块钱
  94. #(`Clock*1);
  95. in = 0;
  96. #(`Clock*1);
  97. in = 1; //1块钱
  98. #(`Clock*1);
  99. in = 0;
  100. #(`Clock*1);
  101. in = 2; //2块钱
  102. #(`Clock*1);
  103. in = 0;
  104. #(`Clock*10);
  105. //情况4--------------------------
  106. in = 1; //1块钱
  107. #(`Clock*1);
  108. in = 0;
  109. #(`Clock*1);
  110. in = 2; //2块钱
  111. #(`Clock*1);
  112. in = 0;
  113. #(`Clock*1);
  114. in = 2; //2块钱
  115. #(`Clock*1);
  116. in = 0;
  117. #(`Clock*10);
  118. //情况5--------------------------
  119. in = 2; //2块钱
  120. #(`Clock*1);
  121. in = 0;
  122. #(`Clock*1);
  123. in = 2; //2块钱
  124. #(`Clock*1);
  125. in = 0;
  126. #(`Clock*10);
  127. $stop;
  128. end
  129. endmodule
3.4 代码设计
(1)一段式状态机

只定义一个转移状态:state,总体结构是一段always时序逻辑,用于描述状态转移和输出。由于是时序逻辑能够自动保持,所以可以省略else。但建议在初始状态时(例如下文的S0),else处赋一下初始值。

  1. //======================================================================
  2. // --- 名称 : FSM_1
  3. // --- 作者 : xianyu_FPGA
  4. // --- 日期 : 2018-12-15
  5. // --- 描述 : 售货机练习,采用一段式状态机
  6. //======================================================================
  7. module FSM_1
  8. //---------------------<端口声明>---------------------------------------
  9. (
  10. input clk ,
  11. input rst_n ,
  12. input [1:0] in ,
  13. output reg [1:0] out ,
  14. output reg out_vld
  15. );
  16. //---------------------<信号定义>---------------------------------------
  17. reg [3:0] state ;
  18. //---------------------<状态机参数>-------------------------------------
  19. localparam S0 = 4'b0001 ;
  20. localparam S1 = 4'b0010 ;
  21. localparam S2 = 4'b0100 ;
  22. localparam S3 = 4'b1000 ;
  23. //----------------------------------------------------------------------
  24. //-- 状态机第1段
  25. //----------------------------------------------------------------------
  26. always@(posedge clk or negedge rst_n)begin
  27. if(!rst_n)begin
  28. state <= S0;
  29. out <= 0 ;
  30. out_vld <= 0 ;
  31. end
  32. else begin
  33. case(state)
  34. S0: begin
  35. if(in==1)begin
  36. state <= S1;
  37. end
  38. else if(in==2)begin
  39. state <= S2;
  40. end
  41. else begin
  42. out <= 0 ;
  43. out_vld <= 0 ;
  44. end
  45. end
  46. S1: begin
  47. if(in==1)begin
  48. state <= S2;
  49. end
  50. else if(in==2)begin
  51. state <= S3;
  52. end
  53. end
  54. S2: begin
  55. if(in==1)begin
  56. state <= S3;
  57. end
  58. else if(in==2)begin
  59. state <= S0;
  60. out_vld <= 1 ;
  61. end
  62. end
  63. S3: begin
  64. if(in==1)begin
  65. state <= S0;
  66. out_vld <= 1 ;
  67. end
  68. else if(in==2)begin
  69. state <= S0;
  70. out <= 1 ;
  71. out_vld <= 1 ;
  72. end
  73. end
  74. default:state <= S0;
  75. endcase
  76. end
  77. end
  78. endmodule

仿真波形如下:

状态机的Verilog写法 - 第2张

结论:波形和预想一致!

(2)二段式状态机

二段式状态机,第一段用时序逻辑描述state_c(现态)和state_n(次态),第二段用组合逻辑描述状态转移和输出。由于是组合逻辑,为避免产生锁存器,else处一定要写上 if 中说使用了的信号。

  1. //======================================================================
  2. // --- 名称 : FSM_2
  3. // --- 作者 : xianyu_FPGA
  4. // --- 日期 : 2018-12-15
  5. // --- 描述 : 售货机练习,采用二段式状态机
  6. //======================================================================
  7. module FSM_2
  8. //---------------------<端口声明>---------------------------------------
  9. (
  10. input clk ,
  11. input rst_n ,
  12. input [1:0] in ,
  13. output reg [1:0] out ,
  14. output reg out_vld
  15. );
  16. //---------------------<信号定义>---------------------------------------
  17. reg [3:0] state_c ;
  18. reg [3:0] state_n ;
  19. //---------------------<状态机参数>-------------------------------------
  20. localparam S0 = 4'b0001 ;
  21. localparam S1 = 4'b0010 ;
  22. localparam S2 = 4'b0100 ;
  23. localparam S3 = 4'b1000 ;
  24. //----------------------------------------------------------------------
  25. //-- 状态机第1段
  26. //----------------------------------------------------------------------
  27. always@(posedge clk or negedge rst_n)begin
  28. if(!rst_n)
  29. state_c <= S0;
  30. else
  31. state_c <= state_n;
  32. end
  33. //----------------------------------------------------------------------
  34. //-- 状态机第2段
  35. //----------------------------------------------------------------------
  36. always@(*)begin
  37. case(state_c)
  38. S0: begin
  39. if(in==1)begin
  40. state_n = S1;
  41. end
  42. else if(in==2)begin
  43. state_n = S2;
  44. end
  45. else begin
  46. state_n = state_c;
  47. out = 0 ;
  48. out_vld = 0 ;
  49. end
  50. end
  51. S1: begin
  52. if(in==1)begin
  53. state_n = S2;
  54. end
  55. else if(in==2)begin
  56. state_n = S3;
  57. end
  58. else begin
  59. state_n = state_c;
  60. end
  61. end
  62. S2: begin
  63. if(in==1)begin
  64. state_n = S3;
  65. end
  66. else if(in==2)begin
  67. state_n = S0;
  68. out_vld = 1 ;
  69. end
  70. else begin
  71. state_n = state_c;
  72. out_vld = 0;
  73. end
  74. end
  75. S3: begin
  76. if(in==1)begin
  77. state_n = S0;
  78. out_vld = 1 ;
  79. end
  80. else if(in==2)begin
  81. state_n = S0;
  82. out = 1 ;
  83. out_vld = 1 ;
  84. end
  85. else begin
  86. state_n = state_c;
  87. out = 0;
  88. out_vld = 0;
  89. end
  90. end
  91. default:state_n = S0;
  92. endcase
  93. end
  94. endmodule

仿真波形如下所示:

状态机的Verilog写法 - 第3张

结论:波形和预想一致!但是产生了毛刺,这也是二段式状态机的缺点。

毛刺产生原因:状态机通常包含主控时序进程、主控组合进程和辅助进程三个部分。其中,主控组合进程的任务是根据外部输入的控制信号和当前状态的状态值确定下一 状态的取向,并确定对外输出内容和对内部其他组合或时序进程输出控制信号的内容。一方面,由于有组合逻辑进程的存在,状态机输出信号会出现毛刺——竞争冒险现象;另一方面,如果状态信号是多位值的,则在电路中对应了多条信号线。由于存在传输延迟,各信号线上的值发生改变的时间则存在先后,从而使得状态迁移时在初始状态和目的状态之间出现临时状态——毛刺。

 简单理解为:state_n 会因为组合逻辑原因不断出现临时状态,这些状态是无效的,而输出也因为组合逻辑原因产生这些临时状态,即毛刺。

(3)三段式状态机

三段式状态机,第一段用时序逻辑描述state_c(现态)和state_n(次态),第二段用组合逻辑描述状态转移,第三段用时序逻辑描述输出,第三段可以是多个always块。

  1. //======================================================================
  2. // --- 名称 : FSM_3
  3. // --- 作者 : xianyu_FPGA
  4. // --- 日期 : 2018-12-15
  5. // --- 描述 : 售货机练习,采用三段式状态机
  6. //======================================================================
  7. module FSM_3
  8. //---------------------<端口声明>---------------------------------------
  9. (
  10. input clk ,
  11. input rst_n ,
  12. input [1:0] in ,
  13. output reg [1:0] out ,
  14. output reg out_vld
  15. );
  16. //---------------------<信号定义>---------------------------------------
  17. reg [3:0] state_c ;
  18. reg [3:0] state_n ;
  19. //---------------------<状态机参数>-------------------------------------
  20. localparam S0 = 4'b0001 ;
  21. localparam S1 = 4'b0010 ;
  22. localparam S2 = 4'b0100 ;
  23. localparam S3 = 4'b1000 ;
  24. //----------------------------------------------------------------------
  25. //-- 状态机第1段
  26. //----------------------------------------------------------------------
  27. always @(posedge clk or negedge rst_n)begin
  28. if(!rst_n)
  29. state_c <= S0;
  30. else
  31. state_c <= state_n;
  32. end
  33. //----------------------------------------------------------------------
  34. //-- 状态机第2段
  35. //----------------------------------------------------------------------
  36. always @(*)begin
  37. case(state_c)
  38. S0: begin
  39. if(in==1)
  40. state_n = S1;
  41. else if(in==2)
  42. state_n = S2;
  43. else
  44. state_n = state_c;
  45. end
  46. S1: begin
  47. if(in==1)
  48. state_n = S2;
  49. else if(in==2)
  50. state_n = S3;
  51. else
  52. state_n = state_c;
  53. end
  54. S2: begin
  55. if(in==1)
  56. state_n = S3;
  57. else if(in==2)
  58. state_n = S0;
  59. else
  60. state_n = state_c;
  61. end
  62. S3: begin
  63. if(in==1 || in==2) // in != 0也行
  64. state_n = S0;
  65. else
  66. state_n = state_c;
  67. end
  68. default:state_n = S0;
  69. endcase
  70. end
  71. //----------------------------------------------------------------------
  72. //-- 状态机第3段
  73. //----------------------------------------------------------------------
  74. //找零钱
  75. always @(posedge clk or negedge rst_n)begin
  76. if(!rst_n)
  77. out <= 0;
  78. else if(state_c==S3 && in==2)
  79. out <= 1;
  80. else
  81. out <= 0;
  82. end
  83. //输出脉动
  84. always @(posedge clk or negedge rst_n)begin
  85. if(rst_n==1'b0)
  86. out_vld <= 0;
  87. else if((state_c==S2 && in==2) || (state_c==S3 && in!=0))
  88. out_vld <= 1;
  89. else
  90. out_vld <= 0;
  91. end
  92. endmodule

仿真波形如下所示:

状态机的Verilog写法 - 第4张

结论:波形和预想一致!这也是较多书籍推荐的写法。

(4)一段式和三段式结合的状态机

状态机,只定义一个转移状态:state。第一段用时序逻辑描述state状态转移,第二段用时序逻辑描述输出,第二段可以是多个always块。由于是时序逻辑能够自动保持,所以可以省略else。这种状态机的优点是既消除了组合逻辑可能产生的毛刺,又减少了代码量。

  1. module FSM_V3
  2. //---------------------<端口声明>---------------------------------------
  3. (
  4. input clk ,
  5. input rst_n ,
  6. input [1:0] in ,
  7. output reg [1:0] out ,
  8. output reg out_vld
  9. );
  10. //---------------------<信号定义>---------------------------------------
  11. reg [3:0] state ;
  12. //---------------------<状态机参数>-------------------------------------
  13. localparam S0 = 4'b0001 ;
  14. localparam S1 = 4'b0010 ;
  15. localparam S2 = 4'b0100 ;
  16. localparam S3 = 4'b1000 ;
  17. //----------------------------------------------------------------------
  18. //-- 状态机
  19. //----------------------------------------------------------------------
  20. always @(posedge clk or negedge rst_n)begin
  21. if(!rst_n)
  22. state <= S0;
  23. else begin
  24. case(state)
  25. S0: begin
  26. if(in==1)
  27. state <= S1;
  28. else if(in==2)
  29. state <= S2;
  30. end
  31. S1: begin
  32. if(in==1)
  33. state <= S2;
  34. else if(in==2)
  35. state <= S3;
  36. end
  37. S2: begin
  38. if(in==1)
  39. state <= S3;
  40. else if(in==2)
  41. state <= S0;
  42. end
  43. S3: begin
  44. if(in==1 || in==2) // in != 0也行
  45. state <= S0;
  46. end
  47. default:state <= S0;
  48. endcase
  49. end
  50. end
  51. //----------------------------------------------------------------------
  52. //-- 输出
  53. //----------------------------------------------------------------------
  54. //找零钱
  55. always @(posedge clk or negedge rst_n)begin
  56. if(!rst_n)
  57. out <= 0;
  58. else if(state==S3 && in==2)
  59. out <= 1;
  60. else
  61. out <= 0;
  62. end
  63. //输出脉动
  64. always @(posedge clk or negedge rst_n)begin
  65. if(rst_n==1'b0)
  66. out_vld <= 0;
  67. else if((state==S2 && in==2) || (state==S3 && in!=0))
  68. out_vld <= 1;
  69. else
  70. out_vld <= 0;
  71. end
  72. endmodule

仿真波形如下所示:

状态机的Verilog写法 - 第5张

结论:波形和预想一致!

四、状态机名称查看器

可以看到,我的Modelsim波形中出现了一个信号state_name,里面显示了状态机的名称,这是怎么做到的呢?方法有很多种,这里介绍两种。

4.1 testbench法

testbench里增加一段参数转ASCII码的代码,如下所示:

  1. //----------------------------------------------------------------------
  2. //-- 状态机名称查看器
  3. //----------------------------------------------------------------------
  4. localparam S0 = 4'b0001 ;
  5. localparam S1 = 4'b0010 ;
  6. localparam S2 = 4'b0100 ;
  7. localparam S3 = 4'b1000 ;
  8. //2字符16位
  9. reg [15:0] state_name ;
  10. always@(*)begin
  11. case(u_FSM_3.state_c)
  12. S0: state_name = "S0";
  13. S1: state_name = "S1";
  14. S2: state_name = "S2";
  15. S3: state_name = "S3";
  16. default:state_name = "S0";
  17. endcase
  18. end

在Modelsim中点击信号state_name,右键选择用ASSIC码查看就可以看到状态机的名称,而不再是头疼的的0001、0010等字符。编写时注意一下位宽,一个ASSIC码字符宽度是8位,例如“S0”有2个字符则需要16位宽。

4.2 do/tcl文件法

首先你得学会怎么使用Modelsim的自动化脚本仿真,那么我们只要再do文件中加入这段代码即可:

  1. # ======================================================================
  2. # == 状态机名称查看器
  3. # ======================================================================
  4. # 结构体设置
  5. virtual type {
  6. {4'b0001 S0}
  7. {4'b0010 S1}
  8. {4'b0100 S2}
  9. {4'b1000 S3}
  10. } fsm_type;
  11. # 结构体和信号名关联,命名为state_name
  12. virtual function {(fsm_type)/fsm_tb/u_fsm/state} state_name
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/8266.html

"愿我的文字能带给您一丝美好"

还没有人赞赏,支持一下

评论

A 为本文作者,G 为游客总数:0
加载中…

提交评论

游客,您好,欢迎参与讨论。

我的购物车

购物车为空

优惠券

没有优惠券