文章目录
一、状态机的基本概念
1.为什么要使用状态机
硬件设计需要串行设计思想,而用Verilog描述的电路大多都是并行实现的,但是对于实际的项目工程,往往需要让硬件来做一些具有顺序的工作,这就要用到状态机的思想。状态机简单的来说就是通过不同的状态迁移来完成一些特定的顺序逻辑
2. 构成状态机的基本要素
- 输入:引发状态变化的条件
- 输出:状态变化后引起的变化
- 状态:S0、S1、S2、S3
以博主浅谈Moore型和Mealy型以及序列检测状态图中的序列检测图为例。
二、关于一段式、二段式、三段式有效状态机状态机
有限状态机:高效的顺序控制模块模型
1. 一段式状态机
将所有的逻辑(输入、输出、状态)在一个always块里进行描述,这种写法看起来简单,但对于复杂的状态会容易出错,并且在大型项目中这些代码是不利于维护的。
2. 二段式状态机
将时序逻辑和组合逻辑划分开来,时序逻辑进行当前状态和下一状态的切换,组合逻辑实现输入、输出以及状态的判断,这种写法相对容易维护,不过组合逻辑输出容易出现毛刺等问题。
3. 三段式状态机
代码易维护,时序逻辑的输出解决了二段式写法中组合逻辑的毛刺问题,但是三段式消耗的资源相对多一点,并且三段式从输入到输出比一段式和二段式会延时一个时钟周期。
三、三种状态机的Verilog实现
输入序列为:010101101 ; 凡收到输入序列101时,输出为1
1. 一段式状态机代码实现
一段式状态机代码`timescale 1ns/1ps
module fsm_1 (
input clk, //50MHZ
input rstn, //复位信号
input data_in,
output reg data_out
);
//序列检测101--可重叠Mealy型--需要三个状态
parameter S0 = 3'b001, //独热码 -- 速度快,资源占用多
S1 = 3'b010,
S2 = 3'b100;
/*
parameter S0 = 2'b00, //二进制编码 -- 资源占用少,速度慢
S1 = 2'b01,
S2 = 2'b10;
*/
reg [2:0] c_state;
//fsm 一段式写法 简单状态机可用,复杂的状态容易出错且不利于维护
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
c_state <= S0;
data_out <= 0;
end
else begin
case(c_state)
S0: begin
if(data_in == 1)begin
c_state <= S1;
data_out <= 0;
end
else begin
c_state <= S0;
data_out <= 0;
end
end
S1: begin
if(data_in == 0)begin
c_state <= S2;
data_out <= 0;
end
else begin
c_state <= S1;
data_out <= 0;
end
end
S2: begin
if(data_in == 1) begin //Mealy型状态机,其输出和S2状态、输入值都有关系
c_state <= S1;
data_out <= 1;
end
else begin
c_state <= S0;
data_out <= 0;
end
end
default : begin c_state = S0; end
endcase
end
end
endmodule
Testbench
`timescale 1ns/1ps
module fsm_tb;
reg clk = 0;
reg rstn = 0;
reg data_in = 0;
wire data_out;
//fsm_2 ; fsm_3 仿真内容相同,只需要修改例化名称即可
fsm_1 dut (
.clk (clk ),
.rstn (rstn ),
.data_in (data_in ),
.data_out (data_out )
);
always
#10 clk = ! clk ; //50Mhz
initial begin
#50 rstn = 1;
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
#1000;
$finish;
end
endmodule
2. 二段式状态机代码实现
二段式状态机代码`timescale 1ns/1ps
module fsm_2 (
input clk, //50MHZ
input rstn, //复位信号
input data_in,
output wire data_out
);
//序列检测101--可重叠Mealy型--需要三个状态
parameter S0 = 3'b001, //独热码 -- 速度快,资源占用多
S1 = 3'b010,
S2 = 3'b100;
/*
parameter S0 = 2'b00, //二进制编码 -- 资源占用少,速度慢
S1 = 2'b01,
S2 = 2'b10;
*/
reg [2:0] c_state;
reg [2:0] n_state;
//fsm 二段式写法 写法相对容易维护,不过组合逻辑输出容易出现毛刺等问题
//第一个always块:进行当前状态和下一个状态的切换 时序逻辑
always @(posedge clk or negedge rstn) begin
if(!rstn)
c_state <= S0;
else
c_state <= n_state;
end
//第二个always块:实现状态跳转判断 组合逻辑 阻塞赋值 =
always @(*) begin
case(c_state)
S0 : n_state = (data_in == 1) ? S1 : S0;
S1 : n_state = (data_in == 0) ? S2 : S1;
S2 : n_state = (data_in == 1) ? S1 : S0;
default : n_state = S0;
endcase
end
assign data_out = (data_in == 1) && (c_state == S2);
endmodule
Testbench
`timescale 1ns/1ps
module fsm_tb;
reg clk = 0;
reg rstn = 0;
reg data_in = 0;
wire data_out;
//fsm_1 ; fsm_3 仿真内容相同,只需要修改例化名称即可
fsm_2 dut (
.clk (clk ),
.rstn (rstn ),
.data_in (data_in ),
.data_out (data_out )
);
always
#10 clk = ! clk ; //50Mhz
initial begin
#50 rstn = 1;
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
#1000;
$finish;
end
endmodule
3. 三段式状态机代码实现
三段式状态机代码`timescale 1ns/1ps
module fsm_2 (
input clk, //50MHZ
input rstn, //复位信号
input data_in,
output reg data_out
);
//序列检测101--可重叠Mealy型--需要三个状态
parameter S0 = 3'b001, //独热码 -- 速度快,资源占用多
S1 = 3'b010,
S2 = 3'b100;
/*
parameter S0 = 2'b00, //二进制编码 -- 资源占用少,速度慢
S1 = 2'b01,
S2 = 2'b10;
*/
reg [2:0] c_state;
reg [2:0] n_state;
//fsm 二段式写法 写法相对容易维护,不过组合逻辑输出容易出现毛刺等问题
//第一个always块:进行当前状态和下一个状态的切换 时序逻辑
always @(posedge clk or negedge rstn) begin
if(!rstn)
c_state <= S0;
else
c_state <= n_state;
end
//第二个always块:实现状态跳转判断 组合逻辑 阻塞赋值 =
always @(*) begin
case(c_state)
S0 : n_state = (data_in == 1) ? S1 : S0;
S1 : n_state = (data_in == 0) ? S2 : S1;
S2 : n_state = (data_in == 1) ? S1 : S0;
default : n_state = S0;
endcase
end
//时序逻辑输出
always@(posedge clk or negedge rstn)begin
if(!rstn)
data_out <= 1'b0;
else if((data_in ==1) && (c_state == S2))
data_out <= 1'b1;
else
data_out <= 1'b0;
end
endmodule
Testbench
`timescale 1ns/1ps
module fsm_tb;
reg clk = 0;
reg rstn = 0;
reg data_in = 0;
wire data_out;
//fsm_1 ; fsm_2 仿真内容相同,只需要修改例化名称即可
fsm_3 dut (
.clk (clk ),
.rstn (rstn ),
.data_in (data_in ),
.data_out (data_out )
);
always
#10 clk = ! clk ; //50Mhz
initial begin
#50 rstn = 1;
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 1;
end
@(posedge clk) begin
data_in <= 0;
end
@(posedge clk) begin
data_in <= 1;
end
#1000;
$finish;
end
endmodule