数字IC-1.7 状态机学习

一、定义(大型工程的基石,每个状态代表着一个事件被触发)

共同的:两种状态机,状态跳转只与输入状态有关。 

不同的:在输出时,若最后的输出只和当前状态有关而与输入无关则是moore(木耳)状态机。若最后的输出不仅当前状态有关还与输入也有关则是mealy(米丽)状态机。 

状态转移:从上一个事件发生跳转下一个事件发生。

作用领域:状态机适合描述那些顺序发生,有时序规律的事件。

 

二、状态转移图(表明状态跳转的条件和先后过程,帮助编写代码逻辑)

                                                                  图例

简单例子:可乐机里的饮料3元一瓶。每次只能投一枚一元的硬币,投三个硬币吐出一瓶可乐。用状态机硬件语言实现其功能。

模块引脚设计图

状态转移图绘制:  ( 1/0符号说明:1状态输入 /  0 状态输出 ) 

状态转移三要素

                                输入: 投入1元硬币

                                输出:吐出可乐、不吐出可乐

                                状态:投入0元时、投入1元时、投入2元时、投入3元时

状态转移三要素

                    输入: 投入1元硬币
                    输出:吐出可乐、不吐出可乐
                    状态:投入0元时、投入1元时、投入2元时、投入3元时

状态分析:
                           * 投入0元时:1、没有投掷,即0/0自身状态不变。2、被投掷了(输入)一枚硬币。即1/0转到下一个1元的状态。
                          * 投入1元时:1、没有投掷,即0/0自身状态不变。2、被投掷了(输入)一枚硬币。即1/0转到下一个2元的状态。
                          * 投入2元时:1、没有投掷,即0/0自身状态不变。2、被投掷了(输入)一枚硬币。对于mealy型状态机(只当前状态、输入都有关),即此时已经3元硬币了,需要转到下一个0元状态,并且输出一瓶可投入3元时乐,即1/1。 对于moore型状态机(只和当前状态有关,和输入无关),则转向下一个3元状态,并且不吐出可乐,即1/0.
                          * 投入3元时(仅moore型状态机):1、此时即使不投硬币也会出可乐,也要转移到下一个状态,因此本状态不存在独立的自环状态。2、需要转移到0元的状态。由于没有投币输入行为,有出可乐输出行为,因此为0/1。3、若在出可乐过程中,又被投入了一枚硬币,此时将要在输出可乐的同时,转移到1元状态,即1/1.

状态机选择:对于本题,由于mealy型状态机(左)比moore型状态机(右)需要的状态更少,代码编写更加方便,因此我们选择mealy型状态机

三、简单状态机VHDL代码

        代码时序逻辑波形图绘制(设高电平持续一个时钟周期表示投入了一枚硬币)

                                          

代码编写

状态机编码方法分为: 独热码(IDLE = 3'b001  ONE  = 3'b010  TWO  = 3'b100)、二进制编码(IDLE = 2'b00  ONE  = 2'b10  TWO  = 2'b11)、格雷码(IDLE = 3'b000  ONE  = 3'b001  TWO  = 3'b011)。       其中格雷码,是一种错误最小化的 编码方式,因为每个数字之间只有一个二进制位发生改变,且最大值和最小值是头尾相连的。在数字系统中,常要求代码按一定顺序变化。例如,按自然数递增计数,若采用8421码,则数0111变到1000时四位均要变化,而在实际电路中,4位的变化不可能绝对同时发生。

                                        

                                                                 格雷码示意

注意:本例使用独热码对状态进行编号。因为二进制编码的存储位数虽然最小,但是其占用的组合逻辑资源更多。由于独热码每个状态都单独占一位二进制,因此综合器会优化器组合逻辑资源至一位。而在芯片设计中组合逻辑资源往往比寄存器资源要小的多,因此我们使用独热码形式。

使用情况
            设状态个数为D。

            当 D < 4, 二进制编码进行状态编码时会使用到联合比较器,可能存在时延稳定问题。
            当 D >= 24,状态较多时使用格雷码进行状态编码。
            当4 <= D < 24,状态较少时使用独热码进行状态编码。
            如果在高速器件中,无论状态数是多少,一般都使用独热码编码。

状态机代码编写形式

             *一段式: 在一段代码中使用时序逻辑描述全部的输入条件状态同步转移状态输出。可是这种形式不能描述较为复杂或者大型的状态机。(缺点太多,避免使用

缺点:在描述当前状态时要考虑下个状态的输出,整个代码不清晰不利于维护修改不利于附加约束不利于综合器和布局布线器对设计的优化。此外,在较大的工程中,一段式状态机的代码冗余比二段式状态机多50%~120%左右。

                                                                

            *二段式:分两段描写状态机。

                第一段 -》使用时序逻辑描述不同输入条件对应的状态同步转移

                第二段 -》使用组合逻辑描述状态的输出。

缺点:面临状态输出时有计数的情况无法适用,因为组合逻辑不允许自加行为。另外,在状态输出时,若使用组合逻辑,会使得信号产生许多毛刺。(在高速电路中推荐使用。如果允许延时一个时钟周期,那么在组合逻辑输出时添加一级时序逻辑寄存器,则可以减小毛刺发生的概率)

             *三段式:分三段描写状态机。(用于解决再不允许多一个时钟延时的情况下,相对于二段式状态机输出出现毛刺的情况,使用时序逻辑输出实现信号稳定)

                第一段 -》 使用时序逻辑描述状态的转移

                第二段 -》 使用组合逻辑判断状态转移条件、描述状态转移规律

                第三段 -》 使用组合逻辑或者时序逻辑描述状态的输出。

缺点:速度相对二段较慢,代码结构复杂。

优点:使FSM状态机做到了同步寄存器输出。消除了组合逻辑输出的不稳定与毛刺的隐患。更利于时序路径分组。在 FPGA/CPLD等可编程逻辑器件上的综合与布局布线效果更佳。

 

 

        上述三种方法是老的方法,编写时必须按照严格的格式进行编写,综合器才能识别其为状态机并生成电路。下面我们对上述三种结构进行总结。

         

         *新二段式:分两段描写状态机。第一段使用时序逻辑描述状态的转移和 状态转移条件,第二段同样使用时序逻辑描述状态的输出。这种形式可以直接根据转台转移图写出,并且几乎所有的综合器都能识别它是状态机,因此本例使用这种结构编写。

寄存器传输级文件

`timescale  1ns/1ns

// Project Name  : simple_fsm
// Target Devices: Altera EP4CE10F17C8
// Tool Versions : Quartus 13.0
// Description   : 简单状态机


module  simple_fsm
(
    input   wire    sys_clk     ,   //系统时钟50MHz
    input   wire    sys_rst_n   ,   //全局复位
    input   wire    pi_money    ,   //投币方式可以为:不投币(0)、投1元(1)

    output  reg     po_cola         //po_cola为1时出可乐,po_cola为0时不出可乐
);

/*态机编码方法分为: 
        独热码(IDLE = 3'b001  ONE  = 3'b010  TWO  = 3'b100)  
        二进制编码(IDLE = 2'b00  ONE  = 2'b10  TWO  = 2'b11)
        格雷码(IDLE = 3'b000  ONE  = 3'b001  TWO  = 3'b011)
        
    其中格雷码,是一种错误最小化的 编码方式,因为每个数字之间只有一个二进制位发生改变,
    且最大值和最小值是头尾相连的。在数字系统中,常要求代码按一定顺序变化。
    例如,按自然数递增计数,若采用8421码,则数0111变到1000时四位均要变化,而在实际电路中,
    4位的变化不可能绝对同时发生。
*/

/*
    使用情况:
            设状态个数为D。当 D < 4, 二进制编码进行状态编码时会使用到联合比较器,
            可能存在时延稳定问题。
            当 D >= 24,状态较多时使用格雷码进行状态编码。
            当4 <= D < 24,状态较少时使用独热码进行状态编码。
            如果在高速器件中,无论状态数是多少,一般都使用独热码编码。
             
    注意:
        本例使用独热码对状态进行编号。因为二进制编码的存储位数虽然最小,
    但是其占用的组合逻辑资源更多。由于独热码每个状态都单独占一位二进制,
    因此综合器会优化器组合逻辑资源(比较器)至一位。而在芯片设计中组合逻辑资源往往比
    寄存器资源要小的多,因此我们使用独热码形式。
*/

//parameter define
parameter   IDLE = 3'b001;
parameter   ONE  = 3'b010;
parameter   TWO  = 3'b100;

//reg   define
reg     [2:0]   state; // 存储状态转移的当前状态

//第一段状态机,描述当前状态state如何根据输入跳转到下一状态
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state <= IDLE;  //任何情况下只要按复位就回到初始状态
    else    case(state)
                IDLE    :   if(pi_money == 1'b1)//判断输入情况
                                state <= ONE;
                            else
                                state <= IDLE;

                ONE     :   if(pi_money == 1'b1)
                                state <= TWO;
                            else
                                state <= ONE;

                TWO     :   if(pi_money == 1'b1)
                                state <= IDLE;
                            else
                                state <= TWO;
                //如果状态机跳转到编码的状态之外也回到初始状态
                default :       state <= IDLE;
            endcase

//第二段状态机,描述当前状态state和输入pi_money如何影响po_cola输出
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_cola <= 1'b0;
    else    if((state == TWO) && (pi_money == 1'b1)) // 因为状态转移图里2元到0元的链接是1/1
        po_cola <= 1'b1;
    else
        po_cola <= 1'b0;

endmodule

仿真测试文件

`timescale  1ns/1ns

// Module Name   : tb_simple_fsm
// Project Name  : simple_fsm
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : 简单状态机仿真文件


module  tb_simple_fsm();

//reg define
reg     sys_clk;
reg     sys_rst_n;
reg     pi_money;

//wire  define
wire    po_cola;

//初始化系统时钟、全局复位
initial begin
    sys_clk    = 1'b1;
    sys_rst_n <= 1'b0;
    #20
    sys_rst_n <= 1'b1;
end

//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
always  #10 sys_clk = ~sys_clk;

//pi_money:产生输入随机数,模拟投币1元的情况
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        pi_money <= 1'b0;
    else
        pi_money <= {$random} % 2;  //取模求余数,产生非负随机数0、1

//------------------------------------------------------------
//将RTL模块中的内部信号引入到Testbench模块中进行观察
wire [2:0] state = simple_fsm_inst.state;

initial begin
    $timeformat(-9, 0, "ns", 6);
    $monitor("@time %t: pi_money=%b state=%b po_cola=%b",$time, pi_money, state, po_cola);
end

//------------------------simple_fsm_inst------------------------
simple_fsm  simple_fsm_inst(
    .sys_clk    (sys_clk    ),  //input     sys_clk
    .sys_rst_n  (sys_rst_n  ),  //input     sys_rst_n
    .pi_money   (pi_money   ),  //input     pi_money

    .po_cola    (po_cola    )   //output    po_cola
);

endmodule

modelsim仿真:

 

       

四、进阶状态机VHDL代码

进阶例子:可乐机里的饮料2.5元一瓶。每次能投一枚1元的硬币或者一枚0.5元的硬币,可乐机里的前大于或者等于2.5元时吐出一瓶可乐,若可乐机里为3元则需要吐出一瓶可乐并且找补零钱0.5元。请用状态机硬件语言实现其功能。

模块引脚图:

时序逻辑分析图:

拽他转移示意图:

 

 * 其中 00/00 是指   "(00无投币、01投币0.5元、10投币1元) /   (00不出可乐不找零钱、 10只出可乐不找零钱、11又出可乐又找0.5元零钱) "

寄存器传输级文件

`timescale 1ns/1ns
// 复杂例子:可乐机里的饮料2.5元一瓶。每次能投一枚1元的硬币或者一枚0.5元的硬币,
// 可乐机里的前大于或者等于2.5元时吐出一瓶可乐。用状态机硬件语言实现其功能。
module complex_fsm(
    input wire sys_clk,
    input wire res_n,
    input wire money_in_one,
    input wire money_in_half,
    
    output reg cola_out,
    output reg half_out
);

 // 状态机编码:独热码    
    localparam IDLE       = 5'b00001; // 0
    localparam HALF       = 5'b00010; // 0.5
    localparam ONE        = 5'b00100; // 1
    localparam ONE_HALF   = 5'b01000; // 1.5
    localparam TWO        = 5'b10000; // 2

 // 状态追踪变量
    reg [4:0] state;
 
 // 新二段式状态机
 
 // 第一段:状态机转移以及触发条件
    always@(posedge sys_clk or negedge res_n) begin
        if(res_n == 1'b0)
            state <= IDLE;
        else begin
            case(state)
                IDLE : if(money_in_one == 1'b1) state <= ONE;  
                        else if(money_in_half == 1'b1) state <= HALF; 
                        else state <= IDLE;
                HALF  : if(money_in_one == 1'b1) state <= ONE_HALF;  
                        else if(money_in_half == 1'b1) state <= ONE; 
                        else state <= HALF;
                ONE  : if(money_in_one == 1'b1) state <= TWO;  
                        else if(money_in_half == 1'b1) state <= ONE_HALF; 
                        else state <= ONE;
                ONE_HALF : if(money_in_one == 1'b1) state <= IDLE;  
                        else if(money_in_half == 1'b1) state <= TWO; 
                        else state <= ONE_HALF;
                TWO   : if(money_in_one == 1'b1) state <= IDLE;  
                        else if(money_in_half == 1'b1) state <= IDLE; 
                        else state <= TWO;
                default: state <= IDLE;
            endcase
        end
    end
 
 // 第二段:状态输出行为判断、输出
    always@(posedge sys_clk or negedge res_n) begin
        if(res_n == 1'b0)
            cola_out <= 1'b0;
        else begin
            if((state == TWO && money_in_half == 1'b1) || 
               (state == TWO && money_in_one  == 1'b1) ||
               (state == ONE_HALF && money_in_one == 1'b1)) 
                cola_out <= 1'b1;
            else
                cola_out <= 1'b0;
        end
    end
    
    always@(posedge sys_clk or negedge res_n) begin
        if(res_n == 1'b0)
            half_out <= 1'b0;
        else begin
            if(state == TWO && money_in_one  == 1'b1)
                half_out <= 1'b1;
            else
                half_out <= 1'b0;
        end
    end
endmodule

 仿真测试文件

`timescale  1ns/1ns

// Module Name   : tb_complex_fsm
// Project Name  : complex_fsm
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : 复杂状态机仿真文件


module  tb_complex_fsm();

//reg define
reg     sys_clk;
reg     res_n;
reg     money_in_one;
reg     money_in_half;
reg     random_data_gen;

//wire  define
wire    cola_out;
wire    half_out;

//初始化系统时钟、全局复位
initial begin
    sys_clk    = 1'b1;
    res_n <= 1'b0;
    #20
    res_n <= 1'b1;
end

//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
always  #10 sys_clk = ~sys_clk;

// 注意:由于money_in_one与money_in_half是互斥的,不能同时为1,因此我们需要另一个信号来同时约束它们输出
//random_data_gen:产生非负随机数0、1
always@(posedge sys_clk or negedge res_n)
    if(res_n == 1'b0)
        random_data_gen <= 1'b0;
    else
        random_data_gen <= {$random} % 2;
        
//money_in_one:产生输入随机数,模拟投币1元的情况
always@(posedge sys_clk or negedge res_n)
    if(res_n == 1'b0 || money_in_half == 1'b1)
        money_in_one <= 1'b0;
    else
        money_in_one <= random_data_gen;  //取模求余数,产生非负随机数0、1
        
//money_in_half:产生输入随机数,模拟投币0.5元的情况
always@(posedge sys_clk or negedge res_n)
    if(res_n == 1'b0)
        money_in_half <= 1'b0;
    else
        money_in_half <= ~random_data_gen;
        
//------------------------------------------------------------
//*******将RTL模块中的内部信号引入到Testbench模块中进行观察*******模块内部过程变量监视方法
wire [4:0] state = tb_complex_fsm_inst.state; // 注意这里的父级引用的是测试生成的新对象文件

initial begin
    $timeformat(-9, 0, "ns", 6);
    $monitor("@time %t: money_one=%b money_half=%b state=%b cola_out=%b half_out=%b",
                $time, money_in_one, money_in_half, state, cola_out, half_out);
end

//------------------------simple_fsm_inst------------------------
complex_fsm  tb_complex_fsm_inst(
    .sys_clk         (sys_clk        ),  //input     sys_clk
    .res_n           (res_n          ),  //input     sys_rst_n
    .money_in_one    (money_in_one   ),  //input     money_in_one
    .money_in_half   (money_in_half  ),  //input     money_in_half

    .cola_out        (cola_out       ),  //output    cola_out
    .half_out        (half_out       )   //output    half_out 
);

endmodule

本教程感谢野火官方教程的支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值