FPGA越往底层走,越发现很多问题只是知其然,而不知其所以然。状态机编码原则就是其中之一。我们在实际开发中,只记住了建议使用独热码(one hot)作为状态编码,至于为什么(大概也就记得不容易跑飞),可能早就忘了。
以经典的案例来说明其中的一些问题:
- 序列检测,每检测到一组“11011”,然后输出一个高电平。
状态转移图如下图所示:

状态机的Verilog代码如下:
1module FSM_test( 2 input clk, 3 input rst_n, 4 input d_in, 5 output d_out 6 ); 7 8/* parameter S0 = 5'b000000, 9 S1 = 5'b000001, 10 S2 = 5'b000011, 11 S3 = 5'b000010, 12 S4 = 5'b000110, 13 S5 = 5'b000111; */ 14 15 parameter S0 = 5'b00000, 16 S1 = 5'b00001, 17 S2 = 5'b00010, 18 S3 = 5'b00100, 19 S4 = 5'b01000, 20 S5 = 5'b10000; 21/* parameter S0 = 5'd0, 22 S1 = 5'd1, 23 S2 = 5'd2, 24 S3 = 5'd3, 25 S4 = 5'd4, 26 S5 = 5'd5; */ 27 28 reg r_d_out; 29 reg [4:0] cs,ns; 30 31 always@(posedge clk or negedge rst_n) begin 32 if(rst_n == 1'b0) begin 33 cs <= S0; 34 end else begin 35 cs <= ns; 36 end 37 end 38 39 always@(*) begin 40 ns = 5'dx; 41 case(cs) 42 S0: begin 43 if(d_in == 1'b1) 44 ns = S1; 45 else 46 ns = S0; 47 end 48 S1: begin 49 if(d_in == 1'b1) 50 ns = S2; 51 else 52 ns = S0; 53 end 54 S2: begin 55 if(d_in == 1'b1) 56 ns = S2; 57 else 58 ns = S3; 59 end 60 S3: begin 61 if(d_in == 1'b1) 62 ns = S4; 63 else 64 ns = S0; 65 end 66 S4: begin 67 if(d_in == 1'b1) 68 ns = S5; 69 else 70 ns = S0; 71 end 72 S5: begin 73 if(d_in == 1'b1) 74 ns = S2; 75 else 76 ns = S3; 77 end 78 default: 79 ns = S0; 80 81 endcase 82 end 83 84 always@(posedge clk or negedge rst_n) begin 85 if(rst_n == 1'b0) begin 86 r_d_out <= 1'b0; 87 end else if(cs == S5)begin 88 r_d_out <= 1'b1; 89 end else begin 90 r_d_out <= 1'b0; 91 end 92 end 93 94 assign d_out = r_d_out; 95endmodule 96
上面代码中,定义了格雷码、独热码以及二进制码(序列码)的状态编码方式,本想在vivado下看综合后的原理图,发现三种编码方式综合后的结果一样!不符合理论啊,所以想是不是被vivado优化了。果然,查书发现在综合选项中,“-fsm_extraction”选项为auto,在auto下,本段状态就会被优化成格雷码的编码方式。


取消该优化之后,仅对比格雷码和独热码的综合结果,原理图如下:
- 格雷码

- 独热码

格雷码消耗4个LUT和3个Register,而独热码消耗7个LUT和6个Register。不是说独热码更省组合逻辑吗??为什么反而消耗LUT更多。到了这一层,还是不能往下理解,是代码设计问题还是综合问题?
做了一个尝试之后,还是把前人结论和经验总结一下:
- 二进制码:
有过渡状态,容易跑飞。
- 格雷码:
减少过渡状态,每次只有一位变化,因此可以降低功耗。但是如果当一个状态到下一个状态有多种转换路径时,就不能保证状态跳转时只有一个位变化,这样就无法发挥格雷码的特点了。
- 独热码:
少用组合逻辑,多了寄存器。速度更快、可靠性更好。
至于二进制码咱不用讨论,因为对于状态少的情况(小于4),也可以使用,因为跑飞的概率极其小。
对于格雷码和独热码,到底应该怎么选择才能达到最优综合?查阅书籍及网络资料,总结起来就是:
- 格雷码:适合所有状态是顺序序列,可以用格雷码来消除毛刺,但如果有复杂分支判断,则格雷码也不能达到消毛刺的目的,简单的说,格雷码适合条件不复杂,状态多的情况;
- 独热码:消耗较少组合逻辑,消耗更多寄存器,因此在FPGA中有利于速度和可靠性。适合条件复杂,状态少的情况。
另外,在CPLD中由于组合逻辑多,而寄存器少,所以可能不适合独热码,更适合格雷码和二进制编码。