有限状态机
有限状态机的描述方式很多,在Verilog HDL中常用的有限状态机描述方式是两段式和三段式(一段式非常不推荐)。有限状态机三段描述方式实际上对应的是时序电路中的三个方程:状态转移方程、激励方程和输出方程。两段式描述实际上是将激励方程和输出方程合并在一段中,因此,两段式和三段式的描述方式在本质上是一致的。
有限状态机一般包括寄存器逻辑和组合逻辑两部分,寄存器用于存储状态,组合逻辑用于状态译码和产生输出信号。状态机转移部分是存储电路,而状态的转移判断条件的判断是组合逻辑。
想掌握好状态机,以下几个要素必不可忽略:
- 输入、输出和状态
- 状态转移图
- 状态编码:独热码、二进制码和格雷码的优劣
以下面这个例子展开,先从数字电路分析开始,后再使用Verilog HDL进行状态机的描述,让我们慢慢剖析其中奥秘!
例:设计一个五进制同步加法计数器,且该计数器带有进位输出。
数字电路简要分析
-
确定输入、输出变量个数 本设计为一个五进制计数器,并没有输入,只有一个输出进位信号z。需要设计的是同步五进制计数器,状态个数为五个(异步计数器需要六个状态),我们使用S0,S1,S2,S3,S4表示这五个状态。
-
确定触发器的个数。本文暂使用二进制编码方式,根据数电知识,有一个计算公式
其中N为状态数,m为所需要的触发器,很容易得知,m=3,即只要三个触发器。
- 状态编码。 独热码即每个状态数只有1bit为1,其余都为0。有N个状态,就要N位宽的状态变量。独热码每个状态只有1bit是不同的,综合器会将其识别为一个个比较器,而每次比较的位为1位,节省了大量组合逻辑资源,虽然使用寄存器资源有所增多,谁叫咱FPGA中寄存器资源多呢!
但是如何状态N数目过多时,FPGA也吃不消独热码对寄存器资源的消耗。这时候,格雷码就发挥作用了,格雷码虽然也是和二进制编码一样使用的寄存器资源少,组合逻辑资源多,但是相邻状态转换时就只有一个状态发生翻转,能消除状态转换时有多条信号线的传输延时所造成的毛刺,又可以降低功耗,所以要优于二进制码编码方式。
二进制编码和独热码恰恰相反,它使用了较少的寄存器资源,但是使用的组合逻辑资源较多,虽然减少了寄存器状态,但无法进行比较器部分的优化,且不利于后面的布局布线和时序分析。
通过以上分析得知,我们这里使用三位二进制编码对触发器的状态进行编码,即S0~S4编码为000、001、010、011、100,此时,我们发现三位二进制数可表示8个状态,还剩下三个状态,分别是101、110、111,将其作为无关项,下面是编码后的状态转移图:
4. 建立触发器次态卡诺图,从中分解出各个触发器的次态卡诺图,合并相邻项。
- 对个卡诺图进行化简,根据D触发器的特性方程
可以知晓状态方程、电路的驱动方程和输出方程:
6. 检查电路是否能自启动。将三个无关状态带入到电路的状态方程中,易知三个无关状态的下一个状态都可以进入到主循环中,所以该电路可以自启动。完整的状态转移图如下:
- 根据得到的电路输出方程和驱动方程,可以画出该时序逻辑电路的逻辑电路图,
至此,理论部分结束,整个例子的数字电路的分析也到此为止。
两段式状态机描述五进制同步加法计数器
module counter5_fsm(
input wire sys_clk,
input wire sys_rst_n,
output reg z
);
parameter S0 = 3'b000;
parameter S1 = 3'b001;
parameter S2 = 3'b010;
parameter S3 = 3'b011;
parameter S4 = 3'b100;
reg [2:0] pre_state,next_state;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pre_state <= S0;
else
pre_state <= next_state;
always@(pre_state) begin
next_state = 3'bxxx; // 全都定义为x态,仿真的时候可以检查其完备性,下面有分析
z =1'b0;
case(pre_state)
S0 : begin next_state = S1; z =0;end
S1 : begin next_state = S2; z =0;end
S2 : begin next_state = S3; z =0;end
S3 : begin next_state = S4; z =0;end
S4 : begin next_state = S0; z =1;end
default : begin next_state = S0;z= 0;end
endcase
end
endmodule
程序中使用了两个always语句块,第一个always语句块为同步时序always模块,描述的是状态转移方程;第二个为组合逻辑always模块,描述的是激励方程和输出方程。
值得注意的是,在上面的程序中,第二个always块中对下一个状态进行了默认赋值,即“next_state= 3'bxxx”,通常对下一状态进行默认赋值有三种方式:全部设置为不定态x;设置成预先规定的初始状态;设置成状态机的某一有效状态。通常推荐默认将状态设置成不定态x,这样做的好处是在仿真时可以很好的检查所设计的状态机的完备性,若设计的状态机不完备,则会进入任意状态,仿真时很容易发现。
三段式状态机描述五进制同步加法计数器
程序中使用了三个always语句块,第一个always语句块为同步时序always模块,描述的是状态转移方程;第二个过程块为组合逻辑always模块,描述的是激励方程,第三个过程块为同步时序always模块,描述的是输出方程。
module counter5_fsm(
input wire sys_clk,
input wire sys_rst_n,
output reg z
);
parameter S0 = 3'b000;
parameter S1 = 3'b001;
parameter S2 = 3'b010;
parameter S3 = 3'b011;
parameter S4 = 3'b100;
reg [2:0] pre_state,next_state;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pre_state <= S0;
else
pre_state <= next_state;
always@(pre_state)
begin
next_state = 3'bxxx;
case(pre_state)
S0 : begin next_state = S1; end
S1 : begin next_state = S2; end
S2 : begin next_state = S3; end
S3 : begin next_state = S4; end
S4 : begin next_state = S0; end
default : begin next_state = S0;end
endcase
end
always@(pre_state)
begin
z = 1'b0;
case(pre_state)
S0 : begin z=0; end
S1 : begin z=0; end
S2 : begin z=0; end
S3 : begin z=0; end
S4 : begin z=1; end
default : begin z=0;end
endcase
end
endmodule
仿真文件、波形如下
`timescale 1ns/1ns
module tb_counter5_fsm();
reg sys_clk;
reg sys_rst_n;
wire z;
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
counter5_fsm counter5_fsm_inst(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.z(z)
);
endmodule
在输出z的波形图上,很容易得知整个计数器模块契合了我们的设计!!!
未完待续......