在Verilog中可以采用多种方法来描述有限状态机最常见的方法就是用always和case语句。如下图所示的状态转移图就表示了一个简单的有限状态机:
图中:图表示了一个四状态的状态机,输入为A和Reset,同步时钟为clk,输出信号是K1和K2,状态机只能在信号的上升沿发生。
(A)下面是可综合的Verilog模块设计状态机的典型方法:(格雷码表示状态)
module fsm(A,Reset,K2,K1,clk,state);
input A,Reset,clk;
output K2,K1;
output [:]state;
reg K2,K1;
reg [:]state;
parameter Idel='b00,
start='b01,
stop='b10,
clear='b11;
always @(posedge clk)
if (!Reset)
begin
state<=Idel;
K2<=;
K1<=;
end
else
case(state)
Idel:if (A)
begin
state<=start;
K1<=;
end
else
begin
state<=Idel;
K2<=;
K1<=;
end
start:if(!A) state<=stop;
else state<=start;
stop: if(A)
begin
state<=clear;
K2<=;
end
else
begin
state<=stop;
K2<=;
K1<=;
end
clear:if(!A)
begin
state<=Idel;
K2<=;
K1<=;
end
else
begin
state<=clear;
K2<=;
K1<=;
end
default:state<='bxx;
endcase
endmodule
(B)用可以综合的Verilog模块设计、用独热码表示状态的状态机
独热码,在英文文献中称做 one-hot code, 直观来说就是有多少个状态就有多少比特,而且只有一个比特为1,其他全为0的一种码制。
module fsm(A,Reset,K2,K1,clk,state);
input A,Reset,clk;
output K2,K1;
output [:]state;
reg K2,K1;
reg [:]state;
parameter Idel='b1000,
start='b0100,
stop='b0010,
clear='b0001;
always @(posedge clk)
if (!Reset)
begin
state<=Idel;
K2<=;
K1<=;
end
else
case(state)
Idel:if (A)
begin
state<=start;
K1<=;
end
else
begin
state<=Idel;
K2<=;
K1<=;
end
start:if(!A) state<=stop;
else state<=start;
stop: if(A)
begin
state<=clear;
K2<=;
end
else
begin
state<=stop;
K2<=;
K1<=;
end
clear:if(!A)
begin
state<=Idel;
K2<=;
K1<=;
end
else
begin
state<=clear;
K2<=;
K1<=;
end
default:state<=Idel;
endcase
endmodule
二进制编码、格雷码编码使用最少的触发器,消耗较多的组合逻辑,而独热码编码反之。独热码编码的最大优势在于状态比较时仅仅需要比较一个位,从而一定程度上简化了译码逻辑。虽然在需要表示同样的状态数时,独热编码占用较多的位,也就是消耗较多的触发器,但这些额外触发器占用的面积可与译码电路(组合电路)省下来的面积相抵消。并且采用独热码可以使电路的速度和可靠性有显著提高,而总的单元数并无增加。
(C)用可以综合的Verilog模块设计,用状态码直接作为输出
module fsm(A,Reset,K2,K1,clk,state);
input A,Reset,clk;
output K2,K1;
output [:]state;
reg [:]state;
assign K2=state[];
assign K1=state[];
parameter Idel = 'b0_000_0,
start = 'b0_010_0,
stop = 'b0_010_0,
stoptoclear='b1_100_0,
clear = 'b0_101_0,
cleartoIdel='b0_011_1;
always @(posedge clk)
if (!Reset)
begin
state<=Idel;
end
else
case(state)
Idel: if (A) state<=start;
else state<=Idel;
start:if(!A) state<=stop;
else state<=start;
stop: if(A) state<=stoptoclear;
else state<=stop;
stoptoclear: state<=clear;
clear:if(!A) state<=cleartoIdel;
else state<=clear;
cleartoIdel: state<=Idel;
default:state<=Idel;
endcase
endmodule
利用状态机的状态直接作为输出可以提升信号的开关速度并节省电路器件,但是开关的维持时间必须与状态的维持时间一致,如果要实现上述的例子必须增加状态才能实现。
在这里state[4],state[0]分别表示前面的K2和K1。
(D)用可以综合的Verilog模块设计,设计复杂多输出状态机时常用的方法
module fsm(A,Reset,K2,K1,clk,state);
input A,Reset,clk;
output K2,K1;
output [:]state;
reg K2,K1;
reg [:]state,nextstate;
parameter Idel = 'b00,
start = 'b01,
stop = 'b10,
clear = 'b11;
always @(posedge clk) //没一个时钟沿产生一次可能的变化
if (!Reset)
state<=Idel;
else
state<=nextstate;
always @(state or A) //产生下一状态的组合逻辑F
case(state)
Idel: if (A) nextstate=start;
else nextstate=Idel;
start:if(!A) nextstate=stop;
else nextstate=start;
stop: if(A) nextstate=clear;
else nextstate=stop;
clear:if(!A) nextstate=Idel;
else nextstate=clear;
default: nextstate='bxx;
endcase
always @(state or Reset or A) //产生输出K1的组合逻辑
if(!Reset) K1=;
else if(state==clear&&!A) K1=; //由clear转向Idel,因为state为非阻塞赋值,所以此时state的状态还是clear
else K1=;
always @(state or Reset or A) //产生输出K2的组合逻辑
if(!Reset) K2=;
else if(state==stop&&A) K2=; //由stop转向clear,因为state为非阻塞赋值,所以此时state的状态还是stop
else K2=;
endmodule
在比较复杂的状态机设计过程中,往往把状态的变化与输出开关的控制分为两部分,就像前面的Mealy型状态机输出部分的组合逻辑一样。为了调试方便还将每一个输出的开关写成一个个的独立的always块以方便调试。在设计复杂的多输出状态及时建议采用这种方法。
因为大多数的FPGA内部的触发器数目相当多,又加上独热码状态机(one hot code machine)的译码逻辑最为简单,所以在FPGA实现状态机时,往往采用独热码状态机(即每个状态只有一个寄存器置位的状态机)。建议采用case语句来建立状态机的模型,因为这些语句表达清晰明了,可以方便的由当前状态转向下一个状态并设置输出。记得:不要忘记在case语句的最后写上default分支,并将状态设置为'bx这就等于告诉综合器case语句已经指定了所有的状态。这样综合器就可以删除不必要的译码电路使生成的电路简洁。
如果将默认的状态设置为某一确定值的状态可不可以呢?
这样会导致,虽然综合器产生的逻辑和设置default:state=’bx;相同,但是状态机的Verilog模型综合前和综合后的仿真结果不一致。因为启动仿真器时,状态机的所有状态都不确定,因此立即进入default状态。这时便会导致开始的状态为state1,但是实际的硬件电路在通电以后,进入的状态是不确定的,很可能不是state1的状态,因此'bx更切合实际一些。但是再有多余状态的情况下,可以通过哦综合指令(高级教程)将默认状态设置为某一确定的有效状态,因为这样能够使得状态机在偶然进入多余状态后,仍能在下一时钟跳变沿返回正常工作状态,否则引起死锁。
目前大多数综合其往往不支持在一个always快中由多个事件触发的状态机(隐含状态机,implicit state machine),为了能综合出有效的电路,用Verilog描述的状态机应明确的由唯一时钟触发。如果比需要用到不同时钟触发的状态机,可以采用以下程序:编写另一个模块,并采用第二种时钟触发;然后用适中调用的方法再领一个模块中将它们连接起来。为了使设计更简单,调试更加容易,通常使得两个状态机的周期由一定的关系。
在Verilog中状态必须明确赋值,使用参数parameter和define都可以实现:
参数定义:parameter state1=2'0,state2=2'1;
...
current_state=state1;
宏定义:`define state1=2'b0,state2=2'b1;
....
current_state=`state2;