19-状态机 - 2
1.状态机状态编码
- 使用独热码在进行综合的时候,可以讲多位比较器简化为多位比较器,但是寄存器位宽宽
- 使用独热码可以减少组合逻辑占用资源,但是耗费寄存器资源
独热码编码虽然好处多多,但是如果状态数非常多的话即使是 FPGA 也吃不消独热码对寄存器的消耗,所以当状态数特别多的时候可以使用格雷码对状态进行编码。格雷码虽然也是和二进制编码一样使用的寄存器资源少,组合逻辑资源多,但是其相邻状态转换
时只有一个状态发生翻转,这样不仅能消除状态转换时由多条信号线的传输延迟所造成的毛刺,又可以降低功耗,所以要优于二进制码的方式,相当于是独热码和二进制编码的折中。
2. 可乐机设计
可乐定价为 2.5 元一瓶,可投入 0.5 元、1 元硬币,投币不够 2.5 元需要按复位键退回钱款,投币超过 2.5 元需找零。
2.1 模块框图
2.2 状态转移图
1、输入:投入 0.5 元硬币、投入 1 元硬币;
2、输出:不出可乐/不找零、出可乐/不找零、出可乐/找零;
3、状态:可乐机中有 0 元、可乐机中有 0.5 元、可乐机中有 1 元、可乐机中有 1.5 元、可乐机中有 2 元、可乐机中有 2.5 元、可乐机中有 3 元。
- 输入和输出都不再是一个信号,而是两个信号,而在表达状态转移图中状态跳转的条件时依然只能是斜杠左边为输入,斜杠右边为输出,这也就意味着我们要将输入的多个信号编为一组,输出的多个信号编为一组,然后再进行量化编码,编码方式自定义,只要不冲突即可。所以输入我们将不投币、只投入 0.5 元、投入 1 元的情况分别编码为 00、01、10,;输出我们将不出可乐不找零、只出可乐、既出可乐又找零的情况(不存在只找零不出可乐的情况)分别编码为 00、10、11,下面就可以绘制状态转移图了。
- 有多个状态的时候需要进行组合编码
- 画状态图的小技巧:输入有多少种情况,每个状态的跳转就有多少种情况,这样根据输入来确定状态的跳转就能够保证我们不漏掉任何一种状态跳转。
2.3 波形图
2.4 RTL
module complex_fsm(
input wire sys_clk,
input wire sys_rst_n,
input wire pi_money_one,
input wire pi_money_half,
output reg po_money,
output reg po_cola
);
// 定义状态参数
parameter IDLE = 5'b00001;
parameter HALF = 5'b00010;
parameter ONE = 5'b00100;
parameter ONE_HALF = 5'b01000;
parameter TWO = 5'b10000;
// 定义中间变量
wire [1:0] pi_money;
reg [4:0] state;
// 变量赋值
assign pi_money = {pi_money_one, pi_money_half};
//第一段状态机,描述当前状态 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 == 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_HALF;
else
state <= HALF;
ONE : if(pi_money == 2'b01)
state <= ONE_HALF;
else if(pi_money == 2'b10)
state <= TWO;
else
state <= ONE;
ONE_HALF: if(pi_money == 2'b01)
state <= TWO;
else if(pi_money == 2'b10)
state <= IDLE;
else
state <= ONE_HALF;
TWO : if((pi_money == 2'b01) || (pi_money == 2'b10))
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 == 2'b01) || (state == TWO &&
pi_money == 2'b10) || (state == ONE_HALF && pi_money == 2'b10))
po_cola <= 1'b1;
else
po_cola <= 1'b0;
//第二段状态机,描述当前状态 state 和输入 pi_money 如何影响 po_money 输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_money <= 1'b0;
else if((state == TWO) && (pi_money == 2'b10))
po_money <= 1'b1;
else
po_money <= 1'b0;
endmodule
2.5 Testbench
`timescale 1ns/1ns
module tb_complex_fsm();
//reg define
reg sys_clk;
reg sys_rst_n;
reg pi_money_one;
reg pi_money_half;
reg random_data_gen;
//wire define
wire po_cola;
wire po_money;
//初始化系统时钟、全局复位
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;
//random_data_gen:产生非负随机数 0、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;
//pi_money_one:模拟投入 1 元的情况
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_money_one <= 1'b0;
else
pi_money_one <= random_data_gen;
//pi_money_half:模拟投入 0.5 元的情况
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_money_half <= 1'b0;
else
//取反是因为一次只能投一个币,即 pi_money_one 和 pi_money_half 不能同时为 1
pi_money_half <= ~random_data_gen;
//------------------------------------------------------------
//将 RTL 模块中的内部信号引入到 Testbench 模块中进行观察
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_one=%b pi_money_half=%b
pi_money=%b state=%b po_cola=%b po_money=%b", $time, pi_money_one,
pi_money_half, pi_money, state, po_cola, po_money);
end
complex_fsm complex_fsm_inst(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.pi_money_one (pi_money_one ), //input pi_money_one
.pi_money_half (pi_money_half ), //input pi_money_half
.po_cola (po_cola ), //output po_money
.po_money (po_money ) //output po_cola
);
endmodule