【FPGA学习】- 状态机
状态机
状态机是组合逻辑和寄存器逻辑的特殊组合。一般包括两个部分,组合逻辑部分和寄存器逻辑部分。寄存器用于存储状态,组合电路用于状态译码和产生输出信号。状态机的下一个状态不仅和输入信号有关,而且和当前寄存器的状态也有关系。状态机的要素有三个:状态,输入和输出。
状态:在逻辑设计中,使用状态划分逻辑顺序和时序规律。
输入:状态机中进入状态的条件。
输出:在某一个状态特定发生的事件。
依据输出信号与输入信号是否有关,可以将状态机划分位摩尔型(Moore)状态机和米利型(Mealy)状态机。依据输出信号与输入信号是否同步,可以将状态机划分为同步状态机和异步状态机。
状态机描述方式
状态机的描述方式有3种表示方法,状态转移图,状态转移表和编程语言描述。
状态转移图:是状态机最自然的描述方式。使用图形化的方式来定义逻辑功能。
状态转移表:使用列表的方式描述状态机,经常用于简化状态。
编程语言描述:通过编程语言将状态机进行描述。
状态机设计原则
状态编码原则
二进制编码:对确定的状态使用二进制数据进行编码。优点是使用的状态向量最少,简单;缺点是相邻状态切换时,可能有多位发生变化,瞬变次数多,易产生毛刺。
格雷码:在相邻状态下,格雷码只有一位发生变化,虽然减少了毛刺的产生,但是格雷码并不适合有很多状态的情况。
独热码:有多少个状态就有多少比特,而且只有一个比特为1,其他全为0的一种码制。独热码虽然多用了触发器,但是由于译码简单,有效提高了电路的速度和可靠性。
| 十进制数字码 | 独 热 码 | 十进制数字码 | 独 热 码 |
| 0 | 000_0000_00 | 5 | 000_0100_00 |
| 1 | 000_0000_01 | 6 | 000_1000_00 |
| 2 | 000_0000_10 | 7 | 001_0000_00 |
| 3 | 000_0001_00 | 8 | 010_0000_00 |
| 4 | 000_0010_00 | 9 | 100_0000_00 |
状态机容错处理
在状态机的设计当中,不可避免地会出现大量的剩余状态。容错处理就是对这些剩余状态进行处理,以防止状态机进入不可预测的状态,出现短暂失控或始终无法摆脱剩余状态。常见的剩余状态处理方式如下。
1.转入空闲状态,等待下一个工作任务的到来。
2.转入指定状态去执行特定的任务。
3.转入预定义的专门处理错误的状态。
即在剩余状态下,将该状态转到一个我们能够确定的状态。比如if语句不漏掉else,case语句不漏掉default。
状态机设计准则
设计要稳定。状态机不会进入死循环,即使进入非正常状态,也很快恢复正常。
工作速度块。状态机尽可能满足电路的频率要求。尽可能用case语句代替if语句。
所占资源少。尽可能使用较少的资源。
代码易维护。代码书写要规范,做好文档维护。
摩尔型(Moore)状态机
摩尔型时序电路的特点是当前的输出仅与当前电路状态有关,而与输入信号无关。最经典的例子就是红绿灯。考虑一个红绿灯控制器,按照日常功能完成“红灯(25s)->绿灯(15s)->黄灯(5s)”的变化。在状态转换图中,包含的要素有三个:状态数目,状态编码和状态输出。
状态数目就是该时序电路中有几种状态,例如该控制器需要三种状态S0~S2。状态编码用于区分每个不同的状态,00表示红灯,01表示绿灯,10表示黄灯。状态输出就是描述在特定状态下的输出值,在斜线上,格式一般为“输出/输入”,表示的意义为在该输入的情况下,状态沿此箭头方向跳转并产生相应的输出。例如在该控制器中,从S0到S1,100表示亮起绿灯,由于没有输入信号,所以不写。010表示亮起黄灯,100表示亮起红灯。

交通灯设计
module traffic_light(clock, reset, red, yellow, green); input clock, reset; output reg red, yellow, green; reg [1:0] current_state, next_state; //存放状态 reg [4:0] light_count, light_delay; //增加计数器和计数器延迟 parameter red_state = 2'b00, //状态变量 green_state = 2'b10, yellow_state = 2'b01, delay_red = 5'd25, //状态持续时间 delay_yellow = 5'd5, delay_green = 5'd15; always @(posedge clock, negedge reset) begin //状态检测及转换 if(reset == 1'b0) current_state <= red_state; else current_state <= next_state; end always @(*) begin case(current_state) red_state:begin light_delay = delay_red; if(light_count == light_delay) next_state = green_state; end green_state:begin light_delay = delay_green; if(light_count == light_delay) next_state = yellow_state; end yellow_state:begin light_delay = delay_yellow; if(light_count == light_delay) next_state = red_state; end default:begin next_state = red_state; end endcase end always @(posedge clock, negedge reset) begin //输出信号 if(reset==0) begin red <= 1; yellow <= 0; green <= 0; end else begin case(current_state) red_state:begin red <= 1; yellow <= 0; green <= 0; end green_state:begin red <= 0; yellow <= 0; green <= 1; end yellow_state:begin red <= 0; yellow <= 1; green <= 0; end endcase end end always @(posedge clock, negedge reset) begin //计时 if(reset==0) light_count <= 0; else if(light_count==light_delay) light_count <= 1; else light_count <= light_count + 1; end endmodule
激励文件
module test_traffic_light; reg clock, reset; wire red, green, yellow; traffic_light t1(clock, reset, red, yellow, green); //实例引用 initial clock = 0; //产生时钟信号 always #10 clock = ~clock; initial begin reset = 1; //产生一个复位信号沿 #1 reset = 0; #10 reset = 1; #10000; #20 $stop; end endmodule;
测试结果,可以看到按照红绿黄的顺序依次变换。

米利型(Mealy)状态机
米利型电路的输出信号不仅与当前电路的状态有关,而且与输入也有关系,换言之,输入会决定电路转换的跳转。比如一个序列检测器,检测输入信号11010序列,我们可以知道状态转换图如下,S0表示初始态,用于区别其他状态。此状态下,若输入为1,跳转到下一状态S1,输出为0(只有检测到序列才输出为1),若输入为0,则继续在初态S0上,输出也为0;在S1状态下,表明此时为序列“1”,若此时输入为1,跳转到下一状态S2,输出为0,若输入为0,此时为序列“10”,与序列“11010”无法按照顺序吻合,故回到初态S0;在S2状态下,表明此时为序列“11”,若此时输入为0,跳转到下一状态S3,输出为0,若输入为1,此时为序列“111”,与序列“11010”可以按照顺序吻合,维持在状态S2;在S3状态下,表明此时为序列“110”,若此时输入为1,跳转到下一状态S4,输出为0,若输入为0,此时为序列“1100”,与序列“11010”无法按照顺序吻合,故回到初态S0;在S4状态下,表明此时为序列“1101”,若此时输入为0,跳转到下一状态S5,且输出为1,若输入为1,此时为序列“11011”,与序列“11010”可以按照顺序吻合,回到状态S2;在S5状态下,表明此时为序列“11010”,若此时输入为0,此时为序列“110100”,无法吻合,回到初态S0,输出为0,若输入为1,此时为序列“110101”,与序列“11010”可以按照顺序吻合,回到状态S1。

序列检测器设计
module seq_detec(x, z, clk, reset); input x, clk, reset; output reg z; reg [2:0] current_state, next_state; parameter S0 = 3'd0, S1 = 3'd1, S2 = 3'd2, S3 = 3'd3, S4 = 3'd4, S5 = 3'd5; always @(posedge clk, negedge reset) begin //状态转换 if(reset == 0) current_state <= S0; else current_state <= next_state; end always @(*) begin //状态变化 case(current_state) S0:begin if(x==1) next_state = S1; else next_state = S0; end S1:begin if(x==1) next_state = S2; else next_state = S0; end S2:begin if(x==0) next_state = S3; else next_state = S2; end S3:begin if(x==1) next_state = S4; else next_state = S0; end S4:begin if(x==0) next_state = S5; else next_state = S2; end S5:begin if(x==1) next_state = S1; else next_state = S0; end default:begin next_state = S0; end endcase end always @(posedge clk, negedge reset) begin //产生输出 if(reset == 0) z <= 0; else if(current_state == S5) z <= 1; else z <= 0; end endmodule
激励设计
module test_seq_detec; reg x, clk, reset; wire z; integer seed = 8; seq_detec s1(x, z, clk, reset); initial clk = 0; //初始化时钟信号 always #5 clk = ~clk; initial begin reset = 1; #15 reset = 0; #15 reset = 1; @(posedge z); //等待Z的上升沿,避免波形被直接节段 #5 $stop; end always #10 x = ($random(seed) % 2); //产生01随机数 endmodule
结果,在最后产生了一个11010的序列,并产生了脉冲。 
参考资料
[1] 黄海,于斌,Verilog HDL 设计实用教程[M],北京:清华大学出版社,2021.
[2] 周润景,李志,张玉光,基于Quartus Prime的数字系统Verilog HDL设计实例详解(第3版)[M],北京:电子工业出版社,2018.

浙公网安备 33010602011771号