数电基础
状态机的基础知识依然强烈推荐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的状态出可乐并找零。
-
FPGA
+关注
关注
1629文章
21736浏览量
603248 -
状态机
+关注
关注
2文章
492浏览量
27538 -
状态图
+关注
关注
0文章
11浏览量
7318
发布评论请先 登录
相关推荐
评论