二、16【FPGA】时序逻辑电路设计——有限状态机(FS)

前言

学习说明此文档为本人的学习笔记,注重实践,关于理论部分会给出相应的学习链接。

学习视频:是根据野火FPGA视频教程——第十九讲
https://www.bilibili.com/video/BV1nQ4y1Z7zN?p=3

理论学习

关于理论学习请参考《数字电子技术基础》相关笔记:

《数字电子技术基础》6.4 时序逻辑电路——设计方法(FSM)有限状态机_追逐者-桥的博客-CSDN博客时序逻辑电路设计——有限状态机(FSM)https://blog.csdn.net/ARM_qiao/article/details/124461906状态机(FSM),也称为同步有限状态机,Mealy型、Moore型状态机(输出与输入无关)

实战演练一

一、设计规划

1.1 实验目标

可乐机每次只能投入1枚1元硬币,且每瓶可乐卖3元钱,投入3个硬币就可以让可乐机出一瓶可乐,如果投币不够3元放弃投币需要按复位键,否则之前的投币不能退回。

二、程序设计

2.1 波形图绘制 

X表示输入:1投入1元硬币,0不投入硬币

Y表示输出:1出可乐,0不出可乐

S0、S1、S2状态:投入0元(初始状态)、投入1元、投入2元、投入3元。

2.2 代码编写 

状态编码定义方式:

在低速系统中

  • 状态机个数 <4个,二进制码
  • 状态机个数 4—24个,独热码
  • 状态机个数 >4个,格雷码

在高速系统中,无论个数均推荐使用独热码。

状态机代码写法有一段式、二段式、三段式:
一段式指的是在一段状态机中使用时序逻辑既描述状态的转移,也描述数据的输出;
二段式指在第一段状态机中使用时序逻辑描述状态转移,在第二段状态机中使用组合逻辑描述数据的输出;
三段式指在第一段状态机中采用时序逻辑描述状态转移,在第二段在状态机中采用组合逻辑判断状态转移条件描述状态转移规律,在第三段状态机中描述状态输出,可以用组合电路输出,也可以时序电路输出。

老的一段式、二段式、三段式各有优缺点,其中一段式在描述大型状态机时会比较困难,会使整个系统显得十分臃肿,不够清晰;二段式状态机的好处是其结构和理想的理论模型完全吻合,即不会有附加的结构存在,比较精简,但是由于二段状态机的第二段是组合逻辑描述数据的输出,所以有一些情况是无法描述的,比如输出时需要类似计数的累加情况,这种情况在组合逻辑中会产生自迭代,自迭代在组合逻辑电路中是严格禁止的,而且第二段状态机主要是描述数据的输出,输出时使用组合逻辑往往会产生更多的毛刺,所以并不推荐。所以衍生出三段式状态机,三段状态机的输出就可是时序逻辑了,但是其结构并不是最精简的了。三段式状态机的第一段状态机是用时序逻辑描述当前状态,第二段 状态机是用组合逻辑描述下一状态,如果把这两个部分进行合并而第三段状态机保持不变,就是我们现在最新的二段式状态机了。这种新的写法在现在不同综合器中都可以被识别出来,这样既消除了组合逻辑可能产生的毛刺,又减小了代码量,还更加容易上手,不必再去关心理论模型是怎样的,仅仅根据状态转移图就非常容易实现。
所以我们习惯性的使用两个均采用时序逻辑的 always 块,第一个 always 块描述状态的转移为第一段状态机,第二个 always 块描述数据的输出为第二段状态机(如果我们遵循一个 always 块只描述一个变量的原则,如果有多个输出时第二段状态机就可以分为多个always 块来表达,但理论上仍属于新二段状态机,所以几段式状态机并不是由 always 块的数量简单决定的)。
module simple_fsm(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire pi_money,
    output reg po_cola
    );
//状态定义,独热码,其他二进制码、格雷码编码(只有一位发生变化,减少毛刺)
    parameter IDLE = 3'b001,
              ONE  = 3'b010,
              TWO  = 3'b100;   
    reg [2:0] 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
    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))
            po_cola <= 1'b1;
        else 
            po_cola <= 1'b0;    
endmodule

三、逻辑仿真

3.1 仿真代码

`timescale 1ns / 1ns
//
// Company: 追逐者——桥的小作坊
// Create Date: 2022/05/16 15:42:33
// Design Name: 有限状态机
// Module Name: tb_simple_fsm
///
module tb_simple_fsm();
    reg sys_clk;
    reg sys_rst_n;
    reg pi_money;
    wire po_cola;
    initial begin
        sys_clk = 1'b1;
        sys_rst_n <= 1'b0;
        #20 
        sys_rst_n <= 1'b1;
    end 
    always #10 sys_clk = ~sys_clk;
    always @(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            pi_money <= 1'b0;
        else 
            pi_money <= {$random} % 2;
    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 simple_fsm_inst(
    .sys_clk  (sys_clk  ),
    .sys_rst_n(sys_rst_n),
    .pi_money (pi_money ),
    .po_cola  (po_cola  )
    );
endmodule

3.2 波形图对比


实战演练二

一、设计规划

1.1 实验目标

可乐机定价为2.5元一瓶,可投入0.5元、1元硬币,投币不够2.5元需要按复位键退回钱款,投币超过2.5元找零。

二、程序设计

2.1 波形图绘制

输入:不投硬币,投入0.5硬币,投入1元硬币

输出:不出可乐/不找零,出可乐/不找零,出可乐/找零

状态:投入0元(初始状态)、投入0.5元、投入1元、投入1.5元、投入2元、投入2.5元、投入3元。

抽象编码:

输入:00,01,10

输出:00,10,11

2.2 代码编写

module complex_fsm(
    input wire sys_clk  ,
    input wire sys_rst_n,
    input wire pi_money_half,
    input wire pi_money_one,
    output reg po_cola,
    output reg po_money
    );
    parameter IDLE  = 5'b00001,
              HALF  = 5'b00010,
              ONE   = 5'b00100,
              ONE_H = 5'b01000,
              TWO   = 5'b10000;
    wire [1:0] pi_money;           //用assign连续赋值语句
    reg  [4:0] state;
    assign pi_money = {pi_money_one, pi_money_half};
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            state <= IDLE;
        else case(state)
                IDLE :  if(pi_money == 2'b01)
                            state <= HALF;
                        else if(pi_money == 2'b10)
                            state <= ONE;
                        else
                            state <= IDLE;
                HALF :  if(pi_money == 2'b01)
                            state <= ONE;
                        else if(pi_money == 2'b10)
                            state <= ONE_H;
                        else
                            state <= HALF; 
                ONE  :  if(pi_money == 2'b01)
                            state <= ONE_H;
                        else if(pi_money == 2'b10)
                            state <= TWO;
                        else
                            state <= ONE; 
                ONE_H:  if(pi_money == 2'b01)
                            state <= TWO;
                        else if(pi_money == 2'b10)
                            state <= IDLE;
                        else
                            state <= ONE_H;
                TWO  :  if((pi_money == 2'b01) || (pi_money == 2'b10))
                            state <= IDLE;
                        else
                            state <= TWO;
                default:  state <= IDLE;
            endcase  
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            po_cola <= 1'b0;  
        else if((stat == ONE_H && pi_money == 2'b10)
              ||(stat == TWO   && pi_money == 2'b01)
              ||(stat == TWO   && pi_money == 2'b10))
            po_cola <= 1'b1;
        else 
            po_cola <= 1'b0;
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            po_money <= 1'b0;
        else if((stat == TWO) && (pi_money == 2'b10))
            po_money <= 1'b1;
        else 
            po_money <= 1'b0;            
endmodule

三、逻辑仿真

3.1 仿真代码

`timescale 1ns / 1ns
//
// Company: 追逐者——桥的小作坊
// Create Date: 2022/05/16 18:39:18
// Design Name: 复杂可乐鸡
// Module Name: tb_comple_fsm
///
module tb_comple_fsm();
    reg sys_clk;
    reg sys_rst_n;
    reg pi_money_half;
    reg pi_money_one;
    wire po_cola;
    wire po_money;

    reg random_data_gen;
    initial begin
        sys_clk = 1'b1;
        sys_rst_n <= 1'b0;
        #20 
        sys_rst_n <= 1'b1;
    end
    always #10 sys_clk = ~sys_clk;
//由于不能同时投0.5和1元,因此随机数需要统一
    always @(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            random_data_gen <= 1'b0;
        else 
            random_data_gen <= {$random} % 2;
//模拟投币
    always @(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            pi_money_half <= 1'b0;
        else 
            pi_money_half <= random_data_gen;
    always @(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            pi_money_one <= 1'b0;
        else if((({$random}%2)==1'b1) && (pi_money_half==1'b0))
            pi_money_one <= 1'b1;    
        else 
            pi_money_one <= ~random_data_gen; 
            
    wire [4:0] state = complex_fsm_inst.state;
    wire [1:0] pi_money = complex_fsm_inst.pi_money;
    initial begin 
        $timeformat(-9, 0, "ns", 6);
        $monitor("@time %t:pi_money_half=%b, pi_money_one =%b, state=%b, pi_money=%b, po_cola=%b, po_money=%b",$time, 
                    pi_money_half, pi_money_one, state, pi_money, po_cola, po_money);
    end
                
complex_fsm  complex_fsm_inst(
    .sys_clk      (sys_clk      ),
    .sys_rst_n    (sys_rst_n    ),
    .pi_money_half(pi_money_half),
    .pi_money_one (pi_money_one ),
    .po_cola      (po_cola      ),
    .po_money     (po_money     )
    );            
        
endmodule

3.2 波形图对比

 五、总结

一般掌握
1 、学会将 RTL 模块中的内部信号引入到 Testbench 模块中的方法
知识点总结
1 、知道状态机是什么,以及适合它的应用场景;
2 、了解 Moore 型状态机和 Mealy 型状态机的主要区别,以及状态转移图中的差别;
3 、学会根据实际问题抽象出我们设计状态机所必要的输入、输出和状态;
4 、能够根据分析绘制状态转移图,不要遗漏状态,并会化简、合并状态;
5 、能够根据状态转移图画出编写 RTL 代码,编写代码时也要注意一些细节问题,如:如
何进行状态的编码、每种状态编码的特点、我们编写代码的格式、每一段都是做的什么工
作;
6 、学会根据综合器综合的状态转移图和使用 ModelSim 仿真来验证我们状态机的设计;
7 、记住整个状态机的设计流程、方法、技巧,并能够在以后的设计中熟练运用。
  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追逐者-桥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值