上几节内容介绍了状态机建模以及摩尔机、梅利机的描述,状态机的编码格式等内容,本节内容着重介绍状态机的分段描述方法。按照状态机的分段描述方法,状态机可分为一段式状态机,二段式状态机,三段式状态机。下面以我们比较熟悉的例子通过不同的分段方式学习它们之间的异同,并通过这些例子对前面学过的知识加以复习。
例1:按键去抖程序
module debouncing
(
input rst,
input clk,
input ms_p, //毫秒脉冲
input sw_in, //按键输入
output sw_out //按键输出
);
reg [2:0] bn_r; //按键输入锁存
reg bn_hp_debounce; //按键_高电平脉冲_去抖
reg [3:0] deb_count;
reg [1:0] deb_st; //按键去抖状态机的状态
assign sw_out = bn_hp_debounce;
always@(posedge clk or posedge rst )
if(rst)
bn_r = 0;
else
bn_r = {bn_r[1:0], sw_in}; //按键三次锁存,消除亚稳态
always@(posedge clk or posedge rst )
if(rst)
begin
deb_st = 0;
deb_count = 0;
bn_hp_debounce = 0;
end
else
begin
case(deb_st)
0:
begin
deb_count = 0;
bn_hp_debounce = 0;
if(bn_r[2])
deb_st = 1;
end
1:
begin
if(~bn_r[2])
deb_st = 0;
else if(deb_count >= 10)
deb_st = 2;
else if(ms_p)
deb_count = deb_count + 1;
end
2:
begin
deb_count = 0;
if(~bn_r[2])
deb_st = 3;
end
3:
begin
if(bn_r[2])
deb_st = 2;
else if(deb_count >= 10)
begin
deb_st = 0;
bn_hp_debounce = 1'b1;
end
else if(ms_p)
deb_count = deb_count + 1;
end
default: deb_st = 0;
endcase
end
endmodule

上面的代码是比较标准的一段式状态机描述的。状态转移图中描述了总共四个状态,分别是:
- 状态0等待按键输入(按下);
- 状态1判别是否为抖动期,在状态1与状态0之间切换,消除按下抖动;
- 状态2检测按键释放;
- 状态3与状态2联合消除按键释放抖动。
为了清晰表明这四个状态,下面用localparam 重新定义这四个状态,并将程序重新书写如例2,
例2,采用参数常量描述按键去抖状态机
module debouncing
(
input rst,
input clk,
input ms_p, //毫秒脉冲
input sw_in, //按键输入
output sw_out //按键输出
);
localparam [1:0]
PD_CAPTURE = 0,//按下捕获
PD_DEBOUNCING = 1,
RLS_CAPTURE = 2,//释放捕获(release)
RLS_DEBOUNCING = 3;
reg [2:0] bn_r; //按键输入锁存
reg bn_hp_debounce; //按键_高电平脉冲_去抖
reg [3:0] deb_count;
reg [1:0] deb_st; //按键去抖状态机的状态
assign sw_out = bn_hp_debounce;
always@(posedge clk or posedge rst )
if(rst)
bn_r = 0;
else
bn_r = {bn_r[1:0], sw_in}; //按键三次锁存,消除亚稳态
always@(posedge clk or posedge rst )
if(rst)
begin
deb_st = PD_CAPTURE;
deb_count = 0;
bn_hp_debounce = 0;
end
else
begin
case(deb_st)
PD_CAPTURE:
begin
deb_count = 0;
bn_hp_debounce = 0;
if(bn_r[2])
deb_st = PD_DEBOUNCING;
end
PD_DEBOUNCING:
begin
if(~bn_r[2])
deb_st = PD_CAPTURE;
else if(deb_count >= 10)
deb_st = RLS_CAPTURE;
else if(ms_p)
deb_count = deb_count + 1;
end
RLS_CAPTURE:
begin
deb_count = 0;
if(~bn_r[2])
deb_st = RLS_DEBOUNCING;
end
RLS_DEBOUNCING:
begin
if(bn_r[2])
deb_st = RLS_CAPTURE;
else if(deb_count>=10)
begin
deb_st = PD_CAPTURE;
bn_hp_debounce = 1'b1;
end
else if(ms_p)
deb_count = deb_count + 1;
end
default: deb_st = PD_CAPTURE;
endcase
end
endmodule
经过改写之后可以看出,按键去抖程序的状态转换更加清晰。而且从上例的最后可以看出按键最后的输出不仅与状态有关(RLS_DEBOUNCING),而且与按键的输入有关,因此该状态机是梅利机。而且上面是标准的一段式状态机。下面对照

图1 摩尔机的时序图

图2 梅利机的时序图
1. 一段式状态机
一段式的状态机是将状态转移,现态,次态,输出等都在一个由always引导的时序电路中实现的,并由时钟边沿驱动,如例1,例2,这样的状态机称为一段式状态机。
2. 二段式状态机CS
二段式状态机顾名思义是由两段代码组成,第一段描述次态(NS)逻辑值在时钟边沿下锁存到现态(CS)的时序电路。第二段描述如何根据现态(CS)与输入决定次态(NS)的逻辑,并在第二段描述输出逻辑。同时注意其它程序的实现,如状态变量实现的条件等。将上面的按键去抖程序改写成二段式状态机。
例 2:将按键去抖程序改写为两段式状态机
module debouncing
(
input rst,
input clk,
input ms_p, //毫秒脉冲
input sw_in, //按键输入
output sw_out //按键输出
);
localparam [1:0]
PD_CAPTURE = 0, //按下捕获
PD_DEBOUNCING = 1,
RLS_CAPTURE = 2, //释放捕获(release)
RLS_DEBOUNCING = 3;
reg [2:0] bn_r; //按键输入锁存
reg bn_hp_debounce; //按键_高电平脉冲_去抖
reg [3:0] deb_count_cs, deb_count_ns;
reg [1:0] deb_cs, deb_ns; //按键去抖状态机的状态
assign sw_out = bn_hp_debounce;
always@(posedge clk or posedge rst )
if(rst) bn_r <= 3'b0;
else bn_r <= {bn_r[1:0],sw_in};
//==========================================================
// 二段式状态机第一段,只描述次态锁存到现态的过程
always@(posedge clk or posedge rst )
if(rst) deb_cs <= 0;
else deb_cs <= deb_ns;
// 对状态机中使用的变量进行描述,也可以合并到第一段中,但不建议这样做,因为会引起状态机的识别不清晰
always@(posedge clk or posedge rst )
if(rst) deb_count_cs <= 0;
else deb_count_cs <= deb_count_ns;
//==========================================================
//状态机的第二段,描述次态逻辑以及输出,是组合逻辑。一般在考虑输出的同步及时序收敛等因素,
//会将输出进行时钟锁存。状态机的第二段是组合逻辑描述,建议采用阻塞赋值语句。
always@(*)
begin
deb_ns = deb_cs; //初始化,第一句将现态的值赋给次态。该赋值语句是必须的。
bn_hp_debounce = 1'b0;
case(deb_cs)
PD_CAPTURE:
begin
bn_hp_debounce = 0;
if(bn_r[2])
deb_ns = PD_DEBOUNCING;
end
PD_DEBOUNCING:
begin
if(~bn_r[2])
deb_ns = PD_CAPTURE;
else if(deb_count_ns >= 10)
deb_ns = RLS_CAPTURE;
end
RLS_CAPTURE:
begin
if(~bn_r[2])
deb_ns = RLS_DEBOUNCING;
end
RLS_DEBOUNCING:
begin
if(bn_r[2])
deb_ns = RLS_CAPTURE;
else if(deb_count_ns >= 10)
begin
deb_ns = PD_CAPTURE;
bn_hp_debounce = 1'b1;
end
end
default: deb_ns = PD_CAPTURE;
endcase
end
always@(*)
begin
if(deb_cs == PD_CAPTURE || deb_cs == RLS_CAPTURE )
deb_count_ns = 0;
else if(ms_p)
deb_count_ns = deb_count_cs + 1;
else
deb_count_ns = deb_count_cs;
end
//==========================================================
endmodule
上述程序中 else if (deb_count_ns >= 10) 用来做判断条件时,有一些缺点:
1) 组合逻辑过长
2)计数可能不准确,可能出现瞬态过程
所以将此修改成else if (deb_count_cs >= 10),修改后的二段式状态机代码:
module debouncing
(
input rst,
input clk,
input ms_p, //毫秒脉冲
input sw_in, //按键输入
output sw_out //按键输出
);
localparam [1:0]
PD_CAPTURE = 0, //按下捕获
PD_DEBOUNCING = 1,
RLS_CAPTURE = 2, //释放捕获(release)
RLS_DEBOUNCING = 3;
reg [2:0] bn_r; //按键输入锁存
reg bn_hp_debounce; //按键_高电平脉冲_去抖
reg [3:0] deb_count_cs, deb_count_ns;
reg [1:0] deb_cs, deb_ns; //按键去抖状态机的状态
assign sw_out = bn_hp_debounce;
always@(posedge clk or posedge rst )
if(rst) bn_r <= 3'b0;
else bn_r <= {bn_r[1:0],sw_in};
//==========================================================
// 二段式状态机第一段,只描述次态锁存到现态的过程
always@(posedge clk or posedge rst )
if(rst) deb_cs <= 0;
else deb_cs <= deb_ns;
// 对状态机中使用的变量进行描述,也可以合并到第一段中,但不建议这样做,因为会引起状态机的识别不清晰
always@(posedge clk or posedge rst )
if(rst) deb_count_cs <= 0;
else deb_count_cs <= deb_count_ns;
//==========================================================
//状态机的第二段,描述次态逻辑以及输出,是组合逻辑。一般在考虑输出的同步及时序收敛等因素,
//会将输出进行时钟锁存。状态机的第二段是组合逻辑描述,建议采用阻塞赋值语句。
always@(*)
begin
deb_ns = deb_cs; //初始化,第一句将现态的值赋给次态。该赋值语句是必须的。
bn_hp_debounce = 1'b0;
case(deb_cs)
PD_CAPTURE:
begin
bn_hp_debounce = 0;
if(bn_r[2])
deb_ns = PD_DEBOUNCING;
end
PD_DEBOUNCING:
begin
if(~bn_r[2])
deb_ns = PD_CAPTURE;
else if(deb_count_cs >= 10)
deb_ns = RLS_CAPTURE;
end
RLS_CAPTURE:
begin
if(~bn_r[2])
deb_ns = RLS_DEBOUNCING;
end
RLS_DEBOUNCING:
begin
if(bn_r[2])
deb_ns = RLS_CAPTURE;
else if(deb_count_cs >= 10)
begin
deb_ns = PD_CAPTURE;
bn_hp_debounce = 1'b1;
end
end
default: deb_ns = PD_CAPTURE;
endcase
end
always@(*)
begin
if(deb_cs == PD_CAPTURE || deb_cs == RLS_CAPTURE )
deb_count_ns = 0;
else if(ms_p)
deb_count_ns = deb_count_cs + 1;
else
deb_count_ns = deb_count_cs;
end
//==========================================================
endmodule
从上面的代码可以看出,二段式的状态机把状态机的实现分成了两段,第一段专注将次态(deb_ns)逻辑在时钟上升沿时锁存到现态的寄存器中(deb_cs)。
always@(posedge clk or posedge rst )
if(rst)
begin
deb_cs <= 0;
end
else
begin
deb_cs <= deb_ns;
end
状态机的第二段,根据现态和输入(按键输入和毫秒脉冲输入)决定次态(deb_ns)的逻辑以及输出逻辑。为了保持输出稳定及同步,一般会用时钟锁存。如可以添加如下语句:
reg bn_hp_debounce_r;
assign sw_out = bn_hp_debounce_r;
always@(posedge clk or posedge rst )
if(rst)
begin
bn_hp_debounce_r <= 0;
end
else
begin
bn_hp_debounce_r <= bn_hp_debounce;
end
除此之外,二段式状态机可以将deb_count_ns 的计数实现合并到第二段中,如下所示:
//状态机第二段,包括deb_count_ns 的计数
always@(*)
begin
deb_ns = deb_cs; //初始化,第一句将现态的值赋给次态。该赋值语句是必须的。
deb_count_ns = deb_count_cs;
bn_hp_debounce = 1'b0;
case(deb_cs)
PD_CAPTURE:
begin
deb_count_ns = 0;
bn_hp_debounce = 0;
if(bn_r[2])
deb_ns = PD_DEBOUNCING;
end
PD_DEBOUNCING:
begin
if(~bn_r[2])
deb_ns = PD_CAPTURE;
else if(deb_count_cs >= 10)
deb_ns = RLS_CAPTURE;
else if(ms_p)
deb_count_ns = deb_count_cs + 1;
end
RLS_CAPTURE:
begin
deb_count_ns = 0;
if(~bn_r[2])
deb_ns = RLS_DEBOUNCING;
end
RLS_DEBOUNCING:
begin
if(bn_r[2])
deb_ns = RLS_CAPTURE;
else if(deb_count_cs >= 10)
begin
deb_ns = PD_CAPTURE;
bn_hp_debounce = 1'b1;
end
else if(ms_p)
deb_count_ns = deb_count_cs + 1'b1;
end
default: deb_ns = PD_CAPTURE;
endcase
end
3. 三段式状态机
三段式状态机将二段式状态机的第二段再进行拆分,将次态的实现作为状态机的第二段,将输出逻辑作为状态机的第三段。伴随着三段状态机的可能还有其它与状态转移相关的逻辑,该部分内容不属于三段式状态机本身的内容,可以分开描述,也可以合并到对应的状态机的各个段中。
例3,将按键去抖程序用三段式状态机描述
module debouncing
(
input rst,
input clk,
input ms_p, //毫秒脉冲
input sw_in, //按键输入
output sw_out //按键输出
);
localparam [1:0]
PD_CAPTURE = 0, //按下捕获
PD_DEBOUNCING = 1,
RLS_CAPTURE = 2, //释放捕获(release)
RLS_DEBOUNCING = 3;
reg [2:0] bn_r; //按键输入锁存
reg bn_hp_debounce; //按键_高电平脉冲_去抖
reg [3:0] deb_count_cs, deb_count_ns;
reg [1:0] deb_cs, deb_ns; //按键去抖状态机的状态
assign sw_out = bn_hp_debounce;
//======================对状态机中使用的变量进行描述,
always@(posedge clk or posedge rst )
if(rst) bn_r = 3'b0;
else bn_r = {bn_r[1:0],sw_in}; //按键三次锁存,消除亚稳态
//==========================================================
// 三段式状态机第一段,只描述次态锁存到现态的过程
always@(posedge clk or posedge rst )
if(rst) deb_cs <= 0;
else deb_cs <= deb_ns;
always@(posedge clk or posedge rst )
if(rst) deb_count_cs = 0;
else deb_count_cs = deb_count_ns;
//==========================================================
//状态机的第二段,描述次态逻辑以及输出,是组合逻辑。
always@(*)
begin
deb_ns = deb_cs;
case(deb_cs)
PD_CAPTURE:
begin
if(bn_r[2])
deb_ns = PD_DEBOUNCING;
end
PD_DEBOUNCING:
begin
if(~bn_r[2])
deb_ns = PD_CAPTURE;
else if(deb_count_cs >= 10)
deb_ns = RLS_CAPTURE;
end
RLS_CAPTURE:
begin
if(~bn_r[2])
deb_ns = RLS_DEBOUNCING;
end
RLS_DEBOUNCING:
begin
if(bn_r[2])
deb_ns = RLS_CAPTURE;
else if(deb_count_cs >= 10)
begin
deb_ns = PD_CAPTURE;
end
end
default: deb_ns = PD_CAPTURE;
endcase
end
always@(*)
begin
if(deb_cs == PD_CAPTURE || deb_cs == RLS_CAPTURE )
deb_count_ns = 0;
else if(ms_p)
deb_count_ns = deb_count_cs + 1;
else
deb_count_ns = deb_count_cs;
end
//==========================================================
//状态机的第三段,一般在考虑输出的同步及时序收敛等因素,会将输出进行时钟锁存。
always@(posedge clk or posedge rst)
if(rst)
bn_hp_debounce = 1'b0;
else
begin
if( (deb_cs == RLS_DEBOUNCING) && ( deb_count_cs >= 10) )
bn_hp_debounce = 1'b1;
else
bn_hp_debounce = 1'b0;
end
endmodule
从例3可以看出在三段式状态机中,状态机的第二段专注处理次态逻辑,将其它的逻辑实现放在第三段处理。
其中,三段式状态机的第三段还可以使用组合逻辑的方法进行描述,如下所示:
//状态机的第三段使用组合逻辑的方法进行描述
always@(*)
if(rst)
bn_hp_debounce = 1'b0;
else
begin
if( (deb_cs == RLS_DEBOUNCING) && ( deb_count_cs >= 10) )
bn_hp_debounce = 1'b1;
else
bn_hp_debounce = 1'b0;
end
4. 分段状态机的比较
状态机的分段描述比较见表1。
表1:
| 一段式 | 二段式 | 三段式 | |
| 优点 | 输入、输出、状态在同一个always块中描述,容易编写和维护;
输出为同步信号; |
是定义标准的状态机模型,有利于综合器的综合;
高速设计中应用广泛; |
是综合效率最高的一种状态机;
输出信号可以为组合逻辑,也可以为同步信号; |
| 缺点 | 一定程度上不利于综合软件的综合;降低综合的效率和性能;
在较复杂的状态机设计中,修改和维护比较困难。 使用的触发器多 |
设计和分析时需要综合考虑组合逻辑的性能,优化时有一定难度;
输出信号为非同步信号,不利于调试; |
需要分段进行状态机和输出信号的描述,设计和代码编写上有一定难度,不利于阅读和维护; |
具体使用哪种方式作为状态机的描述方式,根据实际需要而定。一般较为简单的状态机描述由于比较容易理解,综合容易,多为一段式状态机。复杂的设计多为二段或三段式状态机。在多存储器(如双口RAM,FIFO等数据处理),特别是在有潜伏期(latency)读写时,可以利用状态机的跳转等待存储器输出延迟,一段式状态机描述更有优势。
作业题:
在Vivado和Quartus下分别例化 18 X 18 的乘法器(使用乘法器IP核),分别设置乘法器的latency为0-6,实现乘法器的连续运算,分别利用一段式,二段式,三段式状态机进行描述,并优化程序的性能(频率优化)。