上节内容讲解了状态机的基本理论、分类以及在数字电路,软件方面的建模及应用,最后以简单的例子对比常规Verilog程序设计状态机程序之间的设计关系。可以看出在时序电路的设计钟,特别是Verilog语言的描述中,尽管没有显式的使用状态机的描述方式,状态机在时序电路的设计中几乎无处不在的。多数时序电路都可以以状态机的方式进行描述。本节内容就状态机的几个要点做比较详细的介绍。
1.状态机状常用的几个名词介绍
-
时钟(clk)
由于状态机在数字逻辑设计中,是时序电路的一个部分,只有在时钟的驱动下才能按节拍运行,因此时钟是状态机关键因素。后面将以clk的代表时钟。
-
寄存器(reg)
在Verilog程序设计中,时序电路是由register 和clock共同实现的,因此register 必不可少,在以后的描述中以reg代表寄存器。
-
状态state
以S代表状态, cs(current state)表示现态(当前状态),ns(next state)表示次态(下一个状态)。
-
输入(input)
一般情况下状态的转换由现态和输入共同决定,输入不是必须的,如上节内容的例1,就没有输入,只是由clk和状态决定状态的条状和输出。
-
方程
表述状态转换或输出与现态与输入之间的关系。如图1,描述摩尔机的三个方程,分别为现态方程,次态方程与输出方程,后面结合摩尔机与梅利机进行详细描述。在时序电路中方程的表象形式为赋值语句。
2. 状态机的分析与实现
-
摩尔机
上节内容中虽然在状态机分类中提到摩尔机以及梅利机两种常用类型,但并没有做详细介绍。下面就这两种状态机作比较详细的介绍。
- 摩尔机:输出只与状态有关,与输入没有关系,这种状态机称为摩尔机。其方程描述如图1。

图1 摩尔机方程

图2 摩尔机时序逻辑图
在图1中, 方程ns=M{inputs, cs }表示,根据现态cs和输入input决定次态ns, 这个方程由组合逻辑实现。输出output=M{CS }仅由现态cs决定。由现态cs生成次态ns的关系描述可以由并发赋值语句,always过程中由if语句或case语句控制实现。
例1: 10进制计数器及数码管译码显示状态机的描述
变量定义如下:
时钟: clk
状态: reg [3:0] count
输出 :reg [6:0] SEVEN_SEG
程序如下:
module count_dis
(
input rst,
input clk,
output reg [6:0] SEVEN_SEG
);
reg [3:0] count;
always@(posedge clk or posedge rst)
if(rst)
begin
count <= 0;
end
else
begin
if(count == 9)
count <= 0;
else
count <= count + 1;
end
always@(*)
case(count)
0: SEVEN_SEG = 7'b100_0000;
1: SEVEN_SEG = 7'b111_1001;
2: SEVEN_SEG = 7'b010_0100;
3: SEVEN_SEG = 7'b011_0000;
4: SEVEN_SEG = 7'b001_1001;
5: SEVEN_SEG = 7'b001_0010;
6: SEVEN_SEG = 7'b000_0010;
7: SEVEN_SEG = 7'b111_1000;
8: SEVEN_SEG = 7'b000_0000;
9: SEVEN_SEG = 7'b001_0000;
4'ha: SEVEN_SEG = 7'b000_1000;
4'hb: SEVEN_SEG = 7'b000_0011;
4'hC: SEVEN_SEG = 7'b100_0110;
4'hd: SEVEN_SEG = 7'b010_0001;
4'hE: SEVEN_SEG = 7'b000_0110;
4'hF: SEVEN_SEG = 7'b000_1110;
default: SEVEN_SEG = 7'b111_1111;
endcase
endmodule
从上面的例子可以看出状态转换是在always过程中由if语句实现的,看看如何和上面的方程对应的。
- ns在状态(count)为9时 ns=0; 在状态为0–8时, ns=count+1,组合逻辑方程,
- cs=ns(在clk的上升沿驱动下),这里的cs实际上就是count,时序逻辑,
- 输出 SEVEN_SEG 由case语句实现(组合逻辑)
上面的状态机程序也可以改为例2方式实现,
例2 用cs 与ns 的方式显示描述10进制计数器的状态机描述。
module count_dis
(
input rst,
input clk,
output reg [6:0] SEVEN_SEG
);
reg [3:0] count; //count ---> cs,这里count表示现态cs.
wire [3:0] ns = (count == 9) ? 0 : count + 1; //ns 的实现
always@(posedge clk or posedge rst)
if(rst)
begin
count <= 0;
end
else
begin
count <= ns;
end
always@(*)
case(count)
0: SEVEN_SEG = 7'b100_0000;
1: SEVEN_SEG = 7'b111_1001;
2: SEVEN_SEG = 7'b010_0100;
3: SEVEN_SEG = 7'b011_0000;
4: SEVEN_SEG = 7'b001_1001;
5: SEVEN_SEG = 7'b001_0010;
6: SEVEN_SEG = 7'b000_0010;
7: SEVEN_SEG = 7'b111_1000;
8: SEVEN_SEG = 7'b000_0000;
9: SEVEN_SEG = 7'b001_0000;
4'ha: SEVEN_SEG = 7'b000_1000;
4'hb: SEVEN_SEG = 7'b000_0011;
4'hC: SEVEN_SEG = 7'b100_0110;
4'hd: SEVEN_SEG = 7'b010_0001;
4'hE: SEVEN_SEG = 7'b000_0110;
4'hF: SEVEN_SEG = 7'b000_1110;
default: SEVEN_SEG = 7'b111_1111;
endcase
endmodule
从上例的描述可以清楚的看出状态机的现态cs(count)及次态ns的实现。而且从本例可以看出,输出仅仅是状态的译码,与输入无关,因此是典型的摩尔机。当然ns的描述也可以在always过程中由case语句实现。如例3:
例3,
module count_dis
(
input rst,
input clk,
output reg [6:0] SEVEN_SEG
);
reg [3:0] count; // count ---> cs,这里count表示现态cs.
reg [3:0] ns; // ns 的实现
always@(posedge clk or posedge rst)
if(rst)
begin
count <= 0;
end
else
begin
count <= ns;
end
always@(*)
case(count)
0,1,2,3,4,5,6,7,8: ns = count + 1;
9: ns = 0;
default: ns = 0;
endcase
always@(*)
case(count)
0: SEVEN_SEG = 7'b100_0000;
1: SEVEN_SEG = 7'b111_1001;
2: SEVEN_SEG = 7'b010_0100;
3: SEVEN_SEG = 7'b011_0000;
4: SEVEN_SEG = 7'b001_1001;
5: SEVEN_SEG = 7'b001_0010;
6: SEVEN_SEG = 7'b000_0010;
7: SEVEN_SEG = 7'b111_1000;
8: SEVEN_SEG = 7'b000_0000;
9: SEVEN_SEG = 7'b001_0000;
4'ha: SEVEN_SEG = 7'b000_1000;
4'hb: SEVEN_SEG = 7'b000_0011;
4'hC: SEVEN_SEG = 7'b100_0110;
4'hd: SEVEN_SEG = 7'b010_0001;
4'hE: SEVEN_SEG = 7'b000_0110;
4'hF: SEVEN_SEG = 7'b000_1110;
default: SEVEN_SEG = 7'b111_1111;
endcase
endmodule
例3是典型的3段式状态机。至于如何使用一段式,二段式,三段式状态机,下节内容将会详细讲解。
-
梅利机
梅利机与摩尔机不同,其输出不仅与状态有关还与输入有关。梅利机的描述方程如图2

图3
从图2可以看出,梅利机与摩尔机的惟一区别就在于输出。

图4
梅利机的描述如下面的例子所示,在状态S0时系统的输出也由输入与状态共同决定,这种状态机是典型的梅利机。梅利机中的输出可以是寄存器输出,也可以是组合逻辑输出。一般为了输出稳定都在clk的边沿下锁存输出。

例4. 利用mealy机实现CPU指令的读取、译码、寄存器读取、执行、回写等过程。
有许多CPU的运行是由状态机实现的。本文举例的CPU运行的基本过程是指令读取、译码、寄存器读取、执行、寄存器(存储器)回写。这5个过程也可以发展为流水线的方式实现,也就成了5级流水线CPU架构,流水线架构依然离不开状态机的控制。

图5
在图5中,由状态机控制完成整个流程,步骤如下
- 状态1: 首先产生指令存储器的地址(ADDR),跳转到状态2
- 状态2:读取指令(input),跳转到状态3
- 状态3:指令输出(output),指令译码,跳转到状态4
- 状态4:寄存器读取,跳转到状态5
- 状态5:指令执行,跳到状态6,
- 状态6:判断是否需要寄存器回写,生成新的指令地址,返回状态2。
假设系统复位后的首地址为32’h8000_0000,指令为32位,程序如下:
module CPU_run
(
input rst,
input clk,
input [31:0] addr_in, //从CPU的执行阶段,获得下条指令的地址
input [31:0] instr,
output reg [31:0] addr_r,
output reg [31:0] instr_r,
output reg dec_r,
output reg reg_rd_r,
output reg exe_r, //执行指示
output reg wr_b_r, //write back
input wr_b_need //write back need,由译码产生
);
reg [2:0] cpu_run_st;
always@(posedge clk or posedge rst)
if(rst)
begin
addr_r <= 32'h8000_0000;
instr_r <= 32'hffff_ffff;
dec_r <= 0;
reg_rd_r <= 0;
exe_r <= 0;
wr_b_r <= 0;
cpu_run_st <= 0;
end
else
begin
case(cpu_run_st)
0: //start , addr_r 设为32'h8000_0000,初始化
begin
addr_r <= 32'h8000_0000;
instr_r <= 32'hffff_ffff;
dec_r <= 0;
reg_rd_r <= 0;
exe_r <= 0;
wr_b_r <= 0;
cpu_run_st <= 1;
end
1: //等待一个时钟,一般存储器输出会有一个时钟的delay
begin
dec_r <= 0;
reg_rd_r <= 0;
exe_r <= 0;
wr_b_r <= 0;
cpu_run_st <= 2;
end
2: //指令输出到译码器 , 指令输出
begin
instr_r <= instr;
cpu_run_st <= 3;
end
3://decode stage,译码阶段
begin
cpu_run_st <= 4;
dec_r <= 1'b1;
end
4: //寄存器读取
begin
cpu_run_st <= 5;
dec_r <= 1'b0;
reg_rd_r <= 1'b1;
end
5: //执行阶段
begin
reg_rd_r <= 1'b0;
exe_r <= 1'b1;
cpu_run_st <= 6;
end
6: //回写
begin
exe_r <= 1'b0;
cpu_run_st <= 1;
addr_r <= addr_in;
if(wr_b_need) //wr_b_need 由指令译码产生
wr_b_r <= 1'b1;
else
wr_b_r <= 1'b0;
end
default: cpu_run_st <= 0;
endcase
end
endmodule
从上面的程序可以看出,指令输出不仅和状态有关(状态2设定),同时输出由输入指令(instr)决定,如wr_b_need是由输入指令(instr)产生的信号,决定wr_b_r输出是否有效,因此是典型的mealy机。
-
摩尔机与梅利机比较
下面以一个例子说明摩尔机与梅利机的不同,如例5:
例5:
module edgeDetector
(
input clk,
input rst,
input level,
output reg Mealy_tick,
output reg Moore_tick
);
localparam
ZERO_MEALY = 1'b0,
ONE_MEALY = 1'b1;
localparam [1:0]
ZERO_MOORE = 2'b00,
EDGE_MOORE = 2'b01,
ONE_MOORE = 2'b10;
reg Mealy_cs, Mealy_ns;
reg [1:0] Moore_cs, Moore_ns;
//current state <= next state , first stage
always @(posedge clk or posedge rst)
if(rst)
begin
Mealy_cs <= ZERO_MEALY;
Moore_cs<= ZERO_MOORE;
end
else
begin
Mealy_cs <= Mealy_ns;
Moore_cs <= Moore_ns;
end
//===========================================
//Mealy state machine , second stage
always @(Mealy_cs, level)
begin
Mealy_ns = Mealy_cs;
Mealy_tick = 1'b0;
case(Mealy_cs)
ZERO_MEALY:
if(level)
begin
Mealy_ns = ONE_MEALY;
Mealy_tick = 1'b1;
end
ONE_MEALY:
if(~level)
Mealy_ns = ZERO_MEALY;
endcase
end
//Moore state machine, second stage
always @(Moore_cs, level)
begin
Moore_ns = Moore_cs;
Moore_tick = 1'b0;
case(Moore_cs)
ZERO_MOORE:
if(level)
Moore_ns = EDGE_MOORE;
EDGE_MOORE:
begin
Moore_tick = 1'b1;
if(level)
Moore_ns = ONE_MOORE;
else
Moore_ns = ZERO_MOORE;
end
ONE_MOORE:
if(~level)
Moore_ns = ZERO_MOORE;
endcase
end
//===========================================
endmodule
仿真的波形图如图5,

图5
从图5的红色虚线圆中标出的位置可以看出Mealy_tick的输出紧随输入level,而摩尔机的输出Moore 机的输出是Moore_tick与clk的边沿对齐的,因此Moore 机的输出只与状态有关。上面的状态机是典型的二段式状态机,关于一段,二段,三段式状态机,下节内容将详细讨论。
3. 状态机中输入与状态的关系
从上面的几个例子可以看出,例1,2,3的状态跳转只与状态自身有关,而与输入无关,因此状态机的转移步伐是等间隔的。这种应用场景也是比较常见的,如钟表的计时,整点报时,等间隔流水作业的过程控制等。而更多的状态机的跳转是在输入的条件下进行的,这样就实现了跳转间隔不等长的情况,可以根据需要调整状态跳转的时间,灵活多变的时间分配可以处理更复杂的需求。如例4,例5所示。注意这里的输入可以是输入输出接口,也可能是module内部组合逻辑产生的信号。