前言
状态机在学习FPGA初期就经常遇到,有必要写一篇笔记整理一下
正文
一、什么是状态机
为啥要用状态机
由于FPGA是并行执行,但当我想要处理有先后顺序
的问题,这个时候就需要用状态机来解决
有几种状态机让我选
Moore状态机
、Mealy状态机
(记得B站数电老师讲到过这两个词语,先挖个坑)
这两个让我怎么选
Moore状态机:输出只和当前状态有关,与输入无关。
Mealy状态机:输出和输入、当前状态有关(常用
)
相同点:状态的跳转只和输入有关
二、状态机的结构
由于一段式状态机、两段式状态机、三段式状态机
中,三段式状态机是最常用的,我就暂时只记录三段式状态机的相关内容
2.1 状态编码
首先要将状态机的所有状态进行编码,这个编码只需要保证没有重复编码即可
parameter IDLE = 3'd0;
parameter HALF_1 = 3'd1;
parameter ONE = 3'd2;
parameter HALF_3 = 3'd3;
2.2 第一段:时序逻辑
第一段是时序逻辑: 描述状态转换,格式固定
always@(posedge Clk or negedge Rst_n)begin
if(Rst_n == 1'b0)
state <= IDLE;
else
state <= next_state;
end
2.3 第二段:组合逻辑
第二段是组合逻辑
状态转移条件:只考虑状态之间的跳转
always@(*)
case(state)
S0:
if()
next_state <= S1;
endcase
2.4 第三段:时序逻辑
第三段是时序逻辑:输出状态
always@(posedge Clk or negedge Rst_n)begin
if(Rst_n == 1'b0)
output<= 1'b0;
else if(((state == S0) && (input== 1'b1)) || ((state == S1) && (input == 1'b1)))//输出由输入+状态共同决定
output<= 1'b1;
三、状态机怎么用?
3.1 可乐贩卖机
目标
:可乐贩卖机;饮料单价 2 元,该售卖机只能接受 0.5 元、1 元的硬币,考虑找零和出货
思路
:首先需要确定选什么作为状态,这里无非就只有可乐和钱,我一定是选钱
明确输入、输出、状态跳变,画出状态转移图
状态机选型
:三段式状态机
设计文件
module fsm_3
(
input Clk,
input Rst_n,
input pi_input_5,
input pi_input_10,
output reg po_out_money,
output reg po_out_water
);
parameter IDLE = 3'd0;
parameter HALF_1 = 3'd1;
parameter ONE = 3'd2;
parameter HALF_3 = 3'd3;
reg [3:0] state ;
reg [3:0] next_state ;
// 第一段
always@(posedge Clk or negedge Rst_n)begin
if(Rst_n == 1'b0)
state <= IDLE;
else
state <= next_state;
end
// 第二段
always@(*)begin
case(state)
IDLE:
case({pi_input_5, pi_input_10})
2'b10: next_state<= HALF_1;
2'b01: next_state<= ONE;
default: next_state<= IDLE;
endcase
HALF_1:
case({pi_input_5, pi_input_10})
2'b10: next_state<= ONE;
2'b01: next_state<= HALF_3;
default: next_state<= HALF_1;
endcase
ONE:
case({pi_input_5, pi_input_10})
2'b10: next_state<= HALF_1;
2'b01: next_state<= IDLE;
default: next_state<= ONE;
endcase
HALF_3:
case({pi_input_5, pi_input_10})
2'b10: next_state<= IDLE;
2'b01: next_state<= IDLE;
default: next_state<= HALF_3;
endcase
endcase
end
// 第三段
always@(posedge Clk or negedge Rst_n)begin
if(Rst_n == 1'b0)
po_out_water <= 1'b0;
else if(((state == HALF_3) && (pi_input_5 == 1'b1)) || ((state == ONE) && (pi_input_10 == 1'b1)))
po_out_water <= 1'b1;
else if((state == HALF_3) && (pi_input_10 == 1'b1)) begin
po_out_water <= 1'b1;
po_out_money <= pi_input_5;
end
else begin
po_out_water <= 1'b0;
po_out_money <= 1'b0;
end
end
endmodule
测试文件
`timescale 1ns/1ns
module tb_fsm_3;
reg clk;
reg Rst_n;
reg in_1;
reg in_2;
reg[1:0] in[2:0];
wire out_money;
wire out_water;
initial begin
in[0]=0;
in[1]=1;
in[2]=2;
clk = 1'b0;
Rst_n = 1'b0;
#10;
Rst_n = 1'b1;
in_1 = 1'b0;
in_2 = 1'b0;
end
always #10 clk = ~ clk;
always@(posedge clk or negedge Rst_n)begin
if(Rst_n == 1'b0)begin
in_1 <= 1'b0;
in_2 <= 1'b0;
end
else begin
{in_1,in_2}<=in[{$random}%3];
end
end
fsm_3 fsm_3_inst
(
.Clk(clk),
.Rst_n(Rst_n),
.pi_input_5(in_1),
.pi_input_10(in_2),
.po_out_money(out_money),
.po_out_water(out_water)
);
endmodule
3.2 序列检测器(序列可重叠)
序列检测器是个啥
:检测输入的一串二进制代码中,是否存在与事先设定的码组一致的代码串,检测电路输出高电平,否则输出低电平
序列检测器用在什么地方
:检测通信系统中的同步码、提取所需信号
要做的事
:使用状态机检测出一串数字序列中的指定6位数据:101011
状态 | 检测序列 |
---|---|
S0 | IDLE |
S1 | 检测1 |
S2 | 检测10 |
S3 | 检测101 |
S4 | 检测1010 |
S5 | 检测10101 |
S6 | 检测101011 |
《关于状态需要返回去东山再起》
:
是否需要返回:不满足检测序列就需要返回
前面几位能用:有几位满足检测序列顺序
自己能否作为起点:自己和序列第1 位相同就能作为起点
设计文件
module seq_1010110
(
input clk ,
input rst_n ,
input data ,
output reg flage
);
reg [2:0]state ;// 下面对状态编码用了1-7,所以这里需要用3位的位宽
reg [2:0]next_state;
parameter S0 = 3'd1;// IDLE
parameter S1 = 3'd2;
parameter S2 = 3'd3;
parameter S3 = 3'd4;
parameter S4 = 3'd5;
parameter S5 = 3'd6;
parameter S6 = 3'd7;
always@(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)
state <= S0;
else
state <= next_state;
end
always@(*)
case(state)
S0:
if(data == 1)
next_state <= S1;
else
next_state <= S0;
S1:
if(data == 0)
next_state <= S2;
else
next_state <= S0;
S2:
if(data == 1)
next_state <= S3;
else
next_state <= S0;
S3:
if(data == 0)
next_state <= S4;
else
next_state <= S1;
S4:
if(data == 1)
next_state <= S5;
else
next_state <= S0;
S5:
if(data == 1)
next_state <= S6;
else
next_state <= S4;
S6:
if(data == 0)
next_state <= S2;
else
next_state <= S1;
endcase
always@(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)
flage <= 1'b0;
else if(state == S6 && data == 0) // Mealy状态机:输出由输入和状态共同决定
flage <= 1'b1;
else
flage <= 1'b0;
end
endmodule
测试文件
`timescale 1ns/1ps
module sequence1010110_tb();
reg clk;
reg rst_n;
reg data;
wire flag;
reg [15:0] ascii_state;//用了2位字符S0-S6,1位字符8bit,所以这里位宽是16
wire [2:0] tb_state;
assign tb_state = seq_1010110_inst.state;
always@(*)begin // 在波形图上能看到用S表示的状态ascii_state
case(tb_state)
3'd1 : ascii_state = "S0";
3'd2 : ascii_state = "S1";
3'd3 : ascii_state = "S2";
3'd4 : ascii_state = "S3";
3'd5 : ascii_state = "S4";
3'd6 : ascii_state = "S5";
3'd7 : ascii_state = "S6";
default : ascii_state = "NO";
endcase
end
initial begin
clk = 1'b0;
rst_n = 1'b0;
data = 1'b0;
#10;
rst_n = 1'b1;
end
always #10 clk = ~ clk;
always begin
#20;
data = 1'b1;
#20;
data = 1'b0;
#20;
data = 1'b1;
#20;
data = 1'b0;
#20;
data = 1'b1;
#20;
data = 1'b1;
#20;
data = 1'b0;
#20;
data = 1'b1;
end
seq_1010110 seq_1010110_inst(
.clk (clk),
.rst_n (rst_n),
.data (data),
.flage (flag)
);
endmodule
波形图
怎么看波形:
重点看state和data
,next_state只是用来给state传递数据的,可以不用重点看
重点看flag在什么时候被拉高,除了要在时钟上升沿,还需要注意是否需要比 (state == S6 &&
data == 0) 慢一拍