如何写好状态机?跑马灯/流水灯Verilog HDL实现

如何写好状态机

很多初学者不知道何时应用状态机。这里介绍两种应用思路:第一种思路,从状态变量入手。如果一个电路具有时序规律或者逻辑顺序,我们就可以自然而然地规划出状态,从这些状态入手,分析每个状态的输入,状态转移和输出,从而完成电路功能;第二种思路是首先明确电路的输出的关系,这些输出相当于状态的输出,回溯规划每个状态,和状态转移条件与状态输入。无论那种思路,使用状态机的目的都是要控制某部分电路,完成某种具有逻辑顺序或时序规律的电路设计。
其实对于逻辑电路而言,小到一个简单的时序逻辑,大到复杂的微处理器,都适合用状态机方法进行描述。打开思路不要仅仅局限于时序逻辑,发现电路的内在规律,确认电路的“状态变量”,大胆使用状态机描述电路模型。由于状态机不仅仅是一种电路描述,它更是一种思想方法,而且状态机的 HDL 语言表达方式比较规范,有章可循,所以有经验的设计者习惯用状态机思想进行逻辑设计,对各种复杂设计都套用状态机的设计理念,从而提高设计的效率和稳定性。

状态机基本要素与分类

基本要素

状态机的基本要素有 3 个,其实我们在第一节的举例中都有涉及,只是没有点明,它们是:状态、输出和输入
1、状态:也叫状态变量。在逻辑设计中,使用状态划分逻辑顺序和时序规律。比如:设计伪随机码发生器时,可以用移位寄存器序列作为状态;在设计电机控制电路时,可以以电机的不同转速作为状态;在设计通信系统时,可以用信令的状态作为状态变量等。
2、输出:输出指在某一个状态时特定发生的事件。如设计电机控制电路中,如果电机转速过高,则输出为转速过高报警,也可以伴随减速指令或降温措施等。
3、输入:指状态机中进入每个状态的条件,有的状态机没有输入条件,其中的状态转移较为简单,有的状态机有输入条件,当某个输入条件存在时才能转移到相应的状态。

分类

根据状态机的输出是否与输入条件相关,可将状态机分为两大类:摩尔(Moore)型状态机和米勒(Mealy)型状态机
1、摩尔状态机:摩尔状态机的输出仅仅依赖于当前状态,而与输入条件无关。每个输出仅仅与状态相关,所以它是一个摩尔型状态
机。
2、米勒型状态机:米勒型状态机的输出不仅依赖于当前状态,而且取决于该状态的输入条件。

工作原理

在这里插入图片描述
根据状态机的数量是否为有限个,可将状态机分为有限状态机(Finite State Machine,FSM)和无限状态机(Infinite State Machine,ISM)。逻辑设计中一般所涉及的状态都是有限的,所以以后我们所说的状态机都指有限状态机,用 FSM 表示。

状态机的基本描述方式

逻辑设计中,状态机的基本描述方式有 3 种,分别是:状态转移图,状态转移列表,HDL 语言描述
1、 状态转移图
状态转移图是状态机描述的最自然的方式。状态转移图经常在设计规划阶段定义逻辑功能时使用,也可以在分析代码中状态机时使用,通过图形化的方式非常有助于理解设计意图。
2、状态转移列表
状态转移列表是用列表的方式描述状态机,是数字逻辑电路常用的设计方法之一,经常被用于对状态化简,对于可编程逻辑设计,由于可用逻辑资源比较丰富,而且状态编码要考虑设计的稳定性,安全性等因素,所以并不经常使用状态转移列表优化状态。
3、HDL 语言描述状态机
使用 HDL 语言描述状态机是本章讨论的重点,使用 HDL 语言描述状态机有一定的灵活性,但是决不是天马行空,而是有章可循的。通过一些规范的描述方法,可以使 HDL 语言描述的状态机更安全、稳定、高效、易于维护。

什么是 RTL 级好的 FSM 描述

首先介绍好的 RTL 级 FSM 的评判标准。其实评判 FSM 的标准很多,这里我们拣选最重要的几个方面讨论一下。好的 RTL 级 FSM 的评判标准如下:
1、FSM 要安全,稳定性高。
所谓 FSM 安全是指 FSM 不会进入死循环,特别是不会进入非预知的状态,而且由于某些扰动进入非设计状态,也能很快的恢复到正常的状态循环中来。这里面有两层含义,第一:要求该 FSM 的综合实现结果无毛刺等异常扰动;第二:要求状态机要完备,即使收到异常扰动进入非设计状态,也能很快恢复到正常状态。
2、 FSM 速度快,满足设计的频率要求。
任何 RTL 设计都应该满足设计的频率要求。
3、 FSM 面积小,满足设计的面积要求。
同理任何 RTL 设计都应该满足设计的面积要求。
4、 FSM 设计要清晰易懂、易维护。
不规范的 FSM 写法很难让其他人解读,甚至过一段时间后设计者也发现很难维护。

RTL 级状态机描述常用语法

FSM 相关的常用关键字如下:

wire 、reg等
对 wire 、reg 等变量、向量定义不加累述,需要补充的是状态编码时(也就是用某种编码描述各个状态)一般都要使用 reg 寄存器型向量。
parameter
用于描述状态名称,增强源代码可读性,简化描述。
例:某状态机使用初始值为“0”的独热码(one-hot)编码方式定义的 4bit 宽度的状态变量 NS(代表 Next State,下一状态)和 CS(代表 Current State,当前状态),且状态机包含 5 个具体状态 IDLE(空闲状态)、S1(工作状态 1)、S2(工作状态 2)、S3(工作状态 3)、ERROR(告警状态),则代码如下:

reg [3:0] NS,CS;
parameter [3:0] //one hot with zero initial
IDLE = 3’b0000,
S1 = 3’b0001,
S2 = 3’b0010,
S3 = 3’b0100,
ERROR = 3’b1000;

always
在 FSM 设计中有 3 种 always 的使用方法,第 1 种用法是根据主时钟沿,完成同步时序的状态迁移。例:某状态机从当前状态 CS 迁移到下一个状态 NS 可以如下表述:

//sequential state transition
always @ (posedge clk or negedge nrst)
if (!nrst)
CS <= IDLE;
else
CS <=NS;

always 的第 2 种用法是根据信号敏感表,完成组合逻辑的输出。
always 的第 3 种用法是根据时钟沿,完成同步时序逻辑的输出。
case/endcase
case/endcase 是 FSM 描述中最重要的语法关键字,这里我们要详细讨论一下。case/endcase 的基本语法结构如下:

case (case_expression)//case_expression 就是 case 的判断条件表达式
case_item1 : case_item_statement1;
case_item2 : case_item_statement2;
case_item3 : case_item_statement3;
case_item4 : case_item_statement4;
default : case_item_statement5;//default 是个可选的关键字,用以指明当所列的所有 case_item 与 case_expression 都不匹配时的操作,在 FSM 设计中,为了提高设计的安全性,排除所设计的 FSM 进入死循环,一般要求加上default 关键字来描述 FSM 所需状态的补集状态下的操作。
endcase

例:某 FSM 的状态转移用 case/endcase 结构描述如下:

case (CS)
IDLE: begin
IDLE_out;
if (~i1) NS = IDLE;
if (i1 && i2) NS = S1;
if (i1 && ~i2) NS = ERROR;
end
S1: begin
S1_out;
if (~i2) NS = S1;
if (i2 && i1) NS = S2;
if (i2 && (~i1)) NS = ERROR;
end
S2: begin
S2_out;
if (i2) NS = S2;
if (~i2 && i1) NS = IDLE;
if (~i2 && (~i1)) NS = ERROR;
end
ERROR: begin
ERROR_out;
if (i1) NS = ERROR;
if (~i1) NS = IDLE;
end
default: begin
Default_out;
NS = ERROR;
end
endcase

状态机描述方式

状态机描述方式,可分为一段式、两段式以及三段式
一段式,整个状态机写到一个 always 模块里面。在该模块中既描述状态转移,又描述状态的输入和输出。
两段式,用两个 always 模块来描述状态机。其中一个 always 模块采用同步时序描述状态转移,另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律及其输出。
三段式,在两个 always 模块描述方法基础上,使用三个 always 模块。一个 always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出(可以用组合电路输出,也可以时序电路输出)。

三种方法比较

在这里插入图片描述

状态机设计的其他技巧

FSM 的编码
Binary(二进制编码)、gray-code(格雷码)编码使用最少的触发器,较多的组合逻辑,而 one-hot(独热码)编码反之。one-hot 编码的最大优势在于状态比较时仅仅需要比较一个 bit,一定程度上从而简化了比较逻辑,减少了毛刺产生的概率。由于 CPLD 更多地提供组合逻辑资源,而 FPGA 更多地提供触发器资源,所以 CPLD 多使用 gray-code,而 FPGA 多使用 one-hot 编码。另一方面,对于小型设计使用 gray-code 和 binary 编码更有效,而大型状态机使用one-hot 更高效。
在代码中添加综合器的综合约束属性或者在图形界面下设置综合约束属性可以比较方便地改变状态的编码。需要注意的是:Synplicity、Synopsys、Exemplar 等综合工具关于 FSM 的综合约束属性的语法格式各不相同。
FSM 初始化状态
一个完备的状态机(健壮性强)应该具备初始化状态和默认状态。当芯片加电或者复位后,状态机应该能够自动将所有判断条件复位,并进入初始化状态。需要注明的一点是,大多数 FPGA 有GSR(Global Set/Reset)信号,当FPGA 加电后,GSR 信号拉高,对所有的寄存器、RAM 等单元复位/置位,这时配置于 FPGA 的逻辑并未生效,所以不能保证正确地进入初始化状态。所以使用 GSR 企图进入 FPGA 的初始化状态,常常会产生种种不必要的麻烦。一般的方法是采用异步复位信号,当然也可以使用同步复位,但是要注意同步复位逻辑的设计。解决这个问题的另一种方法是将默认的初始状态的编码设为全零,这样 GSR 复位后,状态机自动进入初始状态。
FSM 状态编码定义
状态机的定义可以用 parameter 定义,但是不推荐使用`define 宏定义的方式,因为’define 宏定义在编译时自动替换整个设计中所定义的宏,而parameter 仅仅定义模块内部的参数,定义的参数不会与模块外的其他状态机混淆。例如一个工程里面有两个 module 各包含一个 FSM,如果设计时都有IDLE 这一名称的状态,如果使用’define 宏定义就会混淆起来,如果使用parameter 则不会造成任何不良影响。
FSM 输出
如果使用 2 段式 FSM 描述 Mealy 状态机,输出逻辑可以用"?语句"描述,或者使用 case 语句判断转移条件与输入信号即可。如果输出条件比较复杂,而且多个状态共用某些输出,则建议使用task/endtask 将输出封装起来,达到模块复用的目的。
阻塞和非阻塞赋值
为了避免不必要的竞争冒险,不论是做两段式还是三段式 FSM 描述时,必须遵循时序逻辑 always 模块使用非阻塞赋值“<=”,即当前状态向下一状态时序转移,和寄存 FSM 输出等时序 always 模块中都要使用非阻塞赋值;而组合逻辑 always 模块使用阻塞赋值“=”,即状态转移条件判断,组合逻辑输出等always 模块中都要使用阻塞赋值。
FSM 的默认状态
完整的状态机应该包含一个默认(default)状态,当转移条件不满足,或者状态发生了突变时,要能保证逻辑不会陷入“死循环”。这是对状态机健壮性的一个重要要求,也就是常说的要具备“自恢复”功能。对应于编码就是对case 和 if…else 语句要特别注意,尽量使用完备的条件判断语句。Verilog 中,使用 case 语句的时候要用 default 建立默认状态。如果 case 语句中,我们没有写 default 默认状态,其实我们可以将其中一个状态不编码,指定其为 default 默认状态,则任何与所列状态机不匹配的状态都会转到 default 状态,从而增强了 FSM 的健壮性,另外我们也可以添加一个额外的 default 状态,这个一旦进入这个状态就会自动转到 IDLE 状态,从新启动状态机,这样做也增强了状态机的健壮性。

跑马灯/流水灯Verilog HDL实现

led_test

module led_test(
     input clk,
	  input rst_n,
     output reg [3:0] led
     );
   reg [3:0] counters;
   reg [3:0] state;
	
	   parameter//localparam
		CHECK_led0	= 4'b0001,
		CHECK_led1 	= 4'b0010,
		CHECK_led2	= 4'b0100,
		CHECK_led3	= 4'b1000;
	
	
 always@(posedge clk or negedge rst_n)
 begin
     if(!rst_n)  begin
     counters<=4'd0;
     led<=4'b0000;
     state<=CHECK_led0;
     end

     else  begin
       case(state)
           CHECK_led0:begin                        //第一个灯亮
                led<=4'b0001;
                if(counters==4'd15)begin        //20ns  
					     state<=CHECK_led1;
                    counters<=0;
                end    
                else 
                counters<=counters+1;
                
           end
           CHECK_led1:begin                       //第二个灯亮
                led<=4'b0010;
                 if(counters==4'd15)begin          
					     state<=CHECK_led2;
                    counters<=0;
                end    
                else 
                counters<=counters+1;
           end
           CHECK_led2:begin                    //第三个灯亮
                led<=4'b0100;
                 if(counters==4'd15)begin          
					     state<=CHECK_led3;
                    counters<=0;
                end    
                else 
                counters<=counters+1;
           end
           CHECK_led3:begin                    //第四个灯亮
                led<=4'b1000;
                 if(counters==4'd15)begin          
					     state<=CHECK_led0;
                    counters<=0;
                end    
                else 
                counters<=counters+1;
           end
           default: state<=CHECK_led0;
       endcase
	  end
 end
endmodule

led_test_tb

`timescale 1ns/1ns

`define clk_period 20  //常量

module led_test_tb;

	reg clk;
	reg rst_n;
	wire [3:0]led;

	led_test  led_test_uo(
					.clk(clk),
					.rst_n(rst_n),			
					.led(led)
					);
	//初始化
	initial clk= 1;
	//每半个时钟翻转一次     一个时钟周期20ns                             
	always#(`clk_period/2) clk = ~clk;
	
	initial begin
		rst_n = 1'b0;
		#(`clk_period*10) 
		rst_n = 1'b1;
		#(`clk_period*1000) 
		$stop;		
	end
	
endmodule	

仿真波形
在这里插入图片描述
状态转移图
在这里插入图片描述

  • 12
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值