当前位置:   article > 正文

FPGA状态机详解_fpga状态机怎么跳出去

fpga状态机怎么跳出去

FPGA的特点是并行执行,但如果需要处理一些具有前后顺序的事件,就需要使用状态机。状态机是一种用于处理具有前后顺序的事件的计算机模型,包含现态、条件、动作和次态四个要素,它可以将一个复杂的控制流程分解成多个互相独立的状态,从而简化设计过程并提高了系统的可靠性和性能。本文将对FPGA状态机进行详细介绍,帮助大家了解状态机的设计和应用。

一、FPGA状态机基础

1、基础概念

FPGA状态机是一种能够描述对象在运行周期内的所有状态,以及从一个状态到另一个状态转换的过程的抽象模型。状态机可归纳为4个要素,即现态、条件、动作、次态。

①现态:当前所处的状态。

②条件:当一个条件被满足,将会触发一个动作,或者执行一次运行状态的变化。

③动作:条件满足后执行的动作。动作不是必需的,也可以直接迁移到新状态而不进行任何动作。

④次态:条件满足后要跳转到的新状态。其中,“次态”是相对于“现态”而言的,一旦被跳转后,“次态”就转变成新的“现态”了。

2、状态机分类

通常情况下,FPGA状态机一般有两种类型:  

  • Moore型状态机:下一状态只由当前状态决定 。

  • Mealy 型状态机:下一状态不但与当前状态有关,还与当前输入值有关 。

由于Mealy型状态机的输出与输入有关,输出信号很容易出现毛刺,所以一般采用Moore型状态机。

(1)Mealy状态机

输出逻辑不但取决于当前“状态”还取决于“输入”,如图所示。

(2)Moore状态机

输出逻辑仅仅取决于当前状态,且与当前时刻的输入无关,如图所示。

​二、FPGA状态机实现方式

FPGA状态机的描述方式主要分为3种,分别是一段式、两段式、三段式。

1、一段式状态机

一段式状态机使用1个always块,把状态跳转和寄存器输出逻辑都写在一起,其输出是寄存器输出,无毛刺,但是这种方式代码较混乱,逻辑不清晰,难于修改和调试,应该尽量避免使用。

下面给出一个一段式的Mealy状态机示例:

  1. module one_state_machine (
  2. input clk,
  3. input rst_n,
  4. input [1:0] inp,
  5. output reg outp
  6. );
  7. // 定义状态
  8. localparam STATE_0 = 0,
  9. STATE_1 = 1,
  10. STATE_2 = 2,
  11. STATE_3 = 3;
  12. // 定义状态寄存器和初始状态
  13. reg [1:0] state_r;
  14. // 初始化状态寄存器
  15. always @(posedge clk or negedge rst_n) begin
  16. if (~rst_n) begin
  17. state_r<= STATE_0;
  18. end else begin
  19. case (state_reg)
  20. STATE_0: begin
  21. if (inp == 2'b00) begin
  22. state_r <= STATE_0;
  23. outp <= 0;
  24. end else if (inp == 2'b01) begin
  25. state_r <= STATE_1;
  26. outp <= 1;
  27. end else if (inp == 2'b10) begin
  28. state_r <= STATE_2;
  29. outp <= 0;
  30. end else begin
  31. state_r <= STATE_3;
  32. outp <= 1;
  33. end
  34. end
  35. STATE_1: begin
  36. if (inp == 2'b00) begin
  37. state_r <= STATE_1;
  38. outp <= 1;
  39. end else if (inp == 2'b01) begin
  40. state_r <= STATE_2;
  41. outp <= 0;
  42. end else if (inp == 2'b10) begin
  43. state_r <= STATE_3;
  44. outp <= 1;
  45. end else begin
  46. state_r <= STATE_0;
  47. outp <= 0;
  48. end
  49. end
  50. STATE_2: begin
  51. if (inp == 2'b00) begin
  52. state_r <= STATE_2;
  53. outp <= 0;
  54. end else if (inp == 2'b01) begin
  55. state_r <= STATE_3;
  56. outp <= 1;
  57. end else if (inp == 2'b10) begin
  58. state_r <= STATE_0;
  59. outp <= 0;
  60. end else begin
  61. state_r <= STATE_1;
  62. outp <= 1;
  63. end
  64. end
  65. STATE_3: begin
  66. if (inp == 2'b00) begin
  67. state_r <= STATE_3;
  68. outp <= 1;
  69. end else if (inp == 2'b01) begin
  70. state_r <= STATE_0;
  71. outp <= 0;
  72. end else if (inp == 2'b10) begin
  73. state_r <= STATE_1;
  74. outp <= 1;
  75. end else begin
  76. state_reg <= STATE_2;
  77. outp <= 0;
  78. end
  79. end
  80. endcase
  81. end
  82. end
  83. endmodule

2、二段式状态机

二段式状态机使用2个always块,都是时序逻辑,其中一个always块用于写状态机的状态跳转逻辑,另一个always块用于写当前状态下的寄存器输出逻辑。这种方式逻辑代码清晰,易于调试和理解,是比较推荐的一个方式。

下面给出一个二段式的Moore状态机示例:

  1. module state_machine (
  2. input clk,
  3. input rst_n,
  4. output reg out_reg
  5. );
  6. // 状态寄存器和下一个状态寄存器
  7. reg [1:0] state_r;
  8. // 状态定义
  9. parameter IDLE = 2'b00;
  10. parameter STATE1 = 2'b01;
  11. parameter STATE2 = 2'b10;
  12. parameter STATE3 = 2'b11;
  13. // always @(posedge clk or negedge rst_n) 时序逻辑代码块,实现状态跳转逻辑
  14. always@(posedge clk or negedge rst_n) begin
  15. if(~rst_n) begin
  16. state_r <= IDLE;
  17. end else begin
  18. case(state_r)
  19. IDLE: begin
  20. state_r <= STATE1;
  21. end
  22. STATE1: begin
  23. state_r <= STATE2;
  24. end
  25. STATE2: begin
  26. state_r <= STATE3;
  27. end
  28. STATE3: begin
  29. state_r <= IDLE;
  30. end
  31. endcase
  32. end
  33. end
  34. // always @(*) 时序逻辑代码块,实现状态输出逻辑
  35. always@(posedge clk or negedge rst_n) begin
  36. if(~rst_n) begin
  37. out_reg <= 1'b0;
  38. end else begin
  39. case(state_r)
  40. IDLE: begin
  41. out_reg <= 1'b0;
  42. end
  43. STATE1: begin
  44. out_reg <= 1'b1;
  45. end
  46. STATE2: begin
  47. out_reg <= 1'b1;
  48. end
  49. STATE3: begin
  50. out_reg <= 1'b0;
  51. end
  52. endcase
  53. end
  54. end
  55. endmodule

3、三段式状态机

三段式状态机使用3个always块,其中一个组合always块用于写状态机的状态跳转逻辑,一个时序always块用于缓存状态寄存器,另一个always块用于写当前状态下的寄存器输出逻辑。这种方式逻辑代码清晰,易于调试和理解,也是比较推荐的一个方式。

  1. module state_machine (
  2. input clk,
  3. input rst_n,
  4. input [1:0] inp,
  5. output reg outp
  6. );
  7. // 定义状态
  8. localparam STATE_0 = 0,
  9. STATE_1 = 1,
  10. STATE_2 = 2,
  11. STATE_3 = 3;
  12. // 定义状态寄存器和初始状态
  13. reg [1:0] state_r, next_state ;
  14. // 定义状态寄存器
  15. always @(posedge clk or negedge rst_n) begin
  16. if (~rst_n) begin
  17. state_r <= STATE_0;
  18. end else begin
  19. state_r <= next_state;
  20. end
  21. end
  22. // 定义状态转移逻辑
  23. always @(*) begin
  24. case (state_r)
  25. STATE_0: begin
  26. if (inp == 2'b00) begin
  27. next_state = STATE_0;
  28. end else if (inp == 2'b01) begin
  29. next_state = STATE_1;
  30. end else if (inp == 2'b10) begin
  31. next_state = STATE_2;
  32. end else begin
  33. next_state = STATE_3;
  34. end
  35. end
  36. STATE_1: begin
  37. if (inp == 2'b00) begin
  38. next_state = STATE_1;
  39. end else if (inp == 2'b01) begin
  40. next_state = STATE_2;
  41. end else if (inp == 2'b10) begin
  42. next_state = STATE_3;
  43. end else begin
  44. next_state = STATE_0;
  45. end
  46. end
  47. STATE_2: begin
  48. if (inp == 2'b00) begin
  49. next_state = STATE_2;
  50. end else if (inp == 2'b01) begin
  51. next_state = STATE_3;
  52. end else if (inp == 2'b10) begin
  53. next_state = STATE_0;
  54. end else begin
  55. next_state = STATE_1;
  56. end
  57. end
  58. STATE_3: begin
  59. if (inp == 2'b00) begin
  60. next_state = STATE_3;
  61. end else if (inp == 2'b01) begin
  62. next_state = STATE_0;
  63. end else if (inp == 2'b10) begin
  64. next_state = STATE_1;
  65. end else begin
  66. next_state = STATE_2;
  67. end
  68. end
  69. endcase
  70. end
  71. // 定义输出逻辑
  72. always @(*) begin
  73. case (state_r)
  74. STATE_0: outp = 0;
  75. STATE_1: outp = 1;
  76. STATE_2: outp = 0;
  77. STATE_3: outp = 1;
  78. endcase
  79. end
  80. endmodule

注意:组合逻辑代码中,if语句和case语句必须写满,否则容易形成latch,导致实际运行出问题。

三、状态机的编码方式

1、独热码

独热码(One-hot)是一种状态编码方式,其特点是对于任意给定的状态,状态寄存器中只有1位为1,其余位都为0。使用独热码可以简化译码逻辑电路,因为状态机只需对寄存器中的一位进行译码,同时可用省下的面积抵消额外触发器占用的面积。相比于其他类型的有限状态机,加入更多的状态时,独热码的译码逻辑并不会变得更加复杂,速度仅取决于到某特定状态的转移数量。

此外,独热码还具有诸如设计简单、修改灵活、易于综合和调试等优点。但值得注意的是,相对于二进制码,独热码速度更快但占用面积较大。

  1. module state_machine(
  2. input clk,
  3. output reg [3:0] state_out
  4. );
  5. localparam STATE_A = 4'b0001;
  6. localparam STATE_B = 4'b0010;
  7. localparam STATE_C = 4'b0100;
  8. localparam STATE_D = 4'b1000;
  9. reg [3:0] current_state, next_state;
  10. always @(posedge clk) begin
  11. current_state <= next_state; // 当时钟上升沿到来时更新状态
  12. end
  13. always @(*) begin
  14. case (current_state)
  15. STATE_A: next_state = STATE_B;
  16. STATE_B: next_state = STATE_C;
  17. STATE_C: next_state = STATE_D;
  18. STATE_D: next_state = STATE_A;
  19. default: next_state = STATE_A; // 默认情况下返回初始状态
  20. endcase
  21. end
  22. assign state_out = current_state; // 将当前状态作为输出
  23. endmodule

2、格雷码

格雷码是一种相邻的两个码组之间仅有一位不同的编码方式。在格雷码中,相邻的两个码组之间仅有一位不同,这种编码方式可以用于实现相邻的两个状态之间只有一位不同的状态机;

FPGA 中的状态机通常需要高速运行,因此使用格雷码可以减少状态转换的开销,并提高时序性能。

  1. module gray_code_state_machine(
  2. input clk,
  3. output reg [3:0] state_out
  4. );
  5. localparam G0 = 4'b0000;
  6. localparam G1 = 4'b0001;
  7. localparam G2 = 4'b0011;
  8. localparam G3 = 4'b0010;
  9. reg [3:0] current_state, next_state;
  10. always @(posedge clk) begin
  11. current_state <= next_state; // 当时钟上升沿到来时更新状态
  12. end
  13. always @(*) begin
  14. case (current_state)
  15. G0: next_state = G1;
  16. G1: next_state = G3;
  17. G2: next_state = G0;
  18. G3: next_state = G2;
  19. default: next_state = G0; // 默认情况下返回初始状态
  20. endcase
  21. end
  22. assign state_out = current_state; // 将当前状态作为输出
  23. endmodule

3、普通二进制码

FPGA状态机可以用普通二进制码表示,不同状态按照二进制数累加表示,是常用的一种方式,仿真调试时,状态显示清晰,易于理解代码。

  1. module binary_state_machine(
  2. input clk,
  3. output reg [1:0] state_out
  4. );
  5. localparam STATE_A = 2'b00;
  6. localparam STATE_B = 2'b01;
  7. localparam STATE_C = 2'b10;
  8. reg [1:0] current_state, next_state;
  9. always @(posedge clk) begin
  10. current_state <= next_state; // 当时钟上升沿到来时更新状态
  11. end
  12. always @(*) begin
  13. case (current_state)
  14. STATE_A: next_state = STATE_B;
  15. STATE_B: next_state = STATE_C;
  16. STATE_C: next_state = STATE_A;
  17. default: next_state = STATE_A; // 默认情况下返回初始状态
  18. endcase
  19. end
  20. assign state_out = current_state; // 将当前状态作为输出
  21. endmodule

4、格雷码与普通二进制码互转

  • 二进制码:一个n位的二进制码可以表示2^n种状态,它有2^(n-1)个相邻状态之间只有一个位不同。

  • 格雷码:一个n位的格雷码可以表示2^n种状态,它有2^(n-1)个相邻状态之间只有一位变化。

(1)二进制码转换成格雷码

假设当前的状态用二进制码表示为B,那么它所对应的格雷码G可以按照以下方式计算得出:

g[n-1]=b[n-1], g[i]=b[i]^b[i+1], i=[0,1,...,n-2]

下面给出“二进制码转格雷码”的示例代码:

  1. module bin2gray#(
  2. parameter DATA_WIDTH = 8
  3. )(
  4. input [DATA_WIDTH-1:0] bin,
  5. output [DATA_WIDTH-1:0] gray
  6. );
  7. assign gray = {1'b0, bin[DATA_WIDTH-1:1]} ^ bin;
  8. endmodule

(2)格雷码转换成二进制码

假设当前的状态用格雷码表示为G,那么它所对应的二进制码B可以按照以下方式计算得出:

b[n-1]=g[n-1], b[i]=gray[i]^b[i+1], i=[0,1,...,n-2]
B = G xor (G >> 1)

下面给出“二进制码转格雷码”的示例代码:

  1. module gray2bin#(
  2. parameter DATA_WIDTH = 8
  3. )(
  4. input [DATA_WIDTH-1:0] gray,
  5. output [DATA_WIDTH-1:0] bin
  6. );
  7. genvar i;
  8. assign bin[DATA_WIDTH-1] = gray[DATA_WIDTH-1];
  9. generate
  10. for(i = 0; i < DATA_WIDTH-1; i = i+1) begin : gray_to_bin
  11. assign bin[i] = gray[i]^bin[i+1];
  12. end
  13. endgenerate
  14. endmodule

四、总结

以上总结了FPGA状态机中有关的知识点,大家可以参考下,建议状态机的写法一般用二段式或三段式,代码逻辑清晰,易于理解和调试,同时需要注意always@(*)块中的组合逻辑,使用if和case语句都要写满,否则综合后容易形成latch,导致上板与仿真结果不一致。


本文将不断定期更新中,点⭐️赞,收⭐️藏一下,不走丢哦

有任何问题,都可以在评论区和我交流哦

本文由FPGA入门到精通原创,公众号为“FPGA入门到精通”,github开源代码:“FPGA知识库

您的支持是我持续创作的最大动力!如果本文对您有帮助,请给一个鼓励,谢谢。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/915490
推荐阅读
相关标签
  

闽ICP备14008679号