moore&mealy状态机区分(附例子&代码)&三段式描述方式

在状态机部分,moore和mealy也算是老生常谈了吧。

什么是状态?

moore&mealy状态机区分(附例子&代码)&三段式描述方式_第1张图片
说白了就是通过时钟信号不断改变当前的状态,可能是根据输入的数据,也可能是自身发生改变(比如一些计时器),所以少不了触发器,虽然我们有功能十分多的JK,有RS,但是我们一般采用的触发器类型都是D触发器。

状态机的设计

实际电路的设计和verilog设计还不大相同。因为verilog好歹还不是那么底层,不需要自己进行搭线(除非采用结构化描述),也就是我们可以省略一些步骤。
moore&mealy状态机区分(附例子&代码)&三段式描述方式_第2张图片
好了么,基本上省了很多了,只需要将状态找出来,进行分配,然后给出状态转移情况即可。

moore和mealy的区分

moore&mealy状态机区分(附例子&代码)&三段式描述方式_第3张图片
很明显了,mealy的输出不但有当前的Q,还有input的参与,再看看moore就没有这么多事,直接受现态Q的控制。

相互转化?

说是这么说,但是不真正给一个例子分析一下,就这样口嗨很难看懂的,而看不懂就很难明白为什么两者可以相互转换。

例子:从八位拨码开关检测序列01011的存在(别问为什么都是并行输入了还需要使用状态机)

首先,我们先给出一个个的状态:
(一般是默认0为S0,1为S1)

状态 内容 输出
S0 0 0
S1 1 0
S2 01 0
S3 010 0
S4 0101 0
S5 01011 1

我们可以看出来,上面那组输出是只和状态有关,而和输入无关(因为根本就没有给出输入情况)
所以这个的状态分配就是moore型了
状态转换:
moore&mealy状态机区分(附例子&代码)&三段式描述方式_第4张图片
那么mealy是什么样的呢?

状态 输入0/输出 输入1/输出 内容
S0 S0/0 S2/0 0
S1 S0/0 S1/0 1
S2 S3/0 S1/0 01
S3 S0/0 S4/0 010
S4 S3/0 S1/1 0101

可以看出,输出不但和状态有关,也看输入的内容(S4+输入1才能输出Z = 1)

我们可以发现,在Moore和mealy中,状态的个数是不同的,其实在一些情况下,两者的状态种类都是不一样的。(如自动贩卖机中,Moore是投入的总硬币价值,mealy是每次投入硬币的价值)
抓住问题的本质,才能设计出Moore和mealy型的状态。

实现

我们的例子还没完呢,我们现在只是得到了基本的状态,还没有进行分配并实现。
状态分配很简单,Si = i即可。
在实现的过程中,我们就不得不提一下三段式的问题了。
这里声明一下,因为被检测串的第一位是0,所以这里采用001状态(S1)为起始状态。(如果采用0为起始状态,会将1011检测为正确)

三段式

在数字逻辑设计中,我们知道时序电路有三个重要的方程:输入方程、驱动方程和输出方程,不论怎么叫,本质上就是:

  • 确定触发器的输入端内容
  • 确定触发器的次态
  • 确定整体的输出。这里注意,看的是现态而不是次态!

而这三个方程我们分别使用三个always块来实现,这就是三段式。这种描述方法思路更加清晰、便于维护,并且输出变量由时序逻辑控制,不会产生毛刺现象。

接着叙述我们的题目:
利用拨码开关输入8位数,按下s0进行数据输入,使用状态机判断8位中是否存在01011字串,如果有,led灯亮;没有则熄灭。另外我们还有一个异步复位按键,将状态机状态归零并熄灭led灯。

我们先实现Moore型的

有一说一这个例子不太好,看一下三段式的写法即可,就别cv了。(其实虽然实现了,但代码不是很完美)

细节:

  • 我们在每一个输入只能改变状态一次:我们采用按时钟周期输入的方式来解决问题;
  • 在输入结束后,我们需要保持led灯的情况,也就是要保持判断,所以在输出always块中选择如果输出为真则一直为真这样一个分支(除非是有set或者rst信号)
  • 我们需要在输入结束将state状态改为001,也就是起始状态,而我们在控制计数器开始时有一个时钟信号(该信号覆盖了整个状态转移的过程),刚好我们可以使用这个信号来实现。

输入:set、rst、时钟信号和8位拨码开关输入
输出:led灯使能信号detect_o
代码:

`timescale 1ns / 1ps
module moor_top(
    input rst_n_i,				//异步复位&状态机回到初始状态,高电平有效
    input set_i,				//同步使能端,高电平有效
    input clk_i,
    input [7:0]data,
    output reg detect_o
    );
    reg x=1'b0;                 //输入
    reg [7:0]in=8'b0000_0000;   //存储八位
    reg [3:0]number=4'b0000;    //计数器
    reg [2:0]state=3'b000;      //当前状态
    reg if_in=1'b0;             //判断是否停止计数

    //当前状态的状态寄存器
    always @(posedge clk_i)
    begin
        if(set_i)               //有效输入
        begin
            in <= data;
            if_in <= 1'b1;      //开始计数
        end
        else if(if_in)
        begin
            if(number != 4'b1000)
            begin
                x <= in[number];
                number <= number + 1;
            end
            else				//结束计数
            begin
                number <= 0;
                if_in <= 1'b0;
            end
       end
       else;
    end
    //描述下一状态的状态寄存器
    always @(posedge clk_i)
    begin
        if(rst_n_i)				//复位
        begin
            state <= 3'b001;
        end
        else if(!if_in)			//输入结束,不需要状态转移,直接归零
            state <= 3'b001;
        else
        case({
     x,state})
            4'b0000:state <= 3'b000;
            4'b0001:state <= 3'b000;
            4'b0010:state <= 3'b011;
            4'b0011:state <= 3'b000;
            4'b0100:state <= 3'b011;
            4'b0101:state <= 3'b000;
            4'b1000:state <= 3'b010;
            4'b1001:state <= 3'b001;
            4'b1010:state <= 3'b001;
            4'b1011:state <= 3'b100;
            4'b1100:state <= 3'b101;
            4'b1101:state <= 3'b001;
            default;
        endcase
    end
    //描述输出
    always @(*)
    begin
        if(set_i || rst_n_i)            //再次输入&重置
        begin
            detect_o = 1'b0;
        end
        else if(detect_o == 1'b1)       //保持亮
            detect_o = 1'b1;
        else
        begin
            if(state == 3'b101)
            begin
                detect_o = 1'b1;
            end
            else
            begin
                detect_o = 1'b0;
            end
        end
    end
endmodule

这里面有一个部分写的不是很好,如果能看出来就会发现我是从数据的低位到高位读入,也就是会将高低位反过来,所以就需要将约束文件中拨码开关的管脚和数组下标反着对应。这一点在仿真中十分明显。
仿真文件:(包含了三个例子,记得数据是反着输入的,第一个亮,但是会被置零第二个不亮第三个亮)

`timescale 1ns / 1ps
module EX5_moore_sim(    );
    reg rst_n_i=1'b0;
    reg set_i=1'b0;
    reg clk_i=1'b0;
    reg [7:0]data=8'b1110_1010;
    wire detect;
    
    moor_top test(rst_n_i,set_i,clk_i,data,detect);
    
    always #5 clk_i = ~clk_i;
    
    initial 
    begin
        #10 set_i = 1;
        #10  set_i = 0;
        
        #100 rst_n_i = 1;
        #10  rst_n_i = 0;
        
        #100 data = 8'b1111_1011;
        #5  set_i = 1;
        #10 set_i = 0;
        
        #100 data = 8'b0110_1010;
        #5  set_i = 1;
        #10 set_i = 0;
        
    end
endmodule

报错:

  1. [Place 30-574] Poor placement for routing between an IO pin and BUFG. If this sub optimal condition is acceptable for this design, you may use the CLOCK_DEDICATED_ROUTE constraint in the .xdc file to demote this message to a WARNING. However, the use of this override is highly discouraged. These examples can be used directly in the .xdc file to override this clock rule.
    < set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets set_i_IBUF] >
    set_i_IBUF_inst (IBUF.O) is locked to IOB_X1Y34
    and set_i_IBUF_BUFG_inst (BUFG.I) is provisionally placed by clockplacer on BUFGCTRL_X0Y0
  2. [Synth 8-327] inferring latch for variable ‘detect_o_reg’ [“D:/digit_FPGA/EX5_moor/EX5_moor.srcs/sources_1/new/moor_top.v”:64]
  3. [DRC LUTLP-1] Combinatorial Loop Alert: 1 LUT cells form a combinatorial loop. This can create a race condition. Timing analysis may not be accurate. The preferred resolution is to modify the design to remove combinatorial logic loops. If the loop is known and understood, this DRC can be bypassed by acknowledging the condition and setting the following XDC constraint on any one of the nets in the loop: ‘set_property ALLOW_COMBINATORIAL_LOOPS TRUE [get_nets ]’. One net in the loop is detect_o_OBUF. Please evaluate your design. The cells in the loop are: detect_o_OBUF_inst_i_1.

我们先看一下第一个,是因为我们采用了将set_i作为判断程序是否开始的信号,也相当于一个时钟信号,但我们又没有说明,所以就会报错。
解决方式:在约束文件中加上尖括号中的语句(在后来的修改过程中,我们不需要这样处理,所以在最终的版本中我们没有使用这条语句)。

第二个问题:
形成了锁存器,这个是因为两个always块分别为时序和组合逻辑,而vivado又是一个偏向时序的软件,所以我们有时候会看到这个报错,但也是迫不得已吧,其实不需要处理的。
在网上看到一个说法,就是在在报错的形成锁存器的地方先赋初值,然后进行处理,没有尝试过,先给出这样的说法。

第三个,是因为组合逻辑打环?也是按照error的弹出,将对应内容添加到xdc文件即可。
set_property ALLOW_COMBINATORIAL_LOOPS true [get_nets -of_objects [get_cells+报错中的内容]](可能有好几条语句,这里只有一个)
set_property SEVERITY {Warning} [get_drc_checks LUTLP-1]
set_property SEVERITY {Warning} [get_drc_checks NSTD-1]
在这里插入图片描述

mealy型实现

复习一下,mealy型是输出和输入有关的那种。

mealy基本上和moore的结构相同,这个问题刚好状态分配还比较像,只需要改一下状态转移表,然后修改一下判断输出正确的判定即可。

有一点没有提到的

在之前我们都是直接使用这个状态,但是我们有没有想过,我们应该是有两个状态的,那么我们在Moore中使用的是哪个呢?
次态
那会不会造成错误呢?
其实还是会有一点的,但是我们因为是限定八位输入,所以影响不大。
mealy型呢?
这里就要声明一下了,因为我们次态和输入都是卡时钟周期的(前面说过为了保证能够实现八位依次输入)所以在看过仿真波形会发现,我们的状态(次态)是相比输入晚一个周期的,那么也就是说,在我们相比的过程中,两者其实是都晚了一个周期(我们可以采取现态和次态的方式,然后再整一个x_pre,但是在尝试过后感觉实在是太蠢了就放弃了(其实这个串行输入我也感觉很蠢)。
代码:

`timescale 1ns / 1ps

module mealy(
    input rst_n_i,          //低有效的复位变量
    input set_i,            //ͬ高有效的输入使能
    input clk_i,
    input [7:0]data,
    output reg detect_o,
    output reg [2:0]state,
    output reg x
    );
    reg [7:0]in = 8'b0000_0000;
    reg [3:0]number = 4'b0000;
    reg if_in=1'b0;         //判断输入的使能信号
    wire clk_o;
    //输入->触发器内容
    divider_1s div(clk_i,clk_o);
    always@(posedge clk_o)
    begin
        if(set_i)
        begin
            in <= data;
            if_in <= 1'b1;
        end
        else if(if_in)
        begin
            if(number != 4'b1000)
            begin
                x <= in[number];
                number <= number + 1'b1;
            end
            else
            begin
                number <= 4'b0000;
                if_in <= 1'b0;
                x <= 1'b0;
            end
        end
       else;
    end
    //触发器状态转换
    always @(posedge clk_o)
    begin
        if(rst_n_i)//重置
        begin
            state <= 3'b001;
        end
        else if(!if_in)
        begin
            state <= 3'b001;
        end
        else
        case({
     x,state})
            4'b0000:state <= 3'b000;
            4'b0001:state <= 3'b000;
            4'b0010:state <= 3'b011;
            4'b0011:state <= 3'b000;
            4'b0100:state <= 3'b011;
            
            4'b1000:state <= 3'b010;
            4'b1001:state <= 3'b001;
            4'b1010:state <= 3'b001;
            4'b1011:state <= 3'b100;
            4'b1100:state <= 3'b001;
            default;
        endcase
    end
    //输出
    always @(*)
    begin
        if(set_i || rst_n_i)
        begin
            detect_o = 1'b0;
        end
        else if(detect_o == 1'b1)
            detect_o = 1'b1;
        else
        begin
            //需要看当前的输入和现态
            if(state == 3'b100 && x == 1'b1)
            begin
                detect_o = 1'b1;
            end
            else
            begin
                detect_o = 1'b0;
            end
        end
    end
endmodule

(仿真和之前的差不多,就没有贴出)

代码中奇奇怪怪的部分

之前有一个01010101的串一直有一个问题,我就添加了一些东西来检测:
将状态和输入显示在led灯上,之前说过了状态是次态,输入也是下一个输入,不过不耽误我们分析内部逻辑。
但是100MHZ还是太快,所以我加了分频。(点击查看分频部分代码原理)
然后就可以开心的debug了(bushi

二段式和一段式

三段式是将三个always块分开写,那么我们也可以考虑只写两个,甚至于一个always块。

一段式的问题还是比较多的,而且只写一个块并不利于维护,因此在这里不做过多说明。
二段式:两个always块
虽然我上面的代码中输入方程是和时钟周期有关,但是要知道实际上确定输入时钟信号无关(因为要处理每一个节拍读入一位,所以才选择了这样的方式。)
那么就很明显,状态转换模块一定是要时序电路了,那么我完全可以将剩下的两个部分(都是组合逻辑)放在一起,这样也有利于代码的阅读和维护,但是是可能产生毛刺和险象的。

至于三段式,也不是那么完美,三个块占用的资源也更多,不过胜在稳定,但是我们说过vivado更偏爱时序电路,所以有那么一个组合的always块有时也会产生warning,甚至不能运行。

你可能感兴趣的:(Verilog,moore和mealy,三段式)