师傅领进门,修行靠个人(状态机)
写在前面的话
(1)状态机(Finite State Machine, FSM),是用来控制在有限状态之间跳变和时序电路,包含组合逻辑和时序逻辑,用来控制数字IC的流程。
(2)几乎所有的Verilog语法书中都会介绍状态机,而每本语法书中对状态机的描述都各有千秋,每个人对状态机的理解和代码风格也不同。真的算是师傅领进门,修行靠个人。
(3)状态机的基本要素包含状态个数、状态转移条件、输出以及输入。
(4)描述状态机有状态转移图(常考)、状态转移列表以及HDL语言描述(会基础的状态机写法)。
(5)按照代码风格,分为一段式、两段式和三段式(推荐使用)。
状态机简介
状态机类型
状态机分两类,米利型(Mealy)和摩尔型(Moore)
米利状态机:输出取决于当前状态和输入。
摩尔状态机:输出只取决于当前状态。
我是这么记的:当前是摩尔时代,输出只与当前状态有关,就是摩尔状态机。
米利状态机框图:
摩尔状态机框图:
看输出和谁相关。
一段、两端还是三段?
状态机的Coding Style要求:
(1)易于调整,便于修改状态和状态机风格。
(2)简介紧凑、简单、易懂、便于调试。
(3)能够综合,且综合效率高。
几点规则:
(1)使用localparam 定义状态编码,避免使用`define 和parameter
(2)时序always块中,使用非阻塞赋值<=
(3)组合always块中,使用阻塞赋值=
(4)组合逻辑敏感列表使用always@(*)
(5)case语句不要忘记default:
(6)FPGA设计中编码方式用One-hot(速度更快),采用二进制编码时候,若是顺序执行,可用Gray码(避免亚稳态)。
什么是一段、两段、三段?
一段、两段、三段指的是状态机的写法。有几个always块,就是几段式。
(1)一段式:
状态机所有部分都写到一个always块中,并且这个always块敏感列表只能是时钟clk。
一个always块要兼顾描述状态转移和输入输出。
(2)两段式:
状态机分为两个always块来写,一个用时序逻辑描述状态转移。
另一个用组合逻辑描述状态转移条件、转移规律以及输出。
(3)三段式:
状态机分为三个always块来写。
一个用时序逻辑描述状态转移;
一个用组合逻辑描述状态转移条件和转移规律;
一个描述状态输出(可以是时序逻辑,也可以是组合逻辑)
一段、两段、三段优缺点
一段式优点: 简单,适用于状态数少的状态机。
一段式缺点:
(1)不符合时序逻辑和组合逻辑分开描述的Coding Style;
(2)不利于维护修改,时间久了,自己理解也很困难;
(3)不利于附加约束,在时序分析和面积功耗约束时难优化;
(4)不利于综合软件的布局、布线以及对设计的自动优化。
两段式优点: 时序逻辑和组合逻辑分开,便于分析。
两段式缺点: 由于输出只能采用组合逻辑,容易产生毛刺,造成亚稳态。
三段式优点:
(1)时序逻辑和组合逻辑分开,便于分析。
(2)利于综合软件的分析和优化。
(3)代码简介明了,便于维护。
三段式缺点:
(1)代码结构相较两段式复杂。
(2)采用时序逻辑输出避免了亚稳态,但是增加了触发器的使用。
Verilog模板
推荐使用三段式,这里也列举三段式状态机模板。
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// Author : HFUT904 1320343336@qq.com
// File : fsm.v
// Create : 2022-11-03 11:07:12
// Revise : 2022-11-03 11:07:12
// Editor : HFUT Integrated Circuit Design & Research Center
// Verdion: v1.0
// Description: FSM 三段式模板
// -----------------------------------------------------------------------------
// localparam
module fsm (
input clk , // Clock
input rst_n , // Asynchronous reset active low
output reg out // output
);
//状态定义
localparam IDLE = 4'b0001 ;
localparam S0 = 4'b0010 ;
localparam S1 = 4'b0100 ;
localparam S2 = 4'b1000 ;
//定义当前状态和下一状态寄存器
reg [3:0] CS ; //当前状态
reg [3:0] NS ; //下一状态
//第一段时序逻辑 描述状态转移
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
CS <= IDLE;
end
else begin
CS <= NS;
end
end
//第二段组合逻辑 描述状态转移条件和规律
always @(*) begin
case(CS)
IDLE : NS = S0 ;
S0 : NS = S1 ;
S1 : NS = S2 ;
S2 : NS = IDLE ;
default: NS = IDLE ;
endcase
end
//第三段组合逻辑或时序逻辑 描述状态输出
always@(posedge clk or negedge rst_n) begin
if(~rst_n)
out <= 1'b0 ;
else begin
case (CS)
IDLE : out <= 1'b0 ;
S0 : out <= 1'b1 ;
S1 : out <= 1'b1 ;
S2 : out <= 1'b0 ;
default : out <= 1'b0 ;
endcase
end
end
endmodule
总结
状态机的风格因人而异,每个人都有自己熟悉的状态机风格,状态机的关键在于:
(1)简介明了,将多余的状态进行化简;
(2)代码可综合,使用三段式状态机在debug和综合过程中占据优势;
(3)掌握三段式状态机的写法;
(4)编码方式也会影响状态机的性能,体会独热码和格雷码的优缺点。
最后,状态机相关的知识点都是大差不差,重要的还是多加练习,在项目中体会不同状态机的用处,多学习别人的代码。