基于FPGA的状态机设计

描述

数电基础

状态机的基础知识依然强烈推荐mooc上华科的数字电路与逻辑设计,yyds!但是数电基础一定要和实际应用结合起来,理论才能发挥真正的价值。我们知道FPGA是并行执行的,如果我们想要处理具有前后顺序的事件就需要引入状态机。

状态机有同步异步之分,同步是指状态机的状态跳转是在时钟的作用下进行的,异步是指状态跳转不是由统一的时钟控制。同步有限状态机分为Moore型Mealy型 ,Moore型的输出只与当前状态有关,而Mealy型的输出与当前状态和输入有关。

每一个状态都代表一个事件,从初始状态出发,不同的输入可能引发不同的下一个状态,并获得不同的输出(输出不是必须的,但一定有输入)。

设计规划

我们的目标是用状态机实现一个简单的可乐贩卖机系统。具体功能是:可乐机每次只能投入1枚1元硬币,且每瓶可乐卖3元钱,即投入3个硬币就可以让可乐机出可乐,如果投币不够3元想放弃投币需要按复位键,否则之前投入的钱不能退回。

Moore型用状态图来表示:

状态图

初始状态是IDLE,如果输入0枚跳转到自身状态,输入1枚跳转到ONE状态,跳转到TWO状态也是同理,再输入0枚跳转到自身状态,输入1枚跳转到初始状态并输出1表示可乐售卖成功,其间任意状态复位有效都要回到初始状态并退钱。

Mealy型用状态图来表示:

状态图

有四种状态,到TWO状态都与前面一致,TWO状态时投1枚跳转到THREE状态,THREE状态如果输入0枚就售出可乐且跳转到初始状态,输入1枚就售出可乐且跳转到ONE状态。

状态图

编写代码

module simple_fsm
(
input wire sys_clk , 
input wire sys_rst_n , 
input wire pi_money , 
output reg po_cola 
);


 //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))
 po_cola <= 1'b1;
 else
 po_cola <= 1'b0;


 endmodule

输入输出定义

参数定义:状态要用参数来表示,为了区分不同的状态,我们需要给 状态编码 ,这里使用了独热码,只有一位为1其余位为0。事实上这里使用二进制或格雷码也可以表示。二进制编码使用2位位宽就可以表示4种状态(有一种状态未使用)。使用独热码的原因是:独热码每个状态只有1bit是不同的,所以在执行(state == TWO)这条语句时,综合器会识别出这是一个比较器,而因为只有1比特为1,所以综合器会进行智能优化为(state[2] == 1’ b1),这就相当于把之前3比特的比较器变为了1比特的比较器,大大节省了组合逻辑资源。而我们FPGA中组合逻辑资源相对较少,而寄存器资源较多,所以牺牲寄存器资源来节省组合逻辑资源。状态很多时可以采用格雷码进行编码,位数少,且相邻状态转换时只有一位发生变化,相当于二进制和独热码的折衷处理。

采用新两段式,第一段用于定义状态跳转,第二段定义输出。这种新的写法现在在不同综合器中都可以被识别出来,既消除了组合逻辑可能产生的毛刺,又减小了代码量,仅仅根据状态转移图就能实现。如果有多个输出时第二段状态机就可以分为多个always块来表达,但理论上仍属于新二段状态机,所以几段式状态机并不是由always块的数量简单决定的)。

定义状态跳转 :状态变化的条件是时钟上升沿和复位。首先复位时,状态恢复到初始状态。没有复位时,需要定义每个状态的跳转。这里采用了case语句,复习一下:case语句检查表达式与列表中其他表达式是否匹配并对应分支。这里是检查state与IDLE,ONE,TWO匹配,当处于三种状态时,都有pi_money=0或1两种情况,按照之前讨论的跳转状态去设置。注意case语句如果不加default可能出现latch。

定义输出 :复位有效时,输出为0;只有一种情况输出为1,就是有足够买到可乐的钱时,也就是状态为TWO且投入1块钱;其他时候输出为0。

状态图

状态图

编写testbench

`timescale 1ns/1ns
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 ), 
 .sys_rst_n (sys_rst_n ), 
 .pi_money (pi_money ),
 .po_cola (po_cola ) 
 );


 endmodule

输入输出定义、初始化、时钟产生、随机数产生、打印结果、实例化都是我们非常熟悉的内容了。需要补充说明的是第29行,重新定义了一个state(名称尽量与rtl中一致),将实例化模块中的state与其等效,这样就可以在transcript中打印并观察到。因为transcript中观察到打印信息只能是RTL的端口信号,而state是内部信号(端口信号是输入输出时钟复位,中间信号是内部信号)。

对比波形

状态图

状态图

状态跳转与预期一致

应用拓展

前面的可乐贩卖机只能投1元的,我们来看看投0.5元的状态机:可乐定价为2.5元一瓶,可投入0.5元、1元硬币,投币不够2.5元需要按复位键退回钱款,投币超过2.5元需找零。

看似很复杂,实际只是变成两种输入,三种输出,五种状态。输入有0.5,1元;输出有不找零不出可乐,不找零出可乐,找零并出可乐;状态有0,0.5,1,1.5,2,到2块之后输入0.5就到0的状态并出可乐,输入1就到0的状态出可乐并找零。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分