可乐售货机–状态机
状态机引入的原因:FPGA 是并行执行的,如果我们想要处理具有前后顺序的事件,这时就需要引入状态机。
- 背景:可乐定价为 2.5 元一瓶,可投入 0.5 元、1 元硬币,投币不够 2.5 元需要按复位键退回钱款,投币超过 2.5 元需找零。
- 状态转移图的三要素–输入、输出、状态
FPGA为什么大多使用独热码?
独热码,每个编码只有 1 比特为 1,其余比特都为 0。对于几个状态则需要几个比特位。
为什么例子中我们使用的是独热码而非二进制码或格雷码呢?那就要从每种编码的特性上说起了,首先独热码因为每个状态只有 1bit 是不同的,因为只有 1 比特为 1,所以综合器会进行智能优化,这就相当于把之前 a比特的比较器变为了 1 比特的比较器,大大节省了组合逻辑资源,但是付出的代价就是状态变量的位宽需要的比较多,而我们 FPGA 中组合逻辑资源相对较少,所以比较宝贵,而寄存器资源较多,所以很完美。
而二进制编码恰恰相反,虽然使用了较少的寄存器,但比较器无法优化,使用的组合逻辑资源较多,而FPGA器件组合逻辑资源多,寄存器资源较少,所以用独热码更合适。
另外,用独热码编码的状态机可以在高速系统上运行,其原因是多比特的比较器每个比特到达比较器的时间可能会因为布局布线的走线长短而导致路径延时的不同,这样在高速系统下,就会导致采集到不稳定的状态,导致比较后的结果产生一个时钟的毛刺,使输出不稳定,而单比特的比较器就不用考虑这种问题。
用独热码编码虽然好处多多,但是如果状态数非常多的话即使是 FPGA 也吃不消独热码对寄存器的消耗,所以当状态数特别多的时候可以使用格雷码对状态进行编码。格雷码虽然也是和二进制编码一样使用的寄存器资源少,组合逻辑资源多,但是其相邻状态转换时只有一个状态发生翻转,这样不仅能消除状态转换时由多条信号线的传输延迟所造成的毛刺,又可以降低功耗,所以要优于二进制码的方式,相当于是独热码和二进制编码的折中。
- 状态转移图(Melay型)
输入有多少种情况,每个状态的跳转就有多少种情况,这样根据输入来确定状态的跳转就能够保证我们不漏掉任何一种状态跳转。
有Moore 型状态机和 Mealy 型状态机,其共同点是:状态的跳转都只和输入有关。区别主要
是在最后输出的时候:若最后的输出只和当前状态有关而与输入无关则称为 Moore 型状态机;
若最后的输出不仅和当前状态有关还和输入有关则称为 Mealy 型状态机。
- 代码
module complex_fsm
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire one ,
input wire half ,
output reg out_money ,
output reg cola_out
);
parameter IDLE = 5'b00000;
parameter HALF = 5'b00010;
parameter ONE = 5'b00100;
parameter ONE_HALF = 5'b01000;
parameter TWO = 5'b10000;
reg [4:0] state;
wire [1:0] in_money;
assign in_money = {one, half};
always@(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 0 )
state <= IDLE;
else case(state)
IDLE: if (in_money == 2'b01)
state <= HALF;
else if (in_money == 2'b10)
state <= ONE;
else
state <= IDLE;
HALF: if (in_money == 2'b01)
state <= ONE;
else if (in_money == 2'b10)
state <= ONE_HALF;
else
state <= HALF;
ONE: if (in_money == 2'b01)
state <= ONE_HALF;
else if (in_money == 2'b10)
state <= TWO;
else
state <= ONE;
ONE_HALF: if (in_money == 2'b01)
state <= TWO;
else if (in_money == 2'b10)
state <= IDLE;
else
state <= ONE_HALF;
TWO : if (in_money == 2'b01 || in_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 == 0 )
cola_out <= 1'b0;
else if ((state == TWO && (in_money == 2'b01 || in_money == 2'b10) )
|| (state == ONE_HALF && in_money == 2'b10))
cola_out <= 1'b1;
else
cola_out <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 0 )
out_money <= 1'b0;
else if (state == TWO && in_money == 2'b10 )
out_money <= 1'b1;
else
out_money <= 1'b0;
endmodule
- 仿真代码与结果
`timescale 1ns / 1ns
// Create Date: 2022/07/16 17:36:16
module tb_complex_fsm();
reg sys_clk ;
reg sys_rst_n ;
reg one ;
reg half ;
wire out_money ;
wire cola_out ;
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)
{one, half} <= 2'b0;
else
{one, half} <= {$random} % 3; //取模求余数,产生非负随机数 0、1、2
//------------------------complex_fsm_inst------------------------
complex_fsm complex_fsm_inst(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.one (one ),
.half (half),
.out_money (out_money),
.cola_out (cola_out ) //output po_cola
);
endmodule
结果: