状态机电路框图
注:第三段状态机的变化方式较多:
1、输入可以是next_state也可以是cur_state,next_state的化,输出可提前一个周期,但第三段状态机的电路类型必须是时序逻辑电路,否则,组合(输入)+组合(输出)的方式容易出现一些错误;
2、输入是cur_state的话,电路类型可以是组合或时序,因为cur_state是时序输出,因此第三段状态机的电路的时序必然会和cur_state的时序保持一致,因此无需在电路类型。
题目
以一个简单的例子来测试一下3段式状态机的使用
假如有一个饮料机,需要投入3个硬币,才会输出一瓶可乐
其中data_in表示是否投入硬币,data_out表示是否输出一瓶可乐
mealy型
状态转移图
首先根据题目要求,画一个状态转移图。
idle,one:当没有硬币投入的时候,饮料机处于空闲状态,下一状态就会一直维持在当前状态,当有硬币投入的时候就会跳转到下一状态。
two:当投入了2个硬币的情况下,需要判断,是否还会有第3个硬币投入的情况,如有,输出可乐,并重新进入空闲状态,否则,等待(不能说没有硬币投入,就把前两个硬币给取消了吧)
时序图
next_state的输入信号有两个:1、data_in;2、cur_state
具体:next_state根据cur_state和data_in来变化,如果data_in为1,则跳转到下一状态,否则保持当前状态
比如:当cur_state==idle if(data_in==1) next_state:idle->one else next_state:idle->idle
data_out:当前状态是two,且data_in等于1,输出1。
由于是组合逻辑电路,所以是同时变化的,不存在延迟一个周期的情况
仿真图
通过仿真验证,可以发现仿真图的时序和画出来的时序一致。
注:在仿真时要将next_state_name和cur_state_name的格式调成ACSII码,才会显示字符。
RTL代码
module mealy_3
(
input clk,
input rst_n,
input data_in,
output reg data_out
);
//3个状态
localparam idle = 3'b001;
localparam one = 3'b010;
localparam two = 3'b100;
//时序电路的输入输出
reg [2:0] next_state;
reg [2:0] cur_state;
//第一段状态机,输入next_state,输出cur_state,时序电路
always@(posedge clk or negedge rst_n)
if(!rst_n)
cur_state <= idle;
else
cur_state <= next_state;
//第二段状态机,输入有data_in和cur_state,输出有next_state,组合电路
always@(*)
begin
case(cur_state)
idle: next_state = (data_in)?one:idle;
one: next_state = (data_in)?two:one;
two: next_state = (data_in)?idle:two;
default:next_state = idle;
endcase
end
//第三段状态机,输入有data_in和cur_state,输出有data_out,时序电路
always@(posedge clk or negedge rst_n)
begin
case(cur_state)
two:if(data_in)
data_out <= 1'b1;
default:data_out <= 1'b0;
endcase
end
endmodule
仿真代码
`timescale 1ns/1ns
module tb_mealy_3();
reg clk,rst_n ;
reg data_in ;
wire data_out ;
initial
begin
clk = 1'b0;
rst_n <= 1'b0;
data_in <= 1'b0;
#30
rst_n <= 1'b1;
#20
data_in <= 1'b1;//拉高40ns,两个周期
#40
data_in <= 1'b0;//拉低20ns,一个周期
#20
data_in <= 1'b1;//拉高20ns,一个周期
#20
data_in <= 1'b0;//拉低20ns,一个周期
#20
data_in <= 1'b1;//拉高60ns,三个周期
#60
data_in <= 1'b0;//接下来,一直拉低
end
always #10 clk = ~clk;
//在仿真中显示状态的名字,状态名称最长有4位,每位占1个字节
reg [31:0] cur_state_name;
reg [31:0] next_state_name;
always@(*)
begin
case(u_mealy_3.cur_state)
3'b001:cur_state_name = "idle";
3'b010:cur_state_name = "one";
3'b100:cur_state_name = "two";
default:cur_state_name = "idle";
endcase
end
always@(*)
begin
case(u_mealy_3.next_state)
3'b001:next_state_name = "idle";
3'b010:next_state_name = "one";
3'b100:next_state_name = "two";
default:next_state_name = "idle";
endcase
end
mealy_3 u_mealy_3
(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.data_out(data_out)
);
endmodule
进一步对第三段状态机进行讨论
1、时序+cur_state
always@(posedge clk or negedge rst_n)
begin
case(cur_state)
two:if(data_in)
data_out <= 1'b1;
else
data_out <= 1'b0;
default:data_out <= 1'b0;
endcase
end
2、组合+cur_state
always@(*)
begin
case(cur_state)
two:if(data_in)
data_out <= 1'b1;
else
data_out <= 1'b0;
default:data_out <= 1'b0;
endcase
end
从第1和第2种方式可以看出,组合会比时序提前一个周期输出。
3、时序+next_state
always@(posedge clk or negedge rst_n)
begin
case(next_state)
two:if(data_in)
data_out <= 1'b1;
else
data_out <= 1'b0;
default:data_out <= 1'b0;
endcase
end
4、组合+next_state
always@(*)
begin
case(next_state)
two:if(data_in)
data_out <= 1'b1;
else
data_out <= 1'b0;
default:data_out <= 1'b0;
endcase
end
可以看到用next_state输出的data_out的时机并不对,理论上应该是在第三个硬币投入的时候,才能进行输出,但第3和第4种方式都是在第2个硬币投入时输出,所以用next_state做第3段状态机的方式并不合适。
moore型
idle,one,two:当没有硬币投入的时候,饮料机处于空闲状态,下一状态就会一直维持在当前状态,当有硬币投入的时候就会跳转到下一状态。
three:当投入了3个硬币的情况下,需要判断,是否还会有第4个硬币投入的情况,如有,输出可乐,并进入one状态,否则,直接输出一瓶可乐,并进入空闲状态
时序图
next_state的输入信号有两个:1、data_in,2、cur_state
具体:next_state根据cur_state和data_in来变化
当cur_state为idle、one、two:如果data_in为1,则跳转到下一状态,否则保持当前状态
当cur_state为three状态:如果data_in为1,next_state就会变成one,如果是0,那么next_state就变成idle
data_out:当前状态为three时,输出1
仿真图
RTL代码
module moore_3
(
input clk,
input rst_n,
input data_in,
output reg data_out
);
//3个状态
localparam idle = 4'b0001;
localparam one = 4'b0010;
localparam two = 4'b0100;
localparam three= 4'b1000;
//时序电路的输入输出
reg [3:0] next_state;
reg [3:0] cur_state;
//第一段状态机,输入next_state,输出cur_state,时序电路
always@(posedge clk or negedge rst_n)
if(!rst_n)
cur_state <= idle;
else
cur_state <= next_state;
//第二段状态机,输入有data_in和cur_state,输出有next_state,组合电路
always@(*)
begin
case(cur_state)
idle: next_state = (data_in)?one:idle;
one: next_state = (data_in)?two:one;
two: next_state = (data_in)?three:two;
three: next_state = (data_in)?one:idle;
default:next_state = idle;
endcase
end
//第三段状态机,输入只有cur_state,输出有data_out,这里用组合电路
always@(posedge clk or negedge rst_n)
begin
case(cur_state)
three: data_out <= 1'b1;
default:data_out <= 1'b0;
endcase
end
endmodule
仿真代码
`timescale 1ns/1ns
module tb_moore_3();
reg clk,rst_n ;
reg data_in ;
wire data_out ;
initial
begin
clk = 1'b0;
rst_n <= 1'b0;
data_in <= 1'b0;
#30
rst_n <= 1'b1;
#20
data_in <= 1'b1;//拉高40ns,两个周期
#40
data_in <= 1'b0;//拉低20ns,一个周期
#20
data_in <= 1'b1;//拉高20ns,一个周期
#20
data_in <= 1'b0;//拉低20ns,一个周期
#20
data_in <= 1'b1;//拉高60ns,三个周期
#60
data_in <= 1'b0;//接下来,一直拉低
end
always #10 clk = ~clk;
//在仿真中显示状态的名字,状态名称最长有5位,每位占1个字节
reg [39:0] cur_state_name;
reg [39:0] next_state_name;
always@(*)
begin
case(u_moore_3.cur_state)
4'b0001:cur_state_name = "idle";
4'b0010:cur_state_name = "one";
4'b0100:cur_state_name = "two";
4'b1000:cur_state_name = "three";
default:cur_state_name = "idle";
endcase
end
always@(*)
begin
case(u_moore_3.next_state)
4'b0001:next_state_name = "idle";
4'b0010:next_state_name = "one";
4'b0100:next_state_name = "two";
4'b1000:next_state_name = "three";
default:next_state_name = "idle";
endcase
end
moore_3 u_moore_3
(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.data_out(data_out)
);
endmodule
进一步对第三段状态机进行讨论
1、时序+cur_state
always@(posedge clk or negedge rst_n)
begin
case(cur_state)
three: data_out <= 1'b1;
default:data_out <= 1'b0;
endcase
end
2、组合+cur_state
always@(*)
begin
case(cur_state)
three: data_out <= 1'b1;
default:data_out <= 1'b0;
endcase
end
从第1和第2种方式可以看出,组合会比时序提前一个周期输出。
3、时序+next_state
always@(posedge clk or negedge rst_n)
begin
case(next_state)
three: data_out <= 1'b1;
default:data_out <= 1'b0;
endcase
end
4、组合+next_state
always@(*)
begin
case(next_state)
three: data_out <= 1'b1;
default:data_out <= 1'b0;
endcase
end
对于moore状态机的第三段状态机的输入并没有因为是next_state而出现错误。