软件版本:无
操作系统:WIN10 64bit
硬件平台:适用所有系列FPGA
板卡获取平台:https://milianke.tmall.com/
登录"米联客"FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述
让FPGA电路,有序执行任务,我们需要状态机电路。所有的时序电路都是随着时间的流逝发生各种状态,在FPGA中,基于时序编写状态机,可以完成几乎所有的逻辑控制任务。状态机就是用来管理电路的逻辑状态。如果你学习过C语言,你可能主要用switch case进行条件判断,但是学习了FPGA的状态机后,你可以考虑下改进下你的C语言设计,利用C语言的switch case来实现状态机,对于很多基于时间的逻辑类控制,状态机是非常好的管理方案,适合于一切的编程。
状态机的描述通常有三种方:一段式状态机、二段式状态机、三段式状态机,当然你可以写出四段式,五段式,但是一般来说,常用的不超过三段式。如果选用是一段、二段还是三段根据实际情况,一般原则,很简单的状态机一段式搞定,稍微复杂的用二段式,很复杂的用三段式。大部分情况我们都是面对的简单的状态机。
2一段式状态机
cpp
module detect_1(
input clk_i,
input rst_n_i,
output out_o
);
reg out_r;
//状态声明和状态编码
reg [1:0] state;
parameter [1:0] S0=2'b00;
parameter [1:0] S1=2'b01;
parameter [1:0] S2=2'b10;
parameter [1:0] S3=2'b11;
always@(posedge clk_i)
begin
if(!rst_n_i)begin
state<=0;
out_r<=1'b0;
end
else
case(state)
S0 :
begin
out_r<=1'b0;
state<= S1;
end
S1 :
begin
out_r<=1'b1;
state<= S2;
end
S2 :
begin
out_r<=1'b0;
state<= S3;
end
S3 :
begin
out_r<=1'b1;
end
endcase
end
assign out_o=out_r;
endmodule
通常来说一段式状态机是不推荐使用的,但是实际情况是对于简单的代码没必要严格去比较采用一段式或者二段式状态机,而且一段式的状态机,写起来比较顺手,所以规模不大的简单状态机放心用吧。
3两段式状态机
cpp
module detect_2(
input clk_i,
input rst_n_i,
output out_o
);
reg out_r;
//状态声明和状态编码
reg [1:0] Current_state;
reg [1:0] Next_state;
parameter [1:0] S0=2'b00;
parameter [1:0] S1=2'b01;
parameter [1:0] S2=2'b10;
parameter [1:0] S3=2'b11;
//时序逻辑:描述状态转换
always@(posedge clk_i)
begin
if(!rst_n_i)
Current_state<=0;
else
Current_state<=Next_state;
end
//组合逻辑:描述下一状态和输出
always@(*)
begin
case(Current_state)
S0 :
begin
out_r=1'b0;
Next_state= S1;
end
S1 :
begin
out_r=1'b1;
Next_state= S2;
end
S2 :
begin
out_r=1'b0;
Next_state= S3;
end
S3 :
begin
out_r=1'b1;
Next_state=Next_state;
end
endcase
end
assign out_o=out_r;
endmodule
两段式状态机采用两个always模块实现状态机的功能,其中一个always采用同步时序逻辑描述状态转移,另一个always采用组合逻辑来判断状态条件转移。虽然对于简单的代码我们没必要去比较一段式好还是两段式好,但是如果推荐肯定还是使用两段式的状态机,不过很多时候笔者也偷懒,使用一段式的状态机。但是如果对于时序要求和状态机机有一定的规模的电路,还是用两段式的吧。
代码翻译的电路原理图:
总之两段式状态机是推荐的状态机设计方法。
4三段式状态机
cpp
module detect_3(
input clk_i,
input rst_n_i,
output out_o
);
reg out_r;
//状态声明和状态编码
reg [1:0] Current_state;
reg [1:0] Next_state;
parameter [1:0] S0=2'b00;
parameter [1:0] S1=2'b01;
parameter [1:0] S2=2'b10;
parameter [1:0] S3=2'b11;
//时序逻辑:描述状态转换
always@(posedge clk_i)
begin
if(!rst_n_i)
Current_state<=0;
else
Current_state<=Next_state;
end
//组合逻辑:描述下一状态
always@(*)
begin
case(Current_state)
S0:
Next_state = S1;
S1:
Next_state = S2;
S2:
Next_state = S3;
S3:
Next_state = Next_state;
default :
Next_state = S0;
endcase
end
//输出逻辑:让输出out,经过寄存器out_r锁存后输出,消除毛刺
always@(*)
begin
case(Current_state)
S0,S2:
out_r<=1'b0;
S1,S3:
out_r<=1'b1;
default :
out_r<=out_r;
endcase
end assign out_o=out_r;
三段式状态机在第一个always模块采用同步时序逻辑方式描述状态转移,第二个always模块采用组合逻辑方式描述状态转移规律,第三个always描述电路的输出。通常让输出信号经过寄存器缓存之后再输出,消除电路毛刺。这种状态机也是比较推崇的,主要是由于维护方便,组合逻辑与时序逻辑完全独立。
代码翻译的电路原理图:
5总结
通过对比三种状态机的写法,可以发现,三段式的状态机翻译出来的原理图是最简洁最高效的编写方式,两段式也是很简洁的,但是比三段式差了一点点,而一段式是最差的表现。所以推荐是使用两段式以上的状态机。