一、定义(大型工程的基石,每个状态代表着一个事件被触发)
共同的:两种状态机,状态跳转只与输入状态有关。?
不同的:在输出时,若最后的输出只和当前状态有关而与输入无关则是moore(木耳)状态机。若最后的输出不仅当前状态有关还与输入也有关则是mealy(米丽)状态机。?
状态转移:从上一个事件发生跳转下一个事件发生。
作用领域:状态机适合描述那些顺序发生,有时序规律的事件。
二、状态转移图(表明状态跳转的条件和先后过程,帮助编写代码逻辑)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 图例
简单例子:可乐机里的饮料3元一瓶。每次只能投一枚一元的硬币,投三个硬币吐出一瓶可乐。用状态机硬件语言实现其功能。
模块引脚设计图:
状态转移图绘制:? ( 1/0符号说明:1状态输入 /? 0?状态输出 )?
状态转移三要素:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 输入:?投入1元硬币
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 输出:吐出可乐、不吐出可乐
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 状态:投入0元时、投入1元时、投入2元时、投入3元时
状态转移三要素:
????????????????????输入:?投入1元硬币 ????????????????????输出:吐出可乐、不吐出可乐 ????????????????????状态:投入0元时、投入1元时、投入2元时、投入3元时
状态分析: ? ? ? ? ? ? ? ? ? ? ? ? ? ?* 投入0元时:1、没有投掷,即0/0自身状态不变。2、被投掷了(输入)一枚硬币。即1/0转到下一个1元的状态。 ? ? ? ? ? ? ? ? ? ? ? ? ? * 投入1元时:1、没有投掷,即0/0自身状态不变。2、被投掷了(输入)一枚硬币。即1/0转到下一个2元的状态。 ? ? ? ? ? ? ? ? ? ? ? ? ? * 投入2元时:1、没有投掷,即0/0自身状态不变。2、被投掷了(输入)一枚硬币。对于mealy型状态机(只当前状态、输入都有关),即此时已经3元硬币了,需要转到下一个0元状态,并且输出一瓶可投入3元时乐,即1/1。 对于moore型状态机(只和当前状态有关,和输入无关),则转向下一个3元状态,并且不吐出可乐,即1/0. ? ? ? ? ? ? ? ? ? ? ? ? ? * 投入3元时(仅moore型状态机):1、此时即使不投硬币也会出可乐,也要转移到下一个状态,因此本状态不存在独立的自环状态。2、需要转移到0元的状态。由于没有投币输入行为,有出可乐输出行为,因此为0/1。3、若在出可乐过程中,又被投入了一枚硬币,此时将要在输出可乐的同时,转移到1元状态,即1/1.
状态机选择:对于本题,由于mealy型状态机(左)比moore型状态机(右)需要的状态更少,代码编写更加方便,因此我们选择mealy型状态机。
三、简单状态机VHDL代码
? ? ? ? 代码时序逻辑波形图绘制(设高电平持续一个时钟周期表示投入了一枚硬币)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
代码编写:
状态机编码方法分为:?独热码(IDLE = 3'b001? ONE ?= 3'b010? TWO ?= 3'b100)、二进制编码(IDLE = 2'b00? ONE ?= 2'b10? TWO ?= 2'b11)、格雷码(IDLE = 3'b000? ONE ?= 3'b001? TWO ?= 3'b011)。? ? ? ?其中格雷码,是一种错误最小化的?编码方式,因为每个数字之间只有一个二进制位发生改变,且最大值和最小值是头尾相连的。在数字系统中,常要求代码按一定顺序变化。例如,按自然数递增计数,若采用8421码,则数0111变到1000时四位均要变化,而在实际电路中,4位的变化不可能绝对同时发生。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?格雷码示意
注意:本例使用独热码对状态进行编号。因为二进制编码的存储位数虽然最小,但是其占用的组合逻辑资源更多。由于独热码每个状态都单独占一位二进制,因此综合器会优化器组合逻辑资源至一位。而在芯片设计中组合逻辑资源往往比寄存器资源要小的多,因此我们使用独热码形式。
使用情况: ? ? ? ? ? ? 设状态个数为D。
? ? ? ? ? ? 当 D < 4,?二进制编码进行状态编码时会使用到联合比较器,可能存在时延稳定问题。 ? ? ? ? ? ? 当 D >= 24,状态较多时使用格雷码进行状态编码。 ? ? ? ? ? ? 当4 <= D < 24,状态较少时使用独热码进行状态编码。 ? ? ? ? ? ? 如果在高速器件中,无论状态数是多少,一般都使用独热码编码。
状态机代码编写形式:
? ? ? ? ? ? ?*一段式:?在一段代码中使用时序逻辑即?描述状态的转移,也描述状态的输出。可是这种形式不能描述较为复杂或者大型的状态机。
? ? ? ? ? ? ?*二段式:分两段描写状态机。第一段使用时序逻辑描述状态的转移,第二段使用组合逻辑描述状态的输出。但是面临状态输出时有计数的情况无法适用,因为组合逻辑不允许自加行为。另外,在状态输出时,若使用组合逻辑,会使得信号产生许多毛刺。
? ? ? ? ? ? ?*三段式:分三段描写状态机。第一段使用时序逻辑描述状态的转移,第二段使用组合逻辑判断状态转移条件、描述状态转移规律,第三段使用组合逻辑或者时序逻辑描述状态的输出。
? ? ? ? 上述三种方法是老的方法,编写时必须按照严格的格式践行编写,综合器才能识别其为状态机并生成电路。
? ? ? ? ? *新二段式:分两段描写状态机。第一段使用时序逻辑描述状态的转移和?状态转移条件,第二段同样使用时序逻辑描述状态的输出。这种形式可以直接根据转台转移图写出,并且几乎所有的综合器都能识别它是状态机,因此本例使用这种结构编写。
寄存器传输级文件:
`timescale 1ns/1ns
// Project Name : simple_fsm
// Target Devices: Altera EP4CE10F17C8
// Tool Versions : Quartus 13.0
// Description : 简单状态机
module simple_fsm
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire pi_money , //投币方式可以为:不投币(0)、投1元(1)
output reg po_cola //po_cola为1时出可乐,po_cola为0时不出可乐
);
/*态机编码方法分为:?
独热码(IDLE = 3'b001? ONE ?= 3'b010? TWO ?= 3'b100)
二进制编码(IDLE = 2'b00? ONE ?= 2'b10? TWO ?= 2'b11)
格雷码(IDLE = 3'b000? ONE ?= 3'b001? TWO ?= 3'b011)
其中格雷码,是一种错误最小化的?编码方式,因为每个数字之间只有一个二进制位发生改变,
且最大值和最小值是头尾相连的。在数字系统中,常要求代码按一定顺序变化。
例如,按自然数递增计数,若采用8421码,则数0111变到1000时四位均要变化,而在实际电路中,
4位的变化不可能绝对同时发生。
*/
/*
使用情况:
设状态个数为D。当 D < 4,?二进制编码进行状态编码时会使用到联合比较器,
可能存在时延稳定问题。
当 D >= 24,状态较多时使用格雷码进行状态编码。
当4 <= D < 24,状态较少时使用独热码进行状态编码。
如果在高速器件中,无论状态数是多少,一般都使用独热码编码。
注意:
本例使用独热码对状态进行编号。因为二进制编码的存储位数虽然最小,
但是其占用的组合逻辑资源更多。由于独热码每个状态都单独占一位二进制,
因此综合器会优化器组合逻辑资源(比较器)至一位。而在芯片设计中组合逻辑资源往往比
寄存器资源要小的多,因此我们使用独热码形式。
*/
//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)) // 因为状态转移图里2元到0元的链接是1/1
po_cola <= 1'b1;
else
po_cola <= 1'b0;
endmodule
仿真测试文件:
`timescale 1ns/1ns
// Module Name : tb_simple_fsm
// Project Name : simple_fsm
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : 简单状态机仿真文件
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 ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.pi_money (pi_money ), //input pi_money
.po_cola (po_cola ) //output po_cola
);
endmodule
modelsim仿真:
?
?? ? ??
四、进阶状态机VHDL代码
进阶例子:可乐机里的饮料2.5元一瓶。每次能投一枚1元的硬币或者一枚0.5元的硬币,可乐机里的前大于或者等于2.5元时吐出一瓶可乐,若可乐机里为3元则需要吐出一瓶可乐并且找补零钱0.5元。请用状态机硬件语言实现其功能。
模块引脚图:
时序逻辑分析图:
拽他转移示意图:
?
?* 其中 00/00?是指? ?"(00无投币、01投币0.5元、10投币1元) /? ?(00不出可乐不找零钱、 10只出可乐不找零钱、11又出可乐又找0.5元零钱)?"
寄存器传输级文件:
`timescale 1ns/1ns
// 复杂例子:可乐机里的饮料2.5元一瓶。每次能投一枚1元的硬币或者一枚0.5元的硬币,
// 可乐机里的前大于或者等于2.5元时吐出一瓶可乐。用状态机硬件语言实现其功能。
module complex_fsm(
input wire sys_clk,
input wire res_n,
input wire money_in_one,
input wire money_in_half,
output reg cola_out,
output reg half_out
);
// 状态机编码:独热码
localparam IDLE = 5'b00001; // 0
localparam HALF = 5'b00010; // 0.5
localparam ONE = 5'b00100; // 1
localparam ONE_HALF = 5'b01000; // 1.5
localparam TWO = 5'b10000; // 2
// 状态追踪变量
reg [4:0] state;
// 新二段式状态机
// 第一段:状态机转移以及触发条件
always@(posedge sys_clk or negedge res_n) begin
if(res_n == 1'b0)
state <= IDLE;
else begin
case(state)
IDLE : if(money_in_one == 1'b1) state <= ONE;
else if(money_in_half == 1'b1) state <= HALF;
else state <= IDLE;
HALF : if(money_in_one == 1'b1) state <= ONE_HALF;
else if(money_in_half == 1'b1) state <= ONE;
else state <= HALF;
ONE : if(money_in_one == 1'b1) state <= TWO;
else if(money_in_half == 1'b1) state <= ONE_HALF;
else state <= ONE;
ONE_HALF : if(money_in_one == 1'b1) state <= IDLE;
else if(money_in_half == 1'b1) state <= TWO;
else state <= ONE_HALF;
TWO : if(money_in_one == 1'b1) state <= IDLE;
else if(money_in_half == 1'b1) state <= IDLE;
else state <= TWO;
default: state <= IDLE;
endcase
end
end
// 第二段:状态输出行为判断、输出
always@(posedge sys_clk or negedge res_n) begin
if(res_n == 1'b0)
cola_out <= 1'b0;
else begin
if((state == TWO && money_in_half == 1'b1) ||
(state == TWO && money_in_one == 1'b1) ||
(state == ONE_HALF && money_in_one == 1'b1))
cola_out <= 1'b1;
else
cola_out <= 1'b0;
end
end
always@(posedge sys_clk or negedge res_n) begin
if(res_n == 1'b0)
half_out <= 1'b0;
else begin
if(state == TWO && money_in_one == 1'b1)
half_out <= 1'b1;
else
half_out <= 1'b0;
end
end
endmodule
?仿真测试文件:
`timescale 1ns/1ns
// Module Name : tb_complex_fsm
// Project Name : complex_fsm
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : 复杂状态机仿真文件
module tb_complex_fsm();
//reg define
reg sys_clk;
reg res_n;
reg money_in_one;
reg money_in_half;
reg random_data_gen;
//wire define
wire cola_out;
wire half_out;
//初始化系统时钟、全局复位
initial begin
sys_clk = 1'b1;
res_n <= 1'b0;
#20
res_n <= 1'b1;
end
//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
always #10 sys_clk = ~sys_clk;
// 注意:由于money_in_one与money_in_half是互斥的,不能同时为1,因此我们需要另一个信号来同时约束它们输出
//random_data_gen:产生非负随机数0、1
always@(posedge sys_clk or negedge res_n)
if(res_n == 1'b0)
random_data_gen <= 1'b0;
else
random_data_gen <= {$random} % 2;
//money_in_one:产生输入随机数,模拟投币1元的情况
always@(posedge sys_clk or negedge res_n)
if(res_n == 1'b0 || money_in_half == 1'b1)
money_in_one <= 1'b0;
else
money_in_one <= random_data_gen; //取模求余数,产生非负随机数0、1
//money_in_half:产生输入随机数,模拟投币0.5元的情况
always@(posedge sys_clk or negedge res_n)
if(res_n == 1'b0)
money_in_half <= 1'b0;
else
money_in_half <= ~random_data_gen;
//------------------------------------------------------------
//*******将RTL模块中的内部信号引入到Testbench模块中进行观察*******模块内部过程变量监视方法
wire [4:0] state = tb_complex_fsm_inst.state; // 注意这里的父级引用的是测试生成的新对象文件
initial begin
$timeformat(-9, 0, "ns", 6);
$monitor("@time %t: money_one=%b money_half=%b state=%b cola_out=%b half_out=%b",
$time, money_in_one, money_in_half, state, cola_out, half_out);
end
//------------------------simple_fsm_inst------------------------
complex_fsm tb_complex_fsm_inst(
.sys_clk (sys_clk ), //input sys_clk
.res_n (res_n ), //input sys_rst_n
.money_in_one (money_in_one ), //input money_in_one
.money_in_half (money_in_half ), //input money_in_half
.cola_out (cola_out ), //output cola_out
.half_out (half_out ) //output half_out
);
endmodule
本教程感谢野火官方教程的支持。
|