总结来自 《搭建你的数字积木 数字电路与逻辑设计 》 一书 有空大家可以去看看原书
简介
有限状态机(FSM)是fpga的灵魂,能够使得FPGA并行处理的基础上实现和CPU一样的串行处理效果,一样的灵活。同时相比CPU有一下的特性:
(1)高效的顺序控制模型
(2)容易利用现成的EDA工具进行优化设计
(3)性能稳定,解决大规模集成电路的竞争和冒险,可以消除电路毛刺,强化系统功能
(4)高速性能,结合和FPGA的高速优势
(5)高可靠性,和CPU相比不需要繁杂的取指、译码、执行等过程
理论基础
FSM中有两类状态机需要理解,一种叫做Mealy状态机、一种叫做Moore状态机。
经典的FSM是由组合逻辑和状态寄存器构成,组合逻辑负责逻辑计算,状态寄存器负责状态的转换。
Mealy和Moore的区别在于输出和输入的关系不同
Mealy的输出是由当前的状态和输入决定的。
0
Moore的输出只是由当前的状态决定的,与输入无关
简单的来说Moore输出直接由状态来决定,Mealy输出还需要由输入决定。
上图a为状态转移图,b为具体的ASM内部转换图,可以看出a和b为输入,y0和y1为输出,S0、S1、S2为对应状态。读图可以看到y1只和当前的状态有关,所以y1为Moore输出,而y0和b的值有关,所以y0为Mealy输出。
代码实现
每种状态机首先需要使用一个符号常量来表示有限状态机的状态
符号常量的赋值编码也是有一定讲究的,详细参考这篇博客
二进制码、格雷码、独热码 等编码方式在状态机中的运用
该代码实现了上面ASM转换(代码手打的可能有错误)
module fsm(
input clk,
input reset,
input a,
input b,
output y0,
output y1
);
//符号常量表示有限状态机的状态
localparam [1:0]s0 = 2'b00;
localparam [1:0]s1 = 2'b01;
localparam [1:0]s2 = 2'b10;
reg[1:0]state_reg,state_next;
//一个always模块采用组合逻辑判断状态转移
always @(posedge clk or negedge reset) begin
if(reset)
state_reg<=s0;
else
state_reg<=state_next;
end
//一个always模块采用组个逻辑判断转移条件,描述状态转移规律
always @ (*)
case (state_reg)
s0:if(a)
if(b)
state_next = s2;
else
state_next = s1;
else
state_next=s0;
s1:if(a)
stste_next = s0;
else
stste_next = s1;
s2: stste_next=s0;
default:stste_next=s0;
endcase
//一个always模块描述状态输出
// Moore 型逻辑输出
assign y1 = (state_reg == s0)||(state_reg == s1);
// Mealy 型逻辑输出
assign y0 = (stste_reg == s0)&a&b;
endmodule
状态机的类型
状态机的代码一般可以使用一段式状态机、两段式状态机、三段式状态机来表示。
(1)一段式:
一个always模块里面,既有描述状态转移,又有描述状态输入和输出
(2)二段式:
一个always模块采用同步时序描述状态转移
一个always模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出
(3)三段式:
一个always模块采用组合逻辑判断状态转移
一个always模块采用组个逻辑判断转移条件,描述状态转移规律
一个always模块描述状态输出(可以用组合逻辑电路输出,也可以用时序电路输出)
注意:不是通过数always来判断是几段式状态机,比如上面的代码是两组always和一组assign,后面一组assign表示输出规律,所以仍然属于三段式状态机
使用一段式状态机修改上面的三段式状态机代码(代码手打的可能有错误):
module fsm (
input clk,
input reset,
input a,b,
output reg y0,y1
);
localparam [1:0]s0 = 2'b00,s1 = 2'b01,s2 = 2'b10; //符号常量表示有限状态机的状态\
reg[1:0]state_reg;
//一个always模块里面,既有描述状态转移,又有描述状态输入和输出
always @(posedge clk or negedge reset) begin
if(!reset)begin
state_reg<=s0;
y0<=0;
y1<=0;
end
else begin
y0<=0;
y1<=0;
case(state_reg)
s0:begin
y1<=1;
if(a)
if(b)begin
y0<=1;
state_reg<=s2;
end
else begin
state_reg<=s1;
end
else
state_reg<=s0;
end
s1:begin
y1<=1;
if(a)
state_reg<=s0;
else
state_reg<=s1;
end
s2:begin
state_reg<=s0;
end
default:stste_next=s0;
endcase
end
end
endmodule
再使用两段式状态机修改最上面的三段式状态机代码(代码手打的可能有错误):
module fsm(
input clk,
input reset,
input a,b,
output reg y0,y1
);
localparam [1:0]s0 = 2'b00,s1 = 2'b01,s2 = 2'b10; //符号常量表示有限状态机的状态\
reg[1:0]state_reg,state_next;
//一个always模块采用同步时序描述状态转移
always @(posedge clk or negedge reset) begin
if(!reset)
state_reg <= s0;
else
state_reg <= state_next;
end
//一个always模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出
always @(*) begin
case(state_reg)
state_next = state_reg;
y0 = 0;
y1 = 0;
s0:begin
y1 = 1;
if(a)
if(b)begin
state_next = s2;
y0 = 1;
end
else
stste_next = s1;
end
s1:begin
y1 = 1;
if(a)
state_next = s0;
end
s2:state_next = s0;
default : state_next = s0;
endcase
end
endmodule
上面完整的展示了一段 二段 三段状态机,接着评价这三种状态机的优缺点:
一段式状态机:
只有一个always块,结构简单,看起来简洁,直白,但是这种适用于状态简单的有限状态机,主要状态复杂了,逻辑部分就有可能出错,而且不易维护。
两段式状态机:
不利于阅读、理解、维护代码,在第二个组合逻辑输出时,可能存在竞争和冒险,因而产生毛刺。
三段式状态机:
代码容易维护,用时序逻辑输出可以解决两段式组合逻辑产生的毛刺问题,但是从资源消耗的角度来说,三段式资源消耗多。综合与布局布线效果也更佳。