太久没回学校了,甚至想念实验室了,实验室的感觉呢,实验室比家里好door♂了,在家里一个人很无聊,又没有板子玩、又没有仪器用,进了实验室里边个个是人才,说话又好听,哦哟~超喜欢在里边的。
但由于疫情原因,呆在家里没办法,只能趁机划几篇水文才能维持得了生活这样子。这次简单讲讲有限状态机的事情!
啥叫有限状态机?
有限状态机(Finite State Machine,FSM),就是指有限个状态以及在这些状态之间的转移和动作等行为的数学模型。举个栗子便于解释,比如“领袖”要去偷电瓶,需要完成:寻找电瓶车、查看周边环境、下手和骑上就跑等四个步骤,如果将这一系列步骤抽象成为数学模型,可如下图所示
在图中,可以注意到:
- 状态是有限的,图中一共有“寻找电瓶车”、“查看周边环境”、“下手”和“骑上就跑”四个状态;
- 状态转移是根据触发条件来确定的,例如找到电瓶车后,状态就从“寻找电瓶车”状态转移到“查看周边环境”状态;
- 状态转移也限于既定状态内,例如该状态机就只在这四个状态里转移;
- 处于某个状态下的行为是持续的,例如“下手”状态中就一直在开锁等;
- 每个状态的输出结果可能不是一样的,例如直到“骑上就跑”状态时,才获得一辆电瓶车。
有了这样的概念之后,我们就可以在某些设计中就使用有限状态机来实现所需的功能。
需要注意的是
根据状态机的输出是否与输入有关,可以分为下列两种状态机:
- Moore型:输出只与当前状态有关,与输入无关;
- Mealy型:输出不仅与当前状态有关,还与输入有关。
这两种状态机在电路实现上的区别如下图所示[1],当然对这种状态机的具体区别和转换等可以参考[1]
此外,对各个状态进行编码,又有以下几种编码方式:
- 二进制:根据状态数量进行二进制累加编码;
- 格雷码:相邻状态的编码只差1bit;
- 热独码:每个状态的编码只有1bit为1,其他为0;
分别采用这三种编码对上述四个状态进行编码,如下图所示
可以明显看到,二进制和格雷码比热独码更节省寄存器,但热独码具有节省组合逻辑的优点[2],热独码也是在状态机的设计当中较为常用的状态编码方式。
那我该咋写状态机?
比如问“输入串行序列,用状态机实现‘101’序列检测,若序列符合则输出1,否则输出0“,使用Verilog实现的具体流程是:
- 确定各状态:按照问题可以确定S0、S1、S2、S3四种状态,分别表示序列为xxx、xx1、x10、101四种情况,并采用热独码对状态编码,例如S0:4'b0001、S1:4'b0010、S2:4'b0100、S3:4'b1000;
- 确定状态转换条件:根据1可知,当序列输入为1时,S0->S1,当序列输入为0时,S1->S2,以此类推;
- 确定状态输出:根据1可知,当S0时输出0,S1时输出0,S2时输出0,S3时输出1。
以下分别采用三种常见的状态机实现上述要求:
- 一段式:直接粗暴写着爽⭐
一段式即将“状态转移、状态输出和状态转移条件”写在一块,简单粗暴,写的飞起~
//一段式状态机
reg [3:0] state;
localparam S0 = 4'b0001, //采用热独码
S1 = 4'b0010,
S2 = 4'b0100,
S3 = 4'b1000;
//共一段 采用时序逻辑完成状态机的转移、输出等
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
dout <= 1'b0;
state <= S0;
end
else begin
case(state)
S0:begin
dout <= 1'b0; //输出0
if (din == 1'b1) begin
state <= S1;
end
else begin
state <= S0;
end
end
S1:begin
dout <= 1'b0; //输出0
if (din == 1'b1) begin
state <= S1;
end
else begin
state <= S2;
end
end
S2:begin
dout <= 1'b0; //输出0
if (din == 1'b1) begin
state <= S3;
end
else begin
state <= S0;
end
end
S3:begin
dout <= 1'b1; //输出1
if (din == 1'b1) begin
state <= S1;
end
else begin
state <= S2;
end
end
default:begin
dout <= 1'b0;
state <= S0;
end
endcase
end
end
一段式的状态机的RTL如下图所示:
一段式的状态机的RTL综合后的结果如下图所示:
- 两段式:容易理解不难写⭐⭐⭐
两段式即将“状态转移”、“状态输出和状态转移条件”分成两部分写,容易理解,便于维护。(经评论区提醒组合逻辑不用复位)
//两段式状态机
reg [3:0] current_state; //当前状态
reg [3:0] next_state; //下一状态
localparam S0 = 4'b0001, //采用热独码
S1 = 4'b0010,
S2 = 4'b0100,
S3 = 4'b1000;
//第一段 采用时序逻辑完成状态转移
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
current_state <= S0;
end
else begin
current_state <= next_state;
end
end
//第二段 采用组合逻辑完成转移条件判断和状态输出
always @(*) begin
case(current_state)
S0:begin
dout = 1'b0; //输出0
if (din == 1'b1) begin
next_state = S1;
end
else begin
next_state = S0;
end
end
S1:begin
dout = 1'b0; //输出0
if (din == 1'b1) begin
next_state = S1;
end
else begin
next_state = S2;
end
end
S2:begin
dout = 1'b0; //输出0
if (din == 1'b1) begin
next_state = S3;
end
else begin
next_state = S0;
end
end
S3:begin
dout = 1'b1; //输出1
if (din == 1'b1) begin
next_state = S1;
end
else begin
next_state = S2;
end
end
default:begin
dout = 1'b0;
next_state = S0;
end
endcase
end
两段式的状态机的RTL如下图所示:
两段式的状态机的RTL综合后的情况如下图所示:
- 三段式:结构精简八股文⭐⭐⭐⭐⭐
三段式即将“状态转移”、“转移条件”、“状态输出”分三部分写,可能在看起来会比较的难写,但是了解结构之后感觉就像八股文或是填空题一样。但需要注意的是:第三段不一定是采用一个always完成,如果输出的信号较多可以采用多个。(经评论区提醒组合逻辑不用复位)
//三段式状态机
reg [3:0] current_state; //当前状态
reg [3:0] next_state; //下一状态
localparam S0 = 4'b0001, //采用热独码
S1 = 4'b0010,
S2 = 4'b0100,
S3 = 4'b1000;
//第一段 采用时序逻辑完成状态转移
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
current_state <= S0;
end
else begin
current_state <= next_state;
end
end
//第二段 采用组合逻辑完成转移条件判断
always @(*) begin
case(current_state)
S0:begin
if (din == 1'b1) begin
next_state = S1;
end
else begin
next_state = S0;
end
end
S1:begin
if (din == 1'b1) begin
next_state = S1;
end
else begin
next_state = S2;
end
end
S2:begin
if (din == 1'b1) begin
next_state = S3;
end
else begin
next_state = S0;
end
end
S3:begin
if (din == 1'b1) begin
next_state = S1;
end
else begin
next_state = S2;
end
end
default:next_state = S0;
endcase
end
//第三段 采用时序逻辑状态输出
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
dout <= 1'b0;
end
else begin
case(next_state) //采用next state,提前一拍
S0:begin
dout <= 1'b0; //输出0
end
S1:begin
dout <= 1'b0; //输出0
end
S2:begin
dout <= 1'b0; //输出0
end
S3:begin
dout <= 1'b1; //输出1
end
default:dout <= 1'b0;
endcase
end
end
三段式的状态机的RTL如下图所示:
三段式的状态机的RTL综合后的结果如下图所示:
总的来说,三段式状态机是较为常用的写法。
我想是以后带【划水】都是随便写写的,可能内容比较简单,当然写的问题还望批评指正~
参考
- ^ab整理 | FSM 有限状态机 http://www.airbird.info/2017/fpga-fsm/
- ^FPGA编写有限状态机使用独热码为什么会占用较少的组合逻辑电路? https://www.zhihu.com/question/40994717