前言
最近刚刚接触到状态机代码的编写,一个好的状态机的代码风格易于更简洁地实现功能、便于修改内容和查找bug,所以在刚开始学习时养成一个很好的代码风格是很有必要的,本文用简单的售货机作为例子,分享一下自己在状态机代码编写时的一些理解。
一、状态机是什么?
状态机全称为同步有限状态机,简写为FSM(Finite State Machine),是指状态机不同状态下的转换与时钟同步的并且其状态数量有限。状态机分为米利型状态机和穆尔型状态机,其中米利型状态机的输出和当前状态以及当前输入有关,而穆尔型状态机的输出仅仅与当前状态有关。
二、状态机代码风格
状态机的写法主要分为一段式、两段式和三段式状态机,接下来以简单的售货机为例子,介绍三种状态机的代码风格与优缺点。
售货机中只卖一种3元的可乐,每次只能投入一枚1元,投入3次硬币即可出货。
根据需求可知输入输出及状态有以下可能:
状态 | 投入0元、投入1元、投入2元、投入3元 |
---|---|
输入 | 投入1元、不投币 |
输出 | 出货、不出货 |
根据需求可以分别绘制出状态转换图,考虑到米利型状态机的状态数量更少,采用米利型状态机编写代码。
1.一段式
一段式状态机中在一个always语句完成整个状态机编写,既包含了组合逻辑,也包含了时序逻辑。其中状态转移、结果输出均采用时序逻辑。
//一段式状态机
module simple_fsm
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire pi_money ,//投币,1为投入1元,0为未投币
output reg po_cola //输出,出货为1,未出货为0
);
//采用独热码的方式表示状态
parameter IDLE = 3'b001;
parameter ONE = 3'b010;
parameter TWO = 3'b100;
reg [2:0] state;
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)begin
state <= IDLE;
po_cola <= 1'b0;
end
else
case(state)
IDLE : begin
if(pi_money == 1'b1)begin
state <= ONE;
po_cola <= 1'b0;
end
else begin
state <= IDLE;
po_cola <= 1'b0;
end
end
ONE : begin
if(pi_money == 1'b1)begin
state <= TWO;
po_cola <= 1'b0;
end
else begin
state <= ONE;
po_cola <= 1'b0;
end
end
TWO : begin
if(pi_money == 1'b1)begin
state <= IDLE;
po_cola <= 1'b1;
end
else begin
state <= TWO;
po_cola <= 1'b0;
end
end
default : begin
state <= IDLE;
po_cola <= 1'b0;
end
endcase
end
endmodule
2.两段式
两段式状态机引入了下一个状态,采用时序逻辑描述状态转移,采用组合逻辑描述下一状态和输出结果,其中组合逻辑的敏感列表为状态和输入信号(穆尔式状态机仅为状态)。将组合逻辑与时序逻辑区分开,提高了代码的可读性。
//两段式状态机
module simple_fsm
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire pi_money ,//投币,1为投入1元,0为未投币
output reg po_cola //输出,出货为1,未出货为0
);
//采用独热码的方式表示状态
parameter IDLE = 3'b001;
parameter ONE = 3'b010;
parameter TWO = 3'b100;
reg [2:0] state;
reg [2:0] next_state;
//第一段:描述状态转移
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else
state <= next_state;
//第二段:描述下一状态和输出结果
always@(state,pi_money)
case(state)
IDLE : if(pi_money == 1'b1)begin
next_state = ONE;
po_cola = 1'b0;
end
else begin
next_state = IDLE;
po_cola = 1'b0;
end
ONE : if(pi_money == 1'b1)begin
next_state = TWO;
po_cola = 1'b0;
end
else begin
next_state = ONE;
po_cola = 1'b0;
end
TWO : if(pi_money == 1'b1)begin
next_state = IDLE;
po_cola = 1'b1;
end
else begin
next_state = TWO;
po_cola = 1'b0;
end
default : begin
next_state = IDLE;
po_cola = 1'b0;
end
endcase
endmodule
3.三段式
三段式状态机主要分为三个部分,用三个always语句描述状态的转移、描述下一个状态、描述输出结果。在第一段描述状态转移中采用时序逻辑描述赋值当前状态,而在第二段采用组合逻辑判断状态变化,,在第三段中采用时序逻辑描述输出结果。整体逻辑非常清晰明了,也解决了两段式状态机在描述输出信号时组合逻辑描述的一些困难。
//三段式状态机
module simple_fsm
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire pi_money ,//投币,1为投入1元,0为未投币
output reg po_cola //输出,出货为1,未出货为0
);
//采用独热码的方式表示状态
parameter IDLE = 3'b001;
parameter ONE = 3'b010;
parameter TWO = 3'b100;
reg [2:0] state;
reg [2:0] next_state;
//第一段:描述状态转移
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else
state <= next_state;
//第二段:描述下一状态(米利型状态机这里的敏感列表是状态和输入)
always@(state,pi_money)
case(state)
IDLE :if(pi_money == 1'b1)
next_state = ONE;
else
next_state = IDLE;
ONE :if(pi_money == 1'b1)
next_state = TWO;
else
next_state = ONE;
TWO :if(pi_money == 1'b1)
next_state = IDLE;
else
next_state = TWO;
default :next_state = IDLE;
endcase
//第三段:描述结果输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_cola <= 1'b0;
else if((state == TWO)&&(pi_money == 1'b1))
po_cola <= 1'b1;
else
po_cola <= 1'b0;
endmodule
总结
本文对常见的几种状态机编写方法作了比较,从编写结果来看,三段式状态机虽然代码量相较略大,但有着代码逻辑清晰、区分了组合逻辑和时序逻辑、并且消除了两段式状态机中可能存在的输出毛刺以及表达寄存器型变量存在困难的问题等优点,是编写状态机的理想风格。