FPGA状态机学习
温馨提示:这篇文章已超过410天没有更新,请注意相关的内容是否还可用!
Verilog 是硬件描述语言,硬件电路是并行执行的,当需要按照流程或者步骤来完成某个功能时,代码中通常会使用很多个 if 嵌套语句来实现,这样就增加了代码的复杂度,以及降低了代码的可读性,这个时候就可以使用状态机来编写代码。状态机相当于一个控制器,它将一项功能的完成分解为若干步,每一步对应于二进制的一个状态,通过预先设计的顺序在各状态之间进行转换,状态转换的过程就是实现逻辑功能的过程。
状态机,全称是有限状态机(Finite State Machine,缩写为 FSM),是一种在有限个状态之间按一定规律转换的时序电路,可以认为是组合逻辑和时序逻辑的一种组合。状态机通过控制各个状态的跳转来控制流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显,因此基本上都会用到状态机,如 SDRAM 控制器等。在本手册提供的例程中,会有多个用到状态机设计的例子,希望大家能够慢慢体会和理解,并且能够熟练掌握。
根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即摩尔(Moore)型状态机和米勒(Mealy)型状态机。
➢ Mealy 状态机:组合逻辑的输出不仅取决于当前状态,还取决于输入状态。
➢ Moore 状态机:组合逻辑的输出只取决于当前状态。
1、Mealy 状态机
米勒状态机的模型如下图所示,模型中第一个方框是指产生下一状态的组合逻辑 F,F 是当前状态和输入信号的函数,状态是否改变、如何改变,取决于组合逻辑 F 的输出;第二框图是指状态寄存器,其由一组触发器组成,用来记忆状态机当前所处的状态,状态的改变只发生在时钟的跳边沿;第三个框图是指产生输出的组合逻辑 G,状态机的输出是由输出组合逻辑 G 提供的,G 也是当前状态和输入信号的函数。
Mealy 状态机模型
2、Moore 状态机
摩尔状态机的模型如下图所示,对比米勒状态机的模型可以发现,其区别在于米勒状态机的输出由当前状态和输入条件决定的,而摩尔状态机的输出只取决于当前状态。
Moore 状态机模型
3、三段式状态机
根据状态机的实际写法,状态机还可以分为一段式、二段式和三段式状态机。
一段式:整个状态机写到一个 always 模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。不推荐采用这种状态机,因为从代码风格方面来讲,一般都会要求把组合逻辑和时序逻辑分开;从代码维护和升级来说,组合逻辑和时序逻辑混合在一起不利于代码维护和修改,也不利于约束。
二段式:用两个 always 模块来描述状态机,其中一个 always 模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。不同于一段式状态机的是,它需要定义两个状态,现态和次态,然后通过现态和次态的转换来实现时序逻辑。
三段式:在两个 always 模块描述方法基础上,使用三个 always 模块,一个 always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出(可以用组合电路输出,也可以时序电路输出)。
实际应用中三段式状态机使用最多,因为三段式状态机将组合逻辑和时序分开,有利于综合器分析优化以及程序的维护;并且三段式状态机将状态转移与状态输出分开,使代码看上去更加清晰易懂,提高了代码的可读性,推荐大家使用三段式状态机,本文也着重讲解三段式。
三段式状态机的基本格式是:
第一个 always 语句实现同步状态跳转;
第二个 always 语句采用组合逻辑判断状态转移条件;
第三个 always 语句描述状态输出(可以用组合电路输出,也可以时序电路输出)。
在开始编写状态机代码之前,一般先画出状态跳转图,这样在编写代码时思路会比较清晰,下面以一个 7 分频为例(对于分频等较简单的功能,可以不使用状态机,这里只是演示状态机编写的方法),状态跳转图如下图所示:
七分频状态跳转图
状态跳转图画完之后,接下来通过 parameter 来定义各个不同状态的参数,如下代码所示:
parameter S0 = 7’b0000001; //独热码定义方式 parameter S1 = 7’b0000010; parameter S2 = 7’b0000100; parameter S3 = 7’b0001000; parameter S4 = 7’b0010000; parameter S5 = 7’b0100000; parameter S6 = 7’b1000000;
这里是使用独热码的方式来定义状态机,每个状态只有一位为 1,当然也可以直接定义成十进制的 0,1,2……7。
因为我们定义成独热码的方式,每一个状态的位宽为 7 位,接下来还需要定义两个 7 位的寄存器,一个用来表示当前状态,另一个用来表示下一个状态,如下所示:
reg [6:0] curr_st ; //当前状态 reg [6:0] next_st ; //下一个状态
接下来就可以使用三个 always 语句来开始编写状态机的代码,第一个 always 采用同步时序描述状态转移,第二个 always 采用组合逻辑判断状态转移条件,第三个 always 是描述状态输出,一个完整的三段式状态机的例子如下代码所示:
1 module divider7_fsm ( 2 //系统时钟与复位 3 input sys_clk , 4 input sys_rst_n , 5 6 //输出时钟 7 output reg clk_divide_7 8 ); 9 10 //parameter define 11 parameter S0 = 7’b0000001; //独热码定义方式 12 parameter S1 = 7’b0000010; 13 parameter S2 = 7’b0000100; 14 parameter S3 = 7’b0001000; 15 parameter S4 = 7’b0010000; 16 parameter S5 = 7’b0100000; 17 parameter S6 = 7’b1000000; 18 19 //reg define 20 reg [6:0] curr_st ; //当前状态 21 reg [6:0] next_st ; //下一个状态 22 23 //***************************************************** 24 //** main code 25 //***************************************************** 26 27 //状态机的第一段采用同步时序描述状态转移 28 always @(posedge sys_clk or negedge sys_rst_n) begin 29 if (!sys_rst_n) 30 curr_st



